hydrus/include/ClientGUIDialogsManage.py

7208 lines
291 KiB
Python

import Crypto.PublicKey.RSA
import HydrusConstants as HC
import HydrusEncryption
import HydrusTags
import ClientConstants as CC
import ClientConstantsMessages
import ClientGUICommon
import ClientGUIDialogs
import collections
import itertools
import os
import random
import re
import string
import subprocess
import time
import traceback
import urllib
import wx
import yaml
import zipfile
# 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_SIZERS = 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, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
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_SIZERS )
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 EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
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 ):
try:
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 )
headers = {}
headers[ 'Content-Type' ] = ct
connection = HC.get_connection( url = 'https://sys.4chan.org/', accept_cookies = True )
response = connection.request( 'POST', '/auth', headers = headers, body = body )
self._timeout = HC.GetNow() + 365 * 24 * 3600
HC.app.Write( '4chan_pass', token, pin, self._timeout )
self._SetStatus()
except Exception as e:
wx.MessageBox( traceback.format_exc() )
wx.MessageBox( HC.u( e ) )
class DialogManageAccountTypes( ClientGUIDialogs.Dialog ):
def __init__( self, parent, service_identifier ):
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, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
service = HC.app.Read( 'service', service_identifier )
connection = service.GetConnection()
account_types = connection.Get( '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, max_num_requests ) = account_type.GetMaxMonthlyData()
( max_num_bytes_string, max_num_requests_string ) = account_type.GetMaxMonthlyDataString()
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_SIZERS )
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_SIZERS )
self.SetSizer( vbox )
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage account types' )
self._service_identifier = service_identifier
self._edit_log = []
InitialiseControls()
PopulateControls()
ArrangeControls()
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( 980, y ) )
wx.CallAfter( self._apply.SetFocus )
def EventAdd( self, event ):
try:
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, max_num_requests ) = account_type.GetMaxMonthlyData()
( max_num_bytes_string, max_num_requests_string ) = account_type.GetMaxMonthlyDataString()
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( ( '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 ) )
except Exception as e: wx.MessageBox( HC.u( e ) )
def EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
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( ( '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 ]
try:
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, max_num_requests ) = account_type.GetMaxMonthlyData()
( max_num_bytes_string, max_num_requests_string ) = account_type.GetMaxMonthlyDataString()
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( ( '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 ) )
except Exception as e: wx.MessageBox( HC.u( e ) )
def EventOK( self, event ):
try:
service = HC.app.Read( 'service', self._service_identifier )
connection = service.GetConnection()
connection.Post( 'account_types_modification', edit_log = self._edit_log )
except Exception as e: wx.MessageBox( HC.u( e ) )
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, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
boorus = HC.app.Read( 'boorus' )
for booru in boorus:
name = booru.GetName()
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_SIZERS )
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 wx.TextEntryDialog( 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( ( 'add', name ) )
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 EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
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 HC.o( 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( ( 'edit', ( name, page.GetBooru() ) ) )
try:
if len( self._edit_log ) > 0: HC.app.Write( 'update_boorus', self._edit_log )
except Exception as e: wx.MessageBox( 'Saving boorus to DB raised this error: ' + HC.u( e ) )
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( ( 'delete', name ) )
self._boorus.DeleteCurrentPage()
def Import( self, paths ):
for path in paths:
try:
with HC.o( 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( ( 'add', name ) )
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, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
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_SIZERS )
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 wx.TextEntryDialog( self, 'Enter contact\'s address in the form contact_key@hostname:port' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
try:
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( ( 'add', contact ) )
page = self._Panel( self._contacts, contact, is_identity = False )
self._deletable_names.add( name )
self._contacts.AddPage( page, name, select = True )
except Exception as e:
wx.MessageBox( HC.u( e ) )
self.EventAddByContactAddress( event )
def EventAddManually( self, event ):
try: self._CheckCurrentContactIsValid()
except Exception as e:
wx.MessageBox( HC.u( e ) )
return
with wx.TextEntryDialog( self, 'Enter new contact\'s name' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
try:
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( ( 'add', contact ) )
page = self._Panel( self._contacts, contact, is_identity = False )
self._deletable_names.add( name )
self._contacts.AddPage( page, name, select = True )
except Exception as e:
wx.MessageBox( HC.u( e ) )
self.EventAddManually( event )
def EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
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 HC.o( 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 HC.o( 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( ( 'edit', ( page.GetOriginalName(), page.GetContact() ) ) )
try:
if len( self._edit_log ) > 0: HC.app.Write( 'update_contacts', self._edit_log )
except Exception as e: wx.MessageBox( 'Saving contacts to DB raised this error: ' + HC.u( e ) )
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( ( '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 HC.o( 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, 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( ( '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( ( '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 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, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
self._edit_log = []
sites = HC.app.Read( 'imageboards' )
for ( name, imageboards ) in sites:
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_SIZERS )
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 wx.TextEntryDialog( 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( ( 'add', 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 EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
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 HC.o( 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( ( 'edit', ( name, page.GetChanges() ) ) )
try:
if len( self._edit_log ) > 0: HC.app.Write( 'update_imageboards', self._edit_log )
except Exception as e: wx.MessageBox( 'Saving imageboards to DB raised this error: ' + HC.u( e ) )
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( ( 'delete', name ) )
self._sites.DeleteCurrentPage()
def Import( self, paths ):
for path in paths:
try:
with HC.o( 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( ( 'add', 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 ):
wx.Panel.__init__( self, parent )
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():
self._edit_log = []
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 )
InitialiseControls()
PopulateControls()
ArrangeControls()
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( 980, y ) )
def EventAdd( self, event ):
with wx.TextEntryDialog( 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, [], {} )
self._edit_log.append( ( 'add', name ) )
page = self._Panel( self._imageboards, imageboard )
self._imageboards.AddPage( page, name, select = 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 HC.o( 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( ( 'delete', name ) )
self._imageboards.DeleteCurrentPage()
def GetChanges( self ):
for page in self._imageboards.GetNameToPageDict().values():
if page.HasChanges(): self._edit_log.append( ( 'edit', page.GetImageboard() ) )
return self._edit_log
def GetImageboards( self ): return [ page.GetImageboard() for page in self._imageboards.GetNameToPageDict().values() ]
def HasChanges( self ): return len( self._edit_log ) > 0 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( ( 'add', name ) )
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, type, default, editable ) in form_fields:
self._form_fields.Append( ( name, CC.field_string_lookup[ type ], HC.u( default ), HC.u( editable ) ), ( name, 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_SIZERS )
#
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()
# list instead of tumple cause of yaml comparisons
form_fields = self._form_fields.GetClientData()
restrictions = {}
# yaml list again
value = self._min_resolution.GetValue()
if value is not None: restrictions[ CC.RESTRICTION_MIN_RESOLUTION ] = list( value )
# yaml list again
value = self._max_resolution.GetValue()
if value is not None: restrictions[ CC.RESTRICTION_MAX_RESOLUTION ] = list( 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 ):
try:
with ClientGUIDialogs.DialogInputNewFormField( self ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( name, 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[ type ], HC.u( default ), HC.u( editable ) ), ( name, type, default, editable ) )
except Exception as e: wx.MessageBox( HC.u( e ) )
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, type, default, editable ) = self._form_fields.GetClientData( index )
form_field = ( name, type, default, editable )
try:
with ClientGUIDialogs.DialogInputNewFormField( self, form_field ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
old_name = name
( name, 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[ type ], HC.u( default ), HC.u( editable ) ), ( name, type, default, editable ) )
except Exception as e: wx.MessageBox( HC.u( e ) )
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, type, default, editable ) in form_fields:
self._form_fields.Append( ( name, CC.field_string_lookup[ type ], HC.u( default ), HC.u( editable ) ), ( name, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
self._original_import_folders = HC.app.Read( 'import_folders' )
for ( path, details ) in self._original_import_folders:
type = details[ 'type' ]
check_period = details[ 'check_period' ]
local_tag = details[ 'local_tag' ]
( pretty_type, pretty_check_period, pretty_local_tag ) = self._GetPrettyVariables( type, check_period, local_tag )
self._import_folders.Append( ( path, pretty_type, pretty_check_period, pretty_local_tag ), ( path, 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 )
vbox.AddF( self._import_folders, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( file_buttons, FLAGS_BUTTON_SIZERS )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
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, type, check_period, local_tag ) in all_existing_client_data ):
type = HC.IMPORT_FOLDER_TYPE_SYNCHRONISE
check_period = 15 * 60
local_tag = None
with DialogManageImportFoldersEdit( self, path, type, check_period, local_tag ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( path, type, check_period, local_tag ) = dlg.GetInfo()
( pretty_type, pretty_check_period, pretty_local_tag ) = self._GetPrettyVariables( type, check_period, local_tag )
self._import_folders.Append( ( path, pretty_type, pretty_check_period, pretty_local_tag ), ( path, type, check_period, local_tag ) )
def _AddFolders( self, paths ):
for path in paths:
if os.path.isdir( path ): self._AddFolder( path )
def _GetPrettyVariables( self, type, check_period, local_tag ):
if type == HC.IMPORT_FOLDER_TYPE_DELETE: pretty_type = 'delete'
elif 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 EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
def EventDelete( self, event ): self._import_folders.RemoveAllSelected()
def EventEdit( self, event ):
indices = self._import_folders.GetAllSelected()
for index in indices:
( path, type, check_period, local_tag ) = self._import_folders.GetClientData( index )
with DialogManageImportFoldersEdit( self, path, type, check_period, local_tag ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( path, type, check_period, local_tag ) = dlg.GetInfo()
( pretty_type, pretty_check_period, pretty_local_tag ) = self._GetPrettyVariables( type, check_period, local_tag )
self._import_folders.UpdateRow( index, ( path, pretty_type, pretty_check_period, pretty_local_tag ), ( path, type, check_period, local_tag ) )
def EventOK( self, event ):
client_data = self._import_folders.GetClientData()
original_paths_to_details = dict( self._original_import_folders )
import_folders = []
for ( path, type, check_period, local_tag ) in client_data:
if path in original_paths_to_details: details = original_paths_to_details[ path ]
else: details = { 'last_checked' : 0, 'cached_imported_paths' : set(), 'failed_imported_paths' : set() }
details[ 'type' ] = type
details[ 'check_period' ] = check_period
details[ 'local_tag'] = local_tag
import_folders.append( ( path, details ) )
HC.app.Write( 'import_folders', import_folders )
self.EndModal( wx.ID_OK )
class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
def __init__( self, parent, path, type, check_period, local_tag ):
def InitialiseControls():
self._path = wx.DirPickerCtrl( self, style = wx.DIRP_USE_TEXTCTRL )
self._type = ClientGUICommon.BetterChoice( self )
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._check_period = wx.SpinCtrl( self )
self._local_tag = wx.TextCtrl( self )
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( 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():
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self, label='path:' ), FLAGS_MIXED )
gridbox.AddF( self._path, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label='type:' ), FLAGS_MIXED )
gridbox.AddF( self._type, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label='check period (minutes):' ), FLAGS_MIXED )
gridbox.AddF( self._check_period, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self, label='local tag:' ), FLAGS_MIXED )
gridbox.AddF( self._local_tag, FLAGS_EXPAND_BOTH_WAYS )
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, FLAGS_MIXED )
buttons.AddF( self._cancel, FLAGS_MIXED )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( gridbox, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
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()
type = self._type.GetChoice()
check_period = self._check_period.GetValue() * 60
local_tag = self._local_tag.GetValue()
return ( path, type, check_period, local_tag )
class DialogManageOptionsFileRepository( ClientGUIDialogs.Dialog ):
def __init__( self, parent, service_identifier ):
def InitialiseControls():
self._file_repository_panel = ClientGUICommon.StaticBox( self, 'file repository' )
self._max_monthly_data = ClientGUICommon.NoneableSpinCtrl( self._file_repository_panel, 'max monthly data (MB)', multiplier = 1048576 )
self._max_storage = ClientGUICommon.NoneableSpinCtrl( self._file_repository_panel, 'max storage (MB)', multiplier = 1048576 )
self._log_uploader_ips = wx.CheckBox( self._file_repository_panel, label='' )
self._message = wx.TextCtrl( self._file_repository_panel )
self._ok = wx.Button( self, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
self._service = HC.app.Read( 'service', self._service_identifier )
connection = self._service.GetConnection()
options = connection.Get( 'options' )
self._max_monthly_data.SetValue( options[ 'max_monthly_data' ] )
self._max_storage.SetValue( options[ 'max_storage' ] )
self._log_uploader_ips.SetValue( options[ 'log_uploader_ips' ] )
self._message.SetValue( options[ 'message' ] )
def ArrangeControls():
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self._file_repository_panel, label='Log uploader ips?' ), FLAGS_MIXED )
gridbox.AddF( self._log_uploader_ips, FLAGS_MIXED )
gridbox.AddF( wx.StaticText( self._file_repository_panel, label='Message' ), FLAGS_MIXED )
gridbox.AddF( self._message, FLAGS_MIXED )
self._file_repository_panel.AddF( self._max_monthly_data, FLAGS_EXPAND_PERPENDICULAR )
self._file_repository_panel.AddF( self._max_storage, FLAGS_EXPAND_PERPENDICULAR )
self._file_repository_panel.AddF( gridbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
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._file_repository_panel, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x + 80, y ) )
ClientGUIDialogs.Dialog.__init__( self, parent, service_identifier.GetName() + ' options' )
self._service_identifier = service_identifier
InitialiseControls()
PopulateControls()
ArrangeControls()
wx.CallAfter( self._ok.SetFocus )
def EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
def EventOK( self, event ):
options = {}
options[ 'max_monthly_data' ] = self._max_monthly_data.GetValue()
options[ 'max_storage' ] = self._max_storage.GetValue()
options[ 'log_uploader_ips' ] = self._log_uploader_ips.GetValue()
options[ 'message' ] = self._message.GetValue()
try:
connection = self._service.GetConnection()
connection.Post( 'options', options = options )
except Exception as e: wx.MessageBox( 'Something went wrong when trying to send the options to the file repository: ' + HC.u( e ) )
self.EndModal( wx.ID_OK )
class DialogManageOptionsLocal( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
def InitialiseControls():
self._listbook = ClientGUICommon.ListBook( self )
# files and memory
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_cache_size = wx.SpinCtrl( self._file_page, min = 10, max = 3000 )
self._thumbnail_cache_size.Bind( wx.EVT_SPINCTRL, self.EventThumbnailsUpdate )
self._estimated_number_thumbnails = wx.StaticText( self._file_page, label = '' )
self._preview_cache_size = wx.SpinCtrl( self._file_page, min = 20, max = 3000 )
self._preview_cache_size.Bind( wx.EVT_SPINCTRL, self.EventPreviewsUpdate )
self._estimated_number_previews = wx.StaticText( self._file_page, label = '' )
self._fullscreen_cache_size = wx.SpinCtrl( self._file_page, min = 100, max = 3000 )
self._fullscreen_cache_size.Bind( wx.EVT_SPINCTRL, self.EventFullscreensUpdate )
self._estimated_number_fullscreens = wx.StaticText( 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._num_autocomplete_chars = wx.SpinCtrl( self._file_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._listbook.AddPage( self._file_page, 'files and memory' )
# gui
self._gui_page = wx.Panel( self._listbook )
self._gui_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
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 = wx.Choice( self._gui_page )
self._fullscreen_borderless = wx.CheckBox( 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' )
# colours
self._colour_page = wx.Panel( self._listbook )
self._colour_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
self._namespace_colours = ClientGUICommon.TagsBoxOptions( self._colour_page, self._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' )
# 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 = self._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' )
#
self._ok = wx.Button( self, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
if self._options[ 'export_path' ] is not None: self._export_location.SetPath( HC.ConvertPortablePathToAbsPath( self._options[ 'export_path' ] ) )
self._exclude_deleted_files.SetValue( self._options[ 'exclude_deleted_files' ] )
self._thumbnail_cache_size.SetValue( int( self._options[ 'thumbnail_cache_size' ] / 1048576 ) )
self._preview_cache_size.SetValue( int( self._options[ 'preview_cache_size' ] / 1048576 ) )
self._fullscreen_cache_size.SetValue( int( self._options[ 'fullscreen_cache_size' ] / 1048576 ) )
( thumbnail_width, thumbnail_height ) = self._options[ 'thumbnail_dimensions' ]
self._thumbnail_width.SetValue( thumbnail_width )
self._thumbnail_height.SetValue( thumbnail_height )
self._num_autocomplete_chars.SetValue( self._options[ 'num_autocomplete_chars' ] )
#
self._confirm_client_exit.SetValue( self._options[ 'confirm_client_exit' ] )
self._gui_capitalisation.SetValue( self._options[ 'gui_capitalisation' ] )
self._gui_show_all_tags_in_autocomplete.SetValue( self._options[ 'show_all_tags_in_autocomplete' ] )
if self._options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_ASC: self._default_tag_sort.Select( 0 )
elif self._options[ 'default_tag_sort' ] == CC.SORT_BY_LEXICOGRAPHIC_DESC: self._default_tag_sort.Select( 1 )
elif self._options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_DESC: self._default_tag_sort.Select( 2 )
elif self._options[ 'default_tag_sort' ] == CC.SORT_BY_INCIDENCE_ASC: self._default_tag_sort.Select( 3 )
service_identifiers = HC.app.Read( 'service_identifiers', ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ) )
for service_identifier in service_identifiers: self._default_tag_repository.Append( service_identifier.GetName(), service_identifier )
self._default_tag_repository.SetStringSelection( self._options[ 'default_tag_repository' ].GetName() )
self._fullscreen_borderless.SetValue( self._options[ 'fullscreen_borderless' ] )
#
self._play_dumper_noises.SetValue( self._options[ 'play_dumper_noises' ] )
#
system_predicates = self._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, type ) = system_predicates[ 'mime' ]
self._file_system_predicate_mime_media.SetSelection( media )
self.EventFileSystemPredicateMime( None )
self._file_system_predicate_mime_type.SetSelection( 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 ( sort_by_type, sort_by ) in self._options[ 'sort_by' ]: self._sort_by.Append( '-'.join( sort_by ), sort_by )
#
for ( modifier, key_dict ) in self._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()
def ArrangeControls():
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._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='MB memory reserved for thumbnail cache: ' ), FLAGS_MIXED )
gridbox.AddF( thumbnails_sizer, FLAGS_NONE )
gridbox.AddF( wx.StaticText( self._file_page, label='MB memory reserved for preview cache: ' ), FLAGS_MIXED )
gridbox.AddF( previews_sizer, FLAGS_NONE )
gridbox.AddF( wx.StaticText( self._file_page, label='MB memory reserved for fullscreen cache: ' ), FLAGS_MIXED )
gridbox.AddF( fullscreens_sizer, FLAGS_NONE )
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 )
gridbox.AddF( wx.StaticText( self._file_page, label='Autocomplete character threshold: ' ), FLAGS_MIXED )
gridbox.AddF( self._num_autocomplete_chars, FLAGS_MIXED )
self._file_page.SetSizer( gridbox )
#
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
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 )
gridbox.AddF( wx.StaticText( self._gui_page, label='By default, show fullscreen without borders: ' ), FLAGS_MIXED )
gridbox.AddF( self._fullscreen_borderless, 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._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_SIZERS )
hbox.AddF( self._shortcuts_edit, FLAGS_BUTTON_SIZERS )
hbox.AddF( self._shortcuts_delete, FLAGS_BUTTON_SIZERS )
vbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._shortcuts_page.SetSizer( vbox )
#
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_SIZERS )
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._file_page.Layout ) # draws the static texts correctly
wx.CallAfter( self._ok.SetFocus )
def _SortListCtrl( self ): self._shortcuts.SortListItems( 2 )
def EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
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( 'jpeg', HC.IMAGE_JPEG )
self._file_system_predicate_mime_type.Append( 'png', HC.IMAGE_PNG )
self._file_system_predicate_mime_type.Append( 'gif', HC.IMAGE_GIF )
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 == 'video':
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 ):
self._options[ 'play_dumper_noises' ] = self._play_dumper_noises.GetValue()
self._options[ 'confirm_client_exit' ] = self._confirm_client_exit.GetValue()
self._options[ 'gui_capitalisation' ] = self._gui_capitalisation.GetValue()
self._options[ 'show_all_tags_in_autocomplete' ] = self._gui_show_all_tags_in_autocomplete.GetValue()
self._options[ 'fullscreen_borderless' ] = self._fullscreen_borderless.GetValue()
self._options[ 'export_path' ] = HC.ConvertAbsPathToPortablePath( self._export_location.GetPath() )
self._options[ 'default_sort' ] = self._default_sort.GetSelection()
self._options[ 'default_collect' ] = self._default_collect.GetChoice()
self._options[ 'exclude_deleted_files' ] = self._exclude_deleted_files.GetValue()
self._options[ 'thumbnail_cache_size' ] = self._thumbnail_cache_size.GetValue() * 1048576
self._options[ 'preview_cache_size' ] = self._preview_cache_size.GetValue() * 1048576
self._options[ 'fullscreen_cache_size' ] = self._fullscreen_cache_size.GetValue() * 1048576
self._options[ 'thumbnail_dimensions' ] = [ self._thumbnail_width.GetValue(), self._thumbnail_height.GetValue() ]
self._options[ 'num_autocomplete_chars' ] = self._num_autocomplete_chars.GetValue()
self._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 ) )
self._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() )
self._options[ 'file_system_predicates' ] = system_predicates
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
self._options[ 'shortcuts' ] = shortcuts
self._options[ 'default_tag_repository' ] = self._default_tag_repository.GetClientData( self._default_tag_repository.GetSelection() )
self._options[ 'default_tag_sort' ] = self._default_tag_sort.GetClientData( self._default_tag_sort.GetSelection() )
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 )
try:
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()
except Exception as e: wx.MessageBox( HC.u( e ) )
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 DialogManageOptionsServerAdmin( ClientGUIDialogs.Dialog ):
def __init__( self, parent, service_identifier ):
def InitialiseControls():
self._server_panel = ClientGUICommon.StaticBox( self, 'server' )
self._max_monthly_data = ClientGUICommon.NoneableSpinCtrl( self._server_panel, 'max monthly data (MB)', multiplier = 1048576 )
self._max_storage = ClientGUICommon.NoneableSpinCtrl( self._server_panel, 'max storage (MB)', multiplier = 1048576 )
self._message = wx.TextCtrl( self._server_panel )
self._ok = wx.Button( self, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
self._service = HC.app.Read( 'service', self._service_identifier )
connection = self._service.GetConnection()
options = connection.Get( 'options' )
self._max_monthly_data.SetValue( options[ 'max_monthly_data' ] )
self._max_storage.SetValue( options[ 'max_storage' ] )
self._message.SetValue( options[ 'message' ] )
def ArrangeControls():
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self._server_panel, label='Message' ), FLAGS_MIXED )
gridbox.AddF( self._message, FLAGS_MIXED )
self._server_panel.AddF( self._max_monthly_data, FLAGS_EXPAND_PERPENDICULAR )
self._server_panel.AddF( self._max_storage, FLAGS_EXPAND_PERPENDICULAR )
self._server_panel.AddF( gridbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
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._server_panel, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
ClientGUIDialogs.Dialog.__init__( self, parent, service_identifier.GetName() + ' options' )
self._service_identifier = service_identifier
InitialiseControls()
PopulateControls()
ArrangeControls()
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x + 80, y ) )
wx.CallAfter( self._ok.SetFocus )
def EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
def EventOK( self, event ):
options = {}
options[ 'max_monthly_data' ] = self._max_monthly_data.GetValue()
options[ 'max_storage' ] = self._max_storage.GetValue()
options[ 'message' ] = self._message.GetValue()
try:
connection = self._service.GetConnection()
connection.Post( 'options', options = options )
except Exception as e: wx.MessageBox( 'Something went wrong when trying to send the options to the server admin: ' + HC.u( e ) )
self.EndModal( wx.ID_OK )
class DialogManageOptionsTagRepository( ClientGUIDialogs.Dialog ):
def __init__( self, parent, service_identifier ):
def InitialiseControls():
self._tag_repository_panel = ClientGUICommon.StaticBox( self, 'tag repository' )
self._max_monthly_data = ClientGUICommon.NoneableSpinCtrl( self._tag_repository_panel, 'max monthly data (MB)', multiplier = 1048576 )
self._message = wx.TextCtrl( self._tag_repository_panel )
self._ok = wx.Button( self, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
self._service = HC.app.Read( 'service', self._service_identifier )
connection = self._service.GetConnection()
options = connection.Get( 'options' )
self._max_monthly_data.SetValue( options[ 'max_monthly_data' ] )
self._message.SetValue( options[ 'message' ] )
def ArrangeControls():
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self._tag_repository_panel, label='Message' ), FLAGS_MIXED )
gridbox.AddF( self._message, FLAGS_MIXED )
self._tag_repository_panel.AddF( self._max_monthly_data, FLAGS_EXPAND_PERPENDICULAR )
self._tag_repository_panel.AddF( gridbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
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_repository_panel, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
ClientGUIDialogs.Dialog.__init__( self, parent, service_identifier.GetName() + ' options' )
self._service_identifier = service_identifier
InitialiseControls()
PopulateControls()
ArrangeControls()
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x + 80, y ) )
wx.CallAfter( self._ok.SetFocus )
def EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
def EventOK( self, event ):
options = {}
options[ 'max_monthly_data' ] = self._max_monthly_data.GetValue()
options[ 'message' ] = self._message.GetValue()
try:
connection = self._service.GetConnection()
connection.Post( 'options', options = options )
except Exception as e: wx.MessageBox( 'Something went wrong when trying to send the options to the tag repository: ' + HC.u( e ) )
self.EndModal( wx.ID_OK )
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, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
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_SIZERS )
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 EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
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 ):
try:
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'
connection = HC.get_connection( url = 'http://www.pixiv.net/', accept_cookies = True )
response = connection.request( 'POST', '/login.php', headers = headers, body = body, follow_redirects = False )
cookies = connection.GetCookies()
# _ 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, '' )
except Exception as e:
wx.MessageBox( traceback.format_exc() )
wx.MessageBox( HC.u( e ) )
class DialogManageRatings( ClientGUIDialogs.Dialog ):
def __init__( self, parent, media ):
def InitialiseControls():
service_identifiers = HC.app.Read( 'service_identifiers', HC.RATINGS_SERVICES )
# sort according to local/remote, I guess
# and maybe sub-sort according to name?
# maybe just do two get s_i queries
self._panels = []
for service_identifier in service_identifiers: self._panels.append( self._Panel( self, service_identifier, media ) )
self._apply = wx.Button( self, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
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_SIZERS )
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 EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
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.EventCancel( event )
elif command == 'ok': self.EventOK( event )
else: event.Skip()
def EventOK( self, event ):
try:
service_identfiers_to_content_updates = {}
for panel in self._panels:
if panel.HasChanges():
( service_identifier, content_updates ) = panel.GetContentUpdates()
service_identfiers_to_content_updates[ service_identifier ] = content_updates
HC.app.Write( 'content_updates', service_identfiers_to_content_updates )
except Exception as e: wx.MessageBox( 'Saving ratings changes to DB raised this error: ' + HC.u( e ) )
self.EndModal( wx.ID_OK )
def RefreshAcceleratorTable( self ):
interested_actions = [ 'manage_ratings' ]
entries = []
for ( modifier, key_dict ) in self._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_identifier, media ):
wx.Panel.__init__( self, parent )
self._service_identifier = service_identifier
self._service = HC.app.Read( 'service', service_identifier )
extra_info = self._service.GetExtraInfo()
self._media = media
service_type = service_identifier.GetType()
def InitialiseControls():
self._ratings_panel = ClientGUICommon.StaticBox( self, self._service_identifier.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 ) = extra_info
if service_type == HC.LOCAL_RATING_LIKE:
ratings = [ rating_services.GetRating( self._service_identifier ) 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 ) = extra_info
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_identifier ) 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_identifier.GetType()
choice_text = self._choices.GetSelectedClientData()
if choice_text == 'remove rating': rating = None
else:
if service_type == HC.LOCAL_RATING_LIKE:
( like, dislike ) = self._service.GetExtraInfo()
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_identifier, [ content_update ] )
def HasChanges( self ):
choice_text = self._choices.GetSelectedClientData()
if choice_text == 'make no changes': return False
else: return True
def GetServiceIdentifier( self ): return self._service_identifier
class DialogManageServer( ClientGUIDialogs.Dialog ):
def __init__( self, parent, service_identifier ):
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, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
# goes after self._remove, because of events
for service_identifier in self._service_identifiers:
page = self._Panel( self._services_listbook, service_identifier )
name = HC.service_string_lookup[ service_identifier.GetType() ]
self._services_listbook.AddPage( page, name )
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 )
def ArrangeControls():
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 )
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._services_listbook, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( add_remove_hbox, FLAGS_SMALL_INDENT )
vbox.AddF( ok_hbox, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
if y < 400: y = 400 # listbook's setsize ( -1, 400 ) is buggy
self.SetInitialSize( ( 680, y ) )
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage ' + service_identifier.GetName() + ' services' )
self._service = HC.app.Read( 'service', service_identifier )
connection = self._service.GetConnection()
self._service_identifiers = connection.Get( 'services' )
InitialiseControls()
PopulateControls()
ArrangeControls()
current_page = self._services_listbook.GetCurrentPage()
if current_page.GetOriginalServiceIdentifier().GetType() == HC.SERVER_ADMIN: self._remove.Disable()
else: self._remove.Enable()
wx.CallAfter( self._ok.SetFocus )
def _CheckCurrentServiceIsValid( self ):
service_panel = self._services_listbook.GetCurrentPage()
if service_panel is not None:
port = service_panel.GetInfo()
for existing_port in [ page.GetInfo() for page in self._services_listbook.GetNameToPageDict().values() if page != service_panel ]:
if port == existing_port: raise Exception( 'That port is already in use!' )
def EventAdd( self, event ):
try:
self._CheckCurrentServiceIsValid()
service_type = self._service_types.GetClientData( self._service_types.GetSelection() )
existing_ports = [ page.GetInfo() for page in self._services_listbook.GetNameToPageDict().values() ]
port = HC.DEFAULT_SERVICE_PORT
while port in existing_ports: port += 1
service_identifier = HC.ServerServiceIdentifier( service_type, port )
self._edit_log.append( ( HC.ADD, service_identifier ) )
page = self._Panel( self._services_listbook, service_identifier )
name = HC.service_string_lookup[ service_type ]
self._services_listbook.AddPage( page, name, select = True )
except Exception as e: wx.MessageBox( HC.u( e ) )
def EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
def EventOK( self, event ):
try: self._CheckCurrentServiceIsValid()
except Exception as e:
wx.MessageBox( HC.u( e ) )
return
for page in self._services_listbook.GetNameToPageDict().values():
if page.HasChanges(): self._edit_log.append( ( HC.EDIT, ( page.GetOriginalServiceIdentifier(), page.GetInfo() ) ) )
try:
if len( self._edit_log ) > 0:
connection = self._service.GetConnection()
connection.Post( 'services_modification', edit_log = self._edit_log )
HC.app.Write( 'update_server_services', self._service.GetServiceIdentifier(), self._edit_log )
except Exception as e: wx.MessageBox( 'Saving services to server raised this error: ' + HC.u( e ) )
self.EndModal( wx.ID_OK )
def EventRemove( self, event ):
service_panel = self._services_listbook.GetCurrentPage()
if service_panel is not None:
service_identifier = service_panel.GetOriginalServiceIdentifier()
self._edit_log.append( ( HC.DELETE, service_identifier ) )
self._services_listbook.DeleteCurrentPage()
def EventServiceChanged( self, event ):
page = self._services_listbook.GetCurrentPage()
if page.GetOriginalServiceIdentifier().GetType() == 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_identifier ):
wx.Panel.__init__( self, parent )
self._service_identifier = service_identifier
def InitialiseControls():
self._service_panel = ClientGUICommon.StaticBox( self, 'service' )
self._service_port = wx.SpinCtrl( self._service_panel, min = 1, max = 65535 )
def PopulateControls():
self._service_port.SetValue( self._service_identifier.GetPort() )
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 )
gridbox.AddF( wx.StaticText( self._service_panel, label='port' ), FLAGS_MIXED )
gridbox.AddF( self._service_port, FLAGS_EXPAND_BOTH_WAYS )
self._service_panel.AddF( gridbox, FLAGS_EXPAND_SIZER_BOTH_WAYS )
vbox.AddF( self._service_panel, FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
InitialiseControls()
PopulateControls()
ArrangeControls()
def GetInfo( self ):
port = self._service_port.GetValue()
return port
def HasChanges( self ):
port = self.GetInfo()
if port != self._service_identifier.GetPort(): return True
return False
def GetOriginalServiceIdentifier( self ): return self._service_identifier
class DialogManageServices( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
def InitialiseControls():
self._edit_log = []
self._listbook = ClientGUICommon.ListBook( self )
self._listbook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
self._listbook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventPageChanging, source = self._listbook )
self._local_ratings_like = ClientGUICommon.ListBook( self._listbook )
self._local_ratings_like.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
self._local_ratings_numerical = ClientGUICommon.ListBook( self._listbook )
self._local_ratings_numerical.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
self._tag_repositories = ClientGUICommon.ListBook( self._listbook )
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
self._file_repositories = ClientGUICommon.ListBook( self._listbook )
self._file_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
self._message_depots = ClientGUICommon.ListBook( self._listbook )
self._message_depots.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
self._servers_admin = ClientGUICommon.ListBook( self._listbook )
self._servers_admin.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
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, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
services = HC.app.Read( 'services', HC.RESTRICTED_SERVICES + [ HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ] )
for service in services:
service_identifier = service.GetServiceIdentifier()
service_type = service_identifier.GetType()
name = service_identifier.GetName()
if service_type in HC.REMOTE_SERVICES: credentials = service.GetCredentials()
else: credentials = None
extra_info = service.GetExtraInfo()
if service_type == HC.LOCAL_RATING_LIKE: listbook = self._local_ratings_like
elif service_type == HC.LOCAL_RATING_NUMERICAL: listbook = self._local_ratings_numerical
elif service_type == HC.TAG_REPOSITORY: listbook = self._tag_repositories
elif service_type == HC.FILE_REPOSITORY: listbook = self._file_repositories
elif service_type == HC.MESSAGE_DEPOT: listbook = self._message_depots
elif service_type == HC.SERVER_ADMIN: listbook = self._servers_admin
else: continue
page_info = ( self._Panel, ( listbook, service_identifier, credentials, extra_info ), {} )
listbook.AddPage( page_info, name )
self._listbook.AddPage( self._local_ratings_like, 'local ratings like' )
self._listbook.AddPage( self._local_ratings_numerical, 'local ratings numerical' )
self._listbook.AddPage( self._tag_repositories, 'tags' )
self._listbook.AddPage( self._file_repositories, 'files' )
self._listbook.AddPage( self._message_depots, 'message depots' )
self._listbook.AddPage( self._servers_admin, 'servers admin' )
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_SIZERS )
self.SetSizer( vbox )
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage services' )
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 ):
services_listbook = self._listbook.GetCurrentPage()
if services_listbook is not None:
service_panel = services_listbook.GetCurrentPage()
if service_panel is not None:
( service_identifier, credentials, extra_info ) = service_panel.GetInfo()
old_name = services_listbook.GetCurrentName()
name = service_identifier.GetName()
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 wx.TextEntryDialog( self, 'Enter new service\'s name' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
try:
name = dlg.GetValue()
services_listbook = self._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.' )
if services_listbook == self._local_ratings_like: service_type = HC.LOCAL_RATING_LIKE
elif services_listbook == self._local_ratings_numerical: service_type = HC.LOCAL_RATING_NUMERICAL
elif services_listbook == self._tag_repositories: service_type = HC.TAG_REPOSITORY
elif services_listbook == self._file_repositories: service_type = HC.FILE_REPOSITORY
elif services_listbook == self._message_depots: service_type = HC.MESSAGE_DEPOT
elif services_listbook == self._servers_admin: service_type = HC.SERVER_ADMIN
service_identifier = HC.ClientServiceIdentifier( os.urandom( 32 ), service_type, name )
if service_type in HC.REMOTE_SERVICES:
if service_type == HC.SERVER_ADMIN: credentials = CC.Credentials( '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 ) as dlg:
if dlg.ShowModal() != wx.ID_OK: return
credentials = dlg.GetCredentials()
else: credentials = CC.Credentials( 'hostname', 45871, '' )
else: credentials = CC.Credentials( 'hostname', 45871 )
else: credentials = None
if service_type == HC.MESSAGE_DEPOT:
identity_name = 'identity@' + name
check_period = 180
private_key = HydrusEncryption.GenerateNewPrivateKey()
receive_anon = True
extra_info = ( identity_name, check_period, private_key, receive_anon )
elif service_type == HC.LOCAL_RATING_LIKE: extra_info = ( 'like', 'dislike' )
elif service_type == HC.LOCAL_RATING_NUMERICAL: extra_info = ( 0, 5 )
else: extra_info = None
self._edit_log.append( ( 'add', ( service_identifier, credentials, extra_info ) ) )
page = self._Panel( services_listbook, service_identifier, credentials, extra_info )
services_listbook.AddPage( page, name, select = True )
except Exception as e:
wx.MessageBox( HC.u( e ) )
self.EventAdd( event )
def EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
def EventExport( self, event ):
try: self._CheckCurrentServiceIsValid()
except Exception as e:
wx.MessageBox( HC.u( e ) )
return
services_listbook = self._listbook.GetCurrentPage()
if services_listbook is not None:
service_panel = services_listbook.GetCurrentPage()
( service_identifier, credentials, extra_info ) = service_panel.GetInfo()
name = service_identifier.GetName()
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 HC.o( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( ( service_identifier, credentials, extra_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 HC.o( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( ( service_identifier, credentials, extra_info ) ) )
def EventOK( self, event ):
try: self._CheckCurrentServiceIsValid()
except Exception as e:
wx.MessageBox( HC.u( e ) )
return
all_pages = []
all_pages.extend( self._local_ratings_like.GetNameToPageDict().values() )
all_pages.extend( self._local_ratings_numerical.GetNameToPageDict().values() )
all_pages.extend( self._tag_repositories.GetNameToPageDict().values() )
all_pages.extend( self._file_repositories.GetNameToPageDict().values() )
all_pages.extend( self._message_depots.GetNameToPageDict().values() )
all_pages.extend( self._servers_admin.GetNameToPageDict().values() )
for page in all_pages:
if page.HasChanges(): self._edit_log.append( ( 'edit', ( page.GetOriginalServiceIdentifier(), page.GetInfo() ) ) )
try:
if len( self._edit_log ) > 0: HC.app.Write( 'update_services', self._edit_log )
except Exception as e: wx.MessageBox( 'Saving services to DB raised this error: ' + HC.u( e ) )
self.EndModal( wx.ID_OK )
def EventPageChanging( self, event ):
try: self._CheckCurrentServiceIsValid()
except Exception as e:
wx.MessageBox( HC.u( e ) )
event.Veto()
def EventRemove( self, event ):
services_listbook = self._listbook.GetCurrentPage()
service_panel = services_listbook.GetCurrentPage()
if service_panel is not None:
service_identifier = service_panel.GetOriginalServiceIdentifier()
self._edit_log.append( ( 'delete', service_identifier ) )
services_listbook.DeleteCurrentPage()
def EventServiceChanging( self, event ):
try: self._CheckCurrentServiceIsValid()
except Exception as e:
wx.MessageBox( HC.u( e ) )
event.Veto()
def Import( self, paths ):
try: self._CheckCurrentSubscriptionIsValid()
except Exception as e:
wx.MessageBox( HC.u( e ) )
return
for path in paths:
try:
with HC.o( path, 'rb' ) as f: file = f.read()
( service_identifier, credentials, extra_info ) = yaml.safe_load( file )
name = service_identifier.GetName()
service_type = service_identifier.GetType()
if service_type == HC.TAG_REPOSITORY: services_listbook = self._tag_repositories
elif service_type == HC.FILE_REPOSITORY: services_listbook = self._file_repositories
elif service_type == HC.MESSAGE_DEPOT: services_listbook = self._message_depots
elif service_type == HC.SERVER_ADMIN: services_listbook = self._servers_admin
self._listbook.SelectPage( services_listbook )
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_identifier, credentials, extra_info )
else:
self._edit_log.append( ( 'add', ( service_identifier, credentials, extra_info ) ) )
page = self._Panel( services_listbook, service_identifier, credentials, extra_info )
services_listbook.AddPage( page, name, select = True )
except:
wx.MessageBox( traceback.format_exc() )
class _Panel( wx.Panel ):
def __init__( self, parent, service_identifier, credentials, extra_info ):
wx.Panel.__init__( self, parent )
self._service_identifier = service_identifier
self._credentials = credentials
self._extra_info = extra_info
service_type = service_identifier.GetType()
def InitialiseControls():
self._service_panel = ClientGUICommon.StaticBox( self, 'service' )
self._service_name = wx.TextCtrl( self._service_panel )
if service_type in HC.REMOTE_SERVICES: self._service_credentials = wx.TextCtrl( self._service_panel, value = self._credentials.GetConnectionString() )
if service_type == HC.MESSAGE_DEPOT:
( identity_name, check_period, private_key, receive_anon ) = self._extra_info
self._identity_name = wx.TextCtrl( self._service_panel, value = identity_name )
self._check_period = wx.SpinCtrl( self._service_panel, min = 60, max = 86400 * 7 )
self._check_period.SetValue( check_period )
self._private_key = wx.TextCtrl( self._service_panel, value = private_key, style = wx.TE_MULTILINE )
self._receive_anon = wx.CheckBox( self._service_panel )
self._receive_anon.SetValue( receive_anon )
elif service_identifier.GetType() == HC.LOCAL_RATING_LIKE:
( like, dislike ) = self._extra_info
self._like = wx.TextCtrl( self._service_panel, value = like )
self._dislike = wx.TextCtrl( self._service_panel, value = dislike )
elif service_identifier.GetType() == HC.LOCAL_RATING_NUMERICAL:
( lower, upper ) = self._extra_info
self._lower = wx.SpinCtrl( self._service_panel, min = -2000, max = 2000 )
self._lower.SetValue( lower )
self._upper = wx.SpinCtrl( self._service_panel, min = -2000, max = 2000 )
self._upper.SetValue( upper )
def PopulateControls():
self._service_name.SetValue( self._service_identifier.GetName() )
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 )
gridbox.AddF( wx.StaticText( self._service_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._service_panel, label='credentials' ), FLAGS_MIXED )
gridbox.AddF( self._service_credentials, FLAGS_EXPAND_BOTH_WAYS )
if service_type == HC.MESSAGE_DEPOT:
gridbox.AddF( wx.StaticText( self._service_panel, label='identity name' ), FLAGS_MIXED )
gridbox.AddF( self._identity_name, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._service_panel, label='update period' ), FLAGS_MIXED )
gridbox.AddF( self._check_period, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._service_panel, label='private key' ), FLAGS_MIXED )
gridbox.AddF( self._private_key, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._service_panel, label='receive messages from Anonymous?' ), FLAGS_MIXED )
gridbox.AddF( self._receive_anon, FLAGS_EXPAND_BOTH_WAYS )
elif service_identifier.GetType() == HC.LOCAL_RATING_LIKE:
gridbox.AddF( wx.StaticText( self._service_panel, label='like' ), FLAGS_MIXED )
gridbox.AddF( self._like, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._service_panel, label='dislike' ), FLAGS_MIXED )
gridbox.AddF( self._dislike, FLAGS_EXPAND_BOTH_WAYS )
elif service_identifier.GetType() == HC.LOCAL_RATING_NUMERICAL:
gridbox.AddF( wx.StaticText( self._service_panel, label='lower limit' ), FLAGS_MIXED )
gridbox.AddF( self._lower, FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._service_panel, label='upper limit' ), FLAGS_MIXED )
gridbox.AddF( self._upper, FLAGS_EXPAND_BOTH_WAYS )
self._service_panel.AddF( gridbox, FLAGS_EXPAND_SIZER_BOTH_WAYS )
vbox.AddF( self._service_panel, FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
InitialiseControls()
PopulateControls()
ArrangeControls()
def GetInfo( self ):
service_key = self._service_identifier.GetServiceKey()
service_type = self._service_identifier.GetType()
name = self._service_name.GetValue()
if name == '': raise Exception( 'Please enter a name' )
service_identifier = HC.ClientServiceIdentifier( service_key, service_type, 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!' )
try: ( host, port ) = address.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!' )
credentials = CC.Credentials( host, port, access_key )
else:
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!' )
credentials = CC.Credentials( host, port )
else: credentials = None
if service_type == HC.MESSAGE_DEPOT: extra_info = ( self._identity_name.GetValue(), self._check_period.GetValue(), self._private_key.GetValue(), self._receive_anon.GetValue() )
elif service_type == HC.LOCAL_RATING_LIKE: extra_info = ( self._like.GetValue(), self._dislike.GetValue() )
elif service_type == HC.LOCAL_RATING_NUMERICAL:
( lower, upper ) = ( self._lower.GetValue(), self._upper.GetValue() )
if upper < lower: upper = lower + 1
extra_info = ( lower, upper )
else: extra_info = None
return ( service_identifier, credentials, extra_info )
def HasChanges( self ):
( service_identifier, credentials, extra_info ) = self.GetInfo()
if service_identifier != self._service_identifier: return True
if credentials != self._credentials: return True
if extra_info != self._extra_info: return True
return False
def GetOriginalServiceIdentifier( self ): return self._service_identifier
def Update( self, service_identifier, credentials, extra_info ):
service_type = service_identifier.GetType()
self._service_name.SetValue( service_identifier.GetName() )
if service_type in HC.REMOTE_SERVICES: self._service_credentials.SetValue( credentials.GetConnectionString() )
if service_type == HC.MESSAGE_DEPOT:
if len( extra_info ) == 3:
( identity_name, check_period, private_key ) = extra_info
receive_anon = True
else: ( identity_name, check_period, private_key, receive_anon ) = extra_info
self._identity_name.SetValue( identity_name )
self._check_period.SetValue( check_period )
self._private_key.SetValue( private_key )
self._receive_anon.SetValue( receive_anon )
elif service_type == HC.LOCAL_RATING_LIKE:
( like, dislike ) = extra_info
self._like.SetValue( like )
self._dislike.SetValue( dislike )
elif service_type == HC.LOCAL_RATING_NUMERICAL:
( lower, upper ) = extra_info
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.EventServiceChanging )
self._listbook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventPageChanging, source = self._listbook )
self._deviant_art = ClientGUICommon.ListBook( self._listbook )
self._deviant_art.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
self._hentai_foundry = ClientGUICommon.ListBook( self._listbook )
self._hentai_foundry.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
self._giphy = ClientGUICommon.ListBook( self._listbook )
self._giphy.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
self._newgrounds = ClientGUICommon.ListBook( self._listbook )
self._newgrounds.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
self._pixiv = ClientGUICommon.ListBook( self._listbook )
self._pixiv.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
self._booru = ClientGUICommon.ListBook( self._listbook )
self._booru.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
self._tumblr = ClientGUICommon.ListBook( self._listbook )
self._tumblr.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventServiceChanging )
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, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
types_to_listbooks = {}
types_to_listbooks[ HC.SITE_DOWNLOAD_TYPE_DEVIANT_ART ] = self._deviant_art
types_to_listbooks[ HC.SITE_DOWNLOAD_TYPE_HENTAI_FOUNDRY ] = self._hentai_foundry
types_to_listbooks[ HC.SITE_DOWNLOAD_TYPE_GIPHY ] = self._giphy
types_to_listbooks[ HC.SITE_DOWNLOAD_TYPE_PIXIV ] = self._pixiv
types_to_listbooks[ HC.SITE_DOWNLOAD_TYPE_BOORU ] = self._booru
types_to_listbooks[ HC.SITE_DOWNLOAD_TYPE_TUMBLR ] = self._tumblr
types_to_listbooks[ HC.SITE_DOWNLOAD_TYPE_NEWGROUNDS ] = self._newgrounds
for ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) in self._original_subscriptions:
listbook = types_to_listbooks[ site_download_type ]
page = self._Panel( listbook, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
listbook.AddPage( page, name )
self._listbook.AddPage( self._deviant_art, 'deviant art' )
self._listbook.AddPage( self._hentai_foundry, 'hentai foundry' )
self._listbook.AddPage( self._giphy, 'giphy' )
self._listbook.AddPage( self._newgrounds, 'newgrounds' )
self._listbook.AddPage( self._pixiv, 'pixiv' )
self._listbook.AddPage( self._booru, 'booru' )
self._listbook.AddPage( self._tumblr, 'tumblr' )
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_SIZERS )
self.SetSizer( vbox )
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage subscriptions' )
self._original_subscriptions = HC.app.Read( 'subscriptions' )
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 ):
subs_listbook = self._listbook.GetCurrentPage()
if subs_listbook is not None:
sub_panel = subs_listbook.GetCurrentPage()
if sub_panel is not None:
name = sub_panel.GetName()
old_name = subs_listbook.GetCurrentName()
if old_name is not None and name != old_name:
if subs_listbook.NameExists( name ): raise Exception( 'That name is already in use!' )
subs_listbook.RenamePage( old_name, name )
def EventAdd( self, event ):
with wx.TextEntryDialog( self, 'Enter name for subscription' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
try:
name = dlg.GetValue()
subscription_listbook = self._listbook.GetCurrentPage()
if subscription_listbook.NameExists( name ): raise Exception( 'That name is already in use!' )
if name == '': raise Exception( 'Please enter a nickname for the subscription.' )
if subscription_listbook == self._deviant_art: site_download_type = HC.SITE_DOWNLOAD_TYPE_DEVIANT_ART
elif subscription_listbook == self._hentai_foundry: site_download_type = HC.SITE_DOWNLOAD_TYPE_HENTAI_FOUNDRY
elif subscription_listbook == self._giphy: site_download_type = HC.SITE_DOWNLOAD_TYPE_GIPHY
elif subscription_listbook == self._pixiv: site_download_type = HC.SITE_DOWNLOAD_TYPE_PIXIV
elif subscription_listbook == self._booru: site_download_type = HC.SITE_DOWNLOAD_TYPE_BOORU
elif subscription_listbook == self._tumblr: site_download_type = HC.SITE_DOWNLOAD_TYPE_TUMBLR
elif subscription_listbook == self._newgrounds: site_download_type = HC.SITE_DOWNLOAD_TYPE_NEWGROUNDS
if site_download_type == HC.SITE_DOWNLOAD_TYPE_PIXIV:
( id, password ) = HC.app.Read( 'pixiv_account' )
if id == '' and password == '':
wx.MessageBox( 'You need to set up your pixiv credentials before you can add a pixiv subscription!' )
return
if site_download_type in ( HC.SITE_DOWNLOAD_TYPE_DEVIANT_ART, HC.SITE_DOWNLOAD_TYPE_TUMBLR, HC.SITE_DOWNLOAD_TYPE_NEWGROUNDS ): query_type = 'artist'
else: query_type = 'tags'
if site_download_type == HC.SITE_DOWNLOAD_TYPE_BOORU: query_type = ( '', query_type )
query = ''
frequency_type = 86400
frequency_number = 7
advanced_tag_options = {}
advanced_import_options = {} # blaaah not sure
last_checked = None
url_cache = set()
paused = False
page = self._Panel( subscription_listbook, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
subscription_listbook.AddPage( page, name, select = True )
except Exception as e:
wx.MessageBox( HC.u( e ) )
self.EventAdd( event )
def EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
def EventExport( self, event ):
try: self._CheckCurrentSubscriptionIsValid()
except Exception as e:
wx.MessageBox( HC.u( e ) )
return
subscription_listbook = self._listbook.GetCurrentPage()
if subscription_listbook is not None:
sub_panel = subscription_listbook.GetCurrentPage()
if sub_panel is not None:
name = subscription_listbook.GetCurrentName()
info = sub_panel.GetInfo()
( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) = info
advanced_tag_options = advanced_tag_options.items() # yaml parsing bug
info = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
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 HC.o( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( 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 HC.o( dlg.GetPath(), 'wb' ) as f: f.write( yaml.safe_dump( info ) )
def EventOK( self, event ):
try: self._CheckCurrentSubscriptionIsValid()
except Exception as e:
wx.MessageBox( HC.u( e ) )
return
all_pages = []
all_pages.extend( self._deviant_art.GetNameToPageDict().values() )
all_pages.extend( self._hentai_foundry.GetNameToPageDict().values() )
all_pages.extend( self._giphy.GetNameToPageDict().values() )
all_pages.extend( self._pixiv.GetNameToPageDict().values() )
all_pages.extend( self._booru.GetNameToPageDict().values() )
all_pages.extend( self._tumblr.GetNameToPageDict().values() )
all_pages.extend( self._newgrounds.GetNameToPageDict().values() )
subscriptions = [ page.GetInfo() for page in all_pages ]
try: HC.app.Write( 'subscriptions', subscriptions )
except Exception as e: wx.MessageBox( 'Saving services to DB raised this error: ' + HC.u( e ) )
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 ):
subscription_listbook = self._listbook.GetCurrentPage()
sub_panel = subscription_listbook.GetCurrentPage()
if sub_panel is not None: subscription_listbook.DeleteCurrentPage()
def EventServiceChanging( self, event ):
try: self._CheckCurrentSubscriptionIsValid()
except Exception as e:
wx.MessageBox( HC.u( e ) )
event.Veto()
def Import( self, paths ):
try: self._CheckCurrentSubscriptionIsValid()
except Exception as e:
wx.MessageBox( HC.u( e ) )
return
for path in paths:
try:
with HC.o( path, 'rb' ) as f: file = f.read()
( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache ) = yaml.safe_load( file )
advanced_tag_options = dict( advanced_tag_options ) # yaml parsing bug
if site_download_type == HC.SITE_DOWNLOAD_TYPE_BOORU: services_listbook = self._booru
elif site_download_type == HC.SITE_DOWNLOAD_TYPE_DEVIANT_ART: services_listbook = self._deviant_art
elif site_download_type == HC.SITE_DOWNLOAD_TYPE_GIPHY: services_listbook = self._giphy
elif site_download_type == HC.SITE_DOWNLOAD_TYPE_HENTAI_FOUNDRY: services_listbook = self._hentai_foundry
elif site_download_type == HC.SITE_DOWNLOAD_TYPE_PIXIV: services_listbook = self._pixiv
elif site_download_type == HC.SITE_DOWNLOAD_TYPE_TUMBLR: services_listbook = self._tumblr
elif site_download_type == HC.SITE_DOWNLOAD_TYPE_NEWGROUNDS: services_listbook = self._newgrounds
self._listbook.SelectPage( services_listbook )
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( query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
else:
page = self._Panel( services_listbook, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache )
services_listbook.AddPage( page, name, select = True )
except:
wx.MessageBox( traceback.format_exc() )
class _Panel( wx.ScrolledWindow ):
def __init__( self, parent, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ):
def InitialiseControls():
self._name_panel = ClientGUICommon.StaticBox( self, 'name' )
self._name = wx.TextCtrl( self._name_panel )
self._query_panel = ClientGUICommon.StaticBox( self, 'query' )
self._query = wx.TextCtrl( self._query_panel )
self._booru_selector = wx.ListBox( self._query_panel )
self._query_type = ClientGUICommon.RadioBox( self._query_panel, 'query type', ( ( 'artist', 'artist' ), ( 'tags', 'tags' ) ) )
if site_download_type in ( HC.SITE_DOWNLOAD_TYPE_BOORU, HC.SITE_DOWNLOAD_TYPE_DEVIANT_ART, HC.SITE_DOWNLOAD_TYPE_GIPHY, HC.SITE_DOWNLOAD_TYPE_TUMBLR, HC.SITE_DOWNLOAD_TYPE_NEWGROUNDS ): self._query_type.Hide()
self._frequency_number = wx.SpinCtrl( self._query_panel )
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 )
if site_download_type == HC.SITE_DOWNLOAD_TYPE_BOORU: namespaces = [ 'creator', 'series', 'character', '' ]
elif site_download_type == HC.SITE_DOWNLOAD_TYPE_DEVIANT_ART: namespaces = [ 'creator', 'title', '' ]
elif site_download_type == HC.SITE_DOWNLOAD_TYPE_GIPHY: namespaces = [ '' ]
elif site_download_type == HC.SITE_DOWNLOAD_TYPE_HENTAI_FOUNDRY: namespaces = [ 'creator', 'title', '' ]
elif site_download_type == HC.SITE_DOWNLOAD_TYPE_PIXIV: namespaces = [ 'creator', 'title', '' ]
elif site_download_type == HC.SITE_DOWNLOAD_TYPE_TUMBLR: namespaces = [ '' ]
elif site_download_type == HC.SITE_DOWNLOAD_TYPE_NEWGROUNDS: namespaces = [ 'creator', 'title', '' ]
self._advanced_tag_options = ClientGUICommon.AdvancedTagOptions( self, 'send tags to ', namespaces )
self._advanced_import_options = ClientGUICommon.AdvancedImportOptions( self )
def PopulateControls():
if site_download_type == HC.SITE_DOWNLOAD_TYPE_BOORU:
boorus = HC.app.Read( 'boorus' )
for booru in boorus: self._booru_selector.Append( booru.GetName(), booru )
else: self._booru_selector.Hide()
#
self._original_info = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
self._SetControls( *self._original_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_number, FLAGS_MIXED )
hbox.AddF( self._frequency_type, FLAGS_MIXED )
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 last_checked is None: last_checked_message = 'not yet initialised'
else:
now = HC.GetNow()
if last_checked < now: last_checked_message = HC.ConvertTimestampToPrettySync( last_checked )
else: last_checked_message = 'due to error, update is delayed. next check in ' + HC.ConvertTimestampToPrettyPending( 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( 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 )
InitialiseControls()
PopulateControls()
ArrangeControls()
self._reset_cache = False
self.SetScrollRate( 0, 20 )
self.SetMinSize( ( 540, 620 ) )
def _SetControls( self, site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ):
self._name.SetValue( name )
self._query.SetValue( query )
if site_download_type == HC.SITE_DOWNLOAD_TYPE_BOORU:
( booru_name, query_type ) = query_type
index = self._booru_selector.FindString( booru_name )
if index != wx.NOT_FOUND: self._booru_selector.Select( index )
initial_index = 1
else:
self._booru_selector.Hide()
if query_type == 'artist': initial_index = 0
elif query_type == 'tags': initial_index = 1
self._query_type.SetSelection( initial_index )
if site_download_type in ( HC.SITE_DOWNLOAD_TYPE_BOORU, HC.SITE_DOWNLOAD_TYPE_DEVIANT_ART, HC.SITE_DOWNLOAD_TYPE_GIPHY, HC.SITE_DOWNLOAD_TYPE_TUMBLR, HC.SITE_DOWNLOAD_TYPE_NEWGROUNDS ): self._query_type.Hide()
self._frequency_number.SetValue( frequency_number )
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 EventResetCache( self, event ):
self._reset_cache = True
self._reset_cache_button.SetLabel( 'cache will be reset on dialog ok' )
self._reset_cache_button.Disable()
def GetInfo( self ):
( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ) = self._original_info
name = self._name.GetValue()
query_type = self._query_type.GetSelectedClientData()
if site_download_type == HC.SITE_DOWNLOAD_TYPE_BOORU:
booru_name = self._booru_selector.GetStringSelection()
query_type = ( booru_name, query_type )
query = self._query.GetValue()
frequency_number = self._frequency_number.GetValue()
frequency_type = self._frequency_type.GetClientData( self._frequency_type.GetSelection() )
advanced_tag_options = self._advanced_tag_options.GetInfo()
advanced_import_options = self._advanced_import_options.GetInfo()
if self._reset_cache:
last_checked = None
url_cache = set()
paused = self._paused.GetValue()
return ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
def GetName( self ): return self._name.GetValue()
def Update( self, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused ):
site_download_type = self._original_info[0]
name = self._original_info[1]
self._original_info = ( site_download_type, name, query_type, query, frequency_type, frequency_number, advanced_tag_options, advanced_import_options, last_checked, url_cache, paused )
self._SetControls( *self._original_info )
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, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
services = HC.app.Read( 'services', ( HC.TAG_REPOSITORY, ) )
for service in services:
account = service.GetAccount()
if account.HasPermission( HC.POST_DATA ):
service_identifier = service.GetServiceIdentifier()
page_info = ( self._Panel, ( self._tag_repositories, service_identifier, tag ), {} )
name = service_identifier.GetName()
self._tag_repositories.AddPage( page_info, name )
page = self._Panel( self._tag_repositories, HC.LOCAL_TAG_SERVICE_IDENTIFIER, tag )
name = HC.LOCAL_TAG_SERVICE_IDENTIFIER.GetName()
self._tag_repositories.AddPage( page, name )
default_tag_repository = self._options[ 'default_tag_repository' ]
self._tag_repositories.Select( default_tag_repository.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_SIZERS )
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 self._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 EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
def EventMenu( self, event ):
action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
if action is not None:
try:
( command, data ) = action
if command == 'set_search_focus': self._SetSearchFocus()
else: event.Skip()
except Exception as e:
wx.MessageBox( HC.u( e ) )
wx.MessageBox( traceback.format_exc() )
def EventOK( self, event ):
service_identifiers_to_content_updates = {}
try:
for page in self._tag_repositories.GetNameToPageDict().values():
( service_identifier, content_updates ) = page.GetContentUpdates()
service_identifiers_to_content_updates[ service_identifier ] = content_updates
HC.app.Write( 'content_updates', service_identifiers_to_content_updates )
except Exception as e: wx.MessageBox( 'Saving tag parent changes to DB raised this error: ' + HC.u( e ) )
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_identifier, 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 )
self._child_text = wx.StaticText( self )
self._parent_text = wx.StaticText( self )
self._child_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetChild, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
self._parent_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetParent, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
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 ) )
if tag is not None: self.SetChild( tag )
def ArrangeControls():
text_box = wx.BoxSizer( wx.HORIZONTAL )
text_box.AddF( self._child_text, FLAGS_EXPAND_BOTH_WAYS )
text_box.AddF( self._parent_text, 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( self._tag_parents, 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_identifier = service_identifier
if self._service_identifier != HC.LOCAL_TAG_SERVICE_IDENTIFIER:
service = HC.app.Read( 'service', service_identifier )
self._account = service.GetAccount()
self._original_statuses_to_pairs = HC.app.Read( 'tag_parents', service_identifier )
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_parent = None
self._current_child = None
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, yes_label = 'petition it', no_label = 'do nothing' ) as dlg:
if self._service_identifier != HC.LOCAL_TAG_SERVICE_IDENTIFIER:
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 wx.TextEntryDialog( 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, 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, 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_identifier != HC.LOCAL_TAG_SERVICE_IDENTIFIER:
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 wx.TextEntryDialog( 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 that pair would create a loop!' )
return False
return True
def _SetButtonStatus( self ):
if self._current_parent is None or self._current_child is None: self._add.Disable()
else: self._add.Enable()
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 ):
if self._current_child is not None and self._current_parent is not None:
self._AddPair( self._current_child, self._current_parent )
self.SetChild( None )
self.SetParent( None )
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_identifier == HC.LOCAL_TAG_SERVICE_IDENTIFIER:
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_identifier, content_updates )
def SetChild( self, tag, parents = [] ):
if tag is not None and tag == self._current_parent: self.SetParent( None )
self._current_child = tag
if tag is None: self._child_text.SetLabel( '' )
else: self._child_text.SetLabel( tag )
self._SetButtonStatus()
def SetParent( self, tag, parents = [] ):
if tag is not None and tag == self._current_child: self.SetChild( None )
self._current_parent = tag
if tag is None: self._parent_text.SetLabel( '' )
else: self._parent_text.SetLabel( tag )
self._SetButtonStatus()
def SetTagBoxFocus( self ):
if self._current_child is None: 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, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
page = self._Panel( self._tag_repositories, HC.LOCAL_TAG_SERVICE_IDENTIFIER, tag )
name = HC.LOCAL_TAG_SERVICE_IDENTIFIER.GetName()
self._tag_repositories.AddPage( page, name )
services = HC.app.Read( 'services', ( HC.TAG_REPOSITORY, ) )
for service in services:
account = service.GetAccount()
if account.HasPermission( HC.POST_DATA ):
service_identifier = service.GetServiceIdentifier()
page_info = ( self._Panel, ( self._tag_repositories, service_identifier, tag ), {} )
name = service_identifier.GetName()
self._tag_repositories.AddPage( page_info, name )
default_tag_repository = self._options[ 'default_tag_repository' ]
self._tag_repositories.Select( default_tag_repository.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_SIZERS )
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 self._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 EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
def EventMenu( self, event ):
action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
if action is not None:
try:
( command, data ) = action
if command == 'set_search_focus': self._SetSearchFocus()
else: event.Skip()
except Exception as e:
wx.MessageBox( HC.u( e ) )
wx.MessageBox( traceback.format_exc() )
def EventOK( self, event ):
service_identifiers_to_content_updates = {}
try:
for page in self._tag_repositories.GetNameToPageDict().values():
( service_identifier, content_updates ) = page.GetContentUpdates()
service_identifiers_to_content_updates[ service_identifier ] = content_updates
HC.app.Write( 'content_updates', service_identifiers_to_content_updates )
except Exception as e: wx.MessageBox( 'Saving tag sibling changes to DB raised this error: ' + HC.u( e ) )
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_identifier, 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 )
self._old_text = wx.StaticText( self )
self._new_text = wx.StaticText( self )
self._old_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetOld, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
self._new_input = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.SetNew, HC.LOCAL_FILE_SERVICE_IDENTIFIER, service_identifier )
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 ) )
def ArrangeControls():
text_box = wx.BoxSizer( wx.HORIZONTAL )
text_box.AddF( self._old_text, FLAGS_EXPAND_BOTH_WAYS )
text_box.AddF( self._new_text, FLAGS_EXPAND_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( 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_identifier = service_identifier
if self._service_identifier != HC.LOCAL_TAG_SERVICE_IDENTIFIER:
service = HC.app.Read( 'service', service_identifier )
self._account = service.GetAccount()
self._original_statuses_to_pairs = HC.app.Read( 'tag_siblings', service_identifier )
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_old = None
self._current_new = None
InitialiseControls()
PopulateControls()
ArrangeControls()
if tag is not None: self.SetOld( tag )
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, yes_label = 'petition it', no_label = 'do nothing' ) as dlg:
if self._service_identifier != HC.LOCAL_TAG_SERVICE_IDENTIFIER:
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 wx.TextEntryDialog( 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, 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, 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_identifier != HC.LOCAL_TAG_SERVICE_IDENTIFIER:
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 wx.TextEntryDialog( 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 that pair would create a loop!' )
return False
return True
def _SetButtonStatus( self ):
if self._current_new is None or self._current_old is None: self._add.Disable()
else: self._add.Enable()
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_old is not None and self._current_new is not None:
self._AddPair( self._current_old, self._current_new )
self.SetOld( None )
self.SetNew( None )
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_identifier == HC.LOCAL_TAG_SERVICE_IDENTIFIER:
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_identifier, content_updates )
def SetNew( self, new, parents = [] ):
if new is not None and new == self._current_old: self.SetOld( None )
self._current_new = new
if new is None: self._new_text.SetLabel( '' )
else: self._new_text.SetLabel( new )
self._SetButtonStatus()
def SetOld( 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, yes_label = 'I want to overwrite it', no_label = 'do nothing' ) as dlg:
if self._service_identifier != HC.LOCAL_TAG_SERVICE_IDENTIFIER:
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 and old == self._current_new: self.SetNew( None )
self._current_old = old
if old is None: self._old_text.SetLabel( '' )
else: self._old_text.SetLabel( old )
self._SetButtonStatus()
def SetTagBoxFocus( self ):
if self._current_old is None: self._old_input.SetFocus()
else: self._new_input.SetFocus()
class DialogManageTagServicePrecedence( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
def InitialiseControls():
message = 'When services dispute over a file\'s tags,' + os.linesep + 'higher services will overrule those below.'
self._explain = wx.StaticText( self, label = message )
self._tag_services = wx.ListBox( self )
self._up = wx.Button( self, label = u'\u2191' )
self._up.Bind( wx.EVT_BUTTON, self.EventUp )
self._down = wx.Button( self, label = u'\u2193' )
self._down.Bind( wx.EVT_BUTTON, self.EventDown )
self._apply = wx.Button( self, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
tag_service_precedence = HC.app.Read( 'tag_service_precedence' )
for service_identifier in tag_service_precedence:
name = service_identifier.GetName()
self._tag_services.Append( name, service_identifier )
def ArrangeControls():
updown_vbox = wx.BoxSizer( wx.VERTICAL )
updown_vbox.AddF( self._up, FLAGS_MIXED )
updown_vbox.AddF( self._down, FLAGS_MIXED )
main_hbox = wx.BoxSizer( wx.HORIZONTAL )
main_hbox.AddF( self._tag_services, FLAGS_EXPAND_BOTH_WAYS )
main_hbox.AddF( updown_vbox, FLAGS_MIXED )
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._apply, FLAGS_SMALL_INDENT )
buttons.AddF( self._cancel, FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._explain, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( main_hbox, FLAGS_EXPAND_SIZER_BOTH_WAYS )
vbox.AddF( buttons, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
if y < 400: y = 400
self.SetInitialSize( ( x, y ) )
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage tag service precedence' )
InitialiseControls()
PopulateControls()
ArrangeControls()
wx.CallAfter( self._apply.SetFocus )
def EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
def EventOK( self, event ):
message = 'This operation may take several minutes to complete. Are you sure?'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
try:
service_identifiers = [ self._tag_services.GetClientData( i ) for i in range( self._tag_services.GetCount() ) ]
HC.app.Write( 'set_tag_service_precedence', service_identifiers )
except Exception as e: wx.MessageBox( 'Something went wrong when trying to save tag service precedence to the database: ' + HC.u( e ) )
self.EndModal( wx.ID_OK )
def EventUp( self, event ):
selection = self._tag_services.GetSelection()
if selection != wx.NOT_FOUND:
if selection > 0:
service_identifier = self._tag_services.GetClientData( selection )
name = service_identifier.GetName()
self._tag_services.Delete( selection )
self._tag_services.Insert( name, selection - 1, service_identifier )
self._tag_services.Select( selection - 1 )
def EventDown( self, event ):
selection = self._tag_services.GetSelection()
if selection != wx.NOT_FOUND:
if selection + 1 < self._tag_services.GetCount():
service_identifier = self._tag_services.GetClientData( selection )
name = service_identifier.GetName()
self._tag_services.Delete( selection )
self._tag_services.Insert( name, selection + 1, service_identifier )
self._tag_services.Select( selection + 1 )
class DialogManageTags( ClientGUIDialogs.Dialog ):
def __init__( self, parent, file_service_identifier, 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, 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.Bind( wx.EVT_BUTTON, self.EventCancel )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
service_identifiers = HC.app.Read( 'service_identifiers', ( HC.TAG_REPOSITORY, ) )
for service_identifier in list( service_identifiers ) + [ HC.LOCAL_TAG_SERVICE_IDENTIFIER ]:
service_type = service_identifier.GetType()
page_info = ( self._Panel, ( self._tag_repositories, self._file_service_identifier, service_identifier, media ), {} )
name = service_identifier.GetName()
self._tag_repositories.AddPage( page_info, name )
default_tag_repository = self._options[ 'default_tag_repository' ]
self._tag_repositories.Select( default_tag_repository.GetName() )
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_SIZERS )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x + 200, 500 ) )
self._file_service_identifier = file_service_identifier
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 EventCancel( self, event ): self.EndModal( wx.ID_CANCEL )
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.EventCancel( event )
elif command == 'set_search_focus': self._SetSearchFocus()
elif command == 'ok': self.EventOK( event )
else: event.Skip()
def EventOK( self, event ):
try:
service_identfiers_to_content_updates = {}
for page in self._tag_repositories.GetNameToPageDict().values():
( service_identifier, content_updates ) = page.GetContentUpdates()
service_identfiers_to_content_updates[ service_identifier ] = content_updates
if len( service_identfiers_to_content_updates ) > 0: HC.app.Write( 'content_updates', service_identfiers_to_content_updates )
except Exception as e: wx.MessageBox( 'Saving mapping changes to DB raised this error: ' + HC.u( e ) )
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 self._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_identifier, tag_service_identifier, media ):
def InitialiseControls():
self._tags_box = ClientGUICommon.TagsBoxManage( self, self.AddTag, self._current_tags, self._deleted_tags, self._pending_tags, self._petitioned_tags )
self._add_tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddTag, self._file_service_identifier, self._tag_service_identifier )
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():
pass
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 ): 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, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._add_tag_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( copy_paste_hbox, FLAGS_BUTTON_SIZERS )
vbox.AddF( self._modify_mappers, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
wx.Panel.__init__( self, parent )
self._file_service_identifier = file_service_identifier
self._tag_service_identifier = tag_service_identifier
self._i_am_local_tag_service = self._tag_service_identifier.GetType() == HC.LOCAL_TAG
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.Read( 'service', tag_service_identifier )
self._account = service.GetAccount()
tags_managers = [ m.GetTagsManager() for m in media ]
( self._current_tags, self._deleted_tags, self._pending_tags, self._petitioned_tags ) = CC.IntersectTags( tags_managers, tag_service_identifier )
self._current_tags.sort()
self._pending_tags.sort()
InitialiseControls()
PopulateControls()
ArrangeControls()
def _AddTag( self, tag, only_add = False ):
if self._i_am_local_tag_service:
if tag in self._pending_tags:
if only_add: return
self._pending_tags.remove( tag )
self._tags_box.RescindPend( tag )
elif tag in self._petitioned_tags:
self._petitioned_tags.remove( tag )
self._tags_box.RescindPetition( tag )
elif tag in self._current_tags:
if only_add: return
self._petitioned_tags.append( tag )
self._tags_box.PetitionTag( tag )
else:
self._pending_tags.append( tag )
self._tags_box.PendTag( tag )
self._content_updates = []
self._content_updates.extend( [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_ADD, ( tag, self._hashes ) ) for tag in self._pending_tags ] )
self._content_updates.extend( [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_DELETE, ( tag, self._hashes ) ) for tag in self._petitioned_tags ] )
else:
if tag in self._pending_tags:
if only_add: return
self._pending_tags.remove( tag )
self._tags_box.RescindPend( tag )
self._content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_RESCIND_PENDING, ( tag, self._hashes ) ) )
elif tag in self._petitioned_tags:
self._petitioned_tags.remove( tag )
self._tags_box.RescindPetition( tag )
self._content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_RESCIND_PETITION, ( tag, self._hashes ) ) )
elif tag in self._current_tags:
if only_add: return
if self._account.HasPermission( HC.RESOLVE_PETITIONS ):
self._content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_PETITION, ( tag, self._hashes, 'admin' ) ) )
self._petitioned_tags.append( tag )
self._tags_box.PetitionTag( tag )
elif self._account.HasPermission( HC.POST_PETITIONS ):
message = 'Enter a reason for this tag to be removed. A janitor will review your petition.'
with wx.TextEntryDialog( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
self._content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_PETITION, ( tag, self._hashes, dlg.GetValue() ) ) )
self._petitioned_tags.append( tag )
self._tags_box.PetitionTag( tag )
else:
self._content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_PENDING, ( tag, self._hashes ) ) )
self._pending_tags.append( tag )
self._tags_box.PendTag( tag )
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 ):
if wx.TheClipboard.Open():
tags = self._current_tags + self._pending_tags
text = yaml.safe_dump( tags )
data = wx.TextDataObject( text )
wx.TheClipboard.SetData( data )
wx.TheClipboard.Close()
else: wx.MessageBox( 'I could not get permission to access the clipboard.' )
def EventModify( self, event ):
tag = self._tags_box.GetSelectedTag()
if tag is not None and tag in self._current_tags or tag in self._petitioned_tags:
subject_identifiers = [ HC.AccountIdentifier( hash = hash, tag = tag ) for hash in self._hashes ]
try:
with ClientGUIDialogs.DialogModifyAccounts( self, self._tag_service_identifier, subject_identifiers ) as dlg: dlg.ShowModal()
except Exception as e: wx.MessageBox( HC.u( e ) )
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 )
tags = [ tag for tag in tags if tag not in self._current_tags and tag not in self._pending_tags ]
for tag in tags: self.AddTag( tag )
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 EventTagsBoxAction( self, event ):
tag = self._tags_box.GetSelectedTag()
if tag is not None: self.AddTag( tag )
def GetContentUpdates( self ): return ( self._tag_service_identifier, self._content_updates )
def GetServiceIdentifier( self ): return self._tag_service_identifier
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 ): self._add_tag_box.SetFocus()