hydrus/include/ClientGUIDialogs.py

2512 lines
79 KiB
Python
Executable File

import HydrusConstants as HC
import ClientConstants as CC
import ClientData
import ClientDefaults
import ClientDownloading
import ClientDragDrop
import ClientExporting
import ClientCaches
import ClientFiles
import ClientGUIACDropdown
import ClientGUIFrames
import ClientGUICommon
import ClientGUIImport
import ClientGUIListBoxes
import ClientGUIListCtrl
import ClientGUIPredicates
import ClientGUITime
import ClientGUITopLevelWindows
import ClientImporting
import ClientThreading
import collections
import gc
import HydrusExceptions
import HydrusFileHandling
import HydrusNATPunch
import HydrusNetwork
import HydrusPaths
import HydrusSerialisable
import HydrusTagArchive
import HydrusTags
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 urllib
import wx
import wx.lib.agw.customtreectrl
import wx.adv
import yaml
import HydrusData
import ClientSearch
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 }
list_of_tuples = [ ( service.GetName(), service.GetServiceKey() ) for service in services ]
with DialogSelectFromList( HG.client_controller.GetGUI(), 'select service', list_of_tuples ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
service_key = dlg.GetChoice()
return service_key
else:
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_BUTTON, self.EventDialogButton )
if parent is not None and position == 'center':
wx.CallAfter( self.Center )
HG.client_controller.ResetIdleTimer()
def EventDialogButton( self, event ):
if self.IsModal():
self.EndModal( event.GetId() )
def SetInitialSize( self, ( width, height ) ):
( 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 DialogButtonChoice( Dialog ):
def __init__( self, parent, intro, choices, show_always_checkbox = False ):
Dialog.__init__( self, parent, 'choose what to do', position = 'center' )
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
self._buttons = []
self._ids_to_data = {}
i = 0
for ( text, data, tooltip ) in choices:
button = wx.Button( self, label = text, id = i )
button.SetToolTip( tooltip )
self._buttons.append( button )
self._ids_to_data[ i ] = data
i += 1
self._always_do_checkbox = wx.CheckBox( self, label = 'do this for all' )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( ClientGUICommon.BetterStaticText( self, intro ), CC.FLAGS_EXPAND_PERPENDICULAR )
for button in self._buttons:
vbox.Add( button, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._always_do_checkbox, CC.FLAGS_LONE_BUTTON )
if not show_always_checkbox:
self._always_do_checkbox.Hide()
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
self.Bind( wx.EVT_BUTTON, self.EventButton )
if len( self._buttons ) > 0:
wx.CallAfter( self._buttons[0].SetFocus )
def EventButton( self, event ):
id = event.GetId()
if id == wx.ID_CANCEL: self.EndModal( wx.ID_CANCEL )
else:
self._data = self._ids_to_data[ id ]
self.EndModal( wx.ID_OK )
def GetData( self ):
return ( self._always_do_checkbox.GetValue(), self._data )
class DialogChooseNewServiceMethod( Dialog ):
def __init__( self, parent ):
Dialog.__init__( self, parent, 'how to set up the account?', position = 'center' )
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
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 DialogCommitInterstitialFiltering( Dialog ):
def __init__( self, parent, label ):
Dialog.__init__( self, parent, 'commit and continue?', position = 'center' )
self._commit = wx.Button( self, id = wx.ID_YES, label = 'commit and continue' )
self._commit.SetForegroundColour( ( 0, 128, 0 ) )
self._back = wx.Button( self, id = wx.ID_CANCEL, label = 'go back' )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( wx.StaticText( self, label = label, style = wx.ALIGN_CENTER ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._commit, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( wx.StaticText( self, label = '-or-', style = wx.ALIGN_CENTER ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._back, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._commit.SetFocus )
class DialogFinishFiltering( Dialog ):
def __init__( self, parent, label ):
Dialog.__init__( self, parent, 'are you sure?', position = 'center' )
self._commit = ClientGUICommon.BetterButton( self, 'commit', self.EndModal, wx.ID_YES )
self._commit.SetForegroundColour( ( 0, 128, 0 ) )
self._forget = ClientGUICommon.BetterButton( self, 'forget', self.EndModal, wx.ID_NO )
self._forget.SetForegroundColour( ( 128, 0, 0 ) )
self._back = ClientGUICommon.BetterButton( self, 'back to filtering', self.EndModal, wx.ID_CANCEL )
self._back.SetId( wx.ID_CANCEL )
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.Add( self._commit, CC.FLAGS_EXPAND_BOTH_WAYS )
hbox.Add( self._forget, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( wx.StaticText( self, label = label, style = wx.ALIGN_CENTER ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( wx.StaticText( self, label = '-or-', style = wx.ALIGN_CENTER ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.Add( self._back, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._commit.SetFocus )
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, 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.GetChoice()
account_type_key = account_type.GetAccountTypeKey()
lifetime = self._lifetime.GetChoice()
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 DialogInputFileSystemPredicates( Dialog ):
def __init__( self, parent, predicate_type ):
Dialog.__init__( self, parent, 'enter predicate' )
pred_classes = []
if predicate_type == HC.PREDICATE_TYPE_SYSTEM_AGE:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemAgeDelta )
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemAgeDate )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_DIMENSIONS:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemHeight )
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemWidth )
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemRatio )
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemNumPixels )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_DURATION:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemDuration )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_FILE_SERVICE:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemFileService )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_KNOWN_URLS:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemKnownURLsExactURL )
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemKnownURLsDomain )
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemKnownURLsRegex )
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemKnownURLsURLMatch )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_HASH:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemHash )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_LIMIT:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemLimit )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_MIME:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemMime )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_NUM_TAGS:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemNumTags )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_NUM_WORDS:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemNumWords )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_RATING:
services_manager = HG.client_controller.services_manager
ratings_services = services_manager.GetServices( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
if len( ratings_services ) > 0:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemRating )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_SIMILAR_TO:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemSimilarTo )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_SIZE:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemSize )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_TAG_AS_NUMBER:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemTagAsNumber )
elif predicate_type == HC.PREDICATE_TYPE_SYSTEM_DUPLICATE_RELATIONSHIPS:
pred_classes.append( ClientGUIPredicates.PanelPredicateSystemDuplicateRelationships )
vbox = wx.BoxSizer( wx.VERTICAL )
for pred_class in pred_classes:
panel = self._Panel( self, pred_class )
vbox.Add( panel, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
def SubPanelOK( self, predicates ):
self._predicates = predicates
self.EndModal( wx.ID_OK )
def GetPredicates( self ): return self._predicates
class _Panel( wx.Panel ):
def __init__( self, parent, predicate_class ):
wx.Panel.__init__( self, parent )
self._predicate_panel = predicate_class( 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 ) )
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.Add( self._predicate_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
hbox.Add( self._ok, CC.FLAGS_VCENTER )
self.SetSizer( hbox )
self.Bind( wx.EVT_CHAR_HOOK, self.EventCharHook )
def _DoOK( self ):
predicates = self._predicate_panel.GetPredicates()
self.GetParent().SubPanelOK( predicates )
def EventCharHook( self, event ):
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
self._DoOK()
else:
event.Skip()
def EventOK( self, event ):
self._DoOK()
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.SelectClientData( 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.SelectClientData( 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.ConvertIntToPrettyString( 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 ):
self._service = HG.client_controller.services_manager.GetService( CC.LOCAL_BOORU_SERVICE_KEY )
external_ip = HydrusNATPunch.GetExternalIP() # eventually check for optional host replacement here
external_port = self._service.GetUPnPPort()
if external_port is None:
external_port = self._service.GetPort()
url = 'http://' + external_ip + ':' + HydrusData.ToUnicode( external_port ) + '/gallery?share_key=' + self._share_key.encode( 'hex' )
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.encode( '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.GetChoice() + HydrusData.GetNow()
return ( self._share_key, name, text, timeout, self._hashes )
class FrameInputLocalFiles( wx.Frame ):
def __init__( self, parent, paths = None ):
( pos_x, pos_y ) = parent.GetPosition()
pos = ( pos_x + 50, pos_y + 50 )
style = wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT
wx.Frame.__init__( self, parent, title = 'importing files', style = style, pos = pos )
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_FRAMEBK ) )
self.SetIcon( HG.client_controller.frame_icon )
if paths is None:
paths = []
self.SetDropTarget( ClientDragDrop.FileDropTarget( self, filenames_callable = self._AddPathsToList ) )
listctrl_panel = ClientGUIListCtrl.SaneListCtrlPanel( self )
self._paths_list = ClientGUIListCtrl.SaneListCtrl( listctrl_panel, 120, [ ( 'path', -1 ), ( 'guessed mime', 110 ), ( 'size', 60 ) ], delete_key_callback = self.RemovePaths )
listctrl_panel.SetListCtrl( self._paths_list )
listctrl_panel.AddButton( 'add files', self.AddPaths )
listctrl_panel.AddButton( 'add folder', self.AddFolder )
listctrl_panel.AddButton( 'remove files', self.RemovePaths, enabled_only_on_selection = True )
self._progress = ClientGUICommon.TextAndGauge( self )
self._progress_pause = ClientGUICommon.BetterBitmapButton( self, CC.GlobalBMPs.pause, self.PauseProgress )
self._progress_pause.Disable()
self._progress_cancel = ClientGUICommon.BetterBitmapButton( self, CC.GlobalBMPs.stop, self.StopProgress )
self._progress_cancel.Disable()
file_import_options = HG.client_controller.new_options.GetDefaultFileImportOptions( 'loud' )
self._file_import_options = ClientGUIImport.FileImportOptionsButton( self, file_import_options )
self._delete_after_success_st = ClientGUICommon.BetterStaticText( self, style = wx.ALIGN_RIGHT | wx.ST_NO_AUTORESIZE )
self._delete_after_success_st.SetForegroundColour( ( 127, 0, 0 ) )
self._delete_after_success = wx.CheckBox( self, label = 'delete original files after successful import' )
self._delete_after_success.Bind( wx.EVT_CHECKBOX, self.EventDeleteAfterSuccessCheck )
self._add_button = wx.Button( self, label = 'import now' )
self._add_button.Bind( wx.EVT_BUTTON, self.EventOK )
self._add_button.SetForegroundColour( ( 0, 128, 0 ) )
self._tag_button = wx.Button( self, label = 'add tags based on filename' )
self._tag_button.Bind( wx.EVT_BUTTON, self.EventTags )
self._tag_button.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
gauge_sizer = wx.BoxSizer( wx.HORIZONTAL )
gauge_sizer.Add( self._progress, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
gauge_sizer.Add( self._progress_pause, CC.FLAGS_VCENTER )
gauge_sizer.Add( self._progress_cancel, CC.FLAGS_VCENTER )
delete_hbox = wx.BoxSizer( wx.HORIZONTAL )
delete_hbox.Add( self._delete_after_success_st, CC.FLAGS_EXPAND_BOTH_WAYS )
delete_hbox.Add( self._delete_after_success, CC.FLAGS_VCENTER )
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.Add( self._add_button, CC.FLAGS_VCENTER )
buttons.Add( self._tag_button, CC.FLAGS_VCENTER )
buttons.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( gauge_sizer, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( delete_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.Add( self._file_import_options, CC.FLAGS_LONE_BUTTON )
vbox.Add( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
if x < 780: x = 780
if y < 480: y = 480
self.SetInitialSize( ( x, y ) )
self._lock = threading.Lock()
self._current_paths = []
self._current_paths_set = set()
self._job_key = ClientThreading.JobKey()
self._unparsed_paths_queue = Queue.Queue()
self._currently_parsing = threading.Event()
self._work_to_do = threading.Event()
self._parsed_path_queue = Queue.Queue()
self._pause_event = threading.Event()
self._cancel_event = threading.Event()
self._progress_updater = ClientGUICommon.ThreadToGUIUpdater( self._progress, self._progress.SetValue )
if len( paths ) > 0:
self._AddPathsToList( paths )
self.Show()
HG.client_controller.gui.RegisterUIUpdateWindow( self )
HG.client_controller.CallToThreadLongRunning( self.THREADParseImportablePaths, self._unparsed_paths_queue, self._currently_parsing, self._work_to_do, self._parsed_path_queue, self._progress_updater, self._pause_event, self._cancel_event )
def _AddPathsToList( self, paths ):
if self._cancel_event.is_set():
message = 'Please wait for the cancel to clear.'
wx.MessageBox( message )
return
paths = [ HydrusData.ToUnicode( path ) for path in paths ]
self._unparsed_paths_queue.put( paths )
def _TidyUp( self ):
self._pause_event.set()
def AddFolder( self ):
with wx.DirDialog( self, 'Select a folder to add.', style = wx.DD_DIR_MUST_EXIST ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
path = HydrusData.ToUnicode( dlg.GetPath() )
self._AddPathsToList( ( path, ) )
def AddPaths( self ):
with wx.FileDialog( self, 'Select the files to add.', style = wx.FD_MULTIPLE ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
paths = [ HydrusData.ToUnicode( path ) for path in dlg.GetPaths() ]
self._AddPathsToList( paths )
def EventCancel( self, event ):
self._TidyUp()
self.Close()
def EventDeleteAfterSuccessCheck( self, event ):
if self._delete_after_success.GetValue():
self._delete_after_success_st.SetLabelText( 'YOUR ORIGINAL FILES WILL BE DELETED' )
else:
self._delete_after_success_st.SetLabelText( '' )
def EventOK( self, event ):
self._TidyUp()
if len( self._current_paths ) > 0:
file_import_options = self._file_import_options.GetValue()
paths_to_tags = {}
delete_after_success = self._delete_after_success.GetValue()
HG.client_controller.pub( 'new_hdd_import', self._current_paths, file_import_options, paths_to_tags, delete_after_success )
self.Close()
def EventTags( self, event ):
if len( self._current_paths ) > 0:
file_import_options = self._file_import_options.GetValue()
with ClientGUITopLevelWindows.DialogEdit( self, 'filename tagging', frame_key = 'local_import_filename_tagging' ) as dlg:
panel = ClientGUIImport.EditLocalImportFilenameTaggingPanel( dlg, self._current_paths )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
paths_to_tags = panel.GetValue()
delete_after_success = self._delete_after_success.GetValue()
HG.client_controller.pub( 'new_hdd_import', self._current_paths, file_import_options, paths_to_tags, delete_after_success )
self.Close()
def PauseProgress( self ):
if self._pause_event.is_set():
self._pause_event.clear()
else:
self._pause_event.set()
def StopProgress( self ):
self._cancel_event.set()
def RemovePaths( self ):
with DialogYesNo( self, 'Remove all selected?' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._paths_list.RemoveAllSelected()
self._current_paths = [ row[0] for row in self._paths_list.GetClientData() ]
self._current_paths_set = set( self._current_paths )
def THREADParseImportablePaths( self, unparsed_paths_queue, currently_parsing, work_to_do, parsed_path_queue, progress_updater, pause_event, cancel_event ):
unparsed_paths = []
num_files_done = 0
num_good_files = 0
num_empty_files = 0
num_uninteresting_mime_files = 0
num_occupied_files = 0
while not HG.view_shutdown:
if not self:
return
if len( unparsed_paths ) > 0:
work_to_do.set()
else:
work_to_do.clear()
currently_parsing.clear()
# ready to start, let's update ui on status
total_paths = num_files_done + len( unparsed_paths )
if num_good_files == 0:
if num_files_done == 0:
message = 'waiting for paths to parse'
else:
message = 'none of the ' + HydrusData.ConvertIntToPrettyString( total_paths ) + ' files parsed successfully'
else:
message = HydrusData.ConvertValueRangeToPrettyString( num_good_files, total_paths ) + ' files parsed successfully'
if num_empty_files > 0 or num_uninteresting_mime_files > 0 or num_occupied_files > 0:
if num_good_files == 0:
message += ': '
else:
message += ', but '
bad_comments = []
if num_empty_files > 0:
bad_comments.append( HydrusData.ConvertIntToPrettyString( num_empty_files ) + ' were empty' )
if num_uninteresting_mime_files > 0:
bad_comments.append( HydrusData.ConvertIntToPrettyString( num_uninteresting_mime_files ) + ' had unsupported mimes' )
if num_occupied_files > 0:
bad_comments.append( HydrusData.ConvertIntToPrettyString( num_occupied_files ) + ' were probably already in use by another process' )
message += ' and '.join( bad_comments )
message += '.'
progress_updater.Update( message, num_files_done, total_paths )
# status updated, lets see what work there is to do
if cancel_event.is_set():
while not unparsed_paths_queue.empty():
try:
unparsed_paths_queue.get( block = False )
except Queue.Empty:
pass
unparsed_paths = []
cancel_event.clear()
pause_event.clear()
continue
if pause_event.is_set():
time.sleep( 1 )
continue
# let's see if there is anything to parse
# first we'll flesh out unparsed_paths with anything new to look at
while not unparsed_paths_queue.empty():
try:
raw_paths = unparsed_paths_queue.get( block = False )
paths = ClientFiles.GetAllPaths( raw_paths ) # convert any dirs to subpaths
unparsed_paths.extend( paths )
except Queue.Empty:
pass
# if unparsed_paths still has nothing, we'll nonetheless sleep on new paths
if len( unparsed_paths ) == 0:
try:
raw_paths = unparsed_paths_queue.get( timeout = 5 )
paths = ClientFiles.GetAllPaths( raw_paths ) # convert any dirs to subpaths
unparsed_paths.extend( paths )
except Queue.Empty:
pass
continue # either we added some or didn't--in any case, restart the cycle
path = unparsed_paths.pop( 0 )
currently_parsing.set()
# we are now dealing with a file. let's clear out quick no-gos
num_files_done += 1
if path.endswith( os.path.sep + 'Thumbs.db' ) or path.endswith( os.path.sep + 'thumbs.db' ):
num_uninteresting_mime_files += 1
continue
if not HydrusPaths.PathIsFree( path ):
num_occupied_files += 1
continue
size = os.path.getsize( path )
if size == 0:
HydrusData.Print( 'Empty file: ' + path )
num_empty_files += 1
continue
# looks good, let's burn some CPU
mime = HydrusFileHandling.GetMime( path )
if mime in HC.ALLOWED_MIMES:
num_good_files += 1
parsed_path_queue.put( ( path, mime, size ) )
else:
HydrusData.Print( 'Unparsable file: ' + path )
num_uninteresting_mime_files += 1
def TIMERUIUpdate( self ):
while not self._parsed_path_queue.empty():
( path, mime, size ) = self._parsed_path_queue.get()
pretty_mime = HC.mime_string_lookup[ mime ]
pretty_size = HydrusData.ConvertIntToBytes( size )
if path not in self._current_paths_set:
self._current_paths_set.add( path )
self._current_paths.append( path )
self._paths_list.Append( ( path, pretty_mime, pretty_size ), ( path, mime, size ) )
#
paused = self._pause_event.is_set()
no_work_in_queue = not self._work_to_do.is_set()
working_on_a_file_now = self._currently_parsing.is_set()
can_import = ( no_work_in_queue or paused ) and not working_on_a_file_now
if no_work_in_queue:
self._progress_pause.Disable()
self._progress_cancel.Disable()
else:
self._progress_pause.Enable()
self._progress_cancel.Enable()
if can_import:
self._add_button.Enable()
self._tag_button.Enable()
else:
self._add_button.Disable()
self._tag_button.Disable()
if paused:
ClientGUICommon.SetBitmapButtonBitmap( self._progress_pause, CC.GlobalBMPs.play )
else:
ClientGUICommon.SetBitmapButtonBitmap( self._progress_pause, CC.GlobalBMPs.pause )
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 = wx.adv.HyperlinkCtrl( self, id = -1, label = 'a good regex introduction', url = 'http://www.aivosto.com/vbtips/regex.html' )
self._regex_practise_link = wx.adv.HyperlinkCtrl( self, id = -1, label = 'regex practise', url = '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, flags = re.UNICODE )
except Exception as e:
text = 'That regex would not compile!'
text += os.linesep * 2
text += HydrusData.ToUnicode( 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 DialogInputNewFormField( Dialog ):
def __init__( self, parent, form_field = None ):
Dialog.__init__( self, parent, 'configure form field' )
if form_field is None: ( name, field_type, default, editable ) = ( '', CC.FIELD_TEXT, '', True )
else: ( name, field_type, default, editable ) = form_field
self._name = wx.TextCtrl( self )
self._type = wx.Choice( self )
self._default = wx.TextCtrl( self )
self._editable = wx.CheckBox( self )
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._name.SetValue( name )
for temp_type in CC.FIELDS: self._type.Append( CC.field_string_lookup[ temp_type ], temp_type )
self._type.Select( field_type )
self._default.SetValue( default )
self._editable.SetValue( editable )
#
rows = []
rows.append( ( 'name: ', self._name ) )
rows.append( ( 'type: ', self._type ) )
rows.append( ( 'default: ', self._default ) )
rows.append( ( 'editable: ', self._editable ) )
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_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 GetFormField( self ):
name = self._name.GetValue()
field_type = self._type.GetClientData( self._type.GetSelection() )
default = self._default.GetValue()
editable = self._editable.GetValue()
return ( name, field_type, default, editable )
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 )
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._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.GetManager( 'tag_parents' )
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 = 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._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.GetChoice()
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 = HydrusData.ToUnicode( response[ 'account_info' ] )
else: subject_string = 'modifying ' + HydrusData.ConvertIntToPrettyString( 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 occured
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( HydrusData.ToUnicode( account_info ) )
if len( self._subject_identifiers ) > 1: wx.MessageBox( 'Done!' )
def EventAddToExpires( self, event ):
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: self._DoModification( HC.BAN, reason = dlg.GetValue() )
def EventChangeAccountType( self, event ):
self._DoModification( HC.CHANGE_ACCOUNT_TYPE, account_type_key = self._account_types.GetChoice() )
def EventSetExpires( self, event ):
expires = self._set_expires.GetClientData( self._set_expires.GetSelection() )
if expires is not None: expires += HydrusData.GetNow()
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: self._DoModification( HC.SUPERBAN, reason = dlg.GetValue() )
class DialogSelectBooru( Dialog ):
def __init__( self, parent ):
Dialog.__init__( self, parent, 'select booru' )
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
self._boorus = wx.ListBox( self )
self._boorus.Bind( wx.EVT_LISTBOX_DCLICK, self.EventDoubleClick )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetDefault()
#
boorus = HG.client_controller.Read( 'remote_boorus' )
booru_names = boorus.keys()
booru_names.sort()
for name in booru_names:
self._boorus.Append( name )
self._boorus.Select( 0 )
self._boorus.SetFocus()
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._boorus, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( self._ok, CC.FLAGS_LONE_BUTTON )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
x = max( x, 320 )
y = max( y, 320 )
self.SetInitialSize( ( x, y ) )
if len( boorus ) == 1:
wx.CallAfter( self.EndModal, wx.ID_OK )
def EventDoubleClick( self, event ):
self.EndModal( wx.ID_OK )
def EventOK( self, event ):
selection = self._boorus.GetSelection()
if selection != wx.NOT_FOUND:
self.EndModal( wx.ID_OK )
def GetGalleryIdentifier( self ):
name = self._boorus.GetString( self._boorus.GetSelection() )
gallery_identifier = ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_BOORU, additional_info = name )
return gallery_identifier
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 = wx.Button( self, id = wx.ID_OK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_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.ConvertIntToBytes( 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._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
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 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 DialogCheckFromList( Dialog ):
def __init__( self, parent, title, list_of_tuples ):
Dialog.__init__( self, parent, title )
self._check_list_box = ClientGUICommon.BetterCheckListBox( self, style = wx.LB_EXTENDED )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
for ( index, ( text, data, selected ) ) in enumerate( list_of_tuples ):
self._check_list_box.Append( text, data )
if selected:
self._check_list_box.Check( index )
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.Add( self._ok, CC.FLAGS_VCENTER )
hbox.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._check_list_box, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
if x < 320: x = 320
self.SetInitialSize( ( x, y ) )
def GetChecked( self ):
return self._check_list_box.GetChecked()
class DialogSelectFromList( Dialog ):
def __init__( self, parent, title, list_of_tuples ):
Dialog.__init__( self, parent, title )
self._list = wx.ListBox( self )
self._list.Bind( wx.EVT_LISTBOX_DCLICK, self.EventSelect )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.SetDefault()
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) ) # to handle esc key events properly
#
list_of_tuples.sort()
for ( label, value ) in list_of_tuples:
self._list.Append( label, value )
self._list.Select( 0 )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._list, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( self._ok, CC.FLAGS_LONE_BUTTON )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
x = max( x, 320 )
y = max( y, 320 )
self.SetInitialSize( ( x, y ) )
def EventSelect( self, event ):
self.EndModal( wx.ID_OK )
def GetChoice( self ):
selection = self._list.GetSelection()
return self._list.GetClientData( selection )
class DialogSelectYoutubeURL( Dialog ):
def __init__( self, parent, info ):
Dialog.__init__( self, parent, 'choose youtube format' )
self._info = info
self._urls = ClientGUIListCtrl.SaneListCtrl( self, 360, [ ( 'format', 150 ), ( 'resolution', -1 ) ] )
self._urls.Bind( wx.EVT_LIST_ITEM_ACTIVATED, self.EventOK )
self._urls.SetMinSize( ( 360, 200 ) )
self._ok = wx.Button( self, 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 ) )
#
keys = list( self._info.keys() )
keys.sort()
for ( extension, resolution ) in keys:
self._urls.Append( ( extension, resolution ), ( extension, resolution ) )
#self._urls.SortListItems( 0 )
#
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.Add( self._ok, CC.FLAGS_VCENTER )
buttons.Add( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.Add( self._urls, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.Add( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def EventOK( self, event ):
indices = self._urls.GetAllSelected()
if len( indices ) > 0:
for index in indices:
( extension, resolution ) = self._urls.GetClientData( index )
( url, title ) = self._info[ ( extension, resolution ) ]
url_string = title + ' ' + resolution + ' ' + extension
job_key = ClientThreading.JobKey( pausable = True, cancellable = True )
HG.client_controller.CallToThread( ClientImporting.THREADDownloadURL, job_key, url, url_string )
HG.client_controller.pub( 'message', job_key )
self.EndModal( wx.ID_OK )
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 = 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._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.Wrap( 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 DialogYesNo( Dialog ):
def __init__( self, parent, message, title = 'Are you sure?', yes_label = 'yes', no_label = 'no' ):
Dialog.__init__( self, parent, title, position = 'center' )
self._yes = wx.Button( self, id = wx.ID_YES )
self._yes.SetForegroundColour( ( 0, 128, 0 ) )
self._yes.SetLabelText( yes_label )
self._no = wx.Button( self, id = wx.ID_NO )
self._no.SetForegroundColour( ( 128, 0, 0 ) )
self._no.SetLabelText( no_label )
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
#
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.Add( self._yes, CC.FLAGS_SMALL_INDENT )
hbox.Add( self._no, CC.FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
text = ClientGUICommon.BetterStaticText( self, message )
text.Wrap( 480 )
vbox.Add( 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 ) )
wx.CallAfter( self._yes.SetFocus )
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 = wx.Button( self, id = wx.ID_NO )
self._no.SetForegroundColour( ( 128, 0, 0 ) )
self._no.SetLabelText( no_label )
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 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.Wrap( 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