Version 419
This commit is contained in:
commit
193cfce4ad
|
@ -8,6 +8,49 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 419</h3></li>
|
||||
<ul>
|
||||
<li>tag lists and editing predicates:</li>
|
||||
<li>you can now set the default value for any editable system predicate. a star button beside each panel lets you set or clear the custom default</li>
|
||||
<li>all editable system predicate panels now put 'recent' predicate buttons up top, for the five most entered predicates of the respective types. this is a little jank and grows pretty tall with multi-pred-type panels, but let me know what you think</li>
|
||||
<li>all tag lists now support drag-selection!</li>
|
||||
<li>taglists now have 'open a new OR page' menu entry when more than one tag is selected</li>
|
||||
<li>when taglists can change the current search, they now have an 'add an OR to current search' menu entry when more than one tag is selected</li>
|
||||
<li>OR Predicates are now editable! they launch their own little autocomplete input that is a little jank because you can technically make nested ORs, but it works!</li>
|
||||
<li>system:rating is now editable! it launches the whole stack every time. the stack alignment is messed up though :/</li>
|
||||
<li>invertible predicates (inbox/archive, tag/-tag, etc...) now flip on double-click only if you have one selected. if you have more than one selected, they appear as invertible buttons along with the rest of the edit UI</li>
|
||||
<li>the active search predicates taglist now has an 'edit search terms' menu entry, if you find shift+double-click a pain</li>
|
||||
<li>when you shift+double-click on more than one tag to add them to the current search, this is now added as an OR</li>
|
||||
<li>similarly if you shift+middle-click on more than one tag, the new page is now an OR</li>
|
||||
<li>when editing predicates, edited predicates now stay selected</li>
|
||||
<li>shift+clicking on an already selected tag no longer adds any new selections (i.e. shift+click filling-in). this should make it nicer to do shift+double-click on selections. furthermore, the 'last clicked' focus ghost (from which a shift+click selection cascade starts) on tag lists is now cleared on edits or removes, which should reduce some other crazy/annoying select behaviour here</li>
|
||||
<li>the list of active search predicates now correctly initialises sorted</li>
|
||||
<li>entering hex hashes into system:hash or :similar_to now has unified hash parsing, auto-removes 'md5:'-style prefixes, and presents detailed error information when a hash is too long or short</li>
|
||||
<li>.</li>
|
||||
<li>faster and snappier file and tag searching:</li>
|
||||
<li>searching for files by complicated wildcard (i.e. a search phrase that includes an asterisk in a non-rightmost character position) is now greatly optimised when the tag does not start with an asterisk (e.g. 'sm*l' is now much faster, '*all' is still hellmode), and now cancels (due to hitting the stop button or changing the query before results come in) much faster thanks to a new unified results fetching and cancel-checking routine</li>
|
||||
<li>rewrote my autocomplete tag search to use the new namespace and subtag lookup code from the virtual siblings and parents system, unifying lookup logic and benefitting from the same new complicated wildcard optimisation and fast-cancel tech</li>
|
||||
<li>autocomplete tag count aggregation (a later step, after the initial lookup) benefits from a little faster cancel tech</li>
|
||||
<li>all file queries based on tag, wildcard, namespace, tag count, and tag existence now use the new fast-cancel tech. if you put in a 'has >4 tags' query and it is taking ages, changing the query or just hitting the 'stop' button should now free up the db pretty fast</li>
|
||||
<li>related tags suggestions also gets the cancel tech and is now more timing precise for tags with either huge or tiny count</li>
|
||||
<li>.</li>
|
||||
<li>client api:</li>
|
||||
<li>the /get_files/file_metadata call now returns a service_names_to_statuses_to_displayed_tags structure, which reflects the sibling-collapsed and parent-added tags, as displayed to the user in UI. the help is updated to reflect this</li>
|
||||
<li>the client api version is now 15</li>
|
||||
<li>.</li>
|
||||
<li>the rest:</li>
|
||||
<li>fixed an issue where regenerating the tag definition search cache would not tidy up the 'I am busy' modal dialog once it was done, resulting in a soft lock</li>
|
||||
<li>fixed another upnp error handling bug, this time in the upnp daemon</li>
|
||||
<li>updated Qt to 5.15.2 on Windows and Linux builds. this should fix the unusual button clicking area problem for some custom styles</li>
|
||||
<li>.</li>
|
||||
<li>boring specific code changes:</li>
|
||||
<li>wrote widgets to edit invertible preds and OR preds</li>
|
||||
<li>pulled the messy rating code out of the rating system predicate ui code to their own widgets</li>
|
||||
<li>wrote some special predicate ui definitions and initialisation handling for OR preds and grouped 'multiple' preds (for ratings)</li>
|
||||
<li>refactored search and predicate ui code to a new 'search' module</li>
|
||||
<li>refactored collect and sort widgets away from search code</li>
|
||||
<li>misc layout improvements for system pred edit ui</li>
|
||||
</ul>
|
||||
<li><h3>version 418</h3></li>
|
||||
<ul>
|
||||
<li>almost all system predicates are now editable if you shift+double-click them! you can also edit several at once in the same dialog</li>
|
||||
|
|
|
@ -1058,6 +1058,7 @@
|
|||
"is_trashed" : false,
|
||||
"known_urls" : [],
|
||||
"service_names_to_statuses_to_tags" : {}
|
||||
"service_names_to_statuses_to_display_tags" : {}
|
||||
},
|
||||
{
|
||||
"file_id" : 4567,
|
||||
|
@ -1085,9 +1086,19 @@
|
|||
"2" : [ "process this later" ]
|
||||
},
|
||||
"my tag repository" : {
|
||||
"0" : [ "blonde hair", "blue eyes", "looking at viewer" ]
|
||||
"0" : [ "blonde_hair", "blue_eyes", "looking_at_viewer" ]
|
||||
"1" : [ "bodysuit" ]
|
||||
}
|
||||
},
|
||||
"service_names_to_statuses_to_display_tags" : {
|
||||
"my tags" : {
|
||||
"0" : [ "favourites" ]
|
||||
"2" : [ "process this later", "processing" ]
|
||||
},
|
||||
"my tag repository" : {
|
||||
"0" : [ "blonde hair", "blue eyes", "looking at viewer" ]
|
||||
"1" : [ "bodysuit", "clothing" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -1113,7 +1124,7 @@
|
|||
</ul>
|
||||
</li>
|
||||
<p>Size is in bytes. Duration is in milliseconds, and may be an int or a float.</p>
|
||||
<p>The service_names_to_statuses_to_tags structure is similar to the /add_tags/add_tags scheme, excepting that the status numbers are:</p>
|
||||
<p>The service_names_to_statuses_to_tags structures are similar to the /add_tags/add_tags scheme, excepting that the status numbers are:</p>
|
||||
<ul>
|
||||
<li>0 - current</li>
|
||||
<li>1 - pending</li>
|
||||
|
@ -1121,6 +1132,7 @@
|
|||
<li>3 - petitioned</li>
|
||||
</ul>
|
||||
<p>Note that since JSON Object keys must be strings, these status numbers are strings, not ints.</p>
|
||||
<p>While service_names_to_statuses_to_tags represents the actual tags stored on the database for a file, the service_names_to_statuses_to_display_tags structure reflects how tags appear in the UI, after siblings are collapsed and parents are added. If you want to edit a file's tags, use service_names_to_statuses_to_tags. If you want to render to the user, use service_names_to_statuses_to_displayed_tags.</p>
|
||||
<p>If you add detailed_url_information=true, a new entry, 'detailed_known_urls', will be added for each file, with a list of the same structure as /add_urls/get_url_info. This may be an expensive request if you are querying thousands of files at once.</p>
|
||||
<p>For example:</p>
|
||||
<ul>
|
||||
|
|
|
@ -8,7 +8,6 @@ import psutil
|
|||
import random
|
||||
import re
|
||||
import sqlite3
|
||||
import statistics
|
||||
import time
|
||||
import traceback
|
||||
import typing
|
||||
|
@ -7004,7 +7003,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
return ( current_count, pending_count )
|
||||
|
||||
|
||||
def _GetAutocompleteCounts( self, tag_display_type, tag_service_id, file_service_id, tag_ids, include_current, include_pending ):
|
||||
def _GetAutocompleteCounts( self, tag_display_type, tag_service_id, file_service_id, tag_ids, include_current, include_pending, job_key = None ):
|
||||
|
||||
if tag_service_id == self._combined_tag_service_id and file_service_id == self._combined_file_service_id:
|
||||
|
||||
|
@ -7032,6 +7031,11 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
for search_tag_service_id in search_tag_service_ids:
|
||||
|
||||
if job_key is not None and job_key.IsCancelled():
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
cache_results.extend( self._CacheSpecificMappingsGetAutocompleteCounts( tag_display_type, file_service_id, search_tag_service_id, tag_ids ) )
|
||||
|
||||
|
||||
|
@ -7164,166 +7168,64 @@ class DB( HydrusDB.HydrusDB ):
|
|||
return set()
|
||||
|
||||
|
||||
table_join = 'tags'
|
||||
predicates = []
|
||||
parameters = []
|
||||
|
||||
if exact_match:
|
||||
|
||||
table_join = 'subtags_searchable_map NATURAL JOIN tags'
|
||||
|
||||
searchable_subtag = half_complete_searchable_subtag
|
||||
|
||||
if self._SubtagExists( searchable_subtag ):
|
||||
|
||||
searchable_subtag_id = self._GetSubtagId( searchable_subtag )
|
||||
|
||||
predicates.append( 'searchable_subtag_id = ?' )
|
||||
parameters.append( searchable_subtag_id )
|
||||
|
||||
else:
|
||||
if not self._SubtagExists( searchable_subtag ):
|
||||
|
||||
return set()
|
||||
|
||||
|
||||
if namespace != '':
|
||||
searchable_subtag_id = self._GetSubtagId( searchable_subtag )
|
||||
|
||||
if namespace == '':
|
||||
|
||||
if self._NamespaceExists( namespace ):
|
||||
|
||||
namespace_id = self._GetNamespaceId( namespace )
|
||||
|
||||
predicates.append( 'namespace_id = ?' )
|
||||
parameters.append( namespace_id )
|
||||
|
||||
else:
|
||||
tag_ids = self._STS( self._c.execute( 'SELECT tag_id FROM subtags_searchable_map NATURAL JOIN tags WHERE searchable_subtag_id = ?;', ( searchable_subtag_id, ) ) )
|
||||
|
||||
else:
|
||||
|
||||
if not self._NamespaceExists( namespace ):
|
||||
|
||||
return set()
|
||||
|
||||
|
||||
namespace_id = self._GetNamespaceId( namespace )
|
||||
|
||||
# force the smaller of the two to go first
|
||||
tag_ids = self._STS( self._c.execute( 'SELECT tag_id FROM subtags_searchable_map CROSS JOIN tags USING ( subtag_id ) WHERE searchable_subtag_id = ? AND namespace_id = ?;', ( searchable_subtag_id, namespace_id ) ) )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
# Note: you'll have trouble doing MATCH with other OR predicates. namespace LIKE "blah" OR subtag MATCH "blah" is trouble
|
||||
# AND is OK
|
||||
subtag_ids = self._GetSubtagIdsFromWildcard( half_complete_searchable_subtag, job_key = job_key )
|
||||
|
||||
def GetSubTagSearchInfo( half_complete_searchable_subtag ):
|
||||
if namespace in ( '', '*' ):
|
||||
|
||||
# complicated queries are passed to LIKE, because MATCH only supports appended wildcards 'gun*', and not complex stuff like '*gun*'
|
||||
|
||||
if ClientSearch.IsComplexWildcard( half_complete_searchable_subtag ):
|
||||
|
||||
# we search the 'searchable subtags', then link the various mappings back to real tags
|
||||
|
||||
like_param = ConvertWildcardToSQLiteLikeParameter( half_complete_searchable_subtag )
|
||||
|
||||
t_j = ', subtags_searchable_map, subtags ON ( tags.subtag_id = subtags_searchable_map.subtag_id AND subtags_searchable_map.searchable_subtag_id = subtags.subtag_id )'
|
||||
pred = 'subtag LIKE ?'
|
||||
param = like_param
|
||||
|
||||
else:
|
||||
|
||||
# simple 'sam*' style subtag, so we can search fts4 no prob
|
||||
|
||||
subtags_fts4_param = '"{}"'.format( half_complete_searchable_subtag )
|
||||
|
||||
t_j = ', subtags_fts4 ON ( subtag_id = docid )'
|
||||
pred = 'subtag MATCH ?'
|
||||
param = subtags_fts4_param
|
||||
|
||||
|
||||
return ( t_j, pred, param )
|
||||
|
||||
|
||||
if namespace != '':
|
||||
|
||||
if namespace == '*':
|
||||
|
||||
if tag_service_id != self._combined_tag_service_id:
|
||||
|
||||
combined_ac_cache_table_name = GenerateCombinedFilesMappingsACCacheTableName( tag_display_type, tag_service_id )
|
||||
|
||||
table_join = '{} NATURAL JOIN tags'.format( combined_ac_cache_table_name )
|
||||
|
||||
|
||||
predicates.append( '1=1' )
|
||||
|
||||
elif '*' in namespace:
|
||||
|
||||
table_join = 'tags NATURAL JOIN namespaces'
|
||||
|
||||
like_param = ConvertWildcardToSQLiteLikeParameter( namespace )
|
||||
|
||||
predicates.append( 'namespace LIKE ?' )
|
||||
|
||||
parameters.append( like_param )
|
||||
|
||||
else:
|
||||
|
||||
result = self._c.execute( 'SELECT namespace_id FROM namespaces WHERE namespace = ?;', ( namespace, ) ).fetchone()
|
||||
|
||||
if result is None:
|
||||
|
||||
return set()
|
||||
|
||||
else:
|
||||
|
||||
( namespace_id, ) = result
|
||||
|
||||
predicates.append( 'namespace_id = ' + str( namespace_id ) )
|
||||
|
||||
|
||||
|
||||
|
||||
if half_complete_searchable_subtag == '*':
|
||||
|
||||
if tag_service_id != self._combined_tag_service_id:
|
||||
|
||||
combined_ac_cache_table_name = GenerateCombinedFilesMappingsACCacheTableName( tag_display_type, tag_service_id )
|
||||
|
||||
table_join = '{} NATURAL JOIN {}'.format( table_join, combined_ac_cache_table_name )
|
||||
|
||||
|
||||
predicates.append( '1=1' )
|
||||
tag_ids = self._GetTagIdsFromSubtagIds( subtag_ids, job_key = job_key )
|
||||
|
||||
else:
|
||||
|
||||
( t_j, pred, param ) = GetSubTagSearchInfo( half_complete_searchable_subtag )
|
||||
if '*' in namespace:
|
||||
|
||||
namespace_ids = self._GetNamespaceIdsFromWildcard( namespace )
|
||||
|
||||
else:
|
||||
|
||||
if not self._NamespaceExists( namespace ):
|
||||
|
||||
return set()
|
||||
|
||||
|
||||
namespace_ids = ( self._GetNamespaceId( namespace ), )
|
||||
|
||||
|
||||
table_join += t_j
|
||||
predicates.append( pred )
|
||||
parameters.append( param )
|
||||
tag_ids = self._GetTagIdsFromNamespaceIdsSubtagIds( namespace_ids, subtag_ids, job_key = job_key )
|
||||
|
||||
|
||||
|
||||
predicates_phrase = ' AND '.join( predicates )
|
||||
|
||||
tag_ids = set()
|
||||
|
||||
query = 'SELECT DISTINCT tag_id FROM {} WHERE {};'.format( table_join, predicates_phrase )
|
||||
|
||||
if len( parameters ) > 0:
|
||||
|
||||
cursor = self._c.execute( query, parameters )
|
||||
|
||||
else:
|
||||
|
||||
cursor = self._c.execute( query )
|
||||
|
||||
|
||||
group_of_tag_id_tuples = cursor.fetchmany( 1000 )
|
||||
|
||||
while len( group_of_tag_id_tuples ) > 0:
|
||||
|
||||
if job_key is not None and job_key.IsCancelled():
|
||||
|
||||
return set()
|
||||
|
||||
|
||||
tag_ids.update( ( tag_id for ( tag_id, ) in group_of_tag_id_tuples ) )
|
||||
|
||||
group_of_tag_id_tuples = cursor.fetchmany( 1000 )
|
||||
|
||||
|
||||
# now fetch siblings, add to set
|
||||
|
||||
final_tag_ids = set( tag_ids )
|
||||
|
@ -7395,7 +7297,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
return []
|
||||
|
||||
|
||||
ids_to_count = self._GetAutocompleteCounts( tag_display_type, tag_service_id, file_service_id, group_of_tag_ids, include_current, include_pending )
|
||||
ids_to_count = self._GetAutocompleteCounts( tag_display_type, tag_service_id, file_service_id, group_of_tag_ids, include_current, include_pending, job_key = job_key )
|
||||
|
||||
if len( ids_to_count ) == 0:
|
||||
|
||||
|
@ -8012,11 +7914,11 @@ class DB( HydrusDB.HydrusDB ):
|
|||
return hash_ids
|
||||
|
||||
|
||||
def _GetHashIdsFromNamespaceIdsSubtagIds( self, tag_display_type: int, file_service_key, tag_search_context: ClientSearch.TagSearchContext, namespace_ids, subtag_ids, hash_ids = None, hash_ids_table_name = None ):
|
||||
def _GetHashIdsFromNamespaceIdsSubtagIds( self, tag_display_type: int, file_service_key, tag_search_context: ClientSearch.TagSearchContext, namespace_ids, subtag_ids, hash_ids = None, hash_ids_table_name = None, job_key = None ):
|
||||
|
||||
tag_ids = self._GetTagIdsFromNamespaceIdsSubtagIds( namespace_ids, subtag_ids )
|
||||
tag_ids = self._GetTagIdsFromNamespaceIdsSubtagIds( namespace_ids, subtag_ids, job_key = job_key )
|
||||
|
||||
return self._GetHashIdsFromTagIds( tag_display_type, file_service_key, tag_search_context, tag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name )
|
||||
return self._GetHashIdsFromTagIds( tag_display_type, file_service_key, tag_search_context, tag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name, job_key = job_key )
|
||||
|
||||
|
||||
def _GetHashIdsFromNoteName( self, name: str, hash_ids_table_name: str ):
|
||||
|
@ -8570,11 +8472,11 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
if query_hash_ids is None:
|
||||
|
||||
tag_query_hash_ids = self._GetHashIdsFromTag( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, tag )
|
||||
tag_query_hash_ids = self._GetHashIdsFromTag( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, tag, job_key = job_key )
|
||||
|
||||
elif is_inbox and len( query_hash_ids ) == len( self._inbox_hash_ids ):
|
||||
|
||||
tag_query_hash_ids = self._GetHashIdsFromTag( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, tag, hash_ids = self._inbox_hash_ids, hash_ids_table_name = 'file_inbox' )
|
||||
tag_query_hash_ids = self._GetHashIdsFromTag( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, tag, hash_ids = self._inbox_hash_ids, hash_ids_table_name = 'file_inbox', job_key = job_key )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -8582,7 +8484,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
self._AnalyzeTempTable( temp_table_name )
|
||||
|
||||
tag_query_hash_ids = self._GetHashIdsFromTag( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, tag, hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name )
|
||||
tag_query_hash_ids = self._GetHashIdsFromTag( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, tag, hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name, job_key = job_key )
|
||||
|
||||
|
||||
|
||||
|
@ -8626,7 +8528,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
if query_hash_ids is None:
|
||||
|
||||
wildcard_query_hash_ids = self._GetHashIdsFromWildcard( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, wildcard )
|
||||
wildcard_query_hash_ids = self._GetHashIdsFromWildcard( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, wildcard, job_key = job_key )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -8634,7 +8536,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
self._AnalyzeTempTable( temp_table_name )
|
||||
|
||||
wildcard_query_hash_ids = self._GetHashIdsFromWildcard( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, wildcard, hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name )
|
||||
wildcard_query_hash_ids = self._GetHashIdsFromWildcard( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, wildcard, hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name, job_key = job_key )
|
||||
|
||||
|
||||
|
||||
|
@ -8771,7 +8673,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
self._AnalyzeTempTable( temp_table_name )
|
||||
|
||||
unwanted_hash_ids = self._GetHashIdsFromTag( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, tag, hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name )
|
||||
unwanted_hash_ids = self._GetHashIdsFromTag( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, tag, hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name, job_key = job_key )
|
||||
|
||||
query_hash_ids.difference_update( unwanted_hash_ids )
|
||||
|
||||
|
@ -8805,7 +8707,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
self._AnalyzeTempTable( temp_table_name )
|
||||
|
||||
unwanted_hash_ids = self._GetHashIdsFromWildcard( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, wildcard, hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name )
|
||||
unwanted_hash_ids = self._GetHashIdsFromWildcard( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, wildcard, hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name, job_key = job_key )
|
||||
|
||||
query_hash_ids.difference_update( unwanted_hash_ids )
|
||||
|
||||
|
@ -9126,7 +9028,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
self._AnalyzeTempTable( temp_table_name )
|
||||
|
||||
good_hash_ids = self._GetHashIdsThatHaveTagAsNum( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, namespace, num, '>', hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name )
|
||||
good_hash_ids = self._GetHashIdsThatHaveTagAsNum( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, namespace, num, '>', hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name, job_key = job_key )
|
||||
|
||||
|
||||
query_hash_ids = intersection_update_qhi( query_hash_ids, good_hash_ids )
|
||||
|
@ -9140,7 +9042,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
self._AnalyzeTempTable( temp_table_name )
|
||||
|
||||
good_hash_ids = self._GetHashIdsThatHaveTagAsNum( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, namespace, num, '<', hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name )
|
||||
good_hash_ids = self._GetHashIdsThatHaveTagAsNum( ClientTags.TAG_DISPLAY_ACTUAL, file_service_key, tag_search_context, namespace, num, '<', hash_ids = query_hash_ids, hash_ids_table_name = temp_table_name, job_key = job_key )
|
||||
|
||||
|
||||
query_hash_ids = intersection_update_qhi( query_hash_ids, good_hash_ids )
|
||||
|
@ -9190,14 +9092,14 @@ class DB( HydrusDB.HydrusDB ):
|
|||
return query_hash_ids
|
||||
|
||||
|
||||
def _GetHashIdsFromSubtagIds( self, tag_display_type: int, file_service_key, tag_search_context: ClientSearch.TagSearchContext, subtag_ids, hash_ids = None, hash_ids_table_name = None ):
|
||||
def _GetHashIdsFromSubtagIds( self, tag_display_type: int, file_service_key, tag_search_context: ClientSearch.TagSearchContext, subtag_ids, hash_ids = None, hash_ids_table_name = None, job_key = None ):
|
||||
|
||||
tag_ids = self._GetTagIdsFromSubtagIds( subtag_ids )
|
||||
tag_ids = self._GetTagIdsFromSubtagIds( subtag_ids, job_key = job_key )
|
||||
|
||||
return self._GetHashIdsFromTagIds( tag_display_type, file_service_key, tag_search_context, tag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name )
|
||||
return self._GetHashIdsFromTagIds( tag_display_type, file_service_key, tag_search_context, tag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name, job_key = job_key )
|
||||
|
||||
|
||||
def _GetHashIdsFromTag( self, tag_display_type: int, file_service_key, tag_search_context: ClientSearch.TagSearchContext, tag, hash_ids = None, hash_ids_table_name = None, allow_unnamespaced_to_fetch_namespaced = True ):
|
||||
def _GetHashIdsFromTag( self, tag_display_type: int, file_service_key, tag_search_context: ClientSearch.TagSearchContext, tag, hash_ids = None, hash_ids_table_name = None, allow_unnamespaced_to_fetch_namespaced = True, job_key = None ):
|
||||
|
||||
( namespace, subtag ) = HydrusTags.SplitTag( tag )
|
||||
|
||||
|
@ -9224,10 +9126,10 @@ class DB( HydrusDB.HydrusDB ):
|
|||
tag_ids = ( tag_id, )
|
||||
|
||||
|
||||
return self._GetHashIdsFromTagIds( tag_display_type, file_service_key, tag_search_context, tag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name )
|
||||
return self._GetHashIdsFromTagIds( tag_display_type, file_service_key, tag_search_context, tag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name, job_key = job_key )
|
||||
|
||||
|
||||
def _GetHashIdsFromTagIds( self, tag_display_type: int, file_service_key: bytes, tag_search_context: ClientSearch.TagSearchContext, tag_ids: typing.Collection[ int ], hash_ids = None, hash_ids_table_name = None ):
|
||||
def _GetHashIdsFromTagIds( self, tag_display_type: int, file_service_key: bytes, tag_search_context: ClientSearch.TagSearchContext, tag_ids: typing.Collection[ int ], hash_ids = None, hash_ids_table_name = None, job_key = None ):
|
||||
|
||||
do_hash_table_join = False
|
||||
|
||||
|
@ -9250,6 +9152,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
table_names = self._GetMappingTables( tag_display_type, file_service_key, tag_search_context )
|
||||
|
||||
cancelled_hook = None
|
||||
|
||||
if job_key is not None:
|
||||
|
||||
cancelled_hook = job_key.IsCancelled
|
||||
|
||||
|
||||
if len( tag_ids ) == 1:
|
||||
|
||||
( tag_id, ) = tag_ids
|
||||
|
@ -9266,7 +9175,9 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
for query in queries:
|
||||
|
||||
result_hash_ids.update( self._STI( self._c.execute( query, ( tag_id, ) ) ) )
|
||||
cursor = self._c.execute( query, ( tag_id, ) )
|
||||
|
||||
result_hash_ids.update( self._STI( HydrusDB.ReadFromCancellableCursor( cursor, 1024, cancelled_hook ) ) )
|
||||
|
||||
|
||||
else:
|
||||
|
@ -9286,7 +9197,9 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
for query in queries:
|
||||
|
||||
result_hash_ids.update( self._STI( self._c.execute( query ) ) )
|
||||
cursor = self._c.execute( query )
|
||||
|
||||
result_hash_ids.update( self._STI( HydrusDB.ReadFromCancellableCursor( cursor, 1024, cancelled_hook ) ) )
|
||||
|
||||
|
||||
|
||||
|
@ -9412,21 +9325,21 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
def _GetHashIdsFromWildcard( self, tag_display_type: int, file_service_key, tag_search_context: ClientSearch.TagSearchContext, wildcard, hash_ids = None, hash_ids_table_name = None ):
|
||||
def _GetHashIdsFromWildcard( self, tag_display_type: int, file_service_key, tag_search_context: ClientSearch.TagSearchContext, wildcard, hash_ids = None, hash_ids_table_name = None, job_key = None ):
|
||||
|
||||
( namespace_wildcard, subtag_wildcard ) = HydrusTags.SplitTag( wildcard )
|
||||
|
||||
possible_subtag_ids = self._GetSubtagIdsFromWildcard( subtag_wildcard )
|
||||
possible_subtag_ids = self._GetSubtagIdsFromWildcard( subtag_wildcard, job_key = job_key )
|
||||
|
||||
if namespace_wildcard != '':
|
||||
|
||||
possible_namespace_ids = self._GetNamespaceIdsFromWildcard( namespace_wildcard )
|
||||
|
||||
return self._GetHashIdsFromNamespaceIdsSubtagIds( tag_display_type, file_service_key, tag_search_context, possible_namespace_ids, possible_subtag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name )
|
||||
return self._GetHashIdsFromNamespaceIdsSubtagIds( tag_display_type, file_service_key, tag_search_context, possible_namespace_ids, possible_subtag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name, job_key = job_key )
|
||||
|
||||
else:
|
||||
|
||||
return self._GetHashIdsFromSubtagIds( tag_display_type, file_service_key, tag_search_context, possible_subtag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name )
|
||||
return self._GetHashIdsFromSubtagIds( tag_display_type, file_service_key, tag_search_context, possible_subtag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name, job_key = job_key )
|
||||
|
||||
|
||||
|
||||
|
@ -9466,20 +9379,15 @@ class DB( HydrusDB.HydrusDB ):
|
|||
results = []
|
||||
cursor = self._c.execute( query )
|
||||
|
||||
group_of_results = cursor.fetchmany( 1000 )
|
||||
cancelled_hook = None
|
||||
|
||||
while len( group_of_results ) > 0:
|
||||
if job_key is not None:
|
||||
|
||||
if job_key is not None and job_key.IsCancelled():
|
||||
|
||||
return []
|
||||
|
||||
|
||||
results.extend( group_of_results )
|
||||
|
||||
group_of_results = cursor.fetchmany( 1000 )
|
||||
cancelled_hook = job_key.IsCancelled
|
||||
|
||||
|
||||
results = HydrusDB.ReadFromCancellableCursor( cursor, 256, cancelled_hook = cancelled_hook )
|
||||
|
||||
return results
|
||||
|
||||
|
||||
|
@ -9557,13 +9465,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
return nonzero_tag_hash_ids
|
||||
|
||||
|
||||
def _GetHashIdsThatHaveTagAsNum( self, tag_display_type: int, file_service_key, tag_search_context: ClientSearch.TagSearchContext, namespace, num, operator, hash_ids = None, hash_ids_table_name = None ):
|
||||
def _GetHashIdsThatHaveTagAsNum( self, tag_display_type: int, file_service_key, tag_search_context: ClientSearch.TagSearchContext, namespace, num, operator, hash_ids = None, hash_ids_table_name = None, job_key = None ):
|
||||
|
||||
possible_subtag_ids = self._STS( self._c.execute( 'SELECT subtag_id FROM integer_subtags WHERE integer_subtag ' + operator + ' ' + str( num ) + ';' ) )
|
||||
|
||||
if namespace == '':
|
||||
|
||||
return self._GetHashIdsFromSubtagIds( tag_display_type, file_service_key, tag_search_context, possible_subtag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name )
|
||||
return self._GetHashIdsFromSubtagIds( tag_display_type, file_service_key, tag_search_context, possible_subtag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name, job_key = job_key )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -9571,7 +9479,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
possible_namespace_ids = { namespace_id }
|
||||
|
||||
return self._GetHashIdsFromNamespaceIdsSubtagIds( tag_display_type, file_service_key, tag_search_context, possible_namespace_ids, possible_subtag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name )
|
||||
return self._GetHashIdsFromNamespaceIdsSubtagIds( tag_display_type, file_service_key, tag_search_context, possible_namespace_ids, possible_subtag_ids, hash_ids = hash_ids, hash_ids_table_name = hash_ids_table_name, job_key = job_key )
|
||||
|
||||
|
||||
|
||||
|
@ -10647,23 +10555,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
with HydrusDB.TemporaryIntegerTable( self._c, tag_ids, 'tag_id' ) as temp_table_name:
|
||||
|
||||
# temp tags to mappings
|
||||
query = self._c.execute( 'SELECT hash_id FROM {} CROSS JOIN {} USING ( tag_id );'.format( temp_table_name, current_mappings_table_name ) )
|
||||
cursor = self._c.execute( 'SELECT hash_id FROM {} CROSS JOIN {} USING ( tag_id );'.format( temp_table_name, current_mappings_table_name ) )
|
||||
|
||||
results = self._STL( query.fetchmany( 100 ) )
|
||||
cancelled_hook = lambda: HydrusData.TimeHasPassedPrecise( stop_time_for_finding_files )
|
||||
|
||||
while len( results ) > 0:
|
||||
for ( hash_id, ) in HydrusDB.ReadFromCancellableCursor( cursor, 128, cancelled_hook = cancelled_hook ):
|
||||
|
||||
for hash_id in results:
|
||||
|
||||
hash_ids_counter[ hash_id ] += 1
|
||||
|
||||
|
||||
if HydrusData.TimeHasPassedPrecise( stop_time_for_finding_files ):
|
||||
|
||||
break
|
||||
|
||||
|
||||
results = self._STL( query.fetchmany( 100 ) )
|
||||
hash_ids_counter[ hash_id ] += 1
|
||||
|
||||
|
||||
|
||||
|
@ -11220,7 +11118,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
return subtag_id
|
||||
|
||||
|
||||
def _GetSubtagIdsFromWildcard( self, subtag_wildcard ):
|
||||
def _GetSubtagIdsFromWildcard( self, subtag_wildcard, job_key = None ):
|
||||
|
||||
if '*' in subtag_wildcard:
|
||||
|
||||
|
@ -11230,7 +11128,20 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
like_param = ConvertWildcardToSQLiteLikeParameter( subtag_wildcard )
|
||||
|
||||
return self._STL( self._c.execute( 'SELECT subtags_searchable_map.subtag_id FROM subtags_searchable_map, subtags ON ( subtags_searchable_map.searchable_subtag_id = subtags.subtag_id ) WHERE subtag LIKE ?;', ( like_param, ) ) )
|
||||
if subtag_wildcard.startswith( '*' ):
|
||||
|
||||
cursor = self._c.execute( 'SELECT subtags_searchable_map.subtag_id FROM subtags_searchable_map, subtags ON ( subtags_searchable_map.searchable_subtag_id = subtags.subtag_id ) WHERE subtag LIKE ?;', ( like_param, ) )
|
||||
|
||||
else:
|
||||
|
||||
# we have an optimisation here--rather than searching all subtags for bl*ah, let's search all the bl* subtags for bl*ah!
|
||||
|
||||
prefix_fts4_wildcard = subtag_wildcard.split( '*' )[0]
|
||||
|
||||
prefix_fts4_wildcard_param = '"{}*"'.format( prefix_fts4_wildcard )
|
||||
|
||||
cursor = self._c.execute( 'SELECT docid FROM subtags_fts4 WHERE subtag MATCH ? AND EXISTS ( SELECT 1 FROM subtags_searchable_map, subtags ON ( subtags_searchable_map.searchable_subtag_id = subtags.subtag_id ) WHERE subtags_searchable_map.subtag_id = docid AND subtag LIKE ? );', ( prefix_fts4_wildcard_param, like_param ) )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
|
@ -11238,14 +11149,14 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
subtags_fts4_param = '"{}"'.format( subtag_wildcard )
|
||||
|
||||
return self._STL( self._c.execute( 'SELECT docid FROM subtags_fts4 WHERE subtag MATCH ?;', ( subtags_fts4_param, ) ) )
|
||||
cursor = self._c.execute( 'SELECT docid FROM subtags_fts4 WHERE subtag MATCH ?;', ( subtags_fts4_param, ) )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
if self._SubtagExists( subtag_wildcard ):
|
||||
|
||||
return self._STL( self._c.execute( 'SELECT subtags_searchable_map.subtag_id FROM subtags_searchable_map, subtags ON ( subtags_searchable_map.searchable_subtag_id = subtags.subtag_id ) WHERE subtag = ?;', ( subtag_wildcard, ) ) )
|
||||
cursor = self._c.execute( 'SELECT subtags_searchable_map.subtag_id FROM subtags_searchable_map, subtags ON ( subtags_searchable_map.searchable_subtag_id = subtags.subtag_id ) WHERE subtag = ?;', ( subtag_wildcard, ) )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -11253,6 +11164,19 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
results = []
|
||||
|
||||
cancelled_hook = None
|
||||
|
||||
if job_key is not None:
|
||||
|
||||
cancelled_hook = job_key.IsCancelled
|
||||
|
||||
|
||||
results = self._STL( HydrusDB.ReadFromCancellableCursor( cursor, 1024, cancelled_hook = cancelled_hook ) )
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _GetTag( self, tag_id ):
|
||||
|
||||
|
@ -11288,26 +11212,44 @@ class DB( HydrusDB.HydrusDB ):
|
|||
return tag_id
|
||||
|
||||
|
||||
def _GetTagIdsFromNamespaceIdsSubtagIds( self, namespace_ids: typing.Collection[ int ], subtag_ids: typing.Collection[ int ] ):
|
||||
def _GetTagIdsFromNamespaceIdsSubtagIds( self, namespace_ids: typing.Collection[ int ], subtag_ids: typing.Collection[ int ], job_key = None ):
|
||||
|
||||
with HydrusDB.TemporaryIntegerTable( self._c, subtag_ids, 'subtag_id' ) as temp_subtag_ids_table_name:
|
||||
|
||||
with HydrusDB.TemporaryIntegerTable( self._c, namespace_ids, 'namespace_id' ) as temp_namespace_ids_table_name:
|
||||
|
||||
# temp subtags to tags to temp namespaces
|
||||
tag_ids = self._STS( self._c.execute( 'SELECT tag_id FROM {} CROSS JOIN tags USING ( subtag_id ) CROSS JOIN {} USING ( namespace_id );'.format( temp_subtag_ids_table_name, temp_namespace_ids_table_name ) ) )
|
||||
cursor = self._c.execute( 'SELECT tag_id FROM {} CROSS JOIN tags USING ( subtag_id ) CROSS JOIN {} USING ( namespace_id );'.format( temp_subtag_ids_table_name, temp_namespace_ids_table_name ) )
|
||||
|
||||
cancelled_hook = None
|
||||
|
||||
if job_key is not None:
|
||||
|
||||
cancelled_hook = job_key.IsCancelled
|
||||
|
||||
|
||||
tag_ids = self._STS( HydrusDB.ReadFromCancellableCursor( cursor, 128, cancelled_hook ) )
|
||||
|
||||
|
||||
|
||||
return tag_ids
|
||||
|
||||
|
||||
def _GetTagIdsFromSubtagIds( self, subtag_ids: typing.Collection[ int ] ):
|
||||
def _GetTagIdsFromSubtagIds( self, subtag_ids: typing.Collection[ int ], job_key = None ):
|
||||
|
||||
with HydrusDB.TemporaryIntegerTable( self._c, subtag_ids, 'subtag_id' ) as temp_subtag_ids_table_name:
|
||||
|
||||
# temp subtags to tags
|
||||
tag_ids = self._STS( self._c.execute( 'SELECT tag_id FROM {} CROSS JOIN tags USING ( subtag_id );'.format( temp_subtag_ids_table_name ) ) )
|
||||
cursor = self._c.execute( 'SELECT tag_id FROM {} CROSS JOIN tags USING ( subtag_id );'.format( temp_subtag_ids_table_name ) )
|
||||
|
||||
cancelled_hook = None
|
||||
|
||||
if job_key is not None:
|
||||
|
||||
cancelled_hook = job_key.IsCancelled
|
||||
|
||||
|
||||
tag_ids = self._STS( HydrusDB.ReadFromCancellableCursor( cursor, 128, cancelled_hook = cancelled_hook ) )
|
||||
|
||||
|
||||
return tag_ids
|
||||
|
@ -18109,8 +18051,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
raise
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if version == 397:
|
||||
|
||||
|
|
|
@ -1678,6 +1678,28 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
|
|||
|
||||
metadata_row[ 'service_names_to_statuses_to_tags' ] = service_names_to_statuses_to_tags
|
||||
|
||||
#
|
||||
|
||||
service_names_to_statuses_to_tags = {}
|
||||
|
||||
service_keys_to_statuses_to_tags = tags_manager.GetServiceKeysToStatusesToTags( ClientTags.TAG_DISPLAY_ACTUAL )
|
||||
|
||||
for ( service_key, statuses_to_tags ) in service_keys_to_statuses_to_tags.items():
|
||||
|
||||
if service_key not in service_keys_to_names:
|
||||
|
||||
service_keys_to_names[ service_key ] = services_manager.GetName( service_key )
|
||||
|
||||
|
||||
service_name = service_keys_to_names[ service_key ]
|
||||
|
||||
service_names_to_statuses_to_tags[ service_name ] = { str( status ) : list( tags ) for ( status, tags ) in statuses_to_tags.items() }
|
||||
|
||||
|
||||
metadata_row[ 'service_names_to_statuses_to_display_tags' ] = service_names_to_statuses_to_tags
|
||||
|
||||
#
|
||||
|
||||
metadata.append( metadata_row )
|
||||
|
||||
|
||||
|
|
|
@ -452,6 +452,12 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
#
|
||||
|
||||
self._dictionary[ 'custom_default_predicates' ] = HydrusSerialisable.SerialisableList()
|
||||
|
||||
self._dictionary[ 'predicate_types_to_recent_predicates' ] = HydrusSerialisable.SerialisableDictionary()
|
||||
|
||||
#
|
||||
|
||||
self._dictionary[ 'favourite_tag_filters' ] = HydrusSerialisable.SerialisableDictionary()
|
||||
|
||||
#
|
||||
|
@ -775,6 +781,32 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def ClearCustomDefaultSystemPredicates( self, predicate_type = None, comparable_predicate = None ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
custom_default_predicates = self._dictionary[ 'custom_default_predicates' ]
|
||||
|
||||
if predicate_type is not None:
|
||||
|
||||
new_custom_default_predicates = HydrusSerialisable.SerialisableList( [ pred for pred in custom_default_predicates if pred.GetType() != predicate_type ] )
|
||||
|
||||
self._dictionary[ 'custom_default_predicates' ] = new_custom_default_predicates
|
||||
|
||||
return
|
||||
|
||||
|
||||
if comparable_predicate is not None:
|
||||
|
||||
new_custom_default_predicates = HydrusSerialisable.SerialisableList( [ pred for pred in custom_default_predicates if not pred.IsUIEditable( comparable_predicate ) ] )
|
||||
|
||||
self._dictionary[ 'custom_default_predicates' ] = new_custom_default_predicates
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
def FlipBoolean( self, name ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -814,6 +846,26 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def GetCustomDefaultSystemPredicates( self, predicate_type = None, comparable_predicate = None ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
custom_default_predicates = self._dictionary[ 'custom_default_predicates' ]
|
||||
|
||||
if predicate_type is not None:
|
||||
|
||||
return [ pred for pred in custom_default_predicates if pred.GetType() == predicate_type ]
|
||||
|
||||
|
||||
if comparable_predicate is not None:
|
||||
|
||||
return [ pred for pred in custom_default_predicates if pred.IsUIEditable( comparable_predicate ) ]
|
||||
|
||||
|
||||
return []
|
||||
|
||||
|
||||
|
||||
def GetDefaultFileImportOptions( self, options_type ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -1026,6 +1078,26 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def GetRecentPredicates( self, predicate_types ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
result = []
|
||||
|
||||
predicate_types_to_recent_predicates = self._dictionary[ 'predicate_types_to_recent_predicates' ]
|
||||
|
||||
for predicate_type in predicate_types:
|
||||
|
||||
if predicate_type in predicate_types_to_recent_predicates:
|
||||
|
||||
result.extend( predicate_types_to_recent_predicates[ predicate_type ] )
|
||||
|
||||
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def GetSimpleDownloaderFormulae( self ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -1085,6 +1157,38 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def PushRecentPredicates( self, predicates ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
predicate_types_to_recent_predicates = self._dictionary[ 'predicate_types_to_recent_predicates' ]
|
||||
|
||||
for predicate in predicates:
|
||||
|
||||
predicate_type = predicate.GetType()
|
||||
|
||||
if predicate_type not in predicate_types_to_recent_predicates:
|
||||
|
||||
predicate_types_to_recent_predicates[ predicate_type ] = HydrusSerialisable.SerialisableList()
|
||||
|
||||
|
||||
recent_predicates = predicate_types_to_recent_predicates[ predicate_type ]
|
||||
|
||||
if predicate in recent_predicates:
|
||||
|
||||
recent_predicates.remove( predicate )
|
||||
|
||||
|
||||
recent_predicates.insert( 0, predicate )
|
||||
|
||||
while len( recent_predicates ) > 5:
|
||||
|
||||
recent_predicates.pop( 5 )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def SetBoolean( self, name, value ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -1120,6 +1224,44 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def SetCustomDefaultSystemPredicates( self, predicate_type = None, predicates = None, comparable_predicates = None ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
custom_default_predicates = self._dictionary[ 'custom_default_predicates' ]
|
||||
|
||||
if predicate_type is not None and predicates is not None:
|
||||
|
||||
new_custom_default_predicates = HydrusSerialisable.SerialisableList( [ pred for pred in custom_default_predicates if pred.GetType() != predicate_type ] )
|
||||
|
||||
new_custom_default_predicates.extend( predicates )
|
||||
|
||||
self._dictionary[ 'custom_default_predicates' ] = new_custom_default_predicates
|
||||
|
||||
return
|
||||
|
||||
|
||||
if comparable_predicates is not None:
|
||||
|
||||
new_custom_default_predicates = HydrusSerialisable.SerialisableList()
|
||||
|
||||
for pred in custom_default_predicates:
|
||||
|
||||
if True not in ( pred.IsUIEditable( comparable_predicate ) for comparable_predicate in comparable_predicates ):
|
||||
|
||||
new_custom_default_predicates.append( pred )
|
||||
|
||||
|
||||
|
||||
new_custom_default_predicates.extend( comparable_predicates )
|
||||
|
||||
self._dictionary[ 'custom_default_predicates' ] = new_custom_default_predicates
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
def SetDefaultFileImportOptions( self, options_type, file_import_options ):
|
||||
|
||||
with self._lock:
|
||||
|
|
|
@ -2038,6 +2038,11 @@ class ContentParser( HydrusSerialisable.SerialisableBase ):
|
|||
u = re.sub( r'^.*\shttp', 'http', u )
|
||||
|
||||
|
||||
while u.startswith( 'https://https://' ):
|
||||
|
||||
u = u[8:]
|
||||
|
||||
|
||||
return u
|
||||
|
||||
|
||||
|
|
|
@ -1664,6 +1664,11 @@ class Predicate( HydrusSerialisable.SerialisableBase ):
|
|||
return self._inclusive
|
||||
|
||||
|
||||
def IsInvertible( self ):
|
||||
|
||||
return self.GetInverseCopy() is not None
|
||||
|
||||
|
||||
def IsMutuallyExclusive( self, predicate ):
|
||||
|
||||
if self._predicate_type == PREDICATE_TYPE_SYSTEM_EVERYTHING:
|
||||
|
@ -1671,7 +1676,7 @@ class Predicate( HydrusSerialisable.SerialisableBase ):
|
|||
return True
|
||||
|
||||
|
||||
if predicate == self.GetInverseCopy():
|
||||
if self.IsInvertible() and predicate == self.GetInverseCopy():
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
@ -3101,7 +3101,14 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
|
|||
|
||||
status_hook = lambda s: job_key.SetVariable( 'popup_text_1', s )
|
||||
|
||||
self._controller.Write( 'repopulate_tag_search_cache', status_hook = status_hook )
|
||||
def do_it():
|
||||
|
||||
self._controller.WriteSynchronous( 'repopulate_tag_search_cache', status_hook = status_hook )
|
||||
|
||||
job_key.Finish()
|
||||
|
||||
|
||||
self._controller.CallToThreadLongRunning( do_it )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -8,11 +8,11 @@ from hydrus.core import HydrusGlobals as HG
|
|||
|
||||
from hydrus.client import ClientApplicationCommand as CAC
|
||||
from hydrus.client import ClientConstants as CC
|
||||
from hydrus.client.gui import ClientGUIACDropdown
|
||||
from hydrus.client.gui import ClientGUICommon
|
||||
from hydrus.client.gui import ClientGUIScrolledPanels
|
||||
from hydrus.client.gui import ClientGUIShortcuts
|
||||
from hydrus.client.gui import QtPorting as QP
|
||||
from hydrus.client.gui.search import ClientGUIACDropdown
|
||||
|
||||
class RatingLikeSubPanel( QW.QWidget ):
|
||||
|
||||
|
|
|
@ -10,13 +10,13 @@ from hydrus.core import HydrusData
|
|||
from hydrus.core import HydrusGlobals as HG
|
||||
|
||||
from hydrus.client import ClientConstants as CC
|
||||
from hydrus.client.gui import ClientGUIACDropdown
|
||||
from hydrus.client.gui import ClientGUIFrames
|
||||
from hydrus.client.gui import ClientGUIFunctions
|
||||
from hydrus.client.gui import ClientGUICommon
|
||||
from hydrus.client.gui import ClientGUIShortcuts
|
||||
from hydrus.client.gui import QtPorting as QP
|
||||
from hydrus.client.gui.lists import ClientGUIListBoxes
|
||||
from hydrus.client.gui.search import ClientGUIACDropdown
|
||||
|
||||
class Dialog( QP.Dialog ):
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ from hydrus.core import HydrusPaths
|
|||
from hydrus.client import ClientConstants as CC
|
||||
from hydrus.client import ClientExporting
|
||||
from hydrus.client import ClientSearch
|
||||
from hydrus.client.gui import ClientGUIACDropdown
|
||||
from hydrus.client.gui import ClientGUICommon
|
||||
from hydrus.client.gui import ClientGUIDialogsQuick
|
||||
from hydrus.client.gui import ClientGUIScrolledPanels
|
||||
|
@ -25,6 +24,7 @@ from hydrus.client.gui.lists import ClientGUIListBoxes
|
|||
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
|
||||
from hydrus.client.gui.lists import ClientGUIListCtrl
|
||||
from hydrus.client.metadata import ClientTags
|
||||
from hydrus.client.gui.search import ClientGUIACDropdown
|
||||
|
||||
class EditExportFoldersPanel( ClientGUIScrolledPanels.EditPanel ):
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ from hydrus.core import HydrusText
|
|||
|
||||
from hydrus.client import ClientConstants as CC
|
||||
from hydrus.client import ClientData
|
||||
from hydrus.client.gui import ClientGUIACDropdown
|
||||
from hydrus.client.gui import ClientGUICommon
|
||||
from hydrus.client.gui import ClientGUIControls
|
||||
from hydrus.client.gui import ClientGUICore as CGC
|
||||
|
@ -35,6 +34,7 @@ from hydrus.client.gui import QtPorting as QP
|
|||
from hydrus.client.gui.lists import ClientGUIListBoxes
|
||||
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
|
||||
from hydrus.client.gui.lists import ClientGUIListCtrl
|
||||
from hydrus.client.gui.search import ClientGUIACDropdown
|
||||
from hydrus.client.importing import ClientImporting
|
||||
from hydrus.client.importing import ClientImportLocal
|
||||
from hydrus.client.importing import ClientImportOptions
|
||||
|
|
|
@ -21,7 +21,6 @@ from hydrus.client import ClientParsing
|
|||
from hydrus.client import ClientPaths
|
||||
from hydrus.client import ClientSearch
|
||||
from hydrus.client import ClientThreading
|
||||
from hydrus.client.gui import ClientGUIACDropdown
|
||||
from hydrus.client.gui import ClientGUICanvas
|
||||
from hydrus.client.gui import ClientGUICanvasFrame
|
||||
from hydrus.client.gui import ClientGUICommon
|
||||
|
@ -34,16 +33,18 @@ from hydrus.client.gui import ClientGUIImport
|
|||
from hydrus.client.gui import ClientGUIMenus
|
||||
from hydrus.client.gui import ClientGUIParsing
|
||||
from hydrus.client.gui import ClientGUIResults
|
||||
from hydrus.client.gui import ClientGUIResultsSortCollect
|
||||
from hydrus.client.gui import ClientGUIScrolledPanels
|
||||
from hydrus.client.gui import ClientGUIFileSeedCache
|
||||
from hydrus.client.gui import ClientGUIGallerySeedLog
|
||||
from hydrus.client.gui import ClientGUIScrolledPanelsEdit
|
||||
from hydrus.client.gui import ClientGUISearch
|
||||
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
|
||||
from hydrus.client.gui import QtPorting as QP
|
||||
from hydrus.client.gui.lists import ClientGUIListBoxes
|
||||
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
|
||||
from hydrus.client.gui.lists import ClientGUIListCtrl
|
||||
from hydrus.client.gui.search import ClientGUIACDropdown
|
||||
from hydrus.client.gui.search import ClientGUISearch
|
||||
from hydrus.client.importing import ClientImportGallery
|
||||
from hydrus.client.importing import ClientImportLocal
|
||||
from hydrus.client.importing import ClientImportOptions
|
||||
|
@ -681,6 +682,11 @@ class ListBoxTagsMediaManagementPanel( ClientGUIListBoxes.ListBoxTagsMedia ):
|
|||
|
||||
if len( predicates ) > 0:
|
||||
|
||||
if shift_down and len( predicates ) > 1:
|
||||
|
||||
predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_OR_CONTAINER, predicates ), )
|
||||
|
||||
|
||||
HG.client_controller.pub( 'enter_predicates', self._page_key, predicates )
|
||||
|
||||
return True
|
||||
|
@ -713,23 +719,41 @@ class ListBoxTagsMediaManagementPanel( ClientGUIListBoxes.ListBoxTagsMedia ):
|
|||
|
||||
def _ProcessMenuPredicateEvent( self, command ):
|
||||
|
||||
( predicates, inverse_predicates ) = self._GetSelectedPredicatesAndInverseCopies()
|
||||
( predicates, or_predicate, inverse_predicates ) = self._GetSelectedPredicatesAndInverseCopies()
|
||||
|
||||
p = None
|
||||
permit_remove = True
|
||||
permit_add = True
|
||||
|
||||
if command == 'add_predicates':
|
||||
|
||||
HG.client_controller.pub( 'enter_predicates', self._page_key, predicates, permit_remove = False )
|
||||
p = predicates
|
||||
permit_remove = False
|
||||
|
||||
elif command == 'add_or_predicate':
|
||||
|
||||
p = ( or_predicate, )
|
||||
permit_remove = False
|
||||
|
||||
elif command == 'remove_predicates':
|
||||
|
||||
HG.client_controller.pub( 'enter_predicates', self._page_key, predicates, permit_add = False )
|
||||
p = predicates
|
||||
permit_add = False
|
||||
|
||||
elif command == 'add_inverse_predicates':
|
||||
|
||||
HG.client_controller.pub( 'enter_predicates', self._page_key, inverse_predicates, permit_remove = False )
|
||||
p = inverse_predicates
|
||||
permit_remove = False
|
||||
|
||||
elif command == 'remove_inverse_predicates':
|
||||
|
||||
HG.client_controller.pub( 'enter_predicates', self._page_key, inverse_predicates, permit_add = False )
|
||||
p = predicates
|
||||
permit_add = False
|
||||
|
||||
|
||||
if p is not None:
|
||||
|
||||
HG.client_controller.pub( 'enter_predicates', self._page_key, p, permit_remove = permit_remove, permit_add = permit_add )
|
||||
|
||||
|
||||
|
||||
|
@ -764,11 +788,11 @@ class ManagementPanel( QW.QScrollArea ):
|
|||
|
||||
self._current_selection_tags_list = None
|
||||
|
||||
self._media_sort = ClientGUISearch.MediaSortControl( self, management_controller = self._management_controller )
|
||||
self._media_sort = ClientGUIResultsSortCollect.MediaSortControl( self, management_controller = self._management_controller )
|
||||
|
||||
silent_collect = not self.SHOW_COLLECT
|
||||
|
||||
self._media_collect = ClientGUISearch.MediaCollectControl( self, management_controller = self._management_controller, silent = silent_collect )
|
||||
self._media_collect = ClientGUIResultsSortCollect.MediaCollectControl( self, management_controller = self._management_controller, silent = silent_collect )
|
||||
|
||||
if not self.SHOW_COLLECT:
|
||||
|
||||
|
|
|
@ -0,0 +1,483 @@
|
|||
import typing
|
||||
|
||||
from qtpy import QtCore as QC
|
||||
from qtpy import QtWidgets as QW
|
||||
|
||||
from hydrus.core import HydrusConstants as HC
|
||||
from hydrus.core import HydrusExceptions
|
||||
from hydrus.core import HydrusGlobals as HG
|
||||
|
||||
from hydrus.client import ClientConstants as CC
|
||||
from hydrus.client.gui import ClientGUICommon
|
||||
from hydrus.client.gui import ClientGUICore as CGC
|
||||
from hydrus.client.gui import ClientGUIFunctions
|
||||
from hydrus.client.gui import ClientGUIMenus
|
||||
from hydrus.client.gui import QtPorting as QP
|
||||
from hydrus.client.media import ClientMedia
|
||||
|
||||
class MediaCollectControl( QW.QWidget ):
|
||||
|
||||
def __init__( self, parent, management_controller = None, silent = False ):
|
||||
|
||||
QW.QWidget.__init__( self, parent )
|
||||
|
||||
# this is trash, rewrite it to deal with the media_collect object, not the management controller
|
||||
|
||||
self._management_controller = management_controller
|
||||
|
||||
if self._management_controller is not None and self._management_controller.HasVariable( 'media_collect' ):
|
||||
|
||||
self._media_collect = self._management_controller.GetVariable( 'media_collect' )
|
||||
|
||||
else:
|
||||
|
||||
self._media_collect = HG.client_controller.new_options.GetDefaultCollect()
|
||||
|
||||
|
||||
self._silent = silent
|
||||
|
||||
self._collect_comboctrl = QP.CollectComboCtrl( self, self._media_collect )
|
||||
|
||||
self._collect_unmatched = ClientGUICommon.BetterChoice( self )
|
||||
|
||||
width = ClientGUIFunctions.ConvertTextToPixelWidth( self._collect_unmatched, 19 )
|
||||
|
||||
self._collect_unmatched.setMinimumWidth( width )
|
||||
|
||||
self._collect_unmatched.addItem( 'collect unmatched', True )
|
||||
self._collect_unmatched.addItem( 'leave unmatched', False )
|
||||
|
||||
#
|
||||
|
||||
self._collect_unmatched.SetValue( self._media_collect.collect_unmatched )
|
||||
|
||||
#
|
||||
|
||||
hbox = QP.HBoxLayout( margin = 0 )
|
||||
|
||||
QP.AddToLayout( hbox, self._collect_comboctrl, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
QP.AddToLayout( hbox, self._collect_unmatched, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
|
||||
self.setLayout( hbox )
|
||||
|
||||
#
|
||||
|
||||
self._UpdateLabel()
|
||||
|
||||
self._collect_unmatched.currentIndexChanged.connect( self.CollectValuesChanged )
|
||||
self._collect_comboctrl.itemChanged.connect( self.CollectValuesChanged )
|
||||
|
||||
HG.client_controller.sub( self, 'SetCollectFromPage', 'set_page_collect' )
|
||||
|
||||
|
||||
def _BroadcastCollect( self ):
|
||||
|
||||
if not self._silent and self._management_controller is not None:
|
||||
|
||||
self._management_controller.SetVariable( 'media_collect', self._media_collect )
|
||||
|
||||
page_key = self._management_controller.GetKey( 'page' )
|
||||
|
||||
HG.client_controller.pub( 'collect_media', page_key, self._media_collect )
|
||||
HG.client_controller.pub( 'a_collect_happened', page_key )
|
||||
|
||||
|
||||
|
||||
def _UpdateLabel( self ):
|
||||
|
||||
( namespaces, rating_service_keys, description ) = self._collect_comboctrl.GetValues()
|
||||
|
||||
self._collect_comboctrl.SetValue( description )
|
||||
|
||||
|
||||
def GetValue( self ):
|
||||
|
||||
return self._media_collect
|
||||
|
||||
|
||||
def CollectValuesChanged( self ):
|
||||
|
||||
( namespaces, rating_service_keys, description ) = self._collect_comboctrl.GetValues()
|
||||
|
||||
self._UpdateLabel()
|
||||
|
||||
collect_unmatched = self._collect_unmatched.GetValue()
|
||||
|
||||
self._media_collect = ClientMedia.MediaCollect( namespaces = namespaces, rating_service_keys = rating_service_keys, collect_unmatched = collect_unmatched )
|
||||
|
||||
self._BroadcastCollect()
|
||||
|
||||
|
||||
def SetCollect( self, media_collect ):
|
||||
|
||||
self._media_collect = media_collect
|
||||
|
||||
self._collect_comboctrl.blockSignals( True )
|
||||
self._collect_unmatched.blockSignals( True )
|
||||
|
||||
self._collect_comboctrl.SetCollectByValue( self._media_collect )
|
||||
self._collect_unmatched.SetValue( self._media_collect.collect_unmatched )
|
||||
|
||||
self._UpdateLabel()
|
||||
|
||||
self._collect_comboctrl.blockSignals( False )
|
||||
self._collect_unmatched.blockSignals( False )
|
||||
|
||||
self._BroadcastCollect()
|
||||
|
||||
|
||||
def SetCollectFromPage( self, page_key, media_collect ):
|
||||
|
||||
if page_key == self._management_controller.GetKey( 'page' ):
|
||||
|
||||
self.SetCollect( media_collect )
|
||||
|
||||
self._BroadcastCollect()
|
||||
|
||||
|
||||
|
||||
class MediaSortControl( QW.QWidget ):
|
||||
|
||||
sortChanged = QC.Signal( ClientMedia.MediaSort )
|
||||
|
||||
def __init__( self, parent, management_controller = None ):
|
||||
|
||||
QW.QWidget.__init__( self, parent )
|
||||
|
||||
self._management_controller = management_controller
|
||||
|
||||
self._sort_type = ( 'system', CC.SORT_FILES_BY_FILESIZE )
|
||||
|
||||
self._sort_type_button = ClientGUICommon.BetterButton( self, 'sort', self._SortTypeButtonClick )
|
||||
self._sort_order_choice = ClientGUICommon.BetterChoice( self )
|
||||
|
||||
type_width = ClientGUIFunctions.ConvertTextToPixelWidth( self._sort_type_button, 14 )
|
||||
|
||||
self._sort_type_button.setMinimumWidth( type_width )
|
||||
|
||||
asc_width = ClientGUIFunctions.ConvertTextToPixelWidth( self._sort_order_choice, 14 )
|
||||
|
||||
self._sort_order_choice.setMinimumWidth( asc_width )
|
||||
|
||||
self._sort_order_choice.addItem( '', CC.SORT_ASC )
|
||||
|
||||
self._UpdateSortTypeLabel()
|
||||
self._UpdateAscLabels()
|
||||
|
||||
#
|
||||
|
||||
hbox = QP.HBoxLayout( margin = 0 )
|
||||
|
||||
QP.AddToLayout( hbox, self._sort_type_button, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
QP.AddToLayout( hbox, self._sort_order_choice, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
|
||||
self.setLayout( hbox )
|
||||
|
||||
HG.client_controller.sub( self, 'ACollectHappened', 'a_collect_happened' )
|
||||
HG.client_controller.sub( self, 'BroadcastSort', 'do_page_sort' )
|
||||
|
||||
if self._management_controller is not None and self._management_controller.HasVariable( 'media_sort' ):
|
||||
|
||||
media_sort = self._management_controller.GetVariable( 'media_sort' )
|
||||
|
||||
try:
|
||||
|
||||
self.SetSort( media_sort )
|
||||
|
||||
except:
|
||||
|
||||
default_sort = ClientMedia.MediaSort( ( 'system', CC.SORT_FILES_BY_FILESIZE ), CC.SORT_ASC )
|
||||
|
||||
self.SetSort( default_sort )
|
||||
|
||||
|
||||
|
||||
self._sort_order_choice.currentIndexChanged.connect( self.EventSortAscChoice )
|
||||
|
||||
|
||||
def _BroadcastSort( self ):
|
||||
|
||||
media_sort = self._GetCurrentSort()
|
||||
|
||||
if self._management_controller is not None:
|
||||
|
||||
self._management_controller.SetVariable( 'media_sort', media_sort )
|
||||
|
||||
|
||||
self.sortChanged.emit( media_sort )
|
||||
|
||||
|
||||
def _GetCurrentSort( self ) -> ClientMedia.MediaSort:
|
||||
|
||||
sort_order = self._sort_order_choice.GetValue()
|
||||
|
||||
media_sort = ClientMedia.MediaSort( self._sort_type, sort_order )
|
||||
|
||||
return media_sort
|
||||
|
||||
|
||||
def _PopulateSortMenuOrList( self, menu = None ):
|
||||
|
||||
sort_types = []
|
||||
|
||||
menu_items_and_sort_types = []
|
||||
|
||||
submetatypes_to_menus = {}
|
||||
|
||||
for system_sort_type in CC.SYSTEM_SORT_TYPES:
|
||||
|
||||
sort_type = ( 'system', system_sort_type )
|
||||
|
||||
sort_types.append( sort_type )
|
||||
|
||||
if menu is not None:
|
||||
|
||||
submetatype = CC.system_sort_type_submetatype_string_lookup[ system_sort_type ]
|
||||
|
||||
if submetatype is None:
|
||||
|
||||
menu_to_add_to = menu
|
||||
|
||||
else:
|
||||
|
||||
if submetatype not in submetatypes_to_menus:
|
||||
|
||||
submenu = QW.QMenu( menu )
|
||||
|
||||
submetatypes_to_menus[ submetatype ] = submenu
|
||||
|
||||
ClientGUIMenus.AppendMenu( menu, submenu, submetatype )
|
||||
|
||||
|
||||
menu_to_add_to = submetatypes_to_menus[ submetatype ]
|
||||
|
||||
|
||||
label = CC.sort_type_basic_string_lookup[ system_sort_type ]
|
||||
|
||||
menu_item = ClientGUIMenus.AppendMenuItem( menu_to_add_to, label, 'Select this sort type.', self._SetSortType, sort_type )
|
||||
|
||||
menu_items_and_sort_types.append( ( menu_item, sort_type ) )
|
||||
|
||||
|
||||
|
||||
namespace_sort_types = HC.options[ 'sort_by' ]
|
||||
|
||||
if len( namespace_sort_types ) > 0:
|
||||
|
||||
if menu is not None:
|
||||
|
||||
submenu = QW.QMenu( menu )
|
||||
|
||||
ClientGUIMenus.AppendMenu( menu, submenu, 'namespaces' )
|
||||
|
||||
|
||||
for ( namespaces_text, namespaces_list ) in namespace_sort_types:
|
||||
|
||||
sort_type = ( namespaces_text, tuple( namespaces_list ) )
|
||||
|
||||
sort_types.append( sort_type )
|
||||
|
||||
if menu is not None:
|
||||
|
||||
example_sort = ClientMedia.MediaSort( sort_type, CC.SORT_ASC )
|
||||
|
||||
label = example_sort.GetSortTypeString()
|
||||
|
||||
menu_item = ClientGUIMenus.AppendMenuItem( submenu, label, 'Select this sort type.', self._SetSortType, sort_type )
|
||||
|
||||
menu_items_and_sort_types.append( ( menu_item, sort_type ) )
|
||||
|
||||
|
||||
|
||||
|
||||
rating_service_keys = HG.client_controller.services_manager.GetServiceKeys( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
|
||||
|
||||
if len( rating_service_keys ) > 0:
|
||||
|
||||
if menu is not None:
|
||||
|
||||
submenu = QW.QMenu( menu )
|
||||
|
||||
ClientGUIMenus.AppendMenu( menu, submenu, 'ratings' )
|
||||
|
||||
|
||||
for service_key in rating_service_keys:
|
||||
|
||||
sort_type = ( 'rating', service_key )
|
||||
|
||||
sort_types.append( sort_type )
|
||||
|
||||
if menu is not None:
|
||||
|
||||
example_sort = ClientMedia.MediaSort( sort_type, CC.SORT_ASC )
|
||||
|
||||
label = example_sort.GetSortTypeString()
|
||||
|
||||
menu_item = ClientGUIMenus.AppendMenuItem( submenu, label, 'Select this sort type.', self._SetSortType, sort_type )
|
||||
|
||||
menu_items_and_sort_types.append( ( menu_item, sort_type ) )
|
||||
|
||||
|
||||
|
||||
|
||||
if menu is not None:
|
||||
|
||||
for ( menu_item, sort_choice ) in menu_items_and_sort_types:
|
||||
|
||||
if sort_choice == self._sort_type:
|
||||
|
||||
menu_item.setCheckable( True )
|
||||
menu_item.setChecked( True )
|
||||
|
||||
|
||||
|
||||
|
||||
return sort_types
|
||||
|
||||
|
||||
def _SortTypeButtonClick( self ):
|
||||
|
||||
menu = QW.QMenu()
|
||||
|
||||
self._PopulateSortMenuOrList( menu = menu )
|
||||
|
||||
CGC.core().PopupMenu( self, menu )
|
||||
|
||||
|
||||
def _SetSortType( self, sort_type ):
|
||||
|
||||
self._sort_type = sort_type
|
||||
|
||||
self._UpdateSortTypeLabel()
|
||||
self._UpdateAscLabels( set_default_asc = True )
|
||||
|
||||
self._UserChoseASort()
|
||||
|
||||
self._BroadcastSort()
|
||||
|
||||
|
||||
def _UpdateAscLabels( self, set_default_asc = False ):
|
||||
|
||||
media_sort = self._GetCurrentSort()
|
||||
|
||||
self._sort_order_choice.clear()
|
||||
|
||||
if media_sort.CanAsc():
|
||||
|
||||
( asc_str, desc_str, default_sort_order ) = media_sort.GetSortOrderStrings()
|
||||
|
||||
self._sort_order_choice.addItem( asc_str, CC.SORT_ASC )
|
||||
self._sort_order_choice.addItem( desc_str, CC.SORT_DESC )
|
||||
|
||||
if set_default_asc:
|
||||
|
||||
sort_order_to_set = default_sort_order
|
||||
|
||||
else:
|
||||
|
||||
sort_order_to_set = media_sort.sort_order
|
||||
|
||||
|
||||
self._sort_order_choice.SetValue( sort_order_to_set )
|
||||
|
||||
self._sort_order_choice.setEnabled( True )
|
||||
|
||||
else:
|
||||
|
||||
self._sort_order_choice.addItem( '', CC.SORT_ASC )
|
||||
self._sort_order_choice.addItem( '', CC.SORT_DESC )
|
||||
|
||||
self._sort_order_choice.SetValue( CC.SORT_ASC )
|
||||
|
||||
self._sort_order_choice.setEnabled( False )
|
||||
|
||||
|
||||
|
||||
def _UpdateSortTypeLabel( self ):
|
||||
|
||||
example_sort = ClientMedia.MediaSort( self._sort_type, CC.SORT_ASC )
|
||||
|
||||
self._sort_type_button.setText( example_sort.GetSortTypeString() )
|
||||
|
||||
|
||||
def _UserChoseASort( self ):
|
||||
|
||||
if HG.client_controller.new_options.GetBoolean( 'save_page_sort_on_change' ):
|
||||
|
||||
media_sort = self._GetCurrentSort()
|
||||
|
||||
HG.client_controller.new_options.SetDefaultSort( media_sort )
|
||||
|
||||
|
||||
|
||||
def ACollectHappened( self, page_key ):
|
||||
|
||||
if self._management_controller is not None:
|
||||
|
||||
my_page_key = self._management_controller.GetKey( 'page' )
|
||||
|
||||
if page_key == my_page_key:
|
||||
|
||||
self._BroadcastSort()
|
||||
|
||||
|
||||
|
||||
|
||||
def BroadcastSort( self, page_key = None ):
|
||||
|
||||
if page_key is not None and page_key != self._management_controller.GetKey( 'page' ):
|
||||
|
||||
return
|
||||
|
||||
|
||||
self._BroadcastSort()
|
||||
|
||||
|
||||
def EventSortAscChoice( self, index ):
|
||||
|
||||
self._UserChoseASort()
|
||||
|
||||
self._BroadcastSort()
|
||||
|
||||
|
||||
def GetSort( self ) -> ClientMedia.MediaSort:
|
||||
|
||||
return self._GetCurrentSort()
|
||||
|
||||
|
||||
def wheelEvent( self, event ):
|
||||
|
||||
if event.angleDelta().y() > 0:
|
||||
|
||||
index_delta = -1
|
||||
|
||||
else:
|
||||
|
||||
index_delta = 1
|
||||
|
||||
|
||||
sort_types = self._PopulateSortMenuOrList()
|
||||
|
||||
if self._sort_type in sort_types:
|
||||
|
||||
index = sort_types.index( self._sort_type )
|
||||
|
||||
new_index = ( index + index_delta ) % len( sort_types )
|
||||
|
||||
new_sort_type = sort_types[ new_index ]
|
||||
|
||||
self._SetSortType( new_sort_type )
|
||||
|
||||
|
||||
event.accept()
|
||||
|
||||
|
||||
def SetSort( self, media_sort: ClientMedia.MediaSort ):
|
||||
|
||||
self._sort_type = media_sort.sort_type
|
||||
self._sort_order_choice.SetValue( media_sort.sort_order )
|
||||
|
||||
self._UpdateSortTypeLabel()
|
||||
self._UpdateAscLabels()
|
||||
|
||||
|
|
@ -19,14 +19,13 @@ from hydrus.core import HydrusText
|
|||
|
||||
from hydrus.client import ClientApplicationCommand as CAC
|
||||
from hydrus.client import ClientConstants as CC
|
||||
from hydrus.client.gui import ClientGUIACDropdown
|
||||
from hydrus.client.gui import ClientGUICommon
|
||||
from hydrus.client.gui import ClientGUIControls
|
||||
from hydrus.client.gui import ClientGUIDialogs
|
||||
from hydrus.client.gui import ClientGUIDialogsQuick
|
||||
from hydrus.client.gui import ClientGUIFunctions
|
||||
from hydrus.client.gui import ClientGUIImport
|
||||
from hydrus.client.gui import ClientGUISearch
|
||||
from hydrus.client.gui import ClientGUIResultsSortCollect
|
||||
from hydrus.client.gui import ClientGUIScrolledPanels
|
||||
from hydrus.client.gui import ClientGUIScrolledPanelsEdit
|
||||
from hydrus.client.gui import ClientGUIShortcuts
|
||||
|
@ -37,6 +36,8 @@ from hydrus.client.gui import QtPorting as QP
|
|||
from hydrus.client.gui.lists import ClientGUIListBoxes
|
||||
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
|
||||
from hydrus.client.gui.lists import ClientGUIListCtrl
|
||||
from hydrus.client.gui.search import ClientGUIACDropdown
|
||||
from hydrus.client.gui.search import ClientGUISearch
|
||||
from hydrus.client.media import ClientMedia
|
||||
from hydrus.client.networking import ClientNetworkingSessions
|
||||
|
||||
|
@ -2334,13 +2335,13 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
QW.QWidget.__init__( self, parent )
|
||||
|
||||
self._default_media_sort = ClientGUISearch.MediaSortControl( self )
|
||||
self._default_media_sort = ClientGUIResultsSortCollect.MediaSortControl( self )
|
||||
|
||||
self._fallback_media_sort = ClientGUISearch.MediaSortControl( self )
|
||||
self._fallback_media_sort = ClientGUIResultsSortCollect.MediaSortControl( self )
|
||||
|
||||
self._save_page_sort_on_change = QW.QCheckBox( self )
|
||||
|
||||
self._default_media_collect = ClientGUISearch.MediaCollectControl( self, silent = True )
|
||||
self._default_media_collect = ClientGUIResultsSortCollect.MediaCollectControl( self, silent = True )
|
||||
|
||||
namespace_sorting_box = ClientGUICommon.StaticBox( self, 'namespace sorting' )
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ from hydrus.client import ClientSearch
|
|||
from hydrus.client import ClientSerialisable
|
||||
from hydrus.client import ClientThreading
|
||||
from hydrus.client.gui import ClientGUIDragDrop
|
||||
from hydrus.client.gui import ClientGUIACDropdown
|
||||
from hydrus.client.gui import ClientGUIAsync
|
||||
from hydrus.client.gui import ClientGUICommon
|
||||
from hydrus.client.gui import ClientGUIDialogs
|
||||
|
@ -45,6 +44,7 @@ from hydrus.client.gui import ClientGUITopLevelWindowsPanels
|
|||
from hydrus.client.gui import QtPorting as QP
|
||||
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
|
||||
from hydrus.client.gui.lists import ClientGUIListCtrl
|
||||
from hydrus.client.gui.search import ClientGUIACDropdown
|
||||
from hydrus.client.metadata import ClientTags
|
||||
from hydrus.client.networking import ClientNetworkingContexts
|
||||
from hydrus.client.networking import ClientNetworkingDomain
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -22,7 +22,6 @@ from hydrus.client import ClientApplicationCommand as CAC
|
|||
from hydrus.client import ClientConstants as CC
|
||||
from hydrus.client import ClientManagers
|
||||
from hydrus.client.gui import ClientGUIAsync
|
||||
from hydrus.client.gui import ClientGUIACDropdown
|
||||
from hydrus.client.gui import ClientGUICommon
|
||||
from hydrus.client.gui import ClientGUIControls
|
||||
from hydrus.client.gui import ClientGUICore as CGC
|
||||
|
@ -39,6 +38,7 @@ from hydrus.client.gui import QtPorting as QP
|
|||
from hydrus.client.gui.lists import ClientGUIListBoxes
|
||||
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
|
||||
from hydrus.client.gui.lists import ClientGUIListCtrl
|
||||
from hydrus.client.gui.search import ClientGUIACDropdown
|
||||
from hydrus.client.media import ClientMedia
|
||||
from hydrus.client.metadata import ClientTags
|
||||
from hydrus.client.metadata import ClientTagsHandling
|
||||
|
|
|
@ -145,7 +145,7 @@ class DialogApplyCancel( DialogThatTakesScrollablePanel ):
|
|||
|
||||
if len( message ) > 0:
|
||||
|
||||
QW.QMessageBox.critical( self, 'Error', message )
|
||||
QW.QMessageBox.warning( self, 'Cannot OK!', message )
|
||||
|
||||
|
||||
return False
|
||||
|
|
|
@ -22,9 +22,9 @@ from hydrus.client.gui import ClientGUICommon
|
|||
from hydrus.client.gui import ClientGUICore as CGC
|
||||
from hydrus.client.gui import ClientGUIFunctions
|
||||
from hydrus.client.gui import ClientGUIMenus
|
||||
from hydrus.client.gui import ClientGUISearch
|
||||
from hydrus.client.gui import ClientGUIShortcuts
|
||||
from hydrus.client.gui import QtPorting as QP
|
||||
from hydrus.client.gui.search import ClientGUISearch
|
||||
from hydrus.client.media import ClientMedia
|
||||
from hydrus.client.metadata import ClientTags
|
||||
|
||||
|
@ -953,6 +953,7 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
#
|
||||
|
||||
self.setMouseTracking( True )
|
||||
self.setFont( QW.QApplication.font() )
|
||||
|
||||
self._widget_event_filter = QP.WidgetEventFilter( self.widget() )
|
||||
|
@ -1016,6 +1017,11 @@ class ListBox( QW.QScrollArea ):
|
|||
return action_occurred
|
||||
|
||||
|
||||
def _AddEditMenu( self, menu: QW.QMenu ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def _DeleteActivate( self ):
|
||||
|
||||
pass
|
||||
|
@ -1126,11 +1132,11 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
predicates.append( term )
|
||||
|
||||
possible_inverse = term.GetInverseCopy()
|
||||
|
||||
if possible_inverse is not None:
|
||||
if term.IsInvertible():
|
||||
|
||||
inverse_predicates.append( possible_inverse )
|
||||
inverse_predicate = term.GetInverseCopy()
|
||||
|
||||
inverse_predicates.append( inverse_predicate )
|
||||
|
||||
|
||||
else:
|
||||
|
@ -1142,7 +1148,16 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
|
||||
|
||||
return ( predicates, inverse_predicates )
|
||||
if len( predicates ) > 1 and ClientSearch.PREDICATE_TYPE_OR_CONTAINER not in ( p.GetType() for p in predicates ):
|
||||
|
||||
or_predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_OR_CONTAINER, value = list( predicates ) )
|
||||
|
||||
else:
|
||||
|
||||
or_predicate = None
|
||||
|
||||
|
||||
return ( predicates, or_predicate, inverse_predicates )
|
||||
|
||||
|
||||
def _GetSafeHitIndex( self, hit_index, direction = None ):
|
||||
|
@ -1235,7 +1250,7 @@ class ListBox( QW.QScrollArea ):
|
|||
self._Hit( shift, ctrl, hit_index )
|
||||
|
||||
|
||||
def _Hit( self, shift, ctrl, hit_index ):
|
||||
def _Hit( self, shift, ctrl, hit_index, only_add = False ):
|
||||
|
||||
hit_index = self._GetSafeHitIndex( hit_index )
|
||||
|
||||
|
@ -1244,16 +1259,24 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
deselect_all = False
|
||||
|
||||
if shift:
|
||||
if only_add:
|
||||
|
||||
if hit_index is not None:
|
||||
|
||||
to_select.add( hit_index )
|
||||
|
||||
|
||||
elif shift:
|
||||
|
||||
# we are now saying if you shift-click on something already selected, we'll make no changes, but we'll move focus ghost
|
||||
if hit_index is not None and not self._IsSelected( hit_index ):
|
||||
|
||||
if self._last_hit_index is not None:
|
||||
|
||||
lower = min( hit_index, self._last_hit_index )
|
||||
upper = max( hit_index, self._last_hit_index )
|
||||
|
||||
to_select = list( range( lower, upper + 1) )
|
||||
to_select = list( range( lower, upper + 1 ) )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -1543,6 +1566,8 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
self._selected_terms.discard( term )
|
||||
|
||||
self._last_hit_index = None
|
||||
|
||||
del self._terms_to_texts[ term ]
|
||||
|
||||
|
||||
|
@ -1728,6 +1753,22 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
|
||||
|
||||
def mouseMoveEvent( self, event ):
|
||||
|
||||
is_dragging = event.buttons() & QC.Qt.LeftButton
|
||||
|
||||
if is_dragging:
|
||||
|
||||
hit_index = self._GetIndexUnderMouse( event )
|
||||
|
||||
self._Hit( False, False, hit_index, only_add = True )
|
||||
|
||||
else:
|
||||
|
||||
event.ignore()
|
||||
|
||||
|
||||
|
||||
def EventMouseSelect( self, event ):
|
||||
|
||||
self._HandleClick( event )
|
||||
|
@ -2058,61 +2099,28 @@ class ListBoxTags( ListBox ):
|
|||
return False
|
||||
|
||||
|
||||
def _NewSearchPage( self ):
|
||||
|
||||
predicates = []
|
||||
def _NewSearchPages( self, pages_of_predicates ):
|
||||
|
||||
for term in self._selected_terms:
|
||||
activate_window = HG.client_controller.new_options.GetBoolean( 'activate_window_on_tag_search_page_activation' )
|
||||
|
||||
for predicates in pages_of_predicates:
|
||||
|
||||
if isinstance( term, ClientSearch.Predicate ):
|
||||
|
||||
predicates.append( term )
|
||||
|
||||
else:
|
||||
|
||||
predicates.append( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_TAG, term ) )
|
||||
|
||||
predicates = ClientGUISearch.FleshOutPredicates( self, predicates )
|
||||
|
||||
|
||||
predicates = ClientGUISearch.FleshOutPredicates( self, predicates )
|
||||
|
||||
if len( predicates ) > 0:
|
||||
if len( predicates ) == 0:
|
||||
|
||||
break
|
||||
|
||||
|
||||
s = sorted( ( predicate.ToString() for predicate in predicates ) )
|
||||
|
||||
page_name = ', '.join( s )
|
||||
|
||||
activate_window = HG.client_controller.new_options.GetBoolean( 'activate_window_on_tag_search_page_activation' )
|
||||
|
||||
file_service_key = self._GetCurrentFileServiceKey()
|
||||
|
||||
HG.client_controller.pub( 'new_page_query', file_service_key, initial_predicates = predicates, page_name = page_name, activate_window = activate_window )
|
||||
|
||||
|
||||
|
||||
def _NewSearchPageForEach( self ):
|
||||
|
||||
predicates = []
|
||||
|
||||
for term in self._selected_terms:
|
||||
|
||||
if isinstance( term, ClientSearch.Predicate ):
|
||||
|
||||
predicates.append( term )
|
||||
|
||||
else:
|
||||
|
||||
predicates.append( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_TAG, term ) )
|
||||
|
||||
|
||||
|
||||
predicates = ClientGUISearch.FleshOutPredicates( self, predicates )
|
||||
|
||||
for predicate in predicates:
|
||||
|
||||
page_name = predicate.ToString()
|
||||
|
||||
HG.client_controller.pub( 'new_page_query', CC.LOCAL_FILE_SERVICE_KEY, initial_predicates = ( predicate, ), page_name = page_name )
|
||||
activate_window = False
|
||||
|
||||
|
||||
|
||||
|
@ -2243,7 +2251,19 @@ class ListBoxTags( ListBox ):
|
|||
|
||||
if self.can_spawn_new_windows:
|
||||
|
||||
self._NewSearchPage()
|
||||
( predicates, or_predicate, inverse_predicates ) = self._GetSelectedPredicatesAndInverseCopies()
|
||||
|
||||
if len( predicates ) > 0:
|
||||
|
||||
shift_down = event.modifiers() & QC.Qt.ShiftModifier
|
||||
|
||||
if shift_down and or_predicate is not None:
|
||||
|
||||
predicates = ( or_predicate, )
|
||||
|
||||
|
||||
self._NewSearchPages( [ predicates ] )
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2591,17 +2611,28 @@ class ListBoxTags( ListBox ):
|
|||
ClientGUIMenus.AppendMenu( menu, select_menu, 'select' )
|
||||
|
||||
|
||||
( predicates, or_predicate, inverse_predicates ) = self._GetSelectedPredicatesAndInverseCopies()
|
||||
|
||||
if self.can_spawn_new_windows:
|
||||
|
||||
ClientGUIMenus.AppendSeparator( menu )
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'open a new search page for ' + selection_string, 'Open a new search page starting with the selected predicates.', self._NewSearchPage )
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'open a new search page for ' + selection_string, 'Open a new search page starting with the selected predicates.', self._NewSearchPages, [ predicates ] )
|
||||
|
||||
if len( self._selected_terms ) > 1:
|
||||
if or_predicate is not None:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'open new search pages for each in selection', 'Open one new search page for each selected predicate.', self._NewSearchPageForEach )
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'open a new OR search page for ' + selection_string, 'Open a new search page starting with the selected merged as an OR search predicate.', self._NewSearchPages, [ ( or_predicate, ) ] )
|
||||
|
||||
|
||||
if len( predicates ) > 1:
|
||||
|
||||
for_each_predicates = [ ( predicate, ) for predicate in predicates ]
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'open new search pages for each in selection', 'Open one new search page for each selected predicate.', self._NewSearchPages, for_each_predicates )
|
||||
|
||||
|
||||
|
||||
self._AddEditMenu( menu )
|
||||
|
||||
if self._CanProvideCurrentPagePredicates():
|
||||
|
||||
|
@ -2609,8 +2640,6 @@ class ListBoxTags( ListBox ):
|
|||
|
||||
ClientGUIMenus.AppendSeparator( menu )
|
||||
|
||||
( predicates, inverse_predicates ) = self._GetSelectedPredicatesAndInverseCopies()
|
||||
|
||||
predicates = set( predicates )
|
||||
inverse_predicates = set( inverse_predicates )
|
||||
|
||||
|
@ -2629,14 +2658,19 @@ class ListBoxTags( ListBox ):
|
|||
|
||||
if some_selected_in_current:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'discard {} from current search'.format( predicates_selection_string ), 'Remove the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'remove_predicates' )
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'remove {} from current search'.format( predicates_selection_string ), 'Remove the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'remove_predicates' )
|
||||
|
||||
|
||||
some_selected_not_in_current = len( predicates.intersection( current_predicates ) ) < len( predicates )
|
||||
|
||||
if some_selected_not_in_current:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'require {} for current search'.format( predicates_selection_string ), 'Add the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'add_predicates' )
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'add {} to current search'.format( predicates_selection_string ), 'Add the selected predicates to the current search.', self._ProcessMenuPredicateEvent, 'add_predicates' )
|
||||
|
||||
|
||||
if or_predicate is not None:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'add an OR of {} to current search'.format( predicates_selection_string ), 'Add the selected predicates as an OR predicate to the current search.', self._ProcessMenuPredicateEvent, 'add_or_predicate' )
|
||||
|
||||
|
||||
some_selected_are_excluded_explicitly = HydrusData.SetsIntersect( inverse_predicates, current_predicates )
|
||||
|
@ -2868,13 +2902,6 @@ class ListBoxTagsPredicates( ListBoxTags ):
|
|||
return predicate in self._terms
|
||||
|
||||
|
||||
def _Hit( self, shift, ctrl, hit_index ):
|
||||
|
||||
hit_index = self._GetSafeHitIndex( hit_index )
|
||||
|
||||
ListBoxTags._Hit( self, shift, ctrl, hit_index )
|
||||
|
||||
|
||||
def _Select( self, index ):
|
||||
|
||||
to_select = self._GetWithParentIndices( index )
|
||||
|
|
|
@ -21,12 +21,13 @@ from hydrus.client.gui import ClientGUICommon
|
|||
from hydrus.client.gui import ClientGUICore as CGC
|
||||
from hydrus.client.gui import ClientGUIFunctions
|
||||
from hydrus.client.gui import ClientGUIMenus
|
||||
from hydrus.client.gui import ClientGUIResultsSortCollect
|
||||
from hydrus.client.gui import ClientGUIScrolledPanels
|
||||
from hydrus.client.gui import ClientGUIShortcuts
|
||||
from hydrus.client.gui import ClientGUISearch
|
||||
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
|
||||
from hydrus.client.gui import QtPorting as QP
|
||||
from hydrus.client.gui.lists import ClientGUIListBoxes
|
||||
from hydrus.client.gui.search import ClientGUISearch
|
||||
from hydrus.client.metadata import ClientTags
|
||||
|
||||
from hydrus.external import LogicExpressionQueryParser
|
||||
|
@ -1494,7 +1495,7 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
|
|||
searchChanged = QC.Signal( ClientSearch.FileSearchContext )
|
||||
searchCancelled = QC.Signal()
|
||||
|
||||
def __init__( self, parent: QW.QWidget, page_key, file_search_context: ClientSearch.FileSearchContext, media_sort_widget: typing.Optional[ ClientGUISearch.MediaSortControl ] = None, media_collect_widget: typing.Optional[ ClientGUISearch.MediaCollectControl ] = None, media_callable = None, synchronised = True, include_unusual_predicate_types = True, allow_all_known_files = True, force_system_everything = False, hide_favourites_edit_actions = False ):
|
||||
def __init__( self, parent: QW.QWidget, page_key, file_search_context: ClientSearch.FileSearchContext, media_sort_widget: typing.Optional[ ClientGUIResultsSortCollect.MediaSortControl ] = None, media_collect_widget: typing.Optional[ ClientGUIResultsSortCollect.MediaCollectControl ] = None, media_callable = None, synchronised = True, include_unusual_predicate_types = True, allow_all_known_files = True, force_system_everything = False, hide_favourites_edit_actions = False ):
|
||||
|
||||
self._page_key = page_key
|
||||
|
||||
|
@ -1834,7 +1835,7 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
|
|||
|
||||
def _ManageFavouriteSearches( self, favourite_search_row_to_save = None ):
|
||||
|
||||
from hydrus.client.gui import ClientGUISearchPanels
|
||||
from hydrus.client.gui.search import ClientGUISearchPanels
|
||||
|
||||
favourite_searches_rows = HG.client_controller.favourite_search_manager.GetFavouriteSearchRows()
|
||||
|
||||
|
@ -2118,6 +2119,8 @@ class ListBoxTagsActiveSearchPredicates( ClientGUIListBoxes.ListBoxTagsPredicate
|
|||
self._AppendTerm( predicate )
|
||||
|
||||
|
||||
self._SortByText()
|
||||
|
||||
self._DataHasChanged()
|
||||
|
||||
|
||||
|
@ -2143,6 +2146,29 @@ class ListBoxTagsActiveSearchPredicates( ClientGUIListBoxes.ListBoxTagsPredicate
|
|||
return False
|
||||
|
||||
|
||||
def _AddEditMenu( self, menu: QW.QMenu ):
|
||||
|
||||
( editable_predicates, non_editable_predicates ) = ClientGUISearch.GetEditablePredicates( self._selected_terms )
|
||||
|
||||
if len( editable_predicates ) > 0:
|
||||
|
||||
ClientGUIMenus.AppendSeparator( menu )
|
||||
|
||||
if len( editable_predicates ) == 1:
|
||||
|
||||
desc = list( editable_predicates )[0].ToString()
|
||||
|
||||
else:
|
||||
|
||||
desc = '{} search terms'.format( HydrusData.ToHumanInt( len( editable_predicates ) ) )
|
||||
|
||||
|
||||
label = 'edit {}'.format( desc )
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, label, 'Edit these predicates and refresh the search. Not all predicates are editable.', self._EditPredicates, editable_predicates )
|
||||
|
||||
|
||||
|
||||
def _CanProvideCurrentPagePredicates( self ):
|
||||
|
||||
return True
|
||||
|
@ -2188,6 +2214,8 @@ class ListBoxTagsActiveSearchPredicates( ClientGUIListBoxes.ListBoxTagsPredicate
|
|||
self._AppendTerm( predicate )
|
||||
|
||||
|
||||
self._selected_terms.update( predicates_to_add )
|
||||
|
||||
self._SortByText()
|
||||
|
||||
self._DataHasChanged()
|
||||
|
@ -2264,12 +2292,19 @@ class ListBoxTagsActiveSearchPredicates( ClientGUIListBoxes.ListBoxTagsPredicate
|
|||
|
||||
def _ProcessMenuPredicateEvent( self, command ):
|
||||
|
||||
( predicates, inverse_predicates ) = self._GetSelectedPredicatesAndInverseCopies()
|
||||
( predicates, or_predicate, inverse_predicates ) = self._GetSelectedPredicatesAndInverseCopies()
|
||||
|
||||
if command == 'add_predicates':
|
||||
|
||||
self._EnterPredicates( predicates, permit_remove = False )
|
||||
|
||||
elif command == 'add_or_predicate':
|
||||
|
||||
if or_predicate is not None:
|
||||
|
||||
self._EnterPredicates( ( or_predicate, ), permit_remove = False )
|
||||
|
||||
|
||||
elif command == 'remove_predicates':
|
||||
|
||||
self._EnterPredicates( predicates, permit_add = False )
|
||||
|
@ -2301,6 +2336,8 @@ class ListBoxTagsActiveSearchPredicates( ClientGUIListBoxes.ListBoxTagsPredicate
|
|||
self._AppendTerm( predicate )
|
||||
|
||||
|
||||
self._SortByText()
|
||||
|
||||
self._DataHasChanged()
|
||||
|
||||
|
|
@ -0,0 +1,418 @@
|
|||
import typing
|
||||
|
||||
from qtpy import QtCore as QC
|
||||
from qtpy import QtWidgets as QW
|
||||
|
||||
from hydrus.core import HydrusConstants as HC
|
||||
from hydrus.core import HydrusExceptions
|
||||
from hydrus.core import HydrusGlobals as HG
|
||||
|
||||
from hydrus.client import ClientConstants as CC
|
||||
from hydrus.client import ClientSearch
|
||||
from hydrus.client.gui import ClientGUICommon
|
||||
from hydrus.client.gui import ClientGUIRatings
|
||||
from hydrus.client.gui import QtPorting as QP
|
||||
from hydrus.client.gui.search import ClientGUIPredicatesSingle
|
||||
from hydrus.client.metadata import ClientRatings
|
||||
|
||||
class PredicateSystemRatingLikeControl( QW.QWidget ):
|
||||
|
||||
def __init__( self, parent: QW.QWidget, service_key: bytes, predicate: typing.Optional[ ClientSearch.Predicate ] ):
|
||||
|
||||
QW.QWidget.__init__( self, parent )
|
||||
|
||||
self._service_key = service_key
|
||||
|
||||
service = HG.client_controller.services_manager.GetService( self._service_key )
|
||||
|
||||
name = service.GetName()
|
||||
|
||||
name_st = ClientGUICommon.BetterStaticText( self, name )
|
||||
|
||||
name_st.setAlignment( QC.Qt.AlignLeft | QC.Qt.AlignVCenter )
|
||||
|
||||
self._rated_checkbox = QW.QCheckBox( 'rated', self )
|
||||
self._not_rated_checkbox = QW.QCheckBox( 'not rated', self )
|
||||
self._rating_control = ClientGUIRatings.RatingLikeDialog( self, service_key )
|
||||
|
||||
#
|
||||
|
||||
if predicate is not None:
|
||||
|
||||
value = predicate.GetValue()
|
||||
|
||||
if value is not None:
|
||||
|
||||
( operator, rating, service_key ) = value
|
||||
|
||||
if rating == 'rated':
|
||||
|
||||
self._rated_checkbox.setChecked( True )
|
||||
|
||||
elif rating == 'not rated':
|
||||
|
||||
self._not_rated_checkbox.setChecked( True )
|
||||
|
||||
else:
|
||||
|
||||
if rating == 0:
|
||||
|
||||
self._rating_control.SetRatingState( ClientRatings.DISLIKE )
|
||||
|
||||
else:
|
||||
|
||||
self._rating_control.SetRatingState( ClientRatings.LIKE )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#
|
||||
|
||||
hbox = QP.HBoxLayout()
|
||||
|
||||
QP.AddToLayout( hbox, name_st, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
QP.AddToLayout( hbox, self._rated_checkbox, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, self._not_rated_checkbox, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, self._rating_control, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
|
||||
self.setLayout( hbox )
|
||||
|
||||
|
||||
def GetPredicates( self ):
|
||||
|
||||
rating = None
|
||||
|
||||
if self._rated_checkbox.isChecked():
|
||||
|
||||
rating = 'rated'
|
||||
|
||||
elif self._not_rated_checkbox.isChecked():
|
||||
|
||||
rating = 'not rated'
|
||||
|
||||
else:
|
||||
|
||||
rating_state = self._rating_control.GetRatingState()
|
||||
|
||||
if rating_state == ClientRatings.LIKE:
|
||||
|
||||
rating = 1
|
||||
|
||||
elif rating_state == ClientRatings.DISLIKE:
|
||||
|
||||
rating = 0
|
||||
|
||||
|
||||
|
||||
if rating is None:
|
||||
|
||||
return []
|
||||
|
||||
else:
|
||||
|
||||
predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATING, ( '=', rating, self._service_key ) )
|
||||
|
||||
return [ predicate ]
|
||||
|
||||
|
||||
|
||||
class PredicateSystemRatingNumericalControl( QW.QWidget ):
|
||||
|
||||
def __init__( self, parent: QW.QWidget, service_key: bytes, predicate: typing.Optional[ ClientSearch.Predicate ] ):
|
||||
|
||||
QW.QWidget.__init__( self, parent )
|
||||
|
||||
self._service_key = service_key
|
||||
|
||||
service = HG.client_controller.services_manager.GetService( self._service_key )
|
||||
|
||||
name = service.GetName()
|
||||
|
||||
name_st = ClientGUICommon.BetterStaticText( self, name )
|
||||
|
||||
name_st.setAlignment( QC.Qt.AlignLeft | QC.Qt.AlignVCenter )
|
||||
|
||||
self._rated_checkbox = QW.QCheckBox( 'rated', self )
|
||||
self._not_rated_checkbox = QW.QCheckBox( 'not rated', self )
|
||||
self._operator = QP.RadioBox( self, choices = [ '>', '<', '=', '\u2248'] )
|
||||
self._rating_control = ClientGUIRatings.RatingNumericalDialog( self, service_key )
|
||||
|
||||
self._operator.Select( 2 )
|
||||
|
||||
#
|
||||
|
||||
if predicate is not None:
|
||||
|
||||
value = predicate.GetValue()
|
||||
|
||||
if value is not None:
|
||||
|
||||
( operator, rating, service_key ) = value
|
||||
|
||||
if rating == 'rated':
|
||||
|
||||
self._rated_checkbox.setChecked( True )
|
||||
|
||||
elif rating == 'not rated':
|
||||
|
||||
self._not_rated_checkbox.setChecked( True )
|
||||
|
||||
else:
|
||||
|
||||
self._operator.SetStringSelection( operator )
|
||||
|
||||
self._rating_control.SetRating( rating )
|
||||
|
||||
|
||||
|
||||
|
||||
#
|
||||
|
||||
hbox = QP.HBoxLayout()
|
||||
|
||||
QP.AddToLayout( hbox, name_st, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
QP.AddToLayout( hbox, self._rated_checkbox, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, self._not_rated_checkbox, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, self._operator, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, self._rating_control, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
|
||||
self.setLayout( hbox )
|
||||
|
||||
|
||||
def GetPredicates( self ):
|
||||
|
||||
rating = None
|
||||
|
||||
if self._rated_checkbox.isChecked():
|
||||
|
||||
operator = '='
|
||||
rating = 'rated'
|
||||
|
||||
elif self._not_rated_checkbox.isChecked():
|
||||
|
||||
operator = '='
|
||||
rating = 'not rated'
|
||||
|
||||
elif self._rating_control.GetRatingState() != ClientRatings.NULL:
|
||||
|
||||
operator = self._operator.GetStringSelection()
|
||||
|
||||
rating = self._rating_control.GetRating()
|
||||
|
||||
|
||||
if rating is None:
|
||||
|
||||
return []
|
||||
|
||||
else:
|
||||
|
||||
predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATING, ( operator, rating, self._service_key ) )
|
||||
|
||||
return [ predicate ]
|
||||
|
||||
|
||||
|
||||
class PanelPredicateSystemMultiple( ClientGUIPredicatesSingle.PanelPredicateSystem ):
|
||||
|
||||
def _FilterWhatICanEdit( self, predicates: typing.Collection[ ClientSearch.Predicate ] ) -> typing.Collection[ ClientSearch.Predicate ]:
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def _GetPredicatesToInitialisePanelWith( self, predicates: typing.Collection[ ClientSearch.Predicate ] ) -> typing.Collection[ ClientSearch.Predicate ]:
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def ClearCustomDefault( self ):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def GetDefaultPredicates( self ) -> typing.Collection[ ClientSearch.Predicate ]:
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def GetPredicates( self ):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def SaveCustomDefault( self ):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def SetPredicates( self, predicates: typing.Collection[ ClientSearch.Predicate ] ):
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def UsesCustomDefault( self ) -> bool:
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class PanelPredicateSystemRating( PanelPredicateSystemMultiple ):
|
||||
|
||||
def __init__( self, parent, predicates ):
|
||||
|
||||
PanelPredicateSystemMultiple.__init__( self, parent )
|
||||
|
||||
#
|
||||
|
||||
local_like_service_keys = HG.client_controller.services_manager.GetServiceKeys( ( HC.LOCAL_RATING_LIKE, ) )
|
||||
|
||||
self._like_checkboxes_to_info = {}
|
||||
|
||||
self._like_rating_ctrls = []
|
||||
|
||||
gridbox = QP.GridLayout( cols = 5 )
|
||||
|
||||
gridbox.setColumnStretch( 0, 1 )
|
||||
|
||||
predicates = self._GetPredicatesToInitialisePanelWith( predicates )
|
||||
|
||||
service_keys_to_predicates = { predicate.GetValue()[2] : predicate for predicate in predicates }
|
||||
|
||||
self._rating_panels = []
|
||||
|
||||
for service_key in local_like_service_keys:
|
||||
|
||||
if service_key in service_keys_to_predicates:
|
||||
|
||||
predicate = service_keys_to_predicates[ service_key ]
|
||||
|
||||
else:
|
||||
|
||||
predicate = None
|
||||
|
||||
|
||||
panel = PredicateSystemRatingLikeControl( self, service_key, predicate )
|
||||
|
||||
self._rating_panels.append( panel )
|
||||
|
||||
|
||||
#
|
||||
|
||||
local_numerical_service_keys = HG.client_controller.services_manager.GetServiceKeys( ( HC.LOCAL_RATING_NUMERICAL, ) )
|
||||
|
||||
self._numerical_checkboxes_to_info = {}
|
||||
|
||||
self._numerical_rating_ctrls_to_info = {}
|
||||
|
||||
for service_key in local_numerical_service_keys:
|
||||
|
||||
if service_key in service_keys_to_predicates:
|
||||
|
||||
predicate = service_keys_to_predicates[ service_key ]
|
||||
|
||||
else:
|
||||
|
||||
predicate = None
|
||||
|
||||
|
||||
panel = PredicateSystemRatingNumericalControl( self, service_key, predicate )
|
||||
|
||||
self._rating_panels.append( panel )
|
||||
|
||||
|
||||
#
|
||||
|
||||
vbox = QP.VBoxLayout()
|
||||
|
||||
for panel in self._rating_panels:
|
||||
|
||||
QP.AddToLayout( vbox, panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
||||
def _FilterWhatICanEdit( self, predicates: typing.Collection[ ClientSearch.Predicate ] ) -> typing.Collection[ ClientSearch.Predicate ]:
|
||||
|
||||
local_rating_service_keys = HG.client_controller.services_manager.GetServiceKeys( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
|
||||
|
||||
good_predicates = []
|
||||
|
||||
for predicate in predicates:
|
||||
|
||||
value = predicate.GetValue()
|
||||
|
||||
if value is not None:
|
||||
|
||||
( operator, rating, service_key ) = value
|
||||
|
||||
if service_key in local_rating_service_keys:
|
||||
|
||||
good_predicates.append( predicate )
|
||||
|
||||
|
||||
|
||||
|
||||
return good_predicates
|
||||
|
||||
|
||||
def _GetPredicatesToInitialisePanelWith( self, predicates: typing.Collection[ ClientSearch.Predicate ] ) -> typing.Collection[ ClientSearch.Predicate ]:
|
||||
|
||||
predicates = self._FilterWhatICanEdit( predicates )
|
||||
|
||||
if len( predicates ) > 0:
|
||||
|
||||
return predicates
|
||||
|
||||
|
||||
custom_default_predicates = HG.client_controller.new_options.GetCustomDefaultSystemPredicates( predicate_type = ClientSearch.PREDICATE_TYPE_SYSTEM_RATING )
|
||||
|
||||
custom_default_predicates = self._FilterWhatICanEdit( custom_default_predicates )
|
||||
|
||||
if len( custom_default_predicates ) > 0:
|
||||
|
||||
return custom_default_predicates
|
||||
|
||||
|
||||
default_predicates = self.GetDefaultPredicates()
|
||||
|
||||
return default_predicates
|
||||
|
||||
|
||||
def ClearCustomDefault( self ):
|
||||
|
||||
HG.client_controller.new_options.ClearCustomDefaultSystemPredicates( predicate_type = ClientSearch.PREDICATE_TYPE_SYSTEM_RATING )
|
||||
|
||||
|
||||
def GetDefaultPredicates( self ):
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def GetPredicates( self ):
|
||||
|
||||
predicates = []
|
||||
|
||||
for panel in self._rating_panels:
|
||||
|
||||
predicates.extend( panel.GetPredicates() )
|
||||
|
||||
|
||||
return predicates
|
||||
|
||||
|
||||
def SaveCustomDefault( self ):
|
||||
|
||||
predicates = self.GetPredicates()
|
||||
|
||||
HG.client_controller.new_options.SetCustomDefaultSystemPredicates( predicate_type = ClientSearch.PREDICATE_TYPE_SYSTEM_RATING, predicates = predicates )
|
||||
|
||||
|
||||
def UsesCustomDefault( self ) -> bool:
|
||||
|
||||
custom_default_predicates = HG.client_controller.new_options.GetCustomDefaultSystemPredicates( predicate_type = ClientSearch.PREDICATE_TYPE_SYSTEM_RATING )
|
||||
|
||||
custom_default_predicates = self._FilterWhatICanEdit( custom_default_predicates )
|
||||
|
||||
return len( custom_default_predicates ) > 0
|
||||
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import typing
|
||||
|
||||
from qtpy import QtWidgets as QW
|
||||
|
||||
from hydrus.core import HydrusData
|
||||
from hydrus.core import HydrusExceptions
|
||||
|
||||
from hydrus.client import ClientConstants as CC
|
||||
from hydrus.client import ClientSearch
|
||||
from hydrus.client.gui import QtPorting as QP
|
||||
|
||||
# ultimately, rewrite acread to be two classes, acread and acreadthatsupportsor
|
||||
# and then this guy only imports the base class, and only the supportsor will know about this
|
||||
# otherwise we have jank imports, and also nested OR lmao
|
||||
# also this should take file and tag domain
|
||||
|
||||
class ORPredicateControl( QW.QWidget ):
|
||||
|
||||
def __init__( self, parent: QW.QWidget, predicate: ClientSearch.Predicate ):
|
||||
|
||||
QW.QWidget.__init__( self, parent )
|
||||
|
||||
from hydrus.client.gui.search import ClientGUIACDropdown
|
||||
|
||||
if predicate.GetType() != ClientSearch.PREDICATE_TYPE_OR_CONTAINER:
|
||||
|
||||
raise Exception( 'Launched an ORPredicateControl without an OR Pred!' )
|
||||
|
||||
|
||||
predicates = predicate.GetValue()
|
||||
|
||||
page_key = HydrusData.GenerateKey()
|
||||
|
||||
file_search_context = ClientSearch.FileSearchContext( file_service_key = CC.LOCAL_FILE_SERVICE_KEY, predicates = predicates )
|
||||
|
||||
self._search_control = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self, page_key, file_search_context, hide_favourites_edit_actions = True )
|
||||
|
||||
vbox = QP.VBoxLayout()
|
||||
|
||||
QP.AddToLayout( vbox, self._search_control, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
||||
def CheckValid( self ):
|
||||
|
||||
try:
|
||||
|
||||
predicates = self.GetPredicates()
|
||||
|
||||
except Exception as e:
|
||||
|
||||
raise HydrusExceptions.VetoException( str( e ) )
|
||||
|
||||
|
||||
|
||||
def GetPredicates( self ):
|
||||
|
||||
or_sub_predicates = self._search_control.GetPredicates()
|
||||
|
||||
if len( or_sub_predicates ) == 0:
|
||||
|
||||
return []
|
||||
|
||||
elif len( or_sub_predicates ) == 1:
|
||||
|
||||
return or_sub_predicates
|
||||
|
||||
|
||||
or_predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_OR_CONTAINER, or_sub_predicates )
|
||||
|
||||
return [ or_predicate ]
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,699 @@
|
|||
import os
|
||||
import typing
|
||||
|
||||
from qtpy import QtCore as QC
|
||||
from qtpy import QtWidgets as QW
|
||||
|
||||
from hydrus.core import HydrusConstants as HC
|
||||
from hydrus.core import HydrusExceptions
|
||||
from hydrus.core import HydrusGlobals as HG
|
||||
|
||||
from hydrus.client import ClientConstants as CC
|
||||
from hydrus.client import ClientSearch
|
||||
from hydrus.client.gui import ClientGUICommon
|
||||
from hydrus.client.gui import ClientGUICore as CGC
|
||||
from hydrus.client.gui import ClientGUIMenus
|
||||
from hydrus.client.gui import ClientGUIScrolledPanels
|
||||
from hydrus.client.gui import ClientGUIShortcuts
|
||||
from hydrus.client.gui import QtPorting as QP
|
||||
from hydrus.client.gui.search import ClientGUIPredicatesMultiple
|
||||
from hydrus.client.gui.search import ClientGUIPredicatesSingle
|
||||
from hydrus.client.gui.search import ClientGUIPredicatesOR
|
||||
|
||||
EDIT_PRED_TYPES = {
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_AGE,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_PIXELS,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_FRAMERATE,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_FRAMES,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_SERVICE,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_HASH,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_MIME,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_RATING,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_NOTES,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_NOTE_NAME,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_WORDS,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_SIMILAR_TO,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_SIZE,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_TAG_AS_NUMBER,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_COUNT,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS,
|
||||
ClientSearch.PREDICATE_TYPE_OR_CONTAINER
|
||||
}
|
||||
|
||||
FLESH_OUT_SYSTEM_PRED_TYPES = {
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_SIZE,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_DIMENSIONS,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_AGE,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_HASH,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_AUDIO,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_WORDS,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_MIME,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_RATING,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_SIMILAR_TO,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_SERVICE,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_TAG_AS_NUMBER,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_NOTES,
|
||||
ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS
|
||||
}
|
||||
|
||||
def EditPredicates( widget: QW.QWidget, predicates: typing.Collection[ ClientSearch.Predicate ] ) -> typing.List[ ClientSearch.Predicate ]:
|
||||
|
||||
( editable_predicates, non_editable_predicates ) = GetEditablePredicates( predicates )
|
||||
|
||||
window = widget.window()
|
||||
|
||||
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
|
||||
|
||||
if len( editable_predicates ) == 1 and editable_predicates[0].IsInvertible():
|
||||
|
||||
result = list( non_editable_predicates )
|
||||
result.append( editable_predicates[0].GetInverseCopy() )
|
||||
|
||||
return result
|
||||
|
||||
elif len( editable_predicates ) > 0:
|
||||
|
||||
with ClientGUITopLevelWindowsPanels.DialogEdit( window, 'edit predicates' ) as dlg:
|
||||
|
||||
panel = EditPredicatesPanel( dlg, predicates )
|
||||
|
||||
dlg.SetPanel( panel )
|
||||
|
||||
if dlg.exec() == QW.QDialog.Accepted:
|
||||
|
||||
edited_predicates = panel.GetValue()
|
||||
|
||||
HG.client_controller.new_options.PushRecentPredicates( edited_predicates )
|
||||
|
||||
result = list( non_editable_predicates )
|
||||
result.extend( edited_predicates )
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
raise HydrusExceptions.CancelledException()
|
||||
|
||||
def FilterAndConvertLabelPredicates( predicates: typing.Collection[ ClientSearch.Predicate ] ) -> typing.List[ ClientSearch.Predicate ]:
|
||||
|
||||
good_predicates = []
|
||||
|
||||
for predicate in predicates:
|
||||
|
||||
predicate = predicate.GetCountlessCopy()
|
||||
|
||||
predicate_type = predicate.GetType()
|
||||
|
||||
if predicate_type in ( ClientSearch.PREDICATE_TYPE_LABEL, ClientSearch.PREDICATE_TYPE_PARENT ):
|
||||
|
||||
continue
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_UNTAGGED:
|
||||
|
||||
predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( None, '=', 0 ) )
|
||||
|
||||
|
||||
good_predicates.append( predicate )
|
||||
|
||||
|
||||
return good_predicates
|
||||
|
||||
def FleshOutPredicates( widget: QW.QWidget, predicates: typing.Collection[ ClientSearch.Predicate ] ) -> typing.List[ ClientSearch.Predicate ]:
|
||||
|
||||
window = widget.window()
|
||||
|
||||
predicates = FilterAndConvertLabelPredicates( predicates )
|
||||
|
||||
good_predicates = []
|
||||
|
||||
for predicate in predicates:
|
||||
|
||||
value = predicate.GetValue()
|
||||
predicate_type = predicate.GetType()
|
||||
|
||||
if value is None and predicate_type in FLESH_OUT_SYSTEM_PRED_TYPES:
|
||||
|
||||
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
|
||||
|
||||
with ClientGUITopLevelWindowsPanels.DialogEdit( window, 'input predicate', hide_buttons = True ) as dlg:
|
||||
|
||||
panel = FleshOutPredicatePanel( dlg, predicate )
|
||||
|
||||
dlg.SetPanel( panel )
|
||||
|
||||
if dlg.exec() == QW.QDialog.Accepted:
|
||||
|
||||
preds = panel.GetValue()
|
||||
|
||||
HG.client_controller.new_options.PushRecentPredicates( preds )
|
||||
|
||||
good_predicates.extend( preds )
|
||||
|
||||
|
||||
|
||||
else:
|
||||
|
||||
good_predicates.append( predicate )
|
||||
|
||||
|
||||
|
||||
return good_predicates
|
||||
|
||||
def GetEditablePredicates( predicates: typing.Collection[ ClientSearch.Predicate ] ):
|
||||
|
||||
editable_predicates = [ predicate for predicate in predicates if predicate.GetType() in EDIT_PRED_TYPES or predicate.IsInvertible() ]
|
||||
non_editable_predicates = [ predicate for predicate in predicates if predicate not in editable_predicates ]
|
||||
|
||||
return ( editable_predicates, non_editable_predicates )
|
||||
|
||||
class EditPredicatesPanel( ClientGUIScrolledPanels.EditPanel ):
|
||||
|
||||
def __init__( self, parent, predicates: typing.Collection[ ClientSearch.Predicate ] ):
|
||||
|
||||
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
|
||||
|
||||
predicates = list( predicates )
|
||||
|
||||
predicates.sort( key = lambda p: p.ToString( with_count = False ) )
|
||||
|
||||
self._uneditable_predicates = []
|
||||
|
||||
self._invertible_pred_buttons = []
|
||||
self._editable_pred_panels = []
|
||||
|
||||
rating_preds = []
|
||||
|
||||
# I hate this pred comparison stuff, but let's hang in there until we split this stuff up by type mate
|
||||
# then we can just have a dict type->panel_class lookup or whatever
|
||||
# also it would be nice to have proper rating editing here, think about it
|
||||
|
||||
AGE_DELTA_PRED = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( '>', 'delta', ( 2000, 1, 1, 1 ) ) )
|
||||
MODIFIED_DELTA_PRED = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME, ( '>', 'delta', ( 2000, 1, 1, 1 ) ) )
|
||||
KNOWN_URL_EXACT = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( True, 'exact_match', '', '' ) )
|
||||
KNOWN_URL_DOMAIN = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( True, 'domain', '', '' ) )
|
||||
KNOWN_URL_REGEX = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( True, 'regex', '', '' ) )
|
||||
FILE_VIEWS_PRED = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS, ( 'views', ( 'media', ), '>', 0 ) )
|
||||
|
||||
for predicate in predicates:
|
||||
|
||||
predicate_type = predicate.GetType()
|
||||
|
||||
if predicate_type == ClientSearch.PREDICATE_TYPE_OR_CONTAINER:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesOR.ORPredicateControl( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_AGE:
|
||||
|
||||
if predicate.IsUIEditable( AGE_DELTA_PRED ):
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemAgeDelta( self, predicate ) )
|
||||
|
||||
else:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemAgeDate( self, predicate ) )
|
||||
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME:
|
||||
|
||||
if predicate.IsUIEditable( MODIFIED_DELTA_PRED ):
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemModifiedDelta( self, predicate ) )
|
||||
|
||||
else:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemModifiedDate( self, predicate ) )
|
||||
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemHeight( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemWidth( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemRatio( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_PIXELS:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemNumPixels( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemDuration( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FRAMERATE:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemFramerate( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_FRAMES:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemNumFrames( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_SERVICE:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemFileService( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS:
|
||||
|
||||
if predicate.IsUIEditable( KNOWN_URL_EXACT ):
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemKnownURLsExactURL( self, predicate ) )
|
||||
|
||||
elif predicate.IsUIEditable( KNOWN_URL_DOMAIN ):
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemKnownURLsDomain( self, predicate ) )
|
||||
|
||||
elif predicate.IsUIEditable( KNOWN_URL_REGEX ):
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemKnownURLsRegex( self, predicate ) )
|
||||
|
||||
else:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemKnownURLsURLClass( self, predicate ) )
|
||||
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_HASH:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemHash( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemLimit( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_MIME:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemMime( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemNumTags( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_NOTES:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemNumNotes( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_NOTE_NAME:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemHasNoteName( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_WORDS:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemNumWords( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_SIMILAR_TO:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemSimilarTo( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_SIZE:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemSize( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_TAG_AS_NUMBER:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemTagAsNumber( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_COUNT:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemDuplicateRelationships( self, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS:
|
||||
|
||||
if predicate.IsUIEditable( FILE_VIEWS_PRED ):
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemFileViewingStatsViews( self, predicate ) )
|
||||
|
||||
else:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesSingle.PanelPredicateSystemFileViewingStatsViewtime( self, predicate ) )
|
||||
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_RATING:
|
||||
|
||||
rating_preds.append( predicate )
|
||||
|
||||
elif predicate.IsInvertible():
|
||||
|
||||
self._invertible_pred_buttons.append( ClientGUIPredicatesSingle.InvertiblePredicateButton( self, predicate ) )
|
||||
|
||||
else:
|
||||
|
||||
self._uneditable_predicates.append( predicate )
|
||||
|
||||
|
||||
|
||||
if len( rating_preds ) > 0:
|
||||
|
||||
self._editable_pred_panels.append( ClientGUIPredicatesMultiple.PanelPredicateSystemRating( self, rating_preds ) )
|
||||
|
||||
|
||||
vbox = QP.VBoxLayout()
|
||||
|
||||
for button in self._invertible_pred_buttons:
|
||||
|
||||
QP.AddToLayout( vbox, button, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
|
||||
for panel in self._editable_pred_panels:
|
||||
|
||||
QP.AddToLayout( vbox, panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
|
||||
self.widget().setLayout( vbox )
|
||||
|
||||
|
||||
def CheckValid( self ):
|
||||
|
||||
for panel in self._editable_pred_panels:
|
||||
|
||||
panel.CheckValid()
|
||||
|
||||
|
||||
|
||||
def GetValue( self ):
|
||||
|
||||
return_predicates = list( self._uneditable_predicates )
|
||||
|
||||
for button in self._invertible_pred_buttons:
|
||||
|
||||
return_predicates.append( button.GetPredicate() )
|
||||
|
||||
|
||||
for panel in self._editable_pred_panels:
|
||||
|
||||
return_predicates.extend( panel.GetPredicates() )
|
||||
|
||||
|
||||
return return_predicates
|
||||
|
||||
|
||||
class FleshOutPredicatePanel( ClientGUIScrolledPanels.EditPanel ):
|
||||
|
||||
def __init__( self, parent, predicate: ClientSearch.Predicate ):
|
||||
|
||||
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
|
||||
|
||||
predicate_type = predicate.GetType()
|
||||
|
||||
self._predicates = []
|
||||
|
||||
label = None
|
||||
editable_pred_panels = []
|
||||
static_pred_buttons = []
|
||||
|
||||
recent_predicate_types = [ predicate_type ]
|
||||
|
||||
if predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_AGE:
|
||||
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( '<', 'delta', ( 0, 0, 1, 0 ) ) ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( '<', 'delta', ( 0, 0, 7, 0 ) ) ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( '<', 'delta', ( 0, 1, 0, 0 ) ) ), ) ) )
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemAgeDelta, predicate ) )
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemAgeDate, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME:
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemModifiedDelta, predicate ) )
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemModifiedDate, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_DIMENSIONS:
|
||||
|
||||
recent_predicate_types = [ ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT, ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH, ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_PIXELS ]
|
||||
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ( '=', 16, 9 ) ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ( '=', 9, 16 ) ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ( '=', 4, 3 ) ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ( '=', 1, 1 ) ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH, ( '=', 1920 ) ), ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT, ( '=', 1080 ) ) ), forced_label = '1080p' ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH, ( '=', 1280 ) ), ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT, ( '=', 720 ) ) ), forced_label = '720p' ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH, ( '=', 3840 ) ), ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT, ( '=', 2160 ) ) ), forced_label = '4k' ) )
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemHeight, predicate ) )
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemWidth, predicate ) )
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemRatio, predicate ) )
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemNumPixels, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION:
|
||||
|
||||
recent_predicate_types = [ ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION, ClientSearch.PREDICATE_TYPE_SYSTEM_FRAMERATE, ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_FRAMES ]
|
||||
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION, ( '>', 0 ) ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION, ( '=', 0 ) ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FRAMERATE, ( '=', 30 ) ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FRAMERATE, ( '=', 60 ) ), ) ) )
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemDuration, predicate ) )
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemFramerate, predicate ) )
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemNumFrames, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_SERVICE:
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemFileService, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS:
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemKnownURLsExactURL, predicate ) )
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemKnownURLsDomain, predicate ) )
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemKnownURLsRegex, predicate ) )
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemKnownURLsURLClass, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_AUDIO:
|
||||
|
||||
recent_predicate_types = []
|
||||
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_AUDIO, True ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_AUDIO, False ), ) ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_HASH:
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemHash, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT:
|
||||
|
||||
label = 'system:limit clips a large search result down to the given number of files. It is very useful for processing in smaller batches.'
|
||||
label += os.linesep * 2
|
||||
label += 'For all the simpler sorts (filesize, duration, etc...), it will select the n largest/smallest in the result set appropriate for that sort. For complicated sorts like tags, it will sample randomly.'
|
||||
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT, 64 ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT, 256 ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT, 1024 ), ) ) )
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemLimit, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_MIME:
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemMime, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS:
|
||||
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( None, '>', 0 ) ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( None, '=', 0 ) ), ) ) )
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemNumTags, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NOTES:
|
||||
|
||||
recent_predicate_types = [ ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_NOTE_NAME ]
|
||||
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ( '>', 0 ) ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ( '=', 0 ) ), ) ) )
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemNumNotes, predicate ) )
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemHasNoteName, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_WORDS:
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemNumWords, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_RATING:
|
||||
|
||||
services_manager = HG.client_controller.services_manager
|
||||
|
||||
ratings_services = services_manager.GetServices( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
|
||||
|
||||
if len( ratings_services ) > 0:
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesMultiple.PanelPredicateSystemRating, ( predicate, ) ) )
|
||||
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_SIMILAR_TO:
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemSimilarTo, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_SIZE:
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemSize, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_TAG_AS_NUMBER:
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemTagAsNumber, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS:
|
||||
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_KING, False ), ) ) )
|
||||
static_pred_buttons.append( ClientGUIPredicatesSingle.StaticSystemPredicateButton( self, self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_KING, True ), ) ) )
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemDuplicateRelationships, predicate ) )
|
||||
|
||||
elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS:
|
||||
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemFileViewingStatsViews, predicate ) )
|
||||
editable_pred_panels.append( self._PredOKPanel( self, ClientGUIPredicatesSingle.PanelPredicateSystemFileViewingStatsViewtime, predicate ) )
|
||||
|
||||
|
||||
vbox = QP.VBoxLayout()
|
||||
|
||||
if label is not None:
|
||||
|
||||
st = ClientGUICommon.BetterStaticText( self, label = label )
|
||||
|
||||
st.setWordWrap( True )
|
||||
|
||||
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
|
||||
recent_predicates = []
|
||||
|
||||
if len( recent_predicate_types ) > 0:
|
||||
|
||||
recent_predicates = HG.client_controller.new_options.GetRecentPredicates( recent_predicate_types )
|
||||
|
||||
if len( recent_predicates ) > 0:
|
||||
|
||||
recent_predicates_box = ClientGUICommon.StaticBox( self, 'recent' )
|
||||
|
||||
for recent_predicate in recent_predicates:
|
||||
|
||||
button = ClientGUIPredicatesSingle.StaticSystemPredicateButton( recent_predicates_box, self, ( recent_predicate, ) )
|
||||
|
||||
recent_predicates_box.Add( button, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
|
||||
QP.AddToLayout( vbox, recent_predicates_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
|
||||
|
||||
for button in static_pred_buttons:
|
||||
|
||||
QP.AddToLayout( vbox, button, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
|
||||
for panel in editable_pred_panels:
|
||||
|
||||
QP.AddToLayout( vbox, panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
|
||||
if len( static_pred_buttons ) > 0 and len( editable_pred_panels ) == 0:
|
||||
|
||||
HG.client_controller.CallAfterQtSafe( static_pred_buttons[0], static_pred_buttons[0].setFocus, QC.Qt.OtherFocusReason )
|
||||
|
||||
|
||||
self.widget().setLayout( vbox )
|
||||
|
||||
|
||||
def GetValue( self ):
|
||||
|
||||
return self._predicates
|
||||
|
||||
|
||||
def SubPanelOK( self, predicates ):
|
||||
|
||||
self._predicates = predicates
|
||||
|
||||
self.parentWidget().DoOK()
|
||||
|
||||
|
||||
class _PredOKPanel( QW.QWidget ):
|
||||
|
||||
def __init__( self, parent, predicate_panel_class, predicate ):
|
||||
|
||||
QW.QWidget.__init__( self, parent )
|
||||
|
||||
self._defaults_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().star, self._DefaultsMenu )
|
||||
self._defaults_button.setToolTip( 'Set a new default.' )
|
||||
|
||||
self._predicate_panel = predicate_panel_class( self, predicate )
|
||||
self._parent = parent
|
||||
|
||||
self._ok = QW.QPushButton( 'ok', self )
|
||||
self._ok.clicked.connect( self._DoOK )
|
||||
self._ok.setObjectName( 'HydrusAccept' )
|
||||
|
||||
hbox = QP.HBoxLayout()
|
||||
|
||||
QP.AddToLayout( hbox, self._defaults_button, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, self._predicate_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
||||
QP.AddToLayout( hbox, self._ok, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
|
||||
self.setLayout( hbox )
|
||||
|
||||
HG.client_controller.CallAfterQtSafe( self._ok, self._ok.setFocus, QC.Qt.OtherFocusReason )
|
||||
|
||||
|
||||
def _DefaultsMenu( self ):
|
||||
|
||||
menu = QW.QMenu()
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'set this as new default', 'Set the current value of this as the new default for this predicate.', self._predicate_panel.SaveCustomDefault )
|
||||
|
||||
if self._predicate_panel.UsesCustomDefault():
|
||||
|
||||
ClientGUIMenus.AppendSeparator( menu )
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'reset to original default', 'Clear the custom default value for this predicate.', self._predicate_panel.ClearCustomDefault )
|
||||
|
||||
|
||||
CGC.core().PopupMenu( self, menu )
|
||||
|
||||
|
||||
def _DoOK( self ):
|
||||
|
||||
try:
|
||||
|
||||
self._predicate_panel.CheckValid()
|
||||
|
||||
except Exception as e:
|
||||
|
||||
QW.QMessageBox.warning( self, 'Predicate not valid!', str( e ) )
|
||||
|
||||
return
|
||||
|
||||
|
||||
predicates = self._predicate_panel.GetPredicates()
|
||||
|
||||
self._parent.SubPanelOK( predicates )
|
||||
|
||||
|
||||
def keyPressEvent( self, event ):
|
||||
|
||||
( modifier, key ) = ClientGUIShortcuts.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( QC.Qt.Key_Enter, QC.Qt.Key_Return ):
|
||||
|
||||
self._DoOK()
|
||||
|
||||
else:
|
||||
|
||||
event.ignore()
|
||||
|
||||
|
||||
|
||||
|
|
@ -12,8 +12,8 @@ from hydrus.client import ClientSearch
|
|||
from hydrus.client.gui import ClientGUIFunctions
|
||||
from hydrus.client.gui import ClientGUICommon
|
||||
from hydrus.client.gui import ClientGUIDialogsQuick
|
||||
from hydrus.client.gui import ClientGUIResultsSortCollect
|
||||
from hydrus.client.gui import ClientGUIScrolledPanels
|
||||
from hydrus.client.gui import ClientGUISearch
|
||||
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
|
||||
from hydrus.client.gui import QtPorting as QP
|
||||
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
|
||||
|
@ -31,12 +31,12 @@ class EditFavouriteSearchPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
self._foldername = QW.QLineEdit( self )
|
||||
self._name = QW.QLineEdit( self )
|
||||
|
||||
self._media_sort = ClientGUISearch.MediaSortControl( self )
|
||||
self._media_collect = ClientGUISearch.MediaCollectControl( self, silent = True )
|
||||
self._media_sort = ClientGUIResultsSortCollect.MediaSortControl( self )
|
||||
self._media_collect = ClientGUIResultsSortCollect.MediaCollectControl( self, silent = True )
|
||||
|
||||
page_key = HydrusData.GenerateKey()
|
||||
|
||||
from hydrus.client.gui import ClientGUIACDropdown
|
||||
from hydrus.client.gui.search import ClientGUIACDropdown
|
||||
|
||||
self._tag_autocomplete = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self, page_key, file_search_context, media_sort_widget = self._media_sort, media_collect_widget = self._media_collect, synchronised = synchronised, hide_favourites_edit_actions = True )
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -70,8 +70,8 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 19
|
||||
SOFTWARE_VERSION = 418
|
||||
CLIENT_API_VERSION = 14
|
||||
SOFTWARE_VERSION = 419
|
||||
CLIENT_API_VERSION = 15
|
||||
|
||||
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
|
|
@ -46,6 +46,38 @@ def CheckCanVacuum( db_path, stop_time = None ):
|
|||
|
||||
HydrusPaths.CheckHasSpaceForDBTransaction( db_dir, vacuum_estimate )
|
||||
|
||||
def ReadFromCancellableCursor( cursor, largest_group_size, cancelled_hook = None ):
|
||||
|
||||
if cancelled_hook is None:
|
||||
|
||||
return cursor.fetchall()
|
||||
|
||||
|
||||
NUM_TO_GET = 1
|
||||
|
||||
results = []
|
||||
|
||||
group_of_results = cursor.fetchmany( NUM_TO_GET )
|
||||
|
||||
while len( group_of_results ) > 0:
|
||||
|
||||
results.extend( group_of_results )
|
||||
|
||||
if cancelled_hook():
|
||||
|
||||
break
|
||||
|
||||
|
||||
if NUM_TO_GET < 1024:
|
||||
|
||||
NUM_TO_GET *= 2
|
||||
|
||||
|
||||
group_of_results = cursor.fetchmany( NUM_TO_GET )
|
||||
|
||||
|
||||
return results
|
||||
|
||||
def ReadLargeIdQueryInSeparateChunks( cursor, select_statement, chunk_size ):
|
||||
|
||||
table_name = 'tempbigread' + os.urandom( 32 ).hex()
|
||||
|
|
|
@ -1038,6 +1038,43 @@ def PartitionIteratorIntoLists( pred: typing.Callable[ [ object ], bool ], strea
|
|||
|
||||
return ( list( a ), list( b ) )
|
||||
|
||||
def ParseHashesFromRawHexText( hash_type, hex_hashes_raw ):
|
||||
|
||||
hash_type_to_hex_length = {
|
||||
'md5' : 32,
|
||||
'sha1' : 40,
|
||||
'sha256' : 64,
|
||||
'sha512' : 128
|
||||
}
|
||||
|
||||
hex_hashes = HydrusText.DeserialiseNewlinedTexts( hex_hashes_raw )
|
||||
|
||||
# convert md5:abcd to abcd
|
||||
hex_hashes = [ hex_hash.split( ':' )[-1] for hex_hash in hex_hashes ]
|
||||
|
||||
hex_hashes = [ HydrusText.HexFilter( hex_hash ) for hex_hash in hex_hashes ]
|
||||
|
||||
expected_hex_length = hash_type_to_hex_length[ hash_type ]
|
||||
|
||||
bad_hex_hashes = [ hex_hash for hex_hash in hex_hashes if len( hex_hash ) != expected_hex_length ]
|
||||
|
||||
if len( bad_hex_hashes ):
|
||||
|
||||
m = 'Sorry, {} hashes should have {} hex characters! These did not:'.format( hash_type, expected_hex_length )
|
||||
m += os.linesep * 2
|
||||
m += os.linesep.join( ( '{} ({} characters)'.format( bad_hex_hash, len( bad_hex_hash ) ) for bad_hex_hash in bad_hex_hashes ) )
|
||||
|
||||
raise Exception( m )
|
||||
|
||||
|
||||
hex_hashes = [ hex_hash for hex_hash in hex_hashes if len( hex_hash ) % 2 == 0 ]
|
||||
|
||||
hex_hashes = DedupeList( hex_hashes )
|
||||
|
||||
hashes = tuple( [ bytes.fromhex( hex_hash ) for hex_hash in hex_hashes ] )
|
||||
|
||||
return hashes
|
||||
|
||||
def Print( text ):
|
||||
|
||||
try:
|
||||
|
|
|
@ -329,7 +329,7 @@ class ServicesUPnPManager( object ):
|
|||
|
||||
except HydrusExceptions.RouterException:
|
||||
|
||||
HydrusData.Print( 'The UPnP Daemon tried to add ' + local_ip + ':' + internal_port + '->external:' + upnp_port + ' but it failed. Please try it manually to get a full log of what happened.' )
|
||||
HydrusData.Print( 'The UPnP Daemon tried to add {}:{}->external:{} but it failed. Please try it manually to get a full log of what happened.'.format( local_ip, internal_port, upnp_port ) )
|
||||
|
||||
return
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ except:
|
|||
CHARDET_OK = False
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
re_newlines = re.compile( '[\r\n]+' )
|
||||
|
|
|
@ -1681,8 +1681,8 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
file_info_manager = ClientMediaManagers.FileInfoManager( file_id, hash, size = size, mime = mime, width = width, height = height, duration = duration, has_audio = has_audio )
|
||||
|
||||
service_keys_to_statuses_to_tags = { CC.DEFAULT_LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : [ 'blue eyes', 'blonde hair' ], HC.CONTENT_STATUS_PENDING : [ 'bodysuit' ] } }
|
||||
service_keys_to_statuses_to_display_tags = { CC.DEFAULT_LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : [ 'blue eyes', 'blonde hair' ], HC.CONTENT_STATUS_PENDING : [ 'bodysuit' ] } }
|
||||
service_keys_to_statuses_to_tags = { CC.DEFAULT_LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : [ 'blue_eyes', 'blonde_hair' ], HC.CONTENT_STATUS_PENDING : [ 'bodysuit' ] } }
|
||||
service_keys_to_statuses_to_display_tags = { CC.DEFAULT_LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : [ 'blue eyes', 'blonde hair' ], HC.CONTENT_STATUS_PENDING : [ 'bodysuit', 'clothing' ] } }
|
||||
|
||||
tags_manager = ClientMediaManagers.TagsManager( service_keys_to_statuses_to_tags, service_keys_to_statuses_to_display_tags )
|
||||
|
||||
|
@ -1747,6 +1747,24 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
metadata_row[ 'service_names_to_statuses_to_tags' ] = service_names_to_statuses_to_tags
|
||||
|
||||
service_names_to_statuses_to_tags = {}
|
||||
|
||||
service_keys_to_statuses_to_tags = tags_manager.GetServiceKeysToStatusesToTags( ClientTags.TAG_DISPLAY_ACTUAL )
|
||||
|
||||
for ( service_key, statuses_to_tags ) in service_keys_to_statuses_to_tags.items():
|
||||
|
||||
if service_key not in service_keys_to_names:
|
||||
|
||||
service_keys_to_names[ service_key ] = services_manager.GetName( service_key )
|
||||
|
||||
|
||||
service_name = service_keys_to_names[ service_key ]
|
||||
|
||||
service_names_to_statuses_to_tags[ service_name ] = { str( status ) : list( tags ) for ( status, tags ) in statuses_to_tags.items() }
|
||||
|
||||
|
||||
metadata_row[ 'service_names_to_statuses_to_display_tags' ] = service_names_to_statuses_to_tags
|
||||
|
||||
metadata.append( metadata_row )
|
||||
|
||||
detailed_known_urls_metadata_row = dict( metadata_row )
|
||||
|
@ -1903,8 +1921,8 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
file_info_manager = ClientMediaManagers.FileInfoManager( file_id, hash, size = size, mime = mime, width = width, height = height, duration = duration )
|
||||
|
||||
service_keys_to_statuses_to_tags = { CC.DEFAULT_LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : [ 'blue eyes', 'blonde hair' ], HC.CONTENT_STATUS_PENDING : [ 'bodysuit' ] } }
|
||||
service_keys_to_statuses_to_display_tags = { CC.DEFAULT_LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : [ 'blue eyes', 'blonde hair' ], HC.CONTENT_STATUS_PENDING : [ 'bodysuit' ] } }
|
||||
service_keys_to_statuses_to_tags = { CC.DEFAULT_LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : [ 'blue_eyes', 'blonde_hair' ], HC.CONTENT_STATUS_PENDING : [ 'bodysuit' ] } }
|
||||
service_keys_to_statuses_to_display_tags = { CC.DEFAULT_LOCAL_TAG_SERVICE_KEY : { HC.CONTENT_STATUS_CURRENT : [ 'blue eyes', 'blonde hair' ], HC.CONTENT_STATUS_PENDING : [ 'bodysuit', 'clothing' ] } }
|
||||
|
||||
tags_manager = ClientMediaManagers.TagsManager( service_keys_to_statuses_to_tags, service_keys_to_statuses_to_display_tags )
|
||||
|
||||
|
|
Loading…
Reference in New Issue