hydrus/include/ClientGUICommon.py

5770 lines
185 KiB
Python
Executable File

import collections
import HydrusConstants as HC
import ClientCaches
import ClientData
import ClientConstants as CC
import ClientRatings
import itertools
import os
import random
import sys
import time
import traceback
import wx
import wx.combo
import wx.richtext
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
from wx.lib.mixins.listctrl import ColumnSorterMixin
import HydrusTags
import HydrusData
import HydrusExceptions
import ClientSearch
import HydrusGlobals
TEXT_CUTOFF = 1024
#
ID_TIMER_ANIMATED = wx.NewId()
ID_TIMER_SLIDESHOW = wx.NewId()
ID_TIMER_MEDIA_INFO_DISPLAY = wx.NewId()
ID_TIMER_DROPDOWN_HIDE = wx.NewId()
ID_TIMER_AC_LAG = wx.NewId()
ID_TIMER_POPUP = wx.NewId()
def FlushOutPredicates( parent, predicates ):
good_predicates = []
for predicate in predicates:
predicate = predicate.GetCountlessCopy()
( predicate_type, value, inclusive ) = predicate.GetInfo()
if value is None and predicate_type in [ HC.PREDICATE_TYPE_SYSTEM_NUM_TAGS, HC.PREDICATE_TYPE_SYSTEM_LIMIT, HC.PREDICATE_TYPE_SYSTEM_SIZE, HC.PREDICATE_TYPE_SYSTEM_DIMENSIONS, HC.PREDICATE_TYPE_SYSTEM_AGE, HC.PREDICATE_TYPE_SYSTEM_HASH, HC.PREDICATE_TYPE_SYSTEM_DURATION, HC.PREDICATE_TYPE_SYSTEM_NUM_WORDS, HC.PREDICATE_TYPE_SYSTEM_MIME, HC.PREDICATE_TYPE_SYSTEM_RATING, HC.PREDICATE_TYPE_SYSTEM_SIMILAR_TO, HC.PREDICATE_TYPE_SYSTEM_FILE_SERVICE ]:
import ClientGUIDialogs
with ClientGUIDialogs.DialogInputFileSystemPredicates( parent, predicate_type ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
good_predicates.extend( dlg.GetPredicates() )
else:
continue
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_UNTAGGED:
good_predicates.append( ClientData.Predicate( HC.PREDICATE_TYPE_SYSTEM_NUM_TAGS, ( '=', 0 ) ) )
else:
good_predicates.append( predicate )
return good_predicates
def IsWXAncestor( child, ancestor ):
parent = child
while not isinstance( parent, wx.TopLevelWindow ):
if parent == ancestor:
return True
parent = parent.GetParent()
return False
class AnimatedStaticTextTimestamp( wx.StaticText ):
def __init__( self, parent, prefix, rendering_function, timestamp, suffix ):
self._prefix = prefix
self._rendering_function = rendering_function
self._timestamp = timestamp
self._suffix = suffix
self._last_tick = HydrusData.GetNow()
wx.StaticText.__init__( self, parent, label = self._prefix + self._rendering_function( self._timestamp ) + self._suffix )
self.Bind( wx.EVT_TIMER, self.TIMEREventAnimated, id = ID_TIMER_ANIMATED )
self._timer_animated = wx.Timer( self, ID_TIMER_ANIMATED )
self._timer_animated.Start( 1000, wx.TIMER_CONTINUOUS )
def TIMEREventAnimated( self ):
try:
update = False
now = HydrusData.GetNow()
difference = abs( now - self._timestamp )
if difference < 3600: update = True
elif difference < 3600 * 24 and now - self._last_tick > 60: update = True
elif now - self._last_tick > 3600: update = True
if update:
self.SetLabel( self._prefix + self._rendering_function( self._timestamp ) + self._suffix )
wx.PostEvent( self.GetEventHandler(), wx.SizeEvent() )
except wx.PyDeadObjectError:
self._timer_animated.Stop()
except:
self._timer_animated.Stop()
raise
# 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._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.
if issubclass( type( tlp ), wx.Dialog ) 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_BTNFACE ) )
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 = []
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 issubclass( type( 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 = 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().EventExit( event )
def EventKeyDown( self, event ):
HydrusGlobals.client_controller.ResetIdleTimer()
if 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 )
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() )
( char_limit, long_wait, short_wait ) = HC.options[ 'ac_timings' ]
if num_chars == 0 or 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 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 ):
AutoCompleteDropdown.__init__( self, parent )
self._current_namespace = ''
self._current_matches = []
self._cached_results = []
self._file_service_key = file_service_key
self._tag_service_key = tag_service_key
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 = wx.Button( self._dropdown_window, label = file_service.GetName() )
self._file_repo_button.Bind( wx.EVT_BUTTON, self.EventFileButton )
self._file_repo_button.SetMinSize( ( 20, -1 ) )
self._tag_repo_button = wx.Button( self._dropdown_window, label = tag_service.GetName() )
self._tag_repo_button.Bind( wx.EVT_BUTTON, self.EventTagButton )
self._tag_repo_button.SetMinSize( ( 20, -1 ) )
self.Bind( wx.EVT_MENU, self.EventMenu )
def _ChangeFileRepository( self, file_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.SetLabel( name )
def _ChangeTagRepository( self, tag_service_key ):
self._tag_service_key = tag_service_key
tag_service = tag_service = HydrusGlobals.client_controller.GetServicesManager().GetService( self._tag_service_key )
name = tag_service.GetName()
self._tag_repo_button.SetLabel( name )
def _InitDropDownList( self ): return ListBoxTagsAutocompleteDropdown( self._dropdown_window, self.BroadcastChoices, min_height = self._list_height )
def _UpdateList( self ):
self._last_search_text = self._text_ctrl.GetValue()
matches = self._GenerateMatches()
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 EventFileButton( self, event ):
services_manager = HydrusGlobals.client_controller.GetServicesManager()
services = []
services.append( services_manager.GetService( CC.COMBINED_FILE_SERVICE_KEY ) )
services.append( services_manager.GetService( CC.LOCAL_FILE_SERVICE_KEY ) )
services.append( services_manager.GetService( CC.TRASH_SERVICE_KEY ) )
services.extend( services_manager.GetServices( ( HC.FILE_REPOSITORY, ) ) )
menu = wx.Menu()
for service in services: menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'change_file_repository', service.GetServiceKey() ), service.GetName() )
HydrusGlobals.client_controller.PopupMenu( self._file_repo_button, menu )
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_repository': self._ChangeFileRepository( data )
elif command == 'change_tag_repository': self._ChangeTagRepository( data )
else:
event.Skip()
return # this is about select_up and select_down
self._cache_text = ''
self._current_namespace = ''
self._UpdateList()
def EventTagButton( self, event ):
services_manager = HydrusGlobals.client_controller.GetServicesManager()
services = []
services.append( services_manager.GetService( CC.COMBINED_TAG_SERVICE_KEY ) )
services.append( services_manager.GetService( CC.LOCAL_TAG_SERVICE_KEY ) )
services.extend( services_manager.GetServices( ( HC.TAG_REPOSITORY, ) ) )
menu = wx.Menu()
for service in services: menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'change_tag_repository', service.GetServiceKey() ), service.GetName() )
HydrusGlobals.client_controller.PopupMenu( self._tag_repo_button, menu )
class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
def __init__( self, parent, page_key, file_service_key, tag_service_key, media_callable = None, include_current = True, include_pending = True, synchronised = True ):
AutoCompleteDropdownTags.__init__( self, parent, file_service_key, tag_service_key )
self._media_callable = media_callable
self._page_key = page_key
self._include_current = include_current
self._include_pending = include_pending
self._include_current_tags = OnOffButton( self._dropdown_window, self._page_key, 'notify_include_current', on_label = 'include current tags', off_label = 'exclude current tags', start_on = self._include_current )
self._include_current_tags.SetToolTipString( 'select whether to include current tags in the search' )
self._include_pending_tags = OnOffButton( self._dropdown_window, self._page_key, 'notify_include_pending', on_label = 'include pending tags', off_label = 'exclude pending tags', start_on = self._include_pending )
self._include_pending_tags.SetToolTipString( 'select whether to include pending tags in the search' )
self._synchronised = 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' )
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 _ChangeFileRepository( self, file_service_key ):
AutoCompleteDropdownTags._ChangeFileRepository( self, file_service_key )
HydrusGlobals.client_controller.pub( 'change_file_repository', self._page_key, self._file_service_key )
def _ChangeTagRepository( self, tag_service_key ):
AutoCompleteDropdownTags._ChangeTagRepository( self, tag_service_key )
HydrusGlobals.client_controller.pub( 'change_tag_repository', self._page_key, self._tag_service_key )
def _ParseSearchText( self ):
raw_entry = self._text_ctrl.GetValue()
if raw_entry.startswith( '-' ):
inclusive = False
search_text = raw_entry[1:]
else:
inclusive = True
search_text = raw_entry
search_text = HydrusTags.CleanTag( search_text )
entry_predicate = ClientData.Predicate( HC.PREDICATE_TYPE_TAG, search_text, inclusive = 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 ( '', ':' ):
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
matches = HydrusGlobals.client_controller.Read( 'file_system_predicates', search_service_key )
else:
must_do_a_search = False
if '*' in search_text: must_do_a_search = True
if ':' in search_text:
( namespace, half_complete_tag ) = search_text.split( ':', 1 )
if namespace != self._current_namespace:
self._current_namespace = namespace # do a new search, no matter what half_complete tag is
if half_complete_tag != '': must_do_a_search = True
else:
if self._cache_text == self._current_namespace + ':' and half_complete_tag != '': must_do_a_search = True
else:
self._current_namespace = ''
half_complete_tag = search_text
if half_complete_tag == '':
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:
if len( search_text ) < num_autocomplete_chars:
predicates = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, tag = search_text, include_current = self._include_current, include_pending = self._include_pending, add_namespaceless = True )
predicates = ClientSearch.SortPredicates( predicates, 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, half_complete_tag = search_text, include_current = self._include_current, include_pending = self._include_pending, add_namespaceless = True )
self._cached_results = ClientSearch.SortPredicates( self._cached_results, collapse_siblings = False )
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() )
lists_of_current_tags = [ list( tags_manager.GetCurrent( self._tag_service_key ) ) for tags_manager in tags_managers ]
lists_of_pending_tags = [ list( tags_manager.GetPending( self._tag_service_key ) ) for tags_manager in tags_managers ]
current_tags_flat_iterable = itertools.chain.from_iterable( lists_of_current_tags )
pending_tags_flat_iterable = itertools.chain.from_iterable( lists_of_pending_tags )
current_tags_flat = [ tag for tag in current_tags_flat_iterable if ClientSearch.SearchEntryMatchesTag( search_text, tag ) ]
pending_tags_flat = [ tag for tag in pending_tags_flat_iterable if ClientSearch.SearchEntryMatchesTag( search_text, tag ) ]
current_tags_to_count = collections.Counter( current_tags_flat )
pending_tags_to_count = collections.Counter( pending_tags_flat )
tags_to_do = set()
if self._include_current: tags_to_do.update( current_tags_to_count.keys() )
if self._include_pending: tags_to_do.update( pending_tags_to_count.keys() )
predicates = [ ClientData.Predicate( HC.PREDICATE_TYPE_TAG, tag, inclusive = inclusive, counts = { HC.CURRENT : current_tags_to_count[ tag ], HC.PENDING : pending_tags_to_count[ tag ] } ) for tag in tags_to_do ]
predicates = ClientSearch.SortPredicates( predicates, collapse_siblings = True )
self._next_updatelist_is_probably_fast = True
matches = ClientSearch.FilterPredicates( search_text, predicates )
if self._current_namespace != '':
if '*' not in self._current_namespace:
matches.insert( 0, ClientData.Predicate( HC.PREDICATE_TYPE_NAMESPACE, self._current_namespace, inclusive = inclusive ) )
if half_complete_tag != '':
if '*' in self._current_namespace or ( '*' in half_complete_tag and half_complete_tag != '*' ):
matches.insert( 0, ClientData.Predicate( HC.PREDICATE_TYPE_WILDCARD, search_text, inclusive = inclusive ) )
elif '*' in search_text:
matches.insert( 0, ClientData.Predicate( HC.PREDICATE_TYPE_WILDCARD, search_text, inclusive = inclusive ) )
try:
index = matches.index( entry_predicate )
predicate = matches[ index ]
del matches[ index ]
matches.insert( 0, predicate )
except: pass
for match in matches:
if match.GetType() == HC.PREDICATE_TYPE_TAG: match.SetInclusive( inclusive )
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 ClientData.FileSearchContext( file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, include_current_tags = self._include_current, include_pending_tags = self._include_pending )
def IncludeCurrent( self, page_key, value ):
if page_key == self._page_key: self._include_current = value
self._cache_text = ''
self._current_namespace = ''
def IncludePending( self, page_key, value ):
if page_key == self._page_key: self._include_pending = value
self._cache_text = ''
self._current_namespace = ''
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 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 }
tag_censorship_manager = HydrusGlobals.client_controller.GetManager( 'tag_censorship' )
tags = tag_censorship_manager.FilterTags( self._tag_service_key, tags )
if len( tags ) > 0:
if self._expand_parents:
tag_parents_manager = HydrusGlobals.client_controller.GetManager( 'tag_parents' )
parents = set()
for tag in tags:
some_parents = tag_parents_manager.GetParents( self._tag_service_key, tag )
parents.update( some_parents )
parents = tag_censorship_manager.FilterTags( self._tag_service_key, parents )
self._chosen_tag_callable( tags, parents )
else:
self._chosen_tag_callable( tags )
def _ParseSearchText( self ):
raw_entry = self._text_ctrl.GetValue()
search_text = HydrusTags.CleanTag( raw_entry )
entry_predicate = ClientData.Predicate( HC.PREDICATE_TYPE_TAG, search_text )
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
sibling = siblings_manager.GetSibling( search_text )
if sibling is not None:
sibling_predicate = ClientData.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
if sibling_predicate is not None:
self._BroadcastChoices( { sibling_predicate } )
else:
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
if ':' in search_text:
( namespace, other_half ) = search_text.split( ':', 1 )
if other_half != '' 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 = ''
half_complete_tag = search_text
if len( search_text ) < num_autocomplete_chars:
predicates = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, tag = search_text, add_namespaceless = False )
predicates = ClientSearch.SortPredicates( predicates, collapse_siblings = False )
else:
if must_do_a_search or self._cache_text == '' or not half_complete_tag.startswith( self._cache_text ):
self._cache_text = half_complete_tag
self._cached_results = HydrusGlobals.client_controller.Read( 'autocomplete_predicates', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, half_complete_tag = search_text, add_namespaceless = False )
self._cached_results = ClientSearch.SortPredicates( self._cached_results, collapse_siblings = False )
predicates = self._cached_results
self._next_updatelist_is_probably_fast = True
matches = ClientSearch.FilterPredicates( half_complete_tag, predicates, service_key = self._tag_service_key, expand_parents = self._expand_parents )
self._PutAtTopOfMatches( matches, entry_predicate )
if sibling_predicate is not None:
self._PutAtTopOfMatches( matches, sibling_predicate )
return matches
def _PutAtTopOfMatches( self, matches, predicate ):
if self._expand_parents:
parents = []
try:
index = matches.index( predicate )
predicate = matches[ index ]
matches.remove( predicate )
while matches[ index ].GetType() == HC.PREDICATE_TYPE_PARENT:
parent = matches[ index ]
matches.remove( parent )
parents.append( parent )
except:
if predicate.GetType() == HC.PREDICATE_TYPE_TAG:
tag = predicate.GetValue()
parents_manager = HydrusGlobals.client_controller.GetManager( 'tag_parents' )
raw_parents = parents_manager.GetParents( self._tag_service_key, tag )
parents = [ ClientData.Predicate( HC.PREDICATE_TYPE_PARENT, raw_parent ) for raw_parent in raw_parents ]
parents.reverse()
for parent in parents:
matches.insert( 0, parent )
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()
class BufferedWindow( wx.Window ):
def __init__( self, *args, **kwargs ):
wx.Window.__init__( self, *args, **kwargs )
if 'size' in kwargs:
( x, y ) = kwargs[ 'size' ]
self._canvas_bmp = wx.EmptyBitmap( x, y, 24 )
else:
self._canvas_bmp = wx.EmptyBitmap( 20, 20, 24 )
self._dirty = True
self.Bind( wx.EVT_PAINT, self.EventPaint )
self.Bind( wx.EVT_SIZE, self.EventResize )
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
def _Draw( self, dc ):
raise NotImplementedError()
def EventEraseBackground( self, event ): pass
def EventPaint( self, event ):
dc = wx.BufferedPaintDC( self, self._canvas_bmp )
if self._dirty:
self._Draw( dc )
def EventResize( self, event ):
( my_width, my_height ) = self.GetClientSize()
( current_bmp_width, current_bmp_height ) = self._canvas_bmp.GetSize()
if my_width != current_bmp_width or my_height != current_bmp_height:
self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
self._dirty = True
self.Refresh()
class BufferedWindowIcon( BufferedWindow ):
def __init__( self, parent, bmp ):
BufferedWindow.__init__( self, parent, size = bmp.GetSize() )
self._bmp = bmp
def _Draw( self, dc ):
background_colour = self.GetParent().GetBackgroundColour()
dc.SetBackground( wx.Brush( background_colour ) )
dc.Clear()
dc.DrawBitmap( self._bmp, 0, 0 )
self._dirty = False
class BetterChoice( wx.Choice ):
def GetChoice( self ):
selection = self.GetSelection()
if selection != wx.NOT_FOUND: return self.GetClientData( selection )
else: raise Exception( 'Choice not chosen!' )
def SelectClientData( self, client_data ):
for i in range( self.GetCount() ):
if client_data == self.GetClientData( i ):
self.Select( i )
return
class CheckboxCollect( wx.combo.ComboCtrl ):
def __init__( self, parent, page_key = None ):
wx.combo.ComboCtrl.__init__( self, parent, style = wx.CB_READONLY )
self._page_key = page_key
sort_by = HC.options[ 'sort_by' ]
collect_types = set()
for ( sort_by_type, namespaces ) in sort_by: collect_types.update( namespaces )
collect_types = list( [ ( namespace, ( 'namespace', namespace ) ) for namespace in collect_types ] )
collect_types.sort()
ratings_services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
for ratings_service in ratings_services: collect_types.append( ( ratings_service.GetName(), ( 'rating', ratings_service.GetServiceKey() ) ) )
popup = self._Popup( collect_types )
#self.UseAltPopupWindow( True )
self.SetPopupControl( popup )
( collect_types, collect_type_strings ) = popup.GetControl().GetValue()
self.SetCollectTypes( collect_types, collect_type_strings )
def GetChoice( self ): return self._collect_by
def SetCollectTypes( self, collect_types, collect_type_strings ):
if len( collect_type_strings ) > 0:
self.SetValue( 'collect by ' + '-'.join( collect_type_strings ) )
self._collect_by = collect_types
else:
self.SetValue( 'no collections' )
self._collect_by = None
HydrusGlobals.client_controller.pub( 'collect_media', self._page_key, self._collect_by )
class _Popup( wx.combo.ComboPopup ):
def __init__( self, collect_types ):
wx.combo.ComboPopup.__init__( self )
self._collect_types = collect_types
def Create( self, parent ):
self._control = self._Control( parent, self.GetCombo(), self._collect_types )
return True
def GetAdjustedSize( self, preferred_width, preferred_height, max_height ):
return( ( preferred_width, -1 ) )
def GetControl( self ): return self._control
class _Control( wx.CheckListBox ):
def __init__( self, parent, special_parent, collect_types ):
texts = [ text for ( text, data ) in collect_types ] # we do this so it sizes its height properly on init
wx.CheckListBox.__init__( self, parent, choices = texts )
self.Clear()
for ( text, data ) in collect_types: self.Append( text, data )
self._special_parent = special_parent
default = HC.options[ 'default_collect' ]
if default is not None:
strings_we_added = { text for ( text, data ) in collect_types }
strings_to_check = [ s for ( namespace_gumpf, s ) in default if s in strings_we_added ]
self.SetCheckedStrings( strings_to_check )
self.Bind( wx.EVT_CHECKLISTBOX, self.EventChanged )
self.Bind( wx.EVT_LEFT_DOWN, self.EventLeftDown )
# as inspired by http://trac.wxwidgets.org/attachment/ticket/14413/test_clb_workaround.py
# what a clusterfuck
def EventLeftDown( self, event ):
index = self.HitTest( event.GetPosition() )
if index != wx.NOT_FOUND:
self.Check( index, not self.IsChecked( index ) )
self.EventChanged( event )
event.Skip()
def EventChanged( self, event ):
( collect_types, collect_type_strings ) = self.GetValue()
self._special_parent.SetCollectTypes( collect_types, collect_type_strings )
def GetValue( self ):
collect_types = []
for i in self.GetChecked(): collect_types.append( self.GetClientData( i ) )
collect_type_strings = self.GetCheckedStrings()
return ( collect_types, collect_type_strings )
class ChoiceSort( BetterChoice ):
def __init__( self, parent, page_key = None, sort_by = None ):
BetterChoice.__init__( self, parent )
self._page_key = page_key
if sort_by is None: sort_by = HC.options[ 'sort_by' ]
sort_choices = CC.SORT_CHOICES + sort_by
ratings_services = HydrusGlobals.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
for ratings_service in ratings_services:
sort_choices.append( ( 'rating_descend', ratings_service ) )
sort_choices.append( ( 'rating_ascend', ratings_service ) )
for ( sort_by_type, sort_by_data ) in sort_choices:
if sort_by_type == 'system': string = CC.sort_string_lookup[ sort_by_data ]
elif sort_by_type == 'namespaces': string = '-'.join( sort_by_data )
elif sort_by_type == 'rating_descend':
string = sort_by_data.GetName() + ' rating highest first'
sort_by_data = sort_by_data.GetServiceKey()
elif sort_by_type == 'rating_ascend':
string = sort_by_data.GetName() + ' rating lowest first'
sort_by_data = sort_by_data.GetServiceKey()
self.Append( 'sort by ' + string, ( sort_by_type, sort_by_data ) )
try: self.SetSelection( HC.options[ 'default_sort' ] )
except: pass
self.Bind( wx.EVT_CHOICE, self.EventChoice )
HydrusGlobals.client_controller.sub( self, 'ACollectHappened', 'collect_media' )
def _BroadcastSort( self ):
selection = self.GetSelection()
if selection != wx.NOT_FOUND:
sort_by = self.GetClientData( selection )
HydrusGlobals.client_controller.pub( 'sort_media', self._page_key, sort_by )
def ACollectHappened( self, page_key, collect_by ):
if page_key == self._page_key: self._BroadcastSort()
def EventChoice( self, event ):
if self._page_key is not None: self._BroadcastSort()
class ExportPatternButton( wx.Button ):
ID_HASH = 0
ID_TAGS = 1
ID_NN_TAGS = 2
ID_NAMESPACE = 3
ID_TAG = 4
def __init__( self, parent ):
wx.Button.__init__( self, parent, label = 'pattern shortcuts' )
self.Bind( wx.EVT_BUTTON, self.EventButton )
self.Bind( wx.EVT_MENU, self.EventMenu )
def EventMenu( self, event ):
id = event.GetId()
phrase = None
if id == self.ID_HASH: phrase = '{hash}'
if id == self.ID_TAGS: phrase = '{tags}'
if id == self.ID_NN_TAGS: phrase = '{nn tags}'
if id == self.ID_NAMESPACE: phrase = '[...]'
if id == self.ID_TAG: phrase = '(...)'
else: event.Skip()
if phrase is not None: HydrusGlobals.client_controller.pub( 'clipboard', 'text', phrase )
def EventButton( self, event ):
menu = wx.Menu()
menu.Append( -1, 'click on a phrase to copy to clipboard' )
menu.AppendSeparator()
menu.Append( self.ID_HASH, 'the file\'s hash - {hash}' )
menu.Append( self.ID_TAGS, 'all the file\'s tags - {tags}' )
menu.Append( self.ID_NN_TAGS, 'all the file\'s non-namespaced tags - {nn tags}' )
menu.AppendSeparator()
menu.Append( self.ID_NAMESPACE, 'all instances of a particular namespace - [...]' )
menu.AppendSeparator()
menu.Append( self.ID_TAG, 'a particular tag, if the file has it - (...)' )
HydrusGlobals.client_controller.PopupMenu( self, menu )
class FitResistantStaticText( wx.StaticText ):
# this is a huge damn mess! I think I really need to be doing this inside or before the parent's fit, or something
def __init__( self, *args, **kwargs ):
wx.StaticText.__init__( self, *args, **kwargs )
self._wrap = 380
if 'label' in kwargs: self._last_label = kwargs[ 'label' ]
else: self._last_label = ''
def Wrap( self, width ):
self._wrap = width
wx.StaticText.Wrap( self, self._wrap )
( x, y ) = self.GetSize()
if x > self._wrap: x = self._wrap
if x < 150: x = 150
self.SetMinSize( ( x, y ) )
self.SetMaxSize( ( self._wrap, -1 ) )
def SetLabel( self, label ):
if label != self._last_label:
self._last_label = label
wx.StaticText.SetLabel( self, label )
self.Wrap( self._wrap )
class Frame( wx.Frame ):
def __init__( self, *args, **kwargs ):
HydrusGlobals.client_controller.ResetIdleTimer()
wx.Frame.__init__( self, *args, **kwargs )
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
self.SetIcon( wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus.ico' ), wx.BITMAP_TYPE_ICO ) )
def SetInitialSize( self, ( width, height ) ):
( display_width, display_height ) = wx.GetDisplaySize()
width = min( display_width, width )
height = min( display_height, height )
wx.Frame.SetInitialSize( self, ( width, height ) )
min_width = min( 240, width )
min_height = min( 180, height )
self.SetMinSize( ( min_width, min_height ) )
class FrameThatResizes( Frame ):
def __init__( self, *args, **kwargs ):
self._resize_option_prefix = kwargs[ 'resize_option_prefix' ]
del kwargs[ 'resize_option_prefix' ]
Frame.__init__( self, *args, **kwargs )
self._InitialiseSizeAndPosition()
self.Bind( wx.EVT_SIZE, self.EventSpecialResize )
self.Bind( wx.EVT_MOVE, self.EventSpecialMove )
def _InitialiseSizeAndPosition( self ):
client_size = HC.options[ 'client_size' ]
self.SetInitialSize( client_size[ self._resize_option_prefix + 'restored_size' ] )
position = client_size[ self._resize_option_prefix + 'restored_position' ]
display_index = wx.Display.GetFromPoint( position )
if display_index == wx.NOT_FOUND: client_size[ self._resize_option_prefix + 'restored_position' ] = ( 20, 20 )
else:
display = wx.Display( display_index )
geometry = display.GetGeometry()
( p_x, p_y ) = position
x_bad = p_x < geometry.x or p_x > geometry.x + geometry.width
y_bad = p_y < geometry.y or p_y > geometry.y + geometry.height
if x_bad or y_bad: client_size[ self._resize_option_prefix + 'restored_position' ] = ( 20, 20 )
self.SetPosition( client_size[ self._resize_option_prefix + 'restored_position' ] )
if client_size[ self._resize_option_prefix + 'maximised' ]: self.Maximize()
if client_size[ self._resize_option_prefix + 'fullscreen' ]: wx.CallAfter( self.ShowFullScreen, True, wx.FULLSCREEN_ALL )
def _RecordSizeAndPosition( self ):
client_size = HC.options[ 'client_size' ]
client_size[ self._resize_option_prefix + 'maximised' ] = self.IsMaximized()
client_size[ self._resize_option_prefix + 'fullscreen' ] = self.IsFullScreen()
if not ( self.IsMaximized() or self.IsFullScreen() ):
# when dragging window up to be maximised, reported position is sometimes a bit dodgy
display_index = wx.Display.GetFromPoint( self.GetPosition() )
if display_index != wx.NOT_FOUND:
client_size[ self._resize_option_prefix + 'restored_size' ] = tuple( self.GetSize() )
client_size[ self._resize_option_prefix + 'restored_position' ] = tuple( self.GetPosition() )
def EventSpecialMove( self, event ):
self._RecordSizeAndPosition()
event.Skip()
def EventSpecialResize( self, event ):
self._RecordSizeAndPosition()
event.Skip()
class Gauge( wx.Gauge ):
def __init__( self, *args, **kwargs ):
wx.Gauge.__init__( self, *args, **kwargs )
self._actual_max = None
def SetRange( self, max ):
if max > 1000:
self._actual_max = max
wx.Gauge.SetRange( self, 1000 )
else:
self._actual_max = None
wx.Gauge.SetRange( self, max )
def SetValue( self, value ):
if self._actual_max is None: wx.Gauge.SetValue( self, value )
else: wx.Gauge.SetValue( self, min( int( 1000 * ( float( value ) / self._actual_max ) ), 1000 ) )
class ListBook( wx.Panel ):
def __init__( self, *args, **kwargs ):
wx.Panel.__init__( self, *args, **kwargs )
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
self._names_to_active_pages = {}
self._names_to_proto_pages = {}
self._list_box = self.LB( self, style = wx.LB_SINGLE | wx.LB_SORT )
self._empty_panel = wx.Panel( self )
self._empty_panel.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
self._current_name = None
self._current_panel = self._empty_panel
self._panel_sizer = wx.BoxSizer( wx.VERTICAL )
self._panel_sizer.AddF( self._empty_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( self._list_box, CC.FLAGS_EXPAND_PERPENDICULAR )
hbox.AddF( self._panel_sizer, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self._list_box.Bind( wx.EVT_LISTBOX, self.EventSelection )
self.SetSizer( hbox )
self.Bind( wx.EVT_MENU, self.EventMenu )
class LB( wx.ListBox ):
def FindString( self, name ):
if HC.PLATFORM_WINDOWS: return wx.ListBox.FindString( self, name )
else:
for i in range( self.GetCount() ):
if self.GetString( i ) == name: return i
return wx.NOT_FOUND
def _RecalcListBoxWidth( self ): self.Layout()
def _Select( self, selection ):
if selection == wx.NOT_FOUND: self._current_name = None
else: self._current_name = self._list_box.GetString( selection )
self._current_panel.Hide()
self._list_box.SetSelection( selection )
if selection == wx.NOT_FOUND: self._current_panel = self._empty_panel
else:
if self._current_name in self._names_to_proto_pages:
( classname, args, kwargs ) = self._names_to_proto_pages[ self._current_name ]
page = classname( *args, **kwargs )
page.Hide()
self._panel_sizer.AddF( page, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self._names_to_active_pages[ self._current_name ] = page
del self._names_to_proto_pages[ self._current_name ]
self._RecalcListBoxWidth()
self._current_panel = self._names_to_active_pages[ self._current_name ]
self._current_panel.Show()
self.Layout()
self.Refresh()
event = wx.NotifyEvent( wx.wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED, -1 )
self.ProcessEvent( event )
def AddPage( self, name, page, select = False ):
if type( page ) != tuple:
page.Hide()
self._panel_sizer.AddF( page, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self._list_box.Append( name )
self._names_to_active_pages[ name ] = page
self._RecalcListBoxWidth()
if self._list_box.GetCount() == 1: self._Select( 0 )
elif select: self._Select( self._list_box.FindString( name ) )
def AddPageArgs( self, name, classname, args, kwargs ):
if self.NameExists( name ):
raise HydrusExceptions.NameException( 'That name is already in use!' )
self._list_box.Append( name )
self._names_to_proto_pages[ name ] = ( classname, args, kwargs )
self._RecalcListBoxWidth()
if self._list_box.GetCount() == 1: self._Select( 0 )
def DeleteAllPages( self ):
self._panel_sizer.Detach( self._empty_panel )
self._panel_sizer.Clear( deleteWindows = True )
self._panel_sizer.AddF( self._empty_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self._current_name = None
self._current_panel = self._empty_panel
self._names_to_active_pages = {}
self._names_to_proto_pages = {}
self._list_box.Clear()
def DeleteCurrentPage( self ):
selection = self._list_box.GetSelection()
if selection != wx.NOT_FOUND:
name_to_delete = self._current_name
page_to_delete = self._current_panel
next_selection = selection + 1
previous_selection = selection - 1
if next_selection < self._list_box.GetCount(): self._Select( next_selection )
elif previous_selection >= 0: self._Select( previous_selection )
else: self._Select( wx.NOT_FOUND )
self._panel_sizer.Detach( page_to_delete )
wx.CallAfter( page_to_delete.Destroy )
del self._names_to_active_pages[ name_to_delete ]
self._list_box.Delete( self._list_box.FindString( name_to_delete ) )
self._RecalcListBoxWidth()
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 == 'select_down': self.SelectDown()
elif command == 'select_up': self.SelectUp()
else: event.Skip()
def EventSelection( self, event ):
if self._list_box.GetSelection() != self._list_box.FindString( self._current_name ):
event = wx.NotifyEvent( wx.wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING, -1 )
self.GetEventHandler().ProcessEvent( event )
if event.IsAllowed(): self._Select( self._list_box.GetSelection() )
else: self._list_box.SetSelection( self._list_box.FindString( self._current_name ) )
def GetCurrentName( self ): return self._current_name
def GetCurrentPage( self ):
if self._current_panel == self._empty_panel: return None
else: return self._current_panel
def GetNames( self ):
names = set()
names.update( self._names_to_proto_pages.keys() )
names.update( self._names_to_active_pages.keys() )
return names
def GetNamesToActivePages( self ):
return self._names_to_active_pages
def NameExists( self, name ): return self._list_box.FindString( name ) != wx.NOT_FOUND
def RenamePage( self, name, new_name ):
if self.NameExists( new_name ): raise HydrusExceptions.NameException( 'That name is already in use!' )
if self._current_name == name: self._current_name = new_name
if name in self._names_to_active_pages:
dict_to_rename = self._names_to_active_pages
else:
dict_to_rename = self._names_to_proto_pages
page_info = dict_to_rename[ name ]
del dict_to_rename[ name ]
dict_to_rename[ new_name ] = page_info
self._list_box.SetString( self._list_box.FindString( name ), new_name )
self._RecalcListBoxWidth()
def Select( self, name ):
selection = self._list_box.FindString( name )
if selection != wx.NOT_FOUND and selection != self._list_box.GetSelection():
event = wx.NotifyEvent( wx.wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING, -1 )
self.GetEventHandler().ProcessEvent( event )
if event.IsAllowed(): self._Select( selection )
def SelectDown( self ):
current_selection = self._list_box.FindString( self._current_name )
if current_selection != wx.NOT_FOUND:
num_entries = self._list_box.GetCount()
if current_selection == num_entries - 1: selection = 0
else: selection = current_selection + 1
if selection != current_selection: self._Select( selection )
def SelectPage( self, page_to_select ):
for ( name, page ) in self._names_to_active_pages.items():
if page == page_to_select:
self._Select( self._list_box.FindString( name ) )
return
def SelectUp( self ):
current_selection = self._list_box.FindString( self._current_name )
if current_selection != wx.NOT_FOUND:
num_entries = self._list_box.GetCount()
if current_selection == 0: selection = num_entries - 1
else: selection = current_selection - 1
if selection != current_selection: self._Select( selection )
class ListBox( wx.ScrolledWindow ):
delete_key_activates = False
def __init__( self, parent, min_height = 250 ):
wx.ScrolledWindow.__init__( self, parent, style = wx.VSCROLL | wx.BORDER_DOUBLE )
self._background_colour = wx.Colour( 255, 255, 255 )
self._ordered_strings = []
self._strings_to_terms = {}
self._client_bmp = wx.EmptyBitmap( 20, 20, 24 )
self._selected_indices = set()
self._selected_terms = set()
self._last_hit_index = None
self._last_view_start = None
self._dirty = True
dc = wx.MemoryDC( self._client_bmp )
dc.SetFont( wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT ) )
( text_x, self._text_y ) = dc.GetTextExtent( 'abcdefghijklmnopqrstuvwxyz' )
self._num_rows_per_page = 0
self.SetScrollRate( 0, self._text_y )
self.SetMinSize( ( 50, min_height ) )
self.Bind( wx.EVT_PAINT, self.EventPaint )
self.Bind( wx.EVT_SIZE, self.EventResize )
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
self.Bind( wx.EVT_LEFT_DOWN, self.EventMouseSelect )
self.Bind( wx.EVT_LEFT_DCLICK, self.EventDClick )
self.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown )
self.Bind( wx.EVT_CHAR_HOOK, self.EventKeyDown )
def __len__( self ):
return len( self._ordered_strings )
def _Activate( self ):
raise NotImplementedError()
def _Deselect( self, index ):
term = self._strings_to_terms[ self._ordered_strings[ index ] ]
self._selected_indices.discard( index )
self._selected_terms.discard( term )
def _GetIndexUnderMouse( self, mouse_event ):
( xUnit, yUnit ) = self.GetScrollPixelsPerUnit()
( x_scroll, y_scroll ) = self.GetViewStart()
y_offset = y_scroll * yUnit
y = mouse_event.GetY() + y_offset
row_index = ( y / self._text_y )
if row_index >= len( self._ordered_strings ):
return None
return row_index
def _GetTextColour( self, text ): return ( 0, 111, 250 )
def _Hit( self, shift, ctrl, hit_index ):
if hit_index is not None:
if hit_index == -1 or hit_index > len( self._ordered_strings ):
hit_index = len( self._ordered_strings ) - 1
elif hit_index == len( self._ordered_strings ) or hit_index < -1:
hit_index = 0
to_select = set()
to_deselect = set()
if shift:
if hit_index is not None:
if self._last_hit_index is not None:
lower = min( hit_index, self._last_hit_index )
upper = max( hit_index, self._last_hit_index )
to_select = range( lower, upper + 1 )
else:
to_select.add( hit_index )
elif ctrl:
if hit_index is not None:
if hit_index in self._selected_indices:
to_deselect.add( hit_index )
else:
to_select.add( hit_index )
else:
if hit_index is None:
to_deselect = set( self._selected_indices )
else:
if hit_index not in self._selected_indices:
to_select.add( hit_index )
to_deselect = set( self._selected_indices )
for index in to_select:
self._Select( index )
for index in to_deselect:
self._Deselect( index )
self._last_hit_index = hit_index
if self._last_hit_index is not None:
y = self._text_y * self._last_hit_index
( start_x, start_y ) = self.GetViewStart()
( x_unit, y_unit ) = self.GetScrollPixelsPerUnit()
( width, height ) = self.GetClientSize()
if y < start_y * y_unit:
y_to_scroll_to = y / y_unit
self.Scroll( -1, y_to_scroll_to )
wx.PostEvent( self, wx.ScrollWinEvent( wx.wxEVT_SCROLLWIN_THUMBRELEASE ) )
elif y > ( start_y * y_unit ) + height - self._text_y:
y_to_scroll_to = ( y - height ) / y_unit
self.Scroll( -1, y_to_scroll_to + 2 )
wx.PostEvent( self, wx.ScrollWinEvent( wx.wxEVT_SCROLLWIN_THUMBRELEASE ) )
self._SetDirty()
def _Redraw( self, dc ):
( xUnit, yUnit ) = self.GetScrollPixelsPerUnit()
( x_scroll, y_scroll ) = self.GetViewStart()
self._last_view_start = self.GetViewStart()
y_offset = y_scroll * yUnit
( my_width, my_height ) = self.GetClientSize()
first_visible_index = y_offset / self._text_y
last_visible_index = ( y_offset + my_height ) / self._text_y
if ( y_offset + my_height ) % self._text_y != 0:
last_visible_index += 1
last_visible_index = min( last_visible_index, len( self._ordered_strings ) - 1 )
dc.SetFont( wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT ) )
dc.SetBackground( wx.Brush( self._background_colour ) )
dc.Clear()
for ( i, current_index ) in enumerate( range( first_visible_index, last_visible_index + 1 ) ):
text = self._ordered_strings[ current_index ]
( r, g, b ) = self._GetTextColour( text )
text_colour = wx.Colour( r, g, b )
if current_index in self._selected_indices:
dc.SetBrush( wx.Brush( text_colour ) )
dc.SetPen( wx.TRANSPARENT_PEN )
dc.DrawRectangle( 0, i * self._text_y, my_width, self._text_y )
text_colour = wx.WHITE
dc.SetTextForeground( text_colour )
( x, y ) = ( 3, i * self._text_y )
dc.DrawText( text, x, y )
self._dirty = False
def _Select( self, index ):
term = self._strings_to_terms[ self._ordered_strings[ index ] ]
self._selected_indices.add( index )
self._selected_terms.add( term )
def _SetDirty( self ):
self._dirty = True
self.Refresh()
def _TextsHaveChanged( self ):
previous_selected_terms = self._selected_terms
self._selected_indices = set()
self._selected_terms = set()
for ( s, term ) in self._strings_to_terms.items():
if term in previous_selected_terms:
index = self._ordered_strings.index( s )
self._Select( index )
( my_x, my_y ) = self.GetClientSize()
total_height = max( self._text_y * len( self._ordered_strings ), my_y )
( virtual_x, virtual_y ) = self.GetVirtualSize()
if total_height != virtual_y:
wx.PostEvent( self, wx.SizeEvent() )
else:
self._SetDirty()
def EventDClick( self, event ):
self._Activate()
def EventEraseBackground( self, event ): pass
def EventKeyDown( self, event ):
shift = event.ShiftDown()
ctrl = event.CmdDown() or event.ControlDown()
key_code = event.GetKeyCode()
if key_code in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ) or ( self.delete_key_activates and key_code in ( wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE ) ):
self._Activate()
else:
if ctrl and key_code in ( ord( 'A' ), ord( 'a' ) ):
for i in range( len( self._ordered_strings ) ):
self._Select( i )
self._SetDirty()
else:
hit_index = None
if len( self._ordered_strings ) > 0:
if key_code in ( wx.WXK_HOME, wx.WXK_NUMPAD_HOME ):
hit_index = 0
elif key_code in ( wx.WXK_END, wx.WXK_NUMPAD_END ):
hit_index = len( self._ordered_strings ) - 1
elif self._last_hit_index is not None:
if key_code in ( wx.WXK_UP, wx.WXK_NUMPAD_UP ):
hit_index = self._last_hit_index - 1
elif key_code in ( wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN ):
hit_index = self._last_hit_index + 1
elif key_code in ( wx.WXK_PAGEUP, wx.WXK_NUMPAD_PAGEUP ):
hit_index = max( 0, self._last_hit_index - self._num_rows_per_page )
elif key_code in ( wx.WXK_PAGEDOWN, wx.WXK_NUMPAD_PAGEDOWN ):
hit_index = min( len( self._ordered_strings ) - 1, self._last_hit_index + self._num_rows_per_page )
if hit_index is None:
event.Skip()
else:
self._Hit( shift, ctrl, hit_index )
def EventMouseSelect( self, event ):
hit_index = self._GetIndexUnderMouse( event )
shift = event.ShiftDown()
ctrl = event.CmdDown() or event.ControlDown()
self._Hit( shift, ctrl, hit_index )
event.Skip()
def EventPaint( self, event ):
( my_x, my_y ) = self.GetClientSize()
if ( my_x, my_y ) != self._client_bmp.GetSize():
self._client_bmp = wx.EmptyBitmap( my_x, my_y, 24 )
self._dirty = True
dc = wx.BufferedPaintDC( self, self._client_bmp )
if self._dirty or self._last_view_start != self.GetViewStart():
self._Redraw( dc )
def EventResize( self, event ):
( my_x, my_y ) = self.GetClientSize()
self._num_rows_per_page = my_y / self._text_y
ideal_virtual_size = ( my_x, max( self._text_y * len( self._ordered_strings ), my_y ) )
if ideal_virtual_size != self.GetVirtualSize():
self.SetVirtualSize( ideal_virtual_size )
self._SetDirty()
def GetClientData( self, s = None ):
if s is None: return self._strings_to_terms.values()
else: return self._strings_to_terms[ s ]
def SetTexts( self, ordered_strings ):
if ordered_strings != self._ordered_strings:
self._ordered_strings = ordered_strings
self._strings_to_terms = { s : s for s in ordered_strings }
self._TextsHaveChanged()
class ListBoxTags( ListBox ):
has_counts = False
def __init__( self, *args, **kwargs ):
ListBox.__init__( self, *args, **kwargs )
self._background_colour = wx.Colour( *HC.options[ 'gui_colours' ][ 'tags_box' ] )
self.Bind( wx.EVT_RIGHT_DOWN, self.EventMouseRightClick )
self.Bind( wx.EVT_MIDDLE_DOWN, self.EventMouseMiddleClick )
self.Bind( wx.EVT_MENU, self.EventMenu )
def _GetNamespaceColours( self ): return HC.options[ 'namespace_colours' ]
def _GetAllTagsForClipboard( self, with_counts = False ):
return self._ordered_strings
def _GetTextColour( self, tag_string ):
namespace_colours = self._GetNamespaceColours()
if ':' in tag_string:
( namespace, sub_tag ) = tag_string.split( ':', 1 )
if namespace.startswith( '-' ): namespace = namespace[1:]
if namespace.startswith( '(+) ' ): namespace = namespace[4:]
if namespace.startswith( '(-) ' ): namespace = namespace[4:]
if namespace.startswith( '(X) ' ): namespace = namespace[4:]
if namespace.startswith( ' ' ): namespace = namespace[4:]
if namespace in namespace_colours: ( r, g, b ) = namespace_colours[ namespace ]
else: ( r, g, b ) = namespace_colours[ None ]
else: ( r, g, b ) = namespace_colours[ '' ]
return ( r, g, b )
def _NewSearchPage( self ):
predicates = []
for term in self._selected_terms:
if isinstance( term, ClientData.Predicate ):
predicates.append( term )
else:
predicates.append( ClientData.Predicate( HC.PREDICATE_TYPE_TAG, term ) )
predicates = FlushOutPredicates( self, predicates )
if len( predicates ) > 0:
HydrusGlobals.client_controller.pub( 'new_page_query', CC.LOCAL_FILE_SERVICE_KEY, initial_predicates = predicates )
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 in ( 'copy_terms', 'copy_sub_terms', 'copy_all_tags', 'copy_all_tags_with_counts' ):
if command in ( 'copy_terms', 'copy_sub_terms' ):
texts = []
for term in self._selected_terms:
if isinstance( term, ClientData.Predicate ):
text = term.GetUnicode()
else:
text = term
if command == 'copy_sub_terms' and ':' in text:
( namespace_gumpf, text ) = text.split( ':', 1 )
texts.append( text )
texts.sort()
text = os.linesep.join( texts )
elif command == 'copy_all_tags':
text = os.linesep.join( self._GetAllTagsForClipboard() )
elif command == 'copy_all_tags_with_counts':
text = os.linesep.join( self._GetAllTagsForClipboard( with_counts = True ) )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', text )
elif command == 'new_search_page':
self._NewSearchPage()
elif command in ( 'parent', 'sibling' ):
import ClientGUIDialogsManage
if command == 'parent':
with ClientGUIDialogsManage.DialogManageTagParents( self, self._selected_terms ) as dlg: dlg.ShowModal()
elif command == 'sibling':
with ClientGUIDialogsManage.DialogManageTagSiblings( self, self._selected_terms ) as dlg: dlg.ShowModal()
else:
event.Skip()
return # this is about select_up and select_down
def EventMouseMiddleClick( self, event ):
hit_index = self._GetIndexUnderMouse( event )
shift = event.ShiftDown()
ctrl = event.CmdDown() or event.ControlDown()
self._Hit( shift, ctrl, hit_index )
self._NewSearchPage()
def EventMouseRightClick( self, event ):
hit_index = self._GetIndexUnderMouse( event )
shift = event.ShiftDown()
ctrl = event.CmdDown() or event.ControlDown()
self._Hit( shift, ctrl, hit_index )
if len( self._ordered_strings ) > 0:
menu = wx.Menu()
if len( self._selected_terms ) > 0:
if len( self._selected_terms ) == 1:
( term, ) = self._selected_terms
if type( term ) == ClientData.Predicate:
s = term.GetUnicode()
else:
s = term
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'new_search_page' ), 'open a new search page for ' + s )
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_terms' ), 'copy "' + s + '"')
if ':' in s:
sub_s = s.split( ':', 1 )[1]
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_sub_terms' ), 'copy "' + sub_s + '"' )
else:
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'new_search_page' ), 'open a new search page for selection' )
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_terms' ), 'copy selected tags' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_sub_terms' ), 'copy selected subtags' )
if len( self._ordered_strings ) > len( self._selected_terms ):
if menu.GetMenuItemCount() > 0:
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_all_tags' ), 'copy all tags' )
if self.has_counts: menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_all_tags_with_counts' ), 'copy all tags with counts' )
if len( self._selected_terms ) > 0:
term_types = [ type( term ) for term in self._selected_terms ]
if str in term_types or unicode in term_types:
if menu.GetMenuItemCount() > 0:
menu.AppendSeparator()
if len( self._selected_terms ) == 1:
( tag, ) = self._selected_terms
text = tag
else:
text = 'selection'
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'parent' ), 'add parents to ' + text )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'sibling' ), 'add siblings to ' + text )
HydrusGlobals.client_controller.PopupMenu( self, menu )
event.Skip()
def GetSelectedTags( self ):
return self._selected_terms
class ListBoxTagsAutocompleteDropdown( ListBoxTags ):
has_counts = True
def __init__( self, parent, callable, **kwargs ):
ListBoxTags.__init__( self, parent, **kwargs )
self._callable = callable
self._predicates = {}
def _Activate( self ):
predicates = [ term for term in self._selected_terms if term.GetType() != HC.PREDICATE_TYPE_PARENT ]
predicates = FlushOutPredicates( self, predicates )
if len( predicates ) > 0:
self._callable( predicates )
def _GetWithParentIndices( self, index ):
indices = [ index ]
index += 1
while index < len( self._ordered_strings ):
term = self._strings_to_terms[ self._ordered_strings[ index ] ]
if term.GetType() == HC.PREDICATE_TYPE_PARENT:
indices.append( index )
else:
break
index += 1
return indices
def _Deselect( self, index ):
to_deselect = self._GetWithParentIndices( index )
for index in to_deselect:
ListBoxTags._Deselect( self, index )
def _GetAllTagsForClipboard( self, with_counts = False ):
return [ self._strings_to_terms[ s ].GetUnicode( with_counts ) for s in self._ordered_strings ]
def _Hit( self, shift, ctrl, hit_index ):
if hit_index is not None:
if hit_index == -1 or hit_index > len( self._ordered_strings ):
hit_index = len( self._ordered_strings ) - 1
elif hit_index == len( self._ordered_strings ) or hit_index < -1:
hit_index = 0
# this realigns the hit index in the up direction
hit_term = self._strings_to_terms[ self._ordered_strings[ hit_index ] ]
while hit_term.GetType() == HC.PREDICATE_TYPE_PARENT:
hit_index -= 1
hit_term = self._strings_to_terms[ self._ordered_strings[ hit_index ] ]
ListBoxTags._Hit( self, shift, ctrl, hit_index )
def _Select( self, index ):
to_select = self._GetWithParentIndices( index )
for index in to_select:
ListBoxTags._Select( self, index )
def EventKeyDown( self, event ):
# this realigns the hit index in the down direction
key_code = event.GetKeyCode()
hit_index = None
if len( self._ordered_strings ) > 0:
if key_code in ( wx.WXK_END, wx.WXK_NUMPAD_END ):
hit_index = len( self._ordered_strings ) - 1
elif self._last_hit_index is not None:
if key_code in ( wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN ):
hit_index = self._last_hit_index + 1
elif key_code in ( wx.WXK_PAGEDOWN, wx.WXK_NUMPAD_PAGEDOWN ):
hit_index = min( len( self._ordered_strings ) - 1, self._last_hit_index + self._num_rows_per_page )
if hit_index is None:
ListBoxTags.EventKeyDown( self, event )
else:
if hit_index >= len( self._ordered_strings ):
hit_index = 0
hit_term = self._strings_to_terms[ self._ordered_strings[ hit_index ] ]
while hit_term.GetType() == HC.PREDICATE_TYPE_PARENT:
hit_index += 1
if hit_index >= len( self._ordered_strings ):
hit_index = 0
hit_term = self._strings_to_terms[ self._ordered_strings[ hit_index ] ]
shift = event.ShiftDown()
ctrl = event.CmdDown() or event.ControlDown()
self._Hit( shift, ctrl, hit_index )
def SetPredicates( self, predicates ):
# need to do a clever compare, since normal predicate compare doesn't take count into account
they_are_the_same = True
if len( predicates ) == len( self._predicates ):
p_list_1 = list( predicates )
p_list_2 = list( self._predicates )
p_list_1.sort()
p_list_2.sort()
for index in range( len( p_list_1 ) ):
p_1 = p_list_1[ index ]
p_2 = p_list_2[ index ]
if p_1 != p_2 or p_1.GetCount() != p_2.GetCount():
they_are_the_same = False
break
else: they_are_the_same = False
if not they_are_the_same:
self._predicates = predicates
self._ordered_strings = []
self._strings_to_terms = {}
for predicate in predicates:
tag_string = predicate.GetUnicode()
self._ordered_strings.append( tag_string )
self._strings_to_terms[ tag_string ] = predicate
self._TextsHaveChanged()
if len( predicates ) > 0:
self._Hit( False, False, None )
self._Hit( False, False, 0 )
class ListBoxTagsCensorship( ListBoxTags ):
def _Activate( self ):
if len( self._selected_terms ) > 0:
tags = set( self._selected_terms )
for tag in tags:
self._RemoveTag( tag )
self._TextsHaveChanged()
def _AddTag( self, tag ):
tag_string = self._GetTagString( tag )
if tag_string not in self._strings_to_terms:
self._ordered_strings.append( tag_string )
self._strings_to_terms[ tag_string ] = tag
def _GetTagString( self, tag ):
if tag == '': return 'unnamespaced'
elif tag == ':': return 'namespaced'
else: return tag
def _RemoveTag( self, tag ):
tag_string = self._GetTagString( tag )
if tag_string in self._strings_to_terms:
tag_string = self._GetTagString( tag )
self._ordered_strings.remove( tag_string )
del self._strings_to_terms[ tag_string ]
def AddTags( self, tags ):
for tag in tags:
self._AddTag( tag )
self._TextsHaveChanged()
def EnterTags( self, tags ):
for tag in tags:
tag_string = self._GetTagString( tag )
if tag_string in self._strings_to_terms:
self._RemoveTag( tag )
else:
self._AddTag( tag )
self._TextsHaveChanged()
def _RemoveTags( self, tags ):
for tag in tags:
self._RemoveTag( tag )
self._TextsHaveChanged()
class ListBoxTagsColourOptions( ListBoxTags ):
def __init__( self, parent, initial_namespace_colours ):
ListBoxTags.__init__( self, parent )
self._namespace_colours = dict( initial_namespace_colours )
for namespace in self._namespace_colours:
if namespace is None: namespace_string = 'default namespace:tag'
elif namespace == '': namespace_string = 'unnamespaced tag'
else: namespace_string = namespace + ':tag'
self._ordered_strings.append( namespace_string )
self._strings_to_terms[ namespace_string ] = namespace
self._TextsHaveChanged()
def _Activate( self ):
if len( self._selected_terms ) > 0:
self._RemoveNamespaces( self._selected_terms )
def _GetNamespaceColours( self ): return self._namespace_colours
def _RemoveNamespaces( self, namespaces ):
for namespace in namespaces:
if namespace is not None and namespace != '':
namespace_string = namespace + ':tag'
self._ordered_strings.remove( namespace_string )
del self._strings_to_terms[ namespace_string ]
del self._namespace_colours[ namespace ]
self._TextsHaveChanged()
def SetNamespaceColour( self, namespace, colour ):
if namespace not in self._namespace_colours:
namespace_string = namespace + ':tag'
self._ordered_strings.append( namespace_string )
self._strings_to_terms[ namespace_string ] = namespace
self._ordered_strings.sort()
self._namespace_colours[ namespace ] = colour.Get()
self._TextsHaveChanged()
def GetNamespaceColours( self ): return self._namespace_colours
def GetSelectedNamespaceColours( self ):
results = []
for namespace in self._selected_terms:
( r, g, b ) = self._namespace_colours[ namespace ]
colour = wx.Colour( r, g, b )
results.append( ( namespace, colour ) )
return results
class ListBoxTagsStrings( ListBoxTags ):
def __init__( self, parent, removed_callable = None ):
ListBoxTags.__init__( self, parent )
self._removed_callable = removed_callable
self._tags = set()
def _RecalcTags( self ):
self._strings_to_terms = {}
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
for tag in self._tags:
tag_string = tag
sibling = siblings_manager.GetSibling( tag )
if sibling is not None: tag_string += ' (will display as ' + sibling + ')'
self._strings_to_terms[ tag_string ] = tag
self._ordered_strings = self._strings_to_terms.keys()
self._ordered_strings.sort()
self._TextsHaveChanged()
def _Activate( self ):
if len( self._selected_terms ) > 0:
tags = set( self._selected_terms )
self._RemoveTags( tags )
def _RemoveTags( self, tags ):
self._tags.difference_update( tags )
self._RecalcTags()
if self._removed_callable is not None:
self._removed_callable( tags )
def AddTags( self, tags ):
self._tags.update( tags )
self._RecalcTags()
def EnterTags( self, tags ):
removed = set()
for tag in tags:
if tag in self._tags:
self._tags.discard( tag )
removed.add( tag )
else:
self._tags.add( tag )
self._RecalcTags()
if len( removed ) > 0 and self._removed_callable is not None:
self._removed_callable( removed )
def EventKeyDown( self, event ):
if event.KeyCode in ( wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE ):
self._Activate()
else:
event.Skip()
def GetTags( self ):
return self._tags
def RemoveTags( self, tags ):
self._RemoveTags( tags )
def SetTags( self, tags ):
self._tags = set()
for tag in tags:
self._tags.add( tag )
self._RecalcTags()
class ListBoxTagsPredicates( ListBoxTags ):
delete_key_activates = True
has_counts = False
def __init__( self, parent, page_key, initial_predicates = None ):
if initial_predicates is None: initial_predicates = []
ListBoxTags.__init__( self, parent, min_height = 100 )
self._page_key = page_key
if len( initial_predicates ) > 0:
for predicate in initial_predicates:
predicate_string = predicate.GetUnicode()
self._ordered_strings.append( predicate_string )
self._strings_to_terms[ predicate_string ] = predicate
self._TextsHaveChanged()
HydrusGlobals.client_controller.sub( self, 'EnterPredicates', 'enter_predicates' )
def _Activate( self ):
if len( self._selected_terms ) > 0:
self._EnterPredicates( set( self._selected_terms ) )
def _EnterPredicates( self, predicates ):
if len( predicates ) == 0:
return
inbox_predicate = ClientSearch.SYSTEM_PREDICATE_INBOX
archive_predicate = ClientSearch.SYSTEM_PREDICATE_ARCHIVE
local_predicate = ClientSearch.SYSTEM_PREDICATE_LOCAL
not_local_predicate = ClientSearch.SYSTEM_PREDICATE_NOT_LOCAL
predicates_to_be_added = set()
predicates_to_be_removed = set()
for predicate in predicates:
predicate = predicate.GetCountlessCopy()
if self._HasPredicate( predicate ):
predicates_to_be_removed.add( predicate )
else:
predicates_to_be_added.add( predicate )
if predicate == inbox_predicate and self._HasPredicate( archive_predicate ):
predicates_to_be_removed.add( archive_predicate )
elif predicate == archive_predicate and self._HasPredicate( inbox_predicate ):
predicates_to_be_removed.add( inbox_predicate )
if predicate == local_predicate and self._HasPredicate( not_local_predicate ):
predicates_to_be_removed.add( not_local_predicate )
elif predicate == not_local_predicate and self._HasPredicate( local_predicate ):
predicates_to_be_removed.add( local_predicate )
for predicate in predicates_to_be_added:
predicate_string = predicate.GetUnicode()
self._ordered_strings.append( predicate_string )
self._strings_to_terms[ predicate_string ] = predicate
for predicate in predicates_to_be_removed:
for ( s, existing_predicate ) in self._strings_to_terms.items():
if existing_predicate == predicate:
self._ordered_strings.remove( s )
del self._strings_to_terms[ s ]
break
self._ordered_strings.sort()
self._TextsHaveChanged()
HydrusGlobals.client_controller.pub( 'refresh_query', self._page_key )
def _GetAllTagsForClipboard( self, with_counts = False ):
return [ self._strings_to_terms[ s ].GetUnicode( with_counts ) for s in self._ordered_strings ]
def _HasPredicate( self, predicate ): return predicate in self._strings_to_terms.values()
def EnterPredicates( self, page_key, predicates ):
if page_key == self._page_key:
self._EnterPredicates( predicates )
def GetPredicates( self ):
return self._strings_to_terms.values()
class ListBoxTagsSelection( ListBoxTags ):
has_counts = True
def __init__( self, parent, include_counts = True, collapse_siblings = False ):
ListBoxTags.__init__( self, parent, min_height = 200 )
self._sort = HC.options[ 'default_tag_sort' ]
if not include_counts and self._sort not in ( CC.SORT_BY_LEXICOGRAPHIC_ASC, CC.SORT_BY_LEXICOGRAPHIC_DESC ):
self._sort = CC.SORT_BY_LEXICOGRAPHIC_ASC
self._last_media = set()
self._tag_service_key = CC.COMBINED_TAG_SERVICE_KEY
self._include_counts = include_counts
self._collapse_siblings = collapse_siblings
self._current_tags_to_count = collections.Counter()
self._deleted_tags_to_count = collections.Counter()
self._pending_tags_to_count = collections.Counter()
self._petitioned_tags_to_count = collections.Counter()
self._show_current = True
self._show_deleted = False
self._show_pending = True
self._show_petitioned = True
def _GetAllTagsForClipboard( self, with_counts = False ):
if with_counts:
return self._ordered_strings
else:
return [ self._strings_to_terms[ s ] for s in self._ordered_strings ]
def _RecalcStrings( self ):
siblings_manager = HydrusGlobals.client_controller.GetManager( 'tag_siblings' )
all_tags = set()
if self._show_current: all_tags.update( ( tag for ( tag, count ) in self._current_tags_to_count.items() if count > 0 ) )
if self._show_deleted: all_tags.update( ( tag for ( tag, count ) in self._deleted_tags_to_count.items() if count > 0 ) )
if self._show_pending: all_tags.update( ( tag for ( tag, count ) in self._pending_tags_to_count.items() if count > 0 ) )
if self._show_petitioned: all_tags.update( ( tag for ( tag, count ) in self._petitioned_tags_to_count.items() if count > 0 ) )
self._ordered_strings = []
self._strings_to_terms = {}
for tag in all_tags:
tag_string = tag
if self._include_counts:
if self._show_current and tag in self._current_tags_to_count: tag_string += ' (' + HydrusData.ConvertIntToPrettyString( self._current_tags_to_count[ tag ] ) + ')'
if self._show_pending and tag in self._pending_tags_to_count: tag_string += ' (+' + HydrusData.ConvertIntToPrettyString( self._pending_tags_to_count[ tag ] ) + ')'
if self._show_petitioned and tag in self._petitioned_tags_to_count: tag_string += ' (-' + HydrusData.ConvertIntToPrettyString( self._petitioned_tags_to_count[ tag ] ) + ')'
if self._show_deleted and tag in self._deleted_tags_to_count: tag_string += ' (X' + HydrusData.ConvertIntToPrettyString( self._deleted_tags_to_count[ tag ] ) + ')'
else:
if self._show_pending and tag in self._pending_tags_to_count: tag_string += ' (+)'
if self._show_petitioned and tag in self._petitioned_tags_to_count: tag_string += ' (-)'
if self._show_deleted and tag in self._deleted_tags_to_count: tag_string += ' (X)'
if not self._collapse_siblings:
sibling = siblings_manager.GetSibling( tag )
if sibling is not None:
tag_string += ' (will display as ' + sibling + ')'
self._ordered_strings.append( tag_string )
self._strings_to_terms[ tag_string ] = tag
self._SortTags()
def _SortTags( self ):
if self._sort == CC.SORT_BY_LEXICOGRAPHIC_ASC: compare_function = lambda a, b: cmp( a, b )
elif self._sort == CC.SORT_BY_LEXICOGRAPHIC_DESC: compare_function = lambda a, b: cmp( b, a )
elif self._sort in ( CC.SORT_BY_INCIDENCE_ASC, CC.SORT_BY_INCIDENCE_DESC ):
tags_to_count = collections.defaultdict( lambda: 0 )
tags_to_count.update( self._current_tags_to_count )
for ( tag, count ) in self._pending_tags_to_count.items(): tags_to_count[ tag ] += count
if self._sort == CC.SORT_BY_INCIDENCE_ASC: compare_function = lambda a, b: cmp( ( tags_to_count[ self._strings_to_terms[ a ] ], a ), ( tags_to_count[ self._strings_to_terms[ b ] ], b ) )
elif self._sort == CC.SORT_BY_INCIDENCE_DESC: compare_function = lambda a, b: cmp( ( tags_to_count[ self._strings_to_terms[ b ] ], a ), ( tags_to_count[ self._strings_to_terms[ a ] ], b ) )
self._ordered_strings.sort( compare_function )
self._TextsHaveChanged()
def ChangeTagRepository( self, service_key ):
self._tag_service_key = service_key
self.SetTagsByMedia( self._last_media, force_reload = True )
def SetSort( self, sort ):
self._sort = sort
self._SortTags()
def SetShow( self, show_type, value ):
if show_type == 'current': self._show_current = value
elif show_type == 'deleted': self._show_deleted = value
elif show_type == 'pending': self._show_pending = value
elif show_type == 'petitioned': self._show_petitioned = value
self._RecalcStrings()
def SetTagsByMedia( self, media, force_reload = False ):
media = set( media )
if force_reload:
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = ClientData.GetMediasTagCount( media, tag_service_key = self._tag_service_key, collapse_siblings = self._collapse_siblings )
self._current_tags_to_count = current_tags_to_count
self._deleted_tags_to_count = deleted_tags_to_count
self._pending_tags_to_count = pending_tags_to_count
self._petitioned_tags_to_count = petitioned_tags_to_count
else:
removees = self._last_media.difference( media )
adds = media.difference( self._last_media )
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = ClientData.GetMediasTagCount( removees, tag_service_key = self._tag_service_key, collapse_siblings = self._collapse_siblings )
self._current_tags_to_count.subtract( current_tags_to_count )
self._deleted_tags_to_count.subtract( deleted_tags_to_count )
self._pending_tags_to_count.subtract( pending_tags_to_count )
self._petitioned_tags_to_count.subtract( petitioned_tags_to_count )
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = ClientData.GetMediasTagCount( adds, tag_service_key = self._tag_service_key, collapse_siblings = self._collapse_siblings )
self._current_tags_to_count.update( current_tags_to_count )
self._deleted_tags_to_count.update( deleted_tags_to_count )
self._pending_tags_to_count.update( pending_tags_to_count )
self._petitioned_tags_to_count.update( petitioned_tags_to_count )
for counter in ( self._current_tags_to_count, self._deleted_tags_to_count, self._pending_tags_to_count, self._petitioned_tags_to_count ):
tags = counter.keys()
for tag in tags:
if counter[ tag ] == 0: del counter[ tag ]
self._RecalcStrings()
self._last_media = media
class ListBoxTagsSelectionHoverFrame( ListBoxTagsSelection ):
def __init__( self, parent, canvas_key ):
ListBoxTagsSelection.__init__( self, parent, include_counts = False, collapse_siblings = True )
self._canvas_key = canvas_key
def _Activate( self ):
HydrusGlobals.client_controller.pub( 'canvas_manage_tags', self._canvas_key )
class ListBoxTagsSelectionManagementPanel( ListBoxTagsSelection ):
def __init__( self, parent, page_key ):
ListBoxTagsSelection.__init__( self, parent, include_counts = True, collapse_siblings = True )
self._page_key = page_key
HydrusGlobals.client_controller.sub( self, 'SetTagsByMediaPubsub', 'new_tags_selection' )
HydrusGlobals.client_controller.sub( self, 'ChangeTagRepositoryPubsub', 'change_tag_repository' )
def _Activate( self ):
predicates = [ ClientData.Predicate( HC.PREDICATE_TYPE_TAG, term ) for term in self._selected_terms ]
if len( predicates ) > 0:
HydrusGlobals.client_controller.pub( 'enter_predicates', self._page_key, predicates )
def ChangeTagRepositoryPubsub( self, page_key, service_key ):
if page_key == self._page_key: self.ChangeTagRepository( service_key )
def SetTagsByMediaPubsub( self, page_key, media, force_reload = False ):
if page_key == self._page_key: self.SetTagsByMedia( media, force_reload = force_reload )
class ListBoxTagsSelectionTagsDialog( ListBoxTagsSelection ):
delete_key_activates = True
def __init__( self, parent, callable ):
ListBoxTagsSelection.__init__( self, parent, include_counts = True, collapse_siblings = False )
self._callable = callable
def _Activate( self ):
if len( self._selected_terms ) > 0:
self._callable( self._selected_terms )
class ListCtrlAutoWidth( wx.ListCtrl, ListCtrlAutoWidthMixin ):
def __init__( self, parent, height ):
wx.ListCtrl.__init__( self, parent, size=( -1, height ), style=wx.LC_REPORT )
ListCtrlAutoWidthMixin.__init__( self )
def GetAllSelected( self ):
indices = []
i = self.GetFirstSelected()
while i != -1:
indices.append( i )
i = self.GetNextSelected( i )
return indices
def RemoveAllSelected( self ):
indices = self.GetAllSelected()
indices.reverse() # so we don't screw with the indices of deletees below
for index in indices: self.DeleteItem( index )
class NoneableSpinCtrl( wx.Panel ):
def __init__( self, parent, message, none_phrase = 'no limit', min = 0, max = 1000000, multiplier = 1, num_dimensions = 1 ):
wx.Panel.__init__( self, parent )
self._num_dimensions = num_dimensions
self._multiplier = multiplier
self._checkbox = wx.CheckBox( self, label = none_phrase )
self._checkbox.Bind( wx.EVT_CHECKBOX, self.EventCheckBox )
self._one = wx.SpinCtrl( self, min = min, max = max, size = ( 60, -1 ) )
if num_dimensions == 2: self._two = wx.SpinCtrl( self, initial = 0, min = min, max = max, size = ( 60, -1 ) )
hbox = wx.BoxSizer( wx.HORIZONTAL )
if len( message ) > 0:
hbox.AddF( wx.StaticText( self, label = message + ': ' ), CC.FLAGS_MIXED )
hbox.AddF( self._one, CC.FLAGS_MIXED )
if self._num_dimensions == 2:
hbox.AddF( wx.StaticText( self, label = 'x' ), CC.FLAGS_MIXED )
hbox.AddF( self._two, CC.FLAGS_MIXED )
hbox.AddF( self._checkbox, CC.FLAGS_MIXED )
self.SetSizer( hbox )
def Bind( self, event_type, callback ):
self._checkbox.Bind( wx.EVT_CHECKBOX, callback )
self._one.Bind( wx.EVT_SPINCTRL, callback )
if self._num_dimensions == 2: self._two.Bind( wx.EVT_SPINCTRL, callback )
def EventCheckBox( self, event ):
if self._checkbox.GetValue():
self._one.Disable()
if self._num_dimensions == 2: self._two.Disable()
else:
self._one.Enable()
if self._num_dimensions == 2: self._two.Enable()
def GetValue( self ):
if self._checkbox.GetValue(): return None
else:
if self._num_dimensions == 2: return ( self._one.GetValue() * self._multiplier, self._two.GetValue() * self._multiplier )
else: return self._one.GetValue() * self._multiplier
def SetValue( self, value ):
if value is None:
self._checkbox.SetValue( True )
self._one.Disable()
if self._num_dimensions == 2: self._two.Disable()
else:
self._checkbox.SetValue( False )
if self._num_dimensions == 2:
self._two.Enable()
( value, y ) = value
self._two.SetValue( y / self._multiplier )
self._one.Enable()
self._one.SetValue( value / self._multiplier )
class OnOffButton( wx.Button ):
def __init__( self, parent, page_key, topic, on_label, off_label = None, start_on = True ):
if start_on: label = on_label
else: label = off_label
wx.Button.__init__( self, parent, label = label )
self._page_key = page_key
self._topic = topic
self._on_label = on_label
if off_label is None: self._off_label = on_label
else: self._off_label = off_label
self._on = start_on
if self._on: self.SetForegroundColour( ( 0, 128, 0 ) )
else: self.SetForegroundColour( ( 128, 0, 0 ) )
self.Bind( wx.EVT_BUTTON, self.EventButton )
HydrusGlobals.client_controller.sub( self, 'HitButton', 'hit_on_off_button' )
def EventButton( self, event ):
if self._on:
self._on = False
self.SetLabel( self._off_label )
self.SetForegroundColour( ( 128, 0, 0 ) )
HydrusGlobals.client_controller.pub( self._topic, self._page_key, False )
else:
self._on = True
self.SetLabel( self._on_label )
self.SetForegroundColour( ( 0, 128, 0 ) )
HydrusGlobals.client_controller.pub( self._topic, self._page_key, True )
def IsOn( self ): return self._on
class PopupWindow( wx.Window ):
def __init__( self, parent ):
wx.Window.__init__( self, parent, style = wx.BORDER_SIMPLE )
self.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
def TryToDismiss( self ): self.GetParent().Dismiss( self )
def EventDismiss( self, event ): self.TryToDismiss()
class PopupDismissAll( PopupWindow ):
def __init__( self, parent ):
PopupWindow.__init__( self, parent )
hbox = wx.BoxSizer( wx.HORIZONTAL )
self._text = wx.StaticText( self )
self._text.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
button = wx.Button( self, label = 'dismiss all' )
button.Bind( wx.EVT_BUTTON, self.EventButton )
button.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
hbox.AddF( self._text, CC.FLAGS_MIXED )
hbox.AddF( button, CC.FLAGS_MIXED )
self.SetSizer( hbox )
def TryToDismiss( self ): pass
def EventButton( self, event ): self.GetParent().DismissAll()
def SetNumMessages( self, num_messages_pending ): self._text.SetLabel( HydrusData.ConvertIntToPrettyString( num_messages_pending ) + ' more messages' )
class PopupMessage( PopupWindow ):
def __init__( self, parent, job_key ):
PopupWindow.__init__( self, parent )
self._job_key = job_key
vbox = wx.BoxSizer( wx.VERTICAL )
self._title = FitResistantStaticText( self, style = wx.ALIGN_CENTER )
self._title.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._title.Hide()
self._text_1 = FitResistantStaticText( self )
self._text_1.Wrap( 380 )
self._text_1.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._text_1.Hide()
self._gauge_1 = Gauge( self, size = ( 380, -1 ) )
self._gauge_1.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._gauge_1.Hide()
self._text_2 = FitResistantStaticText( self )
self._text_2.Wrap( 380 )
self._text_2.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._text_2.Hide()
self._gauge_2 = Gauge( self, size = ( 380, -1 ) )
self._gauge_2.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._gauge_2.Hide()
self._show_files_button = wx.Button( self )
self._show_files_button.Bind( wx.EVT_BUTTON, self.EventShowFilesButton )
self._show_files_button.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._show_files_button.Hide()
self._show_tb_button = wx.Button( self, label = 'show traceback' )
self._show_tb_button.Bind( wx.EVT_BUTTON, self.EventShowTBButton )
self._show_tb_button.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._show_tb_button.Hide()
self._tb_text = FitResistantStaticText( self )
self._tb_text.Wrap( 380 )
self._tb_text.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._tb_text.Hide()
self._show_caller_tb_button = wx.Button( self, label = 'show caller traceback' )
self._show_caller_tb_button.Bind( wx.EVT_BUTTON, self.EventShowCallerTBButton )
self._show_caller_tb_button.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._show_caller_tb_button.Hide()
self._caller_tb_text = FitResistantStaticText( self )
self._caller_tb_text.Wrap( 380 )
self._caller_tb_text.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._caller_tb_text.Hide()
self._show_db_tb_button = wx.Button( self, label = 'show db traceback' )
self._show_db_tb_button.Bind( wx.EVT_BUTTON, self.EventShowDBTBButton )
self._show_db_tb_button.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._show_db_tb_button.Hide()
self._db_tb_text = FitResistantStaticText( self )
self._db_tb_text.Wrap( 380 )
self._db_tb_text.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._db_tb_text.Hide()
self._copy_tb_button = wx.Button( self, label = 'copy traceback information' )
self._copy_tb_button.Bind( wx.EVT_BUTTON, self.EventCopyTBButton )
self._copy_tb_button.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._copy_tb_button.Hide()
self._pause_button = wx.BitmapButton( self, bitmap = CC.GlobalBMPs.pause )
self._pause_button.Bind( wx.EVT_BUTTON, self.EventPauseButton )
self._pause_button.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._pause_button.Hide()
self._cancel_button = wx.BitmapButton( self, bitmap = CC.GlobalBMPs.stop )
self._cancel_button.Bind( wx.EVT_BUTTON, self.EventCancelButton )
self._cancel_button.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss )
self._cancel_button.Hide()
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( self._pause_button, CC.FLAGS_MIXED )
hbox.AddF( self._cancel_button, CC.FLAGS_MIXED )
vbox.AddF( self._title, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._text_1, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._gauge_1, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._text_2, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._gauge_2, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._show_files_button, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._show_tb_button, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tb_text, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._show_caller_tb_button, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._caller_tb_text, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._show_db_tb_button, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._db_tb_text, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._copy_tb_button, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
def _ProcessText( self, text ):
if len( text ) > TEXT_CUTOFF:
new_text = 'The text is too long to display here. Here is the start of it (the rest is printed to the log):'
new_text += os.linesep * 2
new_text += text[:TEXT_CUTOFF]
text = new_text
return text
def EventCancelButton( self, event ):
self._job_key.Cancel()
self._pause_button.Disable()
self._cancel_button.Disable()
def EventCopyTBButton( self, event ):
HydrusGlobals.client_controller.pub( 'clipboard', 'text', self._job_key.ToString() )
def EventPauseButton( self, event ):
self._job_key.PausePlay()
if self._job_key.IsPaused():
self._pause_button.SetBitmap( CC.GlobalBMPs.play )
else:
self._pause_button.SetBitmap( CC.GlobalBMPs.pause )
def EventShowCallerTBButton( self, event ):
if self._caller_tb_text.IsShown():
self._show_caller_tb_button.SetLabel( 'show caller traceback' )
self._caller_tb_text.Hide()
else:
self._show_caller_tb_button.SetLabel( 'hide caller traceback' )
self._caller_tb_text.Show()
self.GetParent().MakeSureEverythingFits()
def EventShowDBTBButton( self, event ):
if self._db_tb_text.IsShown():
self._show_db_tb_button.SetLabel( 'show db traceback' )
self._db_tb_text.Hide()
else:
self._show_db_tb_button.SetLabel( 'hide db traceback' )
self._db_tb_text.Show()
self.GetParent().MakeSureEverythingFits()
def EventShowFilesButton( self, event ):
hashes = self._job_key.GetVariable( 'popup_files' )
media_results = HydrusGlobals.client_controller.Read( 'media_results', CC.LOCAL_FILE_SERVICE_KEY, hashes )
HydrusGlobals.client_controller.pub( 'new_page_query', CC.LOCAL_FILE_SERVICE_KEY, initial_media_results = media_results )
def EventShowTBButton( self, event ):
if self._tb_text.IsShown():
self._show_tb_button.SetLabel( 'show traceback' )
self._tb_text.Hide()
else:
self._show_tb_button.SetLabel( 'hide traceback' )
self._tb_text.Show()
self.GetParent().MakeSureEverythingFits()
def TryToDismiss( self ):
if self._job_key.IsPausable() or self._job_key.IsCancellable(): return
else: PopupWindow.TryToDismiss( self )
def Update( self ):
if self._job_key.IsDeleted():
self.TryToDismiss()
return
if self._job_key.HasVariable( 'popup_title' ):
text = self._job_key.GetVariable( 'popup_title' )
if self._title.GetLabel() != text: self._title.SetLabel( text )
self._title.Show()
else: self._title.Hide()
if self._job_key.HasVariable( 'popup_text_1' ) and not self._job_key.IsPaused():
text = self._job_key.GetVariable( 'popup_text_1' )
if self._text_1.GetLabel() != text: self._text_1.SetLabel( self._ProcessText( HydrusData.ToUnicode( text ) ) )
self._text_1.Show()
else: self._text_1.Hide()
if self._job_key.HasVariable( 'popup_gauge_1' ) and not self._job_key.IsPaused():
( gauge_value, gauge_range ) = self._job_key.GetVariable( 'popup_gauge_1' )
if gauge_range is None or gauge_value is None: self._gauge_1.Pulse()
else:
self._gauge_1.SetRange( gauge_range )
self._gauge_1.SetValue( gauge_value )
self._gauge_1.Show()
else: self._gauge_1.Hide()
if self._job_key.HasVariable( 'popup_text_2' ) and not self._job_key.IsPaused():
text = self._job_key.GetVariable( 'popup_text_2' )
if self._text_2.GetLabel() != text: self._text_2.SetLabel( self._ProcessText( HydrusData.ToUnicode( text ) ) )
self._text_2.Show()
else: self._text_2.Hide()
if self._job_key.HasVariable( 'popup_gauge_2' ) and not self._job_key.IsPaused():
( gauge_value, gauge_range ) = self._job_key.GetVariable( 'popup_gauge_2' )
if gauge_range is None or gauge_value is None: self._gauge_2.Pulse()
else:
self._gauge_2.SetRange( gauge_range )
self._gauge_2.SetValue( gauge_value )
self._gauge_2.Show()
else: self._gauge_2.Hide()
if self._job_key.HasVariable( 'popup_files' ):
hashes = self._job_key.GetVariable( 'popup_files' )
text = 'show ' + HydrusData.ConvertIntToPrettyString( len( hashes ) ) + ' files'
if self._show_files_button.GetLabel() != text: self._show_files_button.SetLabel( text )
self._show_files_button.Show()
else: self._show_files_button.Hide()
if self._job_key.HasVariable( 'popup_traceback' ) or self._job_key.HasVariable( 'popup_caller_traceback' ) or self._job_key.HasVariable( 'popup_db_traceback' ): self._copy_tb_button.Show()
else: self._copy_tb_button.Hide()
if self._job_key.HasVariable( 'popup_traceback' ):
text = self._job_key.GetVariable( 'popup_traceback' )
if self._tb_text.GetLabel() != text: self._tb_text.SetLabel( self._ProcessText( HydrusData.ToUnicode( text ) ) )
self._show_tb_button.Show()
else:
self._show_tb_button.Hide()
self._tb_text.Hide()
if self._job_key.HasVariable( 'popup_caller_traceback' ):
text = self._job_key.GetVariable( 'popup_caller_traceback' )
if self._caller_tb_text.GetLabel() != text: self._caller_tb_text.SetLabel( self._ProcessText( HydrusData.ToUnicode( text ) ) )
self._show_caller_tb_button.Show()
else:
self._show_caller_tb_button.Hide()
self._caller_tb_text.Hide()
if self._job_key.HasVariable( 'popup_db_traceback' ):
text = self._job_key.GetVariable( 'popup_db_traceback' )
if self._db_tb_text.GetLabel() != text: self._db_tb_text.SetLabel( self._ProcessText( HydrusData.ToUnicode( text ) ) )
self._show_db_tb_button.Show()
else:
self._show_db_tb_button.Hide()
self._db_tb_text.Hide()
if self._job_key.IsPausable(): self._pause_button.Show()
else: self._pause_button.Hide()
if self._job_key.IsCancellable(): self._cancel_button.Show()
else: self._cancel_button.Hide()
class PopupMessageManager( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__( self, parent, style = wx.FRAME_TOOL_WINDOW | wx.FRAME_NO_TASKBAR | wx.FRAME_FLOAT_ON_PARENT | wx.BORDER_NONE )
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
self._max_messages_to_display = 10
vbox = wx.BoxSizer( wx.VERTICAL )
self._message_vbox = wx.BoxSizer( wx.VERTICAL )
self._dismiss_all = PopupDismissAll( self )
self._dismiss_all.Hide()
vbox.AddF( self._message_vbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( self._dismiss_all, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
self._pending_job_keys = []
parent.Bind( wx.EVT_SIZE, self.EventMove )
parent.Bind( wx.EVT_MOVE, self.EventMove )
HydrusGlobals.client_controller.sub( self, 'AddMessage', 'message' )
self._old_excepthook = sys.excepthook
self._old_show_exception = HydrusData.ShowException
sys.excepthook = ClientData.CatchExceptionClient
HydrusData.ShowException = ClientData.ShowExceptionClient
HydrusData.ShowText = ClientData.ShowTextClient
self.Bind( wx.EVT_TIMER, self.TIMEREvent, id = ID_TIMER_POPUP )
self._timer = wx.Timer( self, id = ID_TIMER_POPUP )
self._timer.Start( 500, wx.TIMER_CONTINUOUS )
def _CheckPending( self ):
num_messages_displayed = self._message_vbox.GetItemCount()
if len( self._pending_job_keys ) > 0 and num_messages_displayed < self._max_messages_to_display:
job_key = self._pending_job_keys.pop( 0 )
window = PopupMessage( self, job_key )
window.Update()
self._message_vbox.AddF( window, CC.FLAGS_EXPAND_PERPENDICULAR )
num_messages_pending = len( self._pending_job_keys )
if num_messages_pending > 0:
self._dismiss_all.SetNumMessages( num_messages_pending )
self._dismiss_all.Show()
else: self._dismiss_all.Hide()
self._SizeAndPositionAndShow()
def _SizeAndPositionAndShow( self ):
try:
self.Fit()
parent = self.GetParent()
( parent_width, parent_height ) = parent.GetClientSize()
( my_width, my_height ) = self.GetClientSize()
my_x = ( parent_width - my_width ) - 5
my_y = ( parent_height - my_height ) - 15
self.SetPosition( parent.ClientToScreenXY( my_x, my_y ) )
num_messages_displayed = self._message_vbox.GetItemCount()
if num_messages_displayed > 0:
current_focus = wx.Window.FindFocus()
tlp = wx.GetTopLevelParent( current_focus )
show_happened = self.Show()
if show_happened and tlp is not None:
self.Raise()
tlp.Raise()
else: self.Hide()
except:
# I don't understand the error here.
# It happened for someone in Fit(), causing 'C++ assertion 'm_hDWP failed at blah ... EndRepositioningChildren Shouldn't be called'
# It might be related to an id-cache overflow error I had before, in which case it is fixed
text = 'The popup message manager experienced a fatal error and will now stop working! Please restart the client as soon as possible! If this keeps happening, please email the details and your client.log to the hydrus developer.'
HydrusData.Print( text )
HydrusData.Print( traceback.format_exc() )
wx.MessageBox( text )
self._timer.Stop()
self.CleanBeforeDestroy()
self.Destroy()
def AddMessage( self, job_key ):
try:
self._pending_job_keys.append( job_key )
self._CheckPending()
except: HydrusData.Print( traceback.format_exc() )
def CleanBeforeDestroy( self ):
sys.excepthook = self._old_excepthook
HydrusData.ShowException = self._old_show_exception
def Dismiss( self, window ):
self._message_vbox.Detach( window )
wx.CallAfter( window.Destroy )
self._SizeAndPositionAndShow()
self._CheckPending()
def DismissAll( self ):
self._pending_job_keys = [ job_key for job_key in self._pending_job_keys if job_key.IsPausable() or job_key.IsCancellable() ]
sizer_items = self._message_vbox.GetChildren()
for sizer_item in sizer_items:
message_window = sizer_item.GetWindow()
message_window.TryToDismiss()
self._CheckPending()
def EventMove( self, event ):
self._SizeAndPositionAndShow()
event.Skip()
def MakeSureEverythingFits( self ): self._SizeAndPositionAndShow()
def TIMEREvent( self, event ):
try:
if HydrusGlobals.view_shutdown:
self.Destroy()
return
sizer_items = self._message_vbox.GetChildren()
for sizer_item in sizer_items:
message_window = sizer_item.GetWindow()
message_window.Update()
self._SizeAndPositionAndShow()
except wx.PyDeadObjectError:
self._timer.Stop()
except:
self._timer.Stop()
raise
class RatingLike( wx.Window ):
def __init__( self, parent, service_key ):
wx.Window.__init__( self, parent )
self._service_key = service_key
self._canvas_bmp = wx.EmptyBitmap( 16, 16, 24 )
self.Bind( wx.EVT_PAINT, self.EventPaint )
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
self.Bind( wx.EVT_LEFT_DOWN, self.EventLeftDown )
self.Bind( wx.EVT_LEFT_DCLICK, self.EventLeftDown )
self.Bind( wx.EVT_RIGHT_DOWN, self.EventRightDown )
self.Bind( wx.EVT_RIGHT_DCLICK, self.EventRightDown )
self.SetMinSize( ( 16, 16 ) )
self._dirty = True
def _Draw( self, dc ):
raise NotImplementedError()
def EventEraseBackground( self, event ): pass
def EventLeftDown( self, event ):
raise NotImplementedError()
def EventPaint( self, event ):
dc = wx.BufferedPaintDC( self, self._canvas_bmp )
if self._dirty:
self._Draw( dc )
def EventRightDown( self, event ):
raise NotImplementedError()
def GetServiceKey( self ):
return self._service_key
class RatingLikeDialog( RatingLike ):
def __init__( self, parent, service_key ):
RatingLike.__init__( self, parent, service_key )
self._rating_state = ClientRatings.NULL
def _Draw( self, dc ):
dc.SetBackground( wx.Brush( self.GetParent().GetBackgroundColour() ) )
dc.Clear()
( pen_colour, brush_colour ) = ClientRatings.GetPenAndBrushColours( self._service_key, self._rating_state )
ClientRatings.DrawLike( dc, 0, 0, self._service_key, self._rating_state )
self._dirty = False
def EventLeftDown( self, event ):
if self._rating_state == ClientRatings.LIKE: self._rating_state = ClientRatings.NULL
else: self._rating_state = ClientRatings.LIKE
self._dirty = True
self.Refresh()
def EventRightDown( self, event ):
if self._rating_state == ClientRatings.DISLIKE: self._rating_state = ClientRatings.NULL
else: self._rating_state = ClientRatings.DISLIKE
self._dirty = True
self.Refresh()
def GetRatingState( self ):
return self._rating_state
def SetRatingState( self, rating_state ):
self._rating_state = rating_state
self._dirty = True
self.Refresh()
class RatingLikeCanvas( RatingLike ):
def __init__( self, parent, service_key, canvas_key ):
RatingLike.__init__( self, parent, service_key )
self._canvas_key = canvas_key
self._current_media = None
self._rating_state = None
service = HydrusGlobals.client_controller.GetServicesManager().GetService( service_key )
name = service.GetName()
self.SetToolTipString( name )
HydrusGlobals.client_controller.sub( self, 'ProcessContentUpdates', 'content_updates_gui' )
HydrusGlobals.client_controller.sub( self, 'SetDisplayMedia', 'canvas_new_display_media' )
def _Draw( self, dc ):
dc.SetBackground( wx.Brush( self.GetParent().GetBackgroundColour() ) )
dc.Clear()
if self._current_media is not None:
self._rating_state = ClientRatings.GetLikeStateFromMedia( ( self._current_media, ), self._service_key )
ClientRatings.DrawLike( dc, 0, 0, self._service_key, self._rating_state )
self._dirty = False
def EventLeftDown( self, event ):
if self._current_media is not None:
if self._rating_state == ClientRatings.LIKE: rating = None
else: rating = 1
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, self._hashes ) )
HydrusGlobals.client_controller.Write( 'content_updates', { self._service_key : ( content_update, ) } )
def EventRightDown( self, event ):
if self._current_media is not None:
if self._rating_state == ClientRatings.DISLIKE: rating = None
else: rating = 0
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, self._hashes ) )
HydrusGlobals.client_controller.Write( 'content_updates', { self._service_key : ( content_update, ) } )
def ProcessContentUpdates( self, service_keys_to_content_updates ):
if self._current_media is not None:
for ( service_key, content_updates ) in service_keys_to_content_updates.items():
for content_update in content_updates:
( data_type, action, row ) = content_update.ToTuple()
if data_type == HC.CONTENT_TYPE_RATINGS:
hashes = content_update.GetHashes()
if len( self._hashes.intersection( hashes ) ) > 0:
self._dirty = True
self.Refresh()
return
def SetDisplayMedia( self, canvas_key, media ):
if canvas_key == self._canvas_key:
self._current_media = media
if self._current_media is None:
self._hashes = set()
else:
self._hashes = self._current_media.GetHashes()
self._dirty = True
self.Refresh()
class RatingNumerical( wx.Window ):
def __init__( self, parent, service_key ):
wx.Window.__init__( self, parent )
self._service_key = service_key
self._service = HydrusGlobals.client_controller.GetServicesManager().GetService( self._service_key )
self._num_stars = self._service.GetInfo( 'num_stars' )
self._allow_zero = self._service.GetInfo( 'allow_zero' )
my_width = ClientRatings.GetNumericalWidth( self._service_key )
self._canvas_bmp = wx.EmptyBitmap( my_width, 16, 24 )
self.Bind( wx.EVT_PAINT, self.EventPaint )
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
self.Bind( wx.EVT_LEFT_DOWN, self.EventLeftDown )
self.Bind( wx.EVT_LEFT_DCLICK, self.EventLeftDown )
self.Bind( wx.EVT_RIGHT_DOWN, self.EventRightDown )
self.Bind( wx.EVT_RIGHT_DCLICK, self.EventRightDown )
self.SetMinSize( ( my_width, 16 ) )
self._dirty = True
def _Draw( self, dc ):
raise NotImplementedError()
def _GetRatingFromClickEvent( self, event ):
x = event.GetX()
y = event.GetY()
( my_width, my_height ) = self.GetClientSize()
# assuming a border of 2 on every side here
my_active_width = my_width - 4
my_active_height = my_height - 4
x_adjusted = x - 2
y_adjusted = y - 2
if 0 <= y and y <= my_active_height:
if 0 <= x and x <= my_active_width:
proportion_filled = float( x_adjusted ) / my_active_width
if self._allow_zero:
rating = round( proportion_filled * self._num_stars ) / self._num_stars
else:
rating = float( int( proportion_filled * self._num_stars ) ) / ( self._num_stars - 1 )
return rating
return None
def EventEraseBackground( self, event ): pass
def EventLeftDown( self, event ):
raise NotImplementedError()
def EventPaint( self, event ):
dc = wx.BufferedPaintDC( self, self._canvas_bmp )
if self._dirty:
self._Draw( dc )
def EventRightDown( self, event ):
raise NotImplementedError()
def GetServiceKey( self ):
return self._service_key
class RatingNumericalDialog( RatingNumerical ):
def __init__( self, parent, service_key ):
RatingNumerical.__init__( self, parent, service_key )
self._rating_state = ClientRatings.NULL
self._rating = None
def _Draw( self, dc ):
dc.SetBackground( wx.Brush( self.GetParent().GetBackgroundColour() ) )
dc.Clear()
ClientRatings.DrawNumerical( dc, 0, 0, self._service_key, self._rating_state, self._rating )
self._dirty = False
def EventLeftDown( self, event ):
rating = self._GetRatingFromClickEvent( event )
if rating is not None:
self._rating_state = ClientRatings.SET
self._rating = rating
self._dirty = True
self.Refresh()
def EventRightDown( self, event ):
self._rating_state = ClientRatings.NULL
self._dirty = True
self.Refresh()
def GetRating( self ):
return self._rating
def GetRatingState( self ):
return self._rating_state
def SetRating( self, rating ):
self._rating_state = ClientRatings.SET
self._rating = rating
self._dirty = True
self.Refresh()
def SetRatingState( self, rating_state ):
self._rating_state = rating_state
self._dirty = True
self.Refresh()
class RatingNumericalCanvas( RatingNumerical ):
def __init__( self, parent, service_key, canvas_key ):
RatingNumerical.__init__( self, parent, service_key )
self._canvas_key = canvas_key
self._current_media = None
self._rating_state = None
self._rating = None
name = self._service.GetName()
self.SetToolTipString( name )
HydrusGlobals.client_controller.sub( self, 'ProcessContentUpdates', 'content_updates_gui' )
HydrusGlobals.client_controller.sub( self, 'SetDisplayMedia', 'canvas_new_display_media' )
def _Draw( self, dc ):
dc.SetBackground( wx.Brush( self.GetParent().GetBackgroundColour() ) )
dc.Clear()
if self._current_media is not None:
( self._rating_state, self._rating ) = ClientRatings.GetNumericalStateFromMedia( ( self._current_media, ), self._service_key )
ClientRatings.DrawNumerical( dc, 0, 0, self._service_key, self._rating_state, self._rating )
self._dirty = False
def EventLeftDown( self, event ):
if self._current_media is not None:
rating = self._GetRatingFromClickEvent( event )
if rating is not None:
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, self._hashes ) )
HydrusGlobals.client_controller.Write( 'content_updates', { self._service_key : ( content_update, ) } )
def EventRightDown( self, event ):
if self._current_media is not None:
rating = None
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, self._hashes ) )
HydrusGlobals.client_controller.Write( 'content_updates', { self._service_key : ( content_update, ) } )
def ProcessContentUpdates( self, service_keys_to_content_updates ):
if self._current_media is not None:
for ( service_key, content_updates ) in service_keys_to_content_updates.items():
for content_update in content_updates:
( data_type, action, row ) = content_update.ToTuple()
if data_type == HC.CONTENT_TYPE_RATINGS:
hashes = content_update.GetHashes()
if len( self._hashes.intersection( hashes ) ) > 0:
self._dirty = True
self.Refresh()
return
def SetDisplayMedia( self, canvas_key, media ):
if canvas_key == self._canvas_key:
self._current_media = media
if self._current_media is None:
self._hashes = set()
else:
self._hashes = self._current_media.GetHashes()
self._dirty = True
self.Refresh()
class RegexButton( wx.Button ):
ID_REGEX_WHITESPACE = 0
ID_REGEX_NUMBER = 1
ID_REGEX_ALPHANUMERIC = 2
ID_REGEX_ANY = 3
ID_REGEX_BEGINNING = 4
ID_REGEX_END = 5
ID_REGEX_0_OR_MORE_GREEDY = 6
ID_REGEX_1_OR_MORE_GREEDY = 7
ID_REGEX_0_OR_1_GREEDY = 8
ID_REGEX_0_OR_MORE_MINIMAL = 9
ID_REGEX_1_OR_MORE_MINIMAL = 10
ID_REGEX_0_OR_1_MINIMAL = 11
ID_REGEX_EXACTLY_M = 12
ID_REGEX_M_TO_N_GREEDY = 13
ID_REGEX_M_TO_N_MINIMAL = 14
ID_REGEX_LOOKAHEAD = 15
ID_REGEX_NEGATIVE_LOOKAHEAD = 16
ID_REGEX_LOOKBEHIND = 17
ID_REGEX_NEGATIVE_LOOKBEHIND = 18
ID_REGEX_NUMBER_WITHOUT_ZEROES = 19
ID_REGEX_BACKSPACE = 22
ID_REGEX_SET = 23
ID_REGEX_NOT_SET = 24
ID_REGEX_FILENAME = 25
ID_REGEX_MANAGE_FAVOURITES = 26
ID_REGEX_FAVOURITES = range( 100, 200 )
def __init__( self, parent ):
wx.Button.__init__( self, parent, label = 'regex shortcuts' )
self.Bind( wx.EVT_BUTTON, self.EventButton )
self.Bind( wx.EVT_MENU, self.EventMenu )
def EventButton( self, event ):
menu = wx.Menu()
menu.Append( -1, 'click on a phrase to copy to clipboard' )
menu.AppendSeparator()
submenu = wx.Menu()
submenu.Append( self.ID_REGEX_WHITESPACE, r'whitespace character - \s' )
submenu.Append( self.ID_REGEX_NUMBER, r'number character - \d' )
submenu.Append( self.ID_REGEX_ALPHANUMERIC, r'alphanumeric or backspace character - \w' )
submenu.Append( self.ID_REGEX_ANY, r'any character - .' )
submenu.Append( self.ID_REGEX_BACKSPACE, r'backspace character - \\' )
submenu.Append( self.ID_REGEX_BEGINNING, r'beginning of line - ^' )
submenu.Append( self.ID_REGEX_END, r'end of line - $' )
submenu.Append( self.ID_REGEX_SET, r'any of these - [...]' )
submenu.Append( self.ID_REGEX_NOT_SET, r'anything other than these - [^...]' )
submenu.AppendSeparator()
submenu.Append( self.ID_REGEX_0_OR_MORE_GREEDY, r'0 or more matches, consuming as many as possible - *' )
submenu.Append( self.ID_REGEX_1_OR_MORE_GREEDY, r'1 or more matches, consuming as many as possible - +' )
submenu.Append( self.ID_REGEX_0_OR_1_GREEDY, r'0 or 1 matches, preferring 1 - ?' )
submenu.Append( self.ID_REGEX_0_OR_MORE_MINIMAL, r'0 or more matches, consuming as few as possible - *?' )
submenu.Append( self.ID_REGEX_1_OR_MORE_MINIMAL, r'1 or more matches, consuming as few as possible - +?' )
submenu.Append( self.ID_REGEX_0_OR_1_MINIMAL, r'0 or 1 matches, preferring 0 - *' )
submenu.Append( self.ID_REGEX_EXACTLY_M, r'exactly m matches - {m}' )
submenu.Append( self.ID_REGEX_M_TO_N_GREEDY, r'm to n matches, consuming as many as possible - {m,n}' )
submenu.Append( self.ID_REGEX_M_TO_N_MINIMAL, r'm to n matches, consuming as few as possible - {m,n}?' )
submenu.AppendSeparator()
submenu.Append( self.ID_REGEX_LOOKAHEAD, r'the next characters are: (non-consuming) - (?=...)' )
submenu.Append( self.ID_REGEX_NEGATIVE_LOOKAHEAD, r'the next characters are not: (non-consuming) - (?!...)' )
submenu.Append( self.ID_REGEX_LOOKBEHIND, r'the previous characters are: (non-consuming) - (?<=...)' )
submenu.Append( self.ID_REGEX_NEGATIVE_LOOKBEHIND, r'the previous characters are not: (non-consuming) - (?<!...)' )
submenu.AppendSeparator()
submenu.Append( self.ID_REGEX_NUMBER_WITHOUT_ZEROES, r'0074 -> 74 - [1-9]+\d*' )
submenu.Append( self.ID_REGEX_FILENAME, r'filename - (?<=' + os.path.sep.encode( 'string_escape' ) + r')[\w\s]*?(?=\..*$)' )
menu.AppendMenu( -1, 'regex components', submenu )
submenu = wx.Menu()
submenu.Append( self.ID_REGEX_MANAGE_FAVOURITES, 'manage favourites' )
submenu.AppendSeparator()
for ( index, ( regex_phrase, description ) ) in enumerate( HC.options[ 'regex_favourites' ] ):
menu_id = index + 100
submenu.Append( menu_id, description )
menu.AppendMenu( -1, 'favourites', submenu )
HydrusGlobals.client_controller.PopupMenu( self, menu )
def EventMenu( self, event ):
id = event.GetId()
phrase = None
if id == self.ID_REGEX_WHITESPACE: phrase = r'\s'
elif id == self.ID_REGEX_NUMBER: phrase = r'\d'
elif id == self.ID_REGEX_ALPHANUMERIC: phrase = r'\w'
elif id == self.ID_REGEX_ANY: phrase = r'.'
elif id == self.ID_REGEX_BACKSPACE: phrase = r'\\'
elif id == self.ID_REGEX_BEGINNING: phrase = r'^'
elif id == self.ID_REGEX_END: phrase = r'$'
elif id == self.ID_REGEX_SET: phrase = r'[...]'
elif id == self.ID_REGEX_NOT_SET: phrase = r'[^...]'
elif id == self.ID_REGEX_0_OR_MORE_GREEDY: phrase = r'*'
elif id == self.ID_REGEX_1_OR_MORE_GREEDY: phrase = r'+'
elif id == self.ID_REGEX_0_OR_1_GREEDY: phrase = r'?'
elif id == self.ID_REGEX_0_OR_MORE_MINIMAL: phrase = r'*?'
elif id == self.ID_REGEX_1_OR_MORE_MINIMAL: phrase = r'+?'
elif id == self.ID_REGEX_0_OR_1_MINIMAL: phrase = r'*'
elif id == self.ID_REGEX_EXACTLY_M: phrase = r'{m}'
elif id == self.ID_REGEX_M_TO_N_GREEDY: phrase = r'{m,n}'
elif id == self.ID_REGEX_M_TO_N_MINIMAL: phrase = r'{m,n}?'
elif id == self.ID_REGEX_LOOKAHEAD: phrase = r'(?=...)'
elif id == self.ID_REGEX_NEGATIVE_LOOKAHEAD: phrase = r'(?!...)'
elif id == self.ID_REGEX_LOOKBEHIND: phrase = r'(?<=...)'
elif id == self.ID_REGEX_NEGATIVE_LOOKBEHIND: phrase = r'(?<!...)'
elif id == self.ID_REGEX_NUMBER_WITHOUT_ZEROES: phrase = r'[1-9]+\d*'
elif id == self.ID_REGEX_FILENAME: phrase = r'(?<=' + os.path.sep.encode( 'string_escape' ) + r')[\w\s]*?(?=\..*$)'
elif id == self.ID_REGEX_MANAGE_FAVOURITES:
import ClientGUIDialogsManage
with ClientGUIDialogsManage.DialogManageRegexFavourites( self.GetTopLevelParent() ) as dlg:
dlg.ShowModal()
elif id in self.ID_REGEX_FAVOURITES:
index = id - 100
( phrase, description ) = HC.options[ 'regex_favourites' ][ index ]
else: event.Skip()
if phrase is not None: HydrusGlobals.client_controller.pub( 'clipboard', 'text', phrase )
class SaneListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ):
def __init__( self, parent, height, columns, delete_key_callback = None, use_display_tuple_for_sort = False ):
num_columns = len( columns )
wx.ListCtrl.__init__( self, parent, style = wx.LC_REPORT )
ListCtrlAutoWidthMixin.__init__( self )
ColumnSorterMixin.__init__( self, num_columns )
self.itemDataMap = {}
self._next_data_index = 0
self._use_display_tuple_for_sort = use_display_tuple_for_sort
self._custom_client_data = {}
resize_column = 1
for ( i, ( name, width ) ) in enumerate( columns ):
self.InsertColumn( i, name, width = width )
if width == -1: resize_column = i + 1
self.setResizeColumn( resize_column )
self.SetMinSize( ( -1, height ) )
self._delete_key_callback = delete_key_callback
self.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown )
def Append( self, display_tuple, client_data ):
index = wx.ListCtrl.Append( self, display_tuple )
self.SetItemData( index, self._next_data_index )
if self._use_display_tuple_for_sort:
self.itemDataMap[ self._next_data_index ] = list( display_tuple )
self._custom_client_data[ self._next_data_index ] = client_data
else:
self.itemDataMap[ self._next_data_index ] = list( client_data )
self._next_data_index += 1
def EventKeyDown( self, event ):
if event.KeyCode in ( wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE ):
if self._delete_key_callback is not None: self._delete_key_callback()
else: event.Skip()
def GetAllSelected( self ):
indices = []
i = self.GetFirstSelected()
while i != -1:
indices.append( i )
i = self.GetNextSelected( i )
return indices
def GetClientData( self, index = None ):
if index is None:
data_indicies = [ self.GetItemData( index ) for index in range( self.GetItemCount() ) ]
if self._use_display_tuple_for_sort:
datas = [ self._custom_client_data[ data_index ] for data_index in data_indicies ]
else:
datas = [ tuple( self.itemDataMap[ data_index ] ) for data_index in data_indicies ]
return datas
else:
data_index = self.GetItemData( index )
if self._use_display_tuple_for_sort:
return self._custom_client_data[ data_index ]
else:
return tuple( self.itemDataMap[ data_index ] )
def GetIndexFromClientData( self, data, column_index = None ):
for index in range( self.GetItemCount() ):
client_data = self.GetClientData( index )
if column_index is None:
comparison_data = client_data
else:
comparison_data = client_data[ column_index ]
if comparison_data == data: return index
raise HydrusExceptions.NotFoundException( 'Data not found!' )
def HasClientData( self, data, column_index = None ):
try:
index = self.GetIndexFromClientData( data, column_index )
return True
except HydrusExceptions.NotFoundException:
return False
def GetListCtrl( self ): return self
def GetSelectedClientData( self ):
indices = self.GetAllSelected()
results = []
for index in indices:
results.append( self.GetClientData( index ) )
return results
def RemoveAllSelected( self ):
indices = self.GetAllSelected()
indices.reverse() # so we don't screw with the indices of deletees below
for index in indices: self.DeleteItem( index )
def UpdateValue( self, index, column, display_value, data_value ):
self.SetStringItem( index, column, display_value )
data_index = self.GetItemData( index )
self.itemDataMap[ data_index ][ column ] = data_value
def UpdateRow( self, index, display_tuple, client_data ):
column = 0
for value in display_tuple:
self.SetStringItem( index, column, value )
column += 1
data_index = self.GetItemData( index )
if self._use_display_tuple_for_sort:
self.itemDataMap[ data_index ] = list( display_tuple )
self._custom_client_data[ data_index ] = client_data
else:
self.itemDataMap[ data_index ] = list( client_data )
class SeedCacheControl( SaneListCtrl ):
def __init__( self, parent, seed_cache ):
height = 300
columns = [ ( 'source', -1 ), ( 'status', 90 ), ( 'added', 150 ), ( 'last modified', 150 ), ( 'note', 200 ) ]
SaneListCtrl.__init__( self, parent, height, columns )
self._seed_cache = seed_cache
for info_tuple in self._seed_cache.GetSeedsWithInfo():
self._AddSeed( info_tuple )
self.Bind( wx.EVT_MENU, self.EventMenu )
self.Bind( wx.EVT_RIGHT_DOWN, self.EventShowMenu )
HydrusGlobals.client_controller.sub( self, 'NotifySeedUpdated', 'seed_cache_seed_updated' )
def _AddSeed( self, info_tuple ):
pretty_tuple = self._GetPrettyTuple( info_tuple )
self.Append( pretty_tuple, info_tuple )
def _GetPrettyTuple( self, info_tuple ):
( seed, status, added_timestamp, last_modified_timestamp, note ) = info_tuple
pretty_seed = HydrusData.ToUnicode( seed )
pretty_status = CC.status_string_lookup[ status ]
pretty_added = HydrusData.ConvertTimestampToPrettyAgo( added_timestamp )
pretty_modified = HydrusData.ConvertTimestampToPrettyAgo( last_modified_timestamp )
pretty_note = note
return ( pretty_seed, pretty_status, pretty_added, pretty_modified, pretty_note )
def _CopySelectedNotes( self ):
notes = []
for ( seed, status, added_timestamp, last_modified_timestamp, note ) in self.GetSelectedClientData():
if note != '':
notes.append( note )
if len( notes ) > 0:
separator = os.linesep * 2
text = separator.join( notes )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', text )
def _CopySelectedSeeds( self ):
seeds = [ seed for ( seed, status, added_timestamp, last_modified_timestamp, note ) in self.GetSelectedClientData() ]
if len( seeds ) > 0:
separator = os.linesep * 2
text = separator.join( seeds )
HydrusGlobals.client_controller.pub( 'clipboard', 'text', text )
def _SetSelected( self, status_to_set ):
seeds_to_reset = set()
for ( seed, status, added_timestamp, last_modified_timestamp, note ) in self.GetSelectedClientData():
if status != status_to_set:
seeds_to_reset.add( seed )
for seed in seeds_to_reset:
self._seed_cache.UpdateSeedStatus( seed, status_to_set )
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 == 'copy_seed_notes': self._CopySelectedNotes()
elif command == 'copy_seeds': self._CopySelectedSeeds()
elif command == 'set_seed_unknown': self._SetSelected( CC.STATUS_UNKNOWN )
elif command == 'set_seed_skipped': self._SetSelected( CC.STATUS_SKIPPED )
else: event.Skip()
def EventShowMenu( self, event ):
menu = wx.Menu()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_seeds' ), 'copy sources' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'copy_seed_notes' ), 'copy notes' )
menu.AppendSeparator()
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'set_seed_skipped' ), 'skip' )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'set_seed_unknown' ), 'try again' )
HydrusGlobals.client_controller.PopupMenu( self, menu )
def NotifySeedAdded( self, seed ):
if self._seed_cache.HasSeed( seed ):
info_tuple = self._seed_cache
def NotifySeedUpdated( self, seed ):
if self._seed_cache.HasSeed( seed ):
info_tuple = self._seed_cache.GetSeedInfo( seed )
if self.HasClientData( seed, 0 ):
index = self.GetIndexFromClientData( seed, 0 )
pretty_tuple = self._GetPrettyTuple( info_tuple )
self.UpdateRow( index, pretty_tuple, info_tuple )
else:
self._AddSeed( info_tuple )
else:
if self.HasClientData( seed, 0 ):
index = self.GetIndexFromClientData( seed, 0 )
self.DeleteItem( index )
class Shortcut( wx.TextCtrl ):
def __init__( self, parent, modifier = wx.ACCEL_NORMAL, key = wx.WXK_F7 ):
self._modifier = modifier
self._key = key
wx.TextCtrl.__init__( self, parent, style = wx.TE_PROCESS_ENTER )
self.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown )
self._SetShortcutString()
def _SetShortcutString( self ):
display_string = ''
if self._modifier == wx.ACCEL_ALT: display_string += 'alt + '
elif self._modifier == wx.ACCEL_CTRL: display_string += 'ctrl + '
elif self._modifier == wx.ACCEL_SHIFT: display_string += 'shift + '
if self._key in range( 65, 91 ): display_string += chr( self._key + 32 ) # + 32 for converting ascii A -> a
elif self._key in range( 97, 123 ): display_string += chr( self._key )
else: display_string += CC.wxk_code_string_lookup[ self._key ]
wx.TextCtrl.SetValue( self, display_string )
def EventKeyDown( self, event ):
if event.KeyCode in range( 65, 91 ) or event.KeyCode in CC.wxk_code_string_lookup.keys():
modifier = wx.ACCEL_NORMAL
if event.AltDown(): modifier = wx.ACCEL_ALT
elif event.CmdDown(): modifier = wx.ACCEL_CTRL
elif event.ShiftDown(): modifier = wx.ACCEL_SHIFT
( self._modifier, self._key ) = ClientData.GetShortcutFromEvent( event )
self._SetShortcutString()
def GetValue( self ): return ( self._modifier, self._key )
def SetValue( self, modifier, key ):
( self._modifier, self._key ) = ( modifier, key )
self._SetShortcutString()
class StaticBox( wx.Panel ):
def __init__( self, parent, title ):
wx.Panel.__init__( self, parent, style = wx.BORDER_DOUBLE )
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
self._sizer = wx.BoxSizer( wx.VERTICAL )
normal_font = wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT )
normal_font_size = normal_font.GetPointSize()
normal_font_family = normal_font.GetFamily()
title_font = wx.Font( int( normal_font_size ), normal_font_family, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD )
title_text = wx.StaticText( self, label = title, style = wx.ALIGN_CENTER )
title_text.SetFont( title_font )
self._sizer.AddF( title_text, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( self._sizer )
def AddF( self, widget, flags ): self._sizer.AddF( widget, flags )
class StaticBoxSorterForListBoxTags( StaticBox ):
def __init__( self, parent, title ):
StaticBox.__init__( self, parent, title )
self._sorter = wx.Choice( self )
self._sorter.Append( 'lexicographic (a-z)', CC.SORT_BY_LEXICOGRAPHIC_ASC )
self._sorter.Append( 'lexicographic (z-a)', CC.SORT_BY_LEXICOGRAPHIC_DESC )
self._sorter.Append( 'incidence (desc)', CC.SORT_BY_INCIDENCE_DESC )
self._sorter.Append( 'incidence (asc)', CC.SORT_BY_INCIDENCE_ASC )
if HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_ASC: self._sorter.Select( 0 )
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_DESC: self._sorter.Select( 1 )
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_DESC: self._sorter.Select( 2 )
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_ASC: self._sorter.Select( 3 )
self._sorter.Bind( wx.EVT_CHOICE, self.EventSort )
self.AddF( self._sorter, CC.FLAGS_EXPAND_PERPENDICULAR )
def ChangeTagRepository( self, service_key ): self._tags_box.ChangeTagRepository( service_key )
def EventSort( self, event ):
selection = self._sorter.GetSelection()
if selection != wx.NOT_FOUND:
sort = self._sorter.GetClientData( selection )
self._tags_box.SetSort( sort )
def SetTagsBox( self, tags_box ):
self._tags_box = tags_box
self.AddF( self._tags_box, CC.FLAGS_EXPAND_BOTH_WAYS )
def SetTagsByMedia( self, media, force_reload = False ):
self._tags_box.SetTagsByMedia( media, force_reload = force_reload )
class RadioBox( StaticBox ):
def __init__( self, parent, title, choice_pairs, initial_index = None ):
StaticBox.__init__( self, parent, title )
self._indices_to_radio_buttons = {}
self._radio_buttons_to_data = {}
first_button = True
for ( index, ( text, data ) ) in enumerate( choice_pairs ):
if first_button:
style = wx.RB_GROUP
first_button = False
else: style = 0
radio_button = wx.RadioButton( self, label = text, style = style )
self.AddF( radio_button, CC.FLAGS_EXPAND_PERPENDICULAR )
self._indices_to_radio_buttons[ index ] = radio_button
self._radio_buttons_to_data[ radio_button ] = data
if initial_index is not None and initial_index in self._indices_to_radio_buttons: self._indices_to_radio_buttons[ initial_index ].SetValue( True )
def GetSelectedClientData( self ):
for radio_button in self._radio_buttons_to_data.keys():
if radio_button.GetValue() == True: return self._radio_buttons_to_data[ radio_button ]
def SetSelection( self, index ): self._indices_to_radio_buttons[ index ].SetValue( True )
def SetString( self, index, text ): self._indices_to_radio_buttons[ index ].SetLabel( text )
class ShowKeys( Frame ):
def __init__( self, key_type, keys ):
if key_type == 'registration': title = 'Registration Keys'
elif key_type == 'access': title = 'Access Keys'
# give it no parent, so this doesn't close when the dialog is closed!
Frame.__init__( self, None, title = HydrusGlobals.client_controller.PrepStringForDisplay( title ) )
self._key_type = key_type
self._keys = keys
#
self._text_ctrl = wx.TextCtrl( self, style = wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_DONTWRAP )
self._save_to_file = wx.Button( self, label = 'save to file' )
self._save_to_file.Bind( wx.EVT_BUTTON, self.EventSaveToFile )
self._done = wx.Button( self, id = wx.ID_OK, label = 'done' )
self._done.Bind( wx.EVT_BUTTON, self.EventDone )
#
if key_type == 'registration': prepend = 'r'
else: prepend = ''
self._text = os.linesep.join( [ prepend + key.encode( 'hex' ) for key in self._keys ] )
self._text_ctrl.SetValue( self._text )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._text_ctrl, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._save_to_file, CC.FLAGS_LONE_BUTTON )
vbox.AddF( self._done, CC.FLAGS_LONE_BUTTON )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
if x < 500: x = 500
if y < 200: y = 200
self.SetInitialSize( ( x, y ) )
self.Show( True )
def EventDone( self, event ): self.Close()
def EventSaveToFile( self, event ):
filename = 'keys.txt'
with wx.FileDialog( None, style=wx.FD_SAVE, defaultFile = filename ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
path = HydrusData.ToUnicode( dlg.GetPath() )
with open( path, 'wb' ) as f: f.write( HydrusData.ToByteString( self._text ) )