2019-01-09 22:59:03 +00:00
from . import ClientConstants as CC
from . import ClientData
from . import ClientGUICommon
2020-03-04 22:12:53 +00:00
from . import ClientGUICore as CGC
2019-06-26 21:27:18 +00:00
from . import ClientGUIFunctions
2019-01-09 22:59:03 +00:00
from . import ClientGUIListBoxes
from . import ClientGUIMenus
from . import ClientGUIShortcuts
2020-03-11 21:52:11 +00:00
from . import ClientGUISearch
2019-01-09 22:59:03 +00:00
from . import ClientSearch
2019-10-02 23:38:59 +00:00
from . import ClientTags
2019-01-30 22:14:54 +00:00
from . import ClientThreading
2020-02-12 22:50:37 +00:00
from . import ClientGUIScrolledPanels
2019-08-15 00:40:48 +00:00
from . import ClientGUITopLevelWindows
2016-09-21 19:54:04 +00:00
import collections
2019-01-09 22:59:03 +00:00
from . import HydrusConstants as HC
from . import HydrusData
from . import HydrusExceptions
from . import HydrusGlobals as HG
from . import HydrusTags
2019-06-26 21:27:18 +00:00
from . import HydrusText
2016-09-21 19:54:04 +00:00
import itertools
2020-02-12 22:50:37 +00:00
from . import LogicExpressionQueryParser
import os
2020-03-11 21:52:11 +00:00
import typing
2019-11-14 03:56:30 +00:00
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from . import QtPorting as QP
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
def CacheCanBeUsedForInput ( search_text_for_cache , new_search_text ) :
if search_text_for_cache is None :
return False
if new_search_text is None :
return False
namespace_cache = ' : ' in search_text_for_cache
namespace_search = ' : ' in new_search_text
if ( namespace_cache and namespace_search ) or ( not namespace_cache and not namespace_search ) :
if new_search_text . startswith ( search_text_for_cache ) :
return True
return False
def InsertStaticPredicatesForRead ( predicates , parsed_search_text , include_unusual_predicate_types , under_construction_or_predicate ) :
2020-03-04 22:12:53 +00:00
( raw_entry , inclusive , entry_text , wildcard_text , search_text , explicit_wildcard , cache_text , entry_predicate ) = parsed_search_text
2019-07-17 22:10:19 +00:00
2020-03-25 21:15:57 +00:00
if ClientSearch . IsUnacceptableTagSearch ( search_text ) :
2019-07-17 22:10:19 +00:00
pass
else :
if include_unusual_predicate_types :
2020-03-18 21:35:57 +00:00
( namespace , subtag ) = HydrusTags . SplitTag ( search_text )
if namespace != ' ' and subtag in ( ' ' , ' * ' ) :
2020-03-25 21:15:57 +00:00
( unprocessed_namespace , unprocessed_subtag ) = HydrusTags . SplitTag ( entry_text )
predicates . insert ( 0 , ClientSearch . Predicate ( ClientSearch . PREDICATE_TYPE_NAMESPACE , unprocessed_namespace , inclusive ) )
2020-03-18 21:35:57 +00:00
elif explicit_wildcard :
2019-07-17 22:10:19 +00:00
2019-07-24 21:39:02 +00:00
if wildcard_text != search_text :
2020-03-11 21:52:11 +00:00
predicates . insert ( 0 , ClientSearch . Predicate ( ClientSearch . PREDICATE_TYPE_WILDCARD , search_text , inclusive ) )
2019-07-24 21:39:02 +00:00
2020-03-11 21:52:11 +00:00
predicates . insert ( 0 , ClientSearch . Predicate ( ClientSearch . PREDICATE_TYPE_WILDCARD , wildcard_text , inclusive ) )
2019-07-17 22:10:19 +00:00
try :
index = predicates . index ( entry_predicate )
predicate = predicates [ index ]
del predicates [ index ]
predicates . insert ( 0 , predicate )
except :
pass
2019-09-05 00:05:32 +00:00
if under_construction_or_predicate is not None :
predicates . insert ( 0 , under_construction_or_predicate )
2019-07-17 22:10:19 +00:00
return predicates
def InsertStaticPredicatesForWrite ( predicates , parsed_search_text , tag_service_key , expand_parents ) :
( raw_entry , search_text , cache_text , entry_predicate , sibling_predicate ) = parsed_search_text
2019-08-15 00:40:48 +00:00
( namespace , subtag ) = HydrusTags . SplitTag ( search_text )
2020-03-25 21:15:57 +00:00
if ClientSearch . IsUnacceptableTagSearch ( search_text ) or subtag == ' ' :
2019-07-17 22:10:19 +00:00
pass
else :
PutAtTopOfMatches ( predicates , entry_predicate )
if sibling_predicate is not None :
PutAtTopOfMatches ( predicates , sibling_predicate )
if expand_parents :
predicates = HG . client_controller . tag_parents_manager . ExpandPredicates ( tag_service_key , predicates )
return predicates
2020-03-11 21:52:11 +00:00
def ReadFetch ( win , job_key , results_callable , parsed_search_text , qt_media_callable , file_search_context : ClientSearch . FileSearchContext , synchronised , include_unusual_predicate_types , initial_matches_fetched , search_text_for_current_cache , cached_results , under_construction_or_predicate , force_system_everything ) :
2019-01-30 22:14:54 +00:00
next_search_is_probably_fast = False
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
include_current_tags = tag_search_context . include_current_tags
include_pending_tags = tag_search_context . include_pending_tags
2019-01-30 22:14:54 +00:00
2020-03-04 22:12:53 +00:00
( raw_entry , inclusive , entry_text , wildcard_text , search_text , explicit_wildcard , cache_text , entry_predicate ) = parsed_search_text
2019-01-30 22:14:54 +00:00
2020-03-25 21:15:57 +00:00
if ClientSearch . IsUnacceptableTagSearch ( search_text ) :
2019-01-30 22:14:54 +00:00
# if the user inputs '-' or similar, let's go to an empty list
if raw_entry == ' ' :
input_just_changed = search_text_for_current_cache is not None
2019-08-15 00:40:48 +00:00
definitely_do_it = input_just_changed or not initial_matches_fetched
2019-01-30 22:14:54 +00:00
db_not_going_to_hang_if_we_hit_it = not HG . client_controller . DBCurrentlyDoingJob ( )
2019-08-15 00:40:48 +00:00
if definitely_do_it 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
search_text_for_current_cache = None
2020-03-04 22:12:53 +00:00
cached_results = HG . client_controller . Read ( ' file_system_predicates ' , search_service_key , force_system_everything = force_system_everything )
2019-01-30 22:14:54 +00:00
2019-08-15 00:40:48 +00:00
matches = cached_results
elif ( search_text_for_current_cache is None or search_text_for_current_cache == ' ' ) and cached_results is not None : # if repeating query but busy, use same
matches = cached_results
else :
matches = [ ]
2019-01-30 22:14:54 +00:00
else :
matches = [ ]
else :
( namespace , half_complete_subtag ) = HydrusTags . SplitTag ( search_text )
2019-08-15 00:40:48 +00:00
if half_complete_subtag == ' ' :
2019-01-30 22:14:54 +00:00
search_text_for_current_cache = None
matches = [ ] # a query like 'namespace:'
else :
fetch_from_db = True
2019-11-14 03:56:30 +00:00
if synchronised and qt_media_callable is not None :
2019-01-30 22:14:54 +00:00
try :
2019-11-14 03:56:30 +00:00
media = HG . client_controller . CallBlockingToQt ( win , qt_media_callable )
2019-01-30 22:14:54 +00:00
2019-11-14 03:56:30 +00:00
except HydrusExceptions . QtDeadWindowException :
2019-01-30 22:14:54 +00:00
return
2019-07-24 21:39:02 +00:00
if job_key . IsCancelled ( ) :
return
2019-01-30 22:14:54 +00:00
2019-07-24 21:39:02 +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
fetch_from_db = False
if fetch_from_db :
# 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 namespace
2019-07-17 22:10:19 +00:00
small_exact_match_search = ShouldDoExactSearch ( cache_text )
2019-01-30 22:14:54 +00:00
2019-07-17 22:10:19 +00:00
if small_exact_match_search :
2019-01-30 22:14:54 +00:00
2020-03-11 21:52:11 +00:00
predicates = HG . client_controller . Read ( ' autocomplete_predicates ' , file_service_key = file_service_key , tag_search_context = tag_search_context , search_text = cache_text , exact_match = True , inclusive = inclusive , add_namespaceless = add_namespaceless , job_key = job_key , collapse_siblings = True )
2019-01-30 22:14:54 +00:00
else :
2019-07-17 22:10:19 +00:00
cache_valid = CacheCanBeUsedForInput ( search_text_for_current_cache , cache_text )
2019-01-30 22:14:54 +00:00
2019-07-17 22:10:19 +00:00
if not cache_valid :
2019-01-30 22:14:54 +00:00
new_search_text_for_current_cache = cache_text
2020-03-11 21:52:11 +00:00
cached_results = HG . client_controller . Read ( ' autocomplete_predicates ' , file_service_key = file_service_key , tag_search_context = tag_search_context , search_text = search_text , inclusive = inclusive , add_namespaceless = add_namespaceless , job_key = job_key , collapse_siblings = True )
2019-01-30 22:14:54 +00:00
predicates = cached_results
next_search_is_probably_fast = True
2019-07-24 21:39:02 +00:00
if job_key . IsCancelled ( ) :
return
matches = ClientSearch . FilterPredicatesBySearchText ( tag_service_key , search_text , predicates )
2019-01-30 22:14:54 +00:00
else :
# it is possible that media will change between calls to this, so don't cache it
# it's also quick as hell, so who cares
tags_managers = [ ]
for m in media :
if m . IsCollection ( ) :
tags_managers . extend ( m . GetSingletonsTagsManagers ( ) )
else :
tags_managers . append ( m . GetTagsManager ( ) )
2019-07-24 21:39:02 +00:00
if job_key . IsCancelled ( ) :
return
2019-01-30 22:14:54 +00:00
current_tags_to_count = collections . Counter ( )
pending_tags_to_count = collections . Counter ( )
2019-07-24 21:39:02 +00:00
for group_of_tags_managers in HydrusData . SplitListIntoChunks ( tags_managers , 1000 ) :
2019-01-30 22:14:54 +00:00
2020-03-11 21:52:11 +00:00
if include_current_tags :
2019-07-24 21:39:02 +00:00
2019-10-02 23:38:59 +00:00
current_tags_to_count . update ( itertools . chain . from_iterable ( tags_manager . GetCurrent ( tag_service_key , ClientTags . TAG_DISPLAY_SIBLINGS_AND_PARENTS ) for tags_manager in group_of_tags_managers ) )
2019-07-24 21:39:02 +00:00
2019-01-30 22:14:54 +00:00
2020-03-11 21:52:11 +00:00
if include_pending_tags :
2019-07-24 21:39:02 +00:00
2019-10-02 23:38:59 +00:00
pending_tags_to_count . update ( itertools . chain . from_iterable ( [ tags_manager . GetPending ( tag_service_key , ClientTags . TAG_DISPLAY_SIBLINGS_AND_PARENTS ) for tags_manager in group_of_tags_managers ] ) )
2019-07-24 21:39:02 +00:00
2019-01-30 22:14:54 +00:00
2019-07-24 21:39:02 +00:00
if job_key . IsCancelled ( ) :
return
2019-01-30 22:14:54 +00:00
2019-07-24 21:39:02 +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_do = ClientSearch . FilterTagsBySearchText ( tag_service_key , search_text , tags_to_do )
if job_key . IsCancelled ( ) :
2019-01-30 22:14:54 +00:00
2019-07-24 21:39:02 +00:00
return
2019-01-30 22:14:54 +00:00
2020-03-11 21:52:11 +00:00
predicates = [ ClientSearch . Predicate ( ClientSearch . PREDICATE_TYPE_TAG , tag , inclusive , current_tags_to_count [ tag ] , pending_tags_to_count [ tag ] ) for tag in tags_to_do ]
2019-01-30 22:14:54 +00:00
2019-07-24 21:39:02 +00:00
if job_key . IsCancelled ( ) :
return
2019-01-30 22:14:54 +00:00
if namespace == ' ' :
predicates = ClientData . MergePredicates ( predicates , add_namespaceless = True )
next_search_is_probably_fast = True
2019-07-24 21:39:02 +00:00
matches = predicates
2019-01-30 22:14:54 +00:00
matches = ClientSearch . SortPredicates ( matches )
for match in matches :
if match . GetInclusive ( ) != inclusive :
match . SetInclusive ( inclusive )
2019-07-17 22:10:19 +00:00
matches = InsertStaticPredicatesForRead ( matches , parsed_search_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
2019-11-14 03:56:30 +00:00
HG . client_controller . CallLaterQtSafe ( win , 0.0 , results_callable , job_key , search_text , search_text_for_current_cache , cached_results , matches , next_search_is_probably_fast )
2019-01-30 22:14:54 +00:00
def PutAtTopOfMatches ( matches , predicate ) :
try :
index = matches . index ( predicate )
predicate = matches [ index ]
matches . remove ( predicate )
except ValueError :
pass
matches . insert ( 0 , predicate )
2019-07-17 22:10:19 +00:00
def ShouldDoExactSearch ( cache_text ) :
if cache_text is None :
return False
autocomplete_exact_match_threshold = HG . client_controller . new_options . GetNoneableInteger ( ' autocomplete_exact_match_threshold ' )
if autocomplete_exact_match_threshold is None :
return False
if ' : ' in cache_text :
( namespace , test_text ) = HydrusTags . SplitTag ( cache_text )
else :
test_text = cache_text
return len ( test_text ) < = autocomplete_exact_match_threshold
2019-01-30 22:14:54 +00:00
def WriteFetch ( win , job_key , results_callable , parsed_search_text , file_service_key , tag_service_key , expand_parents , search_text_for_current_cache , cached_results ) :
next_search_is_probably_fast = False
2020-03-11 21:52:11 +00:00
tag_search_context = ClientSearch . TagSearchContext ( service_key = tag_service_key )
2019-01-30 22:14:54 +00:00
( raw_entry , search_text , cache_text , entry_predicate , sibling_predicate ) = parsed_search_text
2020-03-25 21:15:57 +00:00
if ClientSearch . IsUnacceptableTagSearch ( search_text ) :
2019-01-30 22:14:54 +00:00
search_text_for_current_cache = None
matches = [ ]
else :
2019-08-15 00:40:48 +00:00
( namespace , subtag ) = HydrusTags . SplitTag ( search_text )
2019-01-30 22:14:54 +00:00
2019-08-15 00:40:48 +00:00
if subtag == ' ' :
search_text_for_current_cache = None
2019-01-30 22:14:54 +00:00
2019-08-15 00:40:48 +00:00
matches = [ ] # a query like 'namespace:'
2019-01-30 22:14:54 +00:00
else :
2019-08-15 00:40:48 +00:00
must_do_a_search = False
2019-01-30 22:14:54 +00:00
2019-08-15 00:40:48 +00:00
small_exact_match_search = ShouldDoExactSearch ( cache_text )
if small_exact_match_search :
2019-01-30 22:14:54 +00:00
2020-03-11 21:52:11 +00:00
predicates = HG . client_controller . Read ( ' autocomplete_predicates ' , file_service_key = file_service_key , tag_search_context = tag_search_context , search_text = cache_text , exact_match = True , add_namespaceless = False , job_key = job_key , collapse_siblings = False )
2019-01-30 22:14:54 +00:00
2019-08-15 00:40:48 +00:00
else :
cache_valid = CacheCanBeUsedForInput ( search_text_for_current_cache , cache_text )
if must_do_a_search or not cache_valid :
search_text_for_current_cache = cache_text
2020-03-11 21:52:11 +00:00
cached_results = HG . client_controller . Read ( ' autocomplete_predicates ' , file_service_key = file_service_key , tag_search_context = tag_search_context , search_text = search_text , add_namespaceless = False , job_key = job_key , collapse_siblings = False )
2019-08-15 00:40:48 +00:00
predicates = cached_results
next_search_is_probably_fast = True
2019-01-30 22:14:54 +00:00
2019-08-15 00:40:48 +00:00
matches = ClientSearch . FilterPredicatesBySearchText ( tag_service_key , search_text , predicates )
2019-01-30 22:14:54 +00:00
2019-08-15 00:40:48 +00:00
matches = ClientSearch . SortPredicates ( matches )
2019-01-30 22:14:54 +00:00
2019-07-17 22:10:19 +00:00
matches = InsertStaticPredicatesForWrite ( matches , parsed_search_text , tag_service_key , expand_parents )
2019-01-30 22:14:54 +00:00
2019-11-14 03:56:30 +00:00
HG . client_controller . CallLaterQtSafe ( win , 0.0 , results_callable , job_key , search_text , search_text_for_current_cache , cached_results , matches , next_search_is_probably_fast )
2019-01-30 22:14:54 +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
self . _intercept_key_events = True
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
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
2019-11-14 03:56:30 +00:00
QP . AddToLayout ( self . _text_input_hbox , self . _text_ctrl , CC . FLAGS_VCENTER_EXPAND_DEPTH_ONLY )
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
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
#
2019-11-14 03:56:30 +00:00
self . _list_height_num_chars = 8
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
2019-08-15 00:40:48 +00:00
self . _current_list_raw_entry = ' '
2019-01-30 22:14:54 +00:00
self . _next_search_is_probably_fast = False
self . _search_text_for_current_cache = None
2016-09-21 19:54:04 +00:00
self . _cached_results = [ ]
2019-01-30 22:14:54 +00:00
self . _current_fetch_job_key = None
2016-09-21 19:54:04 +00:00
self . _initial_matches_fetched = False
2019-01-30 22:14:54 +00:00
self . _refresh_list_job = None
2018-01-03 22:37:30 +00:00
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
2018-02-14 21:47:18 +00:00
self . _ScheduleListRefresh ( 0.0 )
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 ( )
2019-04-03 22:45:57 +00:00
def _BroadcastCurrentText ( self , shift_down ) :
2016-09-21 19:54:04 +00:00
2019-07-17 22:10:19 +00:00
raise NotImplementedError ( )
2016-09-21 19:54:04 +00:00
2019-06-19 22:08:48 +00:00
def _CancelCurrentResultsFetchJob ( self ) :
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
2018-02-14 21:47:18 +00:00
def _CancelScheduledListRefresh ( self ) :
if self . _refresh_list_job is not None :
self . _refresh_list_job . Cancel ( )
2018-05-23 21:05:06 +00:00
def _ClearInput ( self ) :
2019-07-17 22:10:19 +00:00
self . _CancelCurrentResultsFetchJob ( )
2019-11-14 03:56:30 +00:00
self . _text_ctrl . setText ( ' ' )
2018-05-23 21:05:06 +00:00
2019-04-10 22:50:53 +00:00
self . _SetResultsToList ( [ ] )
2018-05-23 21:05:06 +00:00
self . _ScheduleListRefresh ( 0.0 )
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 ( )
2018-02-14 21:47:18 +00:00
def _ScheduleListRefresh ( self , delay ) :
if self . _refresh_list_job is not None and delay == 0.0 :
2018-05-16 20:09:50 +00:00
self . _refresh_list_job . Wake ( )
2018-02-14 21:47:18 +00:00
else :
self . _CancelScheduledListRefresh ( )
2019-11-14 03:56:30 +00:00
self . _refresh_list_job = HG . client_controller . CallLaterQtSafe ( self , delay , self . _UpdateSearchResultsList )
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 ) :
2019-01-30 22:14:54 +00:00
self . _search_text_for_current_cache = None
2018-02-14 21:47:18 +00:00
self . _ScheduleListRefresh ( 0.0 )
2019-01-30 22:14:54 +00:00
def _SetResultsToList ( self , results ) :
raise NotImplementedError ( )
2016-09-21 19:54:04 +00:00
def _ShouldShow ( self ) :
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
2019-01-30 22:14:54 +00:00
def _StartResultsFetchJob ( self , job_key ) :
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
if not self . _intercept_key_events :
colour = ClientData . GetLighterDarkerColour ( colour )
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
2018-03-28 21:55:58 +00:00
def _UpdateSearchResultsList ( self ) :
2016-09-21 19:54:04 +00:00
2019-01-30 22:14:54 +00:00
self . _refresh_list_job = None
2019-06-19 22:08:48 +00:00
self . _CancelCurrentResultsFetchJob ( )
2019-01-30 22:14:54 +00:00
self . _current_fetch_job_key = ClientThreading . JobKey ( cancellable = True )
self . _StartResultsFetchJob ( 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 ) :
self . _CancelCurrentResultsFetchJob ( )
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
2019-11-14 03:56:30 +00:00
raw_control_modifier = QC . Qt . ControlModifier
2019-11-20 23:10:46 +00:00
if HC . PLATFORM_MACOS :
2019-11-14 03:56:30 +00:00
raw_control_modifier = QC . Qt . MetaModifier # This way raw_control_modifier always means the Control key, even on Mac. See Qt docs.
if key in ( QC . Qt . Key_Insert , ) :
2016-09-21 19:54:04 +00:00
2017-09-20 19:47:31 +00:00
self . _intercept_key_events = not self . _intercept_key_events
2017-09-13 20:50:41 +00:00
2017-09-20 19:47:31 +00:00
self . _UpdateBackgroundColour ( )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
elif key == QC . Qt . Key_Space and event . modifiers ( ) & raw_control_modifier :
2016-09-21 19:54:04 +00:00
2018-02-14 21:47:18 +00:00
self . _ScheduleListRefresh ( 0.0 )
2016-09-21 19:54:04 +00:00
elif self . _intercept_key_events :
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
current_list_is_empty = len ( current_results_list ) == 0
2019-11-14 03:56:30 +00:00
input_is_empty = self . _text_ctrl . text ( ) == ' '
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
2018-03-28 21:55:58 +00:00
elif input_is_empty : # maybe we should be sending a 'move' event to a different place
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
if key in ( QC . Qt . Key_Up , QC . Qt . Key_Down ) and current_list_is_empty :
2017-09-20 19:47:31 +00:00
2019-11-14 03:56:30 +00:00
if key in ( QC . Qt . Key_Up , ) :
2018-03-28 21:55:58 +00:00
2019-11-14 03:56:30 +00:00
self . selectUp . emit ( )
2018-03-28 21:55:58 +00:00
2019-11-14 03:56:30 +00:00
elif key in ( QC . Qt . Key_Down , ) :
2018-03-28 21:55:58 +00:00
2019-11-14 03:56:30 +00:00
self . selectDown . emit ( )
2018-03-28 21:55:58 +00:00
2017-09-20 19:47:31 +00:00
2019-11-14 03:56:30 +00:00
elif key in ( QC . Qt . Key_PageDown , QC . Qt . Key_PageUp ) and current_list_is_empty :
2017-09-20 19:47:31 +00:00
2019-11-14 03:56:30 +00:00
if key in ( QC . Qt . Key_PageUp , ) :
2018-03-28 21:55:58 +00:00
2019-11-14 03:56:30 +00:00
self . showPrevious . emit ( )
2018-03-28 21:55:58 +00:00
2019-11-14 03:56:30 +00:00
elif key in ( QC . Qt . Key_PageDown , ) :
2018-03-28 21:55:58 +00:00
2019-11-14 03:56:30 +00:00
self . showNext . emit ( )
2018-03-28 21:55:58 +00:00
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
elif key in ( QC . Qt . Key_Right , QC . Qt . Key_Left ) :
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
if key in ( QC . Qt . Key_Left , ) :
2018-03-28 21:55:58 +00:00
direction = - 1
2019-11-14 03:56:30 +00:00
elif key in ( QC . Qt . Key_Right , ) :
2018-03-28 21:55:58 +00:00
direction = 1
self . MoveNotebookPageFocus ( direction = direction )
else :
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 :
2018-02-14 21:47:18 +00:00
self . _ScheduleListRefresh ( 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
2019-07-17 22:10:19 +00:00
if HG . client_controller . new_options . GetBoolean ( ' autocomplete_results_fetch_automatically ' ) :
2018-02-07 23:40:33 +00:00
2019-07-17 22:10:19 +00:00
self . _ScheduleListRefresh ( 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
2019-11-14 03:56:30 +00:00
self . _text_ctrl . setFocus ( QC . Qt . OtherFocusReason )
2018-03-28 21:55:58 +00:00
2019-01-30 22:14:54 +00:00
def SetFetchedResults ( self , job_key , search_text , search_text_for_cache , cached_results , results ) :
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 ( ) :
2019-07-17 22:10:19 +00:00
self . _CancelCurrentResultsFetchJob ( )
2019-01-30 22:14:54 +00:00
self . _search_text_for_current_cache = search_text_for_cache
self . _cached_results = cached_results
self . _initial_matches_fetched = True
self . _SetResultsToList ( results )
2018-03-28 21:55:58 +00:00
2019-11-14 03:56:30 +00:00
def setFocus ( self , focus_reason = QC . Qt . OtherFocusReason ) :
2018-05-16 20:09:50 +00:00
2019-11-20 23:10:46 +00:00
if HC . PLATFORM_MACOS :
2018-05-16 20:09:50 +00:00
2020-03-11 21:52:11 +00:00
HG . client_controller . CallAfterQtSafe ( self . _text_ctrl , self . _text_ctrl . setFocus , focus_reason )
2018-05-16 20:09:50 +00:00
else :
2019-11-14 03:56:30 +00:00
self . _text_ctrl . setFocus ( focus_reason )
2018-05-16 20:09:50 +00:00
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 ) :
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 ' )
2016-09-21 19:54:04 +00:00
def _ChangeFileService ( self , file_service_key ) :
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 ) :
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
2018-03-28 21:55:58 +00:00
self . _search_results_list . SetTagService ( 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
2019-01-30 22:14:54 +00:00
def _InitFavouritesList ( self ) :
2016-09-21 19:54:04 +00:00
2019-01-30 22:14:54 +00:00
raise NotImplementedError ( )
2016-09-21 19:54:04 +00:00
2019-01-30 22:14:54 +00:00
def _SetResultsToList ( self , results ) :
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
self . _current_list_raw_entry = self . _text_ctrl . text ( )
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-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 :
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
2019-01-30 22:14:54 +00:00
def RefreshFavouriteTags ( self ) :
favourite_tags = list ( HG . client_controller . new_options . GetStringList ( ' favourite_tags ' ) )
favourite_tags . sort ( )
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 )
2016-10-26 20:45:34 +00:00
def SetFileService ( self , file_service_key ) :
self . _ChangeFileService ( file_service_key )
2019-07-17 22:10:19 +00:00
def SetStubPredicates ( self , job_key , stub_predicates ) :
if self . _current_fetch_job_key is not None and self . _current_fetch_job_key . GetKey ( ) == job_key . GetKey ( ) :
self . _SetResultsToList ( stub_predicates )
2016-10-26 20:45:34 +00:00
def SetTagService ( self , tag_service_key ) :
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-03-18 21:35:57 +00:00
def __init__ ( self , parent : QW . QWidget , page_key , file_search_context : ClientSearch . FileSearchContext , media_sort_widget : typing . Optional [ ClientGUISearch . MediaSortControl ] = None , media_collect_widget : typing . Optional [ ClientGUISearch . MediaCollectControl ] = None , media_callable = None , synchronised = True , include_unusual_predicate_types = True , allow_all_known_files = True , force_system_everything = False , hide_favourites_edit_actions = False ) :
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
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
AutoCompleteDropdownTags . __init__ ( self , parent , file_service_key , tag_search_context . service_key )
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
2019-04-03 22:45:57 +00:00
self . _under_construction_or_predicate = None
2016-09-21 19:54:04 +00:00
self . _file_search_context = file_search_context
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-03-11 21:52:11 +00:00
QP . AddToLayout ( self . _text_input_hbox , self . _favourite_searches_button , CC . FLAGS_VCENTER )
2020-03-25 21:15:57 +00:00
QP . AddToLayout ( self . _text_input_hbox , self . _cancel_search_button , CC . FLAGS_VCENTER )
#
2020-03-11 21:52:11 +00:00
self . _include_current_tags = ClientGUICommon . OnOffButton ( self . _dropdown_window , self . _page_key , ' notify_include_current ' , 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-03-11 21:52:11 +00:00
self . _include_pending_tags = ClientGUICommon . OnOffButton ( self . _dropdown_window , self . _page_key , ' notify_include_pending ' , 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 . _synchronised = ClientGUICommon . OnOffButton ( self . _dropdown_window , self . _page_key , ' notify_search_immediately ' , on_label = ' searching immediately ' , off_label = ' waiting -- tag counts may be inaccurate ' , start_on = synchronised )
2019-11-14 03:56:30 +00:00
self . _synchronised . 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
2019-11-14 03:56:30 +00:00
QP . AddToLayout ( sync_button_hbox , self . _synchronised , CC . FLAGS_EXPAND_BOTH_WAYS )
QP . AddToLayout ( sync_button_hbox , self . _or_advanced , CC . FLAGS_VCENTER )
QP . AddToLayout ( sync_button_hbox , self . _or_cancel , CC . FLAGS_VCENTER )
QP . AddToLayout ( sync_button_hbox , self . _or_rewind , CC . FLAGS_VCENTER )
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
2017-05-10 21:33:58 +00:00
HG . client_controller . sub ( self , ' IncludeCurrent ' , ' notify_include_current ' )
HG . client_controller . sub ( self , ' IncludePending ' , ' notify_include_pending ' )
2016-09-21 19:54:04 +00:00
2020-03-25 21:15:57 +00:00
self . _predicates_listbox . listBoxChanged . connect ( self . _SignalNewSearchState )
2016-09-21 19:54:04 +00:00
2019-08-15 00:40:48 +00:00
def _AdvancedORInput ( self ) :
title = ' enter advanced OR predicates '
with ClientGUITopLevelWindows . DialogEdit ( self , title ) as dlg :
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 self . _under_construction_or_predicate is not None and not or_pred_in_broadcast :
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
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
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
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
2019-04-03 22:45:57 +00:00
def _BroadcastCurrentText ( self , shift_down ) :
2016-09-21 19:54:04 +00:00
2020-03-04 22:12:53 +00:00
( raw_entry , inclusive , entry_text , wildcard_text , search_text , explicit_wildcard , cache_text , entry_predicate ) = self . _ParseSearchText ( )
2016-09-21 19:54:04 +00:00
2019-06-05 19:42:39 +00:00
( namespace , subtag ) = HydrusTags . SplitTag ( search_text )
2020-03-25 21:15:57 +00:00
if not ClientSearch . IsUnacceptableTagSearch ( search_text ) and namespace != ' ' and subtag in ( ' ' , ' * ' ) :
2016-09-21 19:54:04 +00:00
2020-03-25 21:15:57 +00:00
( unprocessed_namespace , unprocessed_subtag ) = HydrusTags . SplitTag ( entry_text )
entry_predicate = ClientSearch . Predicate ( ClientSearch . PREDICATE_TYPE_NAMESPACE , unprocessed_namespace , inclusive )
2016-09-21 19:54:04 +00:00
2019-06-05 19:42:39 +00:00
else :
2016-09-21 19:54:04 +00:00
2019-06-05 19:42:39 +00:00
try :
HydrusTags . CheckTagNotEmpty ( search_text )
except HydrusExceptions . SizeException :
return
2016-09-21 19:54:04 +00:00
2019-04-03 22:45:57 +00:00
self . _BroadcastChoices ( { entry_predicate } , shift_down )
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 )
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 )
names = list ( folders_to_names [ folder_name ] )
names . sort ( )
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
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 ) :
2020-03-04 22:12:53 +00:00
favs_list = ClientGUIListBoxes . ListBoxTagsACRead ( self . _dropdown_notebook , self . BroadcastChoices , self . _float_mode , self . _tag_service_key , height_num_chars = self . _list_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 ) :
2019-11-14 03:56:30 +00:00
if self . _float_mode :
self . _list_height_num_chars = 19
else :
self . _list_height_num_chars = 8
2020-03-04 22:12:53 +00:00
return ClientGUIListBoxes . ListBoxTagsACRead ( self . _dropdown_notebook , self . BroadcastChoices , self . _tag_service_key , self . _float_mode , height_num_chars = self . _list_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 )
self . _synchronised . SetOnOff ( False )
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 . _synchronised . SetOnOff ( synchronised )
def _ManageFavouriteSearches ( self , favourite_search_row_to_save = None ) :
from . import ClientGUISearchPanels
favourite_searches_rows = HG . client_controller . favourite_search_manager . GetFavouriteSearchRows ( )
title = ' edit favourite searches '
with ClientGUITopLevelWindows . DialogEdit ( self , title ) as dlg :
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 )
2016-09-21 19:54:04 +00:00
def _ParseSearchText ( self ) :
2019-11-14 03:56:30 +00:00
raw_entry = self . _text_ctrl . text ( )
2016-09-21 19:54:04 +00:00
if raw_entry . startswith ( ' - ' ) :
inclusive = False
2017-03-08 23:23:12 +00:00
entry_text = raw_entry [ 1 : ]
2016-09-21 19:54:04 +00:00
else :
inclusive = True
2017-03-08 23:23:12 +00:00
entry_text = raw_entry
2016-09-21 19:54:04 +00:00
2020-03-04 22:12:53 +00:00
entry_text = HydrusTags . CleanTag ( entry_text )
2017-03-08 23:23:12 +00:00
explicit_wildcard = ' * ' in entry_text
2016-09-21 19:54:04 +00:00
2019-07-24 21:39:02 +00:00
( wildcard_text , search_text ) = ClientSearch . ConvertEntryTextToSearchText ( entry_text )
2016-09-21 19:54:04 +00:00
2017-03-08 23:23:12 +00:00
if explicit_wildcard :
cache_text = None
2016-09-21 19:54:04 +00:00
2020-03-11 21:52:11 +00:00
entry_predicate = ClientSearch . Predicate ( ClientSearch . PREDICATE_TYPE_WILDCARD , search_text , inclusive )
2016-09-21 19:54:04 +00:00
else :
2020-03-04 22:12:53 +00:00
tag = entry_text
2019-07-24 21:39:02 +00:00
2017-03-08 23:23:12 +00:00
cache_text = search_text [ : - 1 ] # take off the trailing '*' for the cache text
2019-04-17 21:51:50 +00:00
siblings_manager = HG . client_controller . tag_siblings_manager
2017-03-08 23:23:12 +00:00
sibling = siblings_manager . GetSibling ( self . _tag_service_key , tag )
if sibling is None :
2020-03-11 21:52:11 +00:00
entry_predicate = ClientSearch . Predicate ( ClientSearch . PREDICATE_TYPE_TAG , tag , inclusive )
2017-03-08 23:23:12 +00:00
else :
2020-03-11 21:52:11 +00:00
entry_predicate = ClientSearch . Predicate ( ClientSearch . PREDICATE_TYPE_TAG , sibling , inclusive )
2017-03-08 23:23:12 +00:00
2016-09-21 19:54:04 +00:00
2020-03-04 22:12:53 +00:00
return ( raw_entry , inclusive , entry_text , wildcard_text , search_text , explicit_wildcard , cache_text , entry_predicate )
2016-09-21 19:54:04 +00:00
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 )
QP . AddToLayout ( self . _main_vbox , self . _predicates_listbox , CC . FLAGS_EXPAND_PERPENDICULAR )
2019-01-30 22:14:54 +00:00
def _StartResultsFetchJob ( self , job_key ) :
2016-09-21 19:54:04 +00:00
2019-01-30 22:14:54 +00:00
parsed_search_text = self . _ParseSearchText ( )
2016-09-21 19:54:04 +00:00
2019-07-17 22:10:19 +00:00
stub_predicates = [ ]
stub_predicates = InsertStaticPredicatesForRead ( stub_predicates , parsed_search_text , self . _include_unusual_predicate_types , self . _under_construction_or_predicate )
AppendLoadingPredicate ( stub_predicates )
2019-11-14 03:56:30 +00:00
HG . client_controller . CallLaterQtSafe ( self , 0.2 , self . SetStubPredicates , job_key , stub_predicates )
2019-07-17 22:10:19 +00:00
2020-03-04 22:12:53 +00:00
HG . client_controller . CallToThread ( ReadFetch , self , job_key , self . SetFetchedResults , parsed_search_text , self . _media_callable , self . _file_search_context , self . _synchronised . IsOn ( ) , self . _include_unusual_predicate_types , self . _initial_matches_fetched , self . _search_text_for_current_cache , self . _cached_results , self . _under_construction_or_predicate , self . _force_system_everything )
2016-09-21 19:54:04 +00:00
def _ShouldTakeResponsibilityForEnter ( self ) :
2020-03-04 22:12:53 +00:00
( raw_entry , inclusive , entry_text , wildcard_text , search_text , explicit_wildcard , cache_text , entry_predicate ) = self . _ParseSearchText ( )
2019-01-30 22:14:54 +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
2019-08-15 00:40:48 +00:00
something_to_broadcast = cache_text != ' '
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
2019-08-15 00:40:48 +00:00
results_desynced_with_text = raw_entry != self . _current_list_raw_entry
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
2019-04-03 22:45:57 +00:00
self . _BroadcastCurrentText ( 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 ( )
2016-09-21 19:54:04 +00:00
def IncludeCurrent ( self , page_key , value ) :
if page_key == self . _page_key :
self . _file_search_context . SetIncludeCurrentTags ( value )
2018-02-14 21:47:18 +00:00
self . _SetListDirty ( )
2017-01-18 22:52:39 +00:00
2020-03-25 21:15:57 +00:00
self . _SignalNewSearchState ( )
2017-01-18 22:52:39 +00:00
2016-09-21 19:54:04 +00:00
def IncludePending ( self , page_key , value ) :
if page_key == self . _page_key :
self . _file_search_context . SetIncludePendingTags ( value )
2018-02-14 21:47:18 +00:00
self . _SetListDirty ( )
2017-01-18 22:52:39 +00:00
2020-03-25 21:15:57 +00:00
self . _SignalNewSearchState ( )
2017-01-18 22:52:39 +00:00
2016-09-21 19:54:04 +00:00
2019-04-24 22:18:50 +00:00
def IsSynchronised ( self ) :
return self . _synchronised . IsOn ( )
2019-01-30 22:14:54 +00:00
def SetFetchedResults ( self , job_key , search_text , search_text_for_cache , cached_results , results , next_search_is_probably_fast ) :
if self . _current_fetch_job_key is not None and self . _current_fetch_job_key . GetKey ( ) == job_key . GetKey ( ) :
AutoCompleteDropdownTags . SetFetchedResults ( self , job_key , search_text , search_text_for_cache , cached_results , results )
self . _next_search_is_probably_fast = next_search_is_probably_fast
2019-11-14 03:56:30 +00:00
num_chars = len ( self . _text_ctrl . text ( ) )
2019-01-30 22:14:54 +00:00
if num_chars == 0 :
# refresh system preds after five mins
self . _ScheduleListRefresh ( 300 )
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-03-25 21:15:57 +00:00
def SynchronisedWaitSwitch ( self ) :
2016-09-21 19:54:04 +00:00
2020-03-25 21:15:57 +00:00
self . _synchronised . Flip ( )
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 ) :
has_counts = False
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 :
for predicate in initial_predicates :
self . _AppendTerm ( predicate )
self . _DataHasChanged ( )
HG . client_controller . sub ( self , ' EnterPredicates ' , ' enter_predicates ' )
def _Activate ( self ) :
if len ( self . _selected_terms ) > 0 :
self . _EnterPredicates ( set ( self . _selected_terms ) )
def _DeleteActivate ( self ) :
self . _Activate ( )
def _EnterPredicates ( self , predicates , permit_add = True , permit_remove = True ) :
if len ( predicates ) == 0 :
return
predicates_to_be_added = set ( )
predicates_to_be_removed = set ( )
for predicate in predicates :
predicate = predicate . GetCountlessCopy ( )
if self . _HasPredicate ( predicate ) :
if permit_remove :
predicates_to_be_removed . add ( predicate )
else :
if permit_add :
predicates_to_be_added . add ( predicate )
predicates_to_be_removed . update ( self . _GetMutuallyExclusivePredicates ( predicate ) )
for predicate in predicates_to_be_added :
self . _AppendTerm ( predicate )
for predicate in predicates_to_be_removed :
self . _RemoveTerm ( predicate )
self . _SortByText ( )
self . _DataHasChanged ( )
def _GetCurrentFileServiceKey ( self ) :
return self . _my_ac_parent . GetFileSearchContext ( ) . GetFileServiceKey ( )
def _GetCurrentPagePredicates ( self ) - > typing . Set [ ClientSearch . Predicate ] :
return self . GetPredicates ( )
def _GetTextFromTerm ( self , term ) :
predicate = term
return predicate . ToString ( render_for_user = True )
2020-03-25 21:15:57 +00:00
def _HasCurrentPagePredicates ( self ) :
return True
2020-03-18 21:35:57 +00:00
def _ProcessMenuPredicateEvent ( self , command ) :
( include_predicates , exclude_predicates ) = self . _GetSelectedIncludeExcludePredicates ( )
if command == ' add_include_predicates ' :
self . _EnterPredicates ( include_predicates , permit_remove = False )
elif command == ' remove_include_predicates ' :
self . _EnterPredicates ( include_predicates , permit_add = False )
elif command == ' add_exclude_predicates ' :
self . _EnterPredicates ( exclude_predicates , permit_remove = False )
elif command == ' remove_exclude_predicates ' :
self . _EnterPredicates ( exclude_predicates , permit_add = False )
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 ( )
for predicate in predicates :
self . _AppendTerm ( predicate )
self . _DataHasChanged ( )
2016-09-21 19:54:04 +00:00
class AutoCompleteDropdownTagsWrite ( AutoCompleteDropdownTags ) :
2019-06-26 21:27:18 +00:00
def __init__ ( self , parent , chosen_tag_callable , expand_parents , 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
self . _chosen_tag_callable = chosen_tag_callable
self . _expand_parents = expand_parents
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
2019-09-18 22:40:39 +00:00
if service . GetServiceType ( ) == HC . LOCAL_TAG :
2017-05-10 21:33:58 +00:00
file_service_key = CC . LOCAL_FILE_SERVICE_KEY
2019-09-18 22:40:39 +00:00
elif tag_service_key != CC . COMBINED_TAG_SERVICE_KEY and HC . options [ ' show_all_tags_in_autocomplete ' ] :
file_service_key = CC . COMBINED_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
2019-11-14 03:56:30 +00:00
QP . AddToLayout ( self . _text_input_hbox , self . _paste_button , CC . FLAGS_VCENTER )
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
2019-11-14 03:56:30 +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
2019-04-03 22:45:57 +00:00
def _BroadcastCurrentText ( self , shift_down ) :
2016-09-21 19:54:04 +00:00
2018-04-25 22:07:52 +00:00
( raw_entry , search_text , cache_text , entry_predicate , sibling_predicate ) = self . _ParseSearchText ( )
2016-09-21 19:54:04 +00:00
try :
HydrusTags . CheckTagNotEmpty ( search_text )
except HydrusExceptions . SizeException :
return
2019-04-03 22:45:57 +00:00
self . _BroadcastChoices ( { entry_predicate } , shift_down )
2016-09-21 19:54:04 +00:00
2018-11-21 22:22:36 +00:00
def _ChangeTagService ( self , tag_service_key ) :
AutoCompleteDropdownTags . _ChangeTagService ( self , tag_service_key )
if self . _tag_service_key_changed_callable is not None :
self . _tag_service_key_changed_callable ( tag_service_key )
2018-03-28 21:55:58 +00:00
def _InitFavouritesList ( self ) :
2020-03-04 22:12:53 +00:00
favs_list = ClientGUIListBoxes . ListBoxTagsACWrite ( self . _dropdown_notebook , self . BroadcastChoices , self . _tag_service_key , self . _float_mode , height_num_chars = self . _list_height_num_chars )
2018-03-28 21:55:58 +00:00
return favs_list
def _InitSearchResultsList ( self ) :
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
self . _list_height_num_chars = 8
2020-03-04 22:12:53 +00:00
return ClientGUIListBoxes . ListBoxTagsACWrite ( self . _dropdown_notebook , self . BroadcastChoices , self . _tag_service_key , self . _float_mode , height_num_chars = self . _list_height_num_chars )
2016-09-21 19:54:04 +00:00
2019-07-17 22:10:19 +00:00
def _ParseSearchText ( self ) :
2019-11-14 03:56:30 +00:00
raw_entry = self . _text_ctrl . text ( )
2019-07-17 22:10:19 +00:00
tag = HydrusTags . CleanTag ( raw_entry )
2020-03-25 21:15:57 +00:00
explicit_wildcard = ' * ' in tag
2019-07-17 22:10:19 +00:00
2020-03-25 21:15:57 +00:00
( wildcard_text , search_text ) = ClientSearch . ConvertEntryTextToSearchText ( tag )
2019-07-24 21:39:02 +00:00
if explicit_wildcard :
2019-07-17 22:10:19 +00:00
cache_text = None
else :
cache_text = search_text [ : - 1 ] # take off the trailing '*' for the cache text
2020-03-11 21:52:11 +00:00
entry_predicate = ClientSearch . Predicate ( ClientSearch . PREDICATE_TYPE_TAG , tag )
2019-07-17 22:10:19 +00:00
siblings_manager = HG . client_controller . tag_siblings_manager
sibling = siblings_manager . GetSibling ( self . _tag_service_key , tag )
if sibling is not None :
2020-03-11 21:52:11 +00:00
sibling_predicate = ClientSearch . Predicate ( ClientSearch . PREDICATE_TYPE_TAG , sibling )
2019-07-17 22:10:19 +00:00
else :
sibling_predicate = None
return ( raw_entry , search_text , cache_text , entry_predicate , sibling_predicate )
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
2019-01-30 22:14:54 +00:00
( raw_entry , search_text , cache_text , entry_predicate , sibling_predicate ) = self . _ParseSearchText ( )
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
2019-01-30 22:14:54 +00:00
sitting_on_empty = raw_entry == ' '
2016-09-21 19:54:04 +00:00
2019-08-07 22:59:53 +00:00
something_to_broadcast = not sitting_on_empty
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
2019-08-15 00:40:48 +00:00
results_desynced_with_text = raw_entry != self . _current_list_raw_entry
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
2019-01-30 22:14:54 +00:00
def _StartResultsFetchJob ( self , job_key ) :
parsed_search_text = self . _ParseSearchText ( )
2019-07-17 22:10:19 +00:00
stub_predicates = [ ]
stub_predicates = InsertStaticPredicatesForWrite ( stub_predicates , parsed_search_text , self . _tag_service_key , self . _expand_parents )
AppendLoadingPredicate ( stub_predicates )
2019-11-14 03:56:30 +00:00
HG . client_controller . CallLaterQtSafe ( self , 0.2 , self . SetStubPredicates , job_key , stub_predicates )
2019-07-17 22:10:19 +00:00
2019-01-30 22:14:54 +00:00
HG . client_controller . CallToThread ( WriteFetch , self , job_key , self . SetFetchedResults , parsed_search_text , self . _file_service_key , self . _tag_service_key , self . _expand_parents , self . _search_text_for_current_cache , self . _cached_results )
2019-04-03 22:45:57 +00:00
def _TakeResponsibilityForEnter ( self , shift_down ) :
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
if self . _text_ctrl . text ( ) == ' ' 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 :
2019-04-03 22:45:57 +00:00
self . _BroadcastCurrentText ( 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 ) :
favourite_tags = list ( HG . client_controller . new_options . GetStringList ( ' favourite_tags ' ) )
favourite_tags . sort ( )
2020-03-11 21:52:11 +00:00
predicates = [ ClientSearch . Predicate ( ClientSearch . PREDICATE_TYPE_TAG , tag ) for tag in favourite_tags ]
2018-04-05 01:22:26 +00:00
2019-04-17 21:51:50 +00:00
parents_manager = HG . client_controller . tag_parents_manager
2018-04-05 01:22:26 +00:00
predicates = parents_manager . ExpandPredicates ( CC . COMBINED_TAG_SERVICE_KEY , predicates )
self . _favourites_list . SetPredicates ( predicates )
2019-07-31 22:01:02 +00:00
def SetExpandParents ( self , expand_parents ) :
self . _expand_parents = expand_parents
2019-01-30 22:14:54 +00:00
def SetFetchedResults ( self , job_key , search_text , search_text_for_cache , cached_results , results , next_search_is_probably_fast ) :
if self . _current_fetch_job_key is not None and self . _current_fetch_job_key . GetKey ( ) == job_key . GetKey ( ) :
AutoCompleteDropdownTags . SetFetchedResults ( self , job_key , search_text , search_text_for_cache , cached_results , results )
self . _next_search_is_probably_fast = next_search_is_probably_fast
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
summary + = ' Parentheses work the usual way. \ can be used to escape characters (e.g. to search for tags including parentheses) '
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 )
if ' * ' not in namespace and subtag == ' * ' :
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