hydrus/include/ClientGUIControls.py

1881 lines
61 KiB
Python

import ClientCaches
import ClientConstants as CC
import ClientData
import ClientGUICommon
import ClientGUIDialogs
import ClientGUIListCtrl
import ClientGUIMenus
import ClientGUIScrolledPanels
import ClientGUIShortcuts
import ClientGUITime
import ClientGUITopLevelWindows
import ClientParsing
import HydrusConstants as HC
import HydrusData
import HydrusExceptions
import HydrusGlobals as HG
import HydrusNetworking
import HydrusText
import os
import wx
class BandwidthRulesCtrl( ClientGUICommon.StaticBox ):
def __init__( self, parent, bandwidth_rules ):
ClientGUICommon.StaticBox.__init__( self, parent, 'bandwidth rules' )
listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
columns = [ ( 'max allowed', 14 ), ( 'every', 16 ) ]
self._listctrl = ClientGUIListCtrl.BetterListCtrl( listctrl_panel, 'bandwidth_rules', 8, 10, columns, self._ConvertRuleToListCtrlTuples, use_simple_delete = True, activation_callback = self._Edit )
listctrl_panel.SetListCtrl( self._listctrl )
listctrl_panel.AddButton( 'add', self._Add )
listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
listctrl_panel.AddDeleteButton()
#
self._listctrl.AddDatas( bandwidth_rules.GetRules() )
self._listctrl.Sort( 0 )
#
self.Add( listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
def _Add( self ):
rule = ( HC.BANDWIDTH_TYPE_DATA, None, 1024 * 1024 * 100 )
with ClientGUITopLevelWindows.DialogEdit( self, 'edit rule' ) as dlg:
panel = self._EditPanel( dlg, rule )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
new_rule = panel.GetValue()
self._listctrl.AddDatas( ( new_rule, ) )
self._listctrl.Sort()
def _ConvertRuleToListCtrlTuples( self, rule ):
( bandwidth_type, time_delta, max_allowed ) = rule
pretty_time_delta = HydrusData.TimeDeltaToPrettyTimeDelta( time_delta )
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
pretty_max_allowed = HydrusData.ConvertIntToBytes( max_allowed )
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
pretty_max_allowed = HydrusData.ToHumanInt( max_allowed ) + ' requests'
sort_tuple = ( max_allowed, time_delta )
display_tuple = ( pretty_max_allowed, pretty_time_delta )
return ( display_tuple, sort_tuple )
def _Edit( self ):
selected_rules = self._listctrl.GetData( only_selected = True )
for rule in selected_rules:
with ClientGUITopLevelWindows.DialogEdit( self, 'edit rule' ) as dlg:
panel = self._EditPanel( dlg, rule )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
edited_rule = panel.GetValue()
self._listctrl.DeleteDatas( ( rule, ) )
self._listctrl.AddDatas( ( edited_rule, ) )
else:
break
self._listctrl.Sort()
def GetValue( self ):
bandwidth_rules = HydrusNetworking.BandwidthRules()
for rule in self._listctrl.GetData():
( bandwidth_type, time_delta, max_allowed ) = rule
bandwidth_rules.AddRule( bandwidth_type, time_delta, max_allowed )
return bandwidth_rules
class _EditPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, rule ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._bandwidth_type = ClientGUICommon.BetterChoice( self )
self._bandwidth_type.Append( 'data', HC.BANDWIDTH_TYPE_DATA )
self._bandwidth_type.Append( 'requests', HC.BANDWIDTH_TYPE_REQUESTS )
self._bandwidth_type.Bind( wx.EVT_CHOICE, self.EventBandwidth )
self._max_allowed_bytes = BytesControl( self )
self._max_allowed_requests = wx.SpinCtrl( self, min = 1, max = 1048576 )
self._time_delta = ClientGUITime.TimeDeltaButton( self, min = 1, days = True, hours = True, minutes = True, seconds = True, monthly_allowed = True )
#
( bandwidth_type, time_delta, max_allowed ) = rule
self._bandwidth_type.SelectClientData( bandwidth_type )
self._time_delta.SetValue( time_delta )
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
self._max_allowed_bytes.SetValue( max_allowed )
else:
self._max_allowed_requests.SetValue( max_allowed )
self._UpdateEnabled()
#
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.Add( self._max_allowed_bytes, CC.FLAGS_VCENTER )
hbox.Add( self._max_allowed_requests, CC.FLAGS_VCENTER )
hbox.Add( self._bandwidth_type, CC.FLAGS_VCENTER )
hbox.Add( ClientGUICommon.BetterStaticText( self, ' every ' ), CC.FLAGS_VCENTER )
hbox.Add( self._time_delta, CC.FLAGS_VCENTER )
self.SetSizer( hbox )
def _UpdateEnabled( self ):
bandwidth_type = self._bandwidth_type.GetChoice()
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
self._max_allowed_bytes.Show()
self._max_allowed_requests.Hide()
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
self._max_allowed_bytes.Hide()
self._max_allowed_requests.Show()
self.Layout()
def EventBandwidth( self, event ):
self._UpdateEnabled()
def GetValue( self ):
bandwidth_type = self._bandwidth_type.GetChoice()
time_delta = self._time_delta.GetValue()
if bandwidth_type == HC.BANDWIDTH_TYPE_DATA:
max_allowed = self._max_allowed_bytes.GetValue()
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
max_allowed = self._max_allowed_requests.GetValue()
return ( bandwidth_type, time_delta, max_allowed )
class BytesControl( wx.Panel ):
def __init__( self, parent, initial_value = 65536 ):
wx.Panel.__init__( self, parent )
self._spin = wx.SpinCtrl( self, min = 0, max = 1048576 )
width = ClientGUICommon.ConvertTextToPixelWidth( self._spin, 12 )
self._spin.SetSize( ( width, -1 ) )
self._unit = ClientGUICommon.BetterChoice( self )
self._unit.Append( 'B', 1 )
self._unit.Append( 'KB', 1024 )
self._unit.Append( 'MB', 1024 * 1024 )
self._unit.Append( 'GB', 1024 * 1024 * 1024 )
#
self.SetValue( initial_value )
#
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.Add( self._spin, CC.FLAGS_VCENTER )
hbox.Add( self._unit, CC.FLAGS_VCENTER )
self.SetSizer( hbox )
def Bind( self, event_type, callback ):
self._spin.Bind( wx.EVT_SPINCTRL, callback )
self._unit.Bind( wx.EVT_CHOICE, callback )
def Disable( self ):
self._spin.Disable()
self._unit.Disable()
def Enable( self ):
self._spin.Enable()
self._unit.Enable()
def GetSeparatedValue( self ):
return ( self._spin.GetValue(), self._unit.GetChoice() )
def GetValue( self ):
return self._spin.GetValue() * self._unit.GetChoice()
def SetSeparatedValue( self, value, unit ):
return ( self._spin.SetValue( value ), self._unit.SelectClientData( unit ) )
def SetValue( self, value ):
max_unit = 1024 * 1024 * 1024
unit = 1
while value % 1024 == 0 and unit < max_unit:
value /= 1024
unit *= 1024
self._spin.SetValue( value )
self._unit.SelectClientData( unit )
class EditStringConverterPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, string_converter, example_string_override = None ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
transformations_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
columns = [ ( '#', 3 ), ( 'transformation', 30 ), ( 'result', -1 ) ]
self._transformations = ClientGUIListCtrl.BetterListCtrl( transformations_panel, 'string_converter_transformations', 7, 35, columns, self._ConvertTransformationToListCtrlTuples, delete_key_callback = self._DeleteTransformation, activation_callback = self._EditTransformation )
transformations_panel.SetListCtrl( self._transformations )
transformations_panel.AddButton( 'add', self._AddTransformation )
transformations_panel.AddButton( 'edit', self._EditTransformation, enabled_only_on_selection = True )
transformations_panel.AddDeleteButton()
transformations_panel.AddSeparator()
transformations_panel.AddButton( 'move up', self._MoveUp, enabled_check_func = self._CanMoveUp )
transformations_panel.AddButton( 'move down', self._MoveDown, enabled_check_func = self._CanMoveDown )
self._example_string = wx.TextCtrl( self )
#
self._transformations.AddDatas( [ ( i + 1, transformation_type, data ) for ( i, ( transformation_type, data ) ) in enumerate( string_converter.transformations ) ] )
if example_string_override is None:
self._example_string.SetValue( string_converter.example_string )
else:
self._example_string.SetValue( example_string_override )
self._transformations.UpdateDatas() # to refresh, now they are all in the list
self._transformations.Sort( 0 )
#
rows = []
rows.append( ( 'example string: ', self._example_string ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( transformations_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
#
self._example_string.Bind( wx.EVT_TEXT, self.EventUpdate )
def _AddTransformation( self ):
transformation_type = ClientParsing.STRING_TRANSFORMATION_APPEND_TEXT
data = ' extra text'
with ClientGUITopLevelWindows.DialogEdit( self, 'edit transformation', frame_key = 'deeply_nested_dialog' ) as dlg:
panel = self._TransformationPanel( dlg, transformation_type, data )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
number = self._transformations.GetItemCount() + 1
( transformation_type, data ) = panel.GetValue()
enumerated_transformation = ( number, transformation_type, data )
self._transformations.AddDatas( ( enumerated_transformation, ) )
self._transformations.UpdateDatas() # need to refresh string after the insertion, so the new row can be included in the parsing calcs
self._transformations.Sort()
def _CanMoveDown( self ):
selected_data = self._transformations.GetData( only_selected = True )
if len( selected_data ) == 1:
( number, transformation_type, data ) = selected_data[0]
if number < self._transformations.GetItemCount():
return True
return False
def _CanMoveUp( self ):
selected_data = self._transformations.GetData( only_selected = True )
if len( selected_data ) == 1:
( number, transformation_type, data ) = selected_data[0]
if number > 1:
return True
return False
def _ConvertTransformationToListCtrlTuples( self, transformation ):
( number, transformation_type, data ) = transformation
pretty_number = HydrusData.ToHumanInt( number )
pretty_transformation = ClientParsing.StringConverter.TransformationToUnicode( ( transformation_type, data ) )
string_converter = self._GetValue()
try:
pretty_result = ClientParsing.MakeParsedTextPretty( string_converter.Convert( self._example_string.GetValue(), number ) )
except HydrusExceptions.StringConvertException as e:
pretty_result = str( e )
display_tuple = ( pretty_number, pretty_transformation, pretty_result )
sort_tuple = ( number, number, number )
return ( display_tuple, sort_tuple )
def _DeleteTransformation( self ):
if len( self._transformations.GetData( only_selected = True ) ) > 0:
with ClientGUIDialogs.DialogYesNo( self, 'Delete all selected?' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._transformations.DeleteSelected()
# now we need to shuffle up any missing numbers
num_rows = self._transformations.GetItemCount()
i = 1
search_i = i
while i <= num_rows:
try:
transformation = self._GetTransformation( search_i )
if search_i != i:
self._transformations.DeleteDatas( ( transformation, ) )
( search_i, transformation_type, data ) = transformation
transformation = ( i, transformation_type, data )
self._transformations.AddDatas( ( transformation, ) )
i += 1
search_i = i
except HydrusExceptions.DataMissing:
search_i += 1
self._transformations.UpdateDatas()
self._transformations.Sort()
def _EditTransformation( self ):
selected_data = self._transformations.GetData( only_selected = True )
for enumerated_transformation in selected_data:
( number, transformation_type, data ) = enumerated_transformation
with ClientGUITopLevelWindows.DialogEdit( self, 'edit transformation', frame_key = 'deeply_nested_dialog' ) as dlg:
panel = self._TransformationPanel( dlg, transformation_type, data )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
self._transformations.DeleteDatas( ( enumerated_transformation, ) )
( transformation_type, data ) = panel.GetValue()
enumerated_transformation = ( number, transformation_type, data )
self._transformations.AddDatas( ( enumerated_transformation, ) )
else:
break
self._transformations.UpdateDatas()
self._transformations.Sort()
def _GetTransformation( self, desired_number ):
for transformation in self._transformations.GetData():
( number, transformation_type, data ) = transformation
if number == desired_number:
return transformation
raise HydrusExceptions.DataMissing()
def _GetValue( self ):
enumerated_transformations = list( self._transformations.GetData() )
enumerated_transformations.sort()
transformations = [ ( transformation_type, data ) for ( number, transformation_type, data ) in enumerated_transformations ]
example_string = self._example_string.GetValue()
string_converter = ClientParsing.StringConverter( transformations, example_string )
return string_converter
def _MoveDown( self ):
selected_transformation = self._transformations.GetData( only_selected = True )[0]
( number, transformation_type, data ) = selected_transformation
swap_transformation = self._GetTransformation( number + 1 )
self._SwapTransformations( selected_transformation, swap_transformation )
self._transformations.UpdateDatas()
self._transformations.Sort()
def _MoveUp( self ):
selected_transformation = self._transformations.GetData( only_selected = True )[0]
( number, transformation_type, data ) = selected_transformation
swap_transformation = self._GetTransformation( number - 1 )
self._SwapTransformations( selected_transformation, swap_transformation )
self._transformations.UpdateDatas()
self._transformations.Sort()
def _SwapTransformations( self, one, two ):
selected_data = self._transformations.GetData( only_selected = True )
one_selected = one in selected_data
two_selected = two in selected_data
self._transformations.DeleteDatas( ( one, two ) )
( number_1, transformation_type_1, data_1 ) = one
( number_2, transformation_type_2, data_2 ) = two
one = ( number_2, transformation_type_1, data_1 )
two = ( number_1, transformation_type_2, data_2 )
self._transformations.AddDatas( ( one, two ) )
if one_selected:
self._transformations.SelectDatas( ( one, ) )
if two_selected:
self._transformations.SelectDatas( ( two, ) )
def EventUpdate( self, event ):
self._transformations.UpdateDatas()
def GetValue( self ):
string_converter = self._GetValue()
try:
string_converter.Convert( self._example_string.GetValue() )
except HydrusExceptions.StringConvertException:
raise HydrusExceptions.VetoException( 'Please enter an example text that can be converted!' )
return string_converter
class _TransformationPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, transformation_type, data ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._transformation_type = ClientGUICommon.BetterChoice( self )
for t_type in ( ClientParsing.STRING_TRANSFORMATION_REMOVE_TEXT_FROM_BEGINNING, ClientParsing.STRING_TRANSFORMATION_REMOVE_TEXT_FROM_END, ClientParsing.STRING_TRANSFORMATION_CLIP_TEXT_FROM_BEGINNING, ClientParsing.STRING_TRANSFORMATION_CLIP_TEXT_FROM_END, ClientParsing.STRING_TRANSFORMATION_PREPEND_TEXT, ClientParsing.STRING_TRANSFORMATION_APPEND_TEXT, ClientParsing.STRING_TRANSFORMATION_ENCODE, ClientParsing.STRING_TRANSFORMATION_DECODE, ClientParsing.STRING_TRANSFORMATION_REVERSE, ClientParsing.STRING_TRANSFORMATION_REGEX_SUB, ClientParsing.STRING_TRANSFORMATION_DATE_DECODE, ClientParsing.STRING_TRANSFORMATION_INTEGER_ADDITION ):
self._transformation_type.Append( ClientParsing.transformation_type_str_lookup[ t_type ], t_type )
self._data_text = wx.TextCtrl( self )
self._data_number = wx.SpinCtrl( self, min = 0, max = 65535 )
self._data_encoding = ClientGUICommon.BetterChoice( self )
self._data_regex_pattern = wx.TextCtrl( self )
self._data_regex_repl = wx.TextCtrl( self )
self._data_date_link = ClientGUICommon.BetterHyperLink( self, 'link to date info', 'https://docs.python.org/2/library/datetime.html#strftime-strptime-behavior' )
self._data_timezone = ClientGUICommon.BetterChoice( self )
self._data_timezone_offset = wx.SpinCtrl( self, min = -86400, max = 86400 )
for e in ( 'hex', 'base64' ):
self._data_encoding.Append( e, e )
self._data_timezone.Append( 'GMT', HC.TIMEZONE_GMT )
self._data_timezone.Append( 'Local', HC.TIMEZONE_LOCAL )
self._data_timezone.Append( 'Offset', HC.TIMEZONE_OFFSET )
#
self._transformation_type.SelectClientData( transformation_type )
self._UpdateDataControls()
#
if transformation_type in ( ClientParsing.STRING_TRANSFORMATION_DECODE, ClientParsing.STRING_TRANSFORMATION_ENCODE ):
self._data_encoding.SelectClientData( data )
elif transformation_type == ClientParsing.STRING_TRANSFORMATION_REGEX_SUB:
( pattern, repl ) = data
self._data_regex_pattern.SetValue( pattern )
self._data_regex_repl.SetValue( repl )
elif transformation_type == ClientParsing.STRING_TRANSFORMATION_DATE_DECODE:
( phrase, timezone_type, timezone_offset ) = data
self._data_text.SetValue( phrase )
self._data_timezone.SelectClientData( timezone_type )
self._data_timezone_offset.SetValue( timezone_offset )
elif data is not None:
if isinstance( data, int ):
self._data_number.SetValue( data )
else:
self._data_text.SetValue( data )
#
rows = []
rows.append( ( 'string data: ', self._data_text ) )
rows.append( ( 'number data: ', self._data_number ) )
rows.append( ( 'encoding data: ', self._data_encoding ) )
rows.append( ( 'regex pattern: ', self._data_regex_pattern ) )
rows.append( ( 'regex replacement: ', self._data_regex_repl ) )
rows.append( ( 'date info: ', self._data_date_link ) )
rows.append( ( 'date timezone: ', self._data_timezone ) )
rows.append( ( 'timezone offset: ', self._data_timezone_offset ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._transformation_type, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.SetSizer( vbox )
#
self._transformation_type.Bind( wx.EVT_CHOICE, self.EventChoice )
self._data_timezone.Bind( wx.EVT_CHOICE, self.EventChoice )
def _UpdateDataControls( self ):
self._data_text.Disable()
self._data_number.Disable()
self._data_encoding.Disable()
self._data_regex_pattern.Disable()
self._data_regex_repl.Disable()
self._data_timezone.Disable()
self._data_timezone_offset.Disable()
transformation_type = self._transformation_type.GetChoice()
if transformation_type in ( ClientParsing.STRING_TRANSFORMATION_ENCODE, ClientParsing.STRING_TRANSFORMATION_DECODE ):
self._data_encoding.Enable()
elif transformation_type in ( ClientParsing.STRING_TRANSFORMATION_PREPEND_TEXT, ClientParsing.STRING_TRANSFORMATION_APPEND_TEXT, ClientParsing.STRING_TRANSFORMATION_DATE_DECODE ):
self._data_text.Enable()
if transformation_type == ClientParsing.STRING_TRANSFORMATION_DATE_DECODE:
self._data_timezone.Enable()
if self._data_timezone.GetChoice() == HC.TIMEZONE_OFFSET:
self._data_timezone_offset.Enable()
elif transformation_type in ( ClientParsing.STRING_TRANSFORMATION_REMOVE_TEXT_FROM_BEGINNING, ClientParsing.STRING_TRANSFORMATION_REMOVE_TEXT_FROM_END, ClientParsing.STRING_TRANSFORMATION_CLIP_TEXT_FROM_BEGINNING, ClientParsing.STRING_TRANSFORMATION_CLIP_TEXT_FROM_END, ClientParsing.STRING_TRANSFORMATION_INTEGER_ADDITION ):
self._data_number.Enable()
if transformation_type == ClientParsing.STRING_TRANSFORMATION_INTEGER_ADDITION:
self._data_number.SetMin( -65535 )
else:
self._data_number.SetMin( 0 )
elif transformation_type == ClientParsing.STRING_TRANSFORMATION_REGEX_SUB:
self._data_regex_pattern.Enable()
self._data_regex_repl.Enable()
def EventChoice( self, event ):
self._UpdateDataControls()
def GetValue( self ):
transformation_type = self._transformation_type.GetChoice()
if transformation_type in ( ClientParsing.STRING_TRANSFORMATION_ENCODE, ClientParsing.STRING_TRANSFORMATION_DECODE ):
data = self._data_encoding.GetChoice()
elif transformation_type in ( ClientParsing.STRING_TRANSFORMATION_PREPEND_TEXT, ClientParsing.STRING_TRANSFORMATION_APPEND_TEXT ):
data = self._data_text.GetValue()
elif transformation_type in ( ClientParsing.STRING_TRANSFORMATION_REMOVE_TEXT_FROM_BEGINNING, ClientParsing.STRING_TRANSFORMATION_REMOVE_TEXT_FROM_END, ClientParsing.STRING_TRANSFORMATION_CLIP_TEXT_FROM_BEGINNING, ClientParsing.STRING_TRANSFORMATION_CLIP_TEXT_FROM_END, ClientParsing.STRING_TRANSFORMATION_INTEGER_ADDITION ):
data = self._data_number.GetValue()
elif transformation_type == ClientParsing.STRING_TRANSFORMATION_REGEX_SUB:
pattern = self._data_regex_pattern.GetValue()
repl = self._data_regex_repl.GetValue()
data = ( pattern, repl )
elif transformation_type == ClientParsing.STRING_TRANSFORMATION_DATE_DECODE:
phrase = self._data_text.GetValue()
timezone_time = self._data_timezone.GetChoice()
timezone_offset = self._data_timezone_offset.GetValue()
data = ( phrase, timezone_time, timezone_offset )
else:
data = None
return ( transformation_type, data )
class EditStringMatchPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, string_match = None ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
if string_match is None:
string_match = ClientParsing.StringMatch()
self._match_type = ClientGUICommon.BetterChoice( self )
self._match_type.Append( 'any characters', ClientParsing.STRING_MATCH_ANY )
self._match_type.Append( 'fixed characters', ClientParsing.STRING_MATCH_FIXED )
self._match_type.Append( 'character set', ClientParsing.STRING_MATCH_FLEXIBLE )
self._match_type.Append( 'regex', ClientParsing.STRING_MATCH_REGEX )
self._match_value_text_input = wx.TextCtrl( self )
self._match_value_flexible_input = ClientGUICommon.BetterChoice( self )
self._match_value_flexible_input.Append( 'alphabetic characters (a-zA-Z)', ClientParsing.ALPHA )
self._match_value_flexible_input.Append( 'alphanumeric characters (a-zA-Z0-9)', ClientParsing.ALPHANUMERIC )
self._match_value_flexible_input.Append( 'numeric characters (0-9)', ClientParsing.NUMERIC )
self._min_chars = ClientGUICommon.NoneableSpinCtrl( self, min = 1, max = 65535, unit = 'characters', none_phrase = 'no limit' )
self._max_chars = ClientGUICommon.NoneableSpinCtrl( self, min = 1, max = 65535, unit = 'characters', none_phrase = 'no limit' )
self._example_string = wx.TextCtrl( self )
self._example_string_matches = ClientGUICommon.BetterStaticText( self )
#
self.SetValue( string_match )
#
rows = []
rows.append( ( 'match type: ', self._match_type ) )
rows.append( ( 'match text: ', self._match_value_text_input ) )
rows.append( ( 'match value (character set): ', self._match_value_flexible_input ) )
rows.append( ( 'minumum allowed number of characters: ', self._min_chars ) )
rows.append( ( 'maximum allowed number of characters: ', self._max_chars ) )
rows.append( ( 'example string: ', self._example_string ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._example_string_matches, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
#
self._match_type.Bind( wx.EVT_CHOICE, self.EventUpdate )
self._match_value_text_input.Bind( wx.EVT_TEXT, self.EventUpdate )
self._match_value_flexible_input.Bind( wx.EVT_CHOICE, self.EventUpdate )
self._min_chars.Bind( wx.EVT_SPINCTRL, self.EventUpdate )
self._max_chars.Bind( wx.EVT_SPINCTRL, self.EventUpdate )
self._example_string.Bind( wx.EVT_TEXT, self.EventUpdate )
def _GetValue( self ):
match_type = self._match_type.GetChoice()
if match_type == ClientParsing.STRING_MATCH_ANY:
match_value = ''
elif match_type == ClientParsing.STRING_MATCH_FLEXIBLE:
match_value = self._match_value_flexible_input.GetChoice()
else:
match_value = self._match_value_text_input.GetValue()
min_chars = self._min_chars.GetValue()
max_chars = self._max_chars.GetValue()
example_string = self._example_string.GetValue()
string_match = ClientParsing.StringMatch( match_type = match_type, match_value = match_value, min_chars = min_chars, max_chars = max_chars, example_string = example_string )
return string_match
def _UpdateControls( self ):
match_type = self._match_type.GetChoice()
if match_type == ClientParsing.STRING_MATCH_ANY:
self._match_value_text_input.Disable()
self._match_value_flexible_input.Disable()
elif match_type == ClientParsing.STRING_MATCH_FLEXIBLE:
self._match_value_text_input.Disable()
self._match_value_flexible_input.Enable()
else:
self._match_value_text_input.Enable()
self._match_value_flexible_input.Disable()
if match_type == ClientParsing.STRING_MATCH_FIXED:
self._min_chars.SetValue( None )
self._max_chars.SetValue( None )
self._min_chars.Disable()
self._max_chars.Disable()
self._example_string.SetValue( self._match_value_text_input.GetValue() )
self._example_string_matches.SetLabelText( '' )
else:
self._min_chars.Enable()
self._max_chars.Enable()
string_match = self._GetValue()
try:
string_match.Test( self._example_string.GetValue() )
self._example_string_matches.SetLabelText( 'Example matches ok!' )
self._example_string_matches.SetForegroundColour( ( 0, 128, 0 ) )
except HydrusExceptions.StringMatchException as e:
reason = HydrusData.ToUnicode( e )
self._example_string_matches.SetLabelText( 'Example does not match - ' + reason )
self._example_string_matches.SetForegroundColour( ( 128, 0, 0 ) )
def EventUpdate( self, event ):
self._UpdateControls()
event.Skip()
def GetValue( self ):
string_match = self._GetValue()
try:
string_match.Test( self._example_string.GetValue() )
except HydrusExceptions.StringMatchException:
raise HydrusExceptions.VetoException( 'Please enter an example text that matches the given rules!' )
return string_match
def SetValue( self, string_match ):
( match_type, match_value, min_chars, max_chars, example_string ) = string_match.ToTuple()
self._match_type.SelectClientData( match_type )
if match_type == ClientParsing.STRING_MATCH_FLEXIBLE:
self._match_value_flexible_input.SelectClientData( match_value )
else:
self._match_value_flexible_input.SelectClientData( ClientParsing.ALPHA )
self._match_value_text_input.SetValue( match_value )
self._min_chars.SetValue( min_chars )
self._max_chars.SetValue( max_chars )
self._example_string.SetValue( example_string )
self._UpdateControls()
class NoneableBytesControl( wx.Panel ):
def __init__( self, parent, initial_value = 65536, none_label = 'no limit' ):
wx.Panel.__init__( self, parent )
self._bytes = BytesControl( self )
self._none_checkbox = wx.CheckBox( self, label = none_label )
#
self.SetValue( initial_value )
#
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.Add( self._bytes, CC.FLAGS_SIZER_VCENTER )
hbox.Add( self._none_checkbox, CC.FLAGS_VCENTER )
self.SetSizer( hbox )
#
self._none_checkbox.Bind( wx.EVT_CHECKBOX, self.EventNoneChecked )
def _UpdateEnabled( self ):
if self._none_checkbox.GetValue():
self._bytes.Disable()
else:
self._bytes.Enable()
def EventNoneChecked( self, event ):
self._UpdateEnabled()
def Bind( self, event_type, callback ):
self._bytes.Bind( wx.EVT_SPINCTRL, callback )
self._none_checkbox.Bind( wx.EVT_CHECKBOX, callback )
def GetValue( self ):
if self._none_checkbox.GetValue():
return None
else:
return self._bytes.GetValue()
def SetToolTip( self, text ):
wx.Panel.SetToolTip( self, text )
for c in self.GetChildren():
c.SetToolTip( text )
def SetValue( self, value ):
if value is None:
self._none_checkbox.SetValue( True )
else:
self._none_checkbox.SetValue( False )
self._bytes.SetValue( value )
self._UpdateEnabled()
class NetworkJobControl( wx.Panel ):
def __init__( self, parent ):
wx.Panel.__init__( self, parent, style = wx.BORDER_DOUBLE )
self._network_job = None
self._download_started = False
self._auto_override_bandwidth_rules = False
self._left_text = ClientGUICommon.BetterStaticText( self, style = wx.ST_ELLIPSIZE_END )
self._right_text = ClientGUICommon.BetterStaticText( self, style = wx.ALIGN_RIGHT )
self._last_right_min_width = ( -1, -1 )
self._gauge = ClientGUICommon.Gauge( self )
self._cog_button = ClientGUICommon.BetterBitmapButton( self, CC.GlobalBMPs.cog, self._ShowCogMenu )
self._cancel_button = ClientGUICommon.BetterBitmapButton( self, CC.GlobalBMPs.stop, self.Cancel )
#
self._Update()
#
st_hbox = wx.BoxSizer( wx.HORIZONTAL )
st_hbox.Add( self._left_text, CC.FLAGS_EXPAND_BOTH_WAYS )
st_hbox.Add( self._right_text, CC.FLAGS_VCENTER )
left_vbox = wx.BoxSizer( wx.VERTICAL )
left_vbox.Add( st_hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
left_vbox.Add( self._gauge, CC.FLAGS_EXPAND_PERPENDICULAR )
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.Add( left_vbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
hbox.Add( self._cog_button, CC.FLAGS_VCENTER )
hbox.Add( self._cancel_button, CC.FLAGS_VCENTER )
self.SetSizer( hbox )
def _ShowCogMenu( self ):
menu = wx.Menu()
if self._network_job is not None:
if self._network_job.ObeysBandwidth():
ClientGUIMenus.AppendMenuItem( self, menu, 'override bandwidth rules for this job', 'Tell the current job to ignore existing bandwidth rules and go ahead anyway.', self._network_job.OverrideBandwidth )
if not self._network_job.TokensOK():
ClientGUIMenus.AppendMenuItem( self, menu, 'override gallery slot requirements for this job', 'Force-allow this download to proceed, ignoring the normal gallery wait times.', self._network_job.OverrideToken )
ClientGUIMenus.AppendSeparator( menu )
ClientGUIMenus.AppendMenuCheckItem( self, menu, 'auto-override bandwidth rules for all jobs here after five seconds', 'Ignore existing bandwidth rules for all jobs under this control, instead waiting a flat five seconds.', self._auto_override_bandwidth_rules, self.FlipAutoOverrideBandwidth )
HG.client_controller.PopupMenu( self._cog_button, menu )
def _OverrideBandwidthIfAppropriate( self ):
if self._network_job is None or self._network_job.NoEngineYet():
return
else:
if self._auto_override_bandwidth_rules and HydrusData.TimeHasPassed( self._network_job.GetCreationTime() + 5 ):
self._network_job.OverrideBandwidth()
def _Update( self ):
if self._network_job is None or self._network_job.NoEngineYet():
self._left_text.SetLabelText( '' )
self._right_text.SetLabelText( '' )
self._gauge.SetRange( 1 )
self._gauge.SetValue( 0 )
can_cancel = False
else:
if self._network_job.IsDone():
can_cancel = False
else:
can_cancel = True
( status_text, current_speed, bytes_read, bytes_to_read ) = self._network_job.GetStatus()
self._left_text.SetLabelText( status_text )
if not self._download_started and current_speed > 0:
self._download_started = True
speed_text = ''
if self._download_started and not self._network_job.HasError():
if bytes_read is not None:
if bytes_to_read is not None and bytes_read != bytes_to_read:
speed_text += HydrusData.ConvertValueRangeToBytes( bytes_read, bytes_to_read )
else:
speed_text += HydrusData.ConvertIntToBytes( bytes_read )
if current_speed != bytes_to_read: # if it is a real quick download, just say its size
speed_text += ' ' + HydrusData.ConvertIntToBytes( current_speed ) + '/s'
self._right_text.SetLabelText( speed_text )
right_width = ClientGUICommon.ConvertTextToPixelWidth( self._right_text, len( speed_text ) )
right_min_size = ( right_width, -1 )
if right_min_size != self._last_right_min_width:
self._last_right_min_width = right_min_size
self._right_text.SetMinSize( right_min_size )
self.Layout()
self._gauge.SetRange( bytes_to_read )
self._gauge.SetValue( bytes_read )
if can_cancel:
if not self._cancel_button.IsEnabled():
self._cancel_button.Enable()
else:
if self._cancel_button.IsEnabled():
self._cancel_button.Disable()
def Cancel( self ):
if self._network_job is not None:
self._network_job.Cancel()
def ClearNetworkJob( self ):
self.SetNetworkJob( None )
def FlipAutoOverrideBandwidth( self ):
self._auto_override_bandwidth_rules = not self._auto_override_bandwidth_rules
def SetNetworkJob( self, network_job ):
if network_job is None:
if self._network_job is not None:
self._network_job = None
self._Update()
HG.client_controller.gui.UnregisterUIUpdateWindow( self )
else:
if self._network_job != network_job:
self._network_job = network_job
self._download_started = False
HG.client_controller.gui.RegisterUIUpdateWindow( self )
def TIMERUIUpdate( self ):
self._OverrideBandwidthIfAppropriate()
if HG.client_controller.gui.IShouldRegularlyUpdate( self ):
self._Update()
( StringConverterEvent, EVT_STRING_CONVERTER ) = wx.lib.newevent.NewCommandEvent()
class StringConverterButton( ClientGUICommon.BetterButton ):
def __init__( self, parent, string_converter ):
ClientGUICommon.BetterButton.__init__( self, parent, 'edit string converter', self._Edit )
self._string_converter = string_converter
self._example_string_override = None
self._UpdateLabel()
def _Edit( self ):
with ClientGUITopLevelWindows.DialogEdit( self, 'edit string converter', frame_key = 'deeply_nested_dialog' ) as dlg:
panel = EditStringConverterPanel( dlg, self._string_converter, example_string_override = self._example_string_override )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
self._string_converter = panel.GetValue()
self._UpdateLabel()
wx.QueueEvent( self.GetEventHandler(), StringConverterEvent( -1 ) )
def _UpdateLabel( self ):
num_rules = len( self._string_converter.transformations )
if num_rules == 0:
label = 'no string transformations'
else:
label = HydrusData.ToHumanInt( num_rules ) + ' string transformations'
self.SetLabelText( label )
def GetValue( self ):
return self._string_converter
def SetExampleString( self, example_string ):
self._example_string_override = example_string
def SetValue( self, string_converter ):
self._string_converter = string_converter
self._UpdateLabel()
class StringMatchButton( ClientGUICommon.BetterButton ):
def __init__( self, parent, string_match ):
ClientGUICommon.BetterButton.__init__( self, parent, 'edit string match', self._Edit )
self._string_match = string_match
self._UpdateLabel()
def _Edit( self ):
with ClientGUITopLevelWindows.DialogEdit( self, 'edit string match', frame_key = 'deeply_nested_dialog' ) as dlg:
panel = EditStringMatchPanel( dlg, self._string_match )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
self._string_match = panel.GetValue()
self._UpdateLabel()
def _UpdateLabel( self ):
label = self._string_match.ToUnicode()
self.SetLabelText( label )
def GetValue( self ):
return self._string_match
def SetValue( self, string_match ):
self._string_match = string_match
self._UpdateLabel()
class StringToStringDictButton( ClientGUICommon.BetterButton ):
def __init__( self, parent, label ):
ClientGUICommon.BetterButton.__init__( self, parent, label, self._Edit )
self._value = {}
def _Edit( self ):
with ClientGUITopLevelWindows.DialogEdit( self, 'edit string dictionary' ) as dlg:
panel = ClientGUIScrolledPanels.EditSingleCtrlPanel( dlg )
control = StringToStringDictControl( panel, self._value )
panel.SetControl( control )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
self._value = control.GetValue()
def GetValue( self ):
return self._value
def SetValue( self, value ):
self._value = value
class StringToStringDictControl( wx.Panel ):
def __init__( self, parent, initial_dict, min_height = 10, key_name = 'key', value_name = 'value' ):
wx.Panel.__init__( self, parent )
self._key_name = key_name
self._value_name = value_name
listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
columns = [ ( self._key_name, 20 ), ( self._value_name, -1 ) ]
self._listctrl = ClientGUIListCtrl.BetterListCtrl( listctrl_panel, 'key_to_value', min_height, 36, columns, self._ConvertDataToListCtrlTuples, use_simple_delete = True, activation_callback = self._Edit )
listctrl_panel.SetListCtrl( self._listctrl )
listctrl_panel.AddButton( 'add', self._Add )
listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
listctrl_panel.AddDeleteButton()
#
self._listctrl.AddDatas( initial_dict.items() )
self._listctrl.Sort()
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
def _ConvertDataToListCtrlTuples( self, data ):
( key, value ) = data
display_tuple = ( key, value )
sort_tuple = ( key, value )
return ( display_tuple, sort_tuple )
def _Add( self ):
with ClientGUIDialogs.DialogTextEntry( self, 'enter the ' + self._key_name, allow_blank = False ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
key = dlg.GetValue()
if key in self._GetExistingKeys():
wx.MessageBox( 'That ' + self._key_name + ' already exists!' )
return
with ClientGUIDialogs.DialogTextEntry( self, 'enter the ' + self._value_name, allow_blank = True ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
value = dlg.GetValue()
data = ( key, value )
self._listctrl.AddDatas( ( data, ) )
def _Edit( self ):
for data in self._listctrl.GetData( only_selected = True ):
( key, value ) = data
with ClientGUIDialogs.DialogTextEntry( self, 'edit the ' + self._key_name, default = key, allow_blank = False ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
edited_key = dlg.GetValue()
if edited_key != key and edited_key in self._GetExistingKeys():
wx.MessageBox( 'That ' + self._key_name + ' already exists!' )
break
else:
break
with ClientGUIDialogs.DialogTextEntry( self, 'edit the ' + self._value_name, default = value, allow_blank = True ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
edited_value = dlg.GetValue()
else:
break
self._listctrl.DeleteDatas( ( data, ) )
edited_data = ( edited_key, edited_value )
self._listctrl.AddDatas( ( edited_data, ) )
self._listctrl.Sort()
def _GetExistingKeys( self ):
return { key for ( key, value ) in self._listctrl.GetData() }
def GetValue( self ):
value_dict = dict( self._listctrl.GetData() )
return value_dict
class StringToStringMatchDictControl( wx.Panel ):
def __init__( self, parent, initial_dict, min_height = 10, key_name = 'key' ):
wx.Panel.__init__( self, parent )
self._key_name = key_name
listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self )
columns = [ ( self._key_name, 20 ), ( 'matching', -1 ) ]
self._listctrl = ClientGUIListCtrl.BetterListCtrl( listctrl_panel, 'key_to_string_match', min_height, 36, columns, self._ConvertDataToListCtrlTuples, use_simple_delete = True, activation_callback = self._Edit )
listctrl_panel.SetListCtrl( self._listctrl )
listctrl_panel.AddButton( 'add', self._Add )
listctrl_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
listctrl_panel.AddDeleteButton()
#
self._listctrl.AddDatas( initial_dict.items() )
self._listctrl.Sort()
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
def _ConvertDataToListCtrlTuples( self, data ):
( key, string_match ) = data
pretty_string_match = string_match.ToUnicode()
display_tuple = ( key, pretty_string_match )
sort_tuple = ( key, pretty_string_match )
return ( display_tuple, sort_tuple )
def _Add( self ):
with ClientGUIDialogs.DialogTextEntry( self, 'enter the ' + self._key_name, allow_blank = False ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
key = dlg.GetValue()
if key in self._GetExistingKeys():
wx.MessageBox( 'That ' + self._key_name + ' already exists!' )
return
with ClientGUITopLevelWindows.DialogEdit( self, 'edit match' ) as dlg:
string_match = ClientParsing.StringMatch()
panel = EditStringMatchPanel( dlg, string_match )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
string_match = panel.GetValue()
data = ( key, string_match )
self._listctrl.AddDatas( ( data, ) )
def _Edit( self ):
for data in self._listctrl.GetData( only_selected = True ):
( key, string_match ) = data
with ClientGUIDialogs.DialogTextEntry( self, 'edit the ' + self._key_name, default = key, allow_blank = False ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
edited_key = dlg.GetValue()
if edited_key != key and edited_key in self._GetExistingKeys():
wx.MessageBox( 'That ' + self._key_name + ' already exists!' )
break
else:
break
with ClientGUITopLevelWindows.DialogEdit( self, 'edit match' ) as dlg:
string_match = ClientParsing.StringMatch()
panel = EditStringMatchPanel( dlg, string_match )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
edited_string_match = panel.GetValue()
else:
break
self._listctrl.DeleteDatas( ( data, ) )
edited_data = ( edited_key, edited_string_match )
self._listctrl.AddDatas( ( edited_data, ) )
self._listctrl.Sort()
def _GetExistingKeys( self ):
return { key for ( key, value ) in self._listctrl.GetData() }
def GetValue( self ):
value_dict = dict( self._listctrl.GetData() )
return value_dict
class TextAndPasteCtrl( wx.Panel ):
def __init__( self, parent, add_callable, allow_empty_input = False ):
self._add_callable = add_callable
self._allow_empty_input = allow_empty_input
wx.Panel.__init__( self, parent )
self._text_input = wx.TextCtrl( self, style = wx.TE_PROCESS_ENTER )
self._text_input.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown )
self._paste_button = ClientGUICommon.BetterBitmapButton( self, CC.GlobalBMPs.paste, self._Paste )
self._paste_button.SetToolTip( 'Paste multiple inputs from the clipboard. Assumes the texts are newline-separated.' )
#
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.Add( self._text_input, CC.FLAGS_EXPAND_BOTH_WAYS )
hbox.Add( self._paste_button, CC.FLAGS_VCENTER )
self.SetSizer( hbox )
def _Paste( self ):
raw_text = HG.client_controller.GetClipboardText()
try:
texts = [ text for text in HydrusText.DeserialiseNewlinedTexts( raw_text ) ]
if not self._allow_empty_input:
texts = [ text for text in texts if text != '' ]
if len( texts ) > 0:
self._add_callable( texts )
except:
wx.MessageBox( 'I could not understand what was in the clipboard' )
def EventKeyDown( self, event ):
( modifier, key ) = ClientGUIShortcuts.ConvertKeyEventToSimpleTuple( event )
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
text = self._text_input.GetValue()
if not self._allow_empty_input and text == '':
return
self._add_callable( ( text, ) )
self._text_input.SetValue( '' )
else:
event.Skip()
def GetValue( self ):
return self._text_input.GetValue()
def SetValue( self, text ):
self._text_input.SetValue( text )