hydrus/hydrus/client/gui/search/ClientGUIACDropdown.py

2811 lines
97 KiB
Python
Raw Normal View History

2016-09-21 19:54:04 +00:00
import collections
import itertools
2020-02-12 22:50:37 +00:00
import os
2020-03-11 21:52:11 +00:00
import typing
2020-04-22 21:00:35 +00:00
2019-11-14 03:56:30 +00:00
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
2020-04-22 21:00:35 +00:00
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 HydrusTags
from hydrus.core import HydrusText
2020-07-29 20:52:44 +00:00
2021-01-27 22:14:03 +00:00
from hydrus.client import ClientApplicationCommand as CAC
2020-04-22 21:00:35 +00:00
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientSearch
from hydrus.client import ClientThreading
from hydrus.client.gui import ClientGUICore as CGC
from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import ClientGUIMenus
2020-11-25 22:22:47 +00:00
from hydrus.client.gui import ClientGUIResultsSortCollect
2020-04-22 21:00:35 +00:00
from hydrus.client.gui import ClientGUIScrolledPanels
from hydrus.client.gui import ClientGUIShortcuts
2020-04-29 21:44:12 +00:00
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
2020-04-22 21:00:35 +00:00
from hydrus.client.gui import QtPorting as QP
2020-07-15 20:52:09 +00:00
from hydrus.client.gui.lists import ClientGUIListBoxes
2021-02-11 01:59:52 +00:00
from hydrus.client.gui.lists import ClientGUIListBoxesData
2020-11-25 22:22:47 +00:00
from hydrus.client.gui.search import ClientGUISearch
2021-03-17 21:59:28 +00:00
from hydrus.client.gui.widgets import ClientGUICommon
from hydrus.client.metadata import ClientTags
2020-07-29 20:52:44 +00:00
2020-04-22 21:00:35 +00:00
from hydrus.external import LogicExpressionQueryParser
2018-01-31 22:58:15 +00:00
2019-07-17 22:10:19 +00:00
def AppendLoadingPredicate( predicates ):
2020-03-11 21:52:11 +00:00
predicates.append( ClientSearch.Predicate( predicate_type = ClientSearch.PREDICATE_TYPE_LABEL, value = 'loading results\u2026' ) )
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
def InsertOtherPredicatesForRead( predicates: list, parsed_autocomplete_text: ClientSearch.ParsedAutocompleteText, include_unusual_predicate_types: bool, under_construction_or_predicate: typing.Optional[ ClientSearch.Predicate ] ):
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
if include_unusual_predicate_types:
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
non_tag_predicates = list( parsed_autocomplete_text.GetNonTagFileSearchPredicates() )
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
non_tag_predicates.reverse()
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
for predicate in non_tag_predicates:
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
PutAtTopOfMatches( predicates, predicate )
2019-07-17 22:10:19 +00:00
2019-09-05 00:05:32 +00:00
if under_construction_or_predicate is not None:
2020-04-22 21:00:35 +00:00
PutAtTopOfMatches( predicates, under_construction_or_predicate )
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
def InsertTagPredicates( predicates: list, tag_service_key: bytes, parsed_autocomplete_text: ClientSearch.ParsedAutocompleteText, insert_if_does_not_exist: bool = True ):
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
if parsed_autocomplete_text.IsTagSearch():
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
tag_predicate = parsed_autocomplete_text.GetImmediateFileSearchPredicate()
2019-07-17 22:10:19 +00:00
2020-09-09 20:59:19 +00:00
actual_tag = tag_predicate.GetValue()
2019-07-17 22:10:19 +00:00
2020-09-09 20:59:19 +00:00
ideal_predicate = None
other_matching_predicates = []
for predicate in predicates:
# this works due to __hash__
if predicate == tag_predicate:
ideal_predicate = predicate.GetIdealPredicate()
continue
matchable_search_texts = predicate.GetMatchableSearchTexts()
if len( matchable_search_texts ) <= 1:
continue
if actual_tag in matchable_search_texts:
other_matching_predicates.append( predicate )
for predicate in other_matching_predicates:
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
PutAtTopOfMatches( predicates, predicate, insert_if_does_not_exist = insert_if_does_not_exist )
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
PutAtTopOfMatches( predicates, tag_predicate, insert_if_does_not_exist = insert_if_does_not_exist )
2020-09-09 20:59:19 +00:00
if ideal_predicate is not None:
PutAtTopOfMatches( predicates, ideal_predicate, insert_if_does_not_exist = insert_if_does_not_exist )
2019-07-17 22:10:19 +00:00
2020-05-20 21:36:02 +00:00
def ReadFetch(
win: QW.QWidget,
job_key: ClientThreading.JobKey,
results_callable,
parsed_autocomplete_text: ClientSearch.ParsedAutocompleteText,
qt_media_callable,
file_search_context: ClientSearch.FileSearchContext,
synchronised,
include_unusual_predicate_types,
results_cache: ClientSearch.PredicateResultsCache,
under_construction_or_predicate,
force_system_everything
):
2019-01-30 22:14:54 +00:00
file_service_key = file_search_context.GetFileServiceKey()
2020-03-11 21:52:11 +00:00
tag_search_context = file_search_context.GetTagSearchContext()
tag_service_key = tag_search_context.service_key
2020-05-20 21:36:02 +00:00
if not parsed_autocomplete_text.IsAcceptableForTagSearches():
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
if parsed_autocomplete_text.IsEmpty():
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
cache_valid = isinstance( results_cache, ClientSearch.PredicateResultsCacheSystem )
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
we_need_results = not cache_valid
2019-08-15 00:40:48 +00:00
2019-01-30 22:14:54 +00:00
db_not_going_to_hang_if_we_hit_it = not HG.client_controller.DBCurrentlyDoingJob()
2020-04-22 21:00:35 +00:00
if we_need_results or db_not_going_to_hang_if_we_hit_it:
2019-01-30 22:14:54 +00:00
if file_service_key == CC.COMBINED_FILE_SERVICE_KEY:
search_service_key = tag_service_key
else:
search_service_key = file_service_key
2020-04-22 21:00:35 +00:00
predicates = HG.client_controller.Read( 'file_system_predicates', search_service_key, force_system_everything = force_system_everything )
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
results_cache = ClientSearch.PredicateResultsCacheSystem( predicates )
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
matches = predicates
2019-08-15 00:40:48 +00:00
else:
2020-04-22 21:00:35 +00:00
matches = results_cache.GetPredicates()
2019-08-15 00:40:48 +00:00
2019-01-30 22:14:54 +00:00
else:
2020-04-22 21:00:35 +00:00
# if the user inputs '-' or 'creator:' or similar, let's go to an empty list
2019-01-30 22:14:54 +00:00
matches = []
else:
2020-04-22 21:00:35 +00:00
fetch_from_db = True
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
if synchronised and qt_media_callable is not None:
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
try:
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
media = HG.client_controller.CallBlockingToQt( win, qt_media_callable )
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
except HydrusExceptions.QtDeadWindowException:
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
return
2019-07-24 21:39:02 +00:00
2020-04-22 21:00:35 +00:00
if job_key.IsCancelled():
return
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
media_available_and_good = media is not None and len( media ) > 0
if media_available_and_good:
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
fetch_from_db = False
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
strict_search_text = parsed_autocomplete_text.GetSearchText( False )
autocomplete_search_text = parsed_autocomplete_text.GetSearchText( True )
# if user searches 'blah', then we include 'blah (23)' for 'series:blah (10)', 'blah (13)'
# if they search for 'series:blah', then we don't!
add_namespaceless = ':' not in strict_search_text
if fetch_from_db:
is_explicit_wildcard = parsed_autocomplete_text.IsExplicitWildcard()
2020-12-23 23:07:58 +00:00
small_exact_match_search = ShouldDoExactSearch( parsed_autocomplete_text )
2020-04-22 21:00:35 +00:00
matches = []
if small_exact_match_search:
2019-01-30 22:14:54 +00:00
2020-12-16 22:29:51 +00:00
if not results_cache.CanServeTagResults( parsed_autocomplete_text, True ):
2019-01-30 22:14:54 +00:00
2020-10-21 22:22:10 +00:00
predicates = HG.client_controller.Read( 'autocomplete_predicates', ClientTags.TAG_DISPLAY_ACTUAL, tag_search_context, file_service_key, search_text = strict_search_text, exact_match = True, inclusive = parsed_autocomplete_text.inclusive, add_namespaceless = add_namespaceless, job_key = job_key )
2019-01-30 22:14:54 +00:00
2020-05-20 21:36:02 +00:00
results_cache = ClientSearch.PredicateResultsCacheTag( predicates, strict_search_text, True )
2019-01-30 22:14:54 +00:00
2020-05-20 21:36:02 +00:00
matches = results_cache.FilterPredicates( tag_service_key, strict_search_text )
2019-01-30 22:14:54 +00:00
else:
2020-04-22 21:00:35 +00:00
if is_explicit_wildcard:
cache_valid = False
else:
2020-12-16 22:29:51 +00:00
cache_valid = results_cache.CanServeTagResults( parsed_autocomplete_text, False )
2020-04-22 21:00:35 +00:00
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
if cache_valid:
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
matches = results_cache.FilterPredicates( tag_service_key, autocomplete_search_text )
else:
2020-05-20 21:36:02 +00:00
search_namespaces_into_full_tags = parsed_autocomplete_text.GetTagAutocompleteOptions().SearchNamespacesIntoFullTags()
2020-10-21 22:22:10 +00:00
predicates = HG.client_controller.Read( 'autocomplete_predicates', ClientTags.TAG_DISPLAY_ACTUAL, tag_search_context, file_service_key, search_text = autocomplete_search_text, inclusive = parsed_autocomplete_text.inclusive, add_namespaceless = add_namespaceless, job_key = job_key, search_namespaces_into_full_tags = search_namespaces_into_full_tags )
2020-04-22 21:00:35 +00:00
2020-09-09 20:59:19 +00:00
if job_key.IsCancelled():
return
2020-04-22 21:00:35 +00:00
if is_explicit_wildcard:
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
matches = ClientSearch.FilterPredicatesBySearchText( tag_service_key, autocomplete_search_text, predicates )
2019-01-30 22:14:54 +00:00
else:
2020-04-22 21:00:35 +00:00
results_cache = ClientSearch.PredicateResultsCacheTag( predicates, strict_search_text, False )
matches = results_cache.FilterPredicates( tag_service_key, autocomplete_search_text )
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
if job_key.IsCancelled():
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
return
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
else:
2020-09-09 20:59:19 +00:00
if not isinstance( results_cache, ClientSearch.PredicateResultsCacheMedia ):
# it is possible that media will change between calls to this, so don't cache it
2020-04-22 21:00:35 +00:00
2020-09-09 20:59:19 +00:00
tags_managers = []
for m in media:
2019-01-30 22:14:54 +00:00
2020-09-09 20:59:19 +00:00
if m.IsCollection():
tags_managers.extend( m.GetSingletonsTagsManagers() )
else:
tags_managers.append( m.GetTagsManager() )
2019-01-30 22:14:54 +00:00
2020-09-09 20:59:19 +00:00
if job_key.IsCancelled():
2019-01-30 22:14:54 +00:00
2020-09-09 20:59:19 +00:00
return
2019-01-30 22:14:54 +00:00
2020-09-09 20:59:19 +00:00
current_tags_to_count = collections.Counter()
pending_tags_to_count = collections.Counter()
2019-07-24 21:39:02 +00:00
2020-09-09 20:59:19 +00:00
include_current_tags = tag_search_context.include_current_tags
include_pending_tags = tag_search_context.include_pending_tags
2019-07-24 21:39:02 +00:00
2020-09-09 20:59:19 +00:00
for group_of_tags_managers in HydrusData.SplitListIntoChunks( tags_managers, 1000 ):
2019-01-30 22:14:54 +00:00
2020-09-09 20:59:19 +00:00
if include_current_tags:
2020-10-21 22:22:10 +00:00
current_tags_to_count.update( itertools.chain.from_iterable( tags_manager.GetCurrent( tag_service_key, ClientTags.TAG_DISPLAY_ACTUAL ) for tags_manager in group_of_tags_managers ) )
2020-09-09 20:59:19 +00:00
2019-01-30 22:14:54 +00:00
2020-09-09 20:59:19 +00:00
if include_pending_tags:
2020-10-21 22:22:10 +00:00
pending_tags_to_count.update( itertools.chain.from_iterable( [ tags_manager.GetPending( tag_service_key, ClientTags.TAG_DISPLAY_ACTUAL ) for tags_manager in group_of_tags_managers ] ) )
2020-09-09 20:59:19 +00:00
2020-04-22 21:00:35 +00:00
2020-09-09 20:59:19 +00:00
if job_key.IsCancelled():
return
2020-04-22 21:00:35 +00:00
2019-01-30 22:14:54 +00:00
2020-09-09 20:59:19 +00:00
tags_to_do = set()
tags_to_do.update( current_tags_to_count.keys() )
tags_to_do.update( pending_tags_to_count.keys() )
tags_to_count = { tag : ( current_tags_to_count[ tag ], pending_tags_to_count[ tag ] ) for tag in tags_to_do }
2019-07-24 21:39:02 +00:00
if job_key.IsCancelled():
return
2020-09-09 20:59:19 +00:00
predicates = HG.client_controller.Read( 'media_predicates', tag_search_context, tags_to_count, parsed_autocomplete_text.inclusive, job_key = job_key )
results_cache = ClientSearch.PredicateResultsCacheMedia( predicates )
2020-04-22 21:00:35 +00:00
if job_key.IsCancelled():
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
return
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
2020-09-09 20:59:19 +00:00
predicates = results_cache.FilterPredicates( tag_service_key, autocomplete_search_text )
2020-08-27 01:00:42 +00:00
2020-04-22 21:00:35 +00:00
if job_key.IsCancelled():
return
2019-07-24 21:39:02 +00:00
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
predicates = ClientData.MergePredicates( predicates, add_namespaceless = add_namespaceless )
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
matches = predicates
matches = ClientSearch.SortPredicates( matches )
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
if not parsed_autocomplete_text.inclusive:
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
for match in matches:
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
match.SetInclusive( False )
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
InsertTagPredicates( matches, tag_service_key, parsed_autocomplete_text, insert_if_does_not_exist = False )
InsertOtherPredicatesForRead( matches, parsed_autocomplete_text, include_unusual_predicate_types, under_construction_or_predicate )
2019-04-10 22:50:53 +00:00
2019-01-30 22:14:54 +00:00
if job_key.IsCancelled():
return
2020-04-22 21:00:35 +00:00
HG.client_controller.CallLaterQtSafe( win, 0.0, results_callable, job_key, parsed_autocomplete_text, results_cache, matches )
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
def PutAtTopOfMatches( matches: list, predicate: ClientSearch.Predicate, insert_if_does_not_exist: bool = True ):
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
# we have to be careful here to preserve autocomplete counts!
# if it already exists, we move it up, do not replace with the test pred param
if predicate in matches:
2019-01-30 22:14:54 +00:00
index = matches.index( predicate )
2020-04-22 21:00:35 +00:00
predicate_to_insert = matches[ index ]
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
del matches[ index ]
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
matches.insert( 0, predicate_to_insert )
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
else:
if insert_if_does_not_exist:
matches.insert( 0, predicate )
2019-01-30 22:14:54 +00:00
2020-12-23 23:07:58 +00:00
def ShouldDoExactSearch( parsed_autocomplete_text: ClientSearch.ParsedAutocompleteText ):
2019-07-17 22:10:19 +00:00
2020-12-23 23:07:58 +00:00
if parsed_autocomplete_text.IsExplicitWildcard():
2019-07-17 22:10:19 +00:00
return False
2020-12-23 23:07:58 +00:00
strict_search_text = parsed_autocomplete_text.GetSearchText( False )
2019-07-17 22:10:19 +00:00
2020-12-23 23:07:58 +00:00
exact_match_character_threshold = parsed_autocomplete_text.GetTagAutocompleteOptions().GetExactMatchCharacterThreshold()
if exact_match_character_threshold is None:
2019-07-17 22:10:19 +00:00
return False
2020-12-23 23:07:58 +00:00
if ':' in strict_search_text:
2019-07-17 22:10:19 +00:00
2020-12-23 23:07:58 +00:00
( namespace, test_text ) = HydrusTags.SplitTag( strict_search_text )
2019-07-17 22:10:19 +00:00
else:
2020-12-23 23:07:58 +00:00
test_text = strict_search_text
if len( test_text ) == 0:
return False
2019-07-17 22:10:19 +00:00
2020-12-23 23:07:58 +00:00
return len( test_text ) <= exact_match_character_threshold
2019-07-17 22:10:19 +00:00
2021-02-24 22:35:18 +00:00
def WriteFetch( win, job_key, results_callable, parsed_autocomplete_text: ClientSearch.ParsedAutocompleteText, tag_search_context: ClientSearch.TagSearchContext, file_service_key: bytes, results_cache: ClientSearch.PredicateResultsCache ):
2019-01-30 22:14:54 +00:00
2020-08-27 01:00:42 +00:00
display_tag_service_key = tag_search_context.display_service_key
2020-03-11 21:52:11 +00:00
2020-05-20 21:36:02 +00:00
if not parsed_autocomplete_text.IsAcceptableForTagSearches():
2019-01-30 22:14:54 +00:00
matches = []
else:
2020-04-22 21:00:35 +00:00
is_explicit_wildcard = parsed_autocomplete_text.IsExplicitWildcard()
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
strict_search_text = parsed_autocomplete_text.GetSearchText( False )
autocomplete_search_text = parsed_autocomplete_text.GetSearchText( True )
2020-12-23 23:07:58 +00:00
small_exact_match_search = ShouldDoExactSearch( parsed_autocomplete_text )
2020-04-22 21:00:35 +00:00
if small_exact_match_search:
2019-01-30 22:14:54 +00:00
2020-12-16 22:29:51 +00:00
if not results_cache.CanServeTagResults( parsed_autocomplete_text, True ):
2020-04-22 21:00:35 +00:00
2020-08-19 22:38:20 +00:00
predicates = HG.client_controller.Read( 'autocomplete_predicates', ClientTags.TAG_DISPLAY_STORAGE, tag_search_context, file_service_key, search_text = strict_search_text, exact_match = True, add_namespaceless = False, job_key = job_key )
2020-04-22 21:00:35 +00:00
2020-05-20 21:36:02 +00:00
results_cache = ClientSearch.PredicateResultsCacheTag( predicates, strict_search_text, True )
2020-04-22 21:00:35 +00:00
2019-01-30 22:14:54 +00:00
2020-06-11 12:01:08 +00:00
matches = results_cache.FilterPredicates( display_tag_service_key, strict_search_text )
2020-05-20 21:36:02 +00:00
2019-01-30 22:14:54 +00:00
else:
2020-04-22 21:00:35 +00:00
if is_explicit_wildcard:
cache_valid = False
else:
2020-12-16 22:29:51 +00:00
cache_valid = results_cache.CanServeTagResults( parsed_autocomplete_text, False )
2020-04-22 21:00:35 +00:00
2019-08-15 00:40:48 +00:00
2020-04-22 21:00:35 +00:00
if cache_valid:
2019-01-30 22:14:54 +00:00
2020-06-11 12:01:08 +00:00
matches = results_cache.FilterPredicates( display_tag_service_key, autocomplete_search_text )
2019-01-30 22:14:54 +00:00
2019-08-15 00:40:48 +00:00
else:
2020-05-20 21:36:02 +00:00
search_namespaces_into_full_tags = parsed_autocomplete_text.GetTagAutocompleteOptions().SearchNamespacesIntoFullTags()
2020-08-19 22:38:20 +00:00
predicates = HG.client_controller.Read( 'autocomplete_predicates', ClientTags.TAG_DISPLAY_STORAGE, tag_search_context, file_service_key, search_text = autocomplete_search_text, add_namespaceless = False, job_key = job_key, search_namespaces_into_full_tags = search_namespaces_into_full_tags )
2019-08-15 00:40:48 +00:00
2020-04-22 21:00:35 +00:00
if is_explicit_wildcard:
2020-06-11 12:01:08 +00:00
matches = ClientSearch.FilterPredicatesBySearchText( display_tag_service_key, autocomplete_search_text, predicates )
2019-08-15 00:40:48 +00:00
2020-04-22 21:00:35 +00:00
else:
2019-08-15 00:40:48 +00:00
2020-04-22 21:00:35 +00:00
results_cache = ClientSearch.PredicateResultsCacheTag( predicates, strict_search_text, False )
2020-06-11 12:01:08 +00:00
matches = results_cache.FilterPredicates( display_tag_service_key, autocomplete_search_text )
2019-08-15 00:40:48 +00:00
2019-01-30 22:14:54 +00:00
2020-12-02 22:04:38 +00:00
if not is_explicit_wildcard:
# this lets us get sibling data for tags that do not exist with count in the domain
# we always do this, because results cache will not have current text input data
input_text_predicates = HG.client_controller.Read( 'autocomplete_predicates', ClientTags.TAG_DISPLAY_STORAGE, tag_search_context, file_service_key, search_text = strict_search_text, exact_match = True, add_namespaceless = False, zero_count_ok = True, job_key = job_key )
for input_text_predicate in input_text_predicates:
if ( input_text_predicate.HasIdealSibling() or input_text_predicate.HasParentPredicates() ) and input_text_predicate not in matches:
matches.append( input_text_predicate )
2020-04-22 21:00:35 +00:00
matches = ClientSearch.SortPredicates( matches )
2020-06-11 12:01:08 +00:00
InsertTagPredicates( matches, display_tag_service_key, parsed_autocomplete_text )
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
HG.client_controller.CallLaterQtSafe( win, 0.0, results_callable, job_key, parsed_autocomplete_text, results_cache, matches )
2019-01-30 22:14:54 +00:00
2021-02-24 22:35:18 +00:00
class ListBoxTagsPredicatesAC( ClientGUIListBoxes.ListBoxTagsPredicates ):
2020-04-29 21:44:12 +00:00
def __init__( self, parent, callable, service_key, float_mode, **kwargs ):
ClientGUIListBoxes.ListBoxTagsPredicates.__init__( self, parent, **kwargs )
self._callable = callable
self._service_key = service_key
self._float_mode = float_mode
self._predicates = {}
2020-11-18 22:15:21 +00:00
def _Activate( self, shift_down ) -> bool:
2020-04-29 21:44:12 +00:00
2021-02-11 01:59:52 +00:00
predicates = self._GetPredicatesFromTerms( self._selected_terms )
2020-04-29 21:44:12 +00:00
if self._float_mode:
widget = self.window().parentWidget()
else:
widget = self
predicates = ClientGUISearch.FleshOutPredicates( widget, predicates )
if len( predicates ) > 0:
self._callable( predicates, shift_down )
2020-11-18 22:15:21 +00:00
return True
return False
2020-04-29 21:44:12 +00:00
2021-02-24 22:35:18 +00:00
def _GenerateTermFromPredicate( self, predicate: ClientSearch.Predicate ):
term = ClientGUIListBoxes.ListBoxTagsPredicates._GenerateTermFromPredicate( self, predicate )
if predicate.GetType() == ClientSearch.PREDICATE_TYPE_OR_CONTAINER:
term.SetORUnderConstruction( True )
return term
2020-04-29 21:44:12 +00:00
def SetPredicates( self, predicates ):
# need to do a clever compare, since normal predicate compare doesn't take count into account
they_are_the_same = True
if len( predicates ) == len( self._predicates ):
for index in range( len( predicates ) ):
p_1 = predicates[ index ]
p_2 = self._predicates[ index ]
if p_1 != p_2 or p_1.GetCount() != p_2.GetCount():
they_are_the_same = False
break
else:
they_are_the_same = False
if not they_are_the_same:
# important to make own copy, as same object originals can be altered (e.g. set non-inclusive) in cache, and we need to notice that change just above
self._predicates = [ predicate.GetCopy() for predicate in predicates ]
self._Clear()
2021-02-11 01:59:52 +00:00
terms = [ self._GenerateTermFromPredicate( predicate ) for predicate in predicates ]
self._AppendTerms( terms )
2020-04-29 21:44:12 +00:00
self._DataHasChanged()
if len( predicates ) > 0:
2021-02-11 01:59:52 +00:00
logical_index = 0
2020-04-29 21:44:12 +00:00
if len( predicates ) > 1:
skip_ors = True
2020-05-13 19:03:16 +00:00
some_preds_have_count = True in ( predicate.GetCount() > 0 for predicate in predicates )
skip_countless = HG.client_controller.new_options.GetBoolean( 'ac_select_first_with_count' ) and some_preds_have_count
2020-04-29 21:44:12 +00:00
for ( index, predicate ) in enumerate( predicates ):
# now only apply this to simple tags, not wildcards and system tags
if skip_ors and predicate.GetType() == ClientSearch.PREDICATE_TYPE_OR_CONTAINER:
continue
if skip_countless and predicate.GetType() in ( ClientSearch.PREDICATE_TYPE_PARENT, ClientSearch.PREDICATE_TYPE_TAG ) and predicate.GetCount() == 0:
continue
2021-02-11 01:59:52 +00:00
logical_index = index
2020-04-29 21:44:12 +00:00
break
2021-02-11 01:59:52 +00:00
self._Hit( False, False, logical_index )
2020-04-29 21:44:12 +00:00
2021-02-24 22:35:18 +00:00
def SetTagServiceKey( self, service_key: bytes ):
2020-04-29 21:44:12 +00:00
self._service_key = service_key
2021-02-24 22:35:18 +00:00
class ListBoxTagsStringsAC( ClientGUIListBoxes.ListBoxTagsStrings ):
2020-04-29 21:44:12 +00:00
2021-02-24 22:35:18 +00:00
def __init__( self, parent, callable, service_key, float_mode, **kwargs ):
2020-04-29 21:44:12 +00:00
2021-02-24 22:35:18 +00:00
ClientGUIListBoxes.ListBoxTagsStrings.__init__( self, parent, service_key = service_key, sort_tags = False, **kwargs )
2020-04-29 21:44:12 +00:00
2021-02-24 22:35:18 +00:00
self._callable = callable
self._float_mode = float_mode
def _Activate( self, shift_down ) -> bool:
predicates = self._GetPredicatesFromTerms( self._selected_terms )
if self._float_mode:
2021-02-11 01:59:52 +00:00
2021-02-24 22:35:18 +00:00
widget = self.window().parentWidget()
else:
widget = self
2021-02-11 01:59:52 +00:00
2021-02-24 22:35:18 +00:00
predicates = ClientGUISearch.FleshOutPredicates( widget, predicates )
2020-04-29 21:44:12 +00:00
2021-02-24 22:35:18 +00:00
if len( predicates ) > 0:
self._callable( predicates, shift_down )
return True
2020-04-29 21:44:12 +00:00
2021-02-24 22:35:18 +00:00
return False
2020-04-29 21:44:12 +00:00
2016-09-21 19:54:04 +00:00
# much of this is based on the excellent TexCtrlAutoComplete class by Edward Flick, Michele Petrazzo and Will Sadkin, just with plenty of simplification and integration into hydrus
2019-11-14 03:56:30 +00:00
class AutoCompleteDropdown( QW.QWidget ):
selectUp = QC.Signal()
selectDown = QC.Signal()
showNext = QC.Signal()
showPrevious = QC.Signal()
2016-09-21 19:54:04 +00:00
def __init__( self, parent ):
2019-11-14 03:56:30 +00:00
QW.QWidget.__init__( self, parent )
2016-09-21 19:54:04 +00:00
2021-01-27 22:14:03 +00:00
self._can_intercept_unusual_key_events = True
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
if self.window() == HG.client_controller.gui:
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
use_float_mode = HG.client_controller.new_options.GetBoolean( 'autocomplete_float_main_gui' )
2016-09-21 19:54:04 +00:00
else:
2019-11-14 03:56:30 +00:00
use_float_mode = HG.client_controller.new_options.GetBoolean( 'autocomplete_float_frames' )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
self._float_mode = use_float_mode
2020-03-11 21:52:11 +00:00
self._text_input_panel = QW.QWidget( self )
self._text_ctrl = QW.QLineEdit( self._text_input_panel )
2016-09-21 19:54:04 +00:00
self.setFocusProxy( self._text_ctrl )
2017-09-20 19:47:31 +00:00
self._UpdateBackgroundColour()
2016-09-21 19:54:04 +00:00
self._last_attempted_dropdown_width = 0
self._last_attempted_dropdown_position = ( None, None )
2019-11-14 03:56:30 +00:00
self._text_ctrl_widget_event_filter = QP.WidgetEventFilter( self._text_ctrl )
self._text_ctrl.textChanged.connect( self.EventText )
2019-11-28 01:11:46 +00:00
2019-11-14 03:56:30 +00:00
self._text_ctrl_widget_event_filter.EVT_KEY_DOWN( self.keyPressFilter )
2016-09-21 19:54:04 +00:00
2020-02-19 21:48:36 +00:00
self._text_ctrl.installEventFilter( self )
2016-09-21 19:54:04 +00:00
2020-03-18 21:35:57 +00:00
self._main_vbox = QP.VBoxLayout( margin = 0 )
self._SetupTopListBox()
2019-06-26 21:27:18 +00:00
2019-11-14 03:56:30 +00:00
self._text_input_hbox = QP.HBoxLayout()
2019-06-26 21:27:18 +00:00
2020-07-29 20:52:44 +00:00
QP.AddToLayout( self._text_input_hbox, self._text_ctrl, CC.FLAGS_CENTER_PERPENDICULAR_EXPAND_DEPTH )
2016-09-21 19:54:04 +00:00
2020-03-11 21:52:11 +00:00
self._text_input_panel.setLayout( self._text_input_hbox )
2020-03-18 21:35:57 +00:00
QP.AddToLayout( self._main_vbox, self._text_input_panel, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
2016-09-21 19:54:04 +00:00
if self._float_mode:
2019-11-14 03:56:30 +00:00
self._dropdown_window = QW.QFrame( self )
2016-09-21 19:54:04 +00:00
2019-12-05 05:29:32 +00:00
self._dropdown_window.setWindowFlags( QC.Qt.Tool | QC.Qt.FramelessWindowHint )
2019-11-14 03:56:30 +00:00
self._dropdown_window.setAttribute( QC.Qt.WA_ShowWithoutActivating )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
self._dropdown_window.setFrameStyle( QW.QFrame.Panel | QW.QFrame.Raised )
self._dropdown_window.setLineWidth( 2 )
2016-09-21 19:54:04 +00:00
2020-02-26 22:28:52 +00:00
self._dropdown_window.move( ClientGUIFunctions.ClientToScreen( self._text_ctrl, QC.QPoint( 0, 0 ) ) )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
self._dropdown_window_widget_event_filter = QP.WidgetEventFilter( self._dropdown_window )
self._dropdown_window_widget_event_filter.EVT_CLOSE( self.EventCloseDropdown )
self._dropdown_hidden = True
2016-09-21 19:54:04 +00:00
2020-04-01 21:51:42 +00:00
self._force_dropdown_hide = False
2016-09-21 19:54:04 +00:00
else:
2020-03-25 21:15:57 +00:00
self._dropdown_window = QW.QWidget( self )
2016-09-21 19:54:04 +00:00
2020-02-19 21:48:36 +00:00
self._dropdown_window.installEventFilter( self )
2019-11-14 03:56:30 +00:00
self._dropdown_notebook = QW.QTabWidget( self._dropdown_window )
2018-03-28 21:55:58 +00:00
#
self._search_results_list = self._InitSearchResultsList()
2019-11-14 03:56:30 +00:00
self._dropdown_notebook.setCurrentIndex( self._dropdown_notebook.addTab( self._search_results_list, 'results' ) )
2018-03-28 21:55:58 +00:00
#
2016-09-21 19:54:04 +00:00
2018-02-07 23:40:33 +00:00
if not self._float_mode:
2020-03-18 21:35:57 +00:00
QP.AddToLayout( self._main_vbox, self._dropdown_window, CC.FLAGS_EXPAND_BOTH_WAYS )
2018-02-07 23:40:33 +00:00
2016-09-21 19:54:04 +00:00
2020-03-18 21:35:57 +00:00
self.setLayout( self._main_vbox )
2016-09-21 19:54:04 +00:00
2020-05-20 21:36:02 +00:00
self._current_list_parsed_autocomplete_text = self._GetParsedAutocompleteText()
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
self._results_cache: ClientSearch.PredicateResultsCache = ClientSearch.PredicateResultsCacheInit()
2016-09-21 19:54:04 +00:00
2019-01-30 22:14:54 +00:00
self._current_fetch_job_key = None
2020-04-01 21:51:42 +00:00
self._schedule_results_refresh_job = None
2018-01-03 22:37:30 +00:00
2021-01-27 22:14:03 +00:00
self._my_shortcut_handler = ClientGUIShortcuts.ShortcutsHandler( self, [ 'tags_autocomplete' ], alternate_filter_target = self._text_ctrl )
2016-09-21 19:54:04 +00:00
if self._float_mode:
2019-11-14 03:56:30 +00:00
self._widget_event_filter = QP.WidgetEventFilter( self )
self._widget_event_filter.EVT_MOVE( self.EventMove )
self._widget_event_filter.EVT_SIZE( self.EventMove )
2016-09-21 19:54:04 +00:00
2020-03-18 21:35:57 +00:00
HG.client_controller.sub( self, '_DropdownHideShow', 'top_level_window_move_event' )
2016-09-21 19:54:04 +00:00
parent = self
2019-11-14 03:56:30 +00:00
self._scroll_event_filters = []
2016-09-21 19:54:04 +00:00
while True:
try:
2019-11-14 03:56:30 +00:00
parent = parent.parentWidget()
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
if isinstance( parent, QW.QScrollArea ):
scroll_event_filter = QP.WidgetEventFilter( parent )
self._scroll_event_filters.append( scroll_event_filter )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
scroll_event_filter.EVT_SCROLLWIN( self.EventMove )
2016-09-21 19:54:04 +00:00
except:
break
2017-09-20 19:47:31 +00:00
HG.client_controller.sub( self, '_UpdateBackgroundColour', 'notify_new_colourset' )
2018-06-27 19:27:05 +00:00
HG.client_controller.sub( self, 'DoDropdownHideShow', 'notify_page_change' )
2017-09-20 19:47:31 +00:00
2020-04-01 21:51:42 +00:00
self._ScheduleResultsRefresh( 0.0 )
2016-09-21 19:54:04 +00:00
HG.client_controller.CallLaterQtSafe( self, 0.05, self._DropdownHideShow )
2016-09-21 19:54:04 +00:00
2019-04-03 22:45:57 +00:00
def _BroadcastChoices( self, predicates, shift_down ):
2016-09-21 19:54:04 +00:00
raise NotImplementedError()
2020-04-01 21:51:42 +00:00
def _CancelSearchResultsFetchJob( self ):
2019-06-19 22:08:48 +00:00
if self._current_fetch_job_key is not None:
self._current_fetch_job_key.Cancel()
2019-08-07 22:59:53 +00:00
self._current_fetch_job_key = None
2019-06-19 22:08:48 +00:00
2020-04-01 21:51:42 +00:00
def _ClearInput( self ):
2018-02-14 21:47:18 +00:00
2020-04-01 21:51:42 +00:00
self._CancelSearchResultsFetchJob()
2018-02-14 21:47:18 +00:00
2020-04-01 21:51:42 +00:00
self._text_ctrl.blockSignals( True )
2019-07-17 22:10:19 +00:00
2020-12-23 23:07:58 +00:00
self._text_ctrl.clear()
2018-05-23 21:05:06 +00:00
2020-05-20 21:36:02 +00:00
self._SetResultsToList( [], self._GetParsedAutocompleteText() )
2020-04-22 21:00:35 +00:00
2020-04-01 21:51:42 +00:00
self._text_ctrl.blockSignals( False )
2019-04-10 22:50:53 +00:00
2020-04-01 21:51:42 +00:00
self._ScheduleResultsRefresh( 0.0 )
2018-05-23 21:05:06 +00:00
2020-05-20 21:36:02 +00:00
def _GetParsedAutocompleteText( self ) -> ClientSearch.ParsedAutocompleteText:
raise NotImplementedError()
2018-02-28 22:30:36 +00:00
def _DropdownHideShow( self ):
if not self._float_mode:
return
try:
if self._ShouldShow():
self._ShowDropdown()
else:
self._HideDropdown()
except:
raise
2019-04-10 22:50:53 +00:00
def _HandleEscape( self ):
2020-03-18 21:35:57 +00:00
if self._text_ctrl.text() != '':
self._ClearInput()
return True
elif self._float_mode:
2019-04-10 22:50:53 +00:00
2019-11-14 03:56:30 +00:00
self.parentWidget().setFocus( QC.Qt.OtherFocusReason )
2019-04-10 22:50:53 +00:00
return True
else:
return False
2016-09-21 19:54:04 +00:00
def _HideDropdown( self ):
if not self._dropdown_hidden:
2019-11-14 03:56:30 +00:00
self._dropdown_window.hide()
2016-09-21 19:54:04 +00:00
self._dropdown_hidden = True
2018-03-28 21:55:58 +00:00
def _InitSearchResultsList( self ):
raise NotImplementedError()
2020-04-01 21:51:42 +00:00
def _ScheduleResultsRefresh( self, delay ):
2018-02-14 21:47:18 +00:00
2020-04-01 21:51:42 +00:00
if self._schedule_results_refresh_job is not None:
2018-02-14 21:47:18 +00:00
2020-04-01 21:51:42 +00:00
self._schedule_results_refresh_job.Cancel()
2018-02-14 21:47:18 +00:00
2020-04-01 21:51:42 +00:00
self._schedule_results_refresh_job = HG.client_controller.CallLaterQtSafe( self, delay, self._UpdateSearchResults )
2018-02-14 21:47:18 +00:00
2020-03-18 21:35:57 +00:00
def _SetupTopListBox( self ):
pass
2018-02-14 21:47:18 +00:00
def _SetListDirty( self ):
2020-04-22 21:00:35 +00:00
self._results_cache = ClientSearch.PredicateResultsCacheInit()
2018-02-14 21:47:18 +00:00
2020-04-01 21:51:42 +00:00
self._ScheduleResultsRefresh( 0.0 )
2018-02-14 21:47:18 +00:00
2020-04-22 21:00:35 +00:00
def _SetResultsToList( self, results, parsed_autocomplete_text ):
2019-01-30 22:14:54 +00:00
raise NotImplementedError()
2016-09-21 19:54:04 +00:00
def _ShouldShow( self ):
2020-04-01 21:51:42 +00:00
if self._force_dropdown_hide:
return False
2020-02-19 21:48:36 +00:00
current_active_window = QW.QApplication.activeWindow()
i_am_active_and_focused = self.window() == current_active_window and self._text_ctrl.hasFocus()
2016-09-21 19:54:04 +00:00
2020-02-19 21:48:36 +00:00
dropdown_is_active = self._dropdown_window == current_active_window
2016-09-21 19:54:04 +00:00
2020-02-12 22:50:37 +00:00
focus_or_active_good = i_am_active_and_focused or dropdown_is_active
2016-09-21 19:54:04 +00:00
2020-02-12 22:50:37 +00:00
visible = self.isVisible()
return focus_or_active_good and visible
2016-09-21 19:54:04 +00:00
def _ShouldTakeResponsibilityForEnter( self ):
raise NotImplementedError()
def _ShowDropdown( self ):
2020-03-11 21:52:11 +00:00
text_panel_size = self._text_input_panel.size()
2020-02-26 22:28:52 +00:00
2020-03-11 21:52:11 +00:00
text_input_width = text_panel_size.width()
text_input_height = text_panel_size.height()
2016-09-21 19:54:04 +00:00
2020-03-11 21:52:11 +00:00
if self._text_input_panel.isVisible():
2016-09-21 19:54:04 +00:00
2020-03-11 21:52:11 +00:00
desired_dropdown_position = ClientGUIFunctions.ClientToScreen( self._text_input_panel, QC.QPoint( 0, text_input_height ) )
2016-09-21 19:54:04 +00:00
2017-05-31 21:50:53 +00:00
if self._last_attempted_dropdown_position != desired_dropdown_position:
2019-11-14 03:56:30 +00:00
self._dropdown_window.move( desired_dropdown_position )
2017-05-31 21:50:53 +00:00
self._last_attempted_dropdown_position = desired_dropdown_position
2016-09-21 19:54:04 +00:00
#
if self._dropdown_hidden:
2019-11-14 03:56:30 +00:00
self._dropdown_window.show()
2016-09-21 19:54:04 +00:00
2018-03-28 21:55:58 +00:00
self._dropdown_hidden = False
2016-09-21 19:54:04 +00:00
2020-03-11 21:52:11 +00:00
if text_input_width != self._last_attempted_dropdown_width:
2018-03-28 21:55:58 +00:00
2020-03-11 21:52:11 +00:00
self._dropdown_window.setFixedWidth( text_input_width )
2016-09-21 19:54:04 +00:00
2020-03-11 21:52:11 +00:00
self._last_attempted_dropdown_width = text_input_width
2016-09-21 19:54:04 +00:00
2020-04-01 21:51:42 +00:00
def _StartSearchResultsFetchJob( self, job_key ):
2019-01-30 22:14:54 +00:00
raise NotImplementedError()
2019-04-03 22:45:57 +00:00
def _TakeResponsibilityForEnter( self, shift_down ):
2016-09-21 19:54:04 +00:00
raise NotImplementedError()
2017-09-20 19:47:31 +00:00
def _UpdateBackgroundColour( self ):
2017-12-06 22:06:56 +00:00
colour = HG.client_controller.new_options.GetColour( CC.COLOUR_AUTOCOMPLETE_BACKGROUND )
2017-09-20 19:47:31 +00:00
2021-01-27 22:14:03 +00:00
if not self._can_intercept_unusual_key_events:
2017-09-20 19:47:31 +00:00
2020-05-20 21:36:02 +00:00
colour = ClientGUIFunctions.GetLighterDarkerColour( colour )
2017-09-20 19:47:31 +00:00
2019-11-14 03:56:30 +00:00
QP.SetBackgroundColour( self._text_ctrl, colour )
2017-09-20 19:47:31 +00:00
2019-11-14 03:56:30 +00:00
self._text_ctrl.update()
2017-09-20 19:47:31 +00:00
2020-04-01 21:51:42 +00:00
def _UpdateSearchResults( self ):
2016-09-21 19:54:04 +00:00
2020-04-01 21:51:42 +00:00
self._schedule_results_refresh_job = None
2019-01-30 22:14:54 +00:00
2020-04-01 21:51:42 +00:00
self._CancelSearchResultsFetchJob()
2019-01-30 22:14:54 +00:00
self._current_fetch_job_key = ClientThreading.JobKey( cancellable = True )
2020-04-01 21:51:42 +00:00
self._StartSearchResultsFetchJob( self._current_fetch_job_key )
2016-09-21 19:54:04 +00:00
2019-04-10 22:50:53 +00:00
def BroadcastChoices( self, predicates, shift_down = False ):
2019-04-03 22:45:57 +00:00
self._BroadcastChoices( predicates, shift_down )
2016-09-21 19:54:04 +00:00
2019-06-19 22:08:48 +00:00
def CancelCurrentResultsFetchJob( self ):
2020-04-01 21:51:42 +00:00
self._CancelSearchResultsFetchJob()
2019-06-19 22:08:48 +00:00
2018-06-27 19:27:05 +00:00
def DoDropdownHideShow( self ):
self._DropdownHideShow()
2019-11-14 03:56:30 +00:00
def keyPressFilter( self, event ):
2016-09-21 19:54:04 +00:00
2017-05-10 21:33:58 +00:00
HG.client_controller.ResetIdleTimer()
2016-09-21 19:54:04 +00:00
2018-05-16 20:09:50 +00:00
( modifier, key ) = ClientGUIShortcuts.ConvertKeyEventToSimpleTuple( event )
2017-04-05 21:16:40 +00:00
2021-01-27 22:14:03 +00:00
if self._can_intercept_unusual_key_events:
2016-09-21 19:54:04 +00:00
2018-03-28 21:55:58 +00:00
send_input_to_current_list = False
2019-11-14 03:56:30 +00:00
current_results_list = self._dropdown_notebook.currentWidget()
2018-03-28 21:55:58 +00:00
2019-11-14 03:56:30 +00:00
if key in ( ord( 'A' ), ord( 'a' ) ) and modifier == QC.Qt.ControlModifier:
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
return True # was: event.ignore()
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
elif key in ( QC.Qt.Key_Return, QC.Qt.Key_Enter ) and self._ShouldTakeResponsibilityForEnter():
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
shift_down = modifier == QC.Qt.ShiftModifier
2019-04-03 22:45:57 +00:00
self._TakeResponsibilityForEnter( shift_down )
2016-09-21 19:54:04 +00:00
2020-03-18 21:35:57 +00:00
elif key == QC.Qt.Key_Escape:
escape_caught = self._HandleEscape()
if not escape_caught:
send_input_to_current_list = True
2016-09-21 19:54:04 +00:00
else:
2018-03-28 21:55:58 +00:00
send_input_to_current_list = True
if send_input_to_current_list:
2019-11-14 03:56:30 +00:00
current_results_list.keyPressEvent( event ) # ultimately, this typically ignores the event, letting the text ctrl take it
return not event.isAccepted()
2016-09-21 19:54:04 +00:00
else:
2019-11-14 03:56:30 +00:00
return True # was: event.ignore()
2016-09-21 19:54:04 +00:00
2017-04-19 20:58:30 +00:00
def EventCloseDropdown( self, event ):
2019-11-14 03:56:30 +00:00
HG.client_controller.gui.close()
2017-04-19 20:58:30 +00:00
2019-12-05 05:29:32 +00:00
return True
2017-04-19 20:58:30 +00:00
2020-02-19 21:48:36 +00:00
def eventFilter( self, watched, event ):
2018-03-28 21:55:58 +00:00
2020-02-19 21:48:36 +00:00
if watched == self._text_ctrl:
2016-09-21 19:54:04 +00:00
2020-02-19 21:48:36 +00:00
if event.type() == QC.QEvent.Wheel:
2018-01-31 22:58:15 +00:00
2020-02-19 21:48:36 +00:00
current_results_list = self._dropdown_notebook.currentWidget()
2016-09-21 19:54:04 +00:00
2020-02-19 21:48:36 +00:00
if self._text_ctrl.text() == '' and len( current_results_list ) == 0:
if event.angleDelta().y() > 0:
self.selectUp.emit()
else:
self.selectDown.emit()
2017-04-19 20:58:30 +00:00
2020-02-19 21:48:36 +00:00
event.accept()
return True
2017-04-19 20:58:30 +00:00
else:
2020-02-19 21:48:36 +00:00
if event.modifiers() & QC.Qt.ControlModifier:
if event.angleDelta().y() > 0:
current_results_list.MoveSelectionUp()
else:
current_results_list.MoveSelectionDown()
event.accept()
return True
2017-04-19 20:58:30 +00:00
2016-09-21 19:54:04 +00:00
2020-02-19 21:48:36 +00:00
elif self._float_mode:
2016-09-21 19:54:04 +00:00
2020-02-19 21:48:36 +00:00
if event.type() in ( QC.QEvent.FocusOut, QC.QEvent.FocusIn ):
self._DropdownHideShow()
return False
2016-09-21 19:54:04 +00:00
2020-02-19 21:48:36 +00:00
elif watched == self._dropdown_window:
if self._float_mode and event.type() in ( QC.QEvent.WindowActivate, QC.QEvent.WindowDeactivate ):
# we delay this slightly because when you click from dropdown to text, the deactivate event fires before the focusin, leading to a frame of hide
HG.client_controller.CallLaterQtSafe( self, 0.05, self._DropdownHideShow )
return False
2016-09-21 19:54:04 +00:00
2020-02-19 21:48:36 +00:00
return False
2020-02-12 22:50:37 +00:00
2016-09-21 19:54:04 +00:00
def EventMove( self, event ):
2020-03-18 21:35:57 +00:00
self._DropdownHideShow()
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
return True # was: event.ignore()
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
def EventText( self, new_text ):
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
num_chars = len( self._text_ctrl.text() )
2016-09-21 19:54:04 +00:00
if num_chars == 0:
2020-04-01 21:51:42 +00:00
self._ScheduleResultsRefresh( 0.0 )
2016-09-21 19:54:04 +00:00
2018-03-28 21:55:58 +00:00
else:
2016-09-21 19:54:04 +00:00
2020-12-23 23:07:58 +00:00
parsed_autocomplete_text = self._GetParsedAutocompleteText()
if parsed_autocomplete_text.GetTagAutocompleteOptions().FetchResultsAutomatically():
2018-02-07 23:40:33 +00:00
2020-04-01 21:51:42 +00:00
self._ScheduleResultsRefresh( 0.0 )
2016-09-21 19:54:04 +00:00
2018-03-28 21:55:58 +00:00
2019-11-14 03:56:30 +00:00
if self._dropdown_notebook.currentWidget() != self._search_results_list:
2016-09-21 19:54:04 +00:00
2018-03-28 21:55:58 +00:00
self.MoveNotebookPageFocus( index = 0 )
2018-01-03 22:37:30 +00:00
2016-09-21 19:54:04 +00:00
2018-02-21 21:59:37 +00:00
def ForceSizeCalcNow( self ):
2018-02-28 22:30:36 +00:00
if self._float_mode:
self._DropdownHideShow()
2018-02-21 21:59:37 +00:00
2018-03-28 21:55:58 +00:00
def MoveNotebookPageFocus( self, index = None, direction = None ):
new_index = None
if index is not None:
new_index = index
elif direction is not None:
2019-11-14 03:56:30 +00:00
current_index = self._dropdown_notebook.currentIndex()
2018-03-28 21:55:58 +00:00
2019-11-14 03:56:30 +00:00
if current_index is not None and current_index != -1:
2018-03-28 21:55:58 +00:00
2019-11-14 03:56:30 +00:00
number_of_pages = self._dropdown_notebook.count()
2018-03-28 21:55:58 +00:00
new_index = ( current_index + direction ) % number_of_pages # does wraparound
if new_index is not None:
2019-11-14 03:56:30 +00:00
self._dropdown_notebook.setCurrentIndex( new_index )
2018-03-28 21:55:58 +00:00
self.setFocus( QC.Qt.OtherFocusReason )
2018-03-28 21:55:58 +00:00
2021-01-27 22:14:03 +00:00
def ProcessApplicationCommand( self, command: CAC.ApplicationCommand ):
command_processed = True
data = command.GetData()
if command.IsSimpleCommand():
action = data
if action == CAC.SIMPLE_AUTOCOMPLETE_IME_MODE:
self._can_intercept_unusual_key_events = not self._can_intercept_unusual_key_events
self._UpdateBackgroundColour()
elif self._can_intercept_unusual_key_events:
current_results_list = self._dropdown_notebook.currentWidget()
current_list_is_empty = len( current_results_list ) == 0
input_is_empty = self._text_ctrl.text() == ''
everything_is_empty = input_is_empty and current_list_is_empty
if action == CAC.SIMPLE_AUTOCOMPLETE_FORCE_FETCH:
self._ScheduleResultsRefresh( 0.0 )
elif input_is_empty and action in ( CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_TAB_LEFT, CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_TAB_RIGHT ):
if action == CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_TAB_LEFT:
direction = -1
else:
direction = 1
self.MoveNotebookPageFocus( direction = direction )
elif everything_is_empty and action == CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_PAGE_LEFT:
self.selectUp.emit()
elif everything_is_empty and action == CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_PAGE_RIGHT:
self.selectDown.emit()
elif everything_is_empty and action == CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_MEDIA_PREVIOUS:
self.showPrevious.emit()
elif everything_is_empty and action == CAC.SIMPLE_AUTOCOMPLETE_IF_EMPTY_MEDIA_NEXT:
self.showNext.emit()
else:
command_processed = False
else:
command_processed = False
else:
command_processed = False
return command_processed
2020-04-22 21:00:35 +00:00
def SetFetchedResults( self, job_key: ClientThreading.JobKey, parsed_autocomplete_text: ClientSearch.ParsedAutocompleteText, results_cache: ClientSearch.PredicateResultsCache, results: list ):
2018-03-28 21:55:58 +00:00
2019-01-30 22:14:54 +00:00
if self._current_fetch_job_key is not None and self._current_fetch_job_key.GetKey() == job_key.GetKey():
2020-04-01 21:51:42 +00:00
self._CancelSearchResultsFetchJob()
2019-07-17 22:10:19 +00:00
2020-04-22 21:00:35 +00:00
self._results_cache = results_cache
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
self._SetResultsToList( results, parsed_autocomplete_text )
2019-01-30 22:14:54 +00:00
2018-03-28 21:55:58 +00:00
2020-04-01 21:51:42 +00:00
def SetForceDropdownHide( self, value ):
self._force_dropdown_hide = value
self._DropdownHideShow()
2016-09-21 19:54:04 +00:00
class AutoCompleteDropdownTags( AutoCompleteDropdown ):
2020-03-25 21:15:57 +00:00
fileServiceChanged = QC.Signal( bytes )
tagServiceChanged = QC.Signal( bytes )
2016-09-21 19:54:04 +00:00
def __init__( self, parent, file_service_key, tag_service_key ):
2020-09-02 21:10:41 +00:00
if not HG.client_controller.services_manager.ServiceExists( file_service_key ):
file_service_key = CC.COMBINED_LOCAL_FILE_SERVICE_KEY
if not HG.client_controller.services_manager.ServiceExists( tag_service_key ):
tag_service_key = CC.COMBINED_TAG_SERVICE_KEY
2016-09-21 19:54:04 +00:00
self._file_service_key = file_service_key
self._tag_service_key = tag_service_key
AutoCompleteDropdown.__init__( self, parent )
2019-04-24 22:18:50 +00:00
self._allow_all_known_files = True
2017-06-28 20:23:21 +00:00
file_service = HG.client_controller.services_manager.GetService( self._file_service_key )
2016-12-21 22:30:54 +00:00
2017-06-28 20:23:21 +00:00
tag_service = HG.client_controller.services_manager.GetService( self._tag_service_key )
2016-09-21 19:54:04 +00:00
2016-10-26 20:45:34 +00:00
self._file_repo_button = ClientGUICommon.BetterButton( self._dropdown_window, file_service.GetName(), self.FileButtonHit )
2019-11-14 03:56:30 +00:00
self._file_repo_button.setMinimumWidth( 20 )
2016-09-21 19:54:04 +00:00
2016-10-26 20:45:34 +00:00
self._tag_repo_button = ClientGUICommon.BetterButton( self._dropdown_window, tag_service.GetName(), self.TagButtonHit )
2019-11-14 03:56:30 +00:00
self._tag_repo_button.setMinimumWidth( 20 )
2016-09-21 19:54:04 +00:00
2019-01-30 22:14:54 +00:00
self._favourites_list = self._InitFavouritesList()
self.RefreshFavouriteTags()
2019-11-14 03:56:30 +00:00
self._dropdown_notebook.addTab( self._favourites_list, 'favourites' )
2019-01-30 22:14:54 +00:00
#
HG.client_controller.sub( self, 'RefreshFavouriteTags', 'notify_new_favourite_tags' )
2021-02-11 01:59:52 +00:00
HG.client_controller.sub( self, 'NotifyNewServices', 'notify_new_services' )
2019-01-30 22:14:54 +00:00
2016-09-21 19:54:04 +00:00
def _ChangeFileService( self, file_service_key ):
2021-02-11 01:59:52 +00:00
if not HG.client_controller.services_manager.ServiceExists( file_service_key ):
file_service_key = CC.COMBINED_LOCAL_FILE_SERVICE_KEY
2016-09-21 19:54:04 +00:00
if file_service_key == CC.COMBINED_FILE_SERVICE_KEY and self._tag_service_key == CC.COMBINED_TAG_SERVICE_KEY:
2019-09-18 22:40:39 +00:00
local_tag_services = HG.client_controller.services_manager.GetServices( ( HC.LOCAL_TAG, ) )
self._ChangeTagService( local_tag_services[0].GetServiceKey() )
2016-09-21 19:54:04 +00:00
self._file_service_key = file_service_key
2020-03-11 21:52:11 +00:00
self._UpdateFileServiceLabel()
2016-09-21 19:54:04 +00:00
2020-03-25 21:15:57 +00:00
self.fileServiceChanged.emit( self._file_service_key )
2018-02-14 21:47:18 +00:00
self._SetListDirty()
2016-09-21 19:54:04 +00:00
def _ChangeTagService( self, tag_service_key ):
2021-02-11 01:59:52 +00:00
if not HG.client_controller.services_manager.ServiceExists( tag_service_key ):
tag_service_key = CC.COMBINED_TAG_SERVICE_KEY
2016-09-21 19:54:04 +00:00
if tag_service_key == CC.COMBINED_TAG_SERVICE_KEY and self._file_service_key == CC.COMBINED_FILE_SERVICE_KEY:
self._ChangeFileService( CC.LOCAL_FILE_SERVICE_KEY )
self._tag_service_key = tag_service_key
2021-02-24 22:35:18 +00:00
self._search_results_list.SetTagServiceKey( self._tag_service_key )
self._favourites_list.SetTagServiceKey( self._tag_service_key )
2016-09-21 19:54:04 +00:00
2020-03-11 21:52:11 +00:00
self._UpdateTagServiceLabel()
2016-09-21 19:54:04 +00:00
2020-03-25 21:15:57 +00:00
self.tagServiceChanged.emit( self._tag_service_key )
2018-02-14 21:47:18 +00:00
self._SetListDirty()
2016-09-21 19:54:04 +00:00
2020-05-20 21:36:02 +00:00
def _GetCurrentBroadcastTextPredicate( self ) -> typing.Optional[ ClientSearch.Predicate ]:
raise NotImplementedError()
2020-04-22 21:00:35 +00:00
def _GetParsedAutocompleteText( self ) -> ClientSearch.ParsedAutocompleteText:
collapse_search_characters = True
2020-05-20 21:36:02 +00:00
tag_autocomplete_options = HG.client_controller.tag_display_manager.GetTagAutocompleteOptions( self._tag_service_key )
2020-04-22 21:00:35 +00:00
2020-05-20 21:36:02 +00:00
parsed_autocomplete_text = ClientSearch.ParsedAutocompleteText( self._text_ctrl.text(), tag_autocomplete_options, collapse_search_characters )
2016-09-21 19:54:04 +00:00
2020-05-20 21:36:02 +00:00
return parsed_autocomplete_text
2016-09-21 19:54:04 +00:00
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
def _InitFavouritesList( self ):
raise NotImplementedError()
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
2020-05-20 21:36:02 +00:00
def _SetResultsToList( self, results, parsed_autocomplete_text: ClientSearch.ParsedAutocompleteText ):
2019-08-15 00:40:48 +00:00
2019-01-30 22:14:54 +00:00
self._search_results_list.SetPredicates( results )
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
self._current_list_parsed_autocomplete_text = parsed_autocomplete_text
2016-09-21 19:54:04 +00:00
2020-03-11 21:52:11 +00:00
def _UpdateFileServiceLabel( self ):
file_service = HG.client_controller.services_manager.GetService( self._file_service_key )
name = file_service.GetName()
self._file_repo_button.setText( name )
self._SetListDirty()
def _UpdateTagServiceLabel( self ):
tag_service = HG.client_controller.services_manager.GetService( self._tag_service_key )
name = tag_service.GetName()
self._tag_repo_button.setText( name )
2016-10-26 20:45:34 +00:00
def FileButtonHit( self ):
2017-06-28 20:23:21 +00:00
services_manager = HG.client_controller.services_manager
2016-10-26 20:45:34 +00:00
2020-03-11 21:52:11 +00:00
service_types_in_order = [ HC.LOCAL_FILE_DOMAIN, HC.LOCAL_FILE_TRASH_DOMAIN ]
2016-12-21 22:30:54 +00:00
2020-03-11 21:52:11 +00:00
advanced_mode = HG.client_controller.new_options.GetBoolean( 'advanced_mode' )
2019-05-22 22:35:06 +00:00
2020-03-11 21:52:11 +00:00
if advanced_mode:
2019-05-22 22:35:06 +00:00
2020-03-11 21:52:11 +00:00
service_types_in_order.append( HC.COMBINED_LOCAL_FILE )
2019-05-22 22:35:06 +00:00
2020-03-11 21:52:11 +00:00
service_types_in_order.append( HC.FILE_REPOSITORY )
2017-11-22 21:03:07 +00:00
2019-04-24 22:18:50 +00:00
if advanced_mode and self._allow_all_known_files:
2017-11-22 21:03:07 +00:00
2020-03-11 21:52:11 +00:00
service_types_in_order.append( HC.COMBINED_FILE )
2017-11-22 21:03:07 +00:00
2016-10-26 20:45:34 +00:00
2020-03-11 21:52:11 +00:00
services = services_manager.GetServices( service_types_in_order )
2019-11-14 03:56:30 +00:00
menu = QW.QMenu()
2016-10-26 20:45:34 +00:00
2016-12-21 22:30:54 +00:00
for service in services:
if service.GetServiceKey() == CC.LOCAL_UPDATE_SERVICE_KEY and not advanced_mode:
continue
2019-11-14 03:56:30 +00:00
ClientGUIMenus.AppendMenuItem( menu, service.GetName(), 'Change the current file domain to ' + service.GetName() + '.', self._ChangeFileService, service.GetServiceKey() )
2016-12-21 22:30:54 +00:00
2016-10-26 20:45:34 +00:00
2020-03-04 22:12:53 +00:00
CGC.core().PopupMenu( self._file_repo_button, menu )
2016-10-26 20:45:34 +00:00
2021-02-11 01:59:52 +00:00
def NotifyNewServices( self ):
2021-02-24 22:35:18 +00:00
self.SetFileServiceKey( self._file_service_key )
self.SetTagServiceKey( self._tag_service_key )
2021-02-11 01:59:52 +00:00
2019-01-30 22:14:54 +00:00
def RefreshFavouriteTags( self ):
2020-05-13 19:03:16 +00:00
favourite_tags = sorted( HG.client_controller.new_options.GetStringList( 'favourite_tags' ) )
2019-01-30 22:14:54 +00:00
2020-03-11 21:52:11 +00:00
predicates = [ ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_TAG, tag ) for tag in favourite_tags ]
2019-01-30 22:14:54 +00:00
self._favourites_list.SetPredicates( predicates )
2021-02-24 22:35:18 +00:00
def SetFileServiceKey( self, file_service_key ):
2016-10-26 20:45:34 +00:00
self._ChangeFileService( file_service_key )
2020-04-22 21:00:35 +00:00
def SetStubPredicates( self, job_key, stub_predicates, parsed_autocomplete_text ):
2019-07-17 22:10:19 +00:00
if self._current_fetch_job_key is not None and self._current_fetch_job_key.GetKey() == job_key.GetKey():
2020-04-22 21:00:35 +00:00
self._SetResultsToList( stub_predicates, parsed_autocomplete_text )
2019-07-17 22:10:19 +00:00
2021-02-24 22:35:18 +00:00
def SetTagServiceKey( self, tag_service_key ):
2016-10-26 20:45:34 +00:00
self._ChangeTagService( tag_service_key )
def TagButtonHit( self ):
2016-09-21 19:54:04 +00:00
2017-06-28 20:23:21 +00:00
services_manager = HG.client_controller.services_manager
2016-09-21 19:54:04 +00:00
2020-03-11 21:52:11 +00:00
service_types_in_order = [ HC.LOCAL_TAG, HC.TAG_REPOSITORY, HC.COMBINED_TAG ]
2016-12-21 22:30:54 +00:00
2020-03-11 21:52:11 +00:00
services = services_manager.GetServices( service_types_in_order )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
menu = QW.QMenu()
2016-09-21 19:54:04 +00:00
2016-12-21 22:30:54 +00:00
for service in services:
2019-11-14 03:56:30 +00:00
ClientGUIMenus.AppendMenuItem( menu, service.GetName(), 'Change the current tag domain to ' + service.GetName() + '.', self._ChangeTagService, service.GetServiceKey() )
2016-12-21 22:30:54 +00:00
2016-09-21 19:54:04 +00:00
2020-03-04 22:12:53 +00:00
CGC.core().PopupMenu( self._tag_repo_button, menu )
2016-09-21 19:54:04 +00:00
class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
2020-03-25 21:15:57 +00:00
searchChanged = QC.Signal( ClientSearch.FileSearchContext )
searchCancelled = QC.Signal()
2020-11-25 22:22:47 +00:00
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 ):
2020-03-11 21:52:11 +00:00
2020-03-18 21:35:57 +00:00
self._page_key = page_key
2016-09-21 19:54:04 +00:00
2020-09-02 21:10:41 +00:00
self._under_construction_or_predicate = None
2016-09-21 19:54:04 +00:00
file_service_key = file_search_context.GetFileServiceKey()
2020-03-11 21:52:11 +00:00
tag_search_context = file_search_context.GetTagSearchContext()
2016-09-21 19:54:04 +00:00
2020-03-18 21:35:57 +00:00
self._include_unusual_predicate_types = include_unusual_predicate_types
self._force_system_everything = force_system_everything
self._hide_favourites_edit_actions = hide_favourites_edit_actions
2020-03-11 21:52:11 +00:00
self._media_sort_widget = media_sort_widget
self._media_collect_widget = media_collect_widget
2016-09-21 19:54:04 +00:00
2019-04-24 22:18:50 +00:00
self._allow_all_known_files = allow_all_known_files
2016-09-21 19:54:04 +00:00
self._media_callable = media_callable
2020-03-11 21:52:11 +00:00
2016-09-21 19:54:04 +00:00
self._file_search_context = file_search_context
2020-09-02 21:10:41 +00:00
AutoCompleteDropdownTags.__init__( self, parent, file_service_key, tag_search_context.service_key )
2020-03-11 21:52:11 +00:00
self._predicates_listbox.SetPredicates( self._file_search_context.GetPredicates() )
#
self._favourite_searches_button = ClientGUICommon.BetterBitmapButton( self._text_input_panel, CC.global_pixmaps().star, self._FavouriteSearchesMenu )
self._favourite_searches_button.setToolTip( 'Load or save a favourite search.' )
2020-03-25 21:15:57 +00:00
self._cancel_search_button = ClientGUICommon.BetterBitmapButton( self._text_input_panel, CC.global_pixmaps().stop, self.searchCancelled.emit )
self._cancel_search_button.hide()
2020-07-29 20:52:44 +00:00
QP.AddToLayout( self._text_input_hbox, self._favourite_searches_button, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( self._text_input_hbox, self._cancel_search_button, CC.FLAGS_CENTER_PERPENDICULAR )
2020-03-25 21:15:57 +00:00
#
2020-03-11 21:52:11 +00:00
2020-05-20 21:36:02 +00:00
self._include_current_tags = ClientGUICommon.OnOffButton( self._dropdown_window, on_label = 'include current tags', off_label = 'exclude current tags', start_on = tag_search_context.include_current_tags )
2019-11-14 03:56:30 +00:00
self._include_current_tags.setToolTip( 'select whether to include current tags in the search' )
2020-05-20 21:36:02 +00:00
self._include_pending_tags = ClientGUICommon.OnOffButton( self._dropdown_window, on_label = 'include pending tags', off_label = 'exclude pending tags', start_on = tag_search_context.include_pending_tags )
2019-11-14 03:56:30 +00:00
self._include_pending_tags.setToolTip( 'select whether to include pending tags in the search' )
2016-09-21 19:54:04 +00:00
self._search_pause_play = ClientGUICommon.OnOffButton( self._dropdown_window, on_label = 'searching immediately', off_label = 'search paused', start_on = synchronised )
self._search_pause_play.setToolTip( 'select whether to renew the search as soon as a new predicate is entered' )
2016-09-21 19:54:04 +00:00
2019-08-15 00:40:48 +00:00
self._or_advanced = ClientGUICommon.BetterButton( self._dropdown_window, 'OR', self._AdvancedORInput )
2019-11-14 03:56:30 +00:00
self._or_advanced.setToolTip( 'Advanced OR Search input.' )
2019-08-15 00:40:48 +00:00
if not HG.client_controller.new_options.GetBoolean( 'advanced_mode' ):
2019-11-14 03:56:30 +00:00
self._or_advanced.hide()
2019-08-15 00:40:48 +00:00
2020-03-11 21:52:11 +00:00
self._or_cancel = ClientGUICommon.BetterBitmapButton( self._dropdown_window, CC.global_pixmaps().delete, self._CancelORConstruction )
2019-11-14 03:56:30 +00:00
self._or_cancel.setToolTip( 'Cancel OR Predicate construction.' )
self._or_cancel.hide()
2016-09-21 19:54:04 +00:00
2020-03-11 21:52:11 +00:00
self._or_rewind = ClientGUICommon.BetterBitmapButton( self._dropdown_window, CC.global_pixmaps().previous, self._RewindORConstruction )
2019-11-14 03:56:30 +00:00
self._or_rewind.setToolTip( 'Rewind OR Predicate construction.' )
self._or_rewind.hide()
2019-04-03 22:45:57 +00:00
2019-11-14 03:56:30 +00:00
button_hbox_1 = QP.HBoxLayout()
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
QP.AddToLayout( button_hbox_1, self._include_current_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( button_hbox_1, self._include_pending_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
sync_button_hbox = QP.HBoxLayout()
2019-04-10 22:50:53 +00:00
QP.AddToLayout( sync_button_hbox, self._search_pause_play, CC.FLAGS_EXPAND_BOTH_WAYS )
2020-07-29 20:52:44 +00:00
QP.AddToLayout( sync_button_hbox, self._or_advanced, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( sync_button_hbox, self._or_cancel, CC.FLAGS_CENTER_PERPENDICULAR )
QP.AddToLayout( sync_button_hbox, self._or_rewind, CC.FLAGS_CENTER_PERPENDICULAR )
2019-04-10 22:50:53 +00:00
2019-11-14 03:56:30 +00:00
button_hbox_2 = QP.HBoxLayout()
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
QP.AddToLayout( button_hbox_2, self._file_repo_button, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( button_hbox_2, self._tag_repo_button, CC.FLAGS_EXPAND_BOTH_WAYS )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
vbox = QP.VBoxLayout()
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
QP.AddToLayout( vbox, button_hbox_1, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
QP.AddToLayout( vbox, sync_button_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
QP.AddToLayout( vbox, button_hbox_2, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
QP.AddToLayout( vbox, self._dropdown_notebook, CC.FLAGS_EXPAND_BOTH_WAYS )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
self._dropdown_window.setLayout( vbox )
2016-09-21 19:54:04 +00:00
2020-03-25 21:15:57 +00:00
self._predicates_listbox.listBoxChanged.connect( self._SignalNewSearchState )
2020-05-20 21:36:02 +00:00
self._include_current_tags.valueChanged.connect( self.SetIncludeCurrent )
self._include_pending_tags.valueChanged.connect( self.SetIncludePending )
self._search_pause_play.valueChanged.connect( self.SetSynchronised )
2020-05-20 21:36:02 +00:00
2016-09-21 19:54:04 +00:00
2019-08-15 00:40:48 +00:00
def _AdvancedORInput( self ):
title = 'enter advanced OR predicates'
2020-04-29 21:44:12 +00:00
with ClientGUITopLevelWindowsPanels.DialogEdit( self, title ) as dlg:
2019-08-15 00:40:48 +00:00
2020-02-12 22:50:37 +00:00
panel = EditAdvancedORPredicates( dlg )
2019-08-15 00:40:48 +00:00
dlg.SetPanel( panel )
2019-11-14 03:56:30 +00:00
if dlg.exec() == QW.QDialog.Accepted:
2019-08-15 00:40:48 +00:00
predicates = panel.GetValue()
shift_down = False
if len( predicates ) > 0:
self._BroadcastChoices( predicates, shift_down )
2019-04-03 22:45:57 +00:00
def _BroadcastChoices( self, predicates, shift_down ):
2016-09-21 19:54:04 +00:00
2019-04-10 22:50:53 +00:00
or_pred_in_broadcast = self._under_construction_or_predicate is not None and self._under_construction_or_predicate in predicates
2019-04-03 22:45:57 +00:00
if shift_down:
if self._under_construction_or_predicate is None:
2020-03-11 21:52:11 +00:00
self._under_construction_or_predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_OR_CONTAINER, predicates )
2019-04-03 22:45:57 +00:00
else:
2019-04-10 22:50:53 +00:00
if or_pred_in_broadcast:
predicates.remove( self._under_construction_or_predicate )
2019-04-03 22:45:57 +00:00
or_preds = list( self._under_construction_or_predicate.GetValue() )
or_preds.extend( [ predicate for predicate in predicates if predicate not in or_preds ] )
2020-03-11 21:52:11 +00:00
self._under_construction_or_predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_OR_CONTAINER, or_preds )
2019-04-03 22:45:57 +00:00
else:
2019-04-10 22:50:53 +00:00
if or_pred_in_broadcast:
2019-04-03 22:45:57 +00:00
2019-04-10 22:50:53 +00:00
or_preds = list( self._under_construction_or_predicate.GetValue() )
if len( or_preds ) == 1:
predicates.remove( self._under_construction_or_predicate )
predicates.extend( or_preds )
2019-04-03 22:45:57 +00:00
2020-07-15 20:52:09 +00:00
elif self._under_construction_or_predicate is not None:
or_preds = list( self._under_construction_or_predicate.GetValue() )
or_preds.extend( [ predicate for predicate in predicates if predicate not in or_preds ] )
predicates = { ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_OR_CONTAINER, or_preds ) }
2019-04-03 22:45:57 +00:00
2019-04-10 22:50:53 +00:00
self._under_construction_or_predicate = None
2020-03-25 21:15:57 +00:00
self._predicates_listbox.EnterPredicates( self._page_key, predicates )
2019-04-03 22:45:57 +00:00
2016-09-21 19:54:04 +00:00
2019-04-10 22:50:53 +00:00
self._UpdateORButtons()
2018-05-23 21:05:06 +00:00
self._ClearInput()
2016-09-21 19:54:04 +00:00
2020-03-25 21:15:57 +00:00
def _SignalNewSearchState( self ):
file_search_context = self.GetFileSearchContext()
self.searchChanged.emit( file_search_context )
2019-04-10 22:50:53 +00:00
def _CancelORConstruction( self ):
self._under_construction_or_predicate = None
self._UpdateORButtons()
self._ClearInput()
2016-09-21 19:54:04 +00:00
def _ChangeFileService( self, file_service_key ):
AutoCompleteDropdownTags._ChangeFileService( self, file_service_key )
self._file_search_context.SetFileServiceKey( file_service_key )
2020-03-25 21:15:57 +00:00
self._SignalNewSearchState()
2016-09-21 19:54:04 +00:00
def _ChangeTagService( self, tag_service_key ):
AutoCompleteDropdownTags._ChangeTagService( self, tag_service_key )
self._file_search_context.SetTagServiceKey( tag_service_key )
2020-03-25 21:15:57 +00:00
self._SignalNewSearchState()
2020-03-11 21:52:11 +00:00
def _FavouriteSearchesMenu( self ):
menu = QW.QMenu()
if not self._hide_favourites_edit_actions:
ClientGUIMenus.AppendMenuItem( menu, 'manage favourite searches', 'Open a dialog to edit your favourite searches.', self._ManageFavouriteSearches )
ClientGUIMenus.AppendSeparator( menu )
ClientGUIMenus.AppendMenuItem( menu, 'save this search', 'Save this search for later.', self._SaveFavouriteSearch )
folders_to_names = HG.client_controller.favourite_search_manager.GetFoldersToNames()
if len( folders_to_names ) > 0:
ClientGUIMenus.AppendSeparator( menu )
folder_names = list( folders_to_names.keys() )
if None in folder_names:
folder_names.remove( None )
folder_names.sort()
folder_names.insert( 0, None )
2020-05-13 19:03:16 +00:00
else:
folder_names.sort()
2020-03-11 21:52:11 +00:00
for folder_name in folder_names:
if folder_name is None:
menu_to_use = menu
else:
menu_to_use = QW.QMenu( menu )
ClientGUIMenus.AppendMenu( menu, menu_to_use, folder_name )
2020-05-13 19:03:16 +00:00
names = sorted( folders_to_names[ folder_name ] )
2020-03-11 21:52:11 +00:00
for name in names:
ClientGUIMenus.AppendMenuItem( menu_to_use, name, 'Load the {} search.'.format( name ), self._LoadFavouriteSearch, folder_name, name )
CGC.core().PopupMenu( self, menu )
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
def _GetCurrentBroadcastTextPredicate( self ) -> typing.Optional[ ClientSearch.Predicate ]:
parsed_autocomplete_text = self._GetParsedAutocompleteText()
2020-05-20 21:36:02 +00:00
if parsed_autocomplete_text.IsAcceptableForFileSearches():
2020-04-22 21:00:35 +00:00
return parsed_autocomplete_text.GetImmediateFileSearchPredicate()
else:
return None
2019-04-10 22:50:53 +00:00
def _HandleEscape( self ):
2019-11-14 03:56:30 +00:00
if self._under_construction_or_predicate is not None and self._text_ctrl.text() == '':
2019-04-10 22:50:53 +00:00
self._CancelORConstruction()
return True
else:
return AutoCompleteDropdown._HandleEscape( self )
2018-03-28 21:55:58 +00:00
def _InitFavouritesList( self ):
height_num_chars = HG.client_controller.new_options.GetInteger( 'ac_read_list_height_num_chars' )
2021-02-24 22:35:18 +00:00
favs_list = ListBoxTagsPredicatesAC( self._dropdown_notebook, self.BroadcastChoices, self._float_mode, self._tag_service_key, tag_display_type = ClientTags.TAG_DISPLAY_ACTUAL, height_num_chars = height_num_chars )
2016-09-21 19:54:04 +00:00
2018-03-28 21:55:58 +00:00
return favs_list
def _InitSearchResultsList( self ):
height_num_chars = HG.client_controller.new_options.GetInteger( 'ac_read_list_height_num_chars' )
2019-11-14 03:56:30 +00:00
2021-02-24 22:35:18 +00:00
return ListBoxTagsPredicatesAC( self._dropdown_notebook, self.BroadcastChoices, self._tag_service_key, self._float_mode, tag_display_type = ClientTags.TAG_DISPLAY_ACTUAL, height_num_chars = height_num_chars )
2016-09-21 19:54:04 +00:00
2020-03-11 21:52:11 +00:00
def _LoadFavouriteSearch( self, folder_name, name ):
( file_search_context, synchronised, media_sort, media_collect ) = HG.client_controller.favourite_search_manager.GetFavouriteSearch( folder_name, name )
2020-05-20 21:36:02 +00:00
self.blockSignals( True )
2020-03-11 21:52:11 +00:00
self.SetFileSearchContext( file_search_context )
if media_sort is not None and self._media_sort_widget is not None:
self._media_sort_widget.SetSort( media_sort )
if media_collect is not None and self._media_collect_widget is not None:
self._media_collect_widget.SetCollect( media_collect )
self._search_pause_play.SetOnOff( synchronised )
2020-03-11 21:52:11 +00:00
2020-05-20 21:36:02 +00:00
self.blockSignals( False )
self._SignalNewSearchState()
2020-03-11 21:52:11 +00:00
def _ManageFavouriteSearches( self, favourite_search_row_to_save = None ):
2020-11-25 22:22:47 +00:00
from hydrus.client.gui.search import ClientGUISearchPanels
2020-03-11 21:52:11 +00:00
favourite_searches_rows = HG.client_controller.favourite_search_manager.GetFavouriteSearchRows()
title = 'edit favourite searches'
2020-04-29 21:44:12 +00:00
with ClientGUITopLevelWindowsPanels.DialogEdit( self, title ) as dlg:
2020-03-11 21:52:11 +00:00
panel = ClientGUISearchPanels.EditFavouriteSearchesPanel( dlg, favourite_searches_rows, initial_search_row_to_edit = favourite_search_row_to_save )
dlg.SetPanel( panel )
if dlg.exec() == QW.QDialog.Accepted:
edited_favourite_searches_rows = panel.GetValue()
HG.client_controller.favourite_search_manager.SetFavouriteSearchRows( edited_favourite_searches_rows )
2019-04-10 22:50:53 +00:00
def _RewindORConstruction( self ):
2019-04-03 22:45:57 +00:00
2019-04-10 22:50:53 +00:00
if self._under_construction_or_predicate is not None:
2019-04-03 22:45:57 +00:00
2019-04-10 22:50:53 +00:00
or_preds = self._under_construction_or_predicate.GetValue()
2019-04-03 22:45:57 +00:00
2019-04-10 22:50:53 +00:00
if len( or_preds ) <= 1:
2019-04-03 22:45:57 +00:00
2019-04-10 22:50:53 +00:00
self._CancelORConstruction()
2019-04-03 22:45:57 +00:00
2019-04-10 22:50:53 +00:00
return
2019-04-03 22:45:57 +00:00
2019-04-10 22:50:53 +00:00
or_preds = or_preds[:-1]
2019-04-03 22:45:57 +00:00
2020-03-11 21:52:11 +00:00
self._under_construction_or_predicate = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_OR_CONTAINER, or_preds )
2019-04-03 22:45:57 +00:00
2019-04-10 22:50:53 +00:00
self._UpdateORButtons()
self._ClearInput()
2019-04-03 22:45:57 +00:00
2020-03-11 21:52:11 +00:00
def _SaveFavouriteSearch( self ):
foldername = None
name = 'new favourite search'
file_search_context = self.GetFileSearchContext()
synchronised = self.IsSynchronised()
if self._media_sort_widget is None:
media_sort = None
else:
media_sort = self._media_sort_widget.GetSort()
if self._media_collect_widget is None:
media_collect = None
else:
media_collect = self._media_collect_widget.GetValue()
search_row = ( foldername, name, file_search_context, synchronised, media_sort, media_collect )
self._ManageFavouriteSearches( favourite_search_row_to_save = search_row )
2020-03-18 21:35:57 +00:00
def _SetupTopListBox( self ):
self._predicates_listbox = ListBoxTagsActiveSearchPredicates( self, self._page_key )
2021-04-14 21:54:17 +00:00
QP.AddToLayout( self._main_vbox, self._predicates_listbox, CC.FLAGS_EXPAND_BOTH_WAYS )
2020-03-18 21:35:57 +00:00
2020-04-01 21:51:42 +00:00
def _StartSearchResultsFetchJob( self, job_key ):
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
parsed_autocomplete_text = self._GetParsedAutocompleteText()
2016-09-21 19:54:04 +00:00
2019-07-17 22:10:19 +00:00
stub_predicates = []
2020-04-22 21:00:35 +00:00
InsertOtherPredicatesForRead( stub_predicates, parsed_autocomplete_text, self._include_unusual_predicate_types, self._under_construction_or_predicate )
2019-07-17 22:10:19 +00:00
AppendLoadingPredicate( stub_predicates )
2020-04-22 21:00:35 +00:00
HG.client_controller.CallLaterQtSafe( self, 0.2, self.SetStubPredicates, job_key, stub_predicates, parsed_autocomplete_text )
2020-04-01 21:51:42 +00:00
if self._under_construction_or_predicate is None:
under_construction_or_predicate = None
else:
under_construction_or_predicate = self._under_construction_or_predicate.Duplicate()
2019-07-17 22:10:19 +00:00
HG.client_controller.CallToThread( ReadFetch, self, job_key, self.SetFetchedResults, parsed_autocomplete_text, self._media_callable, self._file_search_context.Duplicate(), self._search_pause_play.IsOn(), self._include_unusual_predicate_types, self._results_cache, under_construction_or_predicate, self._force_system_everything )
2016-09-21 19:54:04 +00:00
def _ShouldTakeResponsibilityForEnter( self ):
2019-11-14 03:56:30 +00:00
looking_at_search_results = self._dropdown_notebook.currentWidget() == self._search_results_list
2019-08-07 22:59:53 +00:00
2020-04-22 21:00:35 +00:00
something_to_broadcast = self._GetCurrentBroadcastTextPredicate() is not None
parsed_autocomplete_text = self._GetParsedAutocompleteText()
2019-01-30 22:14:54 +00:00
2019-08-15 00:40:48 +00:00
# the list has results, but they are out of sync with what we have currently entered
2016-09-21 19:54:04 +00:00
# when the user has quickly typed something in and the results are not yet in
2020-04-22 21:00:35 +00:00
results_desynced_with_text = parsed_autocomplete_text != self._current_list_parsed_autocomplete_text
2016-09-21 19:54:04 +00:00
2019-08-15 00:40:48 +00:00
p1 = looking_at_search_results and something_to_broadcast and results_desynced_with_text
2019-01-30 22:14:54 +00:00
return p1
2016-09-21 19:54:04 +00:00
2019-04-03 22:45:57 +00:00
def _TakeResponsibilityForEnter( self, shift_down ):
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
current_broadcast_predicate = self._GetCurrentBroadcastTextPredicate()
if current_broadcast_predicate is not None:
self._BroadcastChoices( { current_broadcast_predicate }, shift_down )
2016-09-21 19:54:04 +00:00
2019-04-10 22:50:53 +00:00
def _UpdateORButtons( self ):
if self._under_construction_or_predicate is None:
2019-11-14 03:56:30 +00:00
if self._or_cancel.isVisible():
2019-04-10 22:50:53 +00:00
2019-11-14 03:56:30 +00:00
self._or_cancel.hide()
2019-04-10 22:50:53 +00:00
2019-11-14 03:56:30 +00:00
if self._or_rewind.isVisible():
2019-04-10 22:50:53 +00:00
2019-11-14 03:56:30 +00:00
self._or_rewind.hide()
2019-04-10 22:50:53 +00:00
else:
or_preds = self._under_construction_or_predicate.GetValue()
if len( or_preds ) > 1:
2019-11-14 03:56:30 +00:00
if not self._or_rewind.isVisible():
2019-04-10 22:50:53 +00:00
2019-11-14 03:56:30 +00:00
self._or_rewind.show()
2019-04-10 22:50:53 +00:00
else:
2019-11-14 03:56:30 +00:00
if self._or_rewind.isVisible():
2019-04-10 22:50:53 +00:00
2019-11-14 03:56:30 +00:00
self._or_rewind.hide()
2019-04-10 22:50:53 +00:00
2019-11-14 03:56:30 +00:00
if not self._or_cancel.isVisible():
2019-04-10 22:50:53 +00:00
2019-11-14 03:56:30 +00:00
self._or_cancel.show()
2019-04-10 22:50:53 +00:00
2020-03-11 21:52:11 +00:00
def GetFileSearchContext( self ) -> ClientSearch.FileSearchContext:
fsc = self._file_search_context.Duplicate()
fsc.SetPredicates( self._predicates_listbox.GetPredicates() )
2016-09-21 19:54:04 +00:00
2020-03-11 21:52:11 +00:00
return fsc
2016-09-21 19:54:04 +00:00
2020-03-18 21:35:57 +00:00
def GetPredicates( self ) -> typing.Set[ ClientSearch.Predicate ]:
return self._predicates_listbox.GetPredicates()
2019-04-24 22:18:50 +00:00
def IsSynchronised( self ):
return self._search_pause_play.IsOn()
def PauseSearching( self ):
self._search_pause_play.SetOnOff( False )
2019-04-24 22:18:50 +00:00
2021-01-27 22:14:03 +00:00
def ProcessApplicationCommand( self, command: CAC.ApplicationCommand ):
command_processed = True
data = command.GetData()
if self._can_intercept_unusual_key_events and command.IsSimpleCommand():
action = data
if action == CAC.SIMPLE_SYNCHRONISED_WAIT_SWITCH:
self.PausePlaySearch()
else:
command_processed = False
else:
command_processed = False
if not command_processed:
command_processed = AutoCompleteDropdownTags.ProcessApplicationCommand( self, command )
return command_processed
2020-04-22 21:00:35 +00:00
def SetFetchedResults( self, job_key: ClientThreading.JobKey, parsed_autocomplete_text: ClientSearch.ParsedAutocompleteText, results_cache: ClientSearch.PredicateResultsCache, results: list ):
2019-01-30 22:14:54 +00:00
if self._current_fetch_job_key is not None and self._current_fetch_job_key.GetKey() == job_key.GetKey():
2020-04-22 21:00:35 +00:00
AutoCompleteDropdownTags.SetFetchedResults( self, job_key, parsed_autocomplete_text, results_cache, results )
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
if parsed_autocomplete_text.IsEmpty():
2019-01-30 22:14:54 +00:00
# refresh system preds after five mins
2020-04-01 21:51:42 +00:00
self._ScheduleResultsRefresh( 300 )
2019-01-30 22:14:54 +00:00
2020-03-11 21:52:11 +00:00
def SetFileSearchContext( self, file_search_context: ClientSearch.FileSearchContext ):
self._ClearInput()
self._CancelORConstruction()
self._file_search_context = file_search_context.Duplicate()
self._ChangeFileService( self._file_search_context.GetFileServiceKey() )
self._ChangeTagService( self._file_search_context.GetTagSearchContext().service_key )
self._predicates_listbox.SetPredicates( self._file_search_context.GetPredicates() )
2020-05-20 21:36:02 +00:00
self._SignalNewSearchState()
def SetIncludeCurrent( self, value ):
self._file_search_context.SetIncludeCurrentTags( value )
self._SetListDirty()
self._SignalNewSearchState()
def SetIncludePending( self, value ):
self._file_search_context.SetIncludePendingTags( value )
self._SetListDirty()
self._SignalNewSearchState()
def SetSynchronised( self, value ):
self._SignalNewSearchState()
2020-03-11 21:52:11 +00:00
def PausePlaySearch( self ):
2016-09-21 19:54:04 +00:00
self._search_pause_play.Flip()
2020-03-25 21:15:57 +00:00
def ShowCancelSearchButton( self, show ):
if self._cancel_search_button.isVisible() != show:
2019-04-24 22:18:50 +00:00
2020-03-25 21:15:57 +00:00
self._cancel_search_button.setVisible( show )
2019-04-24 22:18:50 +00:00
2016-09-21 19:54:04 +00:00
2020-03-18 21:35:57 +00:00
class ListBoxTagsActiveSearchPredicates( ClientGUIListBoxes.ListBoxTagsPredicates ):
def __init__( self, parent: AutoCompleteDropdownTagsRead, page_key, initial_predicates = None ):
if initial_predicates is None:
initial_predicates = []
ClientGUIListBoxes.ListBoxTagsPredicates.__init__( self, parent, height_num_chars = 6 )
self._my_ac_parent = parent
self._page_key = page_key
if len( initial_predicates ) > 0:
2021-02-11 01:59:52 +00:00
terms = [ self._GenerateTermFromPredicate( predicate ) for predicate in initial_predicates ]
self._AppendTerms( terms )
2020-03-18 21:35:57 +00:00
2021-02-11 01:59:52 +00:00
self._Sort()
2020-11-25 22:22:47 +00:00
2020-03-18 21:35:57 +00:00
self._DataHasChanged()
HG.client_controller.sub( self, 'EnterPredicates', 'enter_predicates' )
2020-11-18 22:15:21 +00:00
def _Activate( self, shift_down ) -> bool:
2020-03-18 21:35:57 +00:00
2021-02-11 01:59:52 +00:00
predicates = self._GetPredicatesFromTerms( self._selected_terms )
if len( predicates ) > 0:
2020-03-18 21:35:57 +00:00
2020-11-18 22:15:21 +00:00
if shift_down:
2021-02-11 01:59:52 +00:00
self._EditPredicates( set( predicates ) )
2020-11-18 22:15:21 +00:00
else:
2021-02-11 01:59:52 +00:00
self._EnterPredicates( set( predicates ) )
2020-11-18 22:15:21 +00:00
return True
2020-03-18 21:35:57 +00:00
2020-11-18 22:15:21 +00:00
return False
2020-03-18 21:35:57 +00:00
2020-11-25 22:22:47 +00:00
def _AddEditMenu( self, menu: QW.QMenu ):
2021-02-11 01:59:52 +00:00
( editable_predicates, non_editable_predicates ) = ClientGUISearch.GetEditablePredicates( self._GetPredicatesFromTerms( self._selected_terms ) )
2020-11-25 22:22:47 +00:00
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 )
2020-04-29 21:44:12 +00:00
def _CanProvideCurrentPagePredicates( self ):
return True
2020-03-18 21:35:57 +00:00
def _DeleteActivate( self ):
2020-11-18 22:15:21 +00:00
shift_down = False
self._Activate( shift_down )
def _EditPredicates( self, predicates ):
original_predicates = set( predicates )
try:
edited_predicates = set( ClientGUISearch.EditPredicates( self, predicates ) )
except HydrusExceptions.CancelledException:
return
non_edited_predicates = original_predicates.intersection( edited_predicates )
predicates_to_add = edited_predicates.difference( non_edited_predicates )
predicates_to_remove = original_predicates.difference( non_edited_predicates )
if len( predicates_to_add ) + len( predicates_to_remove ) == 0:
return
2021-02-11 01:59:52 +00:00
terms_to_remove = [ self._GenerateTermFromPredicate( predicate ) for predicate in predicates_to_remove ]
2020-11-18 22:15:21 +00:00
2021-02-11 01:59:52 +00:00
self._RemoveTerms( terms_to_remove )
terms_to_add = [ self._GenerateTermFromPredicate( predicate ) for predicate in predicates_to_add ]
self._AppendTerms( terms_to_add )
2020-11-18 22:15:21 +00:00
2021-02-11 01:59:52 +00:00
self._selected_terms.update( terms_to_add )
2020-11-25 22:22:47 +00:00
2021-02-11 01:59:52 +00:00
self._Sort()
2020-11-18 22:15:21 +00:00
self._DataHasChanged()
2020-03-18 21:35:57 +00:00
def _EnterPredicates( self, predicates, permit_add = True, permit_remove = True ):
if len( predicates ) == 0:
return
2021-02-11 01:59:52 +00:00
terms_to_be_added = set()
terms_to_be_removed = set()
2020-03-18 21:35:57 +00:00
for predicate in predicates:
predicate = predicate.GetCountlessCopy()
2021-02-11 01:59:52 +00:00
term = self._GenerateTermFromPredicate( predicate )
if term in self._terms_to_logical_indices:
2020-03-18 21:35:57 +00:00
if permit_remove:
2021-02-11 01:59:52 +00:00
terms_to_be_removed.add( term )
2020-03-18 21:35:57 +00:00
else:
if permit_add:
2021-02-11 01:59:52 +00:00
terms_to_be_added.add( term )
2020-03-18 21:35:57 +00:00
2021-02-24 22:35:18 +00:00
m_e_preds = self._GetMutuallyExclusivePredicates( predicate )
terms_to_be_removed.update( ( self._GenerateTermFromPredicate( pred ) for pred in m_e_preds ) )
2020-03-18 21:35:57 +00:00
2021-02-11 01:59:52 +00:00
self._AppendTerms( terms_to_be_added )
2020-03-18 21:35:57 +00:00
2021-02-11 01:59:52 +00:00
self._RemoveTerms( terms_to_be_removed )
2020-03-18 21:35:57 +00:00
2021-02-11 01:59:52 +00:00
self._Sort()
2020-03-18 21:35:57 +00:00
self._DataHasChanged()
def _GetCurrentFileServiceKey( self ):
return self._my_ac_parent.GetFileSearchContext().GetFileServiceKey()
def _GetCurrentPagePredicates( self ) -> typing.Set[ ClientSearch.Predicate ]:
return self.GetPredicates()
2020-07-29 20:52:44 +00:00
def _HasCounts( self ):
return False
2020-03-18 21:35:57 +00:00
def _ProcessMenuPredicateEvent( self, command ):
2020-11-25 22:22:47 +00:00
( predicates, or_predicate, inverse_predicates ) = self._GetSelectedPredicatesAndInverseCopies()
2020-03-18 21:35:57 +00:00
2020-04-29 21:44:12 +00:00
if command == 'add_predicates':
2020-03-18 21:35:57 +00:00
2020-04-29 21:44:12 +00:00
self._EnterPredicates( predicates, permit_remove = False )
2020-03-18 21:35:57 +00:00
2020-11-25 22:22:47 +00:00
elif command == 'add_or_predicate':
if or_predicate is not None:
self._EnterPredicates( ( or_predicate, ), permit_remove = False )
2020-04-29 21:44:12 +00:00
elif command == 'remove_predicates':
2020-03-18 21:35:57 +00:00
2020-04-29 21:44:12 +00:00
self._EnterPredicates( predicates, permit_add = False )
2020-03-18 21:35:57 +00:00
2020-04-29 21:44:12 +00:00
elif command == 'add_inverse_predicates':
2020-03-18 21:35:57 +00:00
2020-04-29 21:44:12 +00:00
self._EnterPredicates( inverse_predicates, permit_remove = False )
2020-03-18 21:35:57 +00:00
2020-04-29 21:44:12 +00:00
elif command == 'remove_inverse_predicates':
2020-03-18 21:35:57 +00:00
2020-04-29 21:44:12 +00:00
self._EnterPredicates( inverse_predicates, permit_add = False )
2020-03-18 21:35:57 +00:00
def EnterPredicates( self, page_key, predicates, permit_add = True, permit_remove = True ):
if page_key == self._page_key:
self._EnterPredicates( predicates, permit_add = permit_add, permit_remove = permit_remove )
def SetPredicates( self, predicates ):
self._Clear()
2021-02-11 01:59:52 +00:00
terms = [ self._GenerateTermFromPredicate( predicate ) for predicate in predicates ]
self._AppendTerms( terms )
2020-03-18 21:35:57 +00:00
2021-02-11 01:59:52 +00:00
self._Sort()
2020-11-25 22:22:47 +00:00
2020-03-18 21:35:57 +00:00
self._DataHasChanged()
2016-09-21 19:54:04 +00:00
class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
2021-02-24 22:35:18 +00:00
def __init__( self, parent, chosen_tag_callable, file_service_key, tag_service_key, null_entry_callable = None, tag_service_key_changed_callable = None, show_paste_button = False ):
2016-09-21 19:54:04 +00:00
2020-06-11 12:01:08 +00:00
self._display_tag_service_key = tag_service_key
2016-09-21 19:54:04 +00:00
self._chosen_tag_callable = chosen_tag_callable
self._null_entry_callable = null_entry_callable
2018-11-21 22:22:36 +00:00
self._tag_service_key_changed_callable = tag_service_key_changed_callable
2016-09-21 19:54:04 +00:00
2019-09-18 22:40:39 +00:00
service = HG.client_controller.services_manager.GetService( tag_service_key )
2016-09-21 19:54:04 +00:00
2020-05-20 21:36:02 +00:00
tag_autocomplete_options = HG.client_controller.tag_display_manager.GetTagAutocompleteOptions( tag_service_key )
( file_service_key, tag_service_key ) = tag_autocomplete_options.GetWriteAutocompleteServiceKeys( file_service_key )
2017-05-10 21:33:58 +00:00
2016-09-21 19:54:04 +00:00
AutoCompleteDropdownTags.__init__( self, parent, file_service_key, tag_service_key )
2020-03-11 21:52:11 +00:00
self._paste_button = ClientGUICommon.BetterBitmapButton( self._text_input_panel, CC.global_pixmaps().paste, self._Paste )
2019-11-14 03:56:30 +00:00
self._paste_button.setToolTip( 'Paste from the clipboard and quick-enter as if you had typed. This can take multiple newline-separated tags.' )
2019-06-26 21:27:18 +00:00
if not show_paste_button:
2019-11-14 03:56:30 +00:00
self._paste_button.hide()
2019-06-26 21:27:18 +00:00
2020-07-29 20:52:44 +00:00
QP.AddToLayout( self._text_input_hbox, self._paste_button, CC.FLAGS_CENTER_PERPENDICULAR )
2019-06-26 21:27:18 +00:00
2019-11-14 03:56:30 +00:00
vbox = QP.VBoxLayout()
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
hbox = QP.HBoxLayout()
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
QP.AddToLayout( hbox, self._file_repo_button, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, self._tag_repo_button, CC.FLAGS_EXPAND_BOTH_WAYS )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
QP.AddToLayout( vbox, hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
QP.AddToLayout( vbox, self._dropdown_notebook, CC.FLAGS_EXPAND_BOTH_WAYS )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
self._dropdown_window.setLayout( vbox )
2016-09-21 19:54:04 +00:00
2019-04-03 22:45:57 +00:00
def _BroadcastChoices( self, predicates, shift_down ):
2016-09-21 19:54:04 +00:00
2020-05-20 21:36:02 +00:00
tags = { predicate.GetValue() for predicate in predicates }
2016-09-21 19:54:04 +00:00
if len( tags ) > 0:
self._chosen_tag_callable( tags )
2018-05-23 21:05:06 +00:00
self._ClearInput()
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
def _ChangeTagService( self, tag_service_key ):
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
AutoCompleteDropdownTags._ChangeTagService( self, tag_service_key )
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
if self._tag_service_key_changed_callable is not None:
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
self._tag_service_key_changed_callable( tag_service_key )
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
def _GetCurrentBroadcastTextPredicate( self ) -> typing.Optional[ ClientSearch.Predicate ]:
2018-11-21 22:22:36 +00:00
2020-04-22 21:00:35 +00:00
parsed_autocomplete_text = self._GetParsedAutocompleteText()
2018-11-21 22:22:36 +00:00
2020-04-22 21:00:35 +00:00
if parsed_autocomplete_text.IsTagSearch():
2018-11-21 22:22:36 +00:00
2020-04-22 21:00:35 +00:00
return parsed_autocomplete_text.GetImmediateFileSearchPredicate()
else:
return None
2018-11-21 22:22:36 +00:00
2020-04-22 21:00:35 +00:00
def _GetParsedAutocompleteText( self ) -> ClientSearch.ParsedAutocompleteText:
parsed_autocomplete_text = AutoCompleteDropdownTags._GetParsedAutocompleteText( self )
parsed_autocomplete_text.SetInclusive( True )
return parsed_autocomplete_text
2018-03-28 21:55:58 +00:00
def _InitFavouritesList( self ):
height_num_chars = HG.client_controller.new_options.GetInteger( 'ac_write_list_height_num_chars' )
2021-02-24 22:35:18 +00:00
favs_list = ListBoxTagsStringsAC( self._dropdown_notebook, self.BroadcastChoices, self._display_tag_service_key, self._float_mode, tag_display_type = ClientTags.TAG_DISPLAY_STORAGE, height_num_chars = height_num_chars )
favs_list.SetChildRowsAllowed( HG.client_controller.new_options.GetBoolean( 'expand_parents_on_storage_autocomplete_taglists' ) )
2018-03-28 21:55:58 +00:00
return favs_list
def _InitSearchResultsList( self ):
2016-09-21 19:54:04 +00:00
height_num_chars = HG.client_controller.new_options.GetInteger( 'ac_write_list_height_num_chars' )
2019-11-14 03:56:30 +00:00
2021-02-24 22:35:18 +00:00
preds_list = ListBoxTagsPredicatesAC( self._dropdown_notebook, self.BroadcastChoices, self._display_tag_service_key, self._float_mode, tag_display_type = ClientTags.TAG_DISPLAY_STORAGE, height_num_chars = height_num_chars )
preds_list.SetChildRowsAllowed( HG.client_controller.new_options.GetBoolean( 'expand_parents_on_storage_autocomplete_taglists' ) )
return preds_list
2016-09-21 19:54:04 +00:00
2019-06-26 21:27:18 +00:00
def _Paste( self ):
try:
raw_text = HG.client_controller.GetClipboardText()
except HydrusExceptions.DataMissing as e:
2019-11-14 03:56:30 +00:00
QW.QMessageBox.critical( self, 'Error', str(e) )
2019-06-26 21:27:18 +00:00
return
try:
tags = [ text for text in HydrusText.DeserialiseNewlinedTexts( raw_text ) ]
tags = HydrusTags.CleanTags( tags )
2020-03-11 21:52:11 +00:00
entry_predicates = [ ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_TAG, tag ) for tag in tags ]
2019-06-26 21:27:18 +00:00
if len( entry_predicates ) > 0:
shift_down = False
self._BroadcastChoices( entry_predicates, shift_down )
except:
2019-11-14 03:56:30 +00:00
QW.QMessageBox.critical( self, 'Error', 'I could not understand what was in the clipboard' )
2019-07-17 22:10:19 +00:00
2019-06-26 21:27:18 +00:00
raise
2019-07-17 22:10:19 +00:00
2019-06-26 21:27:18 +00:00
2019-01-30 22:14:54 +00:00
def _ShouldTakeResponsibilityForEnter( self ):
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
parsed_autocomplete_text = self._GetParsedAutocompleteText()
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
looking_at_search_results = self._dropdown_notebook.currentWidget() == self._search_results_list
2019-08-07 22:59:53 +00:00
2020-04-22 21:00:35 +00:00
sitting_on_empty = parsed_autocomplete_text.IsEmpty()
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
something_to_broadcast = self._GetCurrentBroadcastTextPredicate() is not None
2019-08-07 22:59:53 +00:00
2019-08-15 00:40:48 +00:00
# the list has results, but they are out of sync with what we have currently entered
2016-09-21 19:54:04 +00:00
# when the user has quickly typed something in and the results are not yet in
2020-04-22 21:00:35 +00:00
results_desynced_with_text = parsed_autocomplete_text != self._current_list_parsed_autocomplete_text
2016-09-21 19:54:04 +00:00
2019-08-15 00:40:48 +00:00
p1 = something_to_broadcast and results_desynced_with_text
2016-09-21 19:54:04 +00:00
2019-08-15 00:40:48 +00:00
# when the text ctrl is empty and we want to push a None to the parent dialog
p2 = sitting_on_empty
2016-09-21 19:54:04 +00:00
2019-08-15 00:40:48 +00:00
return looking_at_search_results and ( p1 or p2 )
2016-09-21 19:54:04 +00:00
2020-04-01 21:51:42 +00:00
def _StartSearchResultsFetchJob( self, job_key ):
2019-01-30 22:14:54 +00:00
2020-04-22 21:00:35 +00:00
parsed_autocomplete_text = self._GetParsedAutocompleteText()
2019-01-30 22:14:54 +00:00
2019-07-17 22:10:19 +00:00
stub_predicates = []
2020-06-11 12:01:08 +00:00
InsertTagPredicates( stub_predicates, self._display_tag_service_key, parsed_autocomplete_text )
2020-04-22 21:00:35 +00:00
2019-07-17 22:10:19 +00:00
AppendLoadingPredicate( stub_predicates )
2020-04-22 21:00:35 +00:00
HG.client_controller.CallLaterQtSafe( self, 0.2, self.SetStubPredicates, job_key, stub_predicates, parsed_autocomplete_text )
2019-07-17 22:10:19 +00:00
2020-08-27 01:00:42 +00:00
tag_search_context = ClientSearch.TagSearchContext( service_key = self._tag_service_key, display_service_key = self._display_tag_service_key )
2021-02-24 22:35:18 +00:00
HG.client_controller.CallToThread( WriteFetch, self, job_key, self.SetFetchedResults, parsed_autocomplete_text, tag_search_context, self._file_service_key, self._results_cache )
2019-01-30 22:14:54 +00:00
2019-04-03 22:45:57 +00:00
def _TakeResponsibilityForEnter( self, shift_down ):
2016-09-21 19:54:04 +00:00
2020-04-22 21:00:35 +00:00
parsed_autocomplete_text = self._GetParsedAutocompleteText()
if parsed_autocomplete_text.IsEmpty() and self._dropdown_notebook.currentWidget() == self._search_results_list:
2016-09-21 19:54:04 +00:00
if self._null_entry_callable is not None:
self._null_entry_callable()
else:
2020-04-22 21:00:35 +00:00
current_broadcast_predicate = self._GetCurrentBroadcastTextPredicate()
if current_broadcast_predicate is not None:
self._BroadcastChoices( { current_broadcast_predicate }, shift_down )
2016-09-21 19:54:04 +00:00
2016-12-21 22:30:54 +00:00
2018-04-05 01:22:26 +00:00
def RefreshFavouriteTags( self ):
2020-05-13 19:03:16 +00:00
favourite_tags = sorted( HG.client_controller.new_options.GetStringList( 'favourite_tags' ) )
2018-04-05 01:22:26 +00:00
2021-02-24 22:35:18 +00:00
self._favourites_list.SetTags( favourite_tags )
2018-04-05 01:22:26 +00:00
2020-09-09 20:59:19 +00:00
def SetDisplayTagServiceKey( self, service_key ):
self._display_tag_service_key = service_key
2020-02-12 22:50:37 +00:00
class EditAdvancedORPredicates( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, initial_string = None ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._input_text = QW.QLineEdit( self )
self._result_preview = QW.QPlainTextEdit()
self._result_preview.setReadOnly( True )
( width, height ) = ClientGUIFunctions.ConvertTextToPixels( self._result_preview, ( 64, 6 ) )
self._result_preview.setMinimumWidth( width )
self._result_preview.setMinimumHeight( height )
self._current_predicates = []
#
if initial_string is not None:
self._input_text.setText( initial_string )
#
rows = []
rows.append( ( 'Input: ', self._input_text ) )
rows.append( ( 'Result preview: ', self._result_preview ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
vbox = QP.VBoxLayout()
summary = 'Enter a complicated tag search here as text, such as \'( blue eyes and blonde hair ) or ( green eyes and red hair )\', and this should turn it into hydrus-compatible search predicates.'
summary += os.linesep * 2
summary += 'Accepted operators: not (!, -), and (&&), or (||), implies (=>), xor, xnor (iff, <=>), nand, nor.'
summary += os.linesep * 2
2020-04-29 21:44:12 +00:00
summary += 'Parentheses work the usual way. \\ can be used to escape characters (e.g. to search for tags including parentheses)'
2020-02-12 22:50:37 +00:00
st = ClientGUICommon.BetterStaticText( self, summary )
st.setWordWrap( True )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self.widget().setLayout( vbox )
self._UpdateText()
self._input_text.textChanged.connect( self.EventUpdateText )
def _UpdateText( self ):
text = self._input_text.text()
self._current_predicates = []
colour = ( 0, 0, 0 )
output = ''
if len( text ) > 0:
try:
# this makes a list of sets, each set representing a list of AND preds
result = LogicExpressionQueryParser.parse_logic_expression_query( text )
for s in result:
row_preds = []
for tag_string in s:
if tag_string.startswith( '-' ):
inclusive = False
tag_string = tag_string[1:]
else:
inclusive = True
if '*' in tag_string:
( namespace, subtag ) = HydrusTags.SplitTag( tag_string )
2020-07-22 20:59:16 +00:00
if len( namespace ) > 0 and subtag == '*':
2020-02-12 22:50:37 +00:00
2020-03-11 21:52:11 +00:00
row_pred = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_NAMESPACE, namespace, inclusive )
2020-02-12 22:50:37 +00:00
else:
2020-03-11 21:52:11 +00:00
row_pred = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_WILDCARD, tag_string, inclusive )
2020-02-12 22:50:37 +00:00
else:
2020-03-11 21:52:11 +00:00
row_pred = ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_TAG, tag_string, inclusive )
2020-02-12 22:50:37 +00:00
row_preds.append( row_pred )
if len( row_preds ) == 1:
self._current_predicates.append( row_preds[0] )
else:
2020-03-11 21:52:11 +00:00
self._current_predicates.append( ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_OR_CONTAINER, row_preds ) )
2020-02-12 22:50:37 +00:00
output = os.linesep.join( ( pred.ToString() for pred in self._current_predicates ) )
colour = ( 0, 128, 0 )
except ValueError:
output = 'Could not parse!'
colour = ( 128, 0, 0 )
self._result_preview.setPlainText( output )
QP.SetForegroundColour( self._result_preview, colour )
def EventUpdateText( self, text ):
self._UpdateText()
def GetValue( self ):
self._UpdateText()
if len( self._current_predicates ) == 0:
raise HydrusExceptions.VetoException( 'Please enter a string that parses into a set of search rules.' )
return self._current_predicates