hydrus/include/ClientGUIACDropdown.py

1296 lines
46 KiB
Python

import ClientCaches
import ClientConstants as CC
import ClientData
import ClientGUICommon
import ClientGUIListBoxes
import ClientSearch
import collections
import HydrusConstants as HC
import HydrusExceptions
import HydrusGlobals
import HydrusTags
import itertools
import wx
ID_TIMER_DROPDOWN_HIDE = wx.NewId()
ID_TIMER_AC_LAG = wx.NewId()
# 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
class AutoCompleteDropdown( wx.Panel ):
def __init__( self, parent ):
wx.Panel.__init__( self, parent )
self._intercept_key_events = True
self._last_search_text = ''
self._next_updatelist_is_probably_fast = False
tlp = self.GetTopLevelParent()
# There's a big bug in wx where FRAME_FLOAT_ON_PARENT Frames don't get passed their mouse events if their parent is a Dialog jej
# I think it is something to do with the initialisation order; if the frame is init'ed before the ShowModal call, but whatever.
# This turned out to be ugly when I added the manage tags frame, so I've set it to if the tlp has a parent, which basically means "not the main gui"
if tlp.GetParent() is not None or HC.options[ 'always_embed_autocompletes' ]:
self._float_mode = False
else:
self._float_mode = True
self._text_ctrl = wx.TextCtrl( self, style=wx.TE_PROCESS_ENTER )
self._text_ctrl.SetBackgroundColour( wx.Colour( *HC.options[ 'gui_colours' ][ 'autocomplete_background' ] ) )
self._last_attempted_dropdown_width = 0
self._last_attempted_dropdown_position = ( None, None )
if self._float_mode:
self._text_ctrl.Bind( wx.EVT_SET_FOCUS, self.EventSetFocus )
self._text_ctrl.Bind( wx.EVT_KILL_FOCUS, self.EventKillFocus )
self._text_ctrl.Bind( wx.EVT_TEXT, self.EventText )
self._text_ctrl.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown )
self._text_ctrl.Bind( wx.EVT_MOUSEWHEEL, self.EventMouseWheel )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._text_ctrl, CC.FLAGS_EXPAND_PERPENDICULAR )
#self._dropdown_window = wx.PopupWindow( self, flags = wx.BORDER_RAISED )
#self._dropdown_window = wx.PopupTransientWindow( self, style = wx.BORDER_RAISED )
#self._dropdown_window = wx.Window( self, style = wx.BORDER_RAISED )
#self._dropdown_window = wx.Panel( self )
if self._float_mode:
self._dropdown_window = wx.Frame( self, style = wx.FRAME_TOOL_WINDOW | wx.FRAME_NO_TASKBAR | wx.FRAME_FLOAT_ON_PARENT | wx.BORDER_RAISED )
self._dropdown_window.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_FRAMEBK ) )
self._dropdown_window.SetSize( ( 0, 0 ) )
self._dropdown_window.SetPosition( self._text_ctrl.ClientToScreenXY( 0, 0 ) )
self._dropdown_window.Show()
self._dropdown_window.Bind( wx.EVT_CLOSE, self.EventCloseDropdown )
self._dropdown_hidden = True
self._list_height = 250
else:
self._dropdown_window = wx.Panel( self )
self._list_height = 125
self._dropdown_list = self._InitDropDownList()
if not self._float_mode: vbox.AddF( self._dropdown_window, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
self._cache_text = ''
self._cached_results = []
self._initial_matches_fetched = False
if self._float_mode:
self.Bind( wx.EVT_MOVE, self.EventMove )
self.Bind( wx.EVT_SIZE, self.EventMove )
self.Bind( wx.EVT_TIMER, self.TIMEREventDropdownHide, id = ID_TIMER_DROPDOWN_HIDE )
self._move_hide_timer = wx.Timer( self, id = ID_TIMER_DROPDOWN_HIDE )
self._move_hide_timer.Start( 1, wx.TIMER_ONE_SHOT )
tlp.Bind( wx.EVT_MOVE, self.EventMove )
parent = self
while True:
try:
parent = parent.GetParent()
if isinstance( parent, wx.ScrolledWindow ):
parent.Bind( wx.EVT_SCROLLWIN, self.EventMove )
except:
break
self.Bind( wx.EVT_TIMER, self.TIMEREventLag, id = ID_TIMER_AC_LAG )
self._lag_timer = wx.Timer( self, id = ID_TIMER_AC_LAG )
wx.CallAfter( self._UpdateList )
def _BroadcastChoices( self, predicates ):
raise NotImplementedError()
def _BroadcastCurrentText( self ):
text = self._text_ctrl.GetValue()
self._BroadcastChoices( { text } )
def _GenerateMatches( self ):
raise NotImplementedError()
def _HideDropdown( self ):
if not self._dropdown_hidden:
self._dropdown_window.SetSize( ( 0, 0 ) )
self._dropdown_hidden = True
def _InitDropDownList( self ):
raise NotImplementedError()
def _ShouldShow( self ):
tlp_active = self.GetTopLevelParent().IsActive() or self._dropdown_window.IsActive()
if HC.PLATFORM_LINUX:
tlp = self.GetTopLevelParent()
if isinstance( tlp, wx.Dialog ):
visible = True
else:
# notebook on linux doesn't 'hide' things apparently, so isshownonscreen, which recursively tests parents' hide status, doesn't work!
gui = HydrusGlobals.client_controller.GetGUI()
current_page = gui.GetCurrentPage()
visible = ClientGUICommon.IsWXAncestor( self, current_page )
else:
visible = self._text_ctrl.IsShownOnScreen()
focus_window = wx.Window.FindFocus()
focus_remains_on_self_or_children = focus_window == self._dropdown_window or focus_window in self._dropdown_window.GetChildren() or focus_window == self._text_ctrl
return tlp_active and visible and focus_remains_on_self_or_children
def _ShouldTakeResponsibilityForEnter( self ):
raise NotImplementedError()
def _ShowDropdown( self ):
( text_width, text_height ) = self._text_ctrl.GetSize()
desired_dropdown_position = self._text_ctrl.ClientToScreenXY( -2, text_height - 2 )
if self._last_attempted_dropdown_position != desired_dropdown_position:
self._dropdown_window.SetPosition( desired_dropdown_position )
self._last_attempted_dropdown_position = desired_dropdown_position
#
show_and_fit_needed = False
if self._dropdown_hidden:
show_and_fit_needed = True
else:
if text_width != self._last_attempted_dropdown_width:
show_and_fit_needed = True
if show_and_fit_needed:
self._dropdown_window.Fit()
self._dropdown_window.SetSize( ( text_width, -1 ) )
self._dropdown_window.Layout()
self._dropdown_hidden = False
self._last_attempted_dropdown_width = text_width
def _TakeResponsibilityForEnter( self ):
raise NotImplementedError()
def _UpdateList( self ):
pass
def BroadcastChoices( self, predicates ):
self._BroadcastChoices( predicates )
def EventCloseDropdown( self, event ):
HydrusGlobals.client_controller.GetGUI().Close()
def EventKeyDown( self, event ):
HydrusGlobals.client_controller.ResetIdleTimer()
if event.KeyCode in ( wx.WXK_INSERT, wx.WXK_NUMPAD_INSERT ):
if self._intercept_key_events:
self._intercept_key_events = False
( r, g, b ) = HC.options[ 'gui_colours' ][ 'autocomplete_background' ]
if r != g or r != b or g != b:
colour = wx.Colour( g, b, r )
elif r > 127:
colour = wx.Colour( g, b, r / 2 )
else:
colour = wx.Colour( g, b, r * 2 )
else:
self._intercept_key_events = True
colour = wx.Colour( *HC.options[ 'gui_colours' ][ 'autocomplete_background' ] )
self._text_ctrl.SetBackgroundColour( colour )
self._text_ctrl.Refresh()
elif event.KeyCode == wx.WXK_SPACE and event.RawControlDown(): # this is control, not command on os x, for which command+space does some os stuff
self._UpdateList()
self._lag_timer.Stop()
elif self._intercept_key_events:
if event.KeyCode in ( ord( 'A' ), ord( 'a' ) ) and event.CmdDown():
event.Skip()
elif event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ) and self._ShouldTakeResponsibilityForEnter():
self._TakeResponsibilityForEnter()
elif event.KeyCode == wx.WXK_ESCAPE:
self.GetTopLevelParent().SetFocus()
elif event.KeyCode in ( wx.WXK_UP, wx.WXK_NUMPAD_UP, wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN ) and self._text_ctrl.GetValue() == '' and len( self._dropdown_list ) == 0:
if event.KeyCode in ( wx.WXK_UP, wx.WXK_NUMPAD_UP ): id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'select_up' )
elif event.KeyCode in ( wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN ): id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'select_down' )
new_event = wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = id )
self._text_ctrl.ProcessEvent( new_event )
elif event.KeyCode in ( wx.WXK_PAGEDOWN, wx.WXK_NUMPAD_PAGEDOWN, wx.WXK_PAGEUP, wx.WXK_NUMPAD_PAGEUP ) and self._text_ctrl.GetValue() == '' and len( self._dropdown_list ) == 0:
if event.KeyCode in ( wx.WXK_PAGEUP, wx.WXK_NUMPAD_PAGEUP ):
id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'canvas_show_previous' )
elif event.KeyCode in ( wx.WXK_PAGEDOWN, wx.WXK_NUMPAD_PAGEDOWN ):
id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'canvas_show_next' )
new_event = wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = id )
self._text_ctrl.ProcessEvent( new_event )
else:
self._dropdown_list.ProcessEvent( event )
else:
event.Skip()
def EventKillFocus( self, event ):
self._move_hide_timer.Start( 1, wx.TIMER_ONE_SHOT )
event.Skip()
def EventMouseWheel( self, event ):
if self._text_ctrl.GetValue() == '' and len( self._dropdown_list ) == 0:
if event.GetWheelRotation() > 0: id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'select_up' )
else: id = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'select_down' )
new_event = wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = id )
self.ProcessEvent( new_event )
else:
if event.CmdDown():
key_event = wx.KeyEvent( wx.EVT_KEY_DOWN.typeId )
if event.GetWheelRotation() > 0: key_event.m_keyCode = wx.WXK_UP
else: key_event.m_keyCode = wx.WXK_DOWN
self._dropdown_list.ProcessEvent( key_event )
else:
# for some reason, the scrolledwindow list doesn't process scroll events properly when in a popupwindow
# so let's just tell it to scroll manually
( start_x, start_y ) = self._dropdown_list.GetViewStart()
if event.GetWheelRotation() > 0: self._dropdown_list.Scroll( -1, start_y - 3 )
else: self._dropdown_list.Scroll( -1, start_y + 3 )
if event.GetWheelRotation() > 0: command_type = wx.wxEVT_SCROLLWIN_LINEUP
else: command_type = wx.wxEVT_SCROLLWIN_LINEDOWN
wx.PostEvent( self, wx.ScrollWinEvent( command_type ) )
def EventMove( self, event ):
try:
self._HideDropdown()
self._move_hide_timer.Start( 250, wx.TIMER_ONE_SHOT )
except wx.PyDeadObjectError: pass
event.Skip()
def EventSetFocus( self, event ):
self._move_hide_timer.Start( 1, wx.TIMER_ONE_SHOT )
event.Skip()
def EventText( self, event ):
num_chars = len( self._text_ctrl.GetValue() )
if num_chars == 0:
self._UpdateList()
elif HC.options[ 'fetch_ac_results_automatically' ]:
( char_limit, long_wait, short_wait ) = HC.options[ 'ac_timings' ]
self._next_updatelist_is_probably_fast = self._next_updatelist_is_probably_fast and num_chars > len( self._last_search_text )
if self._next_updatelist_is_probably_fast: self._UpdateList()
elif num_chars < char_limit: self._lag_timer.Start( long_wait, wx.TIMER_ONE_SHOT )
else: self._lag_timer.Start( short_wait, wx.TIMER_ONE_SHOT )
def RefreshList( self ):
self._cache_text = ''
self._current_namespace = ''
self._UpdateList()
def TIMEREventDropdownHide( self, event ):
try:
should_show = self._ShouldShow()
if should_show:
self._ShowDropdown()
else:
self._HideDropdown()
self._move_hide_timer.Start( 250, wx.TIMER_ONE_SHOT )
except wx.PyDeadObjectError:
self._move_hide_timer.Stop()
except:
self._move_hide_timer.Stop()
raise
def TIMEREventLag( self, event ):
try:
self._UpdateList()
except wx.PyDeadObjectError:
self._lag_timer.Stop()
except:
self._lag_timer.Stop()
raise
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 )
self._current_namespace = ''
self._current_matches = []
file_service = HydrusGlobals.client_controller.GetServicesManager().GetService( self._file_service_key )
tag_service = HydrusGlobals.client_controller.GetServicesManager().GetService( self._tag_service_key )
self._file_repo_button = ClientGUICommon.BetterButton( self._dropdown_window, file_service.GetName(), self.FileButtonHit )
self._file_repo_button.SetMinSize( ( 20, -1 ) )
self._tag_repo_button = ClientGUICommon.BetterButton( self._dropdown_window, tag_service.GetName(), self.TagButtonHit )
self._tag_repo_button.SetMinSize( ( 20, -1 ) )
self.Bind( wx.EVT_MENU, self.EventMenu )
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:
self._ChangeTagService( CC.LOCAL_TAG_SERVICE_KEY )
self._file_service_key = file_service_key
file_service = HydrusGlobals.client_controller.GetServicesManager().GetService( self._file_service_key )
name = file_service.GetName()
self._file_repo_button.SetLabelText( name )
wx.CallAfter( self.RefreshList )
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
self._dropdown_list.SetTagService( self._tag_service_key )
tag_service = tag_service = HydrusGlobals.client_controller.GetServicesManager().GetService( self._tag_service_key )
name = tag_service.GetName()
self._tag_repo_button.SetLabelText( name )
self._cache_text = ''
self._current_namespace = ''
wx.CallAfter( self.RefreshList )
def _UpdateList( self ):
self._last_search_text = self._text_ctrl.GetValue()
matches = self._GenerateMatches()
self._initial_matches_fetched = True
self._dropdown_list.SetPredicates( matches )
self._current_matches = matches
num_chars = len( self._text_ctrl.GetValue() )
if num_chars == 0:
self._lag_timer.Start( 5 * 60 * 1000, wx.TIMER_ONE_SHOT )
def EventMenu( self, event ):
action = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
if action is not None:
( command, data ) = action
if command == 'change_file_service':
self._ChangeFileService( data )
elif command == 'change_tag_service':
self._ChangeTagService( data )
else:
event.Skip()
return # this is about select_up and select_down
def FileButtonHit( self ):
services_manager = HydrusGlobals.client_controller.GetServicesManager()
services = []
services.append( services_manager.GetService( CC.LOCAL_FILE_SERVICE_KEY ) )
services.append( services_manager.GetService( CC.TRASH_SERVICE_KEY ) )
services.append( services_manager.GetService( CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )
services.extend( services_manager.GetServices( ( HC.FILE_REPOSITORY, ) ) )
services.append( services_manager.GetService( CC.COMBINED_FILE_SERVICE_KEY ) )
menu = wx.Menu()
for service in services:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'change_file_service', service.GetServiceKey() ), service.GetName() )
HydrusGlobals.client_controller.PopupMenu( self._file_repo_button, menu )
def SetFileService( self, file_service_key ):
self._ChangeFileService( file_service_key )
def SetTagService( self, tag_service_key ):
self._ChangeTagService( tag_service_key )
def TagButtonHit( self ):
services_manager = HydrusGlobals.client_controller.GetServicesManager()
services = []
services.append( services_manager.GetService( CC.LOCAL_TAG_SERVICE_KEY ) )
services.extend( services_manager.GetServices( ( HC.TAG_REPOSITORY, ) ) )
services.append( services_manager.GetService( CC.COMBINED_TAG_SERVICE_KEY ) )
menu = wx.Menu()
for service in services:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'change_tag_service', service.GetServiceKey() ), service.GetName() )
HydrusGlobals.client_controller.PopupMenu( self._tag_repo_button, menu )
class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
def __init__( self, parent, page_key, file_search_context, media_callable = None, synchronised = True, include_unusual_predicate_types = True ):
file_service_key = file_search_context.GetFileServiceKey()
tag_service_key = file_search_context.GetTagServiceKey()
AutoCompleteDropdownTags.__init__( self, parent, file_service_key, tag_service_key )
self._media_callable = media_callable
self._page_key = page_key
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() )
self._include_current_tags.SetToolTipString( 'select whether to include current tags in the search' )
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() )
self._include_pending_tags.SetToolTipString( 'select whether to include pending tags in the search' )
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 )
self._synchronised.SetToolTipString( 'select whether to renew the search as soon as a new predicate is entered' )
self._include_unusual_predicate_types = include_unusual_predicate_types
button_hbox_1 = wx.BoxSizer( wx.HORIZONTAL )
button_hbox_1.AddF( self._include_current_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
button_hbox_1.AddF( self._include_pending_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
button_hbox_2 = wx.BoxSizer( wx.HORIZONTAL )
button_hbox_2.AddF( self._file_repo_button, CC.FLAGS_EXPAND_BOTH_WAYS )
button_hbox_2.AddF( self._tag_repo_button, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( button_hbox_1, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( self._synchronised, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( button_hbox_2, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( self._dropdown_list, CC.FLAGS_EXPAND_BOTH_WAYS )
self._dropdown_window.SetSizer( vbox )
HydrusGlobals.client_controller.sub( self, 'SetSynchronisedWait', 'synchronised_wait_switch' )
HydrusGlobals.client_controller.sub( self, 'IncludeCurrent', 'notify_include_current' )
HydrusGlobals.client_controller.sub( self, 'IncludePending', 'notify_include_pending' )
def _BroadcastChoices( self, predicates ):
if self._text_ctrl.GetValue() != '':
self._text_ctrl.SetValue( '' )
HydrusGlobals.client_controller.pub( 'enter_predicates', self._page_key, predicates )
def _BroadcastCurrentText( self ):
( inclusive, search_text, entry_predicate ) = self._ParseSearchText()
try:
HydrusTags.CheckTagNotEmpty( search_text )
except HydrusExceptions.SizeException:
return
self._BroadcastChoices( { entry_predicate } )
def _ChangeFileService( self, file_service_key ):
AutoCompleteDropdownTags._ChangeFileService( self, file_service_key )
self._file_search_context.SetFileServiceKey( file_service_key )
HydrusGlobals.client_controller.pub( 'change_file_service', self._page_key, file_service_key )
HydrusGlobals.client_controller.pub( 'refresh_query', self._page_key )
def _ChangeTagService( self, tag_service_key ):
AutoCompleteDropdownTags._ChangeTagService( self, tag_service_key )
self._file_search_context.SetTagServiceKey( tag_service_key )
HydrusGlobals.client_controller.pub( 'change_tag_service', self._page_key, tag_service_key )
HydrusGlobals.client_controller.pub( 'refresh_query', self._page_key )
def _InitDropDownList( self ):
return ClientGUIListBoxes.ListBoxTagsAutocompleteDropdownRead( self._dropdown_window, self._tag_service_key, self.BroadcastChoices, min_height = self._list_height )
def _ParseSearchText( self ):
raw_entry = self._text_ctrl.GetValue()
if raw_entry.startswith( '-' ):
inclusive = False
tag = raw_entry[1:]
else:
inclusive = True
tag = raw_entry
tag = HydrusTags.CleanTag( tag )
search_text = ClientSearch.ConvertTagToSearchable( tag )
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
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 )
return ( inclusive, search_text, entry_predicate )
def _GenerateMatches( self ):
self._next_updatelist_is_probably_fast = False
num_autocomplete_chars = HC.options[ 'num_autocomplete_chars' ]
( inclusive, search_text, entry_predicate ) = self._ParseSearchText()
if search_text in ( '', ':' ):
input_just_changed = self._cache_text != ''
db_not_going_to_hang_if_we_hit_it = not HydrusGlobals.client_controller.DBCurrentlyDoingJob()
if input_just_changed or db_not_going_to_hang_if_we_hit_it or not self._initial_matches_fetched:
self._cache_text = ''
self._current_namespace = ''
if self._file_service_key == CC.COMBINED_FILE_SERVICE_KEY: search_service_key = self._tag_service_key
else: search_service_key = self._file_service_key
self._cached_results = HydrusGlobals.client_controller.Read( 'file_system_predicates', search_service_key )
matches = self._cached_results
else:
must_do_a_search = False
if '*' in search_text:
must_do_a_search = True
( namespace, half_complete_subtag ) = HydrusTags.SplitTag( search_text )
if namespace != '':
if namespace != self._current_namespace:
self._current_namespace = namespace # do a new search, no matter what half_complete tag is
if half_complete_subtag != '': must_do_a_search = True
else:
if self._cache_text == self._current_namespace + ':' and half_complete_subtag != '':
must_do_a_search = True
else:
self._current_namespace = namespace
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
if half_complete_subtag == '':
self._cache_text = self._current_namespace + ':'
matches = [] # a query like 'namespace:'
else:
fetch_from_db = True
if self._media_callable is not None:
media = self._media_callable()
can_fetch_from_media = media is not None and len( media ) > 0
if can_fetch_from_media and self._synchronised.IsOn():
fetch_from_db = False
if fetch_from_db:
include_current = self._file_search_context.IncludeCurrentTags()
include_pending = self._file_search_context.IncludePendingTags()
if len( half_complete_subtag ) < num_autocomplete_chars and '*' not in search_text:
predicates = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, search_text = search_text, exact_match = True, inclusive = inclusive, include_current = include_current, include_pending = include_pending, add_namespaceless = True, collapse_siblings = True )
else:
if must_do_a_search or self._cache_text == '' or not search_text.startswith( self._cache_text ):
self._cache_text = search_text
self._cached_results = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, search_text = search_text, inclusive = inclusive, include_current = include_current, include_pending = include_pending, add_namespaceless = True, collapse_siblings = True )
predicates = self._cached_results
self._next_updatelist_is_probably_fast = True
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() )
tags_to_do = set()
current_tags_to_count = collections.Counter()
pending_tags_to_count = collections.Counter()
if self._file_search_context.IncludeCurrentTags():
lists_of_current_tags = [ list( tags_manager.GetCurrent( self._tag_service_key ) ) for tags_manager in tags_managers ]
current_tags_flat_iterable = itertools.chain.from_iterable( lists_of_current_tags )
current_tags_flat = ClientSearch.FilterTagsBySearchEntry( self._tag_service_key, search_text, current_tags_flat_iterable )
current_tags_to_count.update( current_tags_flat )
tags_to_do.update( current_tags_to_count.keys() )
if self._file_search_context.IncludePendingTags():
lists_of_pending_tags = [ list( tags_manager.GetPending( self._tag_service_key ) ) for tags_manager in tags_managers ]
pending_tags_flat_iterable = itertools.chain.from_iterable( lists_of_pending_tags )
pending_tags_flat = ClientSearch.FilterTagsBySearchEntry( self._tag_service_key, search_text, pending_tags_flat_iterable )
pending_tags_to_count.update( pending_tags_flat )
tags_to_do.update( pending_tags_to_count.keys() )
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 ]
if self._tag_service_key != CC.COMBINED_TAG_SERVICE_KEY:
predicates = siblings_manager.CollapsePredicates( self._tag_service_key, predicates )
if self._current_namespace == '':
predicates = ClientData.MergePredicates( predicates, add_namespaceless = True )
self._next_updatelist_is_probably_fast = True
matches = ClientSearch.FilterPredicatesBySearchEntry( self._tag_service_key, search_text, predicates )
matches = ClientSearch.SortPredicates( matches )
if self._include_unusual_predicate_types:
if self._current_namespace != '':
if '*' not in self._current_namespace and half_complete_subtag == '':
matches.insert( 0, ClientSearch.Predicate( HC.PREDICATE_TYPE_NAMESPACE, self._current_namespace, inclusive ) )
if half_complete_subtag != '':
if '*' in self._current_namespace or ( '*' in half_complete_subtag and half_complete_subtag != '*' ):
matches.insert( 0, ClientSearch.Predicate( HC.PREDICATE_TYPE_WILDCARD, search_text, inclusive ) )
elif '*' in search_text:
matches.insert( 0, ClientSearch.Predicate( HC.PREDICATE_TYPE_WILDCARD, search_text, inclusive ) )
for match in matches:
if match.GetInclusive() != inclusive: match.SetInclusive( inclusive )
try:
index = matches.index( entry_predicate )
predicate = matches[ index ]
del matches[ index ]
matches.insert( 0, predicate )
except:
pass
return matches
def _ShouldTakeResponsibilityForEnter( self ):
# when the user has quickly typed something in and the results are not yet in
return self._text_ctrl.GetValue() != '' and self._last_search_text == ''
def _TakeResponsibilityForEnter( self ):
self._BroadcastCurrentText()
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 )
wx.CallAfter( self.RefreshList )
HydrusGlobals.client_controller.pub( 'refresh_query', self._page_key )
def IncludePending( self, page_key, value ):
if page_key == self._page_key:
self._file_search_context.SetIncludePendingTags( value )
wx.CallAfter( self.RefreshList )
HydrusGlobals.client_controller.pub( 'refresh_query', self._page_key )
def SetSynchronisedWait( self, page_key ):
if page_key == self._page_key: self._synchronised.EventButton( None )
class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
def __init__( self, parent, chosen_tag_callable, expand_parents, file_service_key, tag_service_key, null_entry_callable = None ):
self._chosen_tag_callable = chosen_tag_callable
self._expand_parents = expand_parents
self._null_entry_callable = null_entry_callable
if tag_service_key != CC.COMBINED_TAG_SERVICE_KEY and HC.options[ 'show_all_tags_in_autocomplete' ]:
file_service_key = CC.COMBINED_FILE_SERVICE_KEY
AutoCompleteDropdownTags.__init__( self, parent, file_service_key, tag_service_key )
vbox = wx.BoxSizer( wx.VERTICAL )
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( self._file_repo_button, CC.FLAGS_EXPAND_BOTH_WAYS )
hbox.AddF( self._tag_repo_button, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( self._dropdown_list, CC.FLAGS_EXPAND_BOTH_WAYS )
self._dropdown_window.SetSizer( vbox )
def _BroadcastChoices( self, predicates ):
if self._text_ctrl.GetValue() != '':
self._text_ctrl.SetValue( '' )
tags = { predicate.GetValue() for predicate in predicates }
if len( tags ) > 0:
self._chosen_tag_callable( tags )
def _ParseSearchText( self ):
raw_entry = self._text_ctrl.GetValue()
tag = HydrusTags.CleanTag( raw_entry )
search_text = ClientSearch.ConvertTagToSearchable( tag )
entry_predicate = ClientSearch.Predicate( HC.PREDICATE_TYPE_TAG, tag )
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
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 ( search_text, entry_predicate, sibling_predicate )
def _BroadcastCurrentText( self ):
( search_text, entry_predicate, sibling_predicate ) = self._ParseSearchText()
try:
HydrusTags.CheckTagNotEmpty( search_text )
except HydrusExceptions.SizeException:
return
self._BroadcastChoices( { entry_predicate } )
def _GenerateMatches( self ):
self._next_updatelist_is_probably_fast = False
num_autocomplete_chars = HC.options[ 'num_autocomplete_chars' ]
( search_text, entry_predicate, sibling_predicate ) = self._ParseSearchText()
if search_text in ( '', ':' ):
self._cache_text = ''
self._current_namespace = ''
matches = []
else:
must_do_a_search = False
( namespace, half_complete_subtag ) = HydrusTags.SplitTag( search_text )
if namespace != '':
if half_complete_subtag != '' and namespace != self._current_namespace:
self._current_namespace = namespace # do a new search, no matter what half_complete tag is
must_do_a_search = True
else:
self._current_namespace = namespace
if len( half_complete_subtag ) < num_autocomplete_chars and '*' not in search_text:
predicates = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, search_text = search_text, exact_match = True, add_namespaceless = False, collapse_siblings = False )
else:
if must_do_a_search or self._cache_text == '' or not half_complete_subtag.startswith( self._cache_text ):
self._cache_text = half_complete_subtag
self._cached_results = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, search_text = search_text, add_namespaceless = False, collapse_siblings = False )
predicates = self._cached_results
self._next_updatelist_is_probably_fast = True
matches = ClientSearch.FilterPredicatesBySearchEntry( self._tag_service_key, half_complete_subtag, predicates )
matches = ClientSearch.SortPredicates( matches )
self._PutAtTopOfMatches( matches, entry_predicate )
if sibling_predicate is not None:
self._PutAtTopOfMatches( matches, sibling_predicate )
if self._expand_parents:
parents_manager = HydrusGlobals.client_controller.GetManager( 'tag_parents' )
matches = parents_manager.ExpandPredicates( self._tag_service_key, matches )
return matches
def _InitDropDownList( self ):
return ClientGUIListBoxes.ListBoxTagsAutocompleteDropdownWrite( self._dropdown_window, self._tag_service_key, self.BroadcastChoices, min_height = self._list_height )
def _PutAtTopOfMatches( self, matches, predicate ):
try:
index = matches.index( predicate )
predicate = matches[ index ]
matches.remove( predicate )
except ValueError:
pass
matches.insert( 0, predicate )
def _ShouldTakeResponsibilityForEnter( self ):
# when the user has quickly typed something in and the results are not yet in
p1 = self._text_ctrl.GetValue() != '' and self._last_search_text == ''
# when the text ctrl is empty and we want to push a None to the parent dialog
p2 = self._text_ctrl.GetValue() == ''
return p1 or p2
def _TakeResponsibilityForEnter( self ):
if self._text_ctrl.GetValue() == '':
if self._null_entry_callable is not None:
self._null_entry_callable()
else:
self._BroadcastCurrentText()