2027 lines
65 KiB
Python
2027 lines
65 KiB
Python
from . import ClientCaches
|
|
from . import ClientConstants as CC
|
|
from . import ClientData
|
|
from . import ClientGUICommon
|
|
from . import ClientGUIDialogs
|
|
from . import ClientGUIListCtrl
|
|
from . import ClientGUIMenus
|
|
from . import ClientGUIScrolledPanels
|
|
from . import ClientGUIShortcuts
|
|
from . import ClientGUITime
|
|
from . import ClientGUITopLevelWindows
|
|
from . import ClientParsing
|
|
from . import HydrusConstants as HC
|
|
from . import HydrusData
|
|
from . import HydrusExceptions
|
|
from . import HydrusGlobals as HG
|
|
from . import HydrusNetworking
|
|
from . 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.ToHumanBytes( max_allowed )
|
|
|
|
elif bandwidth_type == HC.BANDWIDTH_TYPE_REQUESTS:
|
|
|
|
pretty_max_allowed = HydrusData.ToHumanInt( max_allowed ) + ' requests'
|
|
|
|
|
|
sort_time_delta = ClientGUIListCtrl.SafeNoneInt( time_delta )
|
|
|
|
sort_tuple = ( max_allowed, sort_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.TransformationToString( ( 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.ChangeValue( 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 = str( 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.ToHumanBytes( bytes_read )
|
|
|
|
|
|
|
|
if current_speed != bytes_to_read: # if it is a real quick download, just say its size
|
|
|
|
speed_text += ' ' + HydrusData.ToHumanBytes( 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( 'Cancelled by user.' )
|
|
|
|
|
|
|
|
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.ToString()
|
|
|
|
self.SetLabelText( label )
|
|
|
|
|
|
def GetValue( self ):
|
|
|
|
return self._string_match
|
|
|
|
|
|
def SetValue( self, string_match ):
|
|
|
|
self._string_match = string_match
|
|
|
|
self._UpdateLabel()
|
|
|
|
|
|
class StringMatchToStringMatchDictControl( 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( list(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, value_string_match ) = data
|
|
|
|
pretty_key = key_string_match.ToString()
|
|
pretty_value = value_string_match.ToString()
|
|
|
|
display_tuple = ( pretty_key, pretty_value )
|
|
sort_tuple = ( pretty_key, pretty_value )
|
|
|
|
return ( display_tuple, sort_tuple )
|
|
|
|
|
|
def _Add( self ):
|
|
|
|
with ClientGUITopLevelWindows.DialogEdit( self, 'edit ' + self._key_name ) as dlg:
|
|
|
|
string_match = ClientParsing.StringMatch()
|
|
|
|
panel = EditStringMatchPanel( dlg, string_match )
|
|
|
|
dlg.SetPanel( panel )
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
key_string_match = panel.GetValue()
|
|
|
|
else:
|
|
|
|
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:
|
|
|
|
value_string_match = panel.GetValue()
|
|
|
|
data = ( key_string_match, value_string_match )
|
|
|
|
self._listctrl.AddDatas( ( data, ) )
|
|
|
|
|
|
|
|
|
|
def _Edit( self ):
|
|
|
|
for data in self._listctrl.GetData( only_selected = True ):
|
|
|
|
( key_string_match, value_string_match ) = data
|
|
|
|
with ClientGUITopLevelWindows.DialogEdit( self, 'edit ' + self._key_name ) as dlg:
|
|
|
|
panel = EditStringMatchPanel( dlg, key_string_match )
|
|
|
|
dlg.SetPanel( panel )
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
key_string_match = panel.GetValue()
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
|
|
|
|
with ClientGUITopLevelWindows.DialogEdit( self, 'edit match' ) as dlg:
|
|
|
|
panel = EditStringMatchPanel( dlg, value_string_match )
|
|
|
|
dlg.SetPanel( panel )
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
value_string_match = panel.GetValue()
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
|
|
|
|
self._listctrl.DeleteDatas( ( data, ) )
|
|
|
|
edited_data = ( key_string_match, value_string_match )
|
|
|
|
self._listctrl.AddDatas( ( edited_data, ) )
|
|
|
|
|
|
self._listctrl.Sort()
|
|
|
|
|
|
def GetValue( self ):
|
|
|
|
value_dict = dict( self._listctrl.GetData() )
|
|
|
|
return value_dict
|
|
|
|
|
|
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( list(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( list(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.ToString()
|
|
|
|
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()
|
|
|
|
text = HydrusText.StripTrailingAndLeadingSpaces( text )
|
|
|
|
if text == '' and not self._allow_empty_input:
|
|
|
|
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 )
|
|
|
|
|