hydrus/hydrus/ClientGUIACDropdown.py

2523 lines
85 KiB
Python
Raw Normal View History

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