8089 lines
326 KiB
Python
8089 lines
326 KiB
Python
import Crypto.PublicKey.RSA
|
|
import HydrusConstants as HC
|
|
import HydrusEncryption
|
|
import HydrusExceptions
|
|
import HydrusTags
|
|
import ClientConstants as CC
|
|
import ClientConstantsMessages
|
|
import ClientGUICommon
|
|
import ClientGUIDialogs
|
|
import ClientGUIMixins
|
|
import collections
|
|
import HydrusNATPunch
|
|
import itertools
|
|
import os
|
|
import random
|
|
import re
|
|
import string
|
|
import subprocess
|
|
import time
|
|
import traceback
|
|
import urllib
|
|
import wx
|
|
import yaml
|
|
|
|
# Option Enums
|
|
|
|
ID_NULL = wx.NewId()
|
|
|
|
ID_TIMER_UPDATE = wx.NewId()
|
|
|
|
# Hue is generally 200, Sat and Lum changes based on need
|
|
|
|
COLOUR_SELECTED = wx.Colour( 217, 242, 255 )
|
|
COLOUR_SELECTED_DARK = wx.Colour( 1, 17, 26 )
|
|
COLOUR_UNSELECTED = wx.Colour( 223, 227, 230 )
|
|
|
|
# Sizer Flags
|
|
|
|
FLAGS_NONE = wx.SizerFlags( 0 )
|
|
|
|
FLAGS_SMALL_INDENT = wx.SizerFlags( 0 ).Border( wx.ALL, 2 )
|
|
FLAGS_BIG_INDENT = wx.SizerFlags( 0 ).Border( wx.ALL, 8 )
|
|
|
|
FLAGS_EXPAND_PERPENDICULAR = wx.SizerFlags( 0 ).Border( wx.ALL, 2 ).Expand()
|
|
FLAGS_EXPAND_BOTH_WAYS = wx.SizerFlags( 2 ).Border( wx.ALL, 2 ).Expand()
|
|
FLAGS_EXPAND_DEPTH_ONLY = wx.SizerFlags( 2 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_CENTER_VERTICAL )
|
|
|
|
FLAGS_EXPAND_SIZER_PERPENDICULAR = wx.SizerFlags( 0 ).Expand()
|
|
FLAGS_EXPAND_SIZER_BOTH_WAYS = wx.SizerFlags( 2 ).Expand()
|
|
FLAGS_EXPAND_SIZER_DEPTH_ONLY = wx.SizerFlags( 2 ).Align( wx.ALIGN_CENTER_VERTICAL )
|
|
|
|
FLAGS_BUTTON_SIZER = wx.SizerFlags( 0 ).Align( wx.ALIGN_RIGHT )
|
|
FLAGS_LONE_BUTTON = wx.SizerFlags( 0 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_RIGHT )
|
|
|
|
FLAGS_MIXED = wx.SizerFlags( 0 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_CENTER_VERTICAL )
|
|
|
|
class DialogManage4chanPass( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._token = wx.TextCtrl( self )
|
|
self._pin = wx.TextCtrl( self )
|
|
|
|
self._status = wx.StaticText( self )
|
|
|
|
self._SetStatus()
|
|
|
|
self._reauthenticate = wx.Button( self, label = 'reauthenticate' )
|
|
self._reauthenticate.Bind( wx.EVT_BUTTON, self.EventReauthenticate )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
self._token.SetValue( token )
|
|
self._pin.SetValue( pin )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
gridbox.AddF( wx.StaticText( self, label = 'token' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._token, FLAGS_EXPAND_BOTH_WAYS )
|
|
gridbox.AddF( wx.StaticText( self, label = 'pin' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._pin, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
b_box = wx.BoxSizer( wx.HORIZONTAL )
|
|
b_box.AddF( self._ok, FLAGS_MIXED )
|
|
b_box.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( gridbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
vbox.AddF( self._status, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._reauthenticate, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( b_box, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage 4chan pass' )
|
|
|
|
( token, pin, self._timeout ) = HC.app.Read( '4chan_pass' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
x = max( x, 240 )
|
|
|
|
self.SetInitialSize( ( x, y ) )
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def _SetStatus( self ):
|
|
|
|
if self._timeout == 0: label = 'not authenticated'
|
|
elif self._timeout < HC.GetNow(): label = 'timed out'
|
|
else: label = 'authenticated - ' + HC.ConvertTimestampToPrettyExpires( self._timeout )
|
|
|
|
self._status.SetLabel( label )
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
token = self._token.GetValue()
|
|
pin = self._pin.GetValue()
|
|
|
|
HC.app.Write( '4chan_pass', ( token, pin, self._timeout ) )
|
|
|
|
self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventReauthenticate( self, event ):
|
|
|
|
token = self._token.GetValue()
|
|
pin = self._pin.GetValue()
|
|
|
|
if token == '' and pin == '':
|
|
|
|
self._timeout = 0
|
|
|
|
else:
|
|
|
|
form_fields = {}
|
|
|
|
form_fields[ 'act' ] = 'do_login'
|
|
form_fields[ 'id' ] = token
|
|
form_fields[ 'pin' ] = pin
|
|
form_fields[ 'long_login' ] = 'yes'
|
|
|
|
( ct, body ) = CC.GenerateMultipartFormDataCTAndBodyFromDict( form_fields )
|
|
|
|
request_headers = {}
|
|
request_headers[ 'Content-Type' ] = ct
|
|
|
|
response = HC.http.Request( HC.POST, 'https://sys.4chan.org/auth', request_headers = request_headers, body = body )
|
|
|
|
self._timeout = HC.GetNow() + 365 * 24 * 3600
|
|
|
|
|
|
HC.app.Write( '4chan_pass', ( token, pin, self._timeout ) )
|
|
|
|
self._SetStatus()
|
|
|
|
|
|
class DialogManageAccountTypes( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent, service_key ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._account_types_panel = ClientGUICommon.StaticBox( self, 'account types' )
|
|
|
|
self._ctrl_account_types = ClientGUICommon.SaneListCtrl( self._account_types_panel, 350, [ ( 'title', 120 ), ( 'permissions', -1 ), ( 'max monthly bytes', 120 ), ( 'max monthly requests', 120 ) ] )
|
|
|
|
self._add = wx.Button( self._account_types_panel, label = 'add' )
|
|
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
|
|
|
|
self._edit = wx.Button( self._account_types_panel, label = 'edit' )
|
|
self._edit.Bind( wx.EVT_BUTTON, self.EventEdit )
|
|
|
|
self._delete = wx.Button( self._account_types_panel, label = 'delete' )
|
|
self._delete.Bind( wx.EVT_BUTTON, self.EventDelete )
|
|
|
|
self._apply = wx.Button( self, id = wx.ID_OK, label = 'apply' )
|
|
self._apply.Bind( wx.EVT_BUTTON, self.EventOK )
|
|
self._apply.SetForegroundColour( ( 0, 128, 0 ) )
|
|
|
|
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
|
|
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
service = HC.app.GetManager( 'services' ).GetService( service_key )
|
|
|
|
response = service.Request( HC.GET, 'account_types' )
|
|
|
|
account_types = response[ 'account_types' ]
|
|
|
|
self._titles_to_account_types = {}
|
|
|
|
for account_type in account_types:
|
|
|
|
title = account_type.GetTitle()
|
|
|
|
self._titles_to_account_types[ title ] = account_type
|
|
|
|
permissions = account_type.GetPermissions()
|
|
|
|
permissions_string = ', '.join( [ HC.permissions_string_lookup[ permission ] for permission in permissions ] )
|
|
|
|
max_num_bytes = account_type.GetMaxBytes()
|
|
max_num_requests = account_type.GetMaxRequests()
|
|
|
|
max_num_bytes_string = account_type.GetMaxBytesString()
|
|
max_num_requests_string = account_type.GetMaxRequestsString()
|
|
|
|
self._ctrl_account_types.Append( ( title, permissions_string, max_num_bytes_string, max_num_requests_string ), ( title, len( permissions ), max_num_bytes, max_num_requests ) )
|
|
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
h_b_box = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
h_b_box.AddF( self._add, FLAGS_MIXED )
|
|
h_b_box.AddF( self._edit, FLAGS_MIXED )
|
|
h_b_box.AddF( self._delete, FLAGS_MIXED )
|
|
|
|
self._account_types_panel.AddF( self._ctrl_account_types, FLAGS_EXPAND_BOTH_WAYS )
|
|
self._account_types_panel.AddF( h_b_box, FLAGS_BUTTON_SIZER )
|
|
|
|
b_box = wx.BoxSizer( wx.HORIZONTAL )
|
|
b_box.AddF( self._apply, FLAGS_MIXED )
|
|
b_box.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
vbox.AddF( self._account_types_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( b_box, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage account types' )
|
|
|
|
self._service_key = service_key
|
|
|
|
self._edit_log = []
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( 980, y ) )
|
|
|
|
wx.CallAfter( self._apply.SetFocus )
|
|
|
|
|
|
def EventAdd( self, event ):
|
|
|
|
with ClientGUIDialogs.DialogInputNewAccountType( self ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
account_type = dlg.GetAccountType()
|
|
|
|
title = account_type.GetTitle()
|
|
|
|
permissions = account_type.GetPermissions()
|
|
|
|
permissions_string = ', '.join( [ HC.permissions_string_lookup[ permission ] for permission in permissions ] )
|
|
|
|
max_num_bytes = account_type.GetMaxBytes()
|
|
max_num_requests = account_type.GetMaxRequests()
|
|
|
|
max_num_bytes_string = account_type.GetMaxBytesString()
|
|
max_num_requests_string = account_type.GetMaxRequestsString()
|
|
|
|
if title in self._titles_to_account_types: raise Exception( 'You already have an account type called ' + title + '; delete or edit that one first' )
|
|
|
|
self._titles_to_account_types[ title ] = account_type
|
|
|
|
self._edit_log.append( ( HC.ADD, account_type ) )
|
|
|
|
self._ctrl_account_types.Append( ( title, permissions_string, max_num_bytes_string, max_num_requests_string ), ( title, len( permissions ), max_num_bytes, max_num_requests ) )
|
|
|
|
|
|
|
|
|
|
def EventDelete( self, event ):
|
|
|
|
indices = self._ctrl_account_types.GetAllSelected()
|
|
|
|
titles_about_to_delete = { self._ctrl_account_types.GetClientData( index )[0] for index in indices }
|
|
|
|
all_titles = set( self._titles_to_account_types.keys() )
|
|
|
|
titles_can_move_to = list( all_titles - titles_about_to_delete )
|
|
|
|
if len( titles_can_move_to ) == 0:
|
|
|
|
wx.MessageBox( 'You cannot delete every account type!' )
|
|
|
|
return
|
|
|
|
|
|
for title in titles_about_to_delete:
|
|
|
|
with ClientGUIDialogs.DialogSelectFromListOfStrings( self, 'what should deleted ' + title + ' accounts become?', titles_can_move_to ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK: title_to_move_to = dlg.GetString()
|
|
else: return
|
|
|
|
|
|
self._edit_log.append( ( HC.DELETE, ( title, title_to_move_to ) ) )
|
|
|
|
|
|
self._ctrl_account_types.RemoveAllSelected()
|
|
|
|
|
|
def EventEdit( self, event ):
|
|
|
|
indices = self._ctrl_account_types.GetAllSelected()
|
|
|
|
for index in indices:
|
|
|
|
title = self._ctrl_account_types.GetClientData( index )[0]
|
|
|
|
account_type = self._titles_to_account_types[ title ]
|
|
|
|
with ClientGUIDialogs.DialogInputNewAccountType( self, account_type ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
old_title = title
|
|
|
|
account_type = dlg.GetAccountType()
|
|
|
|
title = account_type.GetTitle()
|
|
|
|
permissions = account_type.GetPermissions()
|
|
|
|
permissions_string = ', '.join( [ HC.permissions_string_lookup[ permission ] for permission in permissions ] )
|
|
|
|
max_num_bytes = account_type.GetMaxBytes()
|
|
max_num_requests = account_type.GetMaxRequests()
|
|
|
|
max_num_bytes_string = account_type.GetMaxBytesString()
|
|
max_num_requests_string = account_type.GetMaxRequestsString()
|
|
|
|
if old_title != title:
|
|
|
|
if title in self._titles_to_account_types: raise Exception( 'You already have an account type called ' + title + '; delete or edit that one first' )
|
|
|
|
del self._titles_to_account_types[ old_title ]
|
|
|
|
|
|
self._titles_to_account_types[ title ] = account_type
|
|
|
|
self._edit_log.append( ( HC.EDIT, ( old_title, account_type ) ) )
|
|
|
|
self._ctrl_account_types.UpdateRow( index, ( title, permissions_string, max_num_bytes_string, max_num_requests_string ), ( title, len( permissions ), max_num_bytes, max_num_requests ) )
|
|
|
|
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
service = HC.app.GetManager( 'services' ).GetService( self._service_key )
|
|
|
|
service.Request( HC.POST, 'account_types', { 'edit_log' : self._edit_log } )
|
|
|
|
self.EndModal( wx.ID_OK )
|
|
|
|
|
|
class DialogManageBoorus( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._edit_log = []
|
|
|
|
self._boorus = ClientGUICommon.ListBook( self )
|
|
|
|
self._add = wx.Button( self, label = 'add' )
|
|
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
|
|
self._add.SetForegroundColour( ( 0, 128, 0 ) )
|
|
|
|
self._remove = wx.Button( self, label = 'remove' )
|
|
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
|
|
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
|
|
|
|
self._export = wx.Button( self, label = 'export' )
|
|
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
boorus = HC.app.Read( 'remote_boorus' )
|
|
|
|
for ( name, booru ) in boorus.items():
|
|
|
|
page_info = ( self._Panel, ( self._boorus, booru ), {} )
|
|
|
|
self._boorus.AddPage( page_info, name )
|
|
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
add_remove_hbox.AddF( self._add, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._remove, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._export, FLAGS_MIXED )
|
|
|
|
ok_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
ok_hbox.AddF( self._ok, FLAGS_MIXED )
|
|
ok_hbox.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
vbox.AddF( self._boorus, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( add_remove_hbox, FLAGS_SMALL_INDENT )
|
|
vbox.AddF( ok_hbox, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage boorus' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
self.SetDropTarget( ClientGUICommon.FileDropTarget( self.Import ) )
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( 980, y ) )
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def EventAdd( self, event ):
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, 'Enter new booru\'s name.' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
try:
|
|
|
|
name = dlg.GetValue()
|
|
|
|
if self._boorus.NameExists( name ): raise Exception( 'That name is already in use!' )
|
|
|
|
if name == '': raise Exception( 'Please enter a nickname for the service.' )
|
|
|
|
booru = CC.Booru( name, 'search_url', '+', 1, 'thumbnail', '', 'original image', {} )
|
|
|
|
self._edit_log.append( ( HC.SET, ( name, booru ) ) )
|
|
|
|
page = self._Panel( self._boorus, booru )
|
|
|
|
self._boorus.AddPage( page, name, select = True )
|
|
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
self.EventAdd( event )
|
|
|
|
|
|
|
|
|
|
|
|
def EventExport( self, event ):
|
|
|
|
booru_panel = self._boorus.GetCurrentPage()
|
|
|
|
if booru_panel is not None:
|
|
|
|
name = self._boorus.GetCurrentName()
|
|
|
|
booru = booru_panel.GetBooru()
|
|
|
|
with wx.FileDialog( self, 'select where to export booru', defaultFile = 'booru.yaml', style = wx.FD_SAVE ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
with open( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( booru ) )
|
|
|
|
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
for ( name, page ) in self._boorus.GetNameToPageDict().items():
|
|
|
|
if page.HasChanges(): self._edit_log.append( ( HC.SET, ( name, page.GetBooru() ) ) )
|
|
|
|
|
|
try:
|
|
|
|
for ( action, data ) in self._edit_log:
|
|
|
|
if action == HC.SET:
|
|
|
|
( name, booru ) = data
|
|
|
|
HC.app.Write( 'remote_booru', name, booru )
|
|
|
|
elif action == HC.DELETE:
|
|
|
|
name = data
|
|
|
|
HC.app.Write( 'delete_remote_booru', name )
|
|
|
|
|
|
|
|
finally: self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventRemove( self, event ):
|
|
|
|
booru_panel = self._boorus.GetCurrentPage()
|
|
|
|
if booru_panel is not None:
|
|
|
|
name = self._boorus.GetCurrentName()
|
|
|
|
self._edit_log.append( ( HC.DELETE, name ) )
|
|
|
|
self._boorus.DeleteCurrentPage()
|
|
|
|
|
|
|
|
def Import( self, paths ):
|
|
|
|
for path in paths:
|
|
|
|
try:
|
|
|
|
with open( path, 'rb' ) as f: file = f.read()
|
|
|
|
thing = yaml.safe_load( file )
|
|
|
|
if type( thing ) == CC.Booru:
|
|
|
|
booru = thing
|
|
|
|
name = booru.GetName()
|
|
|
|
if not self._boorus.NameExists( name ):
|
|
|
|
new_booru = CC.Booru( name, 'search_url', '+', 1, 'thumbnail', '', 'original image', {} )
|
|
|
|
self._edit_log.append( ( HC.SET, ( name, new_booru ) ) )
|
|
|
|
page = self._Panel( self._boorus, new_booru )
|
|
|
|
self._boorus.AddPage( page, name, select = True )
|
|
|
|
|
|
page = self._boorus.GetNameToPageDict()[ name ]
|
|
|
|
page.Update( booru )
|
|
|
|
|
|
except:
|
|
|
|
wx.MessageBox( traceback.format_exc() )
|
|
|
|
|
|
|
|
|
|
class _Panel( wx.Panel ):
|
|
|
|
def __init__( self, parent, booru ):
|
|
|
|
wx.Panel.__init__( self, parent )
|
|
|
|
self._booru = booru
|
|
|
|
( search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces ) = booru.GetData()
|
|
|
|
def InitialiseControls():
|
|
|
|
self._booru_panel = ClientGUICommon.StaticBox( self, 'booru' )
|
|
|
|
#
|
|
|
|
self._search_panel = ClientGUICommon.StaticBox( self._booru_panel, 'search' )
|
|
|
|
self._search_url = wx.TextCtrl( self._search_panel )
|
|
self._search_url.Bind( wx.EVT_TEXT, self.EventHTML )
|
|
|
|
self._search_separator = wx.Choice( self._search_panel, choices = [ '+', '&', '%20' ] )
|
|
self._search_separator.Bind( wx.EVT_CHOICE, self.EventHTML )
|
|
|
|
self._advance_by_page_num = wx.CheckBox( self._search_panel )
|
|
|
|
self._thumb_classname = wx.TextCtrl( self._search_panel )
|
|
self._thumb_classname.Bind( wx.EVT_TEXT, self.EventHTML )
|
|
|
|
self._example_html_search = wx.StaticText( self._search_panel, style = wx.ST_NO_AUTORESIZE )
|
|
|
|
#
|
|
|
|
self._image_panel = ClientGUICommon.StaticBox( self._booru_panel, 'image' )
|
|
|
|
self._image_info = wx.TextCtrl( self._image_panel )
|
|
self._image_info.Bind( wx.EVT_TEXT, self.EventHTML )
|
|
|
|
self._image_id = wx.RadioButton( self._image_panel, style = wx.RB_GROUP )
|
|
self._image_id.Bind( wx.EVT_RADIOBUTTON, self.EventHTML )
|
|
|
|
self._image_data = wx.RadioButton( self._image_panel )
|
|
self._image_data.Bind( wx.EVT_RADIOBUTTON, self.EventHTML )
|
|
|
|
self._example_html_image = wx.StaticText( self._image_panel, style = wx.ST_NO_AUTORESIZE )
|
|
|
|
#
|
|
|
|
self._tag_panel = ClientGUICommon.StaticBox( self._booru_panel, 'tags' )
|
|
|
|
self._tag_classnames_to_namespaces = wx.ListBox( self._tag_panel, style = wx.LB_SORT )
|
|
self._tag_classnames_to_namespaces.Bind( wx.EVT_LEFT_DCLICK, self.EventRemove )
|
|
|
|
self._tag_classname = wx.TextCtrl( self._tag_panel )
|
|
self._namespace = wx.TextCtrl( self._tag_panel )
|
|
|
|
self._add = wx.Button( self._tag_panel, label = 'add' )
|
|
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
|
|
|
|
self._example_html_tags = wx.StaticText( self._tag_panel, style = wx.ST_NO_AUTORESIZE )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
self._search_url.SetValue( search_url )
|
|
|
|
self._search_separator.Select( self._search_separator.FindString( search_separator ) )
|
|
|
|
self._advance_by_page_num.SetValue( advance_by_page_num )
|
|
|
|
self._thumb_classname.SetValue( thumb_classname )
|
|
|
|
#
|
|
|
|
if image_id is None:
|
|
|
|
self._image_info.SetValue( image_data )
|
|
self._image_data.SetValue( True )
|
|
|
|
else:
|
|
|
|
self._image_info.SetValue( image_id )
|
|
self._image_id.SetValue( True )
|
|
|
|
|
|
#
|
|
|
|
for ( tag_classname, namespace ) in tag_classnames_to_namespaces.items(): self._tag_classnames_to_namespaces.Append( tag_classname + ' : ' + namespace, ( tag_classname, namespace ) )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
gridbox.AddF( wx.StaticText( self._search_panel, label = 'search url' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._search_url, FLAGS_EXPAND_BOTH_WAYS )
|
|
gridbox.AddF( wx.StaticText( self._search_panel, label = 'search tag separator' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._search_separator, FLAGS_EXPAND_BOTH_WAYS )
|
|
gridbox.AddF( wx.StaticText( self._search_panel, label = 'advance by page num' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._advance_by_page_num, FLAGS_EXPAND_BOTH_WAYS )
|
|
gridbox.AddF( wx.StaticText( self._search_panel, label = 'thumbnail classname' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._thumb_classname, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
self._search_panel.AddF( gridbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
self._search_panel.AddF( self._example_html_search, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
#
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
gridbox.AddF( wx.StaticText( self._image_panel, label = 'text' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._image_info, FLAGS_EXPAND_BOTH_WAYS )
|
|
gridbox.AddF( wx.StaticText( self._image_panel, label = 'id of <img>' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._image_id, FLAGS_EXPAND_BOTH_WAYS )
|
|
gridbox.AddF( wx.StaticText( self._image_panel, label = 'text of <a>' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._image_data, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
self._image_panel.AddF( gridbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
self._image_panel.AddF( self._example_html_image, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
#
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( self._tag_classname, FLAGS_MIXED )
|
|
hbox.AddF( self._namespace, FLAGS_MIXED )
|
|
hbox.AddF( self._add, FLAGS_MIXED )
|
|
|
|
self._tag_panel.AddF( self._tag_classnames_to_namespaces, FLAGS_EXPAND_BOTH_WAYS )
|
|
self._tag_panel.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
self._tag_panel.AddF( self._example_html_tags, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
#
|
|
|
|
self._booru_panel.AddF( self._search_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
self._booru_panel.AddF( self._image_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
self._booru_panel.AddF( self._tag_panel, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._booru_panel, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
|
|
def _GetInfo( self ):
|
|
|
|
booru_name = self._booru.GetName()
|
|
|
|
search_url = self._search_url.GetValue()
|
|
|
|
search_separator = self._search_separator.GetStringSelection()
|
|
|
|
advance_by_page_num = self._advance_by_page_num.GetValue()
|
|
|
|
thumb_classname = self._thumb_classname.GetValue()
|
|
|
|
if self._image_id.GetValue():
|
|
|
|
image_id = self._image_info.GetValue()
|
|
image_data = None
|
|
|
|
else:
|
|
|
|
image_id = None
|
|
image_data = self._image_info.GetValue()
|
|
|
|
|
|
tag_classnames_to_namespaces = { tag_classname : namespace for ( tag_classname, namespace ) in [ self._tag_classnames_to_namespaces.GetClientData( i ) for i in range( self._tag_classnames_to_namespaces.GetCount() ) ] }
|
|
|
|
return ( booru_name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces )
|
|
|
|
|
|
def EventAdd( self, event ):
|
|
|
|
tag_classname = self._tag_classname.GetValue()
|
|
namespace = self._namespace.GetValue()
|
|
|
|
if tag_classname != '':
|
|
|
|
self._tag_classnames_to_namespaces.Append( tag_classname + ' : ' + namespace, ( tag_classname, namespace ) )
|
|
|
|
self._tag_classname.SetValue( '' )
|
|
self._namespace.SetValue( '' )
|
|
|
|
self.EventHTML( event )
|
|
|
|
|
|
|
|
def EventHTML( self, event ):
|
|
|
|
pass
|
|
|
|
|
|
def EventRemove( self, event ):
|
|
|
|
selection = self._tag_classnames_to_namespaces.GetSelection()
|
|
|
|
if selection != wx.NOT_FOUND:
|
|
|
|
self._tag_classnames_to_namespaces.Delete( selection )
|
|
|
|
self.EventHTML( event )
|
|
|
|
|
|
|
|
def GetBooru( self ):
|
|
|
|
( booru_name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces ) = self._GetInfo()
|
|
|
|
return CC.Booru( booru_name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces )
|
|
|
|
|
|
def HasChanges( self ):
|
|
|
|
( booru_name, my_search_url, my_search_separator, my_advance_by_page_num, my_thumb_classname, my_image_id, my_image_data, my_tag_classnames_to_namespaces ) = self._GetInfo()
|
|
|
|
( search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces ) = self._booru.GetData()
|
|
|
|
if search_url != my_search_url: return True
|
|
|
|
if search_separator != my_search_separator: return True
|
|
|
|
if advance_by_page_num != my_advance_by_page_num: return True
|
|
|
|
if thumb_classname != my_thumb_classname: return True
|
|
|
|
if image_id != my_image_id: return True
|
|
|
|
if image_data != my_image_data: return True
|
|
|
|
if tag_classnames_to_namespaces != my_tag_classnames_to_namespaces: return True
|
|
|
|
return False
|
|
|
|
|
|
def Update( self, booru ):
|
|
|
|
( search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces ) = booru.GetData()
|
|
|
|
self._search_url.SetValue( search_url )
|
|
|
|
self._search_separator.Select( self._search_separator.FindString( search_separator ) )
|
|
|
|
self._advance_by_page_num.SetValue( advance_by_page_num )
|
|
|
|
self._thumb_classname.SetValue( thumb_classname )
|
|
|
|
if image_id is None:
|
|
|
|
self._image_info.SetValue( image_data )
|
|
self._image_data.SetValue( True )
|
|
|
|
else:
|
|
|
|
self._image_info.SetValue( image_id )
|
|
self._image_id.SetValue( True )
|
|
|
|
|
|
self._tag_classnames_to_namespaces.Clear()
|
|
|
|
for ( tag_classname, namespace ) in tag_classnames_to_namespaces.items(): self._tag_classnames_to_namespaces.Append( tag_classname + ' : ' + namespace, ( tag_classname, namespace ) )
|
|
|
|
|
|
|
|
class DialogManageContacts( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._contacts = ClientGUICommon.ListBook( self )
|
|
|
|
self._contacts.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventContactChanging )
|
|
self._contacts.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventContactChanged )
|
|
|
|
self._add_contact_address = wx.Button( self, label = 'add by contact address' )
|
|
self._add_contact_address.Bind( wx.EVT_BUTTON, self.EventAddByContactAddress )
|
|
self._add_contact_address.SetForegroundColour( ( 0, 128, 0 ) )
|
|
|
|
self._add_manually = wx.Button( self, label = 'add manually' )
|
|
self._add_manually.Bind( wx.EVT_BUTTON, self.EventAddManually )
|
|
self._add_manually.SetForegroundColour( ( 0, 128, 0 ) )
|
|
|
|
self._remove = wx.Button( self, label = 'remove' )
|
|
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
|
|
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
|
|
|
|
self._export = wx.Button( self, label = 'export' )
|
|
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
self._edit_log = []
|
|
|
|
( identities, contacts, deletable_names ) = HC.app.Read( 'identities_and_contacts' )
|
|
|
|
self._deletable_names = deletable_names
|
|
|
|
for identity in identities:
|
|
|
|
name = identity.GetName()
|
|
|
|
page_info = ( self._Panel, ( self._contacts, identity ), { 'is_identity' : True } )
|
|
|
|
self._contacts.AddPage( page_info, ' identity - ' + name )
|
|
|
|
|
|
for contact in contacts:
|
|
|
|
name = contact.GetName()
|
|
|
|
page_info = ( self._Panel, ( self._contacts, contact ), { 'is_identity' : False } )
|
|
|
|
self._contacts.AddPage( page_info, name )
|
|
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
add_remove_hbox.AddF( self._add_manually, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._add_contact_address, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._remove, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._export, FLAGS_MIXED )
|
|
|
|
ok_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
ok_hbox.AddF( self._ok, FLAGS_MIXED )
|
|
ok_hbox.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
vbox.AddF( self._contacts, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( add_remove_hbox, FLAGS_SMALL_INDENT )
|
|
vbox.AddF( ok_hbox, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage contacts' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( 980, y ) )
|
|
|
|
self.SetDropTarget( ClientGUICommon.FileDropTarget( self.Import ) )
|
|
|
|
self.EventContactChanged( None )
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def _CheckCurrentContactIsValid( self ):
|
|
|
|
contact_panel = self._contacts.GetCurrentPage()
|
|
|
|
if contact_panel is not None:
|
|
|
|
contact = contact_panel.GetContact()
|
|
|
|
old_name = self._contacts.GetCurrentName()
|
|
name = contact.GetName()
|
|
|
|
if name != old_name and ' identity - ' + name != old_name:
|
|
|
|
if self._contacts.NameExists( name ) or self._contacts.NameExists( ' identity - ' + name ) or name == 'Anonymous': raise Exception( 'That name is already in use!' )
|
|
|
|
if old_name.startswith( ' identity - ' ): self._contacts.RenamePage( old_name, ' identity - ' + name )
|
|
else: self._contacts.RenamePage( old_name, name )
|
|
|
|
|
|
|
|
|
|
def EventAddByContactAddress( self, event ):
|
|
|
|
try: self._CheckCurrentContactIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
return
|
|
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, 'Enter contact\'s address in the form contact_key@hostname:port.' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
contact_address = dlg.GetValue()
|
|
|
|
try:
|
|
|
|
( contact_key_encoded, address ) = contact_address.split( '@' )
|
|
|
|
contact_key = contact_key_encoded.decode( 'hex' )
|
|
|
|
( host, port ) = address.split( ':' )
|
|
|
|
port = int( port )
|
|
|
|
except: raise Exception( 'Could not parse the address!' )
|
|
|
|
name = contact_key_encoded
|
|
|
|
contact = ClientConstantsMessages.Contact( None, name, host, port )
|
|
|
|
try:
|
|
|
|
connection = contact.GetConnection()
|
|
|
|
public_key = connection.Get( 'public_key', contact_key = contact_key.encode( 'hex' ) )
|
|
|
|
except: raise Exception( 'Could not fetch the contact\'s public key from the address:' + os.linesep + traceback.format_exc() )
|
|
|
|
contact = ClientConstantsMessages.Contact( public_key, name, host, port )
|
|
|
|
self._edit_log.append( ( HC.ADD, contact ) )
|
|
|
|
page = self._Panel( self._contacts, contact, is_identity = False )
|
|
|
|
self._deletable_names.add( name )
|
|
|
|
self._contacts.AddPage( page, name, select = True )
|
|
|
|
|
|
|
|
|
|
def EventAddManually( self, event ):
|
|
|
|
try: self._CheckCurrentContactIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
return
|
|
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, 'Enter new contact\'s name.' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
name = dlg.GetValue()
|
|
|
|
if self._contacts.NameExists( name ) or self._contacts.NameExists( ' identity - ' + name ) or name == 'Anonymous': raise Exception( 'That name is already in use!' )
|
|
|
|
if name == '': raise Exception( 'Please enter a nickname for the service.' )
|
|
|
|
public_key = None
|
|
host = 'hostname'
|
|
port = 45871
|
|
|
|
contact = ClientConstantsMessages.Contact( public_key, name, host, port )
|
|
|
|
self._edit_log.append( ( HC.ADD, contact ) )
|
|
|
|
page = self._Panel( self._contacts, contact, is_identity = False )
|
|
|
|
self._deletable_names.add( name )
|
|
|
|
self._contacts.AddPage( page, name, select = True )
|
|
|
|
|
|
|
|
|
|
def EventContactChanged( self, event ):
|
|
|
|
contact_panel = self._contacts.GetCurrentPage()
|
|
|
|
if contact_panel is not None:
|
|
|
|
old_name = contact_panel.GetOriginalName()
|
|
|
|
if old_name in self._deletable_names: self._remove.Enable()
|
|
else: self._remove.Disable()
|
|
|
|
|
|
|
|
def EventContactChanging( self, event ):
|
|
|
|
try: self._CheckCurrentContactIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
event.Veto()
|
|
|
|
|
|
|
|
def EventExport( self, event ):
|
|
|
|
contact_panel = self._contacts.GetCurrentPage()
|
|
|
|
if contact_panel is not None:
|
|
|
|
name = self._contacts.GetCurrentName()
|
|
|
|
contact = contact_panel.GetContact()
|
|
|
|
try:
|
|
|
|
with wx.FileDialog( self, 'select where to export contact', defaultFile = name + '.yaml', style = wx.FD_SAVE ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
with open( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( contact ) )
|
|
|
|
|
|
|
|
except:
|
|
|
|
with wx.FileDialog( self, 'select where to export contact', defaultFile = 'contact.yaml', style = wx.FD_SAVE ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
with open( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( contact ) )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
try: self._CheckCurrentContactIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
return
|
|
|
|
|
|
for ( name, page ) in self._contacts.GetNameToPageDict().items():
|
|
|
|
if page.HasChanges(): self._edit_log.append( ( HC.EDIT, ( page.GetOriginalName(), page.GetContact() ) ) )
|
|
|
|
|
|
try:
|
|
|
|
if len( self._edit_log ) > 0: HC.app.Write( 'update_contacts', self._edit_log )
|
|
|
|
finally: self.EndModal( wx.ID_OK )
|
|
|
|
|
|
# this isn't used yet!
|
|
def EventRemove( self, event ):
|
|
|
|
contact_panel = self._contacts.GetCurrentPage()
|
|
|
|
if contact_panel is not None:
|
|
|
|
name = contact_panel.GetOriginalName()
|
|
|
|
self._edit_log.append( ( HC.DELETE, name ) )
|
|
|
|
self._contacts.DeleteCurrentPage()
|
|
|
|
self._deletable_names.discard( name )
|
|
|
|
|
|
|
|
def Import( self, paths ):
|
|
|
|
try: self._CheckCurrentContactIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
return
|
|
|
|
|
|
for path in paths:
|
|
|
|
try:
|
|
|
|
with open( path, 'rb' ) as f: file = f.read()
|
|
|
|
obj = yaml.safe_load( file )
|
|
|
|
if type( obj ) == ClientConstantsMessages.Contact:
|
|
|
|
contact = obj
|
|
|
|
name = contact.GetName()
|
|
|
|
if self._contacts.NameExists( name ) or self._contacts.NameExists( ' identities - ' + name ) or name == 'Anonymous':
|
|
|
|
message = 'There already exists a contact or identity with the name ' + name + '. Do you want to overwrite, or make a new contact?'
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Please choose what to do.', yes_label = 'overwrite', no_label = 'make new' ) as dlg:
|
|
|
|
if True:
|
|
|
|
name_to_page_dict = self._contacts.GetNameToPageDict()
|
|
|
|
if name in name_to_page_dict: page = name_to_page_dict[ name ]
|
|
else: page = name_to_page_dict[ ' identities - ' + name ]
|
|
|
|
page.Update( contact )
|
|
|
|
else:
|
|
|
|
while self._contacts.NameExists( name ) or self._contacts.NameExists( ' identities - ' + name ) or name == 'Anonymous': name = name + HC.u( random.randint( 0, 9 ) )
|
|
|
|
( public_key, old_name, host, port ) = contact.GetInfo()
|
|
|
|
new_contact = ClientConstantsMessages.Contact( public_key, name, host, port )
|
|
|
|
self._edit_log.append( ( HC.ADD, contact ) )
|
|
|
|
self._deletable_names.add( name )
|
|
|
|
page = self._Panel( self._contacts, contact, False )
|
|
|
|
self._contacts.AddPage( page, name, select = True )
|
|
|
|
|
|
|
|
else:
|
|
|
|
( public_key, old_name, host, port ) = contact.GetInfo()
|
|
|
|
new_contact = ClientConstantsMessages.Contact( public_key, name, host, port )
|
|
|
|
self._edit_log.append( ( HC.ADD, contact ) )
|
|
|
|
self._deletable_names.add( name )
|
|
|
|
page = self._Panel( self._contacts, contact, False )
|
|
|
|
self._contacts.AddPage( page, name, select = True )
|
|
|
|
|
|
|
|
except:
|
|
|
|
wx.MessageBox( traceback.format_exc() )
|
|
|
|
|
|
|
|
|
|
class _Panel( wx.Panel ):
|
|
|
|
def __init__( self, parent, contact, is_identity ):
|
|
|
|
wx.Panel.__init__( self, parent )
|
|
|
|
self._contact = contact
|
|
self._is_identity = is_identity
|
|
|
|
( public_key, name, host, port ) = contact.GetInfo()
|
|
|
|
contact_key = contact.GetContactKey()
|
|
|
|
def InitialiseControls():
|
|
|
|
self._contact_panel = ClientGUICommon.StaticBox( self, 'contact' )
|
|
|
|
self._name = wx.TextCtrl( self._contact_panel )
|
|
|
|
self._contact_address = wx.TextCtrl( self._contact_panel )
|
|
|
|
self._public_key = wx.TextCtrl( self._contact_panel, style = wx.TE_MULTILINE )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
self._name.SetValue( name )
|
|
|
|
contact_address = host + ':' + HC.u( port )
|
|
|
|
if contact_key is not None: contact_address = contact_key.encode( 'hex' ) + '@' + contact_address
|
|
|
|
self._contact_address.SetValue( contact_address )
|
|
|
|
if public_key is not None: self._public_key.SetValue( public_key )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
gridbox.AddF( wx.StaticText( self._contact_panel, label = 'name' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._name, FLAGS_EXPAND_BOTH_WAYS )
|
|
gridbox.AddF( wx.StaticText( self._contact_panel, label = 'contact address' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._contact_address, FLAGS_EXPAND_BOTH_WAYS )
|
|
gridbox.AddF( wx.StaticText( self._contact_panel, label = 'public key' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._public_key, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
self._contact_panel.AddF( gridbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._contact_panel, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
|
|
def _GetInfo( self ):
|
|
|
|
public_key = self._public_key.GetValue()
|
|
|
|
if public_key == '': public_key = None
|
|
|
|
name = self._name.GetValue()
|
|
|
|
contact_address = self._contact_address.GetValue()
|
|
|
|
try:
|
|
|
|
if '@' in contact_address: ( contact_key, address ) = contact_address.split( '@' )
|
|
else: address = contact_address
|
|
|
|
( host, port ) = address.split( ':' )
|
|
|
|
try: port = int( port )
|
|
except:
|
|
|
|
port = 45871
|
|
|
|
wx.MessageBox( 'Could not parse the port!' )
|
|
|
|
|
|
except:
|
|
|
|
host = 'hostname'
|
|
port = 45871
|
|
|
|
wx.MessageBox( 'Could not parse the contact\'s address!' )
|
|
|
|
|
|
return [ public_key, name, host, port ]
|
|
|
|
|
|
def GetContact( self ):
|
|
|
|
[ public_key, name, host, port ] = self._GetInfo()
|
|
|
|
return ClientConstantsMessages.Contact( public_key, name, host, port )
|
|
|
|
|
|
def GetOriginalName( self ): return self._contact.GetName()
|
|
|
|
def HasChanges( self ):
|
|
|
|
[ my_public_key, my_name, my_host, my_port ] = self._GetInfo()
|
|
|
|
[ public_key, name, host, port ] = self._contact.GetInfo()
|
|
|
|
if my_public_key != public_key: return True
|
|
|
|
if my_name != name: return True
|
|
|
|
if my_host != host: return True
|
|
|
|
if my_port != port: return True
|
|
|
|
return False
|
|
|
|
|
|
def Update( self, contact ):
|
|
|
|
( public_key, name, host, port ) = contact.GetInfo()
|
|
|
|
contact_key = contact.GetContactKey()
|
|
|
|
self._name.SetValue( name )
|
|
|
|
contact_address = host + ':' + HC.u( port )
|
|
|
|
if contact_key is not None: contact_address = contact_key.encode( 'hex' ) + '@' + contact_address
|
|
|
|
self._contact_address.SetValue( contact_address )
|
|
|
|
if public_key is None: public_key = ''
|
|
|
|
self._public_key.SetValue( public_key )
|
|
|
|
|
|
|
|
class DialogManageExportFolders( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._export_folders = ClientGUICommon.SaneListCtrl( self, 480, [ ( 'path', -1 ), ( 'query', 120 ), ( 'period', 120 ), ( 'phrase', 120 ) ] )
|
|
|
|
self._export_folders.SetMinSize( ( 780, 360 ) )
|
|
|
|
self._add_button = wx.Button( self, label = 'add' )
|
|
self._add_button.Bind( wx.EVT_BUTTON, self.EventAdd )
|
|
|
|
self._edit_button = wx.Button( self, label = 'edit' )
|
|
self._edit_button.Bind( wx.EVT_BUTTON, self.EventEdit )
|
|
|
|
self._delete_button = wx.Button( self, label = 'delete' )
|
|
self._delete_button.Bind( wx.EVT_BUTTON, self.EventDelete )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
self._original_paths_to_details = HC.app.Read( 'export_folders' )
|
|
|
|
for ( path, details ) in self._original_paths_to_details.items():
|
|
|
|
predicates = details[ 'predicates' ]
|
|
period = details[ 'period' ]
|
|
phrase = details[ 'phrase' ]
|
|
|
|
( pretty_predicates, pretty_period, pretty_phrase ) = self._GetPrettyVariables( predicates, period, phrase )
|
|
|
|
self._export_folders.Append( ( path, pretty_predicates, pretty_period, pretty_phrase ), ( path, predicates, period, phrase ) )
|
|
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
file_buttons = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
file_buttons.AddF( self._add_button, FLAGS_MIXED )
|
|
file_buttons.AddF( self._edit_button, FLAGS_MIXED )
|
|
file_buttons.AddF( self._delete_button, FLAGS_MIXED )
|
|
|
|
buttons = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
buttons.AddF( self._ok, FLAGS_MIXED )
|
|
buttons.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
intro = 'Here you can set the client to regularly export a certain query to a particular location.'
|
|
|
|
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._export_folders, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( file_buttons, FLAGS_BUTTON_SIZER )
|
|
vbox.AddF( buttons, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage export folders' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( x, y ) )
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def _AddFolder( self, path ):
|
|
|
|
all_existing_client_data = self._export_folders.GetClientData()
|
|
|
|
if path not in ( existing_path for ( existing_path, predicates, period, phrase ) in all_existing_client_data ):
|
|
|
|
predicates = []
|
|
period = 15 * 60
|
|
phrase = '{hash}'
|
|
|
|
with DialogManageExportFoldersEdit( self, path, predicates, period, phrase ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
( path, predicates, period, phrase ) = dlg.GetInfo()
|
|
|
|
( pretty_predicates, pretty_period, pretty_phrase ) = self._GetPrettyVariables( predicates, period, phrase )
|
|
|
|
self._export_folders.Append( ( path, pretty_predicates, pretty_period, pretty_phrase ), ( path, predicates, period, phrase ) )
|
|
|
|
|
|
|
|
|
|
|
|
def _GetPrettyVariables( self, predicates, period, phrase ):
|
|
|
|
pretty_predicates = ', '.join( predicate.GetUnicode( with_count = False ) for predicate in predicates )
|
|
|
|
pretty_period = HC.u( period / 60 ) + ' minutes'
|
|
|
|
pretty_phrase = phrase
|
|
|
|
return ( pretty_predicates, pretty_period, pretty_phrase )
|
|
|
|
|
|
def EventAdd( self, event ):
|
|
|
|
with wx.DirDialog( self, 'Select a folder to add.' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
path = dlg.GetPath()
|
|
|
|
self._AddFolder( path )
|
|
|
|
|
|
|
|
|
|
def EventDelete( self, event ): self._export_folders.RemoveAllSelected()
|
|
|
|
def EventEdit( self, event ):
|
|
|
|
indices = self._export_folders.GetAllSelected()
|
|
|
|
for index in indices:
|
|
|
|
( path, predicates, period, phrase ) = self._export_folders.GetClientData( index )
|
|
|
|
with DialogManageExportFoldersEdit( self, path, predicates, period, phrase ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
( path, predicates, period, phrase ) = dlg.GetInfo()
|
|
|
|
( pretty_predicates, pretty_period, pretty_phrase ) = self._GetPrettyVariables( predicates, period, phrase )
|
|
|
|
self._export_folders.UpdateRow( index, ( path, pretty_predicates, pretty_period, pretty_phrase ), ( path, predicates, period, phrase ) )
|
|
|
|
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
client_data = self._export_folders.GetClientData()
|
|
|
|
export_folders = []
|
|
|
|
paths = set()
|
|
|
|
for ( path, predicates, period, phrase ) in client_data:
|
|
|
|
if path in self._original_paths_to_details: details = self._original_paths_to_details[ path ]
|
|
else: details = { 'last_checked' : 0 }
|
|
|
|
details[ 'predicates' ] = predicates
|
|
details[ 'period' ] = period
|
|
details[ 'phrase' ] = phrase
|
|
|
|
HC.app.Write( 'export_folder', path, details )
|
|
|
|
paths.add( path )
|
|
|
|
|
|
deletees = set( self._original_paths_to_details.keys() ) - paths
|
|
|
|
for deletee in deletees: HC.app.Write( 'delete_export_folder', deletee )
|
|
|
|
HC.pubsub.pub( 'notify_new_export_folders' )
|
|
|
|
self.EndModal( wx.ID_OK )
|
|
|
|
|
|
class DialogManageExportFoldersEdit( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent, path, predicates, period, phrase ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._path_box = ClientGUICommon.StaticBox( self, 'export path' )
|
|
|
|
self._path = wx.DirPickerCtrl( self._path_box, style = wx.DIRP_USE_TEXTCTRL )
|
|
|
|
#
|
|
|
|
self._query_box = ClientGUICommon.StaticBox( self, 'query to export' )
|
|
|
|
self._page_key = os.urandom( 32 )
|
|
|
|
self._predicates_box = ClientGUICommon.TagsBoxPredicates( self._query_box, self._page_key, predicates )
|
|
|
|
self._searchbox = ClientGUICommon.AutoCompleteDropdownTagsRead( self._query_box, self._page_key, HC.LOCAL_FILE_SERVICE_KEY, HC.COMBINED_TAG_SERVICE_KEY )
|
|
|
|
#
|
|
|
|
self._period_box = ClientGUICommon.StaticBox( self, 'export period (minutes)' )
|
|
|
|
self._period = wx.SpinCtrl( self._period_box )
|
|
|
|
#
|
|
|
|
self._phrase_box = ClientGUICommon.StaticBox( self, 'filenames' )
|
|
|
|
self._pattern = wx.TextCtrl( self._phrase_box )
|
|
|
|
self._examples = ClientGUICommon.ExportPatternButton( self._phrase_box )
|
|
|
|
#
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
self._path.SetPath( path )
|
|
|
|
self._period.SetRange( 3, 180 )
|
|
|
|
self._period.SetValue( period / 60 )
|
|
|
|
self._pattern.SetValue( phrase )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
self._path_box.AddF( self._path, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self._query_box.AddF( self._predicates_box, FLAGS_EXPAND_BOTH_WAYS )
|
|
self._query_box.AddF( self._searchbox, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self._period_box.AddF( self._period, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
phrase_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
phrase_hbox.AddF( self._pattern, FLAGS_EXPAND_BOTH_WAYS )
|
|
phrase_hbox.AddF( self._examples, FLAGS_MIXED )
|
|
|
|
self._phrase_box.AddF( phrase_hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
buttons = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
buttons.AddF( self._ok, FLAGS_MIXED )
|
|
buttons.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._path_box, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._query_box, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( self._period_box, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._phrase_box, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( buttons, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( 480, y ) )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'edit export folder' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
HC.pubsub.sub( self, 'AddPredicate', 'add_predicate' )
|
|
HC.pubsub.sub( self, 'RemovePredicate', 'remove_predicate' )
|
|
|
|
|
|
def AddPredicate( self, page_key, predicate ):
|
|
|
|
if page_key == self._page_key:
|
|
|
|
if self._predicates_box.HasPredicate( predicate ): self._predicates_box.RemovePredicate( predicate )
|
|
else: self._predicates_box.AddPredicate( predicate )
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
phrase = self._pattern.GetValue()
|
|
|
|
try: CC.ParseExportPhrase( phrase )
|
|
except:
|
|
|
|
wx.MessageBox( 'Could not parse that export phrase!' )
|
|
|
|
return
|
|
|
|
|
|
self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def GetInfo( self ):
|
|
|
|
path = self._path.GetPath()
|
|
|
|
predicates = self._predicates_box.GetPredicates()
|
|
|
|
period = self._period.GetValue() * 60
|
|
|
|
phrase = self._pattern.GetValue()
|
|
|
|
return ( path, predicates, period, phrase )
|
|
|
|
|
|
def RemovePredicate( self, page_key, predicate ):
|
|
|
|
if page_key == self._page_key:
|
|
|
|
if self._predicates_box.HasPredicate( predicate ):
|
|
|
|
self._predicates_box.RemovePredicate( predicate )
|
|
|
|
|
|
|
|
|
|
class DialogManageImageboards( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._sites = ClientGUICommon.ListBook( self )
|
|
|
|
self._add = wx.Button( self, label = 'add' )
|
|
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
|
|
self._add.SetForegroundColour( ( 0, 128, 0 ) )
|
|
|
|
self._remove = wx.Button( self, label = 'remove' )
|
|
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
|
|
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
|
|
|
|
self._export = wx.Button( self, label = 'export' )
|
|
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
self._edit_log = []
|
|
|
|
sites = HC.app.Read( 'imageboards' )
|
|
|
|
for ( name, imageboards ) in sites.items():
|
|
|
|
page_info = ( self._Panel, ( self._sites, imageboards ), {} )
|
|
|
|
self._sites.AddPage( page_info, name )
|
|
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
add_remove_hbox.AddF( self._add, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._remove, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._export, FLAGS_MIXED )
|
|
|
|
ok_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
ok_hbox.AddF( self._ok, FLAGS_MIXED )
|
|
ok_hbox.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
vbox.AddF( self._sites, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( add_remove_hbox, FLAGS_SMALL_INDENT )
|
|
vbox.AddF( ok_hbox, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage imageboards' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( 980, y ) )
|
|
|
|
self.SetDropTarget( ClientGUICommon.FileDropTarget( self.Import ) )
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def EventAdd( self, event ):
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, 'Enter new site\'s name.' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
try:
|
|
|
|
name = dlg.GetValue()
|
|
|
|
if self._sites.NameExists( name ): raise Exception( 'That name is already in use!' )
|
|
|
|
if name == '': raise Exception( 'Please enter a nickname for the service.' )
|
|
|
|
self._edit_log.append( ( HC.SET, ( name, [] ) ) )
|
|
|
|
page = self._Panel( self._sites, [] )
|
|
|
|
self._sites.AddPage( page, name, select = True )
|
|
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
self.EventAdd( event )
|
|
|
|
|
|
|
|
|
|
|
|
def EventExport( self, event ):
|
|
|
|
site_panel = self._sites.GetCurrentPage()
|
|
|
|
if site_panel is not None:
|
|
|
|
name = self._sites.GetCurrentName()
|
|
|
|
imageboards = site_panel.GetImageboards()
|
|
|
|
dict = { name : imageboards }
|
|
|
|
with wx.FileDialog( self, 'select where to export site', defaultFile = 'site.yaml', style = wx.FD_SAVE ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
with open( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( dict ) )
|
|
|
|
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
for ( name, page ) in self._sites.GetNameToPageDict().items():
|
|
|
|
if page.HasChanges(): self._edit_log.append( ( HC.SET, ( name, page.GetImageboards() ) ) )
|
|
|
|
|
|
try:
|
|
|
|
for ( action, data ) in self._edit_log:
|
|
|
|
if action == HC.DELETE:
|
|
|
|
name = data
|
|
|
|
HC.app.Write( 'delete_imageboard', name )
|
|
|
|
elif action == HC.SET:
|
|
|
|
( name, imageboards ) = data
|
|
|
|
HC.app.Write( 'imageboard', name, imageboards )
|
|
|
|
|
|
|
|
finally: self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventRemove( self, event ):
|
|
|
|
site_panel = self._sites.GetCurrentPage()
|
|
|
|
if site_panel is not None:
|
|
|
|
name = self._sites.GetCurrentName()
|
|
|
|
self._edit_log.append( ( HC.DELETE, name ) )
|
|
|
|
self._sites.DeleteCurrentPage()
|
|
|
|
|
|
|
|
def Import( self, paths ):
|
|
|
|
for path in paths:
|
|
|
|
try:
|
|
|
|
with open( path, 'rb' ) as f: file = f.read()
|
|
|
|
thing = yaml.safe_load( file )
|
|
|
|
if type( thing ) == dict:
|
|
|
|
( name, imageboards ) = thing.items()[0]
|
|
|
|
if not self._sites.NameExists( name ):
|
|
|
|
self._edit_log.append( ( HC.SET, ( name, [] ) ) )
|
|
|
|
page = self._Panel( self._sites, [] )
|
|
|
|
self._sites.AddPage( page, name, select = True )
|
|
|
|
|
|
page = self._sites.GetNameToPageDict()[ name ]
|
|
|
|
for imageboard in imageboards:
|
|
|
|
if type( imageboard ) == CC.Imageboard: page.UpdateImageboard( imageboard )
|
|
|
|
|
|
elif type( thing ) == CC.Imageboard:
|
|
|
|
imageboard = thing
|
|
|
|
page = self._sites.GetCurrentPage()
|
|
|
|
page.UpdateImageboard( imageboard )
|
|
|
|
|
|
except:
|
|
|
|
wx.MessageBox( traceback.format_exc() )
|
|
|
|
|
|
|
|
|
|
class _Panel( wx.Panel ):
|
|
|
|
def __init__( self, parent, imageboards ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._site_panel = ClientGUICommon.StaticBox( self, 'site' )
|
|
|
|
self._imageboards = ClientGUICommon.ListBook( self._site_panel )
|
|
|
|
self._add = wx.Button( self._site_panel, label = 'add' )
|
|
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
|
|
self._add.SetForegroundColour( ( 0, 128, 0 ) )
|
|
|
|
self._remove = wx.Button( self._site_panel, label = 'remove' )
|
|
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
|
|
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
|
|
|
|
self._export = wx.Button( self._site_panel, label = 'export' )
|
|
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
for imageboard in imageboards:
|
|
|
|
name = imageboard.GetName()
|
|
|
|
page_info = ( self._Panel, ( self._imageboards, imageboard ), {} )
|
|
|
|
self._imageboards.AddPage( page_info, name )
|
|
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
add_remove_hbox.AddF( self._add, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._remove, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._export, FLAGS_MIXED )
|
|
|
|
self._site_panel.AddF( self._imageboards, FLAGS_EXPAND_BOTH_WAYS )
|
|
self._site_panel.AddF( add_remove_hbox, FLAGS_SMALL_INDENT )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._site_panel, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
wx.Panel.__init__( self, parent )
|
|
|
|
self._has_changes = False
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( 980, y ) )
|
|
|
|
|
|
def EventAdd( self, event ):
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, 'Enter new imageboard\'s name.' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
try:
|
|
|
|
name = dlg.GetValue()
|
|
|
|
if self._imageboards.NameExists( name ): raise Exception( 'That name is already in use!' )
|
|
|
|
if name == '': raise Exception( 'Please enter a nickname for the service.' )
|
|
|
|
imageboard = CC.Imageboard( name, '', 60, [], {} )
|
|
|
|
page = self._Panel( self._imageboards, imageboard )
|
|
|
|
self._imageboards.AddPage( page, name, select = True )
|
|
|
|
self._has_changes = True
|
|
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
self.EventAdd( event )
|
|
|
|
|
|
|
|
|
|
|
|
def EventExport( self, event ):
|
|
|
|
imageboard_panel = self._imageboards.GetCurrentPage()
|
|
|
|
if imageboard_panel is not None:
|
|
|
|
imageboard = imageboard_panel.GetImageboard()
|
|
|
|
with wx.FileDialog( self, 'select where to export imageboard', defaultFile = 'imageboard.yaml', style = wx.FD_SAVE ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
with open( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( imageboard ) )
|
|
|
|
|
|
|
|
|
|
|
|
def EventRemove( self, event ):
|
|
|
|
imageboard_panel = self._imageboards.GetCurrentPage()
|
|
|
|
if imageboard_panel is not None:
|
|
|
|
name = self._imageboards.GetCurrentName()
|
|
|
|
self._edit_log.append( ( HC.DELETE, name ) )
|
|
|
|
self._imageboards.DeleteCurrentPage()
|
|
|
|
self._has_changes = True
|
|
|
|
|
|
|
|
def GetImageboards( self ): return [ page.GetImageboard() for page in self._imageboards.GetNameToPageDict().values() ]
|
|
|
|
def HasChanges( self ): return self._has_changes or True in ( page.HasChanges() for page in self._imageboards.GetNameToPageDict().values() )
|
|
|
|
def UpdateImageboard( self, imageboard ):
|
|
|
|
name = imageboard.GetName()
|
|
|
|
if not self._imageboards.NameExists( name ):
|
|
|
|
new_imageboard = CC.Imageboard( name, '', 60, [], {} )
|
|
|
|
self._edit_log.append( ( HC.SET, ( name, new_imageboard ) ) )
|
|
|
|
page = self._Panel( self._imageboards, new_imageboard )
|
|
|
|
self._imageboards.AddPage( page, name, select = True )
|
|
|
|
|
|
page = self._imageboards.GetNameToPageDict()[ name ]
|
|
|
|
page.Update( imageboard )
|
|
|
|
|
|
class _Panel( wx.Panel ):
|
|
|
|
def __init__( self, parent, imageboard ):
|
|
|
|
wx.Panel.__init__( self, parent )
|
|
|
|
self._imageboard = imageboard
|
|
|
|
( post_url, flood_time, form_fields, restrictions ) = self._imageboard.GetBoardInfo()
|
|
|
|
def InitialiseControls():
|
|
|
|
self._imageboard_panel = ClientGUICommon.StaticBox( self, 'imageboard' )
|
|
|
|
#
|
|
|
|
self._basic_info_panel = ClientGUICommon.StaticBox( self._imageboard_panel, 'basic info' )
|
|
|
|
self._post_url = wx.TextCtrl( self._basic_info_panel )
|
|
|
|
self._flood_time = wx.SpinCtrl( self._basic_info_panel, min = 5, max = 1200 )
|
|
|
|
#
|
|
|
|
self._form_fields_panel = ClientGUICommon.StaticBox( self._imageboard_panel, 'form fields' )
|
|
|
|
self._form_fields = ClientGUICommon.SaneListCtrl( self._form_fields_panel, 350, [ ( 'name', 120 ), ( 'type', 120 ), ( 'default', -1 ), ( 'editable', 120 ) ] )
|
|
|
|
self._add = wx.Button( self._form_fields_panel, label = 'add' )
|
|
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
|
|
|
|
self._edit = wx.Button( self._form_fields_panel, label = 'edit' )
|
|
self._edit.Bind( wx.EVT_BUTTON, self.EventEdit )
|
|
|
|
self._delete = wx.Button( self._form_fields_panel, label = 'delete' )
|
|
self._delete.Bind( wx.EVT_BUTTON, self.EventDelete )
|
|
|
|
#
|
|
|
|
self._restrictions_panel = ClientGUICommon.StaticBox( self._imageboard_panel, 'restrictions' )
|
|
|
|
self._min_resolution = ClientGUICommon.NoneableSpinCtrl( self._restrictions_panel, 'min resolution', num_dimensions = 2 )
|
|
|
|
self._max_resolution = ClientGUICommon.NoneableSpinCtrl( self._restrictions_panel, 'max resolution', num_dimensions = 2 )
|
|
|
|
self._max_file_size = ClientGUICommon.NoneableSpinCtrl( self._restrictions_panel, 'max file size (KB)', multiplier = 1024 )
|
|
|
|
self._allowed_mimes_panel = ClientGUICommon.StaticBox( self._restrictions_panel, 'allowed mimes' )
|
|
|
|
self._mimes = wx.ListBox( self._allowed_mimes_panel )
|
|
|
|
self._mime_choice = wx.Choice( self._allowed_mimes_panel )
|
|
|
|
self._add_mime = wx.Button( self._allowed_mimes_panel, label = 'add' )
|
|
self._add_mime.Bind( wx.EVT_BUTTON, self.EventAddMime )
|
|
|
|
self._remove_mime = wx.Button( self._allowed_mimes_panel, label = 'remove' )
|
|
self._remove_mime.Bind( wx.EVT_BUTTON, self.EventRemoveMime )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
#
|
|
|
|
self._post_url.SetValue( post_url )
|
|
|
|
self._flood_time.SetRange( 5, 1200 )
|
|
self._flood_time.SetValue( flood_time )
|
|
|
|
#
|
|
|
|
for ( name, field_type, default, editable ) in form_fields:
|
|
|
|
self._form_fields.Append( ( name, CC.field_string_lookup[ field_type ], HC.u( default ), HC.u( editable ) ), ( name, field_type, default, editable ) )
|
|
|
|
|
|
#
|
|
|
|
if CC.RESTRICTION_MIN_RESOLUTION in restrictions: value = restrictions[ CC.RESTRICTION_MIN_RESOLUTION ]
|
|
else: value = None
|
|
|
|
self._min_resolution.SetValue( value )
|
|
|
|
if CC.RESTRICTION_MAX_RESOLUTION in restrictions: value = restrictions[ CC.RESTRICTION_MAX_RESOLUTION ]
|
|
else: value = None
|
|
|
|
self._max_resolution.SetValue( value )
|
|
|
|
if CC.RESTRICTION_MAX_FILE_SIZE in restrictions: value = restrictions[ CC.RESTRICTION_MAX_FILE_SIZE ]
|
|
else: value = None
|
|
|
|
self._max_file_size.SetValue( value )
|
|
|
|
if CC.RESTRICTION_ALLOWED_MIMES in restrictions: mimes = restrictions[ CC.RESTRICTION_ALLOWED_MIMES ]
|
|
else: mimes = []
|
|
|
|
for mime in mimes: self._mimes.Append( HC.mime_string_lookup[ mime ], mime )
|
|
|
|
for mime in HC.ALLOWED_MIMES: self._mime_choice.Append( HC.mime_string_lookup[ mime ], mime )
|
|
|
|
self._mime_choice.SetSelection( 0 )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
#
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
gridbox.AddF( wx.StaticText( self._basic_info_panel, label = 'POST URL' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._post_url, FLAGS_EXPAND_BOTH_WAYS )
|
|
gridbox.AddF( wx.StaticText( self._basic_info_panel, label = 'flood time' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._flood_time, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
self._basic_info_panel.AddF( gridbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
#
|
|
|
|
h_b_box = wx.BoxSizer( wx.HORIZONTAL )
|
|
h_b_box.AddF( self._add, FLAGS_MIXED )
|
|
h_b_box.AddF( self._edit, FLAGS_MIXED )
|
|
h_b_box.AddF( self._delete, FLAGS_MIXED )
|
|
|
|
self._form_fields_panel.AddF( self._form_fields, FLAGS_EXPAND_BOTH_WAYS )
|
|
self._form_fields_panel.AddF( h_b_box, FLAGS_BUTTON_SIZER )
|
|
|
|
#
|
|
|
|
mime_buttons_box = wx.BoxSizer( wx.HORIZONTAL )
|
|
mime_buttons_box.AddF( self._mime_choice, FLAGS_MIXED )
|
|
mime_buttons_box.AddF( self._add_mime, FLAGS_MIXED )
|
|
mime_buttons_box.AddF( self._remove_mime, FLAGS_MIXED )
|
|
|
|
self._allowed_mimes_panel.AddF( self._mimes, FLAGS_EXPAND_BOTH_WAYS )
|
|
self._allowed_mimes_panel.AddF( mime_buttons_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
self._restrictions_panel.AddF( self._min_resolution, FLAGS_EXPAND_PERPENDICULAR )
|
|
self._restrictions_panel.AddF( self._max_resolution, FLAGS_EXPAND_PERPENDICULAR )
|
|
self._restrictions_panel.AddF( self._max_file_size, FLAGS_EXPAND_PERPENDICULAR )
|
|
self._restrictions_panel.AddF( self._allowed_mimes_panel, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
#
|
|
|
|
self._imageboard_panel.AddF( self._basic_info_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
self._imageboard_panel.AddF( self._form_fields_panel, FLAGS_EXPAND_BOTH_WAYS )
|
|
self._imageboard_panel.AddF( self._restrictions_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._imageboard_panel, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
|
|
def _GetInfo( self ):
|
|
|
|
imageboard_name = self._imageboard.GetName()
|
|
|
|
post_url = self._post_url.GetValue()
|
|
|
|
flood_time = self._flood_time.GetValue()
|
|
|
|
form_fields = self._form_fields.GetClientData()
|
|
|
|
restrictions = {}
|
|
|
|
value = self._min_resolution.GetValue()
|
|
if value is not None: restrictions[ CC.RESTRICTION_MIN_RESOLUTION ] = value
|
|
|
|
value = self._max_resolution.GetValue()
|
|
if value is not None: restrictions[ CC.RESTRICTION_MAX_RESOLUTION ] = value
|
|
|
|
value = self._max_file_size.GetValue()
|
|
if value is not None: restrictions[ CC.RESTRICTION_MAX_FILE_SIZE ] = value
|
|
|
|
mimes = [ self._mimes.GetClientData( i ) for i in range( self._mimes.GetCount() ) ]
|
|
|
|
if len( mimes ) > 0: restrictions[ CC.RESTRICTION_ALLOWED_MIMES ] = mimes
|
|
|
|
return ( imageboard_name, post_url, flood_time, form_fields, restrictions )
|
|
|
|
|
|
def EventAdd( self, event ):
|
|
|
|
with ClientGUIDialogs.DialogInputNewFormField( self ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
( name, field_type, default, editable ) = dlg.GetFormField()
|
|
|
|
if name in [ form_field[0] for form_field in self._form_fields.GetClientData() ]:
|
|
|
|
wx.MessageBox( 'There is already a field named ' + name )
|
|
|
|
self.EventAdd( event )
|
|
|
|
return
|
|
|
|
|
|
self._form_fields.Append( ( name, CC.field_string_lookup[ field_type ], HC.u( default ), HC.u( editable ) ), ( name, field_type, default, editable ) )
|
|
|
|
|
|
|
|
|
|
def EventAddMime( self, event ):
|
|
|
|
selection = self._mime_choice.GetSelection()
|
|
|
|
if selection != wx.NOT_FOUND:
|
|
|
|
mime = self._mime_choice.GetClientData( selection )
|
|
|
|
existing_mimes = [ self._mimes.GetClientData( i ) for i in range( self._mimes.GetCount() ) ]
|
|
|
|
if mime not in existing_mimes: self._mimes.Append( HC.mime_string_lookup[ mime ], mime )
|
|
|
|
|
|
|
|
def EventDelete( self, event ): self._form_fields.RemoveAllSelected()
|
|
|
|
def EventRemoveMime( self, event ):
|
|
|
|
selection = self._mimes.GetSelection()
|
|
|
|
if selection != wx.NOT_FOUND: self._mimes.Delete( selection )
|
|
|
|
|
|
def EventEdit( self, event ):
|
|
|
|
indices = self._form_fields.GetAllSelected()
|
|
|
|
for index in indices:
|
|
|
|
( name, field_type, default, editable ) = self._form_fields.GetClientData( index )
|
|
|
|
form_field = ( name, field_type, default, editable )
|
|
|
|
with ClientGUIDialogs.DialogInputNewFormField( self, form_field ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
old_name = name
|
|
|
|
( name, field_type, default, editable ) = dlg.GetFormField()
|
|
|
|
if old_name != name:
|
|
|
|
if name in [ form_field[0] for form_field in self._form_fields.GetClientData() ]: raise Exception( 'You already have a form field called ' + name + '; delete or edit that one first' )
|
|
|
|
|
|
self._form_fields.UpdateRow( index, ( name, CC.field_string_lookup[ field_type ], HC.u( default ), HC.u( editable ) ), ( name, field_type, default, editable ) )
|
|
|
|
|
|
|
|
|
|
|
|
def GetImageboard( self ):
|
|
|
|
( name, post_url, flood_time, form_fields, restrictions ) = self._GetInfo()
|
|
|
|
return CC.Imageboard( name, post_url, flood_time, form_fields, restrictions )
|
|
|
|
|
|
def HasChanges( self ):
|
|
|
|
( my_name, my_post_url, my_flood_time, my_form_fields, my_restrictions ) = self._GetInfo()
|
|
|
|
( post_url, flood_time, form_fields, restrictions ) = self._imageboard.GetBoardInfo()
|
|
|
|
if post_url != my_post_url: return True
|
|
|
|
if flood_time != my_flood_time: return True
|
|
|
|
if set( [ tuple( item ) for item in form_fields ] ) != set( [ tuple( item ) for item in my_form_fields ] ): return True
|
|
|
|
if restrictions != my_restrictions: return True
|
|
|
|
return False
|
|
|
|
|
|
def Update( self, imageboard ):
|
|
|
|
( post_url, flood_time, form_fields, restrictions ) = imageboard.GetBoardInfo()
|
|
|
|
self._post_url.SetValue( post_url )
|
|
self._flood_time.SetValue( flood_time )
|
|
|
|
self._form_fields.ClearAll()
|
|
|
|
self._form_fields.InsertColumn( 0, 'name', width = 120 )
|
|
self._form_fields.InsertColumn( 1, 'type', width = 120 )
|
|
self._form_fields.InsertColumn( 2, 'default' )
|
|
self._form_fields.InsertColumn( 3, 'editable', width = 120 )
|
|
|
|
self._form_fields.setResizeColumn( 3 ) # default
|
|
|
|
for ( name, field_type, default, editable ) in form_fields:
|
|
|
|
self._form_fields.Append( ( name, CC.field_string_lookup[ field_type ], HC.u( default ), HC.u( editable ) ), ( name, field_type, default, editable ) )
|
|
|
|
|
|
if CC.RESTRICTION_MIN_RESOLUTION in restrictions: value = restrictions[ CC.RESTRICTION_MIN_RESOLUTION ]
|
|
else: value = None
|
|
|
|
self._min_resolution.SetValue( value )
|
|
|
|
if CC.RESTRICTION_MAX_RESOLUTION in restrictions: value = restrictions[ CC.RESTRICTION_MAX_RESOLUTION ]
|
|
else: value = None
|
|
|
|
self._max_resolution.SetValue( value )
|
|
|
|
if CC.RESTRICTION_MAX_FILE_SIZE in restrictions: value = restrictions[ CC.RESTRICTION_MAX_FILE_SIZE ]
|
|
else: value = None
|
|
|
|
self._max_file_size.SetValue( value )
|
|
|
|
self._mimes.Clear()
|
|
|
|
if CC.RESTRICTION_ALLOWED_MIMES in restrictions: mimes = restrictions[ CC.RESTRICTION_ALLOWED_MIMES ]
|
|
else: mimes = []
|
|
|
|
for mime in mimes: self._mimes.Append( HC.mime_string_lookup[ mime ], mime )
|
|
|
|
|
|
|
|
|
|
class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._import_folders = ClientGUICommon.SaneListCtrl( self, 480, [ ( 'path', -1 ), ( 'type', 120 ), ( 'check period', 120 ), ( 'local tag', 120 ) ] )
|
|
|
|
self._import_folders.SetMinSize( ( 780, 360 ) )
|
|
|
|
self._add_button = wx.Button( self, label = 'add' )
|
|
self._add_button.Bind( wx.EVT_BUTTON, self.EventAdd )
|
|
|
|
self._edit_button = wx.Button( self, label = 'edit' )
|
|
self._edit_button.Bind( wx.EVT_BUTTON, self.EventEdit )
|
|
|
|
self._delete_button = wx.Button( self, label = 'delete' )
|
|
self._delete_button.Bind( wx.EVT_BUTTON, self.EventDelete )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
self._original_paths_to_details = HC.app.Read( 'import_folders' )
|
|
|
|
for ( path, details ) in self._original_paths_to_details.items():
|
|
|
|
import_type = details[ 'type' ]
|
|
check_period = details[ 'check_period' ]
|
|
local_tag = details[ 'local_tag' ]
|
|
|
|
( pretty_type, pretty_check_period, pretty_local_tag ) = self._GetPrettyVariables( import_type, check_period, local_tag )
|
|
|
|
self._import_folders.Append( ( path, pretty_type, pretty_check_period, pretty_local_tag ), ( path, import_type, check_period, local_tag ) )
|
|
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
file_buttons = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
file_buttons.AddF( self._add_button, FLAGS_MIXED )
|
|
file_buttons.AddF( self._edit_button, FLAGS_MIXED )
|
|
file_buttons.AddF( self._delete_button, FLAGS_MIXED )
|
|
|
|
buttons = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
buttons.AddF( self._ok, FLAGS_MIXED )
|
|
buttons.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
intro = 'Here you can set the client to regularly check certain folders for new files to import.'
|
|
|
|
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._import_folders, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( file_buttons, FLAGS_BUTTON_SIZER )
|
|
vbox.AddF( buttons, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage import folders' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
self.SetDropTarget( ClientGUICommon.FileDropTarget( self._AddFolders ) )
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( x, y ) )
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def _AddFolder( self, path ):
|
|
|
|
all_existing_client_data = self._import_folders.GetClientData()
|
|
|
|
if path not in ( existing_path for ( existing_path, import_type, check_period, local_tag ) in all_existing_client_data ):
|
|
|
|
import_type = HC.IMPORT_FOLDER_TYPE_SYNCHRONISE
|
|
check_period = 15 * 60
|
|
local_tag = None
|
|
|
|
with DialogManageImportFoldersEdit( self, path, import_type, check_period, local_tag ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
( path, import_type, check_period, local_tag ) = dlg.GetInfo()
|
|
|
|
( pretty_type, pretty_check_period, pretty_local_tag ) = self._GetPrettyVariables( import_type, check_period, local_tag )
|
|
|
|
self._import_folders.Append( ( path, pretty_type, pretty_check_period, pretty_local_tag ), ( path, import_type, check_period, local_tag ) )
|
|
|
|
|
|
|
|
|
|
|
|
def _AddFolders( self, paths ):
|
|
|
|
for path in paths:
|
|
|
|
if os.path.isdir( path ): self._AddFolder( path )
|
|
|
|
|
|
|
|
def _GetPrettyVariables( self, import_type, check_period, local_tag ):
|
|
|
|
if import_type == HC.IMPORT_FOLDER_TYPE_DELETE: pretty_type = 'delete'
|
|
elif import_type == HC.IMPORT_FOLDER_TYPE_SYNCHRONISE: pretty_type = 'synchronise'
|
|
|
|
pretty_check_period = HC.u( check_period / 60 ) + ' minutes'
|
|
|
|
if local_tag == None: pretty_local_tag = ''
|
|
else: pretty_local_tag = local_tag
|
|
|
|
return ( pretty_type, pretty_check_period, pretty_local_tag )
|
|
|
|
|
|
def EventAdd( self, event ):
|
|
|
|
with wx.DirDialog( self, 'Select a folder to add.' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
path = dlg.GetPath()
|
|
|
|
self._AddFolder( path )
|
|
|
|
|
|
|
|
|
|
def EventDelete( self, event ): self._import_folders.RemoveAllSelected()
|
|
|
|
def EventEdit( self, event ):
|
|
|
|
indices = self._import_folders.GetAllSelected()
|
|
|
|
for index in indices:
|
|
|
|
( path, import_type, check_period, local_tag ) = self._import_folders.GetClientData( index )
|
|
|
|
with DialogManageImportFoldersEdit( self, path, import_type, check_period, local_tag ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
( path, import_type, check_period, local_tag ) = dlg.GetInfo()
|
|
|
|
( pretty_type, pretty_check_period, pretty_local_tag ) = self._GetPrettyVariables( import_type, check_period, local_tag )
|
|
|
|
self._import_folders.UpdateRow( index, ( path, pretty_type, pretty_check_period, pretty_local_tag ), ( path, import_type, check_period, local_tag ) )
|
|
|
|
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
client_data = self._import_folders.GetClientData()
|
|
|
|
import_folders = []
|
|
|
|
paths = set()
|
|
|
|
for ( path, import_type, check_period, local_tag ) in client_data:
|
|
|
|
if path in self._original_paths_to_details: details = self._original_paths_to_details[ path ]
|
|
else: details = { 'last_checked' : 0, 'cached_imported_paths' : set(), 'failed_imported_paths' : set() }
|
|
|
|
details[ 'type' ] = import_type
|
|
details[ 'check_period' ] = check_period
|
|
details[ 'local_tag' ] = local_tag
|
|
|
|
HC.app.Write( 'import_folder', path, details )
|
|
|
|
paths.add( path )
|
|
|
|
|
|
deletees = set( self._original_paths_to_details.keys() ) - paths
|
|
|
|
for deletee in deletees: HC.app.Write( 'delete_import_folder', deletee )
|
|
|
|
HC.pubsub.pub( 'notify_new_import_folders' )
|
|
|
|
self.EndModal( wx.ID_OK )
|
|
|
|
|
|
class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent, path, import_type, check_period, local_tag ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._path_box = ClientGUICommon.StaticBox( self, 'import path' )
|
|
|
|
self._path = wx.DirPickerCtrl( self._path_box, style = wx.DIRP_USE_TEXTCTRL )
|
|
|
|
#
|
|
|
|
self._type_box = ClientGUICommon.StaticBox( self, 'type of import folder' )
|
|
|
|
self._type = ClientGUICommon.BetterChoice( self._type_box )
|
|
self._type.Append( 'delete', HC.IMPORT_FOLDER_TYPE_DELETE )
|
|
self._type.Append( 'synchronise', HC.IMPORT_FOLDER_TYPE_SYNCHRONISE )
|
|
message = '''delete - try to import all files in folder and delete them if they succeed
|
|
|
|
synchronise - try to import all new files in folder'''
|
|
self._type.SetToolTipString( message )
|
|
|
|
#
|
|
|
|
self._period_box = ClientGUICommon.StaticBox( self, 'check period (minutes)' )
|
|
|
|
self._check_period = wx.SpinCtrl( self._period_box )
|
|
|
|
#
|
|
|
|
self._local_tag_box = ClientGUICommon.StaticBox( self, 'local tag to give to all imports' )
|
|
|
|
self._local_tag = wx.TextCtrl( self._local_tag_box )
|
|
self._local_tag.SetToolTipString( 'add this tag on the local tag service to anything imported from the folder' )
|
|
|
|
#
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
self._path.SetPath( path )
|
|
|
|
self._type.Select( import_type )
|
|
|
|
self._check_period.SetRange( 3, 180 )
|
|
|
|
self._check_period.SetValue( check_period / 60 )
|
|
|
|
if local_tag is not None: self._local_tag.SetValue( local_tag )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
self._path_box.AddF( self._path, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self._type_box.AddF( self._type, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self._period_box.AddF( self._check_period, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self._local_tag_box.AddF( self._local_tag, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
buttons = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
buttons.AddF( self._ok, FLAGS_MIXED )
|
|
buttons.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._path_box, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._type_box, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._period_box, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._local_tag_box, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( buttons, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( 640, y ) )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'edit import folder' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def GetInfo( self ):
|
|
|
|
path = self._path.GetPath()
|
|
|
|
import_type = self._type.GetChoice()
|
|
|
|
check_period = self._check_period.GetValue() * 60
|
|
|
|
local_tag = self._local_tag.GetValue()
|
|
|
|
return ( path, import_type, check_period, local_tag )
|
|
|
|
|
|
class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._listbook = ClientGUICommon.ListBook( self )
|
|
|
|
# files and thumbnails
|
|
|
|
self._file_page = wx.Panel( self._listbook )
|
|
self._file_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
self._export_location = wx.DirPickerCtrl( self._file_page, style = wx.DIRP_USE_TEXTCTRL )
|
|
|
|
self._exclude_deleted_files = wx.CheckBox( self._file_page, label = '' )
|
|
|
|
self._thumbnail_width = wx.SpinCtrl( self._file_page, min=20, max=200 )
|
|
self._thumbnail_width.Bind( wx.EVT_SPINCTRL, self.EventThumbnailsUpdate )
|
|
|
|
self._thumbnail_height = wx.SpinCtrl( self._file_page, min=20, max=200 )
|
|
self._thumbnail_height.Bind( wx.EVT_SPINCTRL, self.EventThumbnailsUpdate )
|
|
|
|
self._listbook.AddPage( self._file_page, 'files and thumbnails' )
|
|
|
|
# maintenance and memory
|
|
|
|
self._maintenance_page = wx.Panel( self._listbook )
|
|
self._maintenance_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
self._thumbnail_cache_size = wx.SpinCtrl( self._maintenance_page, min = 10, max = 3000 )
|
|
self._thumbnail_cache_size.Bind( wx.EVT_SPINCTRL, self.EventThumbnailsUpdate )
|
|
|
|
self._estimated_number_thumbnails = wx.StaticText( self._maintenance_page, label = '' )
|
|
|
|
self._preview_cache_size = wx.SpinCtrl( self._maintenance_page, min = 20, max = 3000 )
|
|
self._preview_cache_size.Bind( wx.EVT_SPINCTRL, self.EventPreviewsUpdate )
|
|
|
|
self._estimated_number_previews = wx.StaticText( self._maintenance_page, label = '' )
|
|
|
|
self._fullscreen_cache_size = wx.SpinCtrl( self._maintenance_page, min = 100, max = 3000 )
|
|
self._fullscreen_cache_size.Bind( wx.EVT_SPINCTRL, self.EventFullscreensUpdate )
|
|
|
|
self._estimated_number_fullscreens = wx.StaticText( self._maintenance_page, label = '' )
|
|
|
|
self._maintenance_idle_period = wx.SpinCtrl( self._maintenance_page, min = 0, max = 1000 )
|
|
self._maintenance_vacuum_period = wx.SpinCtrl( self._maintenance_page, min = 0, max = 365 )
|
|
self._maintenance_delete_orphans_period = wx.SpinCtrl( self._maintenance_page, min = 0, max = 365 )
|
|
|
|
self._num_autocomplete_chars = wx.SpinCtrl( self._maintenance_page, min = 1, max = 100 )
|
|
self._num_autocomplete_chars.SetToolTipString( 'how many characters you enter before the gui fetches autocomplete results from the db' + os.linesep + 'increase this if you find autocomplete results are slow' )
|
|
|
|
self._autocomplete_long_wait = wx.SpinCtrl( self._maintenance_page, min = 0, max = 10000 )
|
|
self._autocomplete_long_wait.SetToolTipString( 'how long the gui will wait, after you enter a character, before it queries the db with what you have entered so far' )
|
|
|
|
self._autocomplete_short_wait_chars = wx.SpinCtrl( self._maintenance_page, min = 1, max = 100 )
|
|
self._autocomplete_short_wait_chars.SetToolTipString( 'how many characters you enter before the gui starts waiting the short time before querying the db' )
|
|
|
|
self._autocomplete_short_wait = wx.SpinCtrl( self._maintenance_page, min = 0, max = 10000 )
|
|
self._autocomplete_short_wait.SetToolTipString( 'how long the gui will wait, after you enter a lot of characters, before it queries the db with what you have entered so far' )
|
|
|
|
self._listbook.AddPage( self._maintenance_page, 'maintenance and memory' )
|
|
|
|
# gui
|
|
|
|
self._gui_page = wx.Panel( self._listbook )
|
|
self._gui_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
self._default_gui_session = wx.Choice( self._gui_page )
|
|
|
|
self._confirm_client_exit = wx.CheckBox( self._gui_page )
|
|
|
|
self._gui_capitalisation = wx.CheckBox( self._gui_page )
|
|
|
|
self._gui_show_all_tags_in_autocomplete = wx.CheckBox( self._gui_page )
|
|
|
|
self._default_tag_sort = wx.Choice( self._gui_page )
|
|
|
|
self._default_tag_sort.Append( 'lexicographic (a-z)', CC.SORT_BY_LEXICOGRAPHIC_ASC )
|
|
self._default_tag_sort.Append( 'lexicographic (z-a)', CC.SORT_BY_LEXICOGRAPHIC_DESC )
|
|
self._default_tag_sort.Append( 'incidence (desc)', CC.SORT_BY_INCIDENCE_DESC )
|
|
self._default_tag_sort.Append( 'incidence (asc)', CC.SORT_BY_INCIDENCE_ASC )
|
|
|
|
self._default_tag_repository = ClientGUICommon.BetterChoice( self._gui_page )
|
|
|
|
self._listbook.AddPage( self._gui_page, 'gui' )
|
|
|
|
# sound
|
|
|
|
self._sound_page = wx.Panel( self._listbook )
|
|
self._sound_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
self._play_dumper_noises = wx.CheckBox( self._sound_page, label = 'play success/fail noises when dumping' )
|
|
|
|
self._listbook.AddPage( self._sound_page, 'sound' )
|
|
|
|
# default file system predicates
|
|
|
|
self._file_system_predicates_page = wx.Panel( self._listbook )
|
|
self._file_system_predicates_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
self._file_system_predicate_age_sign = wx.Choice( self._file_system_predicates_page, choices=[ '<', u'\u2248', '>' ] )
|
|
|
|
self._file_system_predicate_age_years = wx.SpinCtrl( self._file_system_predicates_page, max = 30 )
|
|
|
|
self._file_system_predicate_age_months = wx.SpinCtrl( self._file_system_predicates_page, max = 60 )
|
|
|
|
self._file_system_predicate_age_days = wx.SpinCtrl( self._file_system_predicates_page, max = 90 )
|
|
|
|
self._file_system_predicate_duration_sign = wx.Choice( self._file_system_predicates_page, choices=[ '<', u'\u2248', '=', '>' ] )
|
|
|
|
self._file_system_predicate_duration_s = wx.SpinCtrl( self._file_system_predicates_page, max = 3599 )
|
|
|
|
self._file_system_predicate_duration_ms = wx.SpinCtrl( self._file_system_predicates_page, max = 999 )
|
|
|
|
self._file_system_predicate_height_sign = wx.Choice( self._file_system_predicates_page, choices=[ '<', u'\u2248', '=', '>' ] )
|
|
|
|
self._file_system_predicate_height = wx.SpinCtrl( self._file_system_predicates_page, max = 200000 )
|
|
|
|
self._file_system_predicate_limit = wx.SpinCtrl( self._file_system_predicates_page, max = 1000000 )
|
|
|
|
self._file_system_predicate_mime_media = wx.Choice( self._file_system_predicates_page, choices=[ 'image', 'application' ] )
|
|
self._file_system_predicate_mime_media.Bind( wx.EVT_CHOICE, self.EventFileSystemPredicateMime )
|
|
|
|
self._file_system_predicate_mime_type = wx.Choice( self._file_system_predicates_page, choices=[], size = ( 120, -1 ) )
|
|
|
|
self._file_system_predicate_num_tags_sign = wx.Choice( self._file_system_predicates_page, choices=[ '<', '=', '>' ] )
|
|
|
|
self._file_system_predicate_num_tags = wx.SpinCtrl( self._file_system_predicates_page, max = 2000 )
|
|
|
|
self._file_system_predicate_local_rating_numerical_sign = wx.Choice( self._file_system_predicates_page, choices=[ '>', '<', '=', u'\u2248', '=rated', '=not rated', '=uncertain' ] )
|
|
|
|
self._file_system_predicate_local_rating_numerical_value = wx.SpinCtrl( self._file_system_predicates_page, min = 0, max = 50000 )
|
|
|
|
self._file_system_predicate_local_rating_like_value = wx.Choice( self._file_system_predicates_page, choices=[ 'like', 'dislike', 'rated', 'not rated' ] )
|
|
|
|
self._file_system_predicate_ratio_sign = wx.Choice( self._file_system_predicates_page, choices=[ '=', u'\u2248' ] )
|
|
|
|
self._file_system_predicate_ratio_width = wx.SpinCtrl( self._file_system_predicates_page, max = 50000 )
|
|
|
|
self._file_system_predicate_ratio_height = wx.SpinCtrl( self._file_system_predicates_page, max = 50000 )
|
|
|
|
self._file_system_predicate_size_sign = wx.Choice( self._file_system_predicates_page, choices=[ '<', u'\u2248', '=', '>' ] )
|
|
|
|
self._file_system_predicate_size = wx.SpinCtrl( self._file_system_predicates_page, max = 1048576 )
|
|
|
|
self._file_system_predicate_size_unit = wx.Choice( self._file_system_predicates_page, choices=[ 'B', 'KB', 'MB', 'GB' ] )
|
|
|
|
self._file_system_predicate_width_sign = wx.Choice( self._file_system_predicates_page, choices=[ '<', u'\u2248', '=', '>' ] )
|
|
|
|
self._file_system_predicate_width = wx.SpinCtrl( self._file_system_predicates_page, max = 200000 )
|
|
|
|
self._file_system_predicate_num_words_sign = wx.Choice( self._file_system_predicates_page, choices=[ '<', u'\u2248', '=', '>' ] )
|
|
|
|
self._file_system_predicate_num_words = wx.SpinCtrl( self._file_system_predicates_page, max = 1000000 )
|
|
|
|
self._listbook.AddPage( self._file_system_predicates_page, 'default file system predicates' )
|
|
|
|
# default advanced tag options
|
|
|
|
self._advanced_tag_options_page = wx.Panel( self._listbook )
|
|
self._advanced_tag_options_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
self._advanced_tag_options = wx.ListBox( self._advanced_tag_options_page )
|
|
self._advanced_tag_options.Bind( wx.EVT_LEFT_DCLICK, self.EventATODelete )
|
|
|
|
self._advanced_tag_options_add = wx.Button( self._advanced_tag_options_page, label = 'add' )
|
|
self._advanced_tag_options_add.Bind( wx.EVT_BUTTON, self.EventATOAdd )
|
|
|
|
self._advanced_tag_options_edit = wx.Button( self._advanced_tag_options_page, label = 'edit' )
|
|
self._advanced_tag_options_edit.Bind( wx.EVT_BUTTON, self.EventATOEdit )
|
|
|
|
self._advanced_tag_options_delete = wx.Button( self._advanced_tag_options_page, label = 'delete' )
|
|
self._advanced_tag_options_delete.Bind( wx.EVT_BUTTON, self.EventATODelete )
|
|
|
|
self._listbook.AddPage( self._advanced_tag_options_page, 'default advanced tag options' )
|
|
|
|
# colours
|
|
|
|
self._colour_page = wx.Panel( self._listbook )
|
|
self._colour_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
self._namespace_colours = ClientGUICommon.TagsBoxColourOptions( self._colour_page, HC.options[ 'namespace_colours' ] )
|
|
|
|
self._edit_namespace_colour = wx.Button( self._colour_page, label = 'edit selected' )
|
|
self._edit_namespace_colour.Bind( wx.EVT_BUTTON, self.EventEditNamespaceColour )
|
|
|
|
self._new_namespace_colour = wx.TextCtrl( self._colour_page, style = wx.TE_PROCESS_ENTER )
|
|
self._new_namespace_colour.Bind( wx.EVT_KEY_DOWN, self.EventKeyDownNamespace )
|
|
|
|
self._listbook.AddPage( self._colour_page, 'colours' )
|
|
|
|
# server
|
|
|
|
self._server_page = wx.Panel( self._listbook )
|
|
self._server_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
self._local_port = wx.SpinCtrl( self._server_page, min = 0, max = 65535 )
|
|
|
|
self._listbook.AddPage( self._server_page, 'local server' )
|
|
|
|
# sort/collect
|
|
|
|
self._sort_by_page = wx.Panel( self._listbook )
|
|
self._sort_by_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
self._default_sort = ClientGUICommon.ChoiceSort( self._sort_by_page, sort_by = HC.options[ 'sort_by' ] )
|
|
|
|
self._default_collect = ClientGUICommon.CheckboxCollect( self._sort_by_page )
|
|
|
|
self._sort_by = wx.ListBox( self._sort_by_page )
|
|
self._sort_by.Bind( wx.EVT_LEFT_DCLICK, self.EventRemoveSortBy )
|
|
|
|
self._new_sort_by = wx.TextCtrl( self._sort_by_page, style = wx.TE_PROCESS_ENTER )
|
|
self._new_sort_by.Bind( wx.EVT_KEY_DOWN, self.EventKeyDownSortBy )
|
|
|
|
self._listbook.AddPage( self._sort_by_page, 'sort/collect' )
|
|
|
|
# shortcuts
|
|
|
|
self._shortcuts_page = wx.Panel( self._listbook )
|
|
self._shortcuts_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
self._shortcuts = ClientGUICommon.SaneListCtrl( self._shortcuts_page, 480, [ ( 'modifier', 120 ), ( 'key', 120 ), ( 'action', -1 ) ] )
|
|
|
|
self._shortcuts_add = wx.Button( self._shortcuts_page, label = 'add' )
|
|
self._shortcuts_add.Bind( wx.EVT_BUTTON, self.EventShortcutsAdd )
|
|
|
|
self._shortcuts_edit = wx.Button( self._shortcuts_page, label = 'edit' )
|
|
self._shortcuts_edit.Bind( wx.EVT_BUTTON, self.EventShortcutsEdit )
|
|
|
|
self._shortcuts_delete = wx.Button( self._shortcuts_page, label = 'delete' )
|
|
self._shortcuts_delete.Bind( wx.EVT_BUTTON, self.EventShortcutsDelete )
|
|
|
|
self._listbook.AddPage( self._shortcuts_page, 'shortcuts' )
|
|
|
|
# thread checker
|
|
|
|
self._thread_checker_page = wx.Panel( self._listbook )
|
|
self._thread_checker_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
self._thread_times_to_check = wx.SpinCtrl( self._thread_checker_page, min = 0, max = 100 )
|
|
self._thread_times_to_check.SetToolTipString( 'how many times the thread checker will check' )
|
|
|
|
self._thread_check_period = wx.SpinCtrl( self._thread_checker_page, min = 30, max = 86400 )
|
|
self._thread_check_period.SetToolTipString( 'how long the checker will wait between checks' )
|
|
|
|
self._listbook.AddPage( self._thread_checker_page, 'thread checker' )
|
|
|
|
#
|
|
|
|
self._ok = wx.Button( self, id = wx.ID_OK, label = 'Save' )
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
if HC.options[ 'export_path' ] is not None:
|
|
|
|
abs_path = HC.ConvertPortablePathToAbsPath( HC.options[ 'export_path' ] )
|
|
|
|
if abs_path is not None: self._export_location.SetPath( abs_path )
|
|
|
|
|
|
self._exclude_deleted_files.SetValue( HC.options[ 'exclude_deleted_files' ] )
|
|
|
|
( thumbnail_width, thumbnail_height ) = HC.options[ 'thumbnail_dimensions' ]
|
|
|
|
self._thumbnail_width.SetValue( thumbnail_width )
|
|
|
|
self._thumbnail_height.SetValue( thumbnail_height )
|
|
|
|
#
|
|
|
|
self._thumbnail_cache_size.SetValue( int( HC.options[ 'thumbnail_cache_size' ] / 1048576 ) )
|
|
|
|
self._preview_cache_size.SetValue( int( HC.options[ 'preview_cache_size' ] / 1048576 ) )
|
|
|
|
self._fullscreen_cache_size.SetValue( int( HC.options[ 'fullscreen_cache_size' ] / 1048576 ) )
|
|
|
|
self._num_autocomplete_chars.SetValue( HC.options[ 'num_autocomplete_chars' ] )
|
|
|
|
self._maintenance_idle_period.SetValue( HC.options[ 'idle_period' ] / 60 )
|
|
self._maintenance_vacuum_period.SetValue( HC.options[ 'maintenance_vacuum_period' ] / 86400 )
|
|
self._maintenance_delete_orphans_period.SetValue( HC.options[ 'maintenance_delete_orphans_period' ] / 86400 )
|
|
|
|
( char_limit, long_wait, short_wait ) = HC.options[ 'ac_timings' ]
|
|
|
|
self._autocomplete_long_wait.SetValue( long_wait )
|
|
|
|
self._autocomplete_short_wait_chars.SetValue( char_limit )
|
|
|
|
self._autocomplete_short_wait.SetValue( short_wait )
|
|
|
|
#
|
|
|
|
gui_sessions = HC.app.Read( 'gui_sessions' )
|
|
|
|
gui_session_names = gui_sessions.keys()
|
|
|
|
if 'last session' not in gui_session_names: gui_session_names.insert( 0, 'last session' )
|
|
|
|
self._default_gui_session.Append( 'just a blank page', None )
|
|
|
|
for name in gui_session_names: self._default_gui_session.Append( name, name )
|
|
|
|
try: self._default_gui_session.SetStringSelection( HC.options[ 'default_gui_session' ] )
|
|
except: self._default_gui_session.SetSelection( 0 )
|
|
|
|
self._confirm_client_exit.SetValue( HC.options[ 'confirm_client_exit' ] )
|
|
|
|
self._gui_capitalisation.SetValue( HC.options[ 'gui_capitalisation' ] )
|
|
|
|
self._gui_show_all_tags_in_autocomplete.SetValue( HC.options[ 'show_all_tags_in_autocomplete' ] )
|
|
|
|
if HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_ASC: self._default_tag_sort.Select( 0 )
|
|
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_DESC: self._default_tag_sort.Select( 1 )
|
|
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_DESC: self._default_tag_sort.Select( 2 )
|
|
elif HC.options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_ASC: self._default_tag_sort.Select( 3 )
|
|
|
|
services = HC.app.GetManager( 'services' ).GetServices( ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ) )
|
|
|
|
for service in services: self._default_tag_repository.Append( service.GetName(), service.GetServiceKey() )
|
|
|
|
default_tag_repository_key = HC.options[ 'default_tag_repository' ]
|
|
|
|
self._default_tag_repository.SelectClientData( default_tag_repository_key )
|
|
|
|
#
|
|
|
|
self._play_dumper_noises.SetValue( HC.options[ 'play_dumper_noises' ] )
|
|
|
|
#
|
|
|
|
system_predicates = HC.options[ 'file_system_predicates' ]
|
|
|
|
( sign, years, months, days ) = system_predicates[ 'age' ]
|
|
|
|
self._file_system_predicate_age_sign.SetSelection( sign )
|
|
self._file_system_predicate_age_years.SetValue( years )
|
|
self._file_system_predicate_age_months.SetValue( months )
|
|
self._file_system_predicate_age_days.SetValue( days )
|
|
|
|
( sign, s, ms ) = system_predicates[ 'duration' ]
|
|
|
|
self._file_system_predicate_duration_sign.SetSelection( sign )
|
|
self._file_system_predicate_duration_s.SetValue( s )
|
|
self._file_system_predicate_duration_ms.SetValue( ms )
|
|
|
|
( sign, height ) = system_predicates[ 'height' ]
|
|
|
|
self._file_system_predicate_height_sign.SetSelection( sign )
|
|
self._file_system_predicate_height.SetValue( height )
|
|
|
|
limit = system_predicates[ 'limit' ]
|
|
|
|
self._file_system_predicate_limit.SetValue( limit )
|
|
|
|
( media, media_type ) = system_predicates[ 'mime' ]
|
|
|
|
self._file_system_predicate_mime_media.SetSelection( media )
|
|
|
|
self.EventFileSystemPredicateMime( None )
|
|
|
|
self._file_system_predicate_mime_type.SetSelection( media_type )
|
|
|
|
( sign, num_tags ) = system_predicates[ 'num_tags' ]
|
|
|
|
self._file_system_predicate_num_tags_sign.SetSelection( sign )
|
|
self._file_system_predicate_num_tags.SetValue( num_tags )
|
|
|
|
( sign, value ) = system_predicates[ 'local_rating_numerical' ]
|
|
|
|
self._file_system_predicate_local_rating_numerical_sign.SetSelection( sign )
|
|
self._file_system_predicate_local_rating_numerical_value.SetValue( value )
|
|
|
|
value = system_predicates[ 'local_rating_like' ]
|
|
|
|
self._file_system_predicate_local_rating_like_value.SetSelection( value )
|
|
|
|
( sign, width, height ) = system_predicates[ 'ratio' ]
|
|
|
|
self._file_system_predicate_ratio_sign.SetSelection( sign )
|
|
self._file_system_predicate_ratio_width.SetValue( width )
|
|
self._file_system_predicate_ratio_height.SetValue( height )
|
|
|
|
( sign, size, unit ) = system_predicates[ 'size' ]
|
|
|
|
self._file_system_predicate_size_sign.SetSelection( sign )
|
|
self._file_system_predicate_size.SetValue( size )
|
|
self._file_system_predicate_size_unit.SetSelection( unit )
|
|
|
|
( sign, width ) = system_predicates[ 'width' ]
|
|
|
|
self._file_system_predicate_width_sign.SetSelection( sign )
|
|
self._file_system_predicate_width.SetValue( width )
|
|
|
|
( sign, num_words ) = system_predicates[ 'num_words' ]
|
|
|
|
self._file_system_predicate_num_words_sign.SetSelection( sign )
|
|
self._file_system_predicate_num_words.SetValue( num_words )
|
|
|
|
#
|
|
|
|
for ( name, ato ) in HC.options[ 'default_advanced_tag_options' ].items():
|
|
|
|
if name == 'default': pretty_name = 'default'
|
|
elif type( name ) == int: pretty_name = HC.site_type_string_lookup[ name ]
|
|
else:
|
|
|
|
( booru_id, booru_name ) = name
|
|
|
|
pretty_name = 'booru: ' + booru_name
|
|
|
|
|
|
self._advanced_tag_options.Append( pretty_name, ( name, ato ) )
|
|
|
|
|
|
#
|
|
|
|
self._local_port.SetValue( HC.options[ 'local_port' ] )
|
|
|
|
#
|
|
|
|
for ( sort_by_type, sort_by ) in HC.options[ 'sort_by' ]: self._sort_by.Append( '-'.join( sort_by ), sort_by )
|
|
|
|
#
|
|
|
|
for ( modifier, key_dict ) in HC.options[ 'shortcuts' ].items():
|
|
|
|
for ( key, action ) in key_dict.items():
|
|
|
|
( pretty_modifier, pretty_key, pretty_action ) = HC.ConvertShortcutToPrettyShortcut( modifier, key, action )
|
|
|
|
self._shortcuts.Append( ( pretty_modifier, pretty_key, pretty_action ), ( modifier, key, action ) )
|
|
|
|
|
|
|
|
self._SortListCtrl()
|
|
|
|
#
|
|
|
|
( times_to_check, check_period ) = HC.options[ 'thread_checker_timings' ]
|
|
|
|
self._thread_times_to_check.SetValue( times_to_check )
|
|
|
|
self._thread_check_period.SetValue( check_period )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
gridbox.AddF( wx.StaticText( self._file_page, label = 'Default export directory: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._export_location, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
gridbox.AddF( wx.StaticText( self._file_page, label = 'Exclude deleted files from new imports and remote searches: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._exclude_deleted_files, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._file_page, label = 'Thumbnail width: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._thumbnail_width, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._file_page, label = 'Thumbnail height: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._thumbnail_height, FLAGS_MIXED )
|
|
|
|
self._file_page.SetSizer( gridbox )
|
|
|
|
#
|
|
|
|
thumbnails_sizer = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
thumbnails_sizer.AddF( self._thumbnail_cache_size, FLAGS_MIXED )
|
|
thumbnails_sizer.AddF( self._estimated_number_thumbnails, FLAGS_MIXED )
|
|
|
|
previews_sizer = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
previews_sizer.AddF( self._preview_cache_size, FLAGS_MIXED )
|
|
previews_sizer.AddF( self._estimated_number_previews, FLAGS_MIXED )
|
|
|
|
fullscreens_sizer = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
fullscreens_sizer.AddF( self._fullscreen_cache_size, FLAGS_MIXED )
|
|
fullscreens_sizer.AddF( self._estimated_number_fullscreens, FLAGS_MIXED )
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
gridbox.AddF( wx.StaticText( self._maintenance_page, label = 'MB memory reserved for thumbnail cache: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( thumbnails_sizer, FLAGS_NONE )
|
|
|
|
gridbox.AddF( wx.StaticText( self._maintenance_page, label = 'MB memory reserved for preview cache: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( previews_sizer, FLAGS_NONE )
|
|
|
|
gridbox.AddF( wx.StaticText( self._maintenance_page, label = 'MB memory reserved for fullscreen cache: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( fullscreens_sizer, FLAGS_NONE )
|
|
|
|
gridbox.AddF( wx.StaticText( self._maintenance_page, label = 'Minutes of inactivity until client is considered idle (0 for never): ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._maintenance_idle_period, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._maintenance_page, label = 'Number of days to wait between vacuums (0 for never): ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._maintenance_vacuum_period, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._maintenance_page, label = 'Number of days to wait between orphan deletions (0 for never): ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._maintenance_delete_orphans_period, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._maintenance_page, label = 'Autocomplete character threshold: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._num_autocomplete_chars, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._maintenance_page, label = 'Autocomplete long wait (ms): ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._autocomplete_long_wait, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._maintenance_page, label = 'Autocomplete short wait threshold: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._autocomplete_short_wait_chars, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._maintenance_page, label = 'Autocomplete short wait (ms): ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._autocomplete_short_wait, FLAGS_MIXED )
|
|
|
|
self._maintenance_page.SetSizer( gridbox )
|
|
|
|
#
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
gridbox.AddF( wx.StaticText( self._gui_page, label = 'Default session on startup:' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._default_gui_session, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._gui_page, label = 'Confirm client exit:' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._confirm_client_exit, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._gui_page, label = 'Default tag service in manage tag dialogs:' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._default_tag_repository, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._gui_page, label = 'Default tag sort on management panel:' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._default_tag_sort, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._gui_page, label = 'Capitalise gui: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._gui_capitalisation, FLAGS_MIXED )
|
|
|
|
gridbox.AddF( wx.StaticText( self._gui_page, label = 'By default, search non-local tags in write-autocomplete: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._gui_show_all_tags_in_autocomplete, FLAGS_MIXED )
|
|
|
|
self._gui_page.SetSizer( gridbox )
|
|
|
|
#
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'system:age' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_age_sign, FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_age_years, FLAGS_MIXED )
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'years' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_age_months, FLAGS_MIXED )
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'months' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_age_days, FLAGS_MIXED )
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'days' ), FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'system:duration' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_duration_sign, FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_duration_s, FLAGS_MIXED )
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 's' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_duration_ms, FLAGS_MIXED )
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'ms' ), FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'system:height' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_height_sign, FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_height, FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'system:limit=' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_limit, FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'system:mime' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_mime_media, FLAGS_MIXED )
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = '/' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_mime_type, FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'system:num_tags' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_num_tags_sign, FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_num_tags, FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'system:local_rating_like' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_local_rating_like_value, FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'system:local_rating_numerical' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_local_rating_numerical_sign, FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_local_rating_numerical_value, FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'system:ratio' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_ratio_sign, FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_ratio_width, FLAGS_MIXED )
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = ':' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_ratio_height, FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'system:size' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_size_sign, FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_size, FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_size_unit, FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'system:width' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_width_sign, FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_width, FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._file_system_predicates_page, label = 'system:num_words' ), FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_num_words_sign, FLAGS_MIXED )
|
|
hbox.AddF( self._file_system_predicate_num_words, FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
self._file_system_predicates_page.SetSizer( vbox )
|
|
|
|
#
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._advanced_tag_options, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( self._advanced_tag_options_add, FLAGS_BUTTON_SIZER )
|
|
hbox.AddF( self._advanced_tag_options_edit, FLAGS_BUTTON_SIZER )
|
|
hbox.AddF( self._advanced_tag_options_delete, FLAGS_BUTTON_SIZER )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self._advanced_tag_options_page.SetSizer( vbox )
|
|
|
|
#
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._server_page, label = 'local server port: ' ), FLAGS_MIXED )
|
|
hbox.AddF( self._local_port, FLAGS_MIXED )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
self._server_page.SetSizer( vbox )
|
|
|
|
#
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._play_dumper_noises, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self._sound_page.SetSizer( vbox )
|
|
|
|
#
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._namespace_colours, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( self._new_namespace_colour, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._edit_namespace_colour, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self._colour_page.SetSizer( vbox )
|
|
|
|
#
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
gridbox.AddF( wx.StaticText( self._sort_by_page, label = 'default sort: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._default_sort, FLAGS_EXPAND_BOTH_WAYS )
|
|
gridbox.AddF( wx.StaticText( self._sort_by_page, label = 'default collect: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._default_collect, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( gridbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
vbox.AddF( self._sort_by, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( self._new_sort_by, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self._sort_by_page.SetSizer( vbox )
|
|
|
|
#
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( wx.StaticText( self._shortcuts_page, label = 'These shortcuts are global to the main gui! You probably want to stick to function keys or ctrl + something!' ), FLAGS_MIXED )
|
|
vbox.AddF( self._shortcuts, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( self._shortcuts_add, FLAGS_BUTTON_SIZER )
|
|
hbox.AddF( self._shortcuts_edit, FLAGS_BUTTON_SIZER )
|
|
hbox.AddF( self._shortcuts_delete, FLAGS_BUTTON_SIZER )
|
|
|
|
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
self._shortcuts_page.SetSizer( vbox )
|
|
|
|
#
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
gridbox.AddF( wx.StaticText( self._thread_checker_page, label = 'default number of times to check: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._thread_times_to_check, FLAGS_MIXED )
|
|
gridbox.AddF( wx.StaticText( self._thread_checker_page, label = 'default wait in seconds between checks: ' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._thread_check_period, FLAGS_MIXED )
|
|
|
|
self._thread_checker_page.SetSizer( gridbox )
|
|
|
|
#
|
|
|
|
buttons = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
buttons.AddF( self._ok, FLAGS_SMALL_INDENT )
|
|
buttons.AddF( self._cancel, FLAGS_SMALL_INDENT )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._listbook, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( buttons, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
if x < 800: x = 800
|
|
if y < 600: y = 600
|
|
|
|
self.SetInitialSize( ( x, y ) )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'hydrus client options' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
self.EventFullscreensUpdate( None )
|
|
self.EventPreviewsUpdate( None )
|
|
self.EventThumbnailsUpdate( None )
|
|
|
|
wx.CallAfter( self._maintenance_page.Layout ) # draws the static texts correctly
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def _SortListCtrl( self ): self._shortcuts.SortListItems( 2 )
|
|
|
|
def EventATOAdd( self, event ):
|
|
|
|
pretty_names_to_names = {}
|
|
|
|
for ( k, v ) in HC.site_type_string_lookup.items(): pretty_names_to_names[ v ] = k
|
|
|
|
boorus = HC.app.Read( 'remote_boorus' )
|
|
|
|
for ( booru_name, booru ) in boorus.items(): pretty_names_to_names[ 'booru: ' + booru_name ] = ( HC.SITE_TYPE_BOORU, booru_name )
|
|
|
|
names = pretty_names_to_names.keys()
|
|
|
|
names.sort()
|
|
|
|
pretty_names_to_names[ 'default' ] = 'default'
|
|
|
|
names.insert( 0, 'default' )
|
|
|
|
with ClientGUIDialogs.DialogSelectFromListOfStrings( self, 'select tag domain', names ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
pretty_name = dlg.GetString()
|
|
|
|
for i in range( self._advanced_tag_options.GetCount() ):
|
|
|
|
if pretty_name == self._advanced_tag_options.GetString( i ):
|
|
|
|
wx.MessageBox( 'You already have default advanced tag options set up for that domain!' )
|
|
|
|
return
|
|
|
|
|
|
|
|
name = pretty_names_to_names[ pretty_name ]
|
|
|
|
with ClientGUIDialogs.DialogInputAdvancedTagOptions( self, pretty_name, name, {} ) as ato_dlg:
|
|
|
|
if ato_dlg.ShowModal() == wx.ID_OK:
|
|
|
|
ato = ato_dlg.GetATO()
|
|
|
|
self._advanced_tag_options.Append( pretty_name, ( name, ato ) )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def EventATODelete( self, event ):
|
|
|
|
selection = self._advanced_tag_options.GetSelection()
|
|
|
|
if selection != wx.NOT_FOUND: self._advanced_tag_options.Delete( selection )
|
|
|
|
|
|
def EventATOEdit( self, event ):
|
|
|
|
selection = self._advanced_tag_options.GetSelection()
|
|
|
|
if selection != wx.NOT_FOUND:
|
|
|
|
pretty_name = self._advanced_tag_options.GetString( selection )
|
|
|
|
( name, ato ) = self._advanced_tag_options.GetClientData( selection )
|
|
|
|
with ClientGUIDialogs.DialogInputAdvancedTagOptions( self, pretty_name, name, ato ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
ato = dlg.GetATO()
|
|
|
|
self._advanced_tag_options.SetClientData( selection, ( name, ato ) )
|
|
|
|
|
|
|
|
|
|
|
|
def EventEditNamespaceColour( self, event ):
|
|
|
|
result = self._namespace_colours.GetSelectedNamespaceColour()
|
|
|
|
if result is not None:
|
|
|
|
( namespace, colour ) = result
|
|
|
|
colour_data = wx.ColourData()
|
|
|
|
colour_data.SetColour( colour )
|
|
colour_data.SetChooseFull( True )
|
|
|
|
with wx.ColourDialog( self, data = colour_data ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
colour_data = dlg.GetColourData()
|
|
|
|
colour = colour_data.GetColour()
|
|
|
|
self._namespace_colours.SetNamespaceColour( namespace, colour )
|
|
|
|
|
|
|
|
|
|
|
|
def EventFileSystemPredicateMime( self, event ):
|
|
|
|
media = self._file_system_predicate_mime_media.GetStringSelection()
|
|
|
|
self._file_system_predicate_mime_type.Clear()
|
|
|
|
if media == 'image':
|
|
|
|
self._file_system_predicate_mime_type.Append( 'any', HC.IMAGES )
|
|
self._file_system_predicate_mime_type.Append( 'gif', HC.IMAGE_GIF )
|
|
self._file_system_predicate_mime_type.Append( 'jpeg', HC.IMAGE_JPEG )
|
|
self._file_system_predicate_mime_type.Append( 'png', HC.IMAGE_PNG )
|
|
|
|
elif media == 'application':
|
|
|
|
self._file_system_predicate_mime_type.Append( 'any', HC.APPLICATIONS )
|
|
self._file_system_predicate_mime_type.Append( 'pdf', HC.APPLICATION_PDF )
|
|
self._file_system_predicate_mime_type.Append( 'x-shockwave-flash', HC.APPLICATION_FLASH )
|
|
|
|
elif media == 'audio':
|
|
|
|
self._file_system_predicate_mime_type.Append( 'any', HC.AUDIO )
|
|
self._file_system_predicate_mime_type.Append( 'flac', HC.AUDIO_FLAC )
|
|
self._file_system_predicate_mime_type.Append( 'mp3', HC.AUDIO_MP3 )
|
|
self._file_system_predicate_mime_type.Append( 'ogg', HC.AUDIO_OGG )
|
|
self._file_system_predicate_mime_type.Append( 'x-ms-wma', HC.AUDIO_WMA )
|
|
|
|
elif media == 'video':
|
|
|
|
self._file_system_predicate_mime_type.Append( 'any', HC.VIDEO )
|
|
self._file_system_predicate_mime_type.Append( 'mp4', HC.VIDEO_MP4 )
|
|
self._file_system_predicate_mime_type.Append( 'webm', HC.VIDEO_WEBM )
|
|
self._file_system_predicate_mime_type.Append( 'x-matroska', HC.VIDEO_MKV )
|
|
self._file_system_predicate_mime_type.Append( 'x-ms-wmv', HC.VIDEO_WMV )
|
|
self._file_system_predicate_mime_type.Append( 'x-flv', HC.VIDEO_FLV )
|
|
|
|
|
|
self._file_system_predicate_mime_type.SetSelection( 0 )
|
|
|
|
|
|
def EventFullscreensUpdate( self, event ):
|
|
|
|
( width, height ) = wx.GetDisplaySize()
|
|
|
|
estimated_bytes_per_fullscreen = 3 * width * height
|
|
|
|
self._estimated_number_fullscreens.SetLabel( '(about ' + HC.ConvertIntToPrettyString( ( self._fullscreen_cache_size.GetValue() * 1048576 ) / estimated_bytes_per_fullscreen ) + '-' + HC.ConvertIntToPrettyString( ( self._fullscreen_cache_size.GetValue() * 1048576 ) / ( estimated_bytes_per_fullscreen / 4 ) ) + ' images)' )
|
|
|
|
|
|
def EventKeyDownNamespace( self, event ):
|
|
|
|
if event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
|
|
|
namespace = self._new_namespace_colour.GetValue()
|
|
|
|
if namespace != '':
|
|
|
|
self._namespace_colours.SetNamespaceColour( namespace, wx.Colour( random.randint( 0, 255 ), random.randint( 0, 255 ), random.randint( 0, 255 ) ) )
|
|
|
|
self._new_namespace_colour.SetValue( '' )
|
|
|
|
|
|
else: event.Skip()
|
|
|
|
|
|
def EventKeyDownSortBy( self, event ):
|
|
|
|
if event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
|
|
|
sort_by_string = self._new_sort_by.GetValue()
|
|
|
|
if sort_by_string != '':
|
|
|
|
try: sort_by = sort_by_string.split( '-' )
|
|
except:
|
|
|
|
wx.MessageBox( 'Could not parse that sort by string!' )
|
|
|
|
return
|
|
|
|
|
|
self._sort_by.Append( sort_by_string, sort_by )
|
|
|
|
self._new_sort_by.SetValue( '' )
|
|
|
|
|
|
else: event.Skip()
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
HC.options[ 'play_dumper_noises' ] = self._play_dumper_noises.GetValue()
|
|
|
|
HC.options[ 'default_gui_session' ] = self._default_gui_session.GetStringSelection()
|
|
HC.options[ 'confirm_client_exit' ] = self._confirm_client_exit.GetValue()
|
|
HC.options[ 'gui_capitalisation' ] = self._gui_capitalisation.GetValue()
|
|
HC.options[ 'show_all_tags_in_autocomplete' ] = self._gui_show_all_tags_in_autocomplete.GetValue()
|
|
|
|
HC.options[ 'export_path' ] = HC.ConvertAbsPathToPortablePath( self._export_location.GetPath() )
|
|
HC.options[ 'default_sort' ] = self._default_sort.GetSelection()
|
|
HC.options[ 'default_collect' ] = self._default_collect.GetChoice()
|
|
|
|
HC.options[ 'exclude_deleted_files' ] = self._exclude_deleted_files.GetValue()
|
|
|
|
HC.options[ 'thumbnail_cache_size' ] = self._thumbnail_cache_size.GetValue() * 1048576
|
|
HC.options[ 'preview_cache_size' ] = self._preview_cache_size.GetValue() * 1048576
|
|
HC.options[ 'fullscreen_cache_size' ] = self._fullscreen_cache_size.GetValue() * 1048576
|
|
|
|
HC.options[ 'idle_period' ] = 60 * self._maintenance_idle_period.GetValue()
|
|
HC.options[ 'maintenance_delete_orphans_period' ] = 86400 * self._maintenance_delete_orphans_period.GetValue()
|
|
HC.options[ 'maintenance_vacuum_period' ] = 86400 * self._maintenance_vacuum_period.GetValue()
|
|
|
|
new_thumbnail_dimensions = [ self._thumbnail_width.GetValue(), self._thumbnail_height.GetValue() ]
|
|
|
|
if new_thumbnail_dimensions != HC.options[ 'thumbnail_dimensions' ]:
|
|
|
|
text = 'You have changed the thumbnail dimensions, which will mean deleting all the old resized thumbnails right now, during which time the database will be locked. If you have tens or hundreds of thousands of files, this could take a long time.'
|
|
text += os.linesep * 2
|
|
text += 'Are you sure you want to change your thumbnail dimensions?'
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, text ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_YES: HC.options[ 'thumbnail_dimensions' ] = new_thumbnail_dimensions
|
|
|
|
|
|
|
|
HC.options[ 'num_autocomplete_chars' ] = self._num_autocomplete_chars.GetValue()
|
|
|
|
long_wait = self._autocomplete_long_wait.GetValue()
|
|
|
|
char_limit = self._autocomplete_short_wait_chars.GetValue()
|
|
|
|
short_wait = self._autocomplete_short_wait.GetValue()
|
|
|
|
HC.options[ 'ac_timings' ] = ( char_limit, long_wait, short_wait )
|
|
|
|
HC.options[ 'namespace_colours' ] = self._namespace_colours.GetNamespaceColours()
|
|
|
|
sort_by_choices = []
|
|
|
|
for sort_by in [ self._sort_by.GetClientData( i ) for i in range( self._sort_by.GetCount() ) ]: sort_by_choices.append( ( 'namespaces', sort_by ) )
|
|
|
|
HC.options[ 'sort_by' ] = sort_by_choices
|
|
|
|
system_predicates = {}
|
|
|
|
system_predicates[ 'age' ] = ( self._file_system_predicate_age_sign.GetSelection(), self._file_system_predicate_age_years.GetValue(), self._file_system_predicate_age_months.GetValue(), self._file_system_predicate_age_days.GetValue() )
|
|
system_predicates[ 'duration' ] = ( self._file_system_predicate_duration_sign.GetSelection(), self._file_system_predicate_duration_s.GetValue(), self._file_system_predicate_duration_ms.GetValue() )
|
|
system_predicates[ 'height' ] = ( self._file_system_predicate_height_sign.GetSelection(), self._file_system_predicate_height.GetValue() )
|
|
system_predicates[ 'limit' ] = self._file_system_predicate_limit.GetValue()
|
|
system_predicates[ 'mime' ] = ( self._file_system_predicate_mime_media.GetSelection(), self._file_system_predicate_mime_type.GetSelection() )
|
|
system_predicates[ 'num_tags' ] = ( self._file_system_predicate_num_tags_sign.GetSelection(), self._file_system_predicate_num_tags.GetValue() )
|
|
system_predicates[ 'local_rating_like' ] = self._file_system_predicate_local_rating_like_value.GetSelection()
|
|
system_predicates[ 'local_rating_numerical' ] = ( self._file_system_predicate_local_rating_numerical_sign.GetSelection(), self._file_system_predicate_local_rating_numerical_value.GetValue() )
|
|
system_predicates[ 'ratio' ] = ( self._file_system_predicate_ratio_sign.GetSelection(), self._file_system_predicate_ratio_width.GetValue(), self._file_system_predicate_ratio_height.GetValue() )
|
|
system_predicates[ 'size' ] = ( self._file_system_predicate_size_sign.GetSelection(), self._file_system_predicate_size.GetValue(), self._file_system_predicate_size_unit.GetSelection() )
|
|
system_predicates[ 'width' ] = ( self._file_system_predicate_width_sign.GetSelection(), self._file_system_predicate_width.GetValue() )
|
|
system_predicates[ 'num_words' ] = ( self._file_system_predicate_num_words_sign.GetSelection(), self._file_system_predicate_num_words.GetValue() )
|
|
|
|
HC.options[ 'file_system_predicates' ] = system_predicates
|
|
|
|
default_advanced_tag_options = {}
|
|
|
|
for ( name, ato ) in [ self._advanced_tag_options.GetClientData( i ) for i in range( self._advanced_tag_options.GetCount() ) ]:
|
|
|
|
default_advanced_tag_options[ name ] = ato
|
|
|
|
|
|
HC.options[ 'default_advanced_tag_options' ] = default_advanced_tag_options
|
|
|
|
shortcuts = {}
|
|
|
|
shortcuts[ wx.ACCEL_NORMAL ] = {}
|
|
shortcuts[ wx.ACCEL_CTRL ] = {}
|
|
shortcuts[ wx.ACCEL_ALT ] = {}
|
|
shortcuts[ wx.ACCEL_SHIFT ] = {}
|
|
|
|
for ( modifier, key, action ) in self._shortcuts.GetClientData(): shortcuts[ modifier ][ key ] = action
|
|
|
|
HC.options[ 'shortcuts' ] = shortcuts
|
|
|
|
HC.options[ 'default_tag_repository' ] = self._default_tag_repository.GetChoice()
|
|
HC.options[ 'default_tag_sort' ] = self._default_tag_sort.GetClientData( self._default_tag_sort.GetSelection() )
|
|
|
|
new_local_port = self._local_port.GetValue()
|
|
|
|
if new_local_port != HC.options[ 'local_port' ]: HC.pubsub.pub( 'restart_server' )
|
|
|
|
HC.options[ 'local_port' ] = new_local_port
|
|
|
|
HC.options[ 'thread_checker_timings' ] = ( self._thread_times_to_check.GetValue(), self._thread_check_period.GetValue() )
|
|
|
|
try: HC.app.Write( 'save_options' )
|
|
except: wx.MessageBox( traceback.format_exc() )
|
|
|
|
self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventRemoveSortBy( self, event ):
|
|
|
|
selection = self._sort_by.GetSelection()
|
|
|
|
if selection != wx.NOT_FOUND: self._sort_by.Delete( selection )
|
|
|
|
|
|
def EventPreviewsUpdate( self, event ):
|
|
|
|
estimated_bytes_per_preview = 3 * 400 * 400
|
|
|
|
self._estimated_number_previews.SetLabel( '(about ' + HC.ConvertIntToPrettyString( ( self._preview_cache_size.GetValue() * 1048576 ) / estimated_bytes_per_preview ) + ' previews)' )
|
|
|
|
|
|
def EventShortcutsAdd( self, event ):
|
|
|
|
with ClientGUIDialogs.DialogInputShortcut( self ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
( modifier, key, action ) = dlg.GetInfo()
|
|
|
|
( pretty_modifier, pretty_key, pretty_action ) = HC.ConvertShortcutToPrettyShortcut( modifier, key, action )
|
|
|
|
self._shortcuts.Append( ( pretty_modifier, pretty_key, pretty_action ), ( modifier, key, action ) )
|
|
|
|
self._SortListCtrl()
|
|
|
|
|
|
|
|
|
|
def EventShortcutsDelete( self, event ): self._shortcuts.RemoveAllSelected()
|
|
|
|
def EventShortcutsEdit( self, event ):
|
|
|
|
indices = self._shortcuts.GetAllSelected()
|
|
|
|
for index in indices:
|
|
|
|
( modifier, key, action ) = self._shortcuts.GetClientData( index )
|
|
|
|
with ClientGUIDialogs.DialogInputShortcut( self, modifier, key, action ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
( modifier, key, action ) = dlg.GetInfo()
|
|
|
|
( pretty_modifier, pretty_key, pretty_action ) = HC.ConvertShortcutToPrettyShortcut( modifier, key, action )
|
|
|
|
self._shortcuts.UpdateRow( index, ( pretty_modifier, pretty_key, pretty_action ), ( modifier, key, action ) )
|
|
|
|
self._SortListCtrl()
|
|
|
|
|
|
|
|
|
|
|
|
def EventThumbnailsUpdate( self, event ):
|
|
|
|
estimated_bytes_per_thumb = 3 * self._thumbnail_height.GetValue() * self._thumbnail_width.GetValue()
|
|
|
|
self._estimated_number_thumbnails.SetLabel( '(about ' + HC.ConvertIntToPrettyString( ( self._thumbnail_cache_size.GetValue() * 1048576 ) / estimated_bytes_per_thumb ) + ' thumbnails)' )
|
|
|
|
|
|
class DialogManagePixivAccount( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._id = wx.TextCtrl( self )
|
|
self._password = wx.TextCtrl( self )
|
|
|
|
self._status = wx.StaticText( self )
|
|
|
|
self._test = wx.Button( self, label = 'test' )
|
|
self._test.Bind( wx.EVT_BUTTON, self.EventTest )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
( id, password ) = HC.app.Read( 'pixiv_account' )
|
|
|
|
self._id.SetValue( id )
|
|
self._password.SetValue( password )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
gridbox.AddF( wx.StaticText( self, label = 'id/email' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._id, FLAGS_EXPAND_BOTH_WAYS )
|
|
gridbox.AddF( wx.StaticText( self, label = 'password' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._password, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
b_box = wx.BoxSizer( wx.HORIZONTAL )
|
|
b_box.AddF( self._ok, FLAGS_MIXED )
|
|
b_box.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( gridbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
vbox.AddF( self._status, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._test, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( b_box, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
x = max( x, 240 )
|
|
|
|
self.SetInitialSize( ( x, y ) )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage pixiv account' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
id = self._id.GetValue()
|
|
password = self._password.GetValue()
|
|
|
|
HC.app.Write( 'pixiv_account', ( id, password ) )
|
|
|
|
self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventTest( self, event ):
|
|
|
|
id = self._id.GetValue()
|
|
password = self._password.GetValue()
|
|
|
|
form_fields = {}
|
|
|
|
form_fields[ 'mode' ] = 'login'
|
|
form_fields[ 'pixiv_id' ] = id
|
|
form_fields[ 'pass' ] = password
|
|
|
|
body = urllib.urlencode( form_fields )
|
|
|
|
headers = {}
|
|
headers[ 'Content-Type' ] = 'application/x-www-form-urlencoded'
|
|
|
|
( response_gumpf, cookies ) = HC.http.Request( HC.POST, 'http://www.pixiv.net/login.php', request_headers = headers, body = body, return_cookies = True )
|
|
|
|
# _ only given to logged in php sessions
|
|
if 'PHPSESSID' in cookies and '_' in cookies[ 'PHPSESSID' ]: self._status.SetLabel( 'OK!' )
|
|
else: self._status.SetLabel( 'Did not work!' )
|
|
|
|
wx.CallLater( 2000, self._status.SetLabel, '' )
|
|
|
|
|
|
class DialogManageRatings( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent, media ):
|
|
|
|
def InitialiseControls():
|
|
|
|
services = HC.app.GetManager( 'services' ).GetServices( HC.RATINGS_SERVICES )
|
|
|
|
# sort according to local/remote, I guess
|
|
# and maybe sub-sort according to name?
|
|
# maybe just do two get service_key queries
|
|
|
|
self._panels = []
|
|
|
|
for service in services: self._panels.append( self._Panel( self, service.GetServiceKey(), media ) )
|
|
|
|
self._apply = wx.Button( self, id = wx.ID_OK, label = 'apply' )
|
|
self._apply.Bind( wx.EVT_BUTTON, self.EventOK )
|
|
self._apply.SetForegroundColour( ( 0, 128, 0 ) )
|
|
|
|
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
|
|
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
pass
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
buttonbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
buttonbox.AddF( self._apply, FLAGS_MIXED )
|
|
buttonbox.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
for panel in self._panels: vbox.AddF( panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( buttonbox, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( x + 200, y ) )
|
|
|
|
|
|
self._hashes = set()
|
|
|
|
for m in media: self._hashes.update( m.GetHashes() )
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage ratings for ' + HC.ConvertIntToPrettyString( len( self._hashes ) ) + ' files' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
self.Bind( wx.EVT_MENU, self.EventMenu )
|
|
|
|
self.RefreshAcceleratorTable()
|
|
|
|
|
|
def EventMenu( self, event ):
|
|
|
|
action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
|
|
|
|
if action is not None:
|
|
|
|
( command, data ) = action
|
|
|
|
if command == 'manage_ratings': self.EndModal( wx.ID_CANCEL )
|
|
elif command == 'ok': self.EventOK( event )
|
|
else: event.Skip()
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
try:
|
|
|
|
service_keys_to_content_updates = {}
|
|
|
|
for panel in self._panels:
|
|
|
|
if panel.HasChanges():
|
|
|
|
( service_key, content_updates ) = panel.GetContentUpdates()
|
|
|
|
service_keys_to_content_updates[ service_key ] = content_updates
|
|
|
|
|
|
|
|
HC.app.Write( 'content_updates', service_keys_to_content_updates )
|
|
|
|
finally: self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def RefreshAcceleratorTable( self ):
|
|
|
|
interested_actions = [ 'manage_ratings' ]
|
|
|
|
entries = []
|
|
|
|
for ( modifier, key_dict ) in HC.options[ 'shortcuts' ].items(): entries.extend( [ ( modifier, key, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) for ( key, action ) in key_dict.items() if action in interested_actions ] )
|
|
|
|
self.SetAcceleratorTable( wx.AcceleratorTable( entries ) )
|
|
|
|
|
|
class _Panel( wx.Panel ):
|
|
|
|
def __init__( self, parent, service_key, media ):
|
|
|
|
wx.Panel.__init__( self, parent )
|
|
|
|
self._service_key = service_key
|
|
self._service = HC.app.GetManager( 'services' ).GetService( service_key )
|
|
|
|
self._media = media
|
|
|
|
service_type = self._service.GetServiceType()
|
|
|
|
def InitialiseControls():
|
|
|
|
self._ratings_panel = ClientGUICommon.StaticBox( self, self._service.GetName() )
|
|
|
|
self._current_score = wx.StaticText( self._ratings_panel, style = wx.ALIGN_CENTER )
|
|
|
|
score_font = self._GetScoreFont()
|
|
|
|
self._current_score.SetFont( score_font )
|
|
|
|
if service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ): all_rating_services = [ local_ratings for ( local_ratings, remote_ratings ) in [ media.GetRatings() for media in self._media ] ]
|
|
elif service_type in ( HC.RATING_LIKE_REPOSITORY, HC.RATING_NUMERICAL_REPOSITORY ): all_rating_services = [ remote_ratings for ( local_ratings, remote_ratings ) in [ media.GetRatings() for media in self._media ] ]
|
|
|
|
if service_type in ( HC.LOCAL_RATING_LIKE, HC.RATING_LIKE_REPOSITORY ):
|
|
|
|
( like, dislike ) = self._service.GetLikeDislike()
|
|
|
|
if service_type == HC.LOCAL_RATING_LIKE:
|
|
|
|
ratings = [ rating_services.GetRating( self._service_key ) for rating_services in all_rating_services ]
|
|
|
|
if all( ( i is None for i in ratings ) ):
|
|
|
|
choices = [ like, dislike, 'make no changes' ]
|
|
|
|
if len( self._media ) > 1: self._current_score.SetLabel( 'none rated' )
|
|
else: self._current_score.SetLabel( 'not rated' )
|
|
|
|
elif None in ratings:
|
|
|
|
choices = [ like, dislike, 'remove rating', 'make no changes' ]
|
|
|
|
self._current_score.SetLabel( 'not all rated' )
|
|
|
|
else:
|
|
|
|
if all( ( i == 1 for i in ratings ) ):
|
|
|
|
choices = [ dislike, 'remove rating', 'make no changes' ]
|
|
|
|
if len( self._media ) > 1: self._current_score.SetLabel( 'all ' + like )
|
|
else: self._current_score.SetLabel( like )
|
|
|
|
elif all( ( i == 0 for i in ratings ) ):
|
|
|
|
choices = [ like, 'remove rating', 'make no changes' ]
|
|
|
|
if len( self._media ) > 1: self._current_score.SetLabel( 'all ' + dislike )
|
|
else: self._current_score.SetLabel( dislike )
|
|
|
|
else:
|
|
|
|
choices = [ like, dislike, 'remove rating', 'make no changes' ]
|
|
|
|
|
|
overall_rating = float( sum( ratings ) ) / float( len( ratings ) )
|
|
|
|
self._current_score.SetLabel( HC.u( '%.2f' % overall_rating ) )
|
|
|
|
|
|
if len( self._media ) > 1:
|
|
|
|
ratings_counter = collections.Counter( ratings )
|
|
|
|
likes = ratings_counter[ 1 ]
|
|
dislikes = ratings_counter[ 0 ]
|
|
nones = ratings_counter[ None ]
|
|
|
|
scores = []
|
|
|
|
if likes > 0: scores.append( HC.u( likes ) + ' likes' )
|
|
if dislikes > 0: scores.append( HC.u( dislikes ) + ' dislikes' )
|
|
if nones > 0: scores.append( HC.u( nones ) + ' not rated' )
|
|
|
|
self._current_score.SetLabel( ', '.join( scores ) )
|
|
|
|
else:
|
|
|
|
( rating, ) = ratings
|
|
|
|
if rating is None: self._current_score.SetLabel( 'not rated' )
|
|
elif rating == 1: self._current_score.SetLabel( like )
|
|
elif rating == 0: self._current_score.SetLabel( dislike )
|
|
|
|
|
|
else:
|
|
|
|
self._current_score.SetLabel( '23 ' + like + 's, 44 ' + dislike + 's' )
|
|
|
|
|
|
elif service_type in ( HC.LOCAL_RATING_NUMERICAL, HC.RATING_NUMERICAL_REPOSITORY ):
|
|
|
|
if service_type == HC.LOCAL_RATING_NUMERICAL:
|
|
|
|
( min, max ) = self._service.GetLowerUpper()
|
|
|
|
self._slider = wx.Slider( self._ratings_panel, minValue = min, maxValue = max, style = wx.SL_AUTOTICKS | wx.SL_LABELS )
|
|
self._slider.Bind( wx.EVT_SLIDER, self.EventSlider )
|
|
|
|
ratings = [ rating_services.GetRating( self._service_key ) for rating_services in all_rating_services ]
|
|
|
|
if all( ( i is None for i in ratings ) ):
|
|
|
|
choices = [ 'set rating', 'make no changes' ]
|
|
|
|
if len( self._media ) > 1: self._current_score.SetLabel( 'none rated' )
|
|
else: self._current_score.SetLabel( 'not rated' )
|
|
|
|
elif None in ratings:
|
|
|
|
choices = [ 'set rating', 'remove rating', 'make no changes' ]
|
|
|
|
if len( self._media ) > 1: self._current_score.SetLabel( 'not all rated' )
|
|
else: self._current_score.SetLabel( 'not rated' )
|
|
|
|
else:
|
|
|
|
# you know what? this should really be a bargraph or something!
|
|
# *
|
|
# *
|
|
# *
|
|
# * *
|
|
# * * * *
|
|
# None 0 1 2 3 4 5
|
|
# but we can't rely on integers, so just think about it
|
|
# some kind of sense of distribution would be helpful though
|
|
|
|
choices = [ 'set rating', 'remove rating', 'make no changes' ]
|
|
|
|
overall_rating = float( sum( ratings ) ) / float( len( ratings ) )
|
|
|
|
overall_rating_converted = ( overall_rating * ( max - min ) ) + min
|
|
|
|
self._slider.SetValue( int( overall_rating_converted + 0.5 ) )
|
|
|
|
str_overall_rating = HC.u( '%.2f' % overall_rating_converted )
|
|
|
|
if min in ( 0, 1 ): str_overall_rating += '/' + HC.u( '%.2f' % max )
|
|
|
|
self._current_score.SetLabel( str_overall_rating )
|
|
|
|
|
|
else:
|
|
|
|
self._current_score.SetLabel( '3.82/5' )
|
|
|
|
|
|
|
|
if service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ):
|
|
|
|
initial_index = choices.index( 'make no changes' )
|
|
|
|
choice_pairs = [ ( choice, choice ) for choice in choices ]
|
|
|
|
self._choices = ClientGUICommon.RadioBox( self._ratings_panel, 'actions', choice_pairs, initial_index )
|
|
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
pass
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
if service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ): label = 'local rating'
|
|
elif service_type in ( HC.RATING_LIKE_REPOSITORY, HC.RATING_NUMERICAL_REPOSITORY ): label = 'remote rating'
|
|
|
|
self._ratings_panel.AddF( self._current_score, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
if service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ):
|
|
|
|
if service_type == HC.LOCAL_RATING_LIKE:
|
|
|
|
self._ratings_panel.AddF( self._choices, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
elif service_type == HC.LOCAL_RATING_NUMERICAL:
|
|
|
|
self._ratings_panel.AddF( self._slider, FLAGS_EXPAND_PERPENDICULAR )
|
|
self._ratings_panel.AddF( self._choices, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._ratings_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
|
|
def _GetScoreFont( self ):
|
|
|
|
normal_font = wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT )
|
|
|
|
normal_font_size = normal_font.GetPointSize()
|
|
normal_font_family = normal_font.GetFamily()
|
|
|
|
return wx.Font( normal_font_size * 2, normal_font_family, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD )
|
|
|
|
|
|
def EventSlider( self, event ):
|
|
|
|
rating = self._slider.GetValue()
|
|
|
|
self._choices.SetSelection( 0 )
|
|
|
|
self._choices.SetString( 0, 'set rating to ' + HC.u( rating ) )
|
|
|
|
event.Skip()
|
|
|
|
|
|
def GetContentUpdates( self ):
|
|
|
|
service_type = self._service.GetServiceType()
|
|
|
|
choice_text = self._choices.GetSelectedClientData()
|
|
|
|
if choice_text == 'remove rating': rating = None
|
|
else:
|
|
|
|
if service_type == HC.LOCAL_RATING_LIKE:
|
|
|
|
( like, dislike ) = self._service.GetLikeDislike()
|
|
|
|
if choice_text == like: rating = 1
|
|
elif choice_text == dislike: rating = 0
|
|
|
|
elif service_type == HC.LOCAL_RATING_NUMERICAL: rating = float( self._slider.GetValue() - self._slider.GetMin() ) / float( self._slider.GetMax() - self._slider.GetMin() )
|
|
|
|
|
|
hashes = { hash for hash in itertools.chain.from_iterable( ( media.GetHashes() for media in self._media ) ) }
|
|
|
|
content_update = HC.ContentUpdate( HC.CONTENT_DATA_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, hashes ) )
|
|
|
|
return ( self._service_key, [ content_update ] )
|
|
|
|
|
|
def HasChanges( self ):
|
|
|
|
choice_text = self._choices.GetSelectedClientData()
|
|
|
|
if choice_text == 'make no changes': return False
|
|
else: return True
|
|
|
|
|
|
def GetServiceKey( self ): return self._service_key
|
|
|
|
|
|
class DialogManageServer( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent, service_key ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._edit_log = []
|
|
|
|
self._services_listbook = ClientGUICommon.ListBook( self )
|
|
self._services_listbook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
|
|
self._services_listbook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
|
|
|
|
self._service_types = wx.Choice( self )
|
|
|
|
self._add = wx.Button( self, label = 'add' )
|
|
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
|
|
self._add.SetForegroundColour( ( 0, 128, 0 ) )
|
|
|
|
self._remove = wx.Button( self, label = 'remove' )
|
|
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
|
|
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
for service_type in [ HC.TAG_REPOSITORY, HC.FILE_REPOSITORY, HC.MESSAGE_DEPOT ]: self._service_types.Append( HC.service_string_lookup[ service_type ], service_type )
|
|
|
|
self._service_types.SetSelection( 0 )
|
|
|
|
response = self._service.Request( HC.GET, 'services_info' )
|
|
|
|
self._services_info = response[ 'services_info' ]
|
|
|
|
for ( service_key, service_type, options ) in self._services_info:
|
|
|
|
page = self._Panel( self._services_listbook, service_key, service_type, options )
|
|
|
|
name = HC.service_string_lookup[ service_type ] + '@' + HC.u( options[ 'port' ] )
|
|
|
|
self._services_listbook.AddPage( page, name )
|
|
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
b_box = wx.BoxSizer( wx.HORIZONTAL )
|
|
b_box.AddF( self._ok, FLAGS_MIXED )
|
|
b_box.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
add_remove_hbox.AddF( self._service_types, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._add, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._remove, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
vbox.AddF( self._services_listbook, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( add_remove_hbox, FLAGS_SMALL_INDENT )
|
|
vbox.AddF( b_box, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
if y < 400: y = 400 # listbook's setsize ( -1, 400 ) is buggy
|
|
|
|
self.SetInitialSize( ( 680, y ) )
|
|
|
|
|
|
self._service = HC.app.GetManager( 'services' ).GetService( service_key )
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage ' + self._service.GetName() + ' services' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
self.EventServiceChanged( None )
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def _CheckCurrentServiceIsValid( self ):
|
|
|
|
service_panel = self._services_listbook.GetCurrentPage()
|
|
|
|
if service_panel is not None:
|
|
|
|
( service_key, service_type, options ) = service_panel.GetInfo()
|
|
|
|
for ( existing_service_key, existing_service_type, existing_options ) in [ page.GetInfo() for page in self._services_listbook.GetNameToPageDict().values() if page != service_panel ]:
|
|
|
|
if options[ 'port' ] == existing_options[ 'port' ]: raise Exception( 'That port is already in use!' )
|
|
|
|
|
|
name = self._services_listbook.GetCurrentName()
|
|
|
|
new_name = HC.service_string_lookup[ service_type ] + '@' + HC.u( options[ 'port' ] )
|
|
|
|
if name != new_name: self._services_listbook.RenamePage( name, new_name )
|
|
|
|
|
|
|
|
def EventAdd( self, event ):
|
|
|
|
self._CheckCurrentServiceIsValid()
|
|
|
|
service_key = os.urandom( 32 )
|
|
|
|
service_type = self._service_types.GetClientData( self._service_types.GetSelection() )
|
|
|
|
port = HC.DEFAULT_SERVICE_PORT
|
|
|
|
existing_ports = set()
|
|
|
|
for ( existing_service_key, existing_service_type, existing_options ) in [ page.GetInfo() for page in self._services_listbook.GetNameToPageDict().values() ]: existing_ports.add( existing_options[ 'port' ] )
|
|
|
|
while port in existing_ports: port += 1
|
|
|
|
options = dict( HC.DEFAULT_OPTIONS[ service_type ] )
|
|
|
|
options[ 'port' ] = port
|
|
|
|
self._edit_log.append( ( HC.ADD, ( service_key, service_type, options ) ) )
|
|
|
|
page = self._Panel( self._services_listbook, service_key, service_type, options )
|
|
|
|
name = HC.service_string_lookup[ service_type ] + '@' + HC.u( port )
|
|
|
|
self._services_listbook.AddPage( page, name, select = True )
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
try: self._CheckCurrentServiceIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
return
|
|
|
|
|
|
for ( name, page ) in self._services_listbook.GetNameToPageDict().items():
|
|
|
|
if page.HasChanges():
|
|
|
|
( service_key, service_type, options ) = page.GetInfo()
|
|
|
|
self._edit_log.append( ( HC.EDIT, ( service_key, service_type, options ) ) )
|
|
|
|
|
|
|
|
try:
|
|
|
|
if len( self._edit_log ) > 0:
|
|
|
|
response = self._service.Request( HC.POST, 'services', { 'edit_log' : self._edit_log } )
|
|
|
|
service_keys_to_access_keys = dict( response[ 'service_keys_to_access_keys' ] )
|
|
|
|
admin_service_key = self._service.GetServiceKey()
|
|
|
|
HC.app.Write( 'update_server_services', admin_service_key, self._services_info, self._edit_log, service_keys_to_access_keys )
|
|
|
|
|
|
finally: self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventRemove( self, event ):
|
|
|
|
service_panel = self._services_listbook.GetCurrentPage()
|
|
|
|
if service_panel is not None:
|
|
|
|
( service_key, service_type, options ) = service_panel.GetInfo()
|
|
|
|
self._edit_log.append( ( HC.DELETE, service_key ) )
|
|
|
|
self._services_listbook.DeleteCurrentPage()
|
|
|
|
|
|
|
|
def EventServiceChanged( self, event ):
|
|
|
|
page = self._services_listbook.GetCurrentPage()
|
|
|
|
( service_key, service_type, options ) = page.GetInfo()
|
|
|
|
if service_type == HC.SERVER_ADMIN: self._remove.Disable()
|
|
else: self._remove.Enable()
|
|
|
|
|
|
def EventServiceChanging( self, event ):
|
|
|
|
try: self._CheckCurrentServiceIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
event.Veto()
|
|
|
|
|
|
|
|
class _Panel( wx.Panel ):
|
|
|
|
def __init__( self, parent, service_key, service_type, options ):
|
|
|
|
wx.Panel.__init__( self, parent )
|
|
|
|
self._service_key = service_key
|
|
self._service_type = service_type
|
|
self._options = options
|
|
|
|
def InitialiseControls():
|
|
|
|
self._options_panel = ClientGUICommon.StaticBox( self, 'options' )
|
|
|
|
if 'port' in self._options: self._port = wx.SpinCtrl( self._options_panel, min = 1, max = 65535 )
|
|
if 'max_monthly_data' in self._options: self._max_monthly_data = ClientGUICommon.NoneableSpinCtrl( self._options_panel, 'max monthly data (MB)', multiplier = 1048576 )
|
|
if 'max_storage' in self._options: self._max_storage = ClientGUICommon.NoneableSpinCtrl( self._options_panel, 'max storage (MB)', multiplier = 1048576 )
|
|
if 'log_uploader_ips' in self._options: self._log_uploader_ips = wx.CheckBox( self._options_panel )
|
|
if 'message' in self._options: self._message = wx.TextCtrl( self._options_panel )
|
|
if 'upnp' in self._options: self._upnp = ClientGUICommon.NoneableSpinCtrl( self._options_panel, 'external port', none_phrase = 'do not forward port', max = 65535 )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
if 'port' in self._options: self._port.SetValue( self._options[ 'port' ] )
|
|
if 'max_monthly_data' in self._options: self._max_monthly_data.SetValue( self._options[ 'max_monthly_data' ] )
|
|
if 'max_storage' in self._options: self._max_storage.SetValue( self._options[ 'max_storage' ] )
|
|
if 'log_uploader_ips' in self._options: self._log_uploader_ips.SetValue( self._options[ 'log_uploader_ips' ] )
|
|
if 'message' in self._options: self._message.SetValue( self._options[ 'message' ] )
|
|
if 'upnp' in self._options: self._upnp.SetValue( self._options[ 'upnp' ] )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
if 'port' in self._options:
|
|
|
|
gridbox.AddF( wx.StaticText( self._options_panel, label = 'port' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._port, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
|
|
if 'max_monthly_data' in self._options:
|
|
|
|
gridbox.AddF( wx.StaticText( self._options_panel, label = 'max monthly data' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._max_monthly_data, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
|
|
if 'max_storage' in self._options:
|
|
|
|
gridbox.AddF( wx.StaticText( self._options_panel, label = 'max storage' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._max_storage, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
|
|
if 'log_uploader_ips' in self._options:
|
|
|
|
gridbox.AddF( wx.StaticText( self._options_panel, label = 'log uploader IPs' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._log_uploader_ips, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
|
|
if 'message' in self._options:
|
|
|
|
gridbox.AddF( wx.StaticText( self._options_panel, label = 'message' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._message, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
|
|
if 'upnp' in self._options:
|
|
|
|
gridbox.AddF( wx.StaticText( self._options_panel, label = 'UPnP' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._upnp, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
|
|
self._options_panel.AddF( gridbox, FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
|
|
|
vbox.AddF( self._options_panel, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
|
|
def GetInfo( self ):
|
|
|
|
options = {}
|
|
|
|
if 'port' in self._options: options[ 'port' ] = self._port.GetValue()
|
|
if 'max_monthly_data' in self._options: options[ 'max_monthly_data' ] = self._max_monthly_data.GetValue()
|
|
if 'max_storage' in self._options: options[ 'max_storage' ] = self._max_storage.GetValue()
|
|
if 'log_uploader_ips' in self._options: options[ 'log_uploader_ips' ] = self._log_uploader_ips.GetValue()
|
|
if 'message' in self._options: options[ 'message' ] = self._message.GetValue()
|
|
if 'upnp' in self._options: options[ 'upnp' ] = self._upnp.GetValue()
|
|
|
|
return ( self._service_key, self._service_type, options )
|
|
|
|
|
|
def HasChanges( self ):
|
|
|
|
( service_key, service_type, options ) = self.GetInfo()
|
|
|
|
if options != self._options: return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._edit_log = []
|
|
|
|
self._notebook = wx.Notebook( self )
|
|
self._notebook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
|
|
self._notebook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
|
|
self._notebook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventPageChanging, source = self._notebook )
|
|
|
|
self._local_listbook = ClientGUICommon.ListBook( self._notebook )
|
|
self._local_listbook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventPageChanging, source = self._local_listbook )
|
|
|
|
self._remote_listbook = ClientGUICommon.ListBook( self._notebook )
|
|
self._remote_listbook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventPageChanging, source = self._remote_listbook )
|
|
|
|
self._add = wx.Button( self, label = 'add' )
|
|
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
|
|
self._add.SetForegroundColour( ( 0, 128, 0 ) )
|
|
|
|
self._remove = wx.Button( self, label = 'remove' )
|
|
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
|
|
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
|
|
|
|
self._export = wx.Button( self, label = 'export' )
|
|
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
manageable_service_types = HC.RESTRICTED_SERVICES + [ HC.LOCAL_TAG, HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL, HC.LOCAL_BOORU ]
|
|
|
|
for service_type in manageable_service_types:
|
|
|
|
if service_type == HC.LOCAL_RATING_LIKE: name = 'like/dislike ratings'
|
|
elif service_type == HC.LOCAL_RATING_NUMERICAL: name = 'numerical ratings'
|
|
elif service_type == HC.LOCAL_BOORU: name = 'booru'
|
|
elif service_type == HC.LOCAL_TAG: name = 'local tags'
|
|
elif service_type == HC.TAG_REPOSITORY: name = 'tag repositories'
|
|
elif service_type == HC.FILE_REPOSITORY: name = 'file repositories'
|
|
#elif service_type == HC.MESSAGE_DEPOT: name = 'message repositories'
|
|
elif service_type == HC.SERVER_ADMIN: name = 'administrative services'
|
|
#elif service_type == HC.RATING_LIKE_REPOSITORY: name = 'like/dislike rating repositories'
|
|
#elif service_type == HC.RATING_NUMERICAL_REPOSITORY: name = 'numerical rating repositories'
|
|
else: continue
|
|
|
|
if service_type in HC.LOCAL_SERVICES: parent_listbook = self._local_listbook
|
|
else: parent_listbook = self._remote_listbook
|
|
|
|
listbook = ClientGUICommon.ListBook( parent_listbook )
|
|
listbook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
|
|
|
|
self._service_types_to_listbooks[ service_type ] = listbook
|
|
self._listbooks_to_service_types[ listbook ] = service_type
|
|
|
|
parent_listbook.AddPage( listbook, name )
|
|
|
|
services = HC.app.GetManager( 'services' ).GetServices( ( service_type, ) )
|
|
|
|
for service in services:
|
|
|
|
service_key = service.GetServiceKey()
|
|
name = service.GetName()
|
|
info = service.GetInfo()
|
|
|
|
page_info = ( self._Panel, ( listbook, service_key, service_type, name, info ), {} )
|
|
|
|
listbook.AddPage( page_info, name )
|
|
|
|
|
|
|
|
wx.CallAfter( self._local_listbook.Layout )
|
|
wx.CallAfter( self._remote_listbook.Layout )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
add_remove_hbox.AddF( self._add, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._remove, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._export, FLAGS_MIXED )
|
|
|
|
ok_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
ok_hbox.AddF( self._ok, FLAGS_MIXED )
|
|
ok_hbox.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
self._notebook.AddPage( self._local_listbook, 'local' )
|
|
self._notebook.AddPage( self._remote_listbook, 'remote' )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._notebook, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( add_remove_hbox, FLAGS_SMALL_INDENT )
|
|
vbox.AddF( ok_hbox, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage services' )
|
|
|
|
self._service_types_to_listbooks = {}
|
|
self._listbooks_to_service_types = {}
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( 880, y + 220 ) )
|
|
|
|
self.SetDropTarget( ClientGUICommon.FileDropTarget( self.Import ) )
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def _CheckCurrentServiceIsValid( self ):
|
|
|
|
local_or_remote_listbook = self._notebook.GetCurrentPage()
|
|
|
|
if local_or_remote_listbook is not None:
|
|
|
|
services_listbook = local_or_remote_listbook.GetCurrentPage()
|
|
|
|
if services_listbook is not None:
|
|
|
|
service_panel = services_listbook.GetCurrentPage()
|
|
|
|
if service_panel is not None:
|
|
|
|
( service_key, service_type, name, info ) = service_panel.GetInfo()
|
|
|
|
old_name = services_listbook.GetCurrentName()
|
|
|
|
if old_name is not None and name != old_name:
|
|
|
|
if services_listbook.NameExists( name ): raise Exception( 'That name is already in use!' )
|
|
|
|
services_listbook.RenamePage( old_name, name )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def EventAdd( self, event ):
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, 'Enter new service\'s name.' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
try:
|
|
|
|
name = dlg.GetValue()
|
|
|
|
local_or_remote_listbook = self._notebook.GetCurrentPage()
|
|
|
|
if local_or_remote_listbook is not None:
|
|
|
|
services_listbook = local_or_remote_listbook.GetCurrentPage()
|
|
|
|
if services_listbook.NameExists( name ): raise Exception( 'That name is already in use!' )
|
|
|
|
if name == '': raise Exception( 'Please enter a nickname for the service.' )
|
|
|
|
service_key = os.urandom( 32 )
|
|
service_type = self._listbooks_to_service_types[ services_listbook ]
|
|
|
|
info = {}
|
|
|
|
if service_type in HC.REMOTE_SERVICES:
|
|
|
|
if service_type == HC.SERVER_ADMIN: ( host, port ) = ( 'hostname', 45870 )
|
|
elif service_type in HC.RESTRICTED_SERVICES:
|
|
|
|
with ClientGUIDialogs.DialogChooseNewServiceMethod( self ) as dlg:
|
|
|
|
if dlg.ShowModal() != wx.ID_OK: return
|
|
|
|
register = dlg.GetRegister()
|
|
|
|
if register:
|
|
|
|
with ClientGUIDialogs.DialogRegisterService( self, service_type ) as dlg:
|
|
|
|
if dlg.ShowModal() != wx.ID_OK: return
|
|
|
|
credentials = dlg.GetCredentials()
|
|
|
|
( host, port ) = credentials.GetAddress()
|
|
|
|
if credentials.HasAccessKey(): info[ 'access_key' ] = credentials.GetAccessKey()
|
|
|
|
|
|
else: ( host, port ) = ( 'hostname', 45871 )
|
|
|
|
|
|
else: ( host, port ) = ( 'hostname', 45871 )
|
|
|
|
info[ 'host' ] = host
|
|
info[ 'port' ] = port
|
|
|
|
|
|
if service_type in HC.REPOSITORIES: info[ 'paused' ] = False
|
|
|
|
if service_type == HC.TAG_REPOSITORY: info[ 'tag_archive_sync' ] = {}
|
|
|
|
if service_type == HC.LOCAL_RATING_LIKE:
|
|
|
|
info[ 'like' ] = 'like'
|
|
info[ 'dislike' ] = 'dislike'
|
|
|
|
elif service_type == HC.LOCAL_RATING_NUMERICAL:
|
|
|
|
info[ 'lower' ] = 0
|
|
info[ 'upper' ] = 5
|
|
|
|
|
|
self._edit_log.append( HC.EditLogActionAdd( ( service_key, service_type, name, info ) ) )
|
|
|
|
page = self._Panel( services_listbook, service_key, service_type, name, info )
|
|
|
|
services_listbook.AddPage( page, name, select = True )
|
|
|
|
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( traceback.format_exc() )
|
|
|
|
self.EventAdd( event )
|
|
|
|
|
|
|
|
|
|
|
|
def EventExport( self, event ):
|
|
|
|
try: self._CheckCurrentServiceIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
return
|
|
|
|
|
|
local_or_remote_listbook = self._notebook.GetCurrentPage()
|
|
|
|
if local_or_remote_listbook is not None:
|
|
|
|
services_listbook = local_or_remote_listbook.GetCurrentPage()
|
|
|
|
if services_listbook is not None:
|
|
|
|
service_panel = services_listbook.GetCurrentPage()
|
|
|
|
( service_key, service_type, name, info ) = service_panel.GetInfo()
|
|
|
|
try:
|
|
|
|
with wx.FileDialog( self, 'select where to export service', defaultFile = name + '.yaml', style = wx.FD_SAVE ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
with open( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( ( service_key, service_type, name, info ) ) )
|
|
|
|
|
|
|
|
except:
|
|
|
|
with wx.FileDialog( self, 'select where to export service', defaultFile = 'service.yaml', style = wx.FD_SAVE ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
with open( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( ( service_key, service_type, name, info ) ) )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
try: self._CheckCurrentServiceIsValid()
|
|
except Exception as e:
|
|
|
|
HC.ShowException( e )
|
|
|
|
return
|
|
|
|
|
|
all_listbooks = self._service_types_to_listbooks.values()
|
|
|
|
for listbook in all_listbooks:
|
|
|
|
all_pages = listbook.GetNameToPageDict().values()
|
|
|
|
for page in all_pages:
|
|
|
|
if page.HasChanges():
|
|
|
|
( service_key, service_type, name, info ) = page.GetInfo()
|
|
|
|
self._edit_log.append( HC.EditLogActionEdit( service_key, ( service_key, service_type, name, info ) ) )
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
if len( self._edit_log ) > 0: HC.app.Write( 'update_services', self._edit_log )
|
|
|
|
finally: self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventPageChanging( self, event ):
|
|
|
|
try: self._CheckCurrentServiceIsValid()
|
|
except Exception as e:
|
|
|
|
HC.ShowException( e )
|
|
|
|
event.Veto()
|
|
|
|
|
|
|
|
def EventRemove( self, event ):
|
|
|
|
local_or_remote_listbook = self._notebook.GetCurrentPage()
|
|
|
|
if local_or_remote_listbook is not None:
|
|
|
|
services_listbook = local_or_remote_listbook.GetCurrentPage()
|
|
|
|
service_panel = services_listbook.GetCurrentPage()
|
|
|
|
if service_panel is not None:
|
|
|
|
( service_key, service_type, name, info ) = service_panel.GetInfo()
|
|
|
|
self._edit_log.append( HC.EditLogActionDelete( service_key ) )
|
|
|
|
services_listbook.DeleteCurrentPage()
|
|
|
|
|
|
|
|
|
|
def EventServiceChanged( self, event ):
|
|
|
|
local_or_remote_listbook = self._notebook.GetCurrentPage()
|
|
|
|
if local_or_remote_listbook is not None:
|
|
|
|
services_listbook = local_or_remote_listbook.GetCurrentPage()
|
|
|
|
service_type = self._listbooks_to_service_types[ services_listbook ]
|
|
|
|
if service_type in HC.NONEDITABLE_SERVICES:
|
|
|
|
self._add.Disable()
|
|
self._remove.Disable()
|
|
self._export.Disable()
|
|
|
|
else:
|
|
|
|
self._add.Enable()
|
|
self._remove.Enable()
|
|
self._export.Enable()
|
|
|
|
|
|
|
|
event.Skip()
|
|
|
|
|
|
def EventServiceChanging( self, event ):
|
|
|
|
try: self._CheckCurrentServiceIsValid()
|
|
except Exception as e:
|
|
|
|
HC.ShowException( e )
|
|
|
|
event.Veto()
|
|
|
|
|
|
|
|
def Import( self, paths ):
|
|
|
|
try: self._CheckCurrentServiceIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
return
|
|
|
|
|
|
for path in paths:
|
|
|
|
with open( path, 'rb' ) as f: file = f.read()
|
|
|
|
( service_key, service_type, name, info ) = yaml.safe_load( file )
|
|
|
|
services_listbook = self._service_types_to_listbooks[ service_type ]
|
|
|
|
if services_listbook.NameExists( name ):
|
|
|
|
message = 'A service already exists with that name. Overwrite it?'
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_YES:
|
|
|
|
page = services_listbook.GetNameToPageDict()[ name ]
|
|
|
|
page.Update( service_key, service_type, name, info )
|
|
|
|
|
|
|
|
else:
|
|
|
|
self._edit_log.append( HC.EditLogActionAdd( ( service_key, service_type, name, info ) ) )
|
|
|
|
page = self._Panel( services_listbook, service_key, service_type, name, info )
|
|
|
|
services_listbook.AddPage( page, name, select = True )
|
|
|
|
|
|
|
|
|
|
class _Panel( wx.Panel ):
|
|
|
|
def __init__( self, parent, service_key, service_type, name, info ):
|
|
|
|
wx.Panel.__init__( self, parent )
|
|
|
|
self._original_info = ( service_key, service_type, name, info )
|
|
|
|
def InitialiseControls():
|
|
|
|
if service_type not in HC.NONEDITABLE_SERVICES:
|
|
|
|
if service_type in HC.REMOTE_SERVICES: title = 'name and credentials'
|
|
else: title = 'name'
|
|
|
|
self._credentials_panel = ClientGUICommon.StaticBox( self, title )
|
|
|
|
self._service_name = wx.TextCtrl( self._credentials_panel )
|
|
|
|
if service_type in HC.REMOTE_SERVICES:
|
|
|
|
host = info[ 'host' ]
|
|
port = info[ 'port' ]
|
|
|
|
if 'access_key' in info: access_key = info[ 'access_key' ]
|
|
else: access_key = None
|
|
|
|
credentials = CC.Credentials( host, port, access_key )
|
|
|
|
self._service_credentials = wx.TextCtrl( self._credentials_panel, value = credentials.GetConnectionString() )
|
|
|
|
self._check_service = wx.Button( self._credentials_panel, label = 'test credentials' )
|
|
self._check_service.Bind( wx.EVT_BUTTON, self.EventCheckService )
|
|
|
|
|
|
|
|
if service_type in HC.REPOSITORIES:
|
|
|
|
self._repositories_panel = ClientGUICommon.StaticBox( self, 'repository synchronisation' )
|
|
|
|
self._pause_synchronisation = wx.CheckBox( self._repositories_panel, label = 'pause synchronisation' )
|
|
|
|
self._reset = wx.Button( self._repositories_panel, label = 'reset cache' )
|
|
self._reset.Bind( wx.EVT_BUTTON, self.EventServiceReset )
|
|
|
|
|
|
if service_type in HC.RATINGS_SERVICES:
|
|
|
|
self._local_rating_panel = ClientGUICommon.StaticBox( self, 'local rating configuration' )
|
|
|
|
if service_type == HC.LOCAL_RATING_LIKE:
|
|
|
|
like = info[ 'like' ]
|
|
dislike = info[ 'dislike' ]
|
|
|
|
self._like = wx.TextCtrl( self._local_rating_panel, value = like )
|
|
self._dislike = wx.TextCtrl( self._local_rating_panel, value = dislike )
|
|
|
|
elif service_type == HC.LOCAL_RATING_NUMERICAL:
|
|
|
|
lower = info[ 'lower' ]
|
|
upper = info[ 'upper' ]
|
|
|
|
self._lower = wx.SpinCtrl( self._local_rating_panel, min = -2000, max = 2000 )
|
|
self._lower.SetValue( lower )
|
|
self._upper = wx.SpinCtrl( self._local_rating_panel, min = -2000, max = 2000 )
|
|
self._upper.SetValue( upper )
|
|
|
|
|
|
|
|
if service_type in HC.TAG_SERVICES:
|
|
|
|
self._archive_info = HC.app.Read( 'tag_archive_info' )
|
|
|
|
self._archive_panel = ClientGUICommon.StaticBox( self, 'archive synchronisation' )
|
|
|
|
self._archive_sync = wx.ListBox( self._archive_panel, size = ( -1, 100 ) )
|
|
|
|
self._archive_sync_add = wx.Button( self._archive_panel, label = 'add' )
|
|
self._archive_sync_add.Bind( wx.EVT_BUTTON, self.EventArchiveAdd )
|
|
|
|
self._archive_sync_edit = wx.Button( self._archive_panel, label = 'edit' )
|
|
self._archive_sync_edit.Bind( wx.EVT_BUTTON, self.EventArchiveEdit )
|
|
|
|
self._archive_sync_remove = wx.Button( self._archive_panel, label = 'remove' )
|
|
self._archive_sync_remove.Bind( wx.EVT_BUTTON, self.EventArchiveRemove )
|
|
|
|
|
|
|
|
if service_type == HC.LOCAL_BOORU:
|
|
|
|
self._booru_options_panel = ClientGUICommon.StaticBox( self, 'options' )
|
|
|
|
self._port = wx.SpinCtrl( self._booru_options_panel, min = 0, max = 65535 )
|
|
|
|
self._upnp = ClientGUICommon.NoneableSpinCtrl( self._booru_options_panel, 'upnp port', none_phrase = 'do not forward port', max = 65535 )
|
|
|
|
self._max_monthly_data = ClientGUICommon.NoneableSpinCtrl( self._booru_options_panel, 'max monthly MB', multiplier = 1024 * 1024 )
|
|
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
if service_type not in HC.NONEDITABLE_SERVICES:
|
|
|
|
self._service_name.SetValue( name )
|
|
|
|
|
|
if service_type in HC.REPOSITORIES:
|
|
|
|
self._pause_synchronisation.SetValue( info[ 'paused' ] )
|
|
|
|
|
|
if service_type in HC.TAG_SERVICES:
|
|
|
|
for ( archive_name, namespaces ) in info[ 'tag_archive_sync' ].items():
|
|
|
|
name_to_display = self._GetArchiveNameToDisplay( archive_name, namespaces )
|
|
|
|
self._archive_sync.Append( name_to_display, ( archive_name, namespaces ) )
|
|
|
|
|
|
potential_archives = self._GetPotentialArchives()
|
|
|
|
if len( potential_archives ) == 0: self._archive_sync_add.Disable()
|
|
|
|
|
|
if service_type == HC.LOCAL_BOORU:
|
|
|
|
self._port.SetValue( info[ 'port' ] )
|
|
self._upnp.SetValue( info[ 'upnp' ] )
|
|
self._max_monthly_data.SetValue( info[ 'max_monthly_data' ] )
|
|
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
if service_type not in HC.NONEDITABLE_SERVICES:
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
gridbox.AddF( wx.StaticText( self._credentials_panel, label = 'name' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._service_name, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
if service_type in HC.REMOTE_SERVICES:
|
|
|
|
gridbox.AddF( wx.StaticText( self._credentials_panel, label = 'credentials' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._service_credentials, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
gridbox.AddF( ( 20, 20 ), FLAGS_MIXED )
|
|
gridbox.AddF( self._check_service, FLAGS_LONE_BUTTON )
|
|
|
|
|
|
self._credentials_panel.AddF( gridbox, FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
|
|
|
vbox.AddF( self._credentials_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
|
|
if service_type in HC.REPOSITORIES:
|
|
|
|
self._repositories_panel.AddF( self._pause_synchronisation, FLAGS_MIXED )
|
|
self._repositories_panel.AddF( self._reset, FLAGS_LONE_BUTTON )
|
|
|
|
vbox.AddF( self._repositories_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
|
|
if service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ):
|
|
|
|
gridbox = wx.FlexGridSizer( 0, 2 )
|
|
|
|
gridbox.AddGrowableCol( 1, 1 )
|
|
|
|
if service_type == HC.LOCAL_RATING_LIKE:
|
|
|
|
gridbox.AddF( wx.StaticText( self._local_rating_panel, label = 'like' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._like, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
gridbox.AddF( wx.StaticText( self._local_rating_panel, label = 'dislike' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._dislike, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
elif service_type == HC.LOCAL_RATING_NUMERICAL:
|
|
|
|
gridbox.AddF( wx.StaticText( self._local_rating_panel, label = 'lower limit' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._lower, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
gridbox.AddF( wx.StaticText( self._local_rating_panel, label = 'upper limit' ), FLAGS_MIXED )
|
|
gridbox.AddF( self._upper, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
|
|
self._local_rating_panel.AddF( gridbox, FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
|
|
|
vbox.AddF( self._local_rating_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
|
|
if service_type in HC.TAG_SERVICES:
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( self._archive_sync_add, FLAGS_MIXED )
|
|
hbox.AddF( self._archive_sync_edit, FLAGS_MIXED )
|
|
hbox.AddF( self._archive_sync_remove, FLAGS_MIXED )
|
|
|
|
self._archive_panel.AddF( self._archive_sync, FLAGS_EXPAND_BOTH_WAYS )
|
|
self._archive_panel.AddF( hbox, FLAGS_BUTTON_SIZER )
|
|
|
|
vbox.AddF( self._archive_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
|
|
if service_type == HC.LOCAL_BOORU:
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._booru_options_panel, label = 'port' ), FLAGS_MIXED )
|
|
hbox.AddF( self._port, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
self._booru_options_panel.AddF( hbox, FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
|
self._booru_options_panel.AddF( self._upnp, FLAGS_EXPAND_BOTH_WAYS )
|
|
self._booru_options_panel.AddF( self._max_monthly_data, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
vbox.AddF( self._booru_options_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
|
|
def _GetArchiveNameToDisplay( self, archive_name, namespaces ):
|
|
|
|
if len( namespaces ) == 0: name_to_display = archive_name
|
|
else: name_to_display = archive_name + ' (' + ', '.join( HC.ConvertUglyNamespacesToPrettyStrings( namespaces ) ) + ')'
|
|
|
|
return name_to_display
|
|
|
|
|
|
def _GetPotentialArchives( self ):
|
|
|
|
existing_syncs = set()
|
|
|
|
for i in range( self._archive_sync.GetCount() ):
|
|
|
|
( archive_name, namespaces ) = self._archive_sync.GetClientData( i )
|
|
|
|
existing_syncs.add( archive_name )
|
|
|
|
|
|
potential_archives = { archive_name for archive_name in self._archive_info.keys() if archive_name not in existing_syncs }
|
|
|
|
return potential_archives
|
|
|
|
|
|
def EventArchiveAdd( self, event ):
|
|
|
|
if self._archive_sync.GetCount() == 0:
|
|
|
|
wx.MessageBox( 'Be careful with this tool! Synching a lot of files to a large archive can take a very long time to initialise.' )
|
|
|
|
|
|
potential_archives = self._GetPotentialArchives()
|
|
|
|
if len( potential_archives ) == 1:
|
|
|
|
( archive_name, ) = potential_archives
|
|
|
|
wx.MessageBox( 'There is only one tag archive, ' + archive_name + ', to select, so I am selecting it for you.' )
|
|
|
|
else:
|
|
|
|
with ClientGUIDialogs.DialogSelectFromListOfStrings( self, 'Select the tag archive to add', potential_archives ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK: archive_name = dlg.GetString()
|
|
else: return
|
|
|
|
|
|
|
|
potential_namespaces = self._archive_info[ archive_name ]
|
|
|
|
with ClientGUIDialogs.DialogCheckFromListOfStrings( self, 'Select namespaces', HC.ConvertUglyNamespacesToPrettyStrings( potential_namespaces ) ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
namespaces = HC.ConvertPrettyStringsToUglyNamespaces( dlg.GetChecked() )
|
|
|
|
else: return
|
|
|
|
|
|
name_to_display = self._GetArchiveNameToDisplay( archive_name, namespaces )
|
|
|
|
self._archive_sync.Append( name_to_display, ( archive_name, namespaces ) )
|
|
|
|
potential_archives = self._GetPotentialArchives()
|
|
|
|
if len( potential_archives ) == 0: self._archive_sync_add.Disable()
|
|
|
|
|
|
def EventArchiveEdit( self, event ):
|
|
|
|
selection = self._archive_sync.GetSelection()
|
|
|
|
if selection != wx.NOT_FOUND:
|
|
|
|
( archive_name, existing_namespaces ) = self._archive_sync.GetClientData( selection )
|
|
|
|
if archive_name not in self._archive_info.keys():
|
|
|
|
wx.MessageBox( 'This archive does not seem to exist any longer!' )
|
|
|
|
return
|
|
|
|
|
|
archive_namespaces = self._archive_info[ archive_name ]
|
|
|
|
with ClientGUIDialogs.DialogCheckFromListOfStrings( self, 'Select namespaces', HC.ConvertUglyNamespacesToPrettyStrings( archive_namespaces ), HC.ConvertUglyNamespacesToPrettyStrings( existing_namespaces ) ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
namespaces = HC.ConvertPrettyStringsToUglyNamespaces( dlg.GetChecked() )
|
|
|
|
else: return
|
|
|
|
|
|
name_to_display = self._GetArchiveNameToDisplay( archive_name, namespaces )
|
|
|
|
self._archive_sync.SetString( selection, name_to_display )
|
|
self._archive_sync.SetClientData( selection, ( archive_name, namespaces ) )
|
|
|
|
|
|
|
|
def EventArchiveRemove( self, event ):
|
|
|
|
selection = self._archive_sync.GetSelection()
|
|
|
|
if selection != wx.NOT_FOUND: self._archive_sync.Delete( selection )
|
|
|
|
potential_archives = self._GetPotentialArchives()
|
|
|
|
if len( potential_archives ) == 0: self._archive_sync_add.Disable()
|
|
else: self._archive_sync_add.Enable()
|
|
|
|
|
|
def EventCheckService( self, event ):
|
|
|
|
( service_key, service_type, name, info ) = self.GetInfo()
|
|
|
|
service = CC.Service( service_key, service_type, name, info )
|
|
|
|
try: root = service.Request( HC.GET, '' )
|
|
except HydrusExceptions.WrongServiceTypeException:
|
|
|
|
wx.MessageBox( 'Connection was made, but the service was not a ' + HC.service_string_lookup[ service_type ] + '.' )
|
|
|
|
return
|
|
|
|
except:
|
|
|
|
wx.MessageBox( 'Could not connect!' )
|
|
|
|
return
|
|
|
|
|
|
if service_type in HC.RESTRICTED_SERVICES:
|
|
|
|
if 'access_key' not in info or info[ 'access_key' ] is None:
|
|
|
|
wx.MessageBox( 'No access key!' )
|
|
|
|
return
|
|
|
|
|
|
response = service.Request( HC.GET, 'access_key_verification' )
|
|
|
|
if not response[ 'verified' ]:
|
|
|
|
wx.MessageBox( 'That access key was not recognised!' )
|
|
|
|
return
|
|
|
|
|
|
|
|
wx.MessageBox( 'Everything looks ok!' )
|
|
|
|
|
|
def GetInfo( self ):
|
|
|
|
( service_key, service_type, name, info ) = self._original_info
|
|
|
|
info = dict( info )
|
|
|
|
if service_type not in HC.NONEDITABLE_SERVICES:
|
|
|
|
name = self._service_name.GetValue()
|
|
|
|
if name == '': raise Exception( 'Please enter a name' )
|
|
|
|
|
|
if service_type in HC.REMOTE_SERVICES:
|
|
|
|
connection_string = self._service_credentials.GetValue()
|
|
|
|
if connection_string == '': raise Exception( 'Please enter some credentials' )
|
|
|
|
if '@' in connection_string:
|
|
|
|
try: ( access_key, address ) = connection_string.split( '@' )
|
|
except: raise Exception( 'Could not parse those credentials - no \'@\' symbol!' )
|
|
|
|
try: access_key = access_key.decode( 'hex' )
|
|
except: raise Exception( 'Could not parse those credentials - could not understand access key!' )
|
|
|
|
if access_key == '': access_key = None
|
|
|
|
info[ 'access_key' ] = access_key
|
|
|
|
connection_string = address
|
|
|
|
|
|
try: ( host, port ) = connection_string.split( ':' )
|
|
except: raise Exception( 'Could not parse those credentials - no \':\' symbol!' )
|
|
|
|
try: port = int( port )
|
|
except: raise Exception( 'Could not parse those credentials - could not understand the port!' )
|
|
|
|
info[ 'host' ] = host
|
|
info[ 'port' ] = port
|
|
|
|
|
|
if service_type in HC.REPOSITORIES:
|
|
|
|
info[ 'paused' ] = self._pause_synchronisation.GetValue()
|
|
|
|
|
|
if service_type == HC.LOCAL_RATING_LIKE:
|
|
|
|
info[ 'like' ] = self._like.GetValue()
|
|
info[ 'dislike' ] = self._dislike.GetValue()
|
|
|
|
elif service_type == HC.LOCAL_RATING_NUMERICAL:
|
|
|
|
( lower, upper ) = ( self._lower.GetValue(), self._upper.GetValue() )
|
|
|
|
if upper < lower: upper = lower + 1
|
|
|
|
info[ 'lower' ] = lower
|
|
info[ 'upper' ] = upper
|
|
|
|
|
|
if service_type in HC.TAG_SERVICES:
|
|
|
|
tag_archives = {}
|
|
|
|
for i in range( self._archive_sync.GetCount() ):
|
|
|
|
( archive_name, namespaces ) = self._archive_sync.GetClientData( i )
|
|
|
|
tag_archives[ archive_name ] = namespaces
|
|
|
|
|
|
info[ 'tag_archive_sync' ] = tag_archives
|
|
|
|
|
|
if service_type == HC.LOCAL_BOORU:
|
|
|
|
info[ 'port' ] = self._port.GetValue()
|
|
info[ 'upnp' ] = self._upnp.GetValue()
|
|
info[ 'max_monthly_data' ] = self._max_monthly_data.GetValue()
|
|
|
|
# listctrl stuff here
|
|
|
|
|
|
return ( service_key, service_type, name, info )
|
|
|
|
|
|
def EventServiceReset( self, event ):
|
|
|
|
( service_key, service_type, name, info ) = self._original_info
|
|
|
|
message = 'This will remove all the information for ' + name + ' from the database so it can be reprocessed. It may take several minutes to finish the operation, during which time the gui will likely freeze.' + os.linesep * 2 + 'Once the service is reset, the client will have to reprocess all the information that was deleted, which will take another long time.' + os.linesep * 2 + 'If you do not understand what this button does, you probably want to click no!'
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_YES:
|
|
|
|
with wx.BusyCursor(): HC.app.Write( 'reset_service', service_key )
|
|
|
|
|
|
|
|
|
|
def HasChanges( self ): return self._original_info != self.GetInfo()
|
|
|
|
def Update( self, service_key, service_type, name, info ):
|
|
|
|
self._service_name.SetValue( name )
|
|
|
|
if service_type in HC.REMOTE_SERVICES:
|
|
|
|
host = info[ 'host' ]
|
|
port = info[ 'port' ]
|
|
|
|
if service_type in HC.RESTRICTED_SERVICES: access_key = info[ 'access_key' ]
|
|
else: access_key = None
|
|
|
|
credentials = CC.Credentials( host, port, access_key )
|
|
|
|
self._service_credentials.SetValue( credentials.GetConnectionString() )
|
|
|
|
|
|
if service_type == HC.LOCAL_RATING_LIKE:
|
|
|
|
like = info[ 'like' ]
|
|
dislike = info[ 'dislike' ]
|
|
|
|
self._like.SetValue( like )
|
|
self._dislike.SetValue( dislike )
|
|
|
|
elif service_type == HC.LOCAL_RATING_NUMERICAL:
|
|
|
|
lower = info[ 'lower' ]
|
|
upper = info[ 'upper' ]
|
|
|
|
self._lower.SetValue( lower )
|
|
self._upper.SetValue( upper )
|
|
|
|
|
|
|
|
|
|
class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._listbook = ClientGUICommon.ListBook( self )
|
|
self._listbook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventPageChanging )
|
|
|
|
self._add = wx.Button( self, label = 'add' )
|
|
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
|
|
self._add.SetForegroundColour( ( 0, 128, 0 ) )
|
|
|
|
self._remove = wx.Button( self, label = 'remove' )
|
|
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
|
|
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
|
|
|
|
self._export = wx.Button( self, label = 'export' )
|
|
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
for name in self._original_subscription_names:
|
|
|
|
page_info = ( self._Panel, ( self._listbook, name ), {} )
|
|
|
|
self._listbook.AddPage( page_info, name )
|
|
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
add_remove_hbox.AddF( self._add, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._remove, FLAGS_MIXED )
|
|
add_remove_hbox.AddF( self._export, FLAGS_MIXED )
|
|
|
|
ok_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
ok_hbox.AddF( self._ok, FLAGS_MIXED )
|
|
ok_hbox.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
vbox.AddF( self._listbook, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( add_remove_hbox, FLAGS_SMALL_INDENT )
|
|
vbox.AddF( ok_hbox, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage subscriptions' )
|
|
|
|
self._original_subscription_names = HC.app.Read( 'subscription_names' )
|
|
|
|
self._names_to_delete = set()
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( 680, max( 720, y ) ) )
|
|
|
|
self.SetDropTarget( ClientGUICommon.FileDropTarget( self.Import ) )
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def _CheckCurrentSubscriptionIsValid( self ):
|
|
|
|
panel = self._listbook.GetCurrentPage()
|
|
|
|
if panel is not None:
|
|
|
|
name = panel.GetName()
|
|
old_name = self._listbook.GetCurrentName()
|
|
|
|
if old_name is not None and name != old_name:
|
|
|
|
if self._listbook.NameExists( name ): raise Exception( 'That name is already in use!' )
|
|
|
|
self._listbook.RenamePage( old_name, name )
|
|
|
|
|
|
|
|
|
|
def EventAdd( self, event ):
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, 'Enter name for subscription.' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
try:
|
|
|
|
name = dlg.GetValue()
|
|
|
|
if self._listbook.NameExists( name ): raise Exception( 'That name is already in use!' )
|
|
|
|
if name == '': raise Exception( 'Please enter a nickname for the subscription.' )
|
|
|
|
page = self._Panel( self._listbook, name, new_subscription = True )
|
|
|
|
self._listbook.AddPage( page, name, select = True )
|
|
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
self.EventAdd( event )
|
|
|
|
|
|
|
|
|
|
|
|
def EventExport( self, event ):
|
|
|
|
try: self._CheckCurrentSubscriptionIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
return
|
|
|
|
|
|
panel = self._listbook.GetCurrentPage()
|
|
|
|
if sub_panel is not None:
|
|
|
|
( name, info ) = panel.GetSubscription()
|
|
|
|
try:
|
|
|
|
with wx.FileDialog( self, 'select where to export subscription', defaultFile = name + '.yaml', style = wx.FD_SAVE ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
with open( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( ( name, info ) ) )
|
|
|
|
|
|
|
|
except:
|
|
|
|
with wx.FileDialog( self, 'select where to export subscription', defaultFile = 'subscription.yaml', style = wx.FD_SAVE ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
with open( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( ( name, info ) ) )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
try: self._CheckCurrentSubscriptionIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
return
|
|
|
|
|
|
all_pages = self._listbook.GetNameToPageDict().values()
|
|
|
|
try:
|
|
|
|
for name in self._names_to_delete: HC.app.Write( 'delete_subscription', name )
|
|
|
|
for page in all_pages:
|
|
|
|
( name, info ) = page.GetSubscription()
|
|
|
|
original_name = page.GetOriginalName()
|
|
|
|
if original_name != name: HC.app.Write( 'delete_subscription', original_name )
|
|
|
|
HC.app.Write( 'subscription', name, info )
|
|
|
|
|
|
HC.subs_changed = True
|
|
|
|
HC.pubsub.pub( 'notify_new_subscriptions' )
|
|
|
|
finally: self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventPageChanging( self, event ):
|
|
|
|
try: self._CheckCurrentSubscriptionIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
event.Veto()
|
|
|
|
|
|
|
|
def EventRemove( self, event ):
|
|
|
|
panel = self._listbook.GetCurrentPage()
|
|
|
|
name = panel.GetOriginalName()
|
|
|
|
self._names_to_delete.add( name )
|
|
|
|
if panel is not None: self._listbook.DeleteCurrentPage()
|
|
|
|
|
|
def Import( self, paths ):
|
|
|
|
try: self._CheckCurrentSubscriptionIsValid()
|
|
except Exception as e:
|
|
|
|
wx.MessageBox( HC.u( e ) )
|
|
|
|
return
|
|
|
|
|
|
for path in paths:
|
|
|
|
try:
|
|
|
|
with open( path, 'rb' ) as f: file = f.read()
|
|
|
|
( name, info ) = yaml.safe_load( file )
|
|
|
|
if self._listbook.NameExists( name ):
|
|
|
|
message = 'A service already exists with that name. Overwrite it?'
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_YES:
|
|
|
|
page = self._listbook.GetNameToPageDict()[ name ]
|
|
|
|
page.Update( name, info )
|
|
|
|
|
|
|
|
else:
|
|
|
|
page = self._Panel( self._listbook, name, info )
|
|
|
|
self._listbook.AddPage( page, name, select = True )
|
|
|
|
|
|
except:
|
|
|
|
wx.MessageBox( traceback.format_exc() )
|
|
|
|
|
|
|
|
|
|
class _Panel( wx.ScrolledWindow ):
|
|
|
|
def __init__( self, parent, name, new_subscription = False ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._name_panel = ClientGUICommon.StaticBox( self, 'name' )
|
|
|
|
self._name = wx.TextCtrl( self._name_panel )
|
|
|
|
self._query_panel = ClientGUICommon.StaticBox( self, 'site and query' )
|
|
|
|
self._site_type = ClientGUICommon.BetterChoice( self._query_panel )
|
|
self._site_type.Append( 'booru', HC.SITE_TYPE_BOORU )
|
|
self._site_type.Append( 'deviant art', HC.SITE_TYPE_DEVIANT_ART )
|
|
self._site_type.Append( 'giphy', HC.SITE_TYPE_GIPHY )
|
|
self._site_type.Append( 'hentai foundry', HC.SITE_TYPE_HENTAI_FOUNDRY )
|
|
self._site_type.Append( 'pixiv', HC.SITE_TYPE_PIXIV )
|
|
self._site_type.Append( 'tumblr', HC.SITE_TYPE_TUMBLR )
|
|
self._site_type.Append( 'newgrounds', HC.SITE_TYPE_NEWGROUNDS )
|
|
self._site_type.Bind( wx.EVT_CHOICE, self.EventSiteChanged )
|
|
|
|
self._query = wx.TextCtrl( self._query_panel )
|
|
|
|
self._booru_selector = wx.ListBox( self._query_panel )
|
|
self._booru_selector.Bind( wx.EVT_LISTBOX, self.EventBooruSelected )
|
|
|
|
self._query_type = ClientGUICommon.BetterChoice( self._query_panel )
|
|
self._query_type.Append( 'artist', 'artist' )
|
|
self._query_type.Append( 'tags', 'tags' )
|
|
|
|
self._frequency = wx.SpinCtrl( self._query_panel, min = 1, max = 9999 )
|
|
|
|
self._frequency_type = wx.Choice( self._query_panel )
|
|
|
|
for ( title, timespan ) in ( ( 'days', 86400 ), ( 'weeks', 86400 * 7 ), ( 'months', 86400 * 30 ) ): self._frequency_type.Append( title, timespan )
|
|
|
|
self._info_panel = ClientGUICommon.StaticBox( self, 'info' )
|
|
|
|
self._paused = wx.CheckBox( self._info_panel, label = 'paused' )
|
|
|
|
self._reset_cache_button = wx.Button( self._info_panel, label = ' reset cache on dialog ok ' )
|
|
self._reset_cache_button.Bind( wx.EVT_BUTTON, self.EventResetCache )
|
|
|
|
self._advanced_tag_options = ClientGUICommon.AdvancedTagOptions( self )
|
|
|
|
self._advanced_import_options = ClientGUICommon.AdvancedImportOptions( self )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
self._SetControls( name, info )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
self._name_panel.AddF( self._name, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
hbox.AddF( wx.StaticText( self._query_panel, label = 'Check subscription every ' ), FLAGS_MIXED )
|
|
hbox.AddF( self._frequency, FLAGS_MIXED )
|
|
hbox.AddF( self._frequency_type, FLAGS_MIXED )
|
|
|
|
self._query_panel.AddF( self._site_type, FLAGS_EXPAND_PERPENDICULAR )
|
|
self._query_panel.AddF( self._query, FLAGS_EXPAND_PERPENDICULAR )
|
|
self._query_panel.AddF( self._query_type, FLAGS_EXPAND_PERPENDICULAR )
|
|
self._query_panel.AddF( self._booru_selector, FLAGS_EXPAND_PERPENDICULAR )
|
|
self._query_panel.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
if info[ 'last_checked' ] is None: last_checked_message = 'not yet initialised'
|
|
else:
|
|
|
|
now = HC.GetNow()
|
|
|
|
if info[ 'last_checked' ] < now: last_checked_message = 'updated to ' + HC.ConvertTimestampToPrettySync( info[ 'last_checked' ] )
|
|
else: last_checked_message = 'due to error, update is delayed. next check in ' + HC.ConvertTimestampToPrettyPending( info[ 'last_checked' ] )
|
|
|
|
|
|
self._info_panel.AddF( wx.StaticText( self._info_panel, label = last_checked_message ), FLAGS_EXPAND_PERPENDICULAR )
|
|
self._info_panel.AddF( wx.StaticText( self._info_panel, label = HC.u( len( info[ 'url_cache' ] ) ) + ' urls in cache' ), FLAGS_EXPAND_PERPENDICULAR )
|
|
self._info_panel.AddF( self._paused, FLAGS_LONE_BUTTON )
|
|
self._info_panel.AddF( self._reset_cache_button, FLAGS_LONE_BUTTON )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._name_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._query_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._info_panel, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._advanced_tag_options, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._advanced_import_options, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
wx.ScrolledWindow.__init__( self, parent )
|
|
|
|
if new_subscription:
|
|
|
|
info = {}
|
|
|
|
info[ 'site_type' ] = HC.SITE_TYPE_BOORU
|
|
info[ 'query_type' ] = ( 'safebooru', 'tags' )
|
|
info[ 'query' ] = ''
|
|
info[ 'frequency_type' ] = 86400
|
|
info[ 'frequency' ] = 7
|
|
info[ 'advanced_tag_options' ] = {}
|
|
info[ 'advanced_import_options' ] = {} # blaaah not sure
|
|
info[ 'last_checked' ] = None
|
|
info[ 'url_cache' ] = set()
|
|
info[ 'paused' ] = False
|
|
|
|
self._new_subscription = True
|
|
|
|
else:
|
|
|
|
info = HC.app.Read( 'subscription', name )
|
|
|
|
self._new_subscription = False
|
|
|
|
|
|
self._original_name = name
|
|
self._original_info = info
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
self._reset_cache = False
|
|
|
|
self.SetScrollRate( 0, 20 )
|
|
|
|
self.SetMinSize( ( 540, 620 ) )
|
|
|
|
|
|
def _ConfigureAdvancedTagOptions( self ):
|
|
|
|
site_type = self._site_type.GetChoice()
|
|
|
|
lookup = site_type
|
|
|
|
if site_type == HC.SITE_TYPE_BOORU:
|
|
|
|
selection = self._booru_selector.GetSelection()
|
|
|
|
booru_name = self._booru_selector.GetString( selection )
|
|
|
|
booru = HC.app.Read( 'remote_booru', booru_name )
|
|
|
|
namespaces = booru.GetNamespaces()
|
|
|
|
lookup = ( HC.SITE_TYPE_BOORU, booru.GetName() )
|
|
|
|
elif site_type == HC.SITE_TYPE_DEVIANT_ART: namespaces = [ 'creator', 'title', '' ]
|
|
elif site_type == HC.SITE_TYPE_GIPHY: namespaces = [ '' ]
|
|
elif site_type == HC.SITE_TYPE_HENTAI_FOUNDRY: namespaces = [ 'creator', 'title', '' ]
|
|
elif site_type == HC.SITE_TYPE_PIXIV: namespaces = [ 'creator', 'title', '' ]
|
|
elif site_type == HC.SITE_TYPE_TUMBLR: namespaces = [ '' ]
|
|
elif site_type == HC.SITE_TYPE_NEWGROUNDS: namespaces = [ 'creator', 'title', '' ]
|
|
|
|
ato = HC.GetDefaultAdvancedTagOptions( lookup )
|
|
|
|
if not self._new_subscription:
|
|
|
|
( name, info ) = self.GetSubscription()
|
|
|
|
same_site = info[ 'site_type' ] == self._original_info[ 'site_type' ]
|
|
same_type_of_query = info[ 'query_type' ] == self._original_info[ 'query_type' ]
|
|
|
|
if same_site and same_type_of_query: ato = self._original_info[ 'advanced_tag_options' ]
|
|
|
|
|
|
self._advanced_tag_options.SetNamespaces( namespaces )
|
|
self._advanced_tag_options.SetInfo( ato )
|
|
|
|
|
|
def _PresentForSiteType( self ):
|
|
|
|
site_type = self._site_type.GetChoice()
|
|
|
|
if site_type in ( HC.SITE_TYPE_BOORU, HC.SITE_TYPE_DEVIANT_ART, HC.SITE_TYPE_GIPHY, HC.SITE_TYPE_TUMBLR, HC.SITE_TYPE_NEWGROUNDS ): self._query_type.Hide()
|
|
else: self._query_type.Show()
|
|
|
|
if site_type == HC.SITE_TYPE_BOORU:
|
|
|
|
if self._booru_selector.GetCount() == 0:
|
|
|
|
boorus = HC.app.Read( 'remote_boorus' )
|
|
|
|
for ( name, booru ) in boorus.items(): self._booru_selector.Append( name, booru )
|
|
|
|
self._booru_selector.Select( 0 )
|
|
|
|
|
|
self._booru_selector.Show()
|
|
|
|
else: self._booru_selector.Hide()
|
|
|
|
wx.CallAfter( self._ConfigureAdvancedTagOptions )
|
|
|
|
self.Layout()
|
|
|
|
|
|
def _SetControls( self, name, info ):
|
|
|
|
site_type = info[ 'site_type' ]
|
|
query_type = info[ 'query_type' ]
|
|
query = info[ 'query' ]
|
|
frequency_type = info[ 'frequency_type' ]
|
|
frequency = info[ 'frequency' ]
|
|
advanced_tag_options = info[ 'advanced_tag_options' ]
|
|
advanced_import_options = info[ 'advanced_import_options' ]
|
|
last_checked = info[ 'last_checked' ]
|
|
url_cache = info[ 'url_cache' ]
|
|
paused = info[ 'paused' ]
|
|
|
|
#
|
|
|
|
self._name.SetValue( name )
|
|
|
|
self._site_type.SelectClientData( site_type )
|
|
|
|
self._PresentForSiteType()
|
|
|
|
self._query.SetValue( query )
|
|
|
|
if site_type == HC.SITE_TYPE_BOORU:
|
|
|
|
if self._booru_selector.GetCount() == 0:
|
|
|
|
boorus = HC.app.Read( 'remote_boorus' )
|
|
|
|
for ( name, booru ) in boorus.items(): self._booru_selector.Append( name, booru )
|
|
|
|
|
|
( booru_name, query_type ) = query_type
|
|
|
|
index = self._booru_selector.FindString( booru_name )
|
|
|
|
if index != wx.NOT_FOUND: self._booru_selector.Select( index )
|
|
|
|
|
|
self._query_type.SelectClientData( query_type )
|
|
|
|
self._frequency.SetValue( frequency )
|
|
|
|
index_to_select = None
|
|
i = 0
|
|
|
|
for ( title, timespan ) in ( ( 'days', 86400 ), ( 'weeks', 86400 * 7 ), ( 'months', 86400 * 30 ) ):
|
|
|
|
if frequency_type == timespan: index_to_select = i
|
|
|
|
i += 1
|
|
|
|
|
|
if index_to_select is not None: self._frequency_type.Select( index_to_select )
|
|
|
|
self._paused.SetValue( paused )
|
|
|
|
self._reset_cache_button.SetLabel( ' reset cache on dialog ok ' )
|
|
|
|
self._advanced_tag_options.SetInfo( advanced_tag_options )
|
|
|
|
self._advanced_import_options.SetInfo( advanced_import_options )
|
|
|
|
|
|
def EventBooruSelected( self, event ): self._ConfigureAdvancedTagOptions()
|
|
|
|
def EventResetCache( self, event ):
|
|
|
|
|
|
message = '''Resetting this subscription's cache will delete ''' + HC.ConvertIntToPrettyString( len( self._original_info[ 'url_cache' ] ) ) + ''' remembered links, meaning when the subscription next runs, it will try to download those all over again. This may be expensive in time and data. Only do it if you are willing to wait. Do you want to do it?'''
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_YES:
|
|
|
|
self._reset_cache = True
|
|
|
|
self._reset_cache_button.SetLabel( 'cache will be reset on dialog ok' )
|
|
self._reset_cache_button.Disable()
|
|
|
|
|
|
|
|
|
|
def EventSiteChanged( self, event ): self._PresentForSiteType()
|
|
|
|
def GetSubscription( self ):
|
|
|
|
name = self._name.GetValue()
|
|
|
|
info = dict( self._original_info )
|
|
|
|
info[ 'site_type' ] = self._site_type.GetChoice()
|
|
|
|
if info[ 'site_type' ] in ( HC.SITE_TYPE_BOORU, HC.SITE_TYPE_GIPHY ): query_type = 'tags'
|
|
elif info[ 'site_type' ] in ( HC.SITE_TYPE_DEVIANT_ART, HC.SITE_TYPE_NEWGROUNDS, HC.SITE_TYPE_TUMBLR ): query_type = 'artist'
|
|
else: query_type = self._query_type.GetChoice()
|
|
|
|
if info[ 'site_type' ] == HC.SITE_TYPE_BOORU:
|
|
|
|
booru_name = self._booru_selector.GetStringSelection()
|
|
|
|
info[ 'query_type' ] = ( booru_name, query_type )
|
|
|
|
else: info[ 'query_type' ] = query_type
|
|
|
|
info[ 'query' ] = self._query.GetValue()
|
|
|
|
info[ 'frequency' ] = self._frequency.GetValue()
|
|
info[ 'frequency_type' ] = self._frequency_type.GetClientData( self._frequency_type.GetSelection() )
|
|
|
|
info[ 'advanced_tag_options' ] = self._advanced_tag_options.GetInfo()
|
|
|
|
info[ 'advanced_import_options' ] = self._advanced_import_options.GetInfo()
|
|
|
|
if self._reset_cache:
|
|
|
|
info[ 'last_checked' ] = None
|
|
info[ 'url_cache' ] = set()
|
|
|
|
|
|
info[ 'paused' ] = self._paused.GetValue()
|
|
|
|
return ( name, info )
|
|
|
|
|
|
def GetOriginalName( self ): return self._original_name
|
|
|
|
def GetName( self ): return self._name.GetValue()
|
|
|
|
def Update( self, name, info ):
|
|
|
|
self._original_info = info
|
|
|
|
self._SetControls( name, info )
|
|
|
|
|
|
|
|
class DialogManageTagCensorship( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._tag_services = ClientGUICommon.ListBook( self )
|
|
self._tag_services.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
services = HC.app.GetManager( 'services' ).GetServices( ( HC.COMBINED_TAG, HC.TAG_REPOSITORY, HC.LOCAL_TAG ) )
|
|
|
|
default_tag_repository_key = HC.options[ 'default_tag_repository' ]
|
|
|
|
for service in services:
|
|
|
|
service_key = service.GetServiceKey()
|
|
name = service.GetName()
|
|
|
|
if service_key == default_tag_repository_key: default_name = name
|
|
|
|
page = self._Panel( self._tag_services, service_key )
|
|
|
|
self._tag_services.AddPage( page, name )
|
|
|
|
|
|
self._tag_services.Select( default_name )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
buttons = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
buttons.AddF( self._ok, FLAGS_SMALL_INDENT )
|
|
buttons.AddF( self._cancel, FLAGS_SMALL_INDENT )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
intro = "Here you can set which tags or classes of tags you do not want to see. Input something like 'series:' to censor an entire namespace, or ':' for all namespaced tags, and '' for all unnamespaced tags. You may have to refresh your current queries to see any changes."
|
|
|
|
st = wx.StaticText( self, label = intro )
|
|
|
|
st.Wrap( 350 )
|
|
|
|
vbox.AddF( st, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._tag_services, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( buttons, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
self.SetInitialSize( ( -1, 480 ) )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'tag censorship' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
interested_actions = [ 'set_search_focus' ]
|
|
|
|
entries = []
|
|
|
|
for ( modifier, key_dict ) in HC.options[ 'shortcuts' ].items(): entries.extend( [ ( modifier, key, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) for ( key, action ) in key_dict.items() if action in interested_actions ] )
|
|
|
|
self.SetAcceleratorTable( wx.AcceleratorTable( entries ) )
|
|
|
|
|
|
def _SetSearchFocus( self ):
|
|
|
|
page = self._tag_services.GetCurrentPage()
|
|
|
|
page.SetTagBoxFocus()
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
try:
|
|
|
|
info = [ page.GetInfo() for page in self._tag_services.GetNameToPageDict().values() if page.HasInfo() ]
|
|
|
|
HC.app.Write( 'tag_censorship', info )
|
|
|
|
finally: self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventServiceChanged( self, event ):
|
|
|
|
page = self._tag_services.GetCurrentPage()
|
|
|
|
wx.CallAfter( page.SetTagBoxFocus )
|
|
|
|
|
|
class _Panel( wx.Panel ):
|
|
|
|
def __init__( self, parent, service_key ):
|
|
|
|
def InitialiseControls():
|
|
|
|
choice_pairs = [ ( 'blacklist', True ), ( 'whitelist', False ) ]
|
|
|
|
self._blacklist = ClientGUICommon.RadioBox( self, 'type', choice_pairs )
|
|
|
|
self._tags = ClientGUICommon.TagsBoxCensorship( self )
|
|
|
|
self._tag_input = wx.TextCtrl( self, style = wx.TE_PROCESS_ENTER )
|
|
self._tag_input.Bind( wx.EVT_KEY_DOWN, self.EventKeyDownTag )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
( blacklist, tags ) = HC.app.Read( 'tag_censorship', service_key )
|
|
|
|
if blacklist: self._blacklist.SetSelection( 0 )
|
|
else: self._blacklist.SetSelection( 1 )
|
|
|
|
for tag in tags: self._tags.AddTag( tag )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._blacklist, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._tags, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( self._tag_input, FLAGS_EXPAND_PERPENDICULAR )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
wx.Panel.__init__( self, parent )
|
|
|
|
self._service_key = service_key
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
|
|
def EventKeyDownTag( self, event ):
|
|
|
|
if event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
|
|
|
tag = self._tag_input.GetValue()
|
|
|
|
self._tags.AddTag( tag )
|
|
|
|
self._tag_input.SetValue( '' )
|
|
|
|
else: event.Skip()
|
|
|
|
|
|
def GetInfo( self ):
|
|
|
|
blacklist = self._blacklist.GetSelectedClientData()
|
|
|
|
tags = self._tags.GetClientData()
|
|
|
|
return ( self._service_key, blacklist, tags )
|
|
|
|
|
|
def HasInfo( self ):
|
|
|
|
( service_key, blacklist, tags ) = self.GetInfo()
|
|
|
|
return len( tags ) > 0
|
|
|
|
|
|
def SetTagBoxFocus( self ): self._tag_input.SetFocus()
|
|
|
|
|
|
class DialogManageTagParents( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent, tag = None ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._tag_repositories = ClientGUICommon.ListBook( self )
|
|
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
services = HC.app.GetManager( 'services' ).GetServices( ( HC.TAG_REPOSITORY, ) )
|
|
|
|
for service in services:
|
|
|
|
account = service.GetInfo( 'account' )
|
|
|
|
if account.HasPermission( HC.POST_DATA ) or account.IsUnknownAccount():
|
|
|
|
name = service.GetName()
|
|
service_key = service.GetServiceKey()
|
|
|
|
page_info = ( self._Panel, ( self._tag_repositories, service_key, tag ), {} )
|
|
|
|
self._tag_repositories.AddPage( page_info, name )
|
|
|
|
|
|
|
|
page = self._Panel( self._tag_repositories, HC.LOCAL_TAG_SERVICE_KEY, tag )
|
|
|
|
name = HC.LOCAL_TAG_SERVICE_KEY
|
|
|
|
self._tag_repositories.AddPage( page, name )
|
|
|
|
default_tag_repository_key = HC.options[ 'default_tag_repository' ]
|
|
|
|
service = HC.app.GetManager( 'services' ).GetService( default_tag_repository_key )
|
|
|
|
self._tag_repositories.Select( service.GetName() )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
buttons = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
buttons.AddF( self._ok, FLAGS_SMALL_INDENT )
|
|
buttons.AddF( self._cancel, FLAGS_SMALL_INDENT )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._tag_repositories, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( buttons, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
self.SetInitialSize( ( 550, 680 ) )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'tag parents' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
interested_actions = [ 'set_search_focus' ]
|
|
|
|
entries = []
|
|
|
|
for ( modifier, key_dict ) in HC.options[ 'shortcuts' ].items(): entries.extend( [ ( modifier, key, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) for ( key, action ) in key_dict.items() if action in interested_actions ] )
|
|
|
|
self.SetAcceleratorTable( wx.AcceleratorTable( entries ) )
|
|
|
|
self.Bind( wx.EVT_MENU, self.EventMenu )
|
|
|
|
|
|
def _SetSearchFocus( self ):
|
|
|
|
page = self._tag_repositories.GetCurrentPage()
|
|
|
|
page.SetTagBoxFocus()
|
|
|
|
|
|
def EventMenu( self, event ):
|
|
|
|
action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
|
|
|
|
if action is not None:
|
|
|
|
( command, data ) = action
|
|
|
|
if command == 'set_search_focus': self._SetSearchFocus()
|
|
else: event.Skip()
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
service_keys_to_content_updates = {}
|
|
|
|
try:
|
|
|
|
for page in self._tag_repositories.GetNameToPageDict().values():
|
|
|
|
( service_key, content_updates ) = page.GetContentUpdates()
|
|
|
|
service_keys_to_content_updates[ service_key ] = content_updates
|
|
|
|
|
|
HC.app.Write( 'content_updates', service_keys_to_content_updates )
|
|
|
|
finally: self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventServiceChanged( self, event ):
|
|
|
|
page = self._tag_repositories.GetCurrentPage()
|
|
|
|
wx.CallAfter( page.SetTagBoxFocus )
|
|
|
|
|
|
class _Panel( wx.Panel ):
|
|
|
|
def __init__( self, parent, service_key, tag = None ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._tag_parents = ClientGUICommon.SaneListCtrl( self, 250, [ ( '', 30 ), ( 'child', 160 ), ( 'parent', -1 ) ] )
|
|
self._tag_parents.Bind( wx.EVT_LIST_ITEM_ACTIVATED, self.EventActivated )
|
|
self._tag_parents.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
|
|
self._tag_parents.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )
|
|
|
|
removed_callable = lambda tag: 1
|
|
|
|
self._children = ClientGUICommon.TagsBoxFlat( self, removed_callable )
|
|
self._parents = ClientGUICommon.TagsBoxFlat( self, removed_callable )
|
|
|
|
self._child_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddChild, HC.LOCAL_FILE_SERVICE_KEY, service_key )
|
|
self._parent_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddParent, HC.LOCAL_FILE_SERVICE_KEY, service_key )
|
|
|
|
self._add = wx.Button( self, label = 'add' )
|
|
self._add.Bind( wx.EVT_BUTTON, self.EventAddButton )
|
|
self._add.Disable()
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
for ( status, pairs ) in self._original_statuses_to_pairs.items():
|
|
|
|
sign = HC.ConvertStatusToPrefix( status )
|
|
|
|
for ( child, parent ) in pairs: self._tag_parents.Append( ( sign, child, parent ), ( status, child, parent ) )
|
|
|
|
|
|
self._tag_parents.SortListItems( 2 )
|
|
|
|
if tag is not None: self.AddChild( tag )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
intro = 'Files with a tag on the left will also be given the tag on the right.'
|
|
|
|
tags_box = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
tags_box.AddF( self._children, FLAGS_EXPAND_BOTH_WAYS )
|
|
tags_box.AddF( self._parents, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
input_box = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
input_box.AddF( self._child_input, FLAGS_EXPAND_BOTH_WAYS )
|
|
input_box.AddF( self._parent_input, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._tag_parents, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( self._add, FLAGS_LONE_BUTTON )
|
|
vbox.AddF( tags_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
vbox.AddF( input_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
wx.Panel.__init__( self, parent )
|
|
|
|
self._service_key = service_key
|
|
|
|
if service_key != HC.LOCAL_TAG_SERVICE_KEY:
|
|
|
|
service = HC.app.GetManager( 'services' ).GetService( service_key )
|
|
|
|
self._account = service.GetInfo( 'account' )
|
|
|
|
|
|
self._original_statuses_to_pairs = HC.app.Read( 'tag_parents', service_key )
|
|
|
|
self._current_statuses_to_pairs = collections.defaultdict( set )
|
|
|
|
self._current_statuses_to_pairs.update( { key : set( value ) for ( key, value ) in self._original_statuses_to_pairs.items() } )
|
|
|
|
self._pairs_to_reasons = {}
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
|
|
def _AddPair( self, child, parent ):
|
|
|
|
old_status = None
|
|
new_status = None
|
|
|
|
pair = ( child, parent )
|
|
|
|
pair_string = child + '->' + parent
|
|
|
|
if pair in self._current_statuses_to_pairs[ HC.CURRENT ]:
|
|
|
|
message = pair_string + ' already exists.'
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'petition it', no_label = 'do nothing' ) as dlg:
|
|
|
|
if self._service_key != HC.LOCAL_TAG_SERVICE_KEY:
|
|
|
|
if dlg.ShowModal() == wx.ID_YES:
|
|
|
|
if self._account.HasPermission( HC.RESOLVE_PETITIONS ): reason = 'admin'
|
|
else:
|
|
|
|
message = 'Enter a reason for this pair to be removed. A janitor will review your petition.'
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
|
|
else: return
|
|
|
|
|
|
|
|
self._pairs_to_reasons[ pair ] = reason
|
|
|
|
else: return
|
|
|
|
|
|
old_status = HC.CURRENT
|
|
new_status = HC.PETITIONED
|
|
|
|
|
|
elif pair in self._current_statuses_to_pairs[ HC.PENDING ]:
|
|
|
|
message = pair_string + ' is pending.'
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'rescind the pend', no_label = 'do nothing' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_YES:
|
|
|
|
old_status = HC.PENDING
|
|
|
|
if pair in self._current_statuses_to_pairs[ HC.DELETED ]: new_status = HC.DELETED
|
|
|
|
else: return
|
|
|
|
|
|
elif pair in self._current_statuses_to_pairs[ HC.PETITIONED ]:
|
|
|
|
message = pair_string + ' is petitioned.'
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'rescind the petition', no_label = 'do nothing' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_YES:
|
|
|
|
old_status = HC.PETITIONED
|
|
new_status = HC.CURRENT
|
|
|
|
else: return
|
|
|
|
|
|
else:
|
|
|
|
if self._CanAdd( child, parent ):
|
|
|
|
if self._service_key != HC.LOCAL_TAG_SERVICE_KEY:
|
|
|
|
if self._account.HasPermission( HC.RESOLVE_PETITIONS ): reason = 'admin'
|
|
else:
|
|
|
|
message = 'Enter a reason for ' + pair_string + ' to be added. A janitor will review your petition.'
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
|
|
else: return
|
|
|
|
|
|
|
|
self._pairs_to_reasons[ pair ] = reason
|
|
|
|
|
|
if pair in self._current_statuses_to_pairs[ HC.DELETED ]: old_status = HC.DELETED
|
|
|
|
new_status = HC.PENDING
|
|
|
|
|
|
|
|
if old_status is not None:
|
|
|
|
self._current_statuses_to_pairs[ old_status ].discard( pair )
|
|
|
|
index = self._tag_parents.GetIndexFromClientData( ( old_status, child, parent ) )
|
|
|
|
self._tag_parents.DeleteItem( index )
|
|
|
|
|
|
if new_status is not None:
|
|
|
|
self._current_statuses_to_pairs[ new_status ].add( pair )
|
|
|
|
sign = HC.ConvertStatusToPrefix( new_status )
|
|
|
|
self._tag_parents.Append( ( sign, child, parent ), ( new_status, child, parent ) )
|
|
|
|
|
|
|
|
def _CanAdd( self, potential_child, potential_parent ):
|
|
|
|
if potential_child == potential_parent: return False
|
|
|
|
current_pairs = self._current_statuses_to_pairs[ HC.CURRENT ].union( self._current_statuses_to_pairs[ HC.PENDING ] )
|
|
|
|
current_children = { child for ( child, parent ) in current_pairs }
|
|
|
|
# test for loops
|
|
|
|
if potential_parent in current_children:
|
|
|
|
simple_children_to_parents = HydrusTags.BuildSimpleChildrenToParents( current_pairs )
|
|
|
|
if HydrusTags.LoopInSimpleChildrenToParents( simple_children_to_parents, potential_child, potential_parent ):
|
|
|
|
wx.MessageBox( 'Adding ' + potential_child + '->' + potential_parent + ' would create a loop!' )
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
def _SetButtonStatus( self ):
|
|
|
|
if len( self._children.GetTags() ) == 0 or len( self._parents.GetTags() ) == 0: self._add.Disable()
|
|
else: self._add.Enable()
|
|
|
|
|
|
def AddChild( self, tag, parents = [] ):
|
|
|
|
if tag is not None:
|
|
|
|
if tag in self._parents.GetTags(): self._parents.AddTag( tag )
|
|
|
|
self._children.AddTag( tag )
|
|
|
|
self._SetButtonStatus()
|
|
|
|
|
|
|
|
def AddParent( self, tag, parents = [] ):
|
|
|
|
if tag is not None:
|
|
|
|
if tag in self._children.GetTags(): self._children.AddTag( tag )
|
|
|
|
self._parents.AddTag( tag )
|
|
|
|
self._SetButtonStatus()
|
|
|
|
|
|
|
|
def EventActivated( self, event ):
|
|
|
|
all_selected = self._tag_parents.GetAllSelected()
|
|
|
|
if len( all_selected ) > 0:
|
|
|
|
selection = all_selected[0]
|
|
|
|
( status, child, parent ) = self._tag_parents.GetClientData( selection )
|
|
|
|
self._AddPair( child, parent )
|
|
|
|
|
|
|
|
def EventAddButton( self, event ):
|
|
|
|
children = self._children.GetTags()
|
|
parents = self._parents.GetTags()
|
|
|
|
for ( child, parent ) in itertools.product( children, parents ): self._AddPair( child, parent )
|
|
|
|
self._children.SetTags( [] )
|
|
self._parents.SetTags( [] )
|
|
|
|
self._SetButtonStatus()
|
|
|
|
|
|
def EventItemSelected( self, event ):
|
|
|
|
self._SetButtonStatus()
|
|
|
|
|
|
def GetContentUpdates( self ):
|
|
|
|
# we make it manually here because of the mass pending tags done (but not undone on a rescind) on a pending pair!
|
|
# we don't want to send a pend and then rescind it, cause that will spam a thousand bad tags and not undo it
|
|
|
|
content_updates = []
|
|
|
|
if self._service_key == HC.LOCAL_TAG_SERVICE_KEY:
|
|
|
|
for pair in self._current_statuses_to_pairs[ HC.PENDING ]: content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_ADD, pair ) )
|
|
for pair in self._current_statuses_to_pairs[ HC.PETITIONED ]: content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_DELETE, pair ) )
|
|
|
|
else:
|
|
|
|
current_pending = self._current_statuses_to_pairs[ HC.PENDING ]
|
|
original_pending = self._original_statuses_to_pairs[ HC.PENDING ]
|
|
|
|
current_petitioned = self._current_statuses_to_pairs[ HC.PETITIONED ]
|
|
original_petitioned = self._original_statuses_to_pairs[ HC.PETITIONED ]
|
|
|
|
new_pends = current_pending.difference( original_pending )
|
|
rescinded_pends = original_pending.difference( current_pending )
|
|
|
|
new_petitions = current_petitioned.difference( original_petitioned )
|
|
rescinded_petitions = original_petitioned.difference( current_petitioned )
|
|
|
|
content_updates.extend( ( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_PENDING, ( pair, self._pairs_to_reasons[ pair ] ) ) for pair in new_pends ) )
|
|
content_updates.extend( ( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_RESCIND_PENDING, pair ) for pair in rescinded_pends ) )
|
|
content_updates.extend( ( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_PETITION, ( pair, self._pairs_to_reasons[ pair ] ) ) for pair in new_petitions ) )
|
|
content_updates.extend( ( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_RESCIND_PETITION, pair ) for pair in rescinded_petitions ) )
|
|
|
|
|
|
return ( self._service_key, content_updates )
|
|
|
|
|
|
def SetTagBoxFocus( self ):
|
|
|
|
if len( self._children.GetTags() ) == 0: self._child_input.SetFocus()
|
|
else: self._parent_input.SetFocus()
|
|
|
|
|
|
|
|
class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent, tag = None ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._tag_repositories = ClientGUICommon.ListBook( self )
|
|
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
page = self._Panel( self._tag_repositories, HC.LOCAL_TAG_SERVICE_KEY, tag )
|
|
|
|
name = HC.LOCAL_TAG_SERVICE_KEY
|
|
|
|
self._tag_repositories.AddPage( page, name )
|
|
|
|
services = HC.app.GetManager( 'services' ).GetServices( ( HC.TAG_REPOSITORY, ) )
|
|
|
|
for service in services:
|
|
|
|
account = service.GetInfo( 'account' )
|
|
|
|
if account.HasPermission( HC.POST_DATA ) or account.IsUnknownAccount():
|
|
|
|
name = service.GetName()
|
|
service_key = service.GetServiceKey()
|
|
|
|
page_info = ( self._Panel, ( self._tag_repositories, service_key, tag ), {} )
|
|
|
|
self._tag_repositories.AddPage( page_info, name )
|
|
|
|
|
|
|
|
default_tag_repository_key = HC.options[ 'default_tag_repository' ]
|
|
|
|
service = HC.app.GetManager( 'services' ).GetService( default_tag_repository_key )
|
|
|
|
self._tag_repositories.Select( service.GetName() )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
buttons = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
buttons.AddF( self._ok, FLAGS_SMALL_INDENT )
|
|
buttons.AddF( self._cancel, FLAGS_SMALL_INDENT )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._tag_repositories, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( buttons, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
self.SetInitialSize( ( 550, 680 ) )
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'tag siblings' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
interested_actions = [ 'set_search_focus' ]
|
|
|
|
entries = []
|
|
|
|
for ( modifier, key_dict ) in HC.options[ 'shortcuts' ].items(): entries.extend( [ ( modifier, key, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) for ( key, action ) in key_dict.items() if action in interested_actions ] )
|
|
|
|
self.SetAcceleratorTable( wx.AcceleratorTable( entries ) )
|
|
|
|
self.Bind( wx.EVT_MENU, self.EventMenu )
|
|
|
|
|
|
def _SetSearchFocus( self ):
|
|
|
|
page = self._tag_repositories.GetCurrentPage()
|
|
|
|
page.SetTagBoxFocus()
|
|
|
|
|
|
def EventMenu( self, event ):
|
|
|
|
action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
|
|
|
|
if action is not None:
|
|
|
|
( command, data ) = action
|
|
|
|
if command == 'set_search_focus': self._SetSearchFocus()
|
|
else: event.Skip()
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
service_keys_to_content_updates = {}
|
|
|
|
try:
|
|
|
|
for page in self._tag_repositories.GetNameToPageDict().values():
|
|
|
|
( service_key, content_updates ) = page.GetContentUpdates()
|
|
|
|
service_keys_to_content_updates[ service_key ] = content_updates
|
|
|
|
|
|
HC.app.Write( 'content_updates', service_keys_to_content_updates )
|
|
|
|
finally: self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventServiceChanged( self, event ):
|
|
|
|
page = self._tag_repositories.GetCurrentPage()
|
|
|
|
wx.CallAfter( page.SetTagBoxFocus )
|
|
|
|
|
|
class _Panel( wx.Panel ):
|
|
|
|
def __init__( self, parent, service_key, tag = None ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._tag_siblings = ClientGUICommon.SaneListCtrl( self, 250, [ ( '', 30 ), ( 'old', 160 ), ( 'new', -1 ) ] )
|
|
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_ACTIVATED, self.EventActivated )
|
|
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
|
|
self._tag_siblings.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )
|
|
|
|
removed_callable = lambda tags: 1
|
|
|
|
self._old_siblings = ClientGUICommon.TagsBoxFlat( self, removed_callable )
|
|
self._new_sibling = wx.StaticText( self )
|
|
|
|
self._old_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddOld, HC.LOCAL_FILE_SERVICE_KEY, service_key )
|
|
self._new_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetNew, HC.LOCAL_FILE_SERVICE_KEY, service_key )
|
|
|
|
self._add = wx.Button( self, label = 'add' )
|
|
self._add.Bind( wx.EVT_BUTTON, self.EventAddButton )
|
|
self._add.Disable()
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
for ( status, pairs ) in self._original_statuses_to_pairs.items():
|
|
|
|
sign = HC.ConvertStatusToPrefix( status )
|
|
|
|
for ( old, new ) in pairs: self._tag_siblings.Append( ( sign, old, new ), ( status, old, new ) )
|
|
|
|
|
|
self._tag_siblings.SortListItems( 2 )
|
|
|
|
if tag is not None: self.AddOld( tag )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
intro = 'Tags on the left will be replaced by those on the right.'
|
|
|
|
new_sibling_box = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
new_sibling_box.AddF( ( 10, 10 ), FLAGS_EXPAND_BOTH_WAYS )
|
|
new_sibling_box.AddF( self._new_sibling, FLAGS_EXPAND_PERPENDICULAR )
|
|
new_sibling_box.AddF( ( 10, 10 ), FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
text_box = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
text_box.AddF( self._old_siblings, FLAGS_EXPAND_BOTH_WAYS )
|
|
text_box.AddF( new_sibling_box, FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
|
|
|
input_box = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
input_box.AddF( self._old_input, FLAGS_EXPAND_BOTH_WAYS )
|
|
input_box.AddF( self._new_input, FLAGS_EXPAND_BOTH_WAYS )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( wx.StaticText( self, label = intro ), FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( self._tag_siblings, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( self._add, FLAGS_LONE_BUTTON )
|
|
vbox.AddF( text_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
vbox.AddF( input_box, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
wx.Panel.__init__( self, parent )
|
|
|
|
self._service_key = service_key
|
|
|
|
if self._service_key != HC.LOCAL_TAG_SERVICE_KEY:
|
|
|
|
service = HC.app.GetManager( 'services' ).GetService( service_key )
|
|
|
|
self._account = service.GetInfo( 'account' )
|
|
|
|
|
|
self._original_statuses_to_pairs = HC.app.Read( 'tag_siblings', service_key )
|
|
|
|
self._current_statuses_to_pairs = collections.defaultdict( set )
|
|
|
|
self._current_statuses_to_pairs.update( { key : set( value ) for ( key, value ) in self._original_statuses_to_pairs.items() } )
|
|
|
|
self._pairs_to_reasons = {}
|
|
|
|
self._current_new = None
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
|
|
def _AddPair( self, old, new ):
|
|
|
|
old_status = None
|
|
new_status = None
|
|
|
|
pair = ( old, new )
|
|
|
|
pair_string = old + '->' + new
|
|
|
|
if pair in self._current_statuses_to_pairs[ HC.CURRENT ]:
|
|
|
|
message = pair_string + ' already exists.'
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'petition it', no_label = 'do nothing' ) as dlg:
|
|
|
|
if self._service_key != HC.LOCAL_TAG_SERVICE_KEY:
|
|
|
|
if dlg.ShowModal() == wx.ID_YES:
|
|
|
|
if self._account.HasPermission( HC.RESOLVE_PETITIONS ): reason = 'admin'
|
|
else:
|
|
|
|
message = 'Enter a reason for this pair to be removed. A janitor will review your petition.'
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
|
|
else: return
|
|
|
|
|
|
|
|
self._pairs_to_reasons[ pair ] = reason
|
|
|
|
else: return
|
|
|
|
|
|
old_status = HC.CURRENT
|
|
new_status = HC.PETITIONED
|
|
|
|
|
|
elif pair in self._current_statuses_to_pairs[ HC.PENDING ]:
|
|
|
|
message = pair_string + ' is pending.'
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'rescind the pend', no_label = 'do nothing' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_YES:
|
|
|
|
old_status = HC.PENDING
|
|
|
|
if pair in self._current_statuses_to_pairs[ HC.DELETED ]: new_status = HC.DELETED
|
|
|
|
else: return
|
|
|
|
|
|
elif pair in self._current_statuses_to_pairs[ HC.PETITIONED ]:
|
|
|
|
message = pair_string + ' is petitioned.'
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'rescind the petition', no_label = 'do nothing' ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_YES:
|
|
|
|
old_status = HC.PETITIONED
|
|
new_status = HC.CURRENT
|
|
|
|
else: return
|
|
|
|
|
|
else:
|
|
|
|
if self._CanAdd( old, new ):
|
|
|
|
if self._service_key != HC.LOCAL_TAG_SERVICE_KEY:
|
|
|
|
if self._account.HasPermission( HC.RESOLVE_PETITIONS ): reason = 'admin'
|
|
else:
|
|
|
|
message = 'Enter a reason for ' + pair_string + ' to be added. A janitor will review your petition.'
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
|
|
else: return
|
|
|
|
|
|
|
|
self._pairs_to_reasons[ pair ] = reason
|
|
|
|
|
|
if pair in self._current_statuses_to_pairs[ HC.DELETED ]: old_status = HC.DELETED
|
|
|
|
new_status = HC.PENDING
|
|
|
|
|
|
|
|
if old_status is not None:
|
|
|
|
self._current_statuses_to_pairs[ old_status ].discard( pair )
|
|
|
|
index = self._tag_siblings.GetIndexFromClientData( ( old_status, old, new ) )
|
|
|
|
self._tag_siblings.DeleteItem( index )
|
|
|
|
|
|
if new_status is not None:
|
|
|
|
self._current_statuses_to_pairs[ new_status ].add( pair )
|
|
|
|
sign = HC.ConvertStatusToPrefix( new_status )
|
|
|
|
self._tag_siblings.Append( ( sign, old, new ), ( new_status, old, new ) )
|
|
|
|
|
|
|
|
def _CanAdd( self, potential_old, potential_new ):
|
|
|
|
current_pairs = self._current_statuses_to_pairs[ HC.CURRENT ].union( self._current_statuses_to_pairs[ HC.PENDING ] )
|
|
|
|
current_olds = { old for ( old, new ) in current_pairs }
|
|
|
|
# test for ambiguity
|
|
|
|
if potential_old in current_olds:
|
|
|
|
wx.MessageBox( 'There already is a relationship set for the tag ' + potential_old + '.' )
|
|
|
|
return False
|
|
|
|
|
|
# test for loops
|
|
|
|
if potential_new in current_olds:
|
|
|
|
d = dict( current_pairs )
|
|
|
|
next_new = potential_new
|
|
|
|
while next_new in d:
|
|
|
|
next_new = d[ next_new ]
|
|
|
|
if next_new == potential_old:
|
|
|
|
wx.MessageBox( 'Adding ' + potential_old + '->' + potential_new + ' would create a loop!' )
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
def _SetButtonStatus( self ):
|
|
|
|
if self._current_new is None or len( self._old_siblings.GetTags() ) == 0: self._add.Disable()
|
|
else: self._add.Enable()
|
|
|
|
|
|
def AddOld( self, old, parents = [] ):
|
|
|
|
if old is not None:
|
|
|
|
current_pairs = self._current_statuses_to_pairs[ HC.CURRENT ].union( self._current_statuses_to_pairs[ HC.PENDING ] )
|
|
|
|
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
|
|
|
|
# test for ambiguity
|
|
|
|
while old in current_olds:
|
|
|
|
olds_to_news = dict( current_pairs )
|
|
|
|
new = olds_to_news[ old ]
|
|
|
|
message = 'There already is a relationship set for ' + old + '! It goes to ' + new + '.'
|
|
|
|
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'I want to overwrite it', no_label = 'do nothing' ) as dlg:
|
|
|
|
if self._service_key != HC.LOCAL_TAG_SERVICE_KEY:
|
|
|
|
if dlg.ShowModal() != wx.ID_YES: return
|
|
|
|
self._AddPair( old, new )
|
|
|
|
|
|
|
|
current_pairs = self._current_statuses_to_pairs[ HC.CURRENT ].union( self._current_statuses_to_pairs[ HC.PENDING ] )
|
|
|
|
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
|
|
|
|
|
|
|
|
#
|
|
|
|
if old is not None:
|
|
|
|
if old == self._current_new: self.SetNew( None )
|
|
|
|
self._old_siblings.AddTag( old )
|
|
|
|
|
|
self._SetButtonStatus()
|
|
|
|
|
|
def EventActivated( self, event ):
|
|
|
|
all_selected = self._tag_siblings.GetAllSelected()
|
|
|
|
if len( all_selected ) > 0:
|
|
|
|
selection = all_selected[0]
|
|
|
|
( status, old, new ) = self._tag_siblings.GetClientData( selection )
|
|
|
|
self._AddPair( old, new )
|
|
|
|
|
|
|
|
def EventAddButton( self, event ):
|
|
|
|
if self._current_new is not None and len( self._old_siblings.GetTags() ) > 0:
|
|
|
|
for old in self._old_siblings.GetTags(): self._AddPair( old, self._current_new )
|
|
|
|
self._old_siblings.SetTags( [] )
|
|
self.SetNew( None )
|
|
|
|
self._SetButtonStatus()
|
|
|
|
|
|
|
|
def EventItemSelected( self, event ):
|
|
|
|
self._SetButtonStatus()
|
|
|
|
|
|
def GetContentUpdates( self ):
|
|
|
|
# we make it manually here because of the mass pending tags done (but not undone on a rescind) on a pending pair!
|
|
# we don't want to send a pend and then rescind it, cause that will spam a thousand bad tags and not undo it
|
|
|
|
# actually, we don't do this for siblings, but we do for parents, and let's have them be the same
|
|
|
|
content_updates = []
|
|
|
|
if self._service_key == HC.LOCAL_TAG_SERVICE_KEY:
|
|
|
|
for pair in self._current_statuses_to_pairs[ HC.PENDING ]: content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_ADD, pair ) )
|
|
for pair in self._current_statuses_to_pairs[ HC.PETITIONED ]: content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_DELETE, pair ) )
|
|
|
|
else:
|
|
|
|
current_pending = self._current_statuses_to_pairs[ HC.PENDING ]
|
|
original_pending = self._original_statuses_to_pairs[ HC.PENDING ]
|
|
|
|
current_petitioned = self._current_statuses_to_pairs[ HC.PETITIONED ]
|
|
original_petitioned = self._original_statuses_to_pairs[ HC.PETITIONED ]
|
|
|
|
new_pends = current_pending.difference( original_pending )
|
|
rescinded_pends = original_pending.difference( current_pending )
|
|
|
|
new_petitions = current_petitioned.difference( original_petitioned )
|
|
rescinded_petitions = original_petitioned.difference( current_petitioned )
|
|
|
|
content_updates.extend( ( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_PENDING, ( pair, self._pairs_to_reasons[ pair ] ) ) for pair in new_pends ) )
|
|
content_updates.extend( ( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_RESCIND_PENDING, pair ) for pair in rescinded_pends ) )
|
|
content_updates.extend( ( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_PETITION, ( pair, self._pairs_to_reasons[ pair ] ) ) for pair in new_petitions ) )
|
|
content_updates.extend( ( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_RESCIND_PETITION, pair ) for pair in rescinded_petitions ) )
|
|
|
|
|
|
return ( self._service_key, content_updates )
|
|
|
|
|
|
def SetNew( self, new, parents = [] ):
|
|
|
|
if new is None: self._new_sibling.SetLabel( '' )
|
|
else:
|
|
|
|
if new in self._old_siblings.GetTags(): self._old_siblings.AddTag( new )
|
|
|
|
self._new_sibling.SetLabel( new )
|
|
|
|
|
|
self._current_new = new
|
|
|
|
self._SetButtonStatus()
|
|
|
|
|
|
def SetTagBoxFocus( self ):
|
|
|
|
if len( self._old_siblings.GetTags() ) == 0: self._old_input.SetFocus()
|
|
else: self._new_input.SetFocus()
|
|
|
|
|
|
|
|
class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent, file_service_key, media ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._tag_repositories = ClientGUICommon.ListBook( self )
|
|
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
|
|
|
|
self._apply = wx.Button( self, id = wx.ID_OK, label = 'apply' )
|
|
self._apply.Bind( wx.EVT_BUTTON, self.EventOK )
|
|
self._apply.SetForegroundColour( ( 0, 128, 0 ) )
|
|
|
|
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
|
|
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
services = HC.app.GetManager( 'services' ).GetServices( ( HC.TAG_REPOSITORY, HC.LOCAL_TAG ) )
|
|
|
|
name_to_select = None
|
|
|
|
for service in services:
|
|
|
|
service_key = service.GetServiceKey()
|
|
service_type = service.GetServiceType()
|
|
name = service.GetName()
|
|
|
|
page_info = ( self._Panel, ( self._tag_repositories, self._file_service_key, service.GetServiceKey(), media ), {} )
|
|
|
|
self._tag_repositories.AddPage( page_info, name )
|
|
|
|
if service_key == HC.options[ 'default_tag_repository' ]: name_to_select = name
|
|
|
|
|
|
if name_to_select is not None: self._tag_repositories.Select( name_to_select )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
buttonbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
buttonbox.AddF( self._apply, FLAGS_MIXED )
|
|
buttonbox.AddF( self._cancel, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._tag_repositories, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( buttonbox, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
self.SetInitialSize( ( x + 200, 500 ) )
|
|
|
|
|
|
self._file_service_key = file_service_key
|
|
|
|
self._hashes = set()
|
|
|
|
for m in media: self._hashes.update( m.GetHashes() )
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage tags for ' + HC.ConvertIntToPrettyString( len( self._hashes ) ) + ' files' )
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
self.Bind( wx.EVT_MENU, self.EventMenu )
|
|
|
|
self.RefreshAcceleratorTable()
|
|
|
|
|
|
def _SetSearchFocus( self ):
|
|
|
|
page = self._tag_repositories.GetCurrentPage()
|
|
|
|
page.SetTagBoxFocus()
|
|
|
|
|
|
def EventMenu( self, event ):
|
|
|
|
action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
|
|
|
|
if action is not None:
|
|
|
|
( command, data ) = action
|
|
|
|
if command == 'manage_tags': self.EndModal( wx.ID_CANCEL )
|
|
elif command == 'set_search_focus': self._SetSearchFocus()
|
|
elif command == 'ok': self.EventOK( event )
|
|
else: event.Skip()
|
|
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
try:
|
|
|
|
service_keys_to_content_updates = {}
|
|
|
|
for page in self._tag_repositories.GetNameToPageDict().values():
|
|
|
|
( service_key, content_updates ) = page.GetContentUpdates()
|
|
|
|
service_keys_to_content_updates[ service_key ] = content_updates
|
|
|
|
|
|
if len( service_keys_to_content_updates ) > 0: HC.app.Write( 'content_updates', service_keys_to_content_updates )
|
|
|
|
finally: self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventServiceChanged( self, event ):
|
|
|
|
page = self._tag_repositories.GetCurrentPage()
|
|
|
|
wx.CallAfter( page.SetTagBoxFocus )
|
|
|
|
|
|
def RefreshAcceleratorTable( self ):
|
|
|
|
interested_actions = [ 'manage_tags', 'set_search_focus' ]
|
|
|
|
entries = []
|
|
|
|
for ( modifier, key_dict ) in HC.options[ 'shortcuts' ].items(): entries.extend( [ ( modifier, key, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) for ( key, action ) in key_dict.items() if action in interested_actions ] )
|
|
|
|
self.SetAcceleratorTable( wx.AcceleratorTable( entries ) )
|
|
|
|
|
|
class _Panel( wx.Panel ):
|
|
|
|
def __init__( self, parent, file_service_key, tag_service_key, media ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._tags_box_sorter = ClientGUICommon.TagsBoxCountsSorter( self, 'tags' )
|
|
|
|
self._tags_box = ClientGUICommon.TagsBoxCountsSimple( self._tags_box_sorter, self.AddTag )
|
|
|
|
self._tags_box_sorter.SetTagsBox( self._tags_box )
|
|
|
|
self._show_deleted_checkbox = wx.CheckBox( self._tags_box_sorter, label = 'show deleted' )
|
|
self._show_deleted_checkbox.Bind( wx.EVT_CHECKBOX, self.EventShowDeleted )
|
|
|
|
self._tags_box_sorter.AddF( self._show_deleted_checkbox, FLAGS_LONE_BUTTON )
|
|
|
|
self._add_tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddTag, self._file_service_key, self._tag_service_key )
|
|
|
|
self._modify_mappers = wx.Button( self, label = 'Modify mappers' )
|
|
self._modify_mappers.Bind( wx.EVT_BUTTON, self.EventModify )
|
|
|
|
self._copy_tags = wx.Button( self, label = 'copy tags' )
|
|
self._copy_tags.Bind( wx.EVT_BUTTON, self.EventCopyTags )
|
|
|
|
self._paste_tags = wx.Button( self, label = 'paste tags' )
|
|
self._paste_tags.Bind( wx.EVT_BUTTON, self.EventPasteTags )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
self._tags_box.ChangeTagRepository( self._tag_service_key )
|
|
|
|
self._tags_box.SetTagsByMedia( self._media )
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
|
|
|
if self._i_am_local_tag_service: self._modify_mappers.Hide()
|
|
else:
|
|
|
|
if not ( self._account.HasPermission( HC.POST_DATA ) or self._account.IsUnknownAccount() ): self._add_tag_box.Hide()
|
|
if not self._account.HasPermission( HC.MANAGE_USERS ): self._modify_mappers.Hide()
|
|
|
|
|
|
copy_paste_hbox = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
copy_paste_hbox.AddF( self._copy_tags, FLAGS_MIXED )
|
|
copy_paste_hbox.AddF( self._paste_tags, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._tags_box_sorter, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( self._add_tag_box, FLAGS_EXPAND_PERPENDICULAR )
|
|
vbox.AddF( copy_paste_hbox, FLAGS_BUTTON_SIZER )
|
|
vbox.AddF( self._modify_mappers, FLAGS_BUTTON_SIZER )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
|
|
wx.Panel.__init__( self, parent )
|
|
|
|
self._file_service_key = file_service_key
|
|
self._tag_service_key = tag_service_key
|
|
|
|
self._i_am_local_tag_service = self._tag_service_key == HC.LOCAL_TAG_SERVICE_KEY
|
|
|
|
self._hashes = { hash for hash in itertools.chain.from_iterable( ( m.GetHashes() for m in media ) ) }
|
|
|
|
self._content_updates = []
|
|
|
|
if not self._i_am_local_tag_service:
|
|
|
|
service = HC.app.GetManager( 'services' ).GetService( tag_service_key )
|
|
|
|
self._account = service.GetInfo( 'account' )
|
|
|
|
|
|
hashes = set( itertools.chain.from_iterable( ( m.GetHashes() for m in media ) ) )
|
|
|
|
media_results = HC.app.Read( 'media_results', self._file_service_key, hashes )
|
|
|
|
# this should now be a nice clean copy of the original media
|
|
self._media = [ ClientGUIMixins.MediaSingleton( media_result ) for media_result in media_results ]
|
|
|
|
tags_managers = [ m.GetTagsManager() for m in self._media ]
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
|
|
def _AddTag( self, tag, only_add = False ):
|
|
|
|
tag_managers = [ m.GetTagsManager() for m in self._media ]
|
|
|
|
num_files = len( self._media )
|
|
|
|
num_current = len( [ 1 for tag_manager in tag_managers if tag in tag_manager.GetCurrent( self._tag_service_key ) ] )
|
|
|
|
choices = []
|
|
|
|
if self._i_am_local_tag_service:
|
|
|
|
if num_current < num_files: choices.append( ( 'add ' + tag + ' to ' + HC.ConvertIntToPrettyString( num_files - num_current ) + ' files', HC.CONTENT_UPDATE_ADD ) )
|
|
if num_current > 0 and not only_add: choices.append( ( 'delete ' + tag + ' from ' + HC.ConvertIntToPrettyString( num_current ) + ' files', HC.CONTENT_UPDATE_DELETE ) )
|
|
|
|
else:
|
|
|
|
num_pending = len( [ 1 for tag_manager in tag_managers if tag in tag_manager.GetPending( self._tag_service_key ) ] )
|
|
num_petitioned = len( [ 1 for tag_manager in tag_managers if tag in tag_manager.GetPetitioned( self._tag_service_key ) ] )
|
|
|
|
if num_current + num_pending < num_files: choices.append( ( 'pend ' + tag + ' to ' + HC.ConvertIntToPrettyString( num_files - ( num_current + num_pending ) ) + ' files', HC.CONTENT_UPDATE_PENDING ) )
|
|
if num_current > num_petitioned and not only_add: choices.append( ( 'petition ' + tag + ' from ' + HC.ConvertIntToPrettyString( num_current - num_petitioned ) + ' files', HC.CONTENT_UPDATE_PETITION ) )
|
|
if num_pending > 0 and not only_add: choices.append( ( 'rescind pending ' + tag + ' from ' + HC.ConvertIntToPrettyString( num_pending ) + ' files', HC.CONTENT_UPDATE_RESCIND_PENDING ) )
|
|
if num_petitioned > 0: choices.append( ( 'rescind petitioned ' + tag + ' from ' + HC.ConvertIntToPrettyString( num_petitioned ) + ' files', HC.CONTENT_UPDATE_RESCIND_PETITION ) )
|
|
|
|
|
|
if len( choices ) == 0: return
|
|
elif len( choices ) > 1:
|
|
|
|
intro = 'What would you like to do?'
|
|
|
|
with ClientGUIDialogs.DialogButtonChoice( self, intro, choices ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK: choice = dlg.GetData()
|
|
else: return
|
|
|
|
|
|
else: [ ( text, choice ) ] = choices
|
|
|
|
if choice == HC.CONTENT_UPDATE_ADD: media_to_affect = ( m for m in self._media if tag not in m.GetTagsManager().GetCurrent( self._tag_service_key ) )
|
|
elif choice == HC.CONTENT_UPDATE_DELETE: media_to_affect = ( m for m in self._media if tag in m.GetTagsManager().GetCurrent( self._tag_service_key ) )
|
|
elif choice == HC.CONTENT_UPDATE_PENDING: media_to_affect = ( m for m in self._media if tag not in m.GetTagsManager().GetCurrent( self._tag_service_key ) and tag not in m.GetTagsManager().GetPending( self._tag_service_key ) )
|
|
elif choice == HC.CONTENT_UPDATE_PETITION: media_to_affect = ( m for m in self._media if tag in m.GetTagsManager().GetCurrent( self._tag_service_key ) and tag not in m.GetTagsManager().GetPetitioned( self._tag_service_key ) )
|
|
elif choice == HC.CONTENT_UPDATE_RESCIND_PENDING: media_to_affect = ( m for m in self._media if tag in m.GetTagsManager().GetPending( self._tag_service_key ) )
|
|
elif choice == HC.CONTENT_UPDATE_RESCIND_PETITION: media_to_affect = ( m for m in self._media if tag in m.GetTagsManager().GetPetitioned( self._tag_service_key ) )
|
|
|
|
hashes = set( itertools.chain.from_iterable( ( m.GetHashes() for m in media_to_affect ) ) )
|
|
|
|
if choice == HC.CONTENT_UPDATE_PETITION:
|
|
|
|
if self._account.HasPermission( HC.RESOLVE_PETITIONS ): reason = 'admin'
|
|
else:
|
|
|
|
message = 'Enter a reason for this tag to be removed. A janitor will review your petition.'
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
|
|
else: return
|
|
|
|
|
|
|
|
content_update = HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, choice, ( tag, hashes, reason ) )
|
|
|
|
else: content_update = HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, choice, ( tag, hashes ) )
|
|
|
|
for m in self._media: m.GetMediaResult().ProcessContentUpdate( self._tag_service_key, content_update )
|
|
|
|
self._content_updates.append( content_update )
|
|
|
|
self._tags_box.SetTagsByMedia( self._media, force_reload = True )
|
|
|
|
|
|
def AddTag( self, tag, parents = [] ):
|
|
|
|
if tag is None: wx.PostEvent( self, wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'ok' ) ) )
|
|
else:
|
|
|
|
self._AddTag( tag )
|
|
|
|
for parent in parents: self._AddTag( parent, only_add = True )
|
|
|
|
|
|
|
|
def EventCopyTags( self, event ):
|
|
|
|
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = CC.GetMediasTagCount( self._media, self._tag_service_key )
|
|
|
|
tags = set( current_tags_to_count.keys() ).union( pending_tags_to_count.keys() )
|
|
|
|
text = yaml.safe_dump( tags )
|
|
|
|
HC.pubsub.pub( 'clipboard', 'text', text )
|
|
|
|
|
|
def EventModify( self, event ):
|
|
|
|
tag = self._tags_box.GetSelectedTag()
|
|
|
|
if tag is not None:
|
|
|
|
subject_identifiers = [ HC.AccountIdentifier( hash = hash, tag = tag ) for hash in self._hashes ]
|
|
|
|
with ClientGUIDialogs.DialogModifyAccounts( self, self._tag_service_key, subject_identifiers ) as dlg: dlg.ShowModal()
|
|
|
|
|
|
|
|
def EventPasteTags( self, event ):
|
|
|
|
if wx.TheClipboard.Open():
|
|
|
|
data = wx.TextDataObject()
|
|
|
|
wx.TheClipboard.GetData( data )
|
|
|
|
wx.TheClipboard.Close()
|
|
|
|
text = data.GetText()
|
|
|
|
try:
|
|
|
|
tags = yaml.safe_load( text )
|
|
|
|
for tag in tags: self._AddTag( tag, only_add = True )
|
|
|
|
except: wx.MessageBox( 'I could not understand what was in the clipboard' )
|
|
|
|
else: wx.MessageBox( 'I could not get permission to access the clipboard.' )
|
|
|
|
|
|
def EventShowDeleted( self, event ):
|
|
|
|
self._tags_box.SetShow( 'deleted', self._show_deleted_checkbox.GetValue() )
|
|
|
|
|
|
def GetContentUpdates( self ): return ( self._tag_service_key, self._content_updates )
|
|
|
|
def GetServiceKey( self ): return self._tag_service_key
|
|
|
|
def HasChanges( self ): return len( self._content_updates ) > 0
|
|
|
|
def SetTagBoxFocus( self ):
|
|
|
|
if self._i_am_local_tag_service or self._account.HasPermission( HC.POST_DATA ) or self._account.IsUnknownAccount(): self._add_tag_box.SetFocus()
|
|
|
|
|
|
|
|
class DialogManageUPnP( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent, service_key ):
|
|
|
|
def InitialiseControls():
|
|
|
|
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
|
|
|
|
self._mappings_list_ctrl = ClientGUICommon.SaneListCtrl( self, 480, [ ( 'description', -1 ), ( 'internal ip', 100 ), ( 'internal port', 80 ), ( 'external ip', 100 ), ( 'external port', 80 ), ( 'protocol', 80 ), ( 'lease', 80 ) ] )
|
|
|
|
self._add_local = wx.Button( self, label = 'add service mapping' )
|
|
self._add_local.Bind( wx.EVT_BUTTON, self.EventAddServiceMapping )
|
|
|
|
self._add_custom = wx.Button( self, label = 'add custom mapping' )
|
|
self._add_custom.Bind( wx.EVT_BUTTON, self.EventAddCustomMapping )
|
|
|
|
self._edit = wx.Button( self, label = 'edit mapping' )
|
|
self._edit.Bind( wx.EVT_BUTTON, self.EventEditMapping )
|
|
|
|
self._remove = wx.Button( self, label = 'remove mapping' )
|
|
self._remove.Bind( wx.EVT_BUTTON, self.EventRemoveMapping )
|
|
|
|
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 ) )
|
|
|
|
|
|
def PopulateControls():
|
|
|
|
self._RefreshMappings()
|
|
|
|
|
|
def ArrangeControls():
|
|
|
|
edit_buttons = wx.BoxSizer( wx.HORIZONTAL )
|
|
|
|
if self._service_key == HC.LOCAL_FILE_SERVICE_KEY: self._add_local.Hide()
|
|
|
|
edit_buttons.AddF( self._add_local, FLAGS_MIXED )
|
|
edit_buttons.AddF( self._add_custom, FLAGS_MIXED )
|
|
edit_buttons.AddF( self._edit, FLAGS_MIXED )
|
|
edit_buttons.AddF( self._remove, FLAGS_MIXED )
|
|
|
|
vbox = wx.BoxSizer( wx.VERTICAL )
|
|
|
|
vbox.AddF( self._mappings_list_ctrl, FLAGS_EXPAND_BOTH_WAYS )
|
|
vbox.AddF( edit_buttons, FLAGS_BUTTON_SIZER )
|
|
vbox.AddF( self._ok, FLAGS_LONE_BUTTON )
|
|
|
|
self.SetSizer( vbox )
|
|
|
|
( x, y ) = self.GetEffectiveMinSize()
|
|
|
|
x = max( x, 760 )
|
|
|
|
self.SetInitialSize( ( x, y ) )
|
|
|
|
|
|
if service_key == HC.LOCAL_FILE_SERVICE_KEY: title = 'manage local upnp'
|
|
else:
|
|
|
|
service = HC.app.GetManager( 'services' ).GetService( service_key )
|
|
|
|
title = 'manage upnp for ' + service.GetName()
|
|
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, title )
|
|
|
|
self._service_key = service_key
|
|
|
|
InitialiseControls()
|
|
|
|
PopulateControls()
|
|
|
|
ArrangeControls()
|
|
|
|
wx.CallAfter( self._ok.SetFocus )
|
|
|
|
|
|
def _RefreshMappings( self ):
|
|
|
|
self._mappings_list_ctrl.DeleteAllItems()
|
|
|
|
if self._service_key == HC.LOCAL_FILE_SERVICE_KEY: self._mappings = HydrusNATPunch.GetUPnPMappings()
|
|
else:
|
|
|
|
wx.MessageBox( 'get mappings from service' )
|
|
|
|
|
|
for mapping in self._mappings: self._mappings_list_ctrl.Append( mapping, mapping )
|
|
|
|
self._mappings_list_ctrl.SortListItems( 1 )
|
|
|
|
|
|
def EventAddCustomMapping( self, event ):
|
|
|
|
do_refresh = False
|
|
|
|
external_port = HC.DEFAULT_SERVICE_PORT
|
|
protocol = 'TCP'
|
|
internal_port = HC.DEFAULT_SERVICE_PORT
|
|
description = 'hydrus service'
|
|
duration = 0
|
|
|
|
with ClientGUIDialogs.DialogInputUPnPMapping( self, external_port, protocol, internal_port, description, duration ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
( external_port, protocol, internal_port, description, duration ) = dlg.GetInfo()
|
|
|
|
for ( existing_description, existing_internal_ip, existing_internal_port, existing_external_ip, existing_external_port, existing_protocol, existing_lease ) in self._mappings:
|
|
|
|
if external_port == existing_external_port and protocol == existing_protocol:
|
|
|
|
wx.MessageBox( 'That external port already exists!' )
|
|
|
|
return
|
|
|
|
|
|
|
|
internal_client = HydrusNATPunch.GetLocalIP()
|
|
|
|
HydrusNATPunch.AddUPnPMapping( internal_client, internal_port, external_port, protocol, description, duration = duration )
|
|
|
|
do_refresh = True
|
|
|
|
|
|
|
|
if do_refresh: self._RefreshMappings()
|
|
|
|
|
|
def EventAddServiceMapping( self, event ):
|
|
|
|
# start dialog with helpful default values
|
|
# attempt to add mapping via service
|
|
# add to listctrl
|
|
|
|
pass
|
|
|
|
|
|
def EventEditMapping( self, event ):
|
|
|
|
do_refresh = False
|
|
|
|
for index in self._mappings_list_ctrl.GetAllSelected():
|
|
|
|
( description, internal_ip, internal_port, external_ip, external_port, protocol, duration ) = self._mappings_list_ctrl.GetClientData( index )
|
|
|
|
with ClientGUIDialogs.DialogInputUPnPMapping( self, external_port, protocol, internal_port, description, duration ) as dlg:
|
|
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
|
|
( external_port, protocol, internal_port, description, duration ) = dlg.GetInfo()
|
|
|
|
HydrusNATPunch.RemoveUPnPMapping( external_port, protocol )
|
|
|
|
internal_client = HydrusNATPunch.GetLocalIP()
|
|
|
|
HydrusNATPunch.AddUPnPMapping( internal_client, internal_port, external_port, protocol, description, duration = duration )
|
|
|
|
do_refresh = True
|
|
|
|
|
|
|
|
|
|
if do_refresh: self._RefreshMappings()
|
|
|
|
|
|
def EventOK( self, event ):
|
|
|
|
self.EndModal( wx.ID_OK )
|
|
|
|
|
|
def EventRemoveMapping( self, event ):
|
|
|
|
do_refresh = False
|
|
|
|
for index in self._mappings_list_ctrl.GetAllSelected():
|
|
|
|
( description, internal_ip, internal_port, external_ip, external_port, protocol, duration ) = self._mappings_list_ctrl.GetClientData( index )
|
|
|
|
HydrusNATPunch.RemoveUPnPMapping( external_port, protocol )
|
|
|
|
do_refresh = True
|
|
|
|
|
|
if do_refresh: self._RefreshMappings()
|
|
|
|
|