hydrus/include/ClientGUIDialogs.py

1330 lines
42 KiB
Python
Executable File

from . import HydrusConstants as HC
from . import ClientConstants as CC
from . import ClientData
from . import ClientDefaults
from . import ClientDownloading
from . import ClientDragDrop
from . import ClientExporting
from . import ClientCaches
from . import ClientFiles
from . import ClientGUIACDropdown
from . import ClientGUIFrames
from . import ClientGUICommon
from . import ClientGUIDialogsQuick
from . import ClientGUIFunctions
from . import ClientGUIImport
from . import ClientGUIListBoxes
from . import ClientGUIListCtrl
from . import ClientGUIPredicates
from . import ClientGUIShortcuts
from . import ClientGUITime
from . import ClientGUITopLevelWindows
from . import ClientImporting
from . import ClientTags
from . import ClientThreading
import collections
import gc
from . import HydrusExceptions
from . import HydrusFileHandling
from . import HydrusNATPunch
from . import HydrusNetwork
from . import HydrusPaths
from . import HydrusSerialisable
from . import HydrusTags
from . import HydrusThreading
import itertools
import os
import random
import re
import queue
import shutil
import stat
import string
import threading
import time
import traceback
import wx
import wx.lib.agw.customtreectrl
import yaml
from . import HydrusData
from . import ClientSearch
from . import HydrusGlobals as HG
# Option Enums
ID_NULL = wx.NewId()
ID_TIMER_UPDATE = wx.NewId()
def SelectServiceKey( service_types = HC.ALL_SERVICES, service_keys = None, unallowed = None ):
if service_keys is None:
services = HG.client_controller.services_manager.GetServices( service_types )
service_keys = [ service.GetServiceKey() for service in services ]
if unallowed is not None:
service_keys.difference_update( unallowed )
if len( service_keys ) == 0:
return None
elif len( service_keys ) == 1:
( service_key, ) = service_keys
return service_key
else:
services = { HG.client_controller.services_manager.GetService( service_key ) for service_key in service_keys }
choice_tuples = [ ( service.GetName(), service.GetServiceKey() ) for service in services ]
try:
service_key = ClientGUIDialogsQuick.SelectFromList( HG.client_controller.GetGUI(), 'select service', choice_tuples )
return service_key
except HydrusExceptions.CancelledException:
return None
class Dialog( wx.Dialog ):
def __init__( self, parent, title, style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, position = 'topleft' ):
if parent is not None and position == 'topleft':
if isinstance( parent, wx.TopLevelWindow ):
parent_tlp = parent
else:
parent_tlp = parent.GetTopLevelParent()
( pos_x, pos_y ) = parent_tlp.GetPosition()
pos = ( pos_x + 50, pos_y + 50 )
else:
pos = wx.DefaultPosition
if not HC.PLATFORM_LINUX and parent is not None:
style |= wx.FRAME_FLOAT_ON_PARENT
wx.Dialog.__init__( self, parent, title = title, style = style, pos = pos )
self._new_options = HG.client_controller.new_options
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_FRAMEBK ) )
self.SetIcon( HG.client_controller.frame_icon )
self.Bind( wx.EVT_CHAR_HOOK, self.EventCharHook )
if parent is not None and position == 'center':
wx.CallAfter( self.Center )
HG.client_controller.ResetIdleTimer()
def EventCharHook( self, event ):
( modifier, key ) = ClientGUIShortcuts.ConvertKeyEventToSimpleTuple( event )
if key == wx.WXK_ESCAPE:
self.EndModal( wx.ID_CANCEL )
else:
event.Skip()
def SetInitialSize( self, size ):
( width, height ) = size
( display_width, display_height ) = ClientGUITopLevelWindows.GetDisplaySize( self )
width = min( display_width, width )
height = min( display_height, height )
wx.Dialog.SetInitialSize( self, ( width, height ) )
min_width = min( 240, width )
min_height = min( 240, height )
self.SetMinSize( ( min_width, min_height ) )
class DialogChooseNewServiceMethod( Dialog ):
def __init__( self, parent ):
Dialog.__init__( self, parent, 'how to set up the account?', position = 'center' )
register_message = 'I want to initialise a new account with the server. I have a registration key (a key starting with \'r\').'
self._register = wx.Button( self, label = register_message )
self._register.Bind( wx.EVT_BUTTON, self.EventRegister )
setup_message = 'The account is already initialised; I just want to add it to this client. I have a normal access key.'
self._setup = wx.Button( self, id = wx.ID_OK, label = setup_message )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._register, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( wx.StaticText( self, label = '-or-', style = wx.ALIGN_CENTER ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._setup, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
self._should_register = False
wx.CallAfter( self._register.SetFocus )
def EventRegister( self, event ):
self._should_register = True
self.EndModal( wx.ID_OK )
def GetRegister( self ):
return self._should_register
class DialogGenerateNewAccounts( Dialog ):
def __init__( self, parent, service_key ):
Dialog.__init__( self, parent, 'configure new accounts' )
self._service_key = service_key
self._num = wx.SpinCtrl( self, min = 1, max = 10000, size = ( 80, -1 ) )
self._account_types = ClientGUICommon.BetterChoice( self )
self._lifetime = ClientGUICommon.BetterChoice( self )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'OK' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
self._num.SetValue( 1 )
service = HG.client_controller.services_manager.GetService( service_key )
response = service.Request( HC.GET, 'account_types' )
account_types = response[ 'account_types' ]
for account_type in account_types:
self._account_types.Append( account_type.GetTitle(), account_type )
self._account_types.Select( 0 )
for ( s, value ) in HC.lifetimes:
self._lifetime.Append( s, value )
self._lifetime.SetSelection( 3 ) # one year
#
ctrl_box = wx.BoxSizer( wx.HORIZONTAL )
ctrl_box.Add( ClientGUICommon.BetterStaticText( self, 'generate' ), CC.FLAGS_VCENTER )
ctrl_box.Add( self._num, CC.FLAGS_VCENTER )
ctrl_box.Add( self._account_types, CC.FLAGS_VCENTER )
ctrl_box.Add( ClientGUICommon.BetterStaticText( self, 'accounts, to expire in' ), CC.FLAGS_VCENTER )
ctrl_box.Add( self._lifetime, CC.FLAGS_VCENTER )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.Add( self._ok, CC.FLAGS_VCENTER )
b_box.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( ctrl_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( b_box, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def EventOK( self, event ):
num = self._num.GetValue()
account_type = self._account_types.GetValue()
account_type_key = account_type.GetAccountTypeKey()
lifetime = self._lifetime.GetValue()
if lifetime is None:
expires = None
else:
expires = HydrusData.GetNow() + lifetime
service = HG.client_controller.services_manager.GetService( self._service_key )
try:
request_args = { 'num' : num, 'account_type_key' : account_type_key }
if expires is not None:
request_args[ 'expires' ] = expires
response = service.Request( HC.GET, 'registration_keys', request_args )
registration_keys = response[ 'registration_keys' ]
ClientGUIFrames.ShowKeys( 'registration', registration_keys )
finally:
self.EndModal( wx.ID_OK )
class DialogInputLocalBooruShare( Dialog ):
def __init__( self, parent, share_key, name, text, timeout, hashes, new_share = False ):
Dialog.__init__( self, parent, 'configure local booru share' )
self._name = wx.TextCtrl( self )
self._text = ClientGUICommon.SaneMultilineTextCtrl( self )
self._text.SetMinSize( ( -1, 100 ) )
message = 'expires in'
self._timeout_number = ClientGUICommon.NoneableSpinCtrl( self, message, none_phrase = 'no expiration', max = 1000000, multiplier = 1 )
self._timeout_multiplier = ClientGUICommon.BetterChoice( self )
self._timeout_multiplier.Append( 'minutes', 60 )
self._timeout_multiplier.Append( 'hours', 60 * 60 )
self._timeout_multiplier.Append( 'days', 60 * 60 * 24 )
self._copy_internal_share_link = wx.Button( self, label = 'copy internal share link' )
self._copy_internal_share_link.Bind( wx.EVT_BUTTON, self.EventCopyInternalShareURL )
self._copy_external_share_link = wx.Button( self, label = 'copy external share link' )
self._copy_external_share_link.Bind( wx.EVT_BUTTON, self.EventCopyExternalShareURL )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
self._share_key = share_key
self._name.SetValue( name )
self._text.SetValue( text )
if timeout is None:
self._timeout_number.SetValue( None )
self._timeout_multiplier.SetValue( 60 )
else:
time_left = HydrusData.GetTimeDeltaUntilTime( timeout )
if time_left < 60 * 60 * 12: time_value = 60
elif time_left < 60 * 60 * 24 * 7: time_value = 60 * 60
else: time_value = 60 * 60 * 24
self._timeout_number.SetValue( time_left // time_value )
self._timeout_multiplier.SetValue( time_value )
self._hashes = hashes
self._service = HG.client_controller.services_manager.GetService( CC.LOCAL_BOORU_SERVICE_KEY )
internal_port = self._service.GetPort()
if internal_port is None:
self._copy_internal_share_link.Disable()
self._copy_external_share_link.Disable()
#
rows = []
rows.append( ( 'share name: ', self._name ) )
rows.append( ( 'share text: ', self._text ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
timeout_box = wx.BoxSizer( wx.HORIZONTAL )
timeout_box.Add( self._timeout_number, CC.FLAGS_EXPAND_BOTH_WAYS )
timeout_box.Add( self._timeout_multiplier, CC.FLAGS_EXPAND_BOTH_WAYS )
link_box = wx.BoxSizer( wx.HORIZONTAL )
link_box.Add( self._copy_internal_share_link, CC.FLAGS_VCENTER )
link_box.Add( self._copy_external_share_link, CC.FLAGS_VCENTER )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.Add( self._ok, CC.FLAGS_VCENTER )
b_box.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
intro = 'Sharing ' + HydrusData.ToHumanInt( len( self._hashes ) ) + ' files.'
intro += os.linesep + 'Title and text are optional.'
if new_share: intro += os.linesep + 'The link will not work until you ok this dialog.'
vbox.Add( ClientGUICommon.BetterStaticText( self, intro ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( timeout_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( link_box, CC.FLAGS_BUTTON_SIZER )
vbox.Add( b_box, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
x = max( x, 350 )
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def EventCopyExternalShareURL( self, event ):
internal_port = self._service.GetPort()
if internal_port is None:
wx.MessageBox( 'The local booru is not currently running!' )
try:
url = self._service.GetExternalShareURL( self._share_key )
except Exception as e:
HydrusData.ShowException( e )
wx.MessageBox( 'Unfortunately, could not generate an external URL: {}'.format( e ) )
return
HG.client_controller.pub( 'clipboard', 'text', url )
def EventCopyInternalShareURL( self, event ):
self._service = HG.client_controller.services_manager.GetService( CC.LOCAL_BOORU_SERVICE_KEY )
internal_ip = '127.0.0.1'
internal_port = self._service.GetPort()
url = 'http://' + internal_ip + ':' + str( internal_port ) + '/gallery?share_key=' + self._share_key.hex()
HG.client_controller.pub( 'clipboard', 'text', url )
def GetInfo( self ):
name = self._name.GetValue()
text = self._text.GetValue()
timeout = self._timeout_number.GetValue()
if timeout is not None: timeout = timeout * self._timeout_multiplier.GetValue() + HydrusData.GetNow()
return ( self._share_key, name, text, timeout, self._hashes )
class DialogInputNamespaceRegex( Dialog ):
def __init__( self, parent, namespace = '', regex = '' ):
Dialog.__init__( self, parent, 'configure quick namespace' )
self._namespace = wx.TextCtrl( self )
self._regex = wx.TextCtrl( self )
self._shortcuts = ClientGUICommon.RegexButton( self )
self._regex_intro_link = ClientGUICommon.BetterHyperLink( self, 'a good regex introduction', 'http://www.aivosto.com/vbtips/regex.html' )
self._regex_practise_link = ClientGUICommon.BetterHyperLink( self, 'regex practise', 'http://regexr.com/3cvmf' )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'OK' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
self._namespace.SetValue( namespace )
self._regex.SetValue( regex )
#
control_box = wx.BoxSizer( wx.HORIZONTAL )
control_box.Add( self._namespace, CC.FLAGS_EXPAND_BOTH_WAYS )
control_box.Add( ClientGUICommon.BetterStaticText( self, ':' ), CC.FLAGS_VCENTER )
control_box.Add( self._regex, CC.FLAGS_EXPAND_BOTH_WAYS )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.Add( self._ok, CC.FLAGS_VCENTER )
b_box.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
intro = r'Put the namespace (e.g. page) on the left.' + os.linesep + r'Put the regex (e.g. [1-9]+\d*(?=.{4}$)) on the right.' + os.linesep + r'All files will be tagged with "namespace:regex".'
vbox.Add( ClientGUICommon.BetterStaticText( self, intro ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( control_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( self._shortcuts, CC.FLAGS_LONE_BUTTON )
vbox.Add( self._regex_intro_link, CC.FLAGS_LONE_BUTTON )
vbox.Add( self._regex_practise_link, CC.FLAGS_LONE_BUTTON )
vbox.Add( b_box, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def EventOK( self, event ):
( namespace, regex ) = self.GetInfo()
if namespace == '':
wx.MessageBox( 'Please enter something for the namespace.' )
return
try:
re.compile( regex )
except Exception as e:
text = 'That regex would not compile!'
text += os.linesep * 2
text += str( e )
wx.MessageBox( text )
return
self.EndModal( wx.ID_OK )
def GetInfo( self ):
namespace = self._namespace.GetValue()
regex = self._regex.GetValue()
return ( namespace, regex )
class DialogInputTags( Dialog ):
def __init__( self, parent, service_key, tags, message = '' ):
Dialog.__init__( self, parent, 'input tags' )
self._service_key = service_key
self._tags = ClientGUIListBoxes.ListBoxTagsStringsAddRemove( self, service_key = service_key )
expand_parents = True
self._tag_box = ClientGUIACDropdown.AutoCompleteDropdownTagsWrite( self, self.EnterTags, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key, null_entry_callable = self.OK, show_paste_button = True )
self._ok = ClientGUICommon.BetterButton( self, 'OK', self.EndModal, wx.ID_OK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
self._tags.SetTags( tags )
#
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.Add( self._ok, CC.FLAGS_VCENTER )
b_box.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
if message != '':
vbox.Add( ClientGUICommon.BetterStaticText( self, message ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._tags, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( self._tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( b_box, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
x = max( x, 300 )
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._tag_box.SetFocus )
def EnterTags( self, tags ):
tag_parents_manager = HG.client_controller.tag_parents_manager
parents = set()
for tag in tags:
some_parents = tag_parents_manager.GetParents( self._service_key, tag )
parents.update( some_parents )
if len( tags ) > 0:
self._tags.EnterTags( tags )
self._tags.AddTags( parents )
def GetTags( self ):
return self._tags.GetTags()
def OK( self ):
self.EndModal( wx.ID_OK )
class DialogInputUPnPMapping( Dialog ):
def __init__( self, parent, external_port, protocol_type, internal_port, description, duration ):
Dialog.__init__( self, parent, 'configure upnp mapping' )
self._external_port = wx.SpinCtrl( self, min = 0, max = 65535 )
self._protocol_type = ClientGUICommon.BetterChoice( self )
self._protocol_type.Append( 'TCP', 'TCP' )
self._protocol_type.Append( 'UDP', 'UDP' )
self._internal_port = wx.SpinCtrl( self, min = 0, max = 65535 )
self._description = wx.TextCtrl( self )
self._duration = wx.SpinCtrl( self, min = 0, max = 86400 )
self._ok = ClientGUICommon.BetterButton( self, 'OK', self.EndModal, wx.ID_OK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
self._external_port.SetValue( external_port )
if protocol_type == 'TCP': self._protocol_type.Select( 0 )
elif protocol_type == 'UDP': self._protocol_type.Select( 1 )
self._internal_port.SetValue( internal_port )
self._description.SetValue( description )
self._duration.SetValue( duration )
#
rows = []
rows.append( ( 'external port: ', self._external_port ) )
rows.append( ( 'protocol type: ', self._protocol_type ) )
rows.append( ( 'internal port: ', self._internal_port ) )
rows.append( ( 'description: ', self._description ) )
rows.append( ( 'duration (0 = indefinite): ', self._duration ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.Add( self._ok, CC.FLAGS_VCENTER )
b_box.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
vbox.Add( b_box, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def GetInfo( self ):
external_port = self._external_port.GetValue()
protocol_type = self._protocol_type.GetValue()
internal_port = self._internal_port.GetValue()
description = self._description.GetValue()
duration = self._duration.GetValue()
return ( external_port, protocol_type, internal_port, description, duration )
class DialogModifyAccounts( Dialog ):
def __init__( self, parent, service_key, subject_identifiers ):
Dialog.__init__( self, parent, 'modify account' )
self._service = HG.client_controller.services_manager.GetService( service_key )
self._subject_identifiers = list( subject_identifiers )
#
self._account_info_panel = ClientGUICommon.StaticBox( self, 'account info' )
self._subject_text = wx.StaticText( self._account_info_panel )
#
self._account_types_panel = ClientGUICommon.StaticBox( self, 'account types' )
self._account_types = wx.Choice( self._account_types_panel )
self._account_types_ok = wx.Button( self._account_types_panel, label = 'OK' )
self._account_types_ok.Bind( wx.EVT_BUTTON, self.EventChangeAccountType )
#
self._expiration_panel = ClientGUICommon.StaticBox( self, 'change expiration' )
self._add_to_expires = wx.Choice( self._expiration_panel )
self._add_to_expires_ok = wx.Button( self._expiration_panel, label = 'OK' )
self._add_to_expires_ok.Bind( wx.EVT_BUTTON, self.EventAddToExpires )
self._set_expires = wx.Choice( self._expiration_panel )
self._set_expires_ok = wx.Button( self._expiration_panel, label = 'OK' )
self._set_expires_ok.Bind( wx.EVT_BUTTON, self.EventSetExpires )
#
self._ban_panel = ClientGUICommon.StaticBox( self, 'bans' )
self._ban = wx.Button( self._ban_panel, label = 'ban user' )
self._ban.Bind( wx.EVT_BUTTON, self.EventBan )
self._ban.SetBackgroundColour( ( 255, 0, 0 ) )
self._ban.SetForegroundColour( ( 255, 255, 0 ) )
self._superban = wx.Button( self._ban_panel, label = 'ban user and delete every contribution they have ever made' )
self._superban.Bind( wx.EVT_BUTTON, self.EventSuperban )
self._superban.SetBackgroundColour( ( 255, 0, 0 ) )
self._superban.SetForegroundColour( ( 255, 255, 0 ) )
self._exit = wx.Button( self, id = wx.ID_CANCEL, label = 'Exit' )
#
if len( self._subject_identifiers ) == 1:
( subject_identifier, ) = self._subject_identifiers
response = self._service.Request( HC.GET, 'account_info', { 'subject_identifier' : subject_identifier } )
subject_string = str( response[ 'account_info' ] )
else:
subject_string = 'modifying ' + HydrusData.ToHumanInt( len( self._subject_identifiers ) ) + ' accounts'
self._subject_text.SetLabelText( subject_string )
#
response = self._service.Request( HC.GET, 'account_types' )
account_types = response[ 'account_types' ]
for account_type in account_types: self._account_types.Append( account_type.ConvertToString(), account_type )
self._account_types.SetSelection( 0 )
#
for ( string, value ) in HC.lifetimes:
if value is not None:
self._add_to_expires.Append( string, value ) # don't want 'add no limit'
self._add_to_expires.SetSelection( 1 ) # three months
for ( string, value ) in HC.lifetimes: self._set_expires.Append( string, value )
self._set_expires.SetSelection( 1 ) # three months
#
self._account_info_panel.Add( self._subject_text, CC.FLAGS_EXPAND_PERPENDICULAR )
account_types_hbox = wx.BoxSizer( wx.HORIZONTAL )
account_types_hbox.Add( self._account_types, CC.FLAGS_VCENTER )
account_types_hbox.Add( self._account_types_ok, CC.FLAGS_VCENTER )
self._account_types_panel.Add( account_types_hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
add_to_expires_box = wx.BoxSizer( wx.HORIZONTAL )
add_to_expires_box.Add( wx.StaticText( self._expiration_panel, label = 'add to expires: ' ), CC.FLAGS_VCENTER )
add_to_expires_box.Add( self._add_to_expires, CC.FLAGS_EXPAND_BOTH_WAYS )
add_to_expires_box.Add( self._add_to_expires_ok, CC.FLAGS_VCENTER )
set_expires_box = wx.BoxSizer( wx.HORIZONTAL )
set_expires_box.Add( wx.StaticText( self._expiration_panel, label = 'set expires to: ' ), CC.FLAGS_VCENTER )
set_expires_box.Add( self._set_expires, CC.FLAGS_EXPAND_BOTH_WAYS )
set_expires_box.Add( self._set_expires_ok, CC.FLAGS_VCENTER )
self._expiration_panel.Add( add_to_expires_box, CC.FLAGS_EXPAND_PERPENDICULAR )
self._expiration_panel.Add( set_expires_box, CC.FLAGS_EXPAND_PERPENDICULAR )
self._ban_panel.Add( self._ban, CC.FLAGS_EXPAND_PERPENDICULAR )
self._ban_panel.Add( self._superban, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._account_info_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._account_types_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._expiration_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._ban_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._exit, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._exit.SetFocus )
def _DoModification( self ):
# change this to saveaccounts or whatever. the previous func changes the accounts, and then we push that change
# generate accounts, with the modification having occurred
self._service.Request( HC.POST, 'account', { 'accounts' : self._accounts } )
if len( self._subject_identifiers ) == 1:
( subject_identifier, ) = self._subject_identifiers
response = self._service.Request( HC.GET, 'account_info', { 'subject_identifier' : subject_identifier } )
account_info = response[ 'account_info' ]
self._subject_text.SetLabelText( str( account_info ) )
if len( self._subject_identifiers ) > 1: wx.MessageBox( 'Done!' )
def EventAddToExpires( self, event ):
raise NotImplementedError()
#self._DoModification( HC.ADD_TO_EXPIRES, timespan = self._add_to_expires.GetClientData( self._add_to_expires.GetSelection() ) )
def EventBan( self, event ):
with DialogTextEntry( self, 'Enter reason for the ban.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
raise NotImplementedError()
#self._DoModification( HC.BAN, reason = dlg.GetValue() )
def EventChangeAccountType( self, event ):
raise NotImplementedError()
#self._DoModification( HC.CHANGE_ACCOUNT_TYPE, account_type_key = self._account_types.GetValue() )
def EventSetExpires( self, event ):
expires = self._set_expires.GetClientData( self._set_expires.GetSelection() )
if expires is not None:
expires += HydrusData.GetNow()
raise NotImplementedError()
#self._DoModification( HC.SET_EXPIRES, expires = expires )
def EventSuperban( self, event ):
with DialogTextEntry( self, 'Enter reason for the superban.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
raise NotImplementedError()
#self._DoModification( HC.SUPERBAN, reason = dlg.GetValue() )
class DialogSelectFromURLTree( Dialog ):
def __init__( self, parent, url_tree ):
Dialog.__init__( self, parent, 'select items' )
agwStyle = wx.lib.agw.customtreectrl.TR_DEFAULT_STYLE | wx.lib.agw.customtreectrl.TR_AUTO_CHECK_CHILD
self._tree = wx.lib.agw.customtreectrl.CustomTreeCtrl( self, agwStyle = agwStyle )
self._ok = ClientGUICommon.BetterButton( self, 'OK', self.EndModal, wx.ID_OK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
( text_gumpf, name, size, children ) = url_tree
root_name = self._RenderItemName( name, size )
root_item = self._tree.AddRoot( root_name, ct_type = 1 )
self._AddDirectory( root_item, children )
self._tree.CheckItem( root_item )
self._tree.Expand( root_item )
#
button_hbox = wx.BoxSizer( wx.HORIZONTAL )
button_hbox.Add( self._ok, CC.FLAGS_VCENTER )
button_hbox.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._tree, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( button_hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
x = max( x, 640 )
y = max( y, 640 )
self.SetInitialSize( ( x, y ) )
def _AddDirectory( self, root, children ):
for ( child_type, name, size, data ) in children:
item_name = self._RenderItemName( name, size )
if child_type == 'file':
self._tree.AppendItem( root, item_name, ct_type = 1, data = data )
else:
subroot = self._tree.AppendItem( root, item_name, ct_type = 1 )
self._AddDirectory( subroot, data )
def _GetSelectedChildrenData( self, parent_item ):
result = []
cookie = 0
( child_item, cookie ) = self._tree.GetNextChild( parent_item, cookie )
while child_item is not None:
data = self._tree.GetItemPyData( child_item )
if data is None:
result.extend( self._GetSelectedChildrenData( child_item ) )
else:
if self._tree.IsItemChecked( child_item ):
result.append( data )
( child_item, cookie ) = self._tree.GetNextChild( parent_item, cookie )
return result
def _RenderItemName( self, name, size ):
return name + ' - ' + HydrusData.ToHumanBytes( size )
def GetURLs( self ):
root_item = self._tree.GetRootItem()
urls = self._GetSelectedChildrenData( root_item )
return urls
class DialogSelectImageboard( Dialog ):
def __init__( self, parent ):
Dialog.__init__( self, parent, 'select imageboard' )
self._tree = wx.TreeCtrl( self )
self._tree.Bind( wx.EVT_TREE_ITEM_ACTIVATED, self.EventActivate )
#
all_imageboards = HG.client_controller.Read( 'imageboards' )
root_item = self._tree.AddRoot( 'all sites' )
for ( site, imageboards ) in list(all_imageboards.items()):
site_item = self._tree.AppendItem( root_item, site )
for imageboard in imageboards:
name = imageboard.GetName()
self._tree.AppendItem( site_item, name, data = wx.TreeItemData( imageboard ) )
self._tree.Expand( root_item )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._tree, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
if x < 320: x = 320
if y < 640: y = 640
self.SetInitialSize( ( x, y ) )
def EventActivate( self, event ):
item = self._tree.GetSelection()
data_object = self._tree.GetItemData( item )
if data_object is None: self._tree.Toggle( item )
else: self.EndModal( wx.ID_OK )
def GetImageboard( self ): return self._tree.GetItemData( self._tree.GetSelection() ).GetData()
class DialogTextEntry( Dialog ):
def __init__( self, parent, message, default = '', allow_blank = False, suggestions = None, max_chars = None ):
if suggestions is None:
suggestions = []
Dialog.__init__( self, parent, 'enter text', position = 'center' )
self._chosen_suggestion = None
self._allow_blank = allow_blank
self._max_chars = max_chars
button_choices = []
for text in suggestions:
button_choices.append( ClientGUICommon.BetterButton( self, text, self.ButtonChoice, text ) )
self._text = wx.TextCtrl( self, style = wx.TE_PROCESS_ENTER )
self._text.Bind( wx.EVT_TEXT, self.EventText )
self._text.Bind( wx.EVT_TEXT_ENTER, self.EventEnter )
if self._max_chars is not None:
self._text.SetMaxLength( self._max_chars )
self._ok = ClientGUICommon.BetterButton( self, 'ok', self.EndModal, wx.ID_OK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
self._text.SetValue( default )
self._CheckText()
#
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.Add( self._ok, CC.FLAGS_SMALL_INDENT )
hbox.Add( self._cancel, CC.FLAGS_SMALL_INDENT )
st_message = ClientGUICommon.BetterStaticText( self, message )
st_message.SetWrapWidth( 480 )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( st_message, CC.FLAGS_BIG_INDENT )
for button in button_choices:
vbox.Add( button, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._text, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
x = max( x, 250 )
self.SetInitialSize( ( x, y ) )
def _CheckText( self ):
if not self._allow_blank:
if self._text.GetValue() == '':
self._ok.Disable()
else:
self._ok.Enable()
def ButtonChoice( self, text ):
self._chosen_suggestion = text
self.EndModal( wx.ID_OK )
def EventText( self, event ):
wx.CallAfter( self._CheckText )
event.Skip()
def EventEnter( self, event ):
if self._text.GetValue() != '':
self.EndModal( wx.ID_OK )
def GetValue( self ):
if self._chosen_suggestion is None:
return self._text.GetValue()
else:
return self._chosen_suggestion
class DialogYesYesNo( Dialog ):
def __init__( self, parent, message, title = 'Are you sure?', yes_tuples = None, no_label = 'no' ):
if yes_tuples is None:
yes_tuples = [ ( 'yes', 'yes' ) ]
Dialog.__init__( self, parent, title, position = 'center' )
self._value = None
yes_buttons = []
for ( label, data ) in yes_tuples:
yes_button = ClientGUICommon.BetterButton( self, label, self._DoYes, data )
yes_button.SetForegroundColour( ( 0, 128, 0 ) )
yes_buttons.append( yes_button )
self._no = ClientGUICommon.BetterButton( self, no_label, self.EndModal, wx.ID_NO )
self._no.SetForegroundColour( ( 128, 0, 0 ) )
#
hbox = wx.BoxSizer( wx.HORIZONTAL )
for yes_button in yes_buttons:
hbox.Add( yes_button, CC.FLAGS_SMALL_INDENT )
hbox.Add( self._no, CC.FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
text = ClientGUICommon.BetterStaticText( self, message )
text.SetWrapWidth( 480 )
vbox.Add( text, CC.FLAGS_BIG_INDENT )
vbox.Add( hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
x = max( x, 250 )
self.SetInitialSize( ( x, y ) )
wx.CallAfter( yes_buttons[0].SetFocus )
def _DoYes( self, value ):
self._value = value
self.EndModal( wx.ID_YES )
def GetValue( self ):
return self._value