changelog
-
+
version 419
+ - tag lists and editing predicates: +
- 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 +
- 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 +
- all tag lists now support drag-selection! +
- taglists now have 'open a new OR page' menu entry when more than one tag is selected +
- 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 +
- 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! +
- system:rating is now editable! it launches the whole stack every time. the stack alignment is messed up though :/ +
- 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 +
- the active search predicates taglist now has an 'edit search terms' menu entry, if you find shift+double-click a pain +
- when you shift+double-click on more than one tag to add them to the current search, this is now added as an OR +
- similarly if you shift+middle-click on more than one tag, the new page is now an OR +
- when editing predicates, edited predicates now stay selected +
- 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 +
- the list of active search predicates now correctly initialises sorted +
- 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 +
- . +
- faster and snappier file and tag searching: +
- 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 +
- 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 +
- autocomplete tag count aggregation (a later step, after the initial lookup) benefits from a little faster cancel tech +
- 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 +
- related tags suggestions also gets the cancel tech and is now more timing precise for tags with either huge or tiny count +
- . +
- client api: +
- 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 +
- the client api version is now 15 +
- . +
- the rest: +
- 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 +
- fixed another upnp error handling bug, this time in the upnp daemon +
- updated Qt to 5.15.2 on Windows and Linux builds. this should fix the unusual button clicking area problem for some custom styles +
- . +
- boring specific code changes: +
- wrote widgets to edit invertible preds and OR preds +
- pulled the messy rating code out of the rating system predicate ui code to their own widgets +
- wrote some special predicate ui definitions and initialisation handling for OR preds and grouped 'multiple' preds (for ratings) +
- refactored search and predicate ui code to a new 'search' module +
- refactored collect and sort widgets away from search code +
- misc layout improvements for system pred edit ui +
version 418
- almost all system predicates are now editable if you shift+double-click them! you can also edit several at once in the same dialog diff --git a/help/client_api.html b/help/client_api.html index 454eb8c0..a54a0f09 100644 --- a/help/client_api.html +++ b/help/client_api.html @@ -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 @@
- 0 - current
- 1 - pending @@ -1121,6 +1132,7 @@
- 3 - petitioned
-
+
Size is in bytes. Duration is in milliseconds, and may be an int or a float.
-The service_names_to_statuses_to_tags structure is similar to the /add_tags/add_tags scheme, excepting that the status numbers are:
+The service_names_to_statuses_to_tags structures are similar to the /add_tags/add_tags scheme, excepting that the status numbers are:
Note that since JSON Object keys must be strings, these status numbers are strings, not ints.
+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.
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.
For example:
-
diff --git a/hydrus/client/ClientDB.py b/hydrus/client/ClientDB.py
index 13f6ebd4..870d4b7e 100644
--- a/hydrus/client/ClientDB.py
+++ b/hydrus/client/ClientDB.py
@@ -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:
diff --git a/hydrus/client/ClientLocalServerResources.py b/hydrus/client/ClientLocalServerResources.py
index ae322736..e2c04220 100644
--- a/hydrus/client/ClientLocalServerResources.py
+++ b/hydrus/client/ClientLocalServerResources.py
@@ -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 )
diff --git a/hydrus/client/ClientOptions.py b/hydrus/client/ClientOptions.py
index 437ebe5d..f55eecef 100644
--- a/hydrus/client/ClientOptions.py
+++ b/hydrus/client/ClientOptions.py
@@ -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:
diff --git a/hydrus/client/ClientParsing.py b/hydrus/client/ClientParsing.py
index e8853ca7..0de90fb6 100644
--- a/hydrus/client/ClientParsing.py
+++ b/hydrus/client/ClientParsing.py
@@ -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
diff --git a/hydrus/client/ClientSearch.py b/hydrus/client/ClientSearch.py
index cc09dd91..583e2622 100644
--- a/hydrus/client/ClientSearch.py
+++ b/hydrus/client/ClientSearch.py
@@ -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
diff --git a/hydrus/client/gui/ClientGUI.py b/hydrus/client/gui/ClientGUI.py
index 5426a8a4..3f71c629 100644
--- a/hydrus/client/gui/ClientGUI.py
+++ b/hydrus/client/gui/ClientGUI.py
@@ -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 )
diff --git a/hydrus/client/gui/ClientGUIApplicationCommand.py b/hydrus/client/gui/ClientGUIApplicationCommand.py
index a0f4299e..5f25d517 100644
--- a/hydrus/client/gui/ClientGUIApplicationCommand.py
+++ b/hydrus/client/gui/ClientGUIApplicationCommand.py
@@ -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 ):
diff --git a/hydrus/client/gui/ClientGUIDialogs.py b/hydrus/client/gui/ClientGUIDialogs.py
index 0a96f0e2..b86a5664 100644
--- a/hydrus/client/gui/ClientGUIDialogs.py
+++ b/hydrus/client/gui/ClientGUIDialogs.py
@@ -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 ):
diff --git a/hydrus/client/gui/ClientGUIExport.py b/hydrus/client/gui/ClientGUIExport.py
index 71b20e2b..15a77ff0 100644
--- a/hydrus/client/gui/ClientGUIExport.py
+++ b/hydrus/client/gui/ClientGUIExport.py
@@ -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 ):
diff --git a/hydrus/client/gui/ClientGUIImport.py b/hydrus/client/gui/ClientGUIImport.py
index bfab47b4..dc09f9b9 100644
--- a/hydrus/client/gui/ClientGUIImport.py
+++ b/hydrus/client/gui/ClientGUIImport.py
@@ -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
diff --git a/hydrus/client/gui/ClientGUIManagement.py b/hydrus/client/gui/ClientGUIManagement.py
index 31b7b901..b2682acd 100644
--- a/hydrus/client/gui/ClientGUIManagement.py
+++ b/hydrus/client/gui/ClientGUIManagement.py
@@ -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:
diff --git a/hydrus/client/gui/ClientGUIResultsSortCollect.py b/hydrus/client/gui/ClientGUIResultsSortCollect.py
new file mode 100644
index 00000000..67e30a10
--- /dev/null
+++ b/hydrus/client/gui/ClientGUIResultsSortCollect.py
@@ -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()
+
+
diff --git a/hydrus/client/gui/ClientGUIScrolledPanelsManagement.py b/hydrus/client/gui/ClientGUIScrolledPanelsManagement.py
index 3e6d9090..56e7f34a 100644
--- a/hydrus/client/gui/ClientGUIScrolledPanelsManagement.py
+++ b/hydrus/client/gui/ClientGUIScrolledPanelsManagement.py
@@ -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' )
diff --git a/hydrus/client/gui/ClientGUIScrolledPanelsReview.py b/hydrus/client/gui/ClientGUIScrolledPanelsReview.py
index f9263a64..c582d645 100644
--- a/hydrus/client/gui/ClientGUIScrolledPanelsReview.py
+++ b/hydrus/client/gui/ClientGUIScrolledPanelsReview.py
@@ -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
diff --git a/hydrus/client/gui/ClientGUISearch.py b/hydrus/client/gui/ClientGUISearch.py
deleted file mode 100644
index c9091848..00000000
--- a/hydrus/client/gui/ClientGUISearch.py
+++ /dev/null
@@ -1,3057 +0,0 @@
-import os
-import re
-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 HydrusData
-from hydrus.core import HydrusExceptions
-from hydrus.core import HydrusGlobals as HG
-from hydrus.core import HydrusText
-
-from hydrus.client import ClientConstants as CC
-from hydrus.client import ClientSearch
-from hydrus.client.gui import ClientGUICommon
-from hydrus.client.gui import ClientGUIControls
-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 ClientGUIOptionsPanels
-from hydrus.client.gui import ClientGUIRatings
-from hydrus.client.gui import ClientGUIScrolledPanels
-from hydrus.client.gui import ClientGUIShortcuts
-from hydrus.client.gui import ClientGUITime
-from hydrus.client.gui import QtPorting as QP
-from hydrus.client.media import ClientMedia
-from hydrus.client.metadata import ClientRatings
-
-EDIT_SYSTEM_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_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
-}
-
-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 = [ predicate for predicate in predicates if predicate.GetType() in EDIT_SYSTEM_PRED_TYPES ]
- non_editable_predicates = [ predicate for predicate in predicates if predicate.GetType() not in EDIT_SYSTEM_PRED_TYPES ]
-
- window = widget.window()
-
- from hydrus.client.gui import ClientGUITopLevelWindowsPanels
-
- if 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()
-
- result = list( non_editable_predicates )
- result.extend( edited_predicates )
-
- return result
-
-
-
- else:
-
- inverse_predicates = [ predicate.GetInverseCopy() for predicate in predicates ]
-
- if None not in inverse_predicates:
-
- return inverse_predicates
-
-
-
- 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:
-
- good_predicates.extend( panel.GetValue() )
-
-
-
- else:
-
- good_predicates.append( predicate )
-
-
-
- return good_predicates
-
-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()
-
-
-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._editable_pred_panels = []
-
- # 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_SYSTEM_AGE:
-
- if predicate.IsUIEditable( AGE_DELTA_PRED ):
-
- self._editable_pred_panels.append( PanelPredicateSystemAgeDelta( self, predicate ) )
-
- else:
-
- self._editable_pred_panels.append( PanelPredicateSystemAgeDate( self, predicate ) )
-
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME:
-
- if predicate.IsUIEditable( MODIFIED_DELTA_PRED ):
-
- self._editable_pred_panels.append( PanelPredicateSystemModifiedDelta( self, predicate ) )
-
- else:
-
- self._editable_pred_panels.append( PanelPredicateSystemModifiedDate( self, predicate ) )
-
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT:
-
- self._editable_pred_panels.append( PanelPredicateSystemHeight( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH:
-
- self._editable_pred_panels.append( PanelPredicateSystemWidth( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO:
-
- self._editable_pred_panels.append( PanelPredicateSystemRatio( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_PIXELS:
-
- self._editable_pred_panels.append( PanelPredicateSystemNumPixels( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION:
-
- self._editable_pred_panels.append( PanelPredicateSystemDuration( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FRAMERATE:
-
- self._editable_pred_panels.append( PanelPredicateSystemFramerate( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_FRAMES:
-
- self._editable_pred_panels.append( PanelPredicateSystemNumFrames( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_SERVICE:
-
- self._editable_pred_panels.append( PanelPredicateSystemFileService( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS:
-
- if predicate.IsUIEditable( KNOWN_URL_EXACT ):
-
- self._editable_pred_panels.append( PanelPredicateSystemKnownURLsExactURL( self, predicate ) )
-
- elif predicate.IsUIEditable( KNOWN_URL_DOMAIN ):
-
- self._editable_pred_panels.append( PanelPredicateSystemKnownURLsDomain( self, predicate ) )
-
- elif predicate.IsUIEditable( KNOWN_URL_REGEX ):
-
- self._editable_pred_panels.append( PanelPredicateSystemKnownURLsRegex( self, predicate ) )
-
- else:
-
- self._editable_pred_panels.append( PanelPredicateSystemKnownURLsURLClass( self, predicate ) )
-
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_HASH:
-
- self._editable_pred_panels.append( PanelPredicateSystemHash( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT:
-
- self._editable_pred_panels.append( PanelPredicateSystemLimit( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_MIME:
-
- self._editable_pred_panels.append( PanelPredicateSystemMime( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS:
-
- self._editable_pred_panels.append( PanelPredicateSystemNumTags( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_NOTES:
-
- self._editable_pred_panels.append( PanelPredicateSystemNumNotes( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_NOTE_NAME:
-
- self._editable_pred_panels.append( PanelPredicateSystemHasNoteName( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_WORDS:
-
- self._editable_pred_panels.append( PanelPredicateSystemNumWords( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_SIMILAR_TO:
-
- self._editable_pred_panels.append( PanelPredicateSystemSimilarTo( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_SIZE:
-
- self._editable_pred_panels.append( PanelPredicateSystemSize( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_TAG_AS_NUMBER:
-
- self._editable_pred_panels.append( PanelPredicateSystemTagAsNumber( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_COUNT:
-
- self._editable_pred_panels.append( PanelPredicateSystemDuplicateRelationships( self, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS:
-
- if predicate.IsUIEditable( FILE_VIEWS_PRED ):
-
- self._editable_pred_panels.append( PanelPredicateSystemFileViewingStatsViews( self, predicate ) )
-
- else:
-
- self._editable_pred_panels.append( PanelPredicateSystemFileViewingStatsViewtime( self, predicate ) )
-
-
- else:
-
- self._uneditable_predicates.append( predicate )
-
-
-
-
- vbox = QP.VBoxLayout()
-
- for panel in self._editable_pred_panels:
-
- QP.AddToLayout( vbox, panel, CC.FLAGS_EXPAND_PERPENDICULAR )
-
-
- self.widget().setLayout( vbox )
-
-
- def GetValue( self ):
-
- return_predicates = list( self._uneditable_predicates )
-
- 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 = []
-
- if predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_AGE:
-
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( '<', 'delta', ( 0, 0, 1, 0 ) ) ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( '<', 'delta', ( 0, 0, 7, 0 ) ) ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( '<', 'delta', ( 0, 1, 0, 0 ) ) ), ) ) )
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemAgeDelta, predicate ) )
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemAgeDate, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME:
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemModifiedDelta, predicate ) )
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemModifiedDate, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_DIMENSIONS:
-
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ( '=', 16, 9 ) ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ( '=', 9, 16 ) ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ( '=', 4, 3 ) ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ( '=', 1, 1 ) ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH, ( '=', 1920 ) ), ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT, ( '=', 1080 ) ) ), forced_label = '1080p' ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH, ( '=', 1280 ) ), ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT, ( '=', 720 ) ) ), forced_label = '720p' ) )
- static_pred_buttons.append( StaticSystemPredicateButton( 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, PanelPredicateSystemHeight, predicate ) )
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemWidth, predicate ) )
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemRatio, predicate ) )
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemNumPixels, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION:
-
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION, ( '>', 0 ) ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION, ( '=', 0 ) ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FRAMERATE, ( '=', 30 ) ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FRAMERATE, ( '=', 60 ) ), ) ) )
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemDuration, predicate ) )
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemFramerate, predicate ) )
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemNumFrames, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_SERVICE:
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemFileService, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS:
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemKnownURLsExactURL, predicate ) )
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemKnownURLsDomain, predicate ) )
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemKnownURLsRegex, predicate ) )
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemKnownURLsURLClass, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_AUDIO:
-
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_AUDIO, True ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( 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, 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( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT, 64 ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT, 256 ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT, 1024 ), ) ) )
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemLimit, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_MIME:
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemMime, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS:
-
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( None, '>', 0 ) ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( None, '=', 0 ) ), ) ) )
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemNumTags, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NOTES:
-
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ( '>', 0 ) ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ( '=', 0 ) ), ) ) )
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemNumNotes, predicate ) )
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemHasNoteName, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_WORDS:
-
- editable_pred_panels.append( self._PredOKPanel( self, 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, PanelPredicateSystemRating, predicate ) )
-
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_SIMILAR_TO:
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemSimilarTo, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_SIZE:
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemSize, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_TAG_AS_NUMBER:
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemTagAsNumber, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS:
-
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_KING, False ), ) ) )
- static_pred_buttons.append( StaticSystemPredicateButton( self, ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_KING, True ), ) ) )
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemDuplicateRelationships, predicate ) )
-
- elif predicate_type == ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS:
-
- editable_pred_panels.append( self._PredOKPanel( self, PanelPredicateSystemFileViewingStatsViews, predicate ) )
- editable_pred_panels.append( self._PredOKPanel( self, 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 )
-
-
- 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._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._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 _DoOK( self ):
-
- try:
-
- self._predicate_panel.CheckCanOK()
-
- except Exception as e:
-
- message = 'Cannot OK: {}'.format( e )
-
- QW.QMessageBox.warning( self, 'Warning', message )
-
- 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()
-
-
-
-
-class StaticSystemPredicateButton( QW.QPushButton ):
-
- def __init__( self, parent, predicates, forced_label = None ):
-
- QW.QPushButton.__init__( self, parent )
-
- self._parent = parent
- self._predicates = predicates
- self._forced_label = forced_label
-
- if forced_label is None:
-
- label = ', '.join( ( predicate.ToString() for predicate in self._predicates ) )
-
- else:
-
- label = forced_label
-
-
- self.setText( label )
-
- self.clicked.connect( self.DoOK )
-
-
- def DoOK( self ):
-
- self._parent.SubPanelOK( self._predicates )
-
-
-class PanelPredicateSystem( QW.QWidget ):
-
- def _GetDefaultPredicate( self ) -> ClientSearch.Predicate:
-
- raise NotImplementedError()
-
-
- def _GetPredicateToInitialisePanelWith( self, predicate: ClientSearch.Predicate ) -> ClientSearch.Predicate:
-
- # expand this to check the favourites system for UICompatible preds
-
- default_predicate = self._GetDefaultPredicate()
-
- if predicate.IsUIEditable( default_predicate ):
-
- return predicate
-
-
- return default_predicate
-
-
- def CheckCanOK( self ):
-
- pass
-
-
- def GetPredicates( self ):
-
- raise NotImplementedError()
-
-
-class PanelPredicateSystemAgeDate( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
-
- self._date = QW.QCalendarWidget( self )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, age_type, ( years, months, days ) ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
-
- qt_dt = QC.QDate( years, months, days )
-
- self._date.setSelectedDate( qt_dt )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:time imported'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._date, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ) -> ClientSearch.Predicate:
-
- qt_dt = QC.QDate.currentDate()
-
- qt_dt.addDays( -7 )
-
- year = qt_dt.year()
- month = qt_dt.month()
- day = qt_dt.day()
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( '>', 'date', ( year, month, day ) ) )
-
-
- def GetPredicates( self ):
-
- qt_dt = self._date.selectedDate()
-
- year = qt_dt.year()
- month = qt_dt.month()
- day = qt_dt.day()
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( self._sign.GetStringSelection(), 'date', ( year, month, day ) ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemAgeDelta( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._sign = QP.RadioBox( self, choices=['<','\u2248','>'] )
-
- self._years = QP.MakeQSpinBox( self, max=30, width = 60 )
- self._months = QP.MakeQSpinBox( self, max=60, width = 60 )
- self._days = QP.MakeQSpinBox( self, max=90, width = 60 )
- self._hours = QP.MakeQSpinBox( self, max=24, width = 60 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, age_type, ( years, months, days, hours ) ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
-
- self._years.setValue( years )
- self._months.setValue( months )
- self._days.setValue( days )
- self._hours.setValue( hours )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:time imported'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._years, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'years'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._months, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'months'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._days, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'days'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._hours, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'hours'), CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( '<', 'delta', ( 0, 0, 7, 0 ) ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( self._sign.GetStringSelection(), 'delta', (self._years.value(), self._months.value(), self._days.value(), self._hours.value() ) ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemModifiedDate( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
-
- self._date = QW.QCalendarWidget( self )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, age_type, ( years, months, days ) ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
-
- qt_dt = QC.QDate( years, months, days )
-
- self._date.setSelectedDate( qt_dt )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:modified date'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._date, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ) -> ClientSearch.Predicate:
-
- qt_dt = QC.QDate.currentDate()
-
- qt_dt.addDays( -7 )
-
- year = qt_dt.year()
- month = qt_dt.month()
- day = qt_dt.day()
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME, ( '>', 'date', ( year, month, day ) ) )
-
-
- def GetPredicates( self ):
-
- qt_dt = self._date.selectedDate()
-
- year = qt_dt.year()
- month = qt_dt.month()
- day = qt_dt.day()
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME, ( self._sign.GetStringSelection(), 'date', ( year, month, day ) ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemModifiedDelta( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._sign = QP.RadioBox( self, choices=['<','\u2248','>'] )
-
- self._years = QP.MakeQSpinBox( self, max=30 )
- self._months = QP.MakeQSpinBox( self, max=60 )
- self._days = QP.MakeQSpinBox( self, max=90 )
- self._hours = QP.MakeQSpinBox( self, max=24 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, age_type, ( years, months, days, hours ) ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
-
- self._years.setValue( years )
- self._months.setValue( months )
- self._days.setValue( days )
- self._hours.setValue( hours )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:modified date'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._years, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'years'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._months, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'months'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._days, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'days'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._hours, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'hours'), CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME, ( '<', 'delta', ( 0, 0, 7, 0 ) ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME, ( self._sign.GetStringSelection(), 'delta', ( self._years.value(), self._months.value(), self._days.value(), self._hours.value() ) ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemDuplicateRelationships( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- choices = [ '<', '\u2248', '=', '>' ]
-
- self._sign = QP.RadioBox( self, choices = choices )
-
- self._num = QP.MakeQSpinBox( self, min=0, max=65535 )
-
- choices = [ ( HC.duplicate_type_string_lookup[ status ], status ) for status in ( HC.DUPLICATE_MEMBER, HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_FALSE_POSITIVE, HC.DUPLICATE_POTENTIAL ) ]
-
- self._dupe_type = ClientGUICommon.BetterRadioBox( self, choices = choices, vertical = True )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, num, dupe_type ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
- self._num.setValue( num )
- self._dupe_type.SetValue( dupe_type )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:num file relationships'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._num, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._dupe_type, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- sign = '>'
- num = 0
- dupe_type = HC.DUPLICATE_MEMBER
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_COUNT, ( sign, num, dupe_type ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_COUNT, ( self._sign.GetStringSelection(), self._num.value(), self._dupe_type.GetValue() ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemDuration( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- choices = [ '<', '\u2248', '=', '>' ]
-
- self._sign = QP.RadioBox( self, choices = choices )
-
- self._duration_s = QP.MakeQSpinBox( self, max=3599, width = 60 )
- self._duration_ms = QP.MakeQSpinBox( self, max=999, width = 60 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, ms ) = predicate.GetValue()
-
- s = ms // 1000
-
- ms = ms % 1000
-
- self._sign.SetStringSelection( sign )
-
- self._duration_s.setValue( s )
- self._duration_ms.setValue( ms )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:duration'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._duration_s, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'s'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._duration_ms, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'ms'), CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- sign = '>'
- duration = 0
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION, ( sign, duration ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION, ( self._sign.GetStringSelection(), self._duration_s.value() * 1000 + self._duration_ms.value() ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemFileService( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._sign = ClientGUICommon.BetterRadioBox( self, choices = [ ( 'is', True ), ( 'is not', False ) ], vertical = True )
-
- self._current_pending = ClientGUICommon.BetterRadioBox( self, choices = [ ( 'currently in', HC.CONTENT_STATUS_CURRENT ), ( 'pending to', HC.CONTENT_STATUS_PENDING ) ], vertical = True )
-
- services = HG.client_controller.services_manager.GetServices( HC.FILE_SERVICES )
-
- choices = [ ( service.GetName(), service.GetServiceKey() ) for service in services ]
-
- self._file_service_key = ClientGUICommon.BetterRadioBox( self, choices = choices, vertical = True )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, current_pending, file_service_key ) = predicate.GetValue()
-
- self._sign.SetValue( sign )
- self._current_pending.SetValue( current_pending )
- self._file_service_key.SetValue( file_service_key )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:file service:'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._current_pending, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._file_service_key, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- sign = True
- current_pending = HC.CONTENT_STATUS_CURRENT
- file_service_key = bytes()
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_SERVICE, ( sign, current_pending, file_service_key ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_SERVICE, ( self._sign.GetValue(), self._current_pending.GetValue(), self._file_service_key.GetValue() ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemFileViewingStatsViews( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._viewing_locations = QP.CheckListBox( self )
-
- self._viewing_locations.Append( 'media views', 'media' )
- self._viewing_locations.Append( 'preview views', 'preview' )
-
- self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
-
- self._num = QP.MakeQSpinBox( self, min=0, max=1000000 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( view_type, viewing_locations, sign, num ) = predicate.GetValue()
-
- self._viewing_locations.SetCheckedData( viewing_locations )
-
- ( width, height ) = ClientGUIFunctions.ConvertTextToPixels( self._viewing_locations, ( 10, 3 ) )
-
- self._viewing_locations.setMaximumHeight( height )
-
- self._sign.SetStringSelection( sign )
-
- self._num.setValue( num )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._viewing_locations, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._num, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- viewing_locations = ( 'media', )
- sign = '>'
- num = 10
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS, ( 'views', tuple( viewing_locations ), sign, num ) )
-
-
- def GetPredicates( self ):
-
- viewing_locations = self._viewing_locations.GetChecked()
-
- if len( viewing_locations ) == 0:
-
- viewing_locations = [ 'media' ]
-
-
- sign = self._sign.GetStringSelection()
-
- num = self._num.value()
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS, ( 'views', tuple( viewing_locations ), sign, num ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemFileViewingStatsViewtime( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._viewing_locations = QP.CheckListBox( self )
-
- self._viewing_locations.Append( 'media viewtime', 'media' )
- self._viewing_locations.Append( 'preview viewtime', 'preview' )
-
- self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
-
- self._time_delta = ClientGUITime.TimeDeltaCtrl( self, min = 0, days = True, hours = True, minutes = True, seconds = True )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( view_type, viewing_locations, sign, time_delta ) = predicate.GetValue()
-
- self._viewing_locations.SetCheckedData( viewing_locations )
-
- ( width, height ) = ClientGUIFunctions.ConvertTextToPixels( self._viewing_locations, ( 10, 3 ) )
-
- self._viewing_locations.setMaximumHeight( height )
-
- self._sign.SetStringSelection( sign )
-
- self._time_delta.SetValue( time_delta )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._viewing_locations, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._time_delta, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- viewing_locations = ( 'media', )
- sign = '>'
- time_delta = 600
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS, ( 'viewtime', tuple( viewing_locations ), sign, time_delta ) )
-
-
- def GetPredicates( self ):
-
- viewing_locations = self._viewing_locations.GetChecked()
-
- if len( viewing_locations ) == 0:
-
- viewing_locations = [ 'media' ]
-
-
- sign = self._sign.GetStringSelection()
-
- time_delta = self._time_delta.GetValue()
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS, ( 'viewtime', tuple( viewing_locations ), sign, time_delta ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemFramerate( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- choices = [ '<', '=', '>' ]
-
- self._sign = QP.RadioBox( self, choices = choices )
-
- self._framerate = QP.MakeQSpinBox( self, min = 1, max = 3600, width = 60 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, framerate ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
- self._framerate.setValue( framerate )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText( self, 'system:framerate' ), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._framerate, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText( self, 'fps' ), CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- vbox = QP.VBoxLayout()
-
- QP.AddToLayout( vbox, ClientGUICommon.BetterStaticText( 'All framerate searches are +/- 5%. Exactly searching for 29.97 is not currently possible.' ), CC.FLAGS_EXPAND_PERPENDICULAR )
- QP.AddToLayout( vbox, hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
-
- self.setLayout( vbox )
-
-
- def _GetDefaultPredicate( self ):
-
- sign = '='
- framerate = 60
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FRAMERATE, ( sign, framerate ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FRAMERATE, ( self._sign.GetStringSelection(), self._framerate.value() ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemHash( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._hashes = QW.QPlainTextEdit( self )
-
- ( init_width, init_height ) = ClientGUIFunctions.ConvertTextToPixels( self._hashes, ( 66, 10 ) )
-
- self._hashes.setMinimumSize( QC.QSize( init_width, init_height ) )
-
- choices = [ 'sha256', 'md5', 'sha1', 'sha512' ]
-
- self._hash_type = QP.RadioBox( self, choices = choices, vertical = True )
-
- self._hashes.setPlaceholderText( 'enter hash (paste newline-separated for multiple hashes)' )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( hashes, hash_type ) = predicate.GetValue()
-
- hashes_text = os.linesep.join( [ hash.hex() for hash in hashes ] )
-
- self._hashes.setPlainText( hashes_text )
-
- self._hash_type.SetStringSelection( hash_type )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:hash='), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._hashes, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._hash_type, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- hashes = tuple()
- hash_type = 'sha256'
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HASH, ( hashes, hash_type ) )
-
-
- def GetPredicates( self ):
-
- # replace this with a better 'cleanhashes( hash_type )' thing that checks length properly
- # and have that then show in the plaintext above a red background or whatever when invalid hashes, with some text
-
- hex_hashes_raw = self._hashes.toPlainText()
-
- hex_hashes = HydrusText.DeserialiseNewlinedTexts( hex_hashes_raw )
-
- hex_hashes = [ HydrusText.HexFilter( hex_hash ) for hex_hash in hex_hashes ]
-
- hex_hashes = [ hex_hash for hex_hash in hex_hashes if len( hex_hash ) % 2 == 0 ]
-
- hex_hashes = HydrusData.DedupeList( hex_hashes )
-
- hashes = tuple( [ bytes.fromhex( hex_hash ) for hex_hash in hex_hashes ] )
-
- hash_type = self._hash_type.GetStringSelection()
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HASH, ( hashes, hash_type ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemHasNoteName( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._operator = ClientGUICommon.BetterChoice( self )
-
- self._operator.addItem( 'has note with name ', True )
- self._operator.addItem( 'does not have note with name', False )
-
- self._name = QW.QLineEdit( self )
- self._name.setFixedWidth( 250 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( operator, name ) = predicate.GetValue()
-
- self._operator.SetValue( operator )
- self._name.setText( name )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:note name'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._operator, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._name, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- operator = True
- name = ''
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_NOTE_NAME, ( operator, name ) )
-
-
- def GetPredicates( self ):
-
- name = self._name.text()
-
- if name == '':
-
- name = 'notes'
-
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_NOTE_NAME, ( self._operator.GetValue(), name ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemHeight( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
-
- self._height = QP.MakeQSpinBox( self, max=200000, width = 60 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, height ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
-
- self._height.setValue( height )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:height'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._height, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- sign = '='
- height = 1080
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT, ( sign, height ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT, ( self._sign.GetStringSelection(), self._height.value() ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemKnownURLsExactURL( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._operator = ClientGUICommon.BetterChoice( self )
-
- self._operator.addItem( 'has', True )
- self._operator.addItem( 'does not have', False )
-
- self._exact_url = QW.QLineEdit( self )
- self._exact_url.setFixedWidth( 250 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( operator, rule_type, rule, description ) = predicate.GetValue()
-
- self._operator.SetValue( operator )
- self._exact_url.setText( rule )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:known url'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._operator, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'exact url:'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._exact_url, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- operator = True
- rule_type = 'exact_match'
- rule = ''
- description = ''
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) )
-
-
- def GetPredicates( self ):
-
- operator = self._operator.GetValue()
-
- if operator:
-
- operator_description = 'has url: '
-
- else:
-
- operator_description = 'does not have url: '
-
-
- rule_type = 'exact_match'
-
- exact_url = self._exact_url.text()
-
- rule = exact_url
-
- description = operator_description + exact_url
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemKnownURLsDomain( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._operator = ClientGUICommon.BetterChoice( self )
-
- self._operator.addItem( 'has', True )
- self._operator.addItem( 'does not have', False )
-
- self._domain = QW.QLineEdit( self )
- self._domain.setFixedWidth( 250 )
-
- self._domain.setPlaceholderText( 'example.com' )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( operator, rule_type, rule, description ) = predicate.GetValue()
-
- self._operator.SetValue( operator )
- self._domain.setText( rule )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:known url'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._operator, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'a url with domain:'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._domain, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- operator = True
- rule_type = 'domain'
- rule = ''
- description = ''
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) )
-
-
- def GetPredicates( self ):
-
- operator = self._operator.GetValue()
-
- if operator:
-
- operator_description = 'has a url with domain: '
-
- else:
-
- operator_description = 'does not have a url with domain: '
-
-
- rule_type = 'domain'
-
- domain = self._domain.text()
-
- rule = domain
-
- description = operator_description + domain
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemKnownURLsRegex( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._operator = ClientGUICommon.BetterChoice( self )
-
- self._operator.addItem( 'has', True )
- self._operator.addItem( 'does not have', False )
-
- self._regex = QW.QLineEdit( self )
- self._regex.setFixedWidth( 250 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( operator, rule_type, rule, description ) = predicate.GetValue()
-
- self._operator.SetValue( operator )
- self._regex.setText( rule )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:known url'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._operator, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'a url that matches this regex:'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._regex, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- operator = True
- rule_type = 'regex'
- rule = ''
- description = ''
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) )
-
-
- def CheckCanOK( self ):
-
- regex = self._regex.text()
-
- try:
-
- re.compile( regex )
-
- except Exception as e:
-
- raise Exception( 'Cannot compile that regex: {}'.format( e ) )
-
-
-
- def GetPredicates( self ):
-
- operator = self._operator.GetValue()
-
- if operator:
-
- operator_description = 'has a url matching regex: '
-
- else:
-
- operator_description = 'does not have a url matching regex: '
-
-
- rule_type = 'regex'
-
- regex = self._regex.text()
-
- rule = regex
-
- description = operator_description + regex
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemKnownURLsURLClass( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._operator = ClientGUICommon.BetterChoice( self )
-
- self._operator.addItem( 'has', True )
- self._operator.addItem( 'does not have', False )
-
- self._url_classes = ClientGUICommon.BetterChoice( self )
-
- for url_class in HG.client_controller.network_engine.domain_manager.GetURLClasses():
-
- if url_class.ShouldAssociateWithFiles():
-
- self._url_classes.addItem( url_class.GetName(), url_class )
-
-
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( operator, rule_type, rule, description ) = predicate.GetValue()
-
- self._operator.SetValue( operator )
- self._url_classes.SetValue( rule )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:known url'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._operator, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'url matching this class:'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._url_classes, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- operator = True
- rule_type = 'regex'
- rule = None
- description = ''
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) )
-
-
- def GetPredicates( self ):
-
- operator = self._operator.GetValue()
-
- if operator:
-
- operator_description = 'has '
-
- else:
-
- operator_description = 'does not have '
-
-
- rule_type = 'url_class'
-
- url_class = self._url_classes.GetValue()
-
- rule = url_class
-
- description = operator_description + url_class.GetName() + ' url'
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemLimit( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._limit = QP.MakeQSpinBox( self, max=1000000, width = 60 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- limit = predicate.GetValue()
-
- self._limit.setValue( limit )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:limit='), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._limit, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- limit = 256
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT, limit )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT, self._limit.value() ), )
-
- return predicates
-
-
-class PanelPredicateSystemMime( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._mimes = ClientGUIOptionsPanels.OptionsPanelMimes( self, HC.SEARCHABLE_MIMES )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- mimes = predicate.GetValue()
-
- if isinstance( mimes, int ):
-
- mimes = ( mimes, )
-
-
- self._mimes.SetValue( mimes )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText( self, 'system:filetype' ), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._mimes, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- mimes = tuple()
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MIME, mimes )
-
-
- def GetPredicates( self ):
-
- mimes = self._mimes.GetValue()
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MIME, mimes ), )
-
- return predicates
-
-
-class PanelPredicateSystemNumPixels( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._sign = QP.RadioBox( self, choices=[ '<', '\u2248', '=', '>' ] )
-
- self._num_pixels = QP.MakeQSpinBox( self, max=1048576, width = 60 )
-
- self._unit = QP.RadioBox( self, choices=['pixels','kilopixels','megapixels'] )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, num_pixels, unit ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
-
- self._num_pixels.setValue( num_pixels )
-
- self._unit.SetStringSelection( HydrusData.ConvertIntToPixels( unit ) )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:num_pixels'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._num_pixels, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._unit, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- sign = '\u2248'
- num_pixels = 2
- unit = 1000000
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_PIXELS, ( sign, num_pixels, unit ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_PIXELS, ( self._sign.GetStringSelection(), self._num_pixels.value(), HydrusData.ConvertPixelsToInt( self._unit.GetStringSelection() ) ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemNumFrames( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- choices = [ '<', '\u2248', '=', '>' ]
-
- self._sign = QP.RadioBox( self, choices = choices )
-
- self._num_frames = QP.MakeQSpinBox( self, min = 0, max = 1000000, width = 80 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, num_frames ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
- self._num_frames.setValue( num_frames )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText( self, 'system:number of frames' ), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._num_frames, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- sign = '>'
- num_frames = 600
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_FRAMES, ( sign, num_frames ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_FRAMES, ( self._sign.GetStringSelection(), self._num_frames.value() ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemNumTags( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._namespace = ClientGUICommon.NoneableTextCtrl( self, none_phrase = 'all tags' )
- self._namespace.setToolTip( 'Enable but leave blank for unnamespaced tags.' )
-
- self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
-
- self._num_tags = QP.MakeQSpinBox( self, max=2000, width = 60 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( namespace, sign, num_tags ) = predicate.GetValue()
-
- self._namespace.SetValue( namespace )
-
- self._sign.SetStringSelection( sign )
-
- self._num_tags.setValue( num_tags )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:number of tags: namespace:'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._namespace, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._num_tags, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- namespace = None
- sign = '>'
- num_tags = 4
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( namespace, sign, num_tags ) )
-
-
- def GetPredicates( self ):
-
- ( namespace, operator, value ) = ( self._namespace.GetValue(), self._sign.GetStringSelection(), self._num_tags.value() )
-
- predicate = None
-
- if namespace is not None:
-
- number_test = ClientSearch.NumberTest.STATICCreateFromCharacters( operator, value )
-
- if number_test.IsZero():
-
- predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_NAMESPACE, namespace, inclusive = False )
-
- elif number_test.IsAnythingButZero():
-
- predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_NAMESPACE, namespace )
-
-
-
- if predicate is None:
-
- predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( namespace, operator, value ) )
-
-
- predicates = ( predicate, )
-
- return predicates
-
-
-class PanelPredicateSystemNumNotes( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._sign = QP.RadioBox( self, choices = [ '<', '=', '>' ] )
-
- self._num_notes = QP.MakeQSpinBox( self, max = 256, width = 60 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, num_notes ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
-
- self._num_notes.setValue( num_notes )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:number of notes'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._num_notes, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- sign = '='
- num_notes = 1
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ( sign, num_notes ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ( self._sign.GetStringSelection(), self._num_notes.value() ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemNumWords( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
-
- self._num_words = QP.MakeQSpinBox( self, max=1000000, width = 60 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, num_words ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
-
- self._num_words.setValue( num_words )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:number of words'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._num_words, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- sign = '<'
- num_words = 30000
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_WORDS, ( sign, num_words ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_WORDS, ( self._sign.GetStringSelection(), self._num_words.value() ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemRating( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- #
-
- local_like_services = HG.client_controller.services_manager.GetServices( ( HC.LOCAL_RATING_LIKE, ) )
-
- self._like_checkboxes_to_info = {}
-
- self._like_rating_ctrls = []
-
- gridbox = QP.GridLayout( cols = 5 )
-
- gridbox.setColumnStretch( 0, 1 )
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- for service in local_like_services:
-
- name = service.GetName()
- service_key = service.GetServiceKey()
-
- rated_checkbox = QW.QCheckBox( 'rated', self )
- not_rated_checkbox = QW.QCheckBox( 'not rated', self )
- rating_ctrl = ClientGUIRatings.RatingLikeDialog( self, service_key )
-
- self._like_checkboxes_to_info[ rated_checkbox ] = ( service_key, ClientRatings.SET )
- self._like_checkboxes_to_info[ not_rated_checkbox ] = ( service_key, ClientRatings.NULL )
- self._like_rating_ctrls.append( rating_ctrl )
-
- QP.AddToLayout( gridbox, ClientGUICommon.BetterStaticText(self,name), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( gridbox, rated_checkbox, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( gridbox, not_rated_checkbox, CC.FLAGS_CENTER_PERPENDICULAR )
- ClientGUICommon.AddGridboxStretchSpacer( gridbox )
- QP.AddToLayout( gridbox, rating_ctrl, CC.FLAGS_CENTER_PERPENDICULAR )
-
-
- #
-
- local_numerical_services = HG.client_controller.services_manager.GetServices( ( HC.LOCAL_RATING_NUMERICAL, ) )
-
- self._numerical_checkboxes_to_info = {}
-
- self._numerical_rating_ctrls_to_info = {}
-
- for service in local_numerical_services:
-
- name = service.GetName()
- service_key = service.GetServiceKey()
-
- rated_checkbox = QW.QCheckBox( 'rated', self )
- not_rated_checkbox = QW.QCheckBox( 'not rated', self )
- choice = QP.RadioBox( self, choices=['>','<','=','\u2248'] )
- rating_ctrl = ClientGUIRatings.RatingNumericalDialog( self, service_key )
-
- choice.Select( 2 )
-
- self._numerical_checkboxes_to_info[ rated_checkbox ] = ( service_key, ClientRatings.SET )
- self._numerical_checkboxes_to_info[ not_rated_checkbox ] = ( service_key, ClientRatings.NULL )
- self._numerical_rating_ctrls_to_info[ rating_ctrl ] = choice
-
- QP.AddToLayout( gridbox, ClientGUICommon.BetterStaticText(self,name), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( gridbox, rated_checkbox, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( gridbox, not_rated_checkbox, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( gridbox, choice, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( gridbox, rating_ctrl, CC.FLAGS_CENTER_PERPENDICULAR )
-
-
- #
-
- vbox = QP.VBoxLayout()
-
- QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
-
- self.setLayout( vbox )
-
-
- def _GetDefaultPredicate( self ):
-
- # not used for now. not a great scenario here
-
- service_key = None
- rating_state = ClientRatings.NULL
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATING, ( '=', 'rated', service_key ) )
-
-
- def GetPredicates( self ):
-
- infos = []
-
- #
-
- for ( checkbox, ( service_key, rating_state ) ) in list(self._like_checkboxes_to_info.items()):
-
- if checkbox.isChecked():
-
- if rating_state == ClientRatings.SET:
-
- value = 'rated'
-
- elif rating_state == ClientRatings.NULL:
-
- value = 'not rated'
-
-
- infos.append( ( '=', value, service_key ) )
-
-
-
- for ctrl in self._like_rating_ctrls:
-
- rating_state = ctrl.GetRatingState()
-
- if rating_state in ( ClientRatings.LIKE, ClientRatings.DISLIKE ):
-
- if rating_state == ClientRatings.LIKE:
-
- value = 1
-
- elif rating_state == ClientRatings.DISLIKE:
-
- value = 0
-
-
- service_key = ctrl.GetServiceKey()
-
- infos.append( ( '=', value, service_key ) )
-
-
-
- #
-
- for ( checkbox, ( service_key, rating_state ) ) in list(self._numerical_checkboxes_to_info.items()):
-
- if checkbox.isChecked():
-
- if rating_state == ClientRatings.SET:
-
- value = 'rated'
-
- elif rating_state == ClientRatings.NULL:
-
- value = 'not rated'
-
-
- infos.append( ( '=', value, service_key ) )
-
-
-
- for ( ctrl, choice ) in list(self._numerical_rating_ctrls_to_info.items()):
-
- rating_state = ctrl.GetRatingState()
-
- if rating_state == ClientRatings.SET:
-
- operator = choice.GetStringSelection()
-
- value = ctrl.GetRating()
-
- service_key = ctrl.GetServiceKey()
-
- infos.append( ( operator, value, service_key ) )
-
-
-
- #
-
- predicates = [ ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATING, info ) for info in infos ]
-
- return predicates
-
-
-class PanelPredicateSystemRatio( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._sign = QP.RadioBox( self, choices=['=','wider than','taller than','\u2248'] )
-
- self._width = QP.MakeQSpinBox( self, max=50000, width = 60 )
-
- self._height = QP.MakeQSpinBox( self, max=50000, width = 60 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, width, height ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
-
- self._width.setValue( width )
-
- self._height.setValue( height )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:ratio'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._width, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,':'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._height, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- sign = '<'
- width = 16
- height = 9
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ( sign, width, height ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ( self._sign.GetStringSelection(), self._width.value(), self._height.value() ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemSimilarTo( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._hashes = QW.QPlainTextEdit( self )
-
- ( init_width, init_height ) = ClientGUIFunctions.ConvertTextToPixels( self._hashes, ( 66, 10 ) )
-
- self._hashes.setMinimumSize( QC.QSize( init_width, init_height ) )
-
- self._max_hamming = QP.MakeQSpinBox( self, max=256, width = 60 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- self._hashes.setPlaceholderText( 'enter hash (paste newline-separated for multiple hashes)' )
-
- ( hashes, hamming_distance ) = predicate.GetValue()
-
- hashes_text = os.linesep.join( [ hash.hex() for hash in hashes ] )
-
- self._hashes.setPlainText( hashes_text )
-
- self._max_hamming.setValue( hamming_distance )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:similar_to'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._hashes, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, QW.QLabel( '\u2248', self ), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._max_hamming, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- hashes = tuple()
- max_hamming = 4
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_SIMILAR_TO, ( hashes, max_hamming ) )
-
-
- def GetPredicates( self ):
-
- hex_hashes_raw = self._hashes.toPlainText()
-
- hex_hashes = HydrusText.DeserialiseNewlinedTexts( hex_hashes_raw )
-
- hex_hashes = [ HydrusText.HexFilter( hex_hash ) for hex_hash in hex_hashes ]
-
- hex_hashes = HydrusData.DedupeList( hex_hashes )
-
- hashes = tuple( [ bytes.fromhex( hex_hash ) for hex_hash in hex_hashes ] )
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_SIMILAR_TO, ( hashes, self._max_hamming.value() ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemSize( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
-
- self._bytes = ClientGUIControls.BytesControl( self )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, size, unit ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
-
- self._bytes.SetSeparatedValue( size, unit )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:filesize'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._bytes, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- sign = '<'
- size = 200
- unit = 1024
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_SIZE, ( sign, size, unit ) )
-
-
- def GetPredicates( self ):
-
- ( size, unit ) = self._bytes.GetSeparatedValue()
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_SIZE, ( self._sign.GetStringSelection(), size, unit ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemTagAsNumber( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._namespace = QW.QLineEdit( self )
-
- choices = [ '<', '\u2248', '>' ]
-
- self._sign = QP.RadioBox( self, choices = choices )
-
- self._num = QP.MakeQSpinBox( self, min=-99999999, max=99999999 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( namespace, sign, num ) = predicate.GetValue()
-
- self._namespace.setText( namespace )
- self._sign.SetStringSelection( sign )
- self._num.setValue( num )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:tag as number'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._namespace, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._num, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- namespace = 'page'
- sign = '>'
- num = 0
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_TAG_AS_NUMBER, ( namespace, sign, num ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_TAG_AS_NUMBER, ( self._namespace.text(), self._sign.GetStringSelection(), self._num.value() ) ), )
-
- return predicates
-
-
-class PanelPredicateSystemWidth( PanelPredicateSystem ):
-
- def __init__( self, parent, predicate ):
-
- PanelPredicateSystem.__init__( self, parent )
-
- self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
-
- self._width = QP.MakeQSpinBox( self, max=200000, width = 60 )
-
- #
-
- predicate = self._GetPredicateToInitialisePanelWith( predicate )
-
- ( sign, width ) = predicate.GetValue()
-
- self._sign.SetStringSelection( sign )
-
- self._width.setValue( width )
-
- #
-
- hbox = QP.HBoxLayout()
-
- QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:width'), CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
- QP.AddToLayout( hbox, self._width, CC.FLAGS_CENTER_PERPENDICULAR )
-
- hbox.addStretch( 1 )
-
- self.setLayout( hbox )
-
-
- def _GetDefaultPredicate( self ):
-
- sign = '='
- width = 1920
-
- return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH, ( sign, width ) )
-
-
- def GetPredicates( self ):
-
- predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH, ( self._sign.GetStringSelection(), self._width.value() ) ), )
-
- return predicates
-
-
diff --git a/hydrus/client/gui/ClientGUITags.py b/hydrus/client/gui/ClientGUITags.py
index db809f04..4466d874 100644
--- a/hydrus/client/gui/ClientGUITags.py
+++ b/hydrus/client/gui/ClientGUITags.py
@@ -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
diff --git a/hydrus/client/gui/ClientGUITopLevelWindowsPanels.py b/hydrus/client/gui/ClientGUITopLevelWindowsPanels.py
index b37bc36d..92baf89a 100644
--- a/hydrus/client/gui/ClientGUITopLevelWindowsPanels.py
+++ b/hydrus/client/gui/ClientGUITopLevelWindowsPanels.py
@@ -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
diff --git a/hydrus/client/gui/lists/ClientGUIListBoxes.py b/hydrus/client/gui/lists/ClientGUIListBoxes.py
index b1725f3a..88093ea3 100644
--- a/hydrus/client/gui/lists/ClientGUIListBoxes.py
+++ b/hydrus/client/gui/lists/ClientGUIListBoxes.py
@@ -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 )
diff --git a/hydrus/client/gui/ClientGUIACDropdown.py b/hydrus/client/gui/search/ClientGUIACDropdown.py
similarity index 97%
rename from hydrus/client/gui/ClientGUIACDropdown.py
rename to hydrus/client/gui/search/ClientGUIACDropdown.py
index ca82abd9..c37ddb2c 100644
--- a/hydrus/client/gui/ClientGUIACDropdown.py
+++ b/hydrus/client/gui/search/ClientGUIACDropdown.py
@@ -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()
diff --git a/hydrus/client/gui/search/ClientGUIPredicatesMultiple.py b/hydrus/client/gui/search/ClientGUIPredicatesMultiple.py
new file mode 100644
index 00000000..9bccc8d5
--- /dev/null
+++ b/hydrus/client/gui/search/ClientGUIPredicatesMultiple.py
@@ -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
+
+
diff --git a/hydrus/client/gui/search/ClientGUIPredicatesOR.py b/hydrus/client/gui/search/ClientGUIPredicatesOR.py
new file mode 100644
index 00000000..5a0cb9be
--- /dev/null
+++ b/hydrus/client/gui/search/ClientGUIPredicatesOR.py
@@ -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 ]
+
diff --git a/hydrus/client/gui/search/ClientGUIPredicatesSingle.py b/hydrus/client/gui/search/ClientGUIPredicatesSingle.py
new file mode 100644
index 00000000..535a3ea7
--- /dev/null
+++ b/hydrus/client/gui/search/ClientGUIPredicatesSingle.py
@@ -0,0 +1,1892 @@
+import os
+import re
+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 HydrusData
+from hydrus.core import HydrusExceptions
+from hydrus.core import HydrusGlobals as HG
+from hydrus.core import HydrusText
+
+from hydrus.client import ClientConstants as CC
+from hydrus.client import ClientSearch
+from hydrus.client.gui import ClientGUICommon
+from hydrus.client.gui import ClientGUIControls
+from hydrus.client.gui import ClientGUIFunctions
+from hydrus.client.gui import ClientGUIOptionsPanels
+from hydrus.client.gui import ClientGUITime
+from hydrus.client.gui import QtPorting as QP
+
+class StaticSystemPredicateButton( QW.QPushButton ):
+
+ def __init__( self, parent, ok_panel, predicates, forced_label = None ):
+
+ QW.QPushButton.__init__( self, parent )
+
+ self._ok_panel = ok_panel
+ self._predicates = predicates
+ self._forced_label = forced_label
+
+ if forced_label is None:
+
+ label = ', '.join( ( predicate.ToString() for predicate in self._predicates ) )
+
+ else:
+
+ label = forced_label
+
+
+ self.setText( label )
+
+ self.clicked.connect( self.DoOK )
+
+
+ def DoOK( self ):
+
+ self._ok_panel.SubPanelOK( self._predicates )
+
+
+class InvertiblePredicateButton( ClientGUICommon.BetterButton ):
+
+ def __init__( self, parent: QW.QWidget, predicate: ClientSearch.Predicate ):
+
+ self._predicate = predicate
+
+ ClientGUICommon.BetterButton.__init__( self, parent, 'predicate', self._ButtonHit )
+
+ self._UpdateLabel()
+
+
+ def _ButtonHit( self ):
+
+ inverse_predicate = self._predicate.GetInverseCopy()
+
+ if inverse_predicate is not None:
+
+ self._predicate = inverse_predicate
+
+ self._UpdateLabel()
+
+
+
+ def _UpdateLabel( self ):
+
+ s = self._predicate.ToString( with_count = False )
+
+ self.setText( s )
+
+
+ def GetPredicate( self ):
+
+ return self._predicate
+
+
+class PanelPredicateSystem( QW.QWidget ):
+
+ def CheckValid( self ):
+
+ try:
+
+ predicates = self.GetPredicates()
+
+ except Exception as e:
+
+ raise HydrusExceptions.VetoException( str( e ) )
+
+
+
+ def ClearCustomDefault( self ):
+
+ raise NotImplementedError()
+
+
+ def GetPredicates( self ):
+
+ raise NotImplementedError()
+
+
+ def SaveCustomDefault( self ):
+
+ raise NotImplementedError()
+
+
+ def UsesCustomDefault( self ) -> bool:
+
+ raise NotImplementedError()
+
+
+class PanelPredicateSystemSingle( PanelPredicateSystem ):
+
+ def _GetPredicateToInitialisePanelWith( self, predicate: ClientSearch.Predicate ) -> ClientSearch.Predicate:
+
+ default_predicate = self.GetDefaultPredicate()
+
+ if predicate.IsUIEditable( default_predicate ):
+
+ return predicate
+
+
+ custom_defaults = HG.client_controller.new_options.GetCustomDefaultSystemPredicates( comparable_predicate = default_predicate )
+
+ if len( custom_defaults ) > 0:
+
+ return list( custom_defaults )[0]
+
+
+ return default_predicate
+
+
+ def ClearCustomDefault( self ):
+
+ default_predicate = self.GetDefaultPredicate()
+
+ HG.client_controller.new_options.ClearCustomDefaultSystemPredicates( comparable_predicate = default_predicate )
+
+
+ def GetDefaultPredicate( self ) -> ClientSearch.Predicate:
+
+ raise NotImplementedError()
+
+
+ def GetPredicates( self ):
+
+ raise NotImplementedError()
+
+
+ def SaveCustomDefault( self ):
+
+ predicates = self.GetPredicates()
+
+ HG.client_controller.new_options.SetCustomDefaultSystemPredicates( comparable_predicates = predicates )
+
+
+ def UsesCustomDefault( self ) -> bool:
+
+ default_predicate = self.GetDefaultPredicate()
+
+ custom_defaults = HG.client_controller.new_options.GetCustomDefaultSystemPredicates( comparable_predicate = default_predicate )
+
+ return len( custom_defaults ) > 0
+
+
+class PanelPredicateSystemAgeDate( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
+
+ self._date = QW.QCalendarWidget( self )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, age_type, ( years, months, days ) ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+
+ qt_dt = QC.QDate( years, months, days )
+
+ self._date.setSelectedDate( qt_dt )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:time imported'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._date, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ) -> ClientSearch.Predicate:
+
+ qt_dt = QC.QDate.currentDate()
+
+ qt_dt.addDays( -7 )
+
+ year = qt_dt.year()
+ month = qt_dt.month()
+ day = qt_dt.day()
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( '>', 'date', ( year, month, day ) ) )
+
+
+ def GetPredicates( self ):
+
+ qt_dt = self._date.selectedDate()
+
+ year = qt_dt.year()
+ month = qt_dt.month()
+ day = qt_dt.day()
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( self._sign.GetStringSelection(), 'date', ( year, month, day ) ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemAgeDelta( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._sign = QP.RadioBox( self, choices=['<','\u2248','>'] )
+
+ self._years = QP.MakeQSpinBox( self, max=30, width = 60 )
+ self._months = QP.MakeQSpinBox( self, max=60, width = 60 )
+ self._days = QP.MakeQSpinBox( self, max=90, width = 60 )
+ self._hours = QP.MakeQSpinBox( self, max=24, width = 60 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, age_type, ( years, months, days, hours ) ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+
+ self._years.setValue( years )
+ self._months.setValue( months )
+ self._days.setValue( days )
+ self._hours.setValue( hours )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:time imported'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._years, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'years'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._months, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'months'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._days, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'days'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._hours, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'hours'), CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( '<', 'delta', ( 0, 0, 7, 0 ) ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_AGE, ( self._sign.GetStringSelection(), 'delta', (self._years.value(), self._months.value(), self._days.value(), self._hours.value() ) ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemModifiedDate( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
+
+ self._date = QW.QCalendarWidget( self )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, age_type, ( years, months, days ) ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+
+ qt_dt = QC.QDate( years, months, days )
+
+ self._date.setSelectedDate( qt_dt )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:modified date'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._date, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ) -> ClientSearch.Predicate:
+
+ qt_dt = QC.QDate.currentDate()
+
+ qt_dt.addDays( -7 )
+
+ year = qt_dt.year()
+ month = qt_dt.month()
+ day = qt_dt.day()
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME, ( '>', 'date', ( year, month, day ) ) )
+
+
+ def GetPredicates( self ):
+
+ qt_dt = self._date.selectedDate()
+
+ year = qt_dt.year()
+ month = qt_dt.month()
+ day = qt_dt.day()
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME, ( self._sign.GetStringSelection(), 'date', ( year, month, day ) ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemModifiedDelta( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._sign = QP.RadioBox( self, choices=['<','\u2248','>'] )
+
+ self._years = QP.MakeQSpinBox( self, max=30 )
+ self._months = QP.MakeQSpinBox( self, max=60 )
+ self._days = QP.MakeQSpinBox( self, max=90 )
+ self._hours = QP.MakeQSpinBox( self, max=24 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, age_type, ( years, months, days, hours ) ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+
+ self._years.setValue( years )
+ self._months.setValue( months )
+ self._days.setValue( days )
+ self._hours.setValue( hours )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:modified date'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._years, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'years'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._months, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'months'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._days, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'days'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._hours, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'hours'), CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME, ( '<', 'delta', ( 0, 0, 7, 0 ) ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MODIFIED_TIME, ( self._sign.GetStringSelection(), 'delta', ( self._years.value(), self._months.value(), self._days.value(), self._hours.value() ) ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemDuplicateRelationships( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ choices = [ '<', '\u2248', '=', '>' ]
+
+ self._sign = QP.RadioBox( self, choices = choices )
+
+ self._num = QP.MakeQSpinBox( self, min=0, max=65535 )
+
+ choices = [ ( HC.duplicate_type_string_lookup[ status ], status ) for status in ( HC.DUPLICATE_MEMBER, HC.DUPLICATE_ALTERNATE, HC.DUPLICATE_FALSE_POSITIVE, HC.DUPLICATE_POTENTIAL ) ]
+
+ self._dupe_type = ClientGUICommon.BetterRadioBox( self, choices = choices, vertical = True )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, num, dupe_type ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+ self._num.setValue( num )
+ self._dupe_type.SetValue( dupe_type )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:num file relationships'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._num, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._dupe_type, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ sign = '>'
+ num = 0
+ dupe_type = HC.DUPLICATE_MEMBER
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_COUNT, ( sign, num, dupe_type ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_RELATIONSHIPS_COUNT, ( self._sign.GetStringSelection(), self._num.value(), self._dupe_type.GetValue() ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemDuration( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ choices = [ '<', '\u2248', '=', '>' ]
+
+ self._sign = QP.RadioBox( self, choices = choices )
+
+ self._duration_s = QP.MakeQSpinBox( self, max=3599, width = 60 )
+ self._duration_ms = QP.MakeQSpinBox( self, max=999, width = 60 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, ms ) = predicate.GetValue()
+
+ s = ms // 1000
+
+ ms = ms % 1000
+
+ self._sign.SetStringSelection( sign )
+
+ self._duration_s.setValue( s )
+ self._duration_ms.setValue( ms )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:duration'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._duration_s, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'s'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._duration_ms, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'ms'), CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ sign = '>'
+ duration = 0
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION, ( sign, duration ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_DURATION, ( self._sign.GetStringSelection(), self._duration_s.value() * 1000 + self._duration_ms.value() ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemFileService( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._sign = ClientGUICommon.BetterRadioBox( self, choices = [ ( 'is', True ), ( 'is not', False ) ], vertical = True )
+
+ self._current_pending = ClientGUICommon.BetterRadioBox( self, choices = [ ( 'currently in', HC.CONTENT_STATUS_CURRENT ), ( 'pending to', HC.CONTENT_STATUS_PENDING ) ], vertical = True )
+
+ services = HG.client_controller.services_manager.GetServices( HC.FILE_SERVICES )
+
+ choices = [ ( service.GetName(), service.GetServiceKey() ) for service in services ]
+
+ self._file_service_key = ClientGUICommon.BetterRadioBox( self, choices = choices, vertical = True )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, current_pending, file_service_key ) = predicate.GetValue()
+
+ self._sign.SetValue( sign )
+ self._current_pending.SetValue( current_pending )
+ self._file_service_key.SetValue( file_service_key )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:file service:'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._current_pending, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._file_service_key, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ sign = True
+ current_pending = HC.CONTENT_STATUS_CURRENT
+ file_service_key = bytes()
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_SERVICE, ( sign, current_pending, file_service_key ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_SERVICE, ( self._sign.GetValue(), self._current_pending.GetValue(), self._file_service_key.GetValue() ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemFileViewingStatsViews( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._viewing_locations = QP.CheckListBox( self )
+
+ self._viewing_locations.Append( 'media views', 'media' )
+ self._viewing_locations.Append( 'preview views', 'preview' )
+
+ self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
+
+ self._num = QP.MakeQSpinBox( self, min=0, max=1000000 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( view_type, viewing_locations, sign, num ) = predicate.GetValue()
+
+ self._viewing_locations.SetCheckedData( viewing_locations )
+
+ ( width, height ) = ClientGUIFunctions.ConvertTextToPixels( self._viewing_locations, ( 10, 3 ) )
+
+ self._viewing_locations.setMaximumHeight( height )
+
+ self._sign.SetStringSelection( sign )
+
+ self._num.setValue( num )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._viewing_locations, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._num, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ viewing_locations = ( 'media', )
+ sign = '>'
+ num = 10
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS, ( 'views', tuple( viewing_locations ), sign, num ) )
+
+
+ def GetPredicates( self ):
+
+ viewing_locations = self._viewing_locations.GetChecked()
+
+ if len( viewing_locations ) == 0:
+
+ viewing_locations = [ 'media' ]
+
+
+ sign = self._sign.GetStringSelection()
+
+ num = self._num.value()
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS, ( 'views', tuple( viewing_locations ), sign, num ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemFileViewingStatsViewtime( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._viewing_locations = QP.CheckListBox( self )
+
+ self._viewing_locations.Append( 'media viewtime', 'media' )
+ self._viewing_locations.Append( 'preview viewtime', 'preview' )
+
+ self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
+
+ self._time_delta = ClientGUITime.TimeDeltaCtrl( self, min = 0, days = True, hours = True, minutes = True, seconds = True )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( view_type, viewing_locations, sign, time_delta ) = predicate.GetValue()
+
+ self._viewing_locations.SetCheckedData( viewing_locations )
+
+ ( width, height ) = ClientGUIFunctions.ConvertTextToPixels( self._viewing_locations, ( 10, 3 ) )
+
+ self._viewing_locations.setMaximumHeight( height )
+
+ self._sign.SetStringSelection( sign )
+
+ self._time_delta.SetValue( time_delta )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._viewing_locations, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._time_delta, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ viewing_locations = ( 'media', )
+ sign = '>'
+ time_delta = 600
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS, ( 'viewtime', tuple( viewing_locations ), sign, time_delta ) )
+
+
+ def GetPredicates( self ):
+
+ viewing_locations = self._viewing_locations.GetChecked()
+
+ if len( viewing_locations ) == 0:
+
+ viewing_locations = [ 'media' ]
+
+
+ sign = self._sign.GetStringSelection()
+
+ time_delta = self._time_delta.GetValue()
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FILE_VIEWING_STATS, ( 'viewtime', tuple( viewing_locations ), sign, time_delta ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemFramerate( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ choices = [ '<', '=', '>' ]
+
+ self._sign = QP.RadioBox( self, choices = choices )
+
+ self._framerate = QP.MakeQSpinBox( self, min = 1, max = 3600, width = 60 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, framerate ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+ self._framerate.setValue( framerate )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText( self, 'system:framerate' ), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._framerate, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText( self, 'fps' ), CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ vbox = QP.VBoxLayout()
+
+ QP.AddToLayout( vbox, ClientGUICommon.BetterStaticText( 'All framerate searches are +/- 5%. Exactly searching for 29.97 is not currently possible.' ), CC.FLAGS_EXPAND_PERPENDICULAR )
+ QP.AddToLayout( vbox, hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
+
+ self.setLayout( vbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ sign = '='
+ framerate = 60
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FRAMERATE, ( sign, framerate ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_FRAMERATE, ( self._sign.GetStringSelection(), self._framerate.value() ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemHash( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._hashes = QW.QPlainTextEdit( self )
+
+ ( init_width, init_height ) = ClientGUIFunctions.ConvertTextToPixels( self._hashes, ( 66, 10 ) )
+
+ self._hashes.setMinimumSize( QC.QSize( init_width, init_height ) )
+
+ choices = [ 'sha256', 'md5', 'sha1', 'sha512' ]
+
+ self._hash_type = QP.RadioBox( self, choices = choices, vertical = True )
+
+ self._hashes.setPlaceholderText( 'enter hash (paste newline-separated for multiple hashes)' )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( hashes, hash_type ) = predicate.GetValue()
+
+ hashes_text = os.linesep.join( [ hash.hex() for hash in hashes ] )
+
+ self._hashes.setPlainText( hashes_text )
+
+ self._hash_type.SetStringSelection( hash_type )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:hash='), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._hashes, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._hash_type, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ hashes = tuple()
+ hash_type = 'sha256'
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HASH, ( hashes, hash_type ) )
+
+
+ def GetPredicates( self ):
+
+ hash_type = self._hash_type.GetStringSelection()
+
+ hex_hashes_raw = self._hashes.toPlainText()
+
+ hashes = HydrusData.ParseHashesFromRawHexText( hash_type, hex_hashes_raw )
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HASH, ( hashes, hash_type ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemHasNoteName( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._operator = ClientGUICommon.BetterChoice( self )
+
+ self._operator.addItem( 'has note with name ', True )
+ self._operator.addItem( 'does not have note with name', False )
+
+ self._name = QW.QLineEdit( self )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( operator, name ) = predicate.GetValue()
+
+ self._operator.SetValue( operator )
+ self._name.setText( name )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:note name'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._operator, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._name, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ operator = True
+ name = ''
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_NOTE_NAME, ( operator, name ) )
+
+
+ def GetPredicates( self ):
+
+ name = self._name.text()
+
+ if name == '':
+
+ name = 'notes'
+
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HAS_NOTE_NAME, ( self._operator.GetValue(), name ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemHeight( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
+
+ self._height = QP.MakeQSpinBox( self, max=200000, width = 60 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, height ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+
+ self._height.setValue( height )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:height'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._height, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ sign = '='
+ height = 1080
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT, ( sign, height ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_HEIGHT, ( self._sign.GetStringSelection(), self._height.value() ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemKnownURLsExactURL( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._operator = ClientGUICommon.BetterChoice( self )
+
+ self._operator.addItem( 'has', True )
+ self._operator.addItem( 'does not have', False )
+
+ self._exact_url = QW.QLineEdit( self )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( operator, rule_type, rule, description ) = predicate.GetValue()
+
+ self._operator.SetValue( operator )
+ self._exact_url.setText( rule )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:known url'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._operator, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'exact url:'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._exact_url, CC.FLAGS_EXPAND_BOTH_WAYS )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ operator = True
+ rule_type = 'exact_match'
+ rule = ''
+ description = ''
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) )
+
+
+ def GetPredicates( self ):
+
+ operator = self._operator.GetValue()
+
+ if operator:
+
+ operator_description = 'has url: '
+
+ else:
+
+ operator_description = 'does not have url: '
+
+
+ rule_type = 'exact_match'
+
+ exact_url = self._exact_url.text()
+
+ rule = exact_url
+
+ description = operator_description + exact_url
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemKnownURLsDomain( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._operator = ClientGUICommon.BetterChoice( self )
+
+ self._operator.addItem( 'has', True )
+ self._operator.addItem( 'does not have', False )
+
+ self._domain = QW.QLineEdit( self )
+
+ self._domain.setPlaceholderText( 'example.com' )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( operator, rule_type, rule, description ) = predicate.GetValue()
+
+ self._operator.SetValue( operator )
+ self._domain.setText( rule )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:known url'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._operator, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'a url with domain:'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._domain, CC.FLAGS_EXPAND_BOTH_WAYS )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ operator = True
+ rule_type = 'domain'
+ rule = ''
+ description = ''
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) )
+
+
+ def GetPredicates( self ):
+
+ operator = self._operator.GetValue()
+
+ if operator:
+
+ operator_description = 'has a url with domain: '
+
+ else:
+
+ operator_description = 'does not have a url with domain: '
+
+
+ rule_type = 'domain'
+
+ domain = self._domain.text()
+
+ rule = domain
+
+ description = operator_description + domain
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemKnownURLsRegex( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._operator = ClientGUICommon.BetterChoice( self )
+
+ self._operator.addItem( 'has', True )
+ self._operator.addItem( 'does not have', False )
+
+ self._regex = QW.QLineEdit( self )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( operator, rule_type, rule, description ) = predicate.GetValue()
+
+ self._operator.SetValue( operator )
+ self._regex.setText( rule )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:known url'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._operator, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'a url that matches this regex:'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._regex, CC.FLAGS_EXPAND_BOTH_WAYS )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ operator = True
+ rule_type = 'regex'
+ rule = ''
+ description = ''
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) )
+
+
+ def CheckValid( self ):
+
+ regex = self._regex.text()
+
+ try:
+
+ re.compile( regex )
+
+ except Exception as e:
+
+ raise Exception( 'Cannot compile that regex: {}'.format( e ) )
+
+
+
+ def GetPredicates( self ):
+
+ operator = self._operator.GetValue()
+
+ if operator:
+
+ operator_description = 'has a url matching regex: '
+
+ else:
+
+ operator_description = 'does not have a url matching regex: '
+
+
+ rule_type = 'regex'
+
+ regex = self._regex.text()
+
+ rule = regex
+
+ description = operator_description + regex
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemKnownURLsURLClass( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._operator = ClientGUICommon.BetterChoice( self )
+
+ self._operator.addItem( 'has', True )
+ self._operator.addItem( 'does not have', False )
+
+ self._url_classes = ClientGUICommon.BetterChoice( self )
+
+ for url_class in HG.client_controller.network_engine.domain_manager.GetURLClasses():
+
+ if url_class.ShouldAssociateWithFiles():
+
+ self._url_classes.addItem( url_class.GetName(), url_class )
+
+
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( operator, rule_type, rule, description ) = predicate.GetValue()
+
+ self._operator.SetValue( operator )
+ self._url_classes.SetValue( rule )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:known url'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._operator, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'url matching this class:'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._url_classes, CC.FLAGS_EXPAND_BOTH_WAYS )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ operator = True
+ rule_type = 'regex'
+ rule = None
+ description = ''
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) )
+
+
+ def GetPredicates( self ):
+
+ operator = self._operator.GetValue()
+
+ if operator:
+
+ operator_description = 'has '
+
+ else:
+
+ operator_description = 'does not have '
+
+
+ rule_type = 'url_class'
+
+ url_class = self._url_classes.GetValue()
+
+ rule = url_class
+
+ description = operator_description + url_class.GetName() + ' url'
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_KNOWN_URLS, ( operator, rule_type, rule, description ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemLimit( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._limit = QP.MakeQSpinBox( self, max=1000000, width = 60 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ limit = predicate.GetValue()
+
+ self._limit.setValue( limit )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:limit='), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._limit, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ limit = 256
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT, limit )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_LIMIT, self._limit.value() ), )
+
+ return predicates
+
+
+class PanelPredicateSystemMime( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._mimes = ClientGUIOptionsPanels.OptionsPanelMimes( self, HC.SEARCHABLE_MIMES )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ mimes = predicate.GetValue()
+
+ if isinstance( mimes, int ):
+
+ mimes = ( mimes, )
+
+
+ self._mimes.SetValue( mimes )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText( self, 'system:filetype' ), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._mimes, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ mimes = tuple()
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MIME, mimes )
+
+
+ def GetPredicates( self ):
+
+ mimes = self._mimes.GetValue()
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_MIME, mimes ), )
+
+ return predicates
+
+
+class PanelPredicateSystemNumPixels( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._sign = QP.RadioBox( self, choices=[ '<', '\u2248', '=', '>' ] )
+
+ self._num_pixels = QP.MakeQSpinBox( self, max=1048576, width = 60 )
+
+ self._unit = QP.RadioBox( self, choices=['pixels','kilopixels','megapixels'] )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, num_pixels, unit ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+
+ self._num_pixels.setValue( num_pixels )
+
+ self._unit.SetStringSelection( HydrusData.ConvertIntToPixels( unit ) )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:num_pixels'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._num_pixels, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._unit, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ sign = '\u2248'
+ num_pixels = 2
+ unit = 1000000
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_PIXELS, ( sign, num_pixels, unit ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_PIXELS, ( self._sign.GetStringSelection(), self._num_pixels.value(), HydrusData.ConvertPixelsToInt( self._unit.GetStringSelection() ) ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemNumFrames( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ choices = [ '<', '\u2248', '=', '>' ]
+
+ self._sign = QP.RadioBox( self, choices = choices )
+
+ self._num_frames = QP.MakeQSpinBox( self, min = 0, max = 1000000, width = 80 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, num_frames ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+ self._num_frames.setValue( num_frames )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText( self, 'system:number of frames' ), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._num_frames, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ sign = '>'
+ num_frames = 600
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_FRAMES, ( sign, num_frames ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_FRAMES, ( self._sign.GetStringSelection(), self._num_frames.value() ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemNumTags( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._namespace = ClientGUICommon.NoneableTextCtrl( self, none_phrase = 'all tags' )
+ self._namespace.setToolTip( 'Enable but leave blank for unnamespaced tags.' )
+
+ self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
+
+ self._num_tags = QP.MakeQSpinBox( self, max=2000, width = 60 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( namespace, sign, num_tags ) = predicate.GetValue()
+
+ self._namespace.SetValue( namespace )
+
+ self._sign.SetStringSelection( sign )
+
+ self._num_tags.setValue( num_tags )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:number of tags: namespace:'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._namespace, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._num_tags, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ namespace = None
+ sign = '>'
+ num_tags = 4
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( namespace, sign, num_tags ) )
+
+
+ def GetPredicates( self ):
+
+ ( namespace, operator, value ) = ( self._namespace.GetValue(), self._sign.GetStringSelection(), self._num_tags.value() )
+
+ predicate = None
+
+ if namespace is not None:
+
+ number_test = ClientSearch.NumberTest.STATICCreateFromCharacters( operator, value )
+
+ if number_test.IsZero():
+
+ predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_NAMESPACE, namespace, inclusive = False )
+
+ elif number_test.IsAnythingButZero():
+
+ predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_NAMESPACE, namespace )
+
+
+
+ if predicate is None:
+
+ predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( namespace, operator, value ) )
+
+
+ predicates = ( predicate, )
+
+ return predicates
+
+
+class PanelPredicateSystemNumNotes( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._sign = QP.RadioBox( self, choices = [ '<', '=', '>' ] )
+
+ self._num_notes = QP.MakeQSpinBox( self, max = 256, width = 60 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, num_notes ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+
+ self._num_notes.setValue( num_notes )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:number of notes'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._num_notes, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ sign = '='
+ num_notes = 1
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ( sign, num_notes ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_NOTES, ( self._sign.GetStringSelection(), self._num_notes.value() ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemNumWords( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
+
+ self._num_words = QP.MakeQSpinBox( self, max=1000000, width = 60 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, num_words ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+
+ self._num_words.setValue( num_words )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:number of words'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._num_words, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ sign = '<'
+ num_words = 30000
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_WORDS, ( sign, num_words ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_WORDS, ( self._sign.GetStringSelection(), self._num_words.value() ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemRatio( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._sign = QP.RadioBox( self, choices=['=','wider than','taller than','\u2248'] )
+
+ self._width = QP.MakeQSpinBox( self, max=50000, width = 60 )
+
+ self._height = QP.MakeQSpinBox( self, max=50000, width = 60 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, width, height ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+
+ self._width.setValue( width )
+
+ self._height.setValue( height )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:ratio'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._width, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,':'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._height, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ sign = '<'
+ width = 16
+ height = 9
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ( sign, width, height ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_RATIO, ( self._sign.GetStringSelection(), self._width.value(), self._height.value() ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemSimilarTo( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._hashes = QW.QPlainTextEdit( self )
+
+ ( init_width, init_height ) = ClientGUIFunctions.ConvertTextToPixels( self._hashes, ( 66, 10 ) )
+
+ self._hashes.setMinimumSize( QC.QSize( init_width, init_height ) )
+
+ self._max_hamming = QP.MakeQSpinBox( self, max=256, width = 60 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ self._hashes.setPlaceholderText( 'enter hash (paste newline-separated for multiple hashes)' )
+
+ ( hashes, hamming_distance ) = predicate.GetValue()
+
+ hashes_text = os.linesep.join( [ hash.hex() for hash in hashes ] )
+
+ self._hashes.setPlainText( hashes_text )
+
+ self._max_hamming.setValue( hamming_distance )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:similar_to'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._hashes, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, QW.QLabel( '\u2248', self ), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._max_hamming, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ hashes = tuple()
+ max_hamming = 4
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_SIMILAR_TO, ( hashes, max_hamming ) )
+
+
+ def GetPredicates( self ):
+
+ hex_hashes_raw = self._hashes.toPlainText()
+
+ hashes = HydrusData.ParseHashesFromRawHexText( 'sha256', hex_hashes_raw )
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_SIMILAR_TO, ( hashes, self._max_hamming.value() ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemSize( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
+
+ self._bytes = ClientGUIControls.BytesControl( self )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, size, unit ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+
+ self._bytes.SetSeparatedValue( size, unit )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:filesize'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._bytes, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ sign = '<'
+ size = 200
+ unit = 1024
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_SIZE, ( sign, size, unit ) )
+
+
+ def GetPredicates( self ):
+
+ ( size, unit ) = self._bytes.GetSeparatedValue()
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_SIZE, ( self._sign.GetStringSelection(), size, unit ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemTagAsNumber( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._namespace = QW.QLineEdit( self )
+
+ choices = [ '<', '\u2248', '>' ]
+
+ self._sign = QP.RadioBox( self, choices = choices )
+
+ self._num = QP.MakeQSpinBox( self, min=-99999999, max=99999999 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( namespace, sign, num ) = predicate.GetValue()
+
+ self._namespace.setText( namespace )
+ self._sign.SetStringSelection( sign )
+ self._num.setValue( num )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:tag as number'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._namespace, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._num, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ namespace = 'page'
+ sign = '>'
+ num = 0
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_TAG_AS_NUMBER, ( namespace, sign, num ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_TAG_AS_NUMBER, ( self._namespace.text(), self._sign.GetStringSelection(), self._num.value() ) ), )
+
+ return predicates
+
+
+class PanelPredicateSystemWidth( PanelPredicateSystemSingle ):
+
+ def __init__( self, parent, predicate ):
+
+ PanelPredicateSystemSingle.__init__( self, parent )
+
+ self._sign = QP.RadioBox( self, choices=['<','\u2248','=','>'] )
+
+ self._width = QP.MakeQSpinBox( self, max=200000, width = 60 )
+
+ #
+
+ predicate = self._GetPredicateToInitialisePanelWith( predicate )
+
+ ( sign, width ) = predicate.GetValue()
+
+ self._sign.SetStringSelection( sign )
+
+ self._width.setValue( width )
+
+ #
+
+ hbox = QP.HBoxLayout()
+
+ QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:width'), CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
+ QP.AddToLayout( hbox, self._width, CC.FLAGS_CENTER_PERPENDICULAR )
+
+ hbox.addStretch( 1 )
+
+ self.setLayout( hbox )
+
+
+ def GetDefaultPredicate( self ):
+
+ sign = '='
+ width = 1920
+
+ return ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH, ( sign, width ) )
+
+
+ def GetPredicates( self ):
+
+ predicates = ( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_SYSTEM_WIDTH, ( self._sign.GetStringSelection(), self._width.value() ) ), )
+
+ return predicates
+
+
diff --git a/hydrus/client/gui/search/ClientGUISearch.py b/hydrus/client/gui/search/ClientGUISearch.py
new file mode 100644
index 00000000..c1b293f2
--- /dev/null
+++ b/hydrus/client/gui/search/ClientGUISearch.py
@@ -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()
+
+
+
+
diff --git a/hydrus/client/gui/ClientGUISearchPanels.py b/hydrus/client/gui/search/ClientGUISearchPanels.py
similarity index 97%
rename from hydrus/client/gui/ClientGUISearchPanels.py
rename to hydrus/client/gui/search/ClientGUISearchPanels.py
index 5dd6ee2a..c8157c6b 100644
--- a/hydrus/client/gui/ClientGUISearchPanels.py
+++ b/hydrus/client/gui/search/ClientGUISearchPanels.py
@@ -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 )
diff --git a/hydrus/client/gui/search/__init__.py b/hydrus/client/gui/search/__init__.py
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/hydrus/client/gui/search/__init__.py
@@ -0,0 +1 @@
+
diff --git a/hydrus/core/HydrusConstants.py b/hydrus/core/HydrusConstants.py
index 2ea8cfc6..d8fea402 100644
--- a/hydrus/core/HydrusConstants.py
+++ b/hydrus/core/HydrusConstants.py
@@ -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 )
diff --git a/hydrus/core/HydrusDB.py b/hydrus/core/HydrusDB.py
index 15cf0f7b..a8fbfe70 100644
--- a/hydrus/core/HydrusDB.py
+++ b/hydrus/core/HydrusDB.py
@@ -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()
diff --git a/hydrus/core/HydrusData.py b/hydrus/core/HydrusData.py
index da0e5c46..f63db762 100644
--- a/hydrus/core/HydrusData.py
+++ b/hydrus/core/HydrusData.py
@@ -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:
diff --git a/hydrus/core/HydrusNATPunch.py b/hydrus/core/HydrusNATPunch.py
index 8183395e..c18ba5e2 100644
--- a/hydrus/core/HydrusNATPunch.py
+++ b/hydrus/core/HydrusNATPunch.py
@@ -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
diff --git a/hydrus/core/HydrusText.py b/hydrus/core/HydrusText.py
index 6ff84b99..cde2ef68 100644
--- a/hydrus/core/HydrusText.py
+++ b/hydrus/core/HydrusText.py
@@ -9,6 +9,7 @@ except:
CHARDET_OK = False
import json
+import os
import re
re_newlines = re.compile( '[\r\n]+' )
diff --git a/hydrus/test/TestClientAPI.py b/hydrus/test/TestClientAPI.py
index 5f5c9370..bd3e0ea5 100644
--- a/hydrus/test/TestClientAPI.py
+++ b/hydrus/test/TestClientAPI.py
@@ -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 )