2019-01-09 22:59:03 +00:00
from . import ClientCaches
from . import ClientConstants as CC
from . import ClientData
from . import ClientGUICommon
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
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
2019-11-14 03:56:30 +00:00
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
from . import QtPorting as QP
2018-01-31 22:58:15 +00:00
2019-07-17 22:10:19 +00:00
def AppendLoadingPredicate ( predicates ) :
predicates . append ( ClientSearch . Predicate ( predicate_type = HC . PREDICATE_TYPE_LABEL , value = ' loading results \u2026 ' ) )
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 ) :
2019-07-24 21:39:02 +00:00
( raw_entry , inclusive , wildcard_text , search_text , explicit_wildcard , cache_text , entry_predicate ) = parsed_search_text
2019-07-17 22:10:19 +00:00
if search_text in ( ' ' , ' : ' , ' * ' ) :
pass
else :
if include_unusual_predicate_types :
if explicit_wildcard :
2019-07-24 21:39:02 +00:00
if wildcard_text != search_text :
predicates . insert ( 0 , ClientSearch . Predicate ( HC . PREDICATE_TYPE_WILDCARD , search_text , inclusive ) )
predicates . insert ( 0 , ClientSearch . Predicate ( HC . PREDICATE_TYPE_WILDCARD , wildcard_text , inclusive ) )
2019-07-17 22:10:19 +00:00
else :
2019-08-15 00:40:48 +00:00
( namespace , subtag ) = HydrusTags . SplitTag ( search_text )
2019-07-17 22:10:19 +00:00
2019-08-15 00:40:48 +00:00
if namespace != ' ' and subtag in ( ' ' , ' * ' ) :
2019-07-17 22:10:19 +00:00
predicates . insert ( 0 , ClientSearch . Predicate ( HC . PREDICATE_TYPE_NAMESPACE , namespace , inclusive ) )
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 )
if search_text in ( ' ' , ' : ' , ' * ' ) 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
2019-11-14 03:56:30 +00:00
def ReadFetch ( win , job_key , results_callable , parsed_search_text , qt_media_callable , file_search_context , synchronised , include_unusual_predicate_types , initial_matches_fetched , search_text_for_current_cache , cached_results , under_construction_or_predicate ) :
2019-01-30 22:14:54 +00:00
next_search_is_probably_fast = False
include_current = file_search_context . IncludeCurrentTags ( )
include_pending = file_search_context . IncludePendingTags ( )
file_service_key = file_search_context . GetFileServiceKey ( )
tag_service_key = file_search_context . GetTagServiceKey ( )
2019-07-24 21:39:02 +00:00
( raw_entry , inclusive , wildcard_text , search_text , explicit_wildcard , cache_text , entry_predicate ) = parsed_search_text
2019-01-30 22:14:54 +00:00
if search_text in ( ' ' , ' : ' , ' * ' ) :
# 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
cached_results = HG . client_controller . Read ( ' file_system_predicates ' , search_service_key )
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
predicates = HG . client_controller . Read ( ' autocomplete_predicates ' , file_service_key = file_service_key , tag_service_key = tag_service_key , search_text = cache_text , exact_match = True , inclusive = inclusive , include_current = include_current , include_pending = include_pending , add_namespaceless = add_namespaceless , job_key = job_key , collapse_siblings = True )
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
cached_results = HG . client_controller . Read ( ' autocomplete_predicates ' , file_service_key = file_service_key , tag_service_key = tag_service_key , search_text = search_text , inclusive = inclusive , include_current = include_current , include_pending = include_pending , add_namespaceless = add_namespaceless , job_key = job_key , collapse_siblings = True )
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
2019-07-24 21:39:02 +00:00
if include_current :
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
2019-07-24 21:39:02 +00:00
if include_pending :
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
predicates = [ ClientSearch . Predicate ( HC . PREDICATE_TYPE_TAG , tag , inclusive , 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
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
( raw_entry , search_text , cache_text , entry_predicate , sibling_predicate ) = parsed_search_text
if search_text in ( ' ' , ' : ' , ' * ' ) :
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
2019-08-15 00:40:48 +00:00
predicates = HG . client_controller . Read ( ' autocomplete_predicates ' , file_service_key = file_service_key , tag_service_key = tag_service_key , 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
cached_results = HG . client_controller . Read ( ' autocomplete_predicates ' , file_service_key = file_service_key , tag_service_key = tag_service_key , search_text = search_text , add_namespaceless = False , job_key = job_key , collapse_siblings = False )
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
self . _text_ctrl = QW . QLineEdit ( self )
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 )
2018-02-21 21:59:37 +00:00
self . _last_move_event_started = 0.0
self . _last_move_event_occurred = 0.0
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
2019-11-14 03:56:30 +00:00
vbox = QP . VBoxLayout ( margin = 0 )
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
2019-11-14 03:56:30 +00:00
QP . AddToLayout ( vbox , self . _text_input_hbox , 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 :
2019-11-14 03:56:30 +00:00
self . _dropdown_window = QW . QFrame ( self )
2016-09-21 19:54:04 +00:00
2019-12-18 22:06:34 +00:00
self . _dropdown_window . setFrameShape ( QW . QFrame . NoFrame )
#self._dropdown_window.setFrameStyle( QW.QFrame.Box | QW.QFrame.Raised )
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 :
2019-11-14 03:56:30 +00:00
QP . AddToLayout ( 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
2019-11-14 03:56:30 +00:00
self . setLayout ( 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
2018-02-14 21:47:18 +00:00
self . _move_hide_job = None
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
2019-11-14 03:56:30 +00:00
HG . client_controller . sub ( self , ' _ParentMovedOrResized ' , ' 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 ( )
if self . _move_hide_job is not None :
self . _move_hide_job . Cancel ( )
self . _move_hide_job = None
else :
self . _HideDropdown ( )
except :
if self . _move_hide_job is not None :
self . _move_hide_job . Cancel ( )
self . _move_hide_job = None
raise
2019-04-10 22:50:53 +00:00
def _HandleEscape ( self ) :
if self . _float_mode :
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-21 21:59:37 +00:00
def _ParentMovedOrResized ( self ) :
if self . _float_mode :
if HydrusData . TimeHasPassedFloat ( self . _last_move_event_occurred + 1.0 ) :
self . _last_move_event_started = HydrusData . GetNowFloat ( )
self . _last_move_event_occurred = HydrusData . GetNowFloat ( )
# we'll do smoother move updates for a little bit to stop flickeryness, but after that we'll just hide
NICE_ANIMATION_GRACE_PERIOD = 0.25
time_to_delay_these_calls = HydrusData . TimeHasPassedFloat ( self . _last_move_event_started + NICE_ANIMATION_GRACE_PERIOD )
if time_to_delay_these_calls :
self . _HideDropdown ( )
if self . _ShouldShow ( ) :
if self . _move_hide_job is None :
2019-11-14 03:56:30 +00:00
self . _move_hide_job = HG . client_controller . CallRepeatingQtSafe ( self . _dropdown_window , 0.0 , 0.25 , self . _DropdownHideShow )
2018-02-21 21:59:37 +00:00
self . _move_hide_job . Delay ( 0.25 )
else :
2018-02-28 22:30:36 +00:00
self . _DropdownHideShow ( )
2018-02-21 21:59:37 +00:00
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
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-02-26 22:28:52 +00:00
text_size = self . _text_ctrl . size ( )
text_width = text_size . width ( )
text_height = text_size . height ( )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
if self . _text_ctrl . isVisible ( ) :
2016-09-21 19:54:04 +00:00
2020-02-26 22:28:52 +00:00
desired_dropdown_position = ClientGUIFunctions . ClientToScreen ( self . _text_ctrl , QC . QPoint ( 0 , text_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
2018-03-28 21:55:58 +00:00
if text_width != self . _last_attempted_dropdown_width :
2019-11-14 03:56:30 +00:00
self . _dropdown_window . setFixedWidth ( text_width )
2016-09-21 19:54:04 +00:00
self . _last_attempted_dropdown_width = text_width
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
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 )
2019-11-14 03:56:30 +00:00
elif key == QC . Qt . Key_Escape :
2019-04-10 22:50:53 +00:00
escape_caught = self . _HandleEscape ( )
if not escape_caught :
send_input_to_current_list = True
2018-03-28 21:55:58 +00:00
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 ) :
2018-02-21 21:59:37 +00:00
self . _ParentMovedOrResized ( )
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
2019-11-14 03:56:30 +00:00
QP . CallAfter ( 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 ) :
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
2017-06-28 20:23:21 +00:00
file_service = HG . client_controller . services_manager . GetService ( self . _file_service_key )
2016-09-21 19:54:04 +00:00
name = file_service . GetName ( )
2019-11-14 03:56:30 +00:00
self . _file_repo_button . setText ( name )
2016-09-21 19:54:04 +00:00
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
2017-06-28 20:23:21 +00:00
tag_service = tag_service = HG . client_controller . services_manager . GetService ( self . _tag_service_key )
2016-09-21 19:54:04 +00:00
name = tag_service . GetName ( )
2019-11-14 03:56:30 +00:00
self . _tag_repo_button . setText ( name )
2016-09-21 19:54:04 +00:00
2019-01-30 22:14:54 +00:00
self . _search_text_for_current_cache = None
2016-09-21 19:54:04 +00:00
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
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
services = [ ]
2016-12-21 22:30:54 +00:00
2016-10-26 20:45:34 +00:00
services . append ( services_manager . GetService ( CC . LOCAL_FILE_SERVICE_KEY ) )
services . append ( services_manager . GetService ( CC . TRASH_SERVICE_KEY ) )
2019-05-22 22:35:06 +00:00
if HG . client_controller . new_options . GetBoolean ( ' advanced_mode ' ) :
services . append ( services_manager . GetService ( CC . COMBINED_LOCAL_FILE_SERVICE_KEY ) )
2016-10-26 20:45:34 +00:00
services . extend ( services_manager . GetServices ( ( HC . FILE_REPOSITORY , ) ) )
2017-11-22 21:03:07 +00:00
advanced_mode = HG . client_controller . new_options . GetBoolean ( ' advanced_mode ' )
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
services . append ( services_manager . GetService ( CC . COMBINED_FILE_SERVICE_KEY ) )
2016-10-26 20:45:34 +00:00
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
2017-05-10 21:33:58 +00:00
HG . client_controller . 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 ( )
predicates = [ ClientSearch . Predicate ( HC . PREDICATE_TYPE_TAG , tag ) for tag in favourite_tags ]
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
services = [ ]
2016-12-21 22:30:54 +00:00
2019-09-18 22:40:39 +00:00
services . extend ( services_manager . GetServices ( ( HC . LOCAL_TAG , ) ) )
2016-09-21 19:54:04 +00:00
services . extend ( services_manager . GetServices ( ( HC . TAG_REPOSITORY , ) ) )
2016-12-21 22:30:54 +00:00
services . append ( services_manager . GetService ( CC . COMBINED_TAG_SERVICE_KEY ) )
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
2017-05-10 21:33:58 +00:00
HG . client_controller . PopupMenu ( self . _tag_repo_button , menu )
2016-09-21 19:54:04 +00:00
class AutoCompleteDropdownTagsRead ( AutoCompleteDropdownTags ) :
2019-04-24 22:18:50 +00:00
def __init__ ( self , parent , page_key , file_search_context , media_callable = None , synchronised = True , include_unusual_predicate_types = True , allow_all_known_files = True ) :
2016-09-21 19:54:04 +00:00
file_service_key = file_search_context . GetFileServiceKey ( )
tag_service_key = file_search_context . GetTagServiceKey ( )
AutoCompleteDropdownTags . __init__ ( self , parent , file_service_key , tag_service_key )
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
self . _page_key = page_key
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
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 = file_search_context . IncludeCurrentTags ( ) )
2019-11-14 03:56:30 +00:00
self . _include_current_tags . setToolTip ( ' select whether to include current tags in the search ' )
2016-09-21 19:54:04 +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 = file_search_context . IncludePendingTags ( ) )
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
2019-11-14 03:56:30 +00:00
self . _or_cancel = ClientGUICommon . BetterBitmapButton ( self . _dropdown_window , CC . GlobalPixmaps . delete , self . _CancelORConstruction )
self . _or_cancel . setToolTip ( ' Cancel OR Predicate construction. ' )
self . _or_cancel . hide ( )
2016-09-21 19:54:04 +00:00
2019-11-14 03:56:30 +00:00
self . _or_rewind = ClientGUICommon . BetterBitmapButton ( self . _dropdown_window , CC . GlobalPixmaps . previous , self . _RewindORConstruction )
self . _or_rewind . setToolTip ( ' Rewind OR Predicate construction. ' )
self . _or_rewind . hide ( )
2019-04-03 22:45:57 +00:00
2019-04-10 22:50:53 +00:00
self . _include_unusual_predicate_types = include_unusual_predicate_types
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 , ' SetSynchronisedWait ' , ' synchronised_wait_switch ' )
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
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 :
self . _under_construction_or_predicate = ClientSearch . Predicate ( HC . PREDICATE_TYPE_OR_CONTAINER , predicates )
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 ] )
self . _under_construction_or_predicate = ClientSearch . Predicate ( HC . PREDICATE_TYPE_OR_CONTAINER , or_preds )
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 ] )
predicates = { ClientSearch . Predicate ( HC . PREDICATE_TYPE_OR_CONTAINER , or_preds ) }
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
2019-04-03 22:45:57 +00:00
HG . client_controller . pub ( ' enter_predicates ' , self . _page_key , predicates )
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
2019-07-24 21:39:02 +00:00
( raw_entry , inclusive , 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 )
if namespace != ' ' and subtag in ( ' ' , ' * ' ) :
2016-09-21 19:54:04 +00:00
2019-06-05 19:42:39 +00:00
entry_predicate = ClientSearch . Predicate ( HC . PREDICATE_TYPE_NAMESPACE , 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
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 )
2017-05-10 21:33:58 +00:00
HG . client_controller . pub ( ' change_file_service ' , self . _page_key , file_service_key )
2016-09-21 19:54:04 +00:00
2017-05-10 21:33:58 +00:00
HG . client_controller . pub ( ' refresh_query ' , self . _page_key )
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 )
2017-05-10 21:33:58 +00:00
HG . client_controller . pub ( ' change_tag_service ' , self . _page_key , tag_service_key )
2016-09-21 19:54:04 +00:00
2017-05-10 21:33:58 +00:00
HG . client_controller . pub ( ' refresh_query ' , self . _page_key )
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 ) :
2018-05-23 21:05:06 +00:00
favs_list = ClientGUIListBoxes . ListBoxTagsACRead ( self . _dropdown_notebook , self . BroadcastChoices , 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
2018-05-23 21:05:06 +00:00
return ClientGUIListBoxes . ListBoxTagsACRead ( self . _dropdown_notebook , self . BroadcastChoices , self . _tag_service_key , height_num_chars = self . _list_height_num_chars )
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
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
2017-03-08 23:23:12 +00:00
entry_predicate = ClientSearch . Predicate ( HC . PREDICATE_TYPE_WILDCARD , search_text , inclusive )
2016-09-21 19:54:04 +00:00
else :
2019-07-24 21:39:02 +00:00
tag = HydrusTags . CleanTag ( entry_text )
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 :
entry_predicate = ClientSearch . Predicate ( HC . PREDICATE_TYPE_TAG , tag , inclusive )
else :
entry_predicate = ClientSearch . Predicate ( HC . PREDICATE_TYPE_TAG , sibling , inclusive )
2016-09-21 19:54:04 +00:00
2019-07-24 21:39:02 +00:00
return ( raw_entry , inclusive , 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
2019-04-10 22:50:53 +00:00
self . _under_construction_or_predicate = ClientSearch . Predicate ( HC . 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
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
2019-04-10 22:50: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 )
2016-09-21 19:54:04 +00:00
def _ShouldTakeResponsibilityForEnter ( self ) :
2019-07-24 21:39:02 +00:00
( raw_entry , inclusive , 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
2016-09-21 19:54:04 +00:00
def GetFileSearchContext ( self ) :
return self . _file_search_context
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
2017-05-10 21:33:58 +00:00
HG . client_controller . pub ( ' refresh_query ' , self . _page_key )
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
2017-05-10 21:33:58 +00:00
HG . client_controller . pub ( ' refresh_query ' , self . _page_key )
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 )
2016-09-21 19:54:04 +00:00
def SetSynchronisedWait ( self , page_key ) :
2019-04-24 22:18:50 +00:00
if page_key == self . _page_key :
2019-11-14 03:56:30 +00:00
self . _synchronised . EventButton ( )
2019-04-24 22:18:50 +00:00
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 )
2019-11-14 03:56:30 +00:00
self . _paste_button = ClientGUICommon . BetterBitmapButton ( self , CC . GlobalPixmaps . paste , self . _Paste )
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 ) :
2018-05-23 21:05:06 +00:00
favs_list = ClientGUIListBoxes . ListBoxTagsACWrite ( self . _dropdown_notebook , self . BroadcastChoices , self . _tag_service_key , 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
2018-05-23 21:05:06 +00:00
return ClientGUIListBoxes . ListBoxTagsACWrite ( self . _dropdown_notebook , self . BroadcastChoices , self . _tag_service_key , 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 )
2019-07-24 21:39:02 +00:00
explicit_wildcard = ' * ' in raw_entry
2019-07-17 22:10:19 +00:00
2019-07-24 21:39:02 +00:00
( wildcard_text , search_text ) = ClientSearch . ConvertEntryTextToSearchText ( raw_entry )
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
entry_predicate = ClientSearch . Predicate ( HC . PREDICATE_TYPE_TAG , tag )
siblings_manager = HG . client_controller . tag_siblings_manager
sibling = siblings_manager . GetSibling ( self . _tag_service_key , tag )
if sibling is not None :
sibling_predicate = ClientSearch . Predicate ( HC . PREDICATE_TYPE_TAG , sibling )
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 )
entry_predicates = [ ClientSearch . Predicate ( HC . PREDICATE_TYPE_TAG , tag ) for tag in tags ]
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 ( )
predicates = [ ClientSearch . Predicate ( HC . PREDICATE_TYPE_TAG , tag ) for tag in favourite_tags ]
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 == ' * ' :
row_pred = ClientSearch . Predicate ( HC . PREDICATE_TYPE_NAMESPACE , namespace , inclusive )
else :
row_pred = ClientSearch . Predicate ( HC . PREDICATE_TYPE_WILDCARD , tag_string , inclusive )
else :
row_pred = ClientSearch . Predicate ( HC . PREDICATE_TYPE_TAG , tag_string , inclusive )
row_preds . append ( row_pred )
if len ( row_preds ) == 1 :
self . _current_predicates . append ( row_preds [ 0 ] )
else :
self . _current_predicates . append ( ClientSearch . Predicate ( HC . PREDICATE_TYPE_OR_CONTAINER , row_preds ) )
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