hydrus/include/ClientGUIDialogsManage.py

5065 lines
188 KiB
Python

import ClientCaches
import ClientConstants as CC
import ClientData
import ClientDefaults
import ClientDragDrop
import ClientExporting
import ClientFiles
import ClientGUIACDropdown
import ClientGUICollapsible
import ClientGUICommon
import ClientGUIListBoxes
import ClientGUIDialogs
import ClientDownloading
import ClientGUIOptionsPanels
import ClientGUIPredicates
import ClientGUIScrolledPanelsEdit
import ClientGUITopLevelWindows
import ClientImporting
import ClientMedia
import ClientRatings
import ClientSearch
import ClientServices
import collections
import HydrusConstants as HC
import HydrusData
import HydrusExceptions
import HydrusGlobals as HG
import HydrusNATPunch
import HydrusNetwork
import HydrusPaths
import HydrusSerialisable
import HydrusTagArchive
import HydrusTags
import itertools
import multipart
import os
import random
import re
import string
import time
import traceback
import urllib
import wx
import yaml
# Option Enums
ID_NULL = wx.NewId()
ID_TIMER_UPDATE = wx.NewId()
# Hue is generally 200, Sat and Lum changes based on need
COLOUR_SELECTED = wx.Colour( 217, 242, 255 )
COLOUR_SELECTED_DARK = wx.Colour( 1, 17, 26 )
COLOUR_UNSELECTED = wx.Colour( 223, 227, 230 )
def GenerateMultipartFormDataCTAndBodyFromDict( fields ):
m = multipart.Multipart()
for ( name, value ) in fields.items(): m.field( name, HydrusData.ToByteString( value ) )
return m.get()
class DialogManage4chanPass( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage 4chan pass' )
result = HG.client_controller.Read( 'serialisable_simple', '4chan_pass' )
if result is None:
result = ( '', '', 0 )
( token, pin, self._timeout ) = result
self._token = wx.TextCtrl( self )
self._pin = wx.TextCtrl( self )
self._status = ClientGUICommon.BetterStaticText( self )
self._SetStatus()
self._reauthenticate = wx.Button( self, label = 'reauthenticate' )
self._reauthenticate.Bind( wx.EVT_BUTTON, self.EventReauthenticate )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'Ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
self._token.SetValue( token )
self._pin.SetValue( pin )
rows = []
rows.append( ( 'token: ', self._token ) )
rows.append( ( 'pin: ', self._pin ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.AddF( self._ok, CC.FLAGS_VCENTER )
b_box.AddF( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( self._status, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._reauthenticate, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( b_box, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( 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 HydrusData.TimeHasPassed( self._timeout ): label = 'timed out'
else: label = 'authenticated - ' + HydrusData.ConvertTimestampToPrettyExpires( self._timeout )
self._status.SetLabelText( label )
def EventOK( self, event ):
token = self._token.GetValue()
pin = self._pin.GetValue()
HG.client_controller.Write( 'serialisable_simple', '4chan_pass', ( token, pin, self._timeout ) )
self.EndModal( wx.ID_OK )
def EventReauthenticate( self, event ):
token = self._token.GetValue()
pin = self._pin.GetValue()
if token == '' and pin == '':
self._timeout = 0
else:
form_fields = {}
form_fields[ 'act' ] = 'do_login'
form_fields[ 'id' ] = token
form_fields[ 'pin' ] = pin
form_fields[ 'long_login' ] = 'yes'
( ct, body ) = GenerateMultipartFormDataCTAndBodyFromDict( form_fields )
request_headers = {}
request_headers[ 'Content-Type' ] = ct
response = HG.client_controller.DoHTTP( HC.POST, 'https://sys.4chan.org/auth', request_headers = request_headers, body = body )
self._timeout = HydrusData.GetNow() + 365 * 24 * 3600
HG.client_controller.Write( 'serialisable_simple', '4chan_pass', ( token, pin, self._timeout ) )
self._SetStatus()
class DialogManageBoorus( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage boorus' )
self._names_to_delete = []
self._boorus = ClientGUICommon.ListBook( self )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
self._add.SetForegroundColour( ( 0, 128, 0 ) )
self._remove = wx.Button( self, label = 'remove' )
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
self._export = wx.Button( self, label = 'export' )
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
boorus = HG.client_controller.Read( 'remote_boorus' )
for ( name, booru ) in boorus.items():
self._boorus.AddPageArgs( name, name, self._Panel, ( self._boorus, booru ), {} )
#
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
add_remove_hbox.AddF( self._add, CC.FLAGS_VCENTER )
add_remove_hbox.AddF( self._remove, CC.FLAGS_VCENTER )
add_remove_hbox.AddF( self._export, CC.FLAGS_VCENTER )
ok_hbox = wx.BoxSizer( wx.HORIZONTAL )
ok_hbox.AddF( self._ok, CC.FLAGS_VCENTER )
ok_hbox.AddF( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._boorus, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( add_remove_hbox, CC.FLAGS_SMALL_INDENT )
vbox.AddF( ok_hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
self.SetDropTarget( ClientDragDrop.FileDropTarget( self.Import, None ) )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( 980, y ) )
wx.CallAfter( self._ok.SetFocus )
def EventAdd( self, event ):
with ClientGUIDialogs.DialogTextEntry( self, 'Enter new booru\'s name.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
try:
name = dlg.GetValue()
if self._boorus.KeyExists( name ):
raise HydrusExceptions.NameException( 'That name is already in use!' )
if name == '':
raise HydrusExceptions.NameException( 'Please enter a nickname for the booru.' )
booru = ClientData.Booru( name, 'search_url', '+', 1, 'thumbnail', '', 'original image', {} )
page = self._Panel( self._boorus, booru, is_new = True )
self._boorus.AddPage( name, name, page, select = True )
except HydrusExceptions.NameException as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
self.EventAdd( event )
def EventExport( self, event ):
booru_panel = self._boorus.GetCurrentPage()
if booru_panel is not None:
name = self._boorus.GetCurrentKey()
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:
path = HydrusData.ToUnicode( dlg.GetPath() )
with open( path, 'wb' ) as f: f.write( yaml.safe_dump( booru ) )
def EventOK( self, event ):
try:
for name in self._names_to_delete:
HG.client_controller.Write( 'delete_remote_booru', name )
for page in self._boorus.GetActivePages():
if page.HasChanges():
booru = page.GetBooru()
name = booru.GetName()
HG.client_controller.Write( 'remote_booru', name, booru )
finally: self.EndModal( wx.ID_OK )
def EventRemove( self, event ):
booru_panel = self._boorus.GetCurrentPage()
if booru_panel is not None:
name = self._boorus.GetCurrentKey()
self._names_to_delete.append( name )
self._boorus.DeleteCurrentPage()
def Import( self, paths ):
for path in paths:
try:
with open( path, 'rb' ) as f: file = f.read()
thing = yaml.safe_load( file )
if isinstance( thing, ClientData.Booru ):
booru = thing
name = booru.GetName()
if not self._boorus.KeyExists( name ):
new_booru = ClientData.Booru( name, 'search_url', '+', 1, 'thumbnail', '', 'original image', {} )
page = self._Panel( self._boorus, new_booru, is_new = True )
self._boorus.AddPage( name, name, page, select = True )
self._boorus.Select( name )
page = self._boorus.GetPage( name )
page.Update( booru )
except:
wx.MessageBox( traceback.format_exc() )
class _Panel( wx.Panel ):
def __init__( self, parent, booru, is_new = False ):
wx.Panel.__init__( self, parent )
self._booru = booru
self._is_new = is_new
( search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces ) = booru.GetData()
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 )
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 )
#
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 ) )
#
rows = []
rows.append( ( 'search url: ', self._search_url ) )
rows.append( ( 'search tag separator: ', self._search_separator ) )
rows.append( ( 'advance by page num: ', self._advance_by_page_num ) )
rows.append( ( 'thumbnail classname: ', self._thumb_classname ) )
gridbox = ClientGUICommon.WrapInGrid( self._search_panel, rows )
self._search_panel.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._search_panel.AddF( self._example_html_search, CC.FLAGS_EXPAND_PERPENDICULAR )
#
rows = []
rows.append( ( 'text: ', self._image_info ) )
rows.append( ( 'id of <img>: ', self._image_id ) )
rows.append( ( 'text of <a>: ', self._image_data ) )
gridbox = ClientGUICommon.WrapInGrid( self._image_panel, rows )
self._image_panel.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._image_panel.AddF( self._example_html_image, CC.FLAGS_EXPAND_PERPENDICULAR )
#
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( self._tag_classname, CC.FLAGS_VCENTER )
hbox.AddF( self._namespace, CC.FLAGS_VCENTER )
hbox.AddF( self._add, CC.FLAGS_VCENTER )
self._tag_panel.AddF( self._tag_classnames_to_namespaces, CC.FLAGS_EXPAND_BOTH_WAYS )
self._tag_panel.AddF( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._tag_panel.AddF( self._example_html_tags, CC.FLAGS_EXPAND_PERPENDICULAR )
#
self._booru_panel.AddF( self._search_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
self._booru_panel.AddF( self._image_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
self._booru_panel.AddF( self._tag_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._booru_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
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 ClientData.Booru( booru_name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces )
def HasChanges( self ):
if self._is_new: return True
( booru_name, my_search_url, my_search_separator, my_advance_by_page_num, my_thumb_classname, my_image_id, my_image_data, my_tag_classnames_to_namespaces ) = self._GetInfo()
( search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces ) = self._booru.GetData()
if search_url != my_search_url: return True
if search_separator != my_search_separator: return True
if advance_by_page_num != my_advance_by_page_num: return True
if thumb_classname != my_thumb_classname: return True
if image_id != my_image_id: return True
if image_data != my_image_data: return True
if tag_classnames_to_namespaces != my_tag_classnames_to_namespaces: return True
return False
def Update( self, booru ):
( search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces ) = booru.GetData()
self._search_url.SetValue( search_url )
self._search_separator.Select( self._search_separator.FindString( search_separator ) )
self._advance_by_page_num.SetValue( advance_by_page_num )
self._thumb_classname.SetValue( thumb_classname )
if image_id is None:
self._image_info.SetValue( image_data )
self._image_data.SetValue( True )
else:
self._image_info.SetValue( image_id )
self._image_id.SetValue( True )
self._tag_classnames_to_namespaces.Clear()
for ( tag_classname, namespace ) in tag_classnames_to_namespaces.items(): self._tag_classnames_to_namespaces.Append( tag_classname + ' : ' + namespace, ( tag_classname, namespace ) )
'''
class DialogManageContacts( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
def InitialiseControls():
self._contacts = ClientGUICommon.ListBook( self )
self._contacts.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventContactChanging )
self._contacts.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventContactChanged )
self._add_contact_address = wx.Button( self, label = 'add by contact address' )
self._add_contact_address.Bind( wx.EVT_BUTTON, self.EventAddByContactAddress )
self._add_contact_address.SetForegroundColour( ( 0, 128, 0 ) )
self._add_manually = wx.Button( self, label = 'add manually' )
self._add_manually.Bind( wx.EVT_BUTTON, self.EventAddManually )
self._add_manually.SetForegroundColour( ( 0, 128, 0 ) )
self._remove = wx.Button( self, label = 'remove' )
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
self._export = wx.Button( self, label = 'export' )
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
self._edit_log = []
( identities, contacts, deletable_names ) = HG.client_controller.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, CC.FLAGS_VCENTER )
add_remove_hbox.AddF( self._add_contact_address, CC.FLAGS_VCENTER )
add_remove_hbox.AddF( self._remove, CC.FLAGS_VCENTER )
add_remove_hbox.AddF( self._export, CC.FLAGS_VCENTER )
ok_hbox = wx.BoxSizer( wx.HORIZONTAL )
ok_hbox.AddF( self._ok, CC.FLAGS_VCENTER )
ok_hbox.AddF( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._contacts, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( add_remove_hbox, CC.FLAGS_SMALL_INDENT )
vbox.AddF( ok_hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage contacts' )
InitialiseControls()
PopulateControls()
ArrangeControls()
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( 980, y ) )
self.SetDropTarget( ClientDragDrop.FileDropTarget( self.Import, None ) )
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( HydrusData.ToUnicode( e ) )
return
with ClientGUIDialogs.DialogTextEntry( self, 'Enter contact\'s address in the form contact_key@hostname:port.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
contact_address = dlg.GetValue()
try:
( contact_key_encoded, address ) = contact_address.split( '@' )
contact_key = contact_key_encoded.decode( 'hex' )
( host, port ) = address.split( ':' )
port = int( port )
except: raise Exception( 'Could not parse the address!' )
name = contact_key_encoded
contact = ClientConstantsMessages.Contact( None, name, host, port )
try:
connection = contact.GetConnection()
public_key = connection.Get( 'public_key', contact_key = contact_key.encode( 'hex' ) )
except: raise Exception( 'Could not fetch the contact\'s public key from the address:' + os.linesep + traceback.format_exc() )
contact = ClientConstantsMessages.Contact( public_key, name, host, port )
self._edit_log.append( ( HC.ADD, contact ) )
page = self._Panel( self._contacts, contact, is_identity = False )
self._deletable_names.add( name )
self._contacts.AddPage( page, name, select = True )
def EventAddManually( self, event ):
try: self._CheckCurrentContactIsValid()
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
return
with ClientGUIDialogs.DialogTextEntry( self, 'Enter new contact\'s name.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
name = dlg.GetValue()
if self._contacts.NameExists( name ) or self._contacts.NameExists( ' identity - ' + name ) or name == 'Anonymous': raise Exception( 'That name is already in use!' )
if name == '': raise Exception( 'Please enter a nickname for the service.' )
public_key = None
host = 'hostname'
port = 45871
contact = ClientConstantsMessages.Contact( public_key, name, host, port )
self._edit_log.append( ( HC.ADD, contact ) )
page = self._Panel( self._contacts, contact, is_identity = False )
self._deletable_names.add( name )
self._contacts.AddPage( page, name, select = True )
def EventContactChanged( self, event ):
contact_panel = self._contacts.GetCurrentPage()
if contact_panel is not None:
old_name = contact_panel.GetOriginalName()
if old_name in self._deletable_names: self._remove.Enable()
else: self._remove.Disable()
def EventContactChanging( self, event ):
try: self._CheckCurrentContactIsValid()
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( 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:
path = HydrusData.ToUnicode( dlg.GetPath() )
with open( path, '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:
path = HydrusData.ToUnicode( dlg.GetPath() )
with open( path, 'wb' ) as f: f.write( yaml.safe_dump( contact ) )
def EventOK( self, event ):
try: self._CheckCurrentContactIsValid()
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
return
for ( name, page ) in self._contacts.GetNamesToActivePages().items():
if page.HasChanges(): self._edit_log.append( ( HC.EDIT, ( page.GetOriginalName(), page.GetContact() ) ) )
try:
if len( self._edit_log ) > 0: HG.client_controller.Write( 'update_contacts', self._edit_log )
finally: self.EndModal( wx.ID_OK )
# this isn't used yet!
def EventRemove( self, event ):
contact_panel = self._contacts.GetCurrentPage()
if contact_panel is not None:
name = contact_panel.GetOriginalName()
self._edit_log.append( ( HC.DELETE, name ) )
self._contacts.DeleteCurrentPage()
self._deletable_names.discard( name )
def Import( self, paths ):
try: self._CheckCurrentContactIsValid()
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
return
for path in paths:
try:
with open( path, 'rb' ) as f: file = f.read()
obj = yaml.safe_load( file )
if type( obj ) == ClientConstantsMessages.Contact:
contact = obj
name = contact.GetName()
if self._contacts.NameExists( name ) or self._contacts.NameExists( ' identities - ' + name ) or name == 'Anonymous':
message = 'There already exists a contact or identity with the name ' + name + '. Do you want to overwrite, or make a new contact?'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Please choose what to do.', yes_label = 'overwrite', no_label = 'make new' ) as dlg:
if True:
name_to_page_dict = self._contacts.GetNamesToActivePages()
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 + str( random.randint( 0, 9 ) )
( public_key, old_name, host, port ) = contact.GetInfo()
new_contact = ClientConstantsMessages.Contact( public_key, name, host, port )
self._edit_log.append( ( HC.ADD, contact ) )
self._deletable_names.add( name )
page = self._Panel( self._contacts, contact, False )
self._contacts.AddPage( page, name, select = True )
else:
( public_key, old_name, host, port ) = contact.GetInfo()
new_contact = ClientConstantsMessages.Contact( public_key, name, host, port )
self._edit_log.append( ( HC.ADD, contact ) )
self._deletable_names.add( name )
page = self._Panel( self._contacts, contact, False )
self._contacts.AddPage( page, name, select = True )
except:
wx.MessageBox( traceback.format_exc() )
class _Panel( wx.Panel ):
def __init__( self, parent, contact, is_identity ):
wx.Panel.__init__( self, parent )
self._contact = contact
self._is_identity = is_identity
( public_key, name, host, port ) = contact.GetInfo()
contact_key = contact.GetContactKey()
def InitialiseControls():
self._contact_panel = ClientGUICommon.StaticBox( self, 'contact' )
self._name = wx.TextCtrl( self._contact_panel )
self._contact_address = wx.TextCtrl( self._contact_panel )
self._public_key = ClientGUICommon.SaneMultilineTextCtrl( self._contact_panel )
def PopulateControls():
self._name.SetValue( name )
contact_address = host + ':' + str( 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():
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self._contact_panel, label = 'name' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._name, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._contact_panel, label = 'contact address' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._contact_address, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._contact_panel, label = 'public key' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._public_key, CC.FLAGS_EXPAND_BOTH_WAYS )
self._contact_panel.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._contact_panel, CC.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 + ':' + str( port )
if contact_key is not None: contact_address = contact_key.encode( 'hex' ) + '@' + contact_address
self._contact_address.SetValue( contact_address )
if public_key is None: public_key = ''
self._public_key.SetValue( public_key )
'''
class DialogManageExportFolders( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage export folders' )
self._export_folders = ClientGUICommon.SaneListCtrlForSingleObject( self, 120, [ ( 'name', 120 ), ( 'path', -1 ), ( 'type', 120 ), ( 'query', 120 ), ( 'period', 120 ), ( 'phrase', 120 ) ], delete_key_callback = self.Delete, activation_callback = self.Edit )
export_folders = HG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_EXPORT_FOLDER )
for export_folder in export_folders:
path = export_folder.GetName()
( display_tuple, sort_tuple ) = self._ConvertExportFolderToTuples( export_folder )
self._export_folders.Append( display_tuple, sort_tuple, export_folder )
self._add_button = wx.Button( self, label = 'add' )
self._add_button.Bind( wx.EVT_BUTTON, self.EventAdd )
self._edit_button = wx.Button( self, label = 'edit' )
self._edit_button.Bind( wx.EVT_BUTTON, self.EventEdit )
self._delete_button = wx.Button( self, label = 'delete' )
self._delete_button.Bind( wx.EVT_BUTTON, self.EventDelete )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
file_buttons = wx.BoxSizer( wx.HORIZONTAL )
file_buttons.AddF( self._add_button, CC.FLAGS_VCENTER )
file_buttons.AddF( self._edit_button, CC.FLAGS_VCENTER )
file_buttons.AddF( self._delete_button, CC.FLAGS_VCENTER )
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, CC.FLAGS_VCENTER )
buttons.AddF( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
intro = 'Here you can set the client to regularly export a certain query to a particular location.'
vbox.AddF( ClientGUICommon.BetterStaticText( self, intro ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._export_folders, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( file_buttons, CC.FLAGS_BUTTON_SIZER )
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
if x < 780: x = 780
if y < 480: y = 480
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def _AddFolder( self ):
new_options = HG.client_controller.GetNewOptions()
phrase = new_options.GetString( 'export_phrase' )
name = 'export folder'
path = ''
export_type = HC.EXPORT_FOLDER_TYPE_REGULAR
file_search_context = ClientSearch.FileSearchContext( file_service_key = CC.LOCAL_FILE_SERVICE_KEY )
period = 15 * 60
export_folder = ClientExporting.ExportFolder( name, path, export_type = export_type, file_search_context = file_search_context, period = period, phrase = phrase )
with DialogManageExportFoldersEdit( self, export_folder ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
export_folder = dlg.GetInfo()
self._export_folders.SetNonDupeName( export_folder )
( display_tuple, sort_tuple ) = self._ConvertExportFolderToTuples( export_folder )
self._export_folders.Append( display_tuple, sort_tuple, export_folder )
def _ConvertExportFolderToTuples( self, export_folder ):
( name, path, export_type, file_search_context, period, phrase ) = export_folder.ToTuple()
if export_type == HC.EXPORT_FOLDER_TYPE_REGULAR:
pretty_export_type = 'regular'
elif export_type == HC.EXPORT_FOLDER_TYPE_SYNCHRONISE:
pretty_export_type = 'synchronise'
pretty_file_search_context = ', '.join( predicate.GetUnicode( with_count = False ) for predicate in file_search_context.GetPredicates() )
pretty_period = HydrusData.ConvertTimeDeltaToPrettyString( period )
pretty_phrase = phrase
display_tuple = ( name, path, pretty_export_type, pretty_file_search_context, pretty_period, pretty_phrase )
sort_tuple = ( name, path, pretty_export_type, pretty_file_search_context, period, phrase )
return ( display_tuple, sort_tuple )
def Delete( self ):
with ClientGUIDialogs.DialogYesNo( self, 'Remove all selected?' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._export_folders.RemoveAllSelected()
def Edit( self ):
indices = self._export_folders.GetAllSelected()
for index in indices:
export_folder = self._export_folders.GetObject( index )
original_name = export_folder.GetName()
with DialogManageExportFoldersEdit( self, export_folder ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
export_folder = dlg.GetInfo()
if export_folder.GetName() != original_name:
self._export_folders.SetNonDupeName( export_folder )
( display_tuple, sort_tuple ) = self._ConvertExportFolderToTuples( export_folder )
self._export_folders.UpdateRow( index, display_tuple, sort_tuple, export_folder )
def EventAdd( self, event ):
self._AddFolder()
def EventDelete( self, event ):
self.Delete()
def EventEdit( self, event ):
self.Edit()
def EventOK( self, event ):
existing_db_names = set( HG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_EXPORT_FOLDER ) )
export_folders = self._export_folders.GetObjects()
good_names = set()
for export_folder in export_folders:
HG.client_controller.Write( 'serialisable', export_folder )
good_names.add( export_folder.GetName() )
names_to_delete = existing_db_names - good_names
for name in names_to_delete:
HG.client_controller.Write( 'delete_serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_EXPORT_FOLDER, name )
HG.client_controller.pub( 'notify_new_export_folders' )
self.EndModal( wx.ID_OK )
class DialogManageExportFoldersEdit( ClientGUIDialogs.Dialog ):
def __init__( self, parent, export_folder ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'edit export folder' )
self._export_folder = export_folder
( name, path, export_type, file_search_context, period, phrase ) = self._export_folder.ToTuple()
self._path_box = ClientGUICommon.StaticBox( self, 'name and location' )
self._name = wx.TextCtrl( self._path_box)
self._path = wx.DirPickerCtrl( self._path_box, style = wx.DIRP_USE_TEXTCTRL )
#
self._type_box = ClientGUICommon.StaticBox( self, 'type of export' )
self._type = ClientGUICommon.BetterChoice( self._type_box )
self._type.Append( 'regular', HC.EXPORT_FOLDER_TYPE_REGULAR )
self._type.Append( 'synchronise', HC.EXPORT_FOLDER_TYPE_SYNCHRONISE )
#
self._query_box = ClientGUICommon.StaticBox( self, 'query to export' )
self._page_key = HydrusData.GenerateKey()
predicates = file_search_context.GetPredicates()
self._predicates_box = ClientGUIListBoxes.ListBoxTagsActiveSearchPredicates( self._query_box, self._page_key, predicates )
self._searchbox = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self._query_box, self._page_key, file_search_context )
#
self._period_box = ClientGUICommon.StaticBox( self, 'export period' )
self._period = ClientGUICommon.TimeDeltaButton( self._period_box, min = 3 * 60, days = True, hours = True, minutes = True )
#
self._phrase_box = ClientGUICommon.StaticBox( self, 'filenames' )
self._pattern = wx.TextCtrl( self._phrase_box )
self._examples = ClientGUICommon.ExportPatternButton( self._phrase_box )
#
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
self._name.SetValue( name )
self._path.SetPath( path )
self._type.SelectClientData( export_type )
self._period.SetValue( period )
self._pattern.SetValue( phrase )
#
rows = []
rows.append( ( 'name: ', self._name ) )
rows.append( ( 'folder path: ', self._path ) )
gridbox = ClientGUICommon.WrapInGrid( self._path_box, rows )
self._path_box.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
text = '''regular - try to export the files to the directory, overwriting if the filesize if different
synchronise - try to export the files to the directory, overwriting if the filesize if different, and delete anything else in the directory
If you select synchronise, be careful!'''
st = wx.StaticText( self._type_box, label = text )
st.Wrap( 440 )
self._type_box.AddF( st, CC.FLAGS_EXPAND_PERPENDICULAR )
self._type_box.AddF( self._type, CC.FLAGS_EXPAND_PERPENDICULAR )
self._query_box.AddF( self._predicates_box, CC.FLAGS_EXPAND_BOTH_WAYS )
self._query_box.AddF( self._searchbox, CC.FLAGS_EXPAND_PERPENDICULAR )
self._period_box.AddF( self._period, CC.FLAGS_EXPAND_PERPENDICULAR )
phrase_hbox = wx.BoxSizer( wx.HORIZONTAL )
phrase_hbox.AddF( self._pattern, CC.FLAGS_EXPAND_BOTH_WAYS )
phrase_hbox.AddF( self._examples, CC.FLAGS_VCENTER )
self._phrase_box.AddF( phrase_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, CC.FLAGS_VCENTER )
buttons.AddF( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._path_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._type_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._query_box, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._period_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._phrase_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( 480, y ) )
wx.CallAfter( self._ok.SetFocus )
def EventOK( self, event ):
if self._path.GetPath() in ( '', None ):
wx.MessageBox( 'You must enter a folder path to export to!' )
return
phrase = self._pattern.GetValue()
try:
ClientExporting.ParseExportPhrase( phrase )
except:
wx.MessageBox( 'Could not parse that export phrase!' )
return
self.EndModal( wx.ID_OK )
def GetInfo( self ):
name = self._name.GetValue()
path = HydrusData.ToUnicode( self._path.GetPath() )
export_type = self._type.GetChoice()
file_search_context = self._searchbox.GetFileSearchContext()
predicates = self._predicates_box.GetPredicates()
file_search_context.SetPredicates( predicates )
period = self._period.GetValue()
phrase = self._pattern.GetValue()
export_folder = ClientExporting.ExportFolder( name, path, export_type, file_search_context, period, phrase )
return export_folder
'''
class DialogManageImageboards( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
def InitialiseControls():
self._sites = ClientGUICommon.ListBook( self )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
self._add.SetForegroundColour( ( 0, 128, 0 ) )
self._remove = wx.Button( self, label = 'remove' )
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
self._export = wx.Button( self, label = 'export' )
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
def PopulateControls():
self._names_to_delete = []
sites = HG.client_controller.Read( 'imageboards' )
for ( name, imageboards ) in sites.items():
self._sites.AddPageArgs( name, name, self._Panel, ( self._sites, imageboards ), {} )
def ArrangeControls():
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
add_remove_hbox.AddF( self._add, CC.FLAGS_VCENTER )
add_remove_hbox.AddF( self._remove, CC.FLAGS_VCENTER )
add_remove_hbox.AddF( self._export, CC.FLAGS_VCENTER )
ok_hbox = wx.BoxSizer( wx.HORIZONTAL )
ok_hbox.AddF( self._ok, CC.FLAGS_VCENTER )
ok_hbox.AddF( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._sites, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( add_remove_hbox, CC.FLAGS_SMALL_INDENT )
vbox.AddF( ok_hbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage imageboards' )
InitialiseControls()
PopulateControls()
ArrangeControls()
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( 980, y ) )
self.SetDropTarget( ClientDragDrop.FileDropTarget( self.Import, None ) )
wx.CallAfter( self._ok.SetFocus )
def EventAdd( self, event ):
with ClientGUIDialogs.DialogTextEntry( self, 'Enter new site\'s name.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
try:
name = dlg.GetValue()
if self._sites.KeyExists( name ): raise HydrusExceptions.NameException( 'That name is already in use!' )
if name == '': raise HydrusExceptions.NameException( 'Please enter a nickname for the service.' )
page = self._Panel( self._sites, [], is_new = True )
self._sites.AddPage( name, name, page, select = True )
except HydrusExceptions.NameException as e:
wx.MessageBox( HydrusData.ToUnicode( e ) )
self.EventAdd( event )
def EventExport( self, event ):
site_panel = self._sites.GetCurrentPage()
if site_panel is not None:
name = self._sites.GetCurrentKey()
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:
path = HydrusData.ToUnicode( dlg.GetPath() )
with open( path, 'wb' ) as f: f.write( yaml.safe_dump( dict ) )
def EventOK( self, event ):
try:
for name in self._names_to_delete:
HG.client_controller.Write( 'delete_imageboard', name )
for page in self._sites.GetActivePages():
if page.HasChanges():
imageboards = page.GetImageboards()
name = 'this is old code'
HG.client_controller.Write( 'imageboard', name, imageboards )
finally: self.EndModal( wx.ID_OK )
def EventRemove( self, event ):
site_panel = self._sites.GetCurrentPage()
if site_panel is not None:
name = self._sites.GetCurrentKey()
self._names_to_delete.append( name )
self._sites.DeleteCurrentPage()
def Import( self, paths ):
for path in paths:
try:
with open( path, 'rb' ) as f: file = f.read()
thing = yaml.safe_load( file )
if isinstance( thing, dict ):
( name, imageboards ) = thing.items()[0]
if not self._sites.KeyExists( name ):
page = self._Panel( self._sites, [], is_new = True )
self._sites.AddPage( name, name, page, select = True )
page = self._sites.GetPage( name )
for imageboard in imageboards:
if isinstance( imageboard, ClientData.Imageboard ): page.UpdateImageboard( imageboard )
elif isinstance( thing, ClientData.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, is_new = False ):
def InitialiseControls():
self._site_panel = ClientGUICommon.StaticBox( self, 'site' )
self._imageboards = ClientGUICommon.ListBook( self._site_panel )
self._add = wx.Button( self._site_panel, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
self._add.SetForegroundColour( ( 0, 128, 0 ) )
self._remove = wx.Button( self._site_panel, label = 'remove' )
self._remove.Bind( wx.EVT_BUTTON, self.EventRemove )
self._remove.SetForegroundColour( ( 128, 0, 0 ) )
self._export = wx.Button( self._site_panel, label = 'export' )
self._export.Bind( wx.EVT_BUTTON, self.EventExport )
def PopulateControls():
for imageboard in imageboards:
name = imageboard.GetName()
self._imageboards.AddPageArgs( name, name, self._Panel, ( self._imageboards, imageboard ), {} )
def ArrangeControls():
add_remove_hbox = wx.BoxSizer( wx.HORIZONTAL )
add_remove_hbox.AddF( self._add, CC.FLAGS_VCENTER )
add_remove_hbox.AddF( self._remove, CC.FLAGS_VCENTER )
add_remove_hbox.AddF( self._export, CC.FLAGS_VCENTER )
self._site_panel.AddF( self._imageboards, CC.FLAGS_EXPAND_BOTH_WAYS )
self._site_panel.AddF( add_remove_hbox, CC.FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._site_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
wx.Panel.__init__( self, parent )
self._original_imageboards = imageboards
self._has_changes = False
self._is_new = is_new
InitialiseControls()
PopulateControls()
ArrangeControls()
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( 980, y ) )
def EventAdd( self, event ):
with ClientGUIDialogs.DialogTextEntry( self, 'Enter new imageboard\'s name.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
try:
name = dlg.GetValue()
if self._imageboards.KeyExists( name ): raise HydrusExceptions.NameException()
if name == '': raise Exception( 'Please enter a nickname for the service.' )
imageboard = ClientData.Imageboard( name, '', 60, [], {} )
page = self._Panel( self._imageboards, imageboard, is_new = True )
self._imageboards.AddPage( name, name, page, select = True )
self._has_changes = True
except Exception as e:
wx.MessageBox( HydrusData.ToUnicode( 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:
path = HydrusData.ToUnicode( dlg.GetPath() )
with open( path, '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.GetCurrentKey()
self._imageboards.DeleteCurrentPage()
self._has_changes = True
def GetImageboards( self ):
names_to_imageboards = { imageboard.GetName() : imageboard for imageboard in self._original_imageboards if self._imageboards.KeyExists( imageboard.GetName() ) }
for page in self._imageboards.GetActivePages():
imageboard = page.GetImageboard()
names_to_imageboards[ imageboard.GetName() ] = imageboard
return names_to_imageboards.values()
def HasChanges( self ):
if self._is_new: return True
return self._has_changes or True in ( page.HasChanges() for page in self._imageboards.GetActivePages() )
def UpdateImageboard( self, imageboard ):
name = imageboard.GetName()
if not self._imageboards.KeyExists( name ):
new_imageboard = ClientData.Imageboard( name, '', 60, [], {} )
page = self._Panel( self._imageboards, new_imageboard, is_new = True )
self._imageboards.AddPage( name, name, page, select = True )
page = self._imageboards.GetPage( name )
page.Update( imageboard )
class _Panel( wx.Panel ):
def __init__( self, parent, imageboard, is_new = False ):
wx.Panel.__init__( self, parent )
self._imageboard = imageboard
self._is_new = is_new
( 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 ) ], delete_key_callback = self.Delete )
self._add = wx.Button( self._form_fields_panel, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
self._edit = wx.Button( self._form_fields_panel, label = 'edit' )
self._edit.Bind( wx.EVT_BUTTON, self.EventEdit )
self._delete = wx.Button( self._form_fields_panel, label = 'delete' )
self._delete.Bind( wx.EVT_BUTTON, self.EventDelete )
#
self._restrictions_panel = ClientGUICommon.StaticBox( self._imageboard_panel, 'restrictions' )
self._min_resolution = ClientGUICommon.NoneableSpinCtrl( self._restrictions_panel, 'min resolution', num_dimensions = 2 )
self._max_resolution = ClientGUICommon.NoneableSpinCtrl( self._restrictions_panel, 'max resolution', num_dimensions = 2 )
self._max_file_size = ClientGUICommon.NoneableSpinCtrl( self._restrictions_panel, 'max file size (KB)', multiplier = 1024 )
self._allowed_mimes_panel = ClientGUICommon.StaticBox( self._restrictions_panel, 'allowed mimes' )
self._mimes = wx.ListBox( self._allowed_mimes_panel )
self._mime_choice = wx.Choice( self._allowed_mimes_panel )
self._add_mime = wx.Button( self._allowed_mimes_panel, label = 'add' )
self._add_mime.Bind( wx.EVT_BUTTON, self.EventAddMime )
self._remove_mime = wx.Button( self._allowed_mimes_panel, label = 'remove' )
self._remove_mime.Bind( wx.EVT_BUTTON, self.EventRemoveMime )
def PopulateControls():
#
self._post_url.SetValue( post_url )
self._flood_time.SetRange( 5, 1200 )
self._flood_time.SetValue( flood_time )
#
for ( name, field_type, default, editable ) in form_fields:
self._form_fields.Append( ( name, CC.field_string_lookup[ field_type ], HydrusData.ToUnicode( default ), HydrusData.ToUnicode( editable ) ), ( name, field_type, default, editable ) )
#
if CC.RESTRICTION_MIN_RESOLUTION in restrictions: value = restrictions[ CC.RESTRICTION_MIN_RESOLUTION ]
else: value = None
self._min_resolution.SetValue( value )
if CC.RESTRICTION_MAX_RESOLUTION in restrictions: value = restrictions[ CC.RESTRICTION_MAX_RESOLUTION ]
else: value = None
self._max_resolution.SetValue( value )
if CC.RESTRICTION_MAX_FILE_SIZE in restrictions: value = restrictions[ CC.RESTRICTION_MAX_FILE_SIZE ]
else: value = None
self._max_file_size.SetValue( value )
if CC.RESTRICTION_ALLOWED_MIMES in restrictions: mimes = restrictions[ CC.RESTRICTION_ALLOWED_MIMES ]
else: mimes = []
for mime in mimes: self._mimes.Append( HC.mime_string_lookup[ mime ], mime )
for mime in HC.ALLOWED_MIMES: self._mime_choice.Append( HC.mime_string_lookup[ mime ], mime )
self._mime_choice.SetSelection( 0 )
def ArrangeControls():
gridbox = wx.FlexGridSizer( 0, 2 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self._basic_info_panel, label = 'POST URL' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._post_url, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._basic_info_panel, label = 'flood time' ), CC.FLAGS_VCENTER )
gridbox.AddF( self._flood_time, CC.FLAGS_EXPAND_BOTH_WAYS )
self._basic_info_panel.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
h_b_box = wx.BoxSizer( wx.HORIZONTAL )
h_b_box.AddF( self._add, CC.FLAGS_VCENTER )
h_b_box.AddF( self._edit, CC.FLAGS_VCENTER )
h_b_box.AddF( self._delete, CC.FLAGS_VCENTER )
self._form_fields_panel.AddF( self._form_fields, CC.FLAGS_EXPAND_BOTH_WAYS )
self._form_fields_panel.AddF( h_b_box, CC.FLAGS_BUTTON_SIZER )
#
mime_buttons_box = wx.BoxSizer( wx.HORIZONTAL )
mime_buttons_box.AddF( self._mime_choice, CC.FLAGS_VCENTER )
mime_buttons_box.AddF( self._add_mime, CC.FLAGS_VCENTER )
mime_buttons_box.AddF( self._remove_mime, CC.FLAGS_VCENTER )
self._allowed_mimes_panel.AddF( self._mimes, CC.FLAGS_EXPAND_BOTH_WAYS )
self._allowed_mimes_panel.AddF( mime_buttons_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._restrictions_panel.AddF( self._min_resolution, CC.FLAGS_EXPAND_PERPENDICULAR )
self._restrictions_panel.AddF( self._max_resolution, CC.FLAGS_EXPAND_PERPENDICULAR )
self._restrictions_panel.AddF( self._max_file_size, CC.FLAGS_EXPAND_PERPENDICULAR )
self._restrictions_panel.AddF( self._allowed_mimes_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
#
self._imageboard_panel.AddF( self._basic_info_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
self._imageboard_panel.AddF( self._form_fields_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self._imageboard_panel.AddF( self._restrictions_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._imageboard_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
InitialiseControls()
PopulateControls()
ArrangeControls()
def _GetInfo( self ):
imageboard_name = self._imageboard.GetName()
post_url = self._post_url.GetValue()
flood_time = self._flood_time.GetValue()
form_fields = self._form_fields.GetClientData()
restrictions = {}
value = self._min_resolution.GetValue()
if value is not None: restrictions[ CC.RESTRICTION_MIN_RESOLUTION ] = value
value = self._max_resolution.GetValue()
if value is not None: restrictions[ CC.RESTRICTION_MAX_RESOLUTION ] = value
value = self._max_file_size.GetValue()
if value is not None: restrictions[ CC.RESTRICTION_MAX_FILE_SIZE ] = value
mimes = [ self._mimes.GetClientData( i ) for i in range( self._mimes.GetCount() ) ]
if len( mimes ) > 0: restrictions[ CC.RESTRICTION_ALLOWED_MIMES ] = mimes
return ( imageboard_name, post_url, flood_time, form_fields, restrictions )
def Delete( self ): self._form_fields.RemoveAllSelected()
def EventAdd( self, event ):
with ClientGUIDialogs.DialogInputNewFormField( self ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( name, field_type, default, editable ) = dlg.GetFormField()
if name in [ form_field[0] for form_field in self._form_fields.GetClientData() ]:
wx.MessageBox( 'There is already a field named ' + name )
self.EventAdd( event )
return
self._form_fields.Append( ( name, CC.field_string_lookup[ field_type ], HydrusData.ToUnicode( default ), HydrusData.ToUnicode( editable ) ), ( name, field_type, default, editable ) )
def EventAddMime( self, event ):
selection = self._mime_choice.GetSelection()
if selection != wx.NOT_FOUND:
mime = self._mime_choice.GetClientData( selection )
existing_mimes = [ self._mimes.GetClientData( i ) for i in range( self._mimes.GetCount() ) ]
if mime not in existing_mimes: self._mimes.Append( HC.mime_string_lookup[ mime ], mime )
def EventDelete( self, event ): self.Delete()
def EventRemoveMime( self, event ):
selection = self._mimes.GetSelection()
if selection != wx.NOT_FOUND: self._mimes.Delete( selection )
def EventEdit( self, event ):
indices = self._form_fields.GetAllSelected()
for index in indices:
( name, field_type, default, editable ) = self._form_fields.GetClientData( index )
form_field = ( name, field_type, default, editable )
with ClientGUIDialogs.DialogInputNewFormField( self, form_field ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
old_name = name
( name, field_type, default, editable ) = dlg.GetFormField()
if old_name != name:
if name in [ form_field[0] for form_field in self._form_fields.GetClientData() ]: raise Exception( 'You already have a form field called ' + name + '; delete or edit that one first' )
self._form_fields.UpdateRow( index, ( name, CC.field_string_lookup[ field_type ], HydrusData.ToUnicode( default ), HydrusData.ToUnicode( editable ) ), ( name, field_type, default, editable ) )
def GetImageboard( self ):
( name, post_url, flood_time, form_fields, restrictions ) = self._GetInfo()
return ClientData.Imageboard( name, post_url, flood_time, form_fields, restrictions )
def HasChanges( self ):
if self._is_new: return True
( my_name, my_post_url, my_flood_time, my_form_fields, my_restrictions ) = self._GetInfo()
( post_url, flood_time, form_fields, restrictions ) = self._imageboard.GetBoardInfo()
if post_url != my_post_url: return True
if flood_time != my_flood_time: return True
if set( [ tuple( item ) for item in form_fields ] ) != set( [ tuple( item ) for item in my_form_fields ] ): return True
if restrictions != my_restrictions: return True
return False
def Update( self, imageboard ):
( post_url, flood_time, form_fields, restrictions ) = imageboard.GetBoardInfo()
self._post_url.SetValue( post_url )
self._flood_time.SetValue( flood_time )
self._form_fields.ClearAll()
self._form_fields.InsertColumn( 0, 'name', width = 120 )
self._form_fields.InsertColumn( 1, 'type', width = 120 )
self._form_fields.InsertColumn( 2, 'default' )
self._form_fields.InsertColumn( 3, 'editable', width = 120 )
self._form_fields.setResizeColumn( 3 ) # default
for ( name, field_type, default, editable ) in form_fields:
self._form_fields.Append( ( name, CC.field_string_lookup[ field_type ], HydrusData.ToUnicode( default ), HydrusData.ToUnicode( editable ) ), ( name, field_type, default, editable ) )
if CC.RESTRICTION_MIN_RESOLUTION in restrictions: value = restrictions[ CC.RESTRICTION_MIN_RESOLUTION ]
else: value = None
self._min_resolution.SetValue( value )
if CC.RESTRICTION_MAX_RESOLUTION in restrictions: value = restrictions[ CC.RESTRICTION_MAX_RESOLUTION ]
else: value = None
self._max_resolution.SetValue( value )
if CC.RESTRICTION_MAX_FILE_SIZE in restrictions: value = restrictions[ CC.RESTRICTION_MAX_FILE_SIZE ]
else: value = None
self._max_file_size.SetValue( value )
self._mimes.Clear()
if CC.RESTRICTION_ALLOWED_MIMES in restrictions: mimes = restrictions[ CC.RESTRICTION_ALLOWED_MIMES ]
else: mimes = []
for mime in mimes: self._mimes.Append( HC.mime_string_lookup[ mime ], mime )
'''
class DialogManageImportFolders( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage import folders' )
self._import_folders = ClientGUICommon.SaneListCtrlForSingleObject( self, 120, [ ( 'name', 120 ), ( 'path', -1 ), ( 'check period', 120 ) ], delete_key_callback = self.Delete, activation_callback = self.Edit )
self._add_button = wx.Button( self, label = 'add' )
self._add_button.Bind( wx.EVT_BUTTON, self.EventAdd )
self._edit_button = wx.Button( self, label = 'edit' )
self._edit_button.Bind( wx.EVT_BUTTON, self.EventEdit )
self._delete_button = wx.Button( self, label = 'delete' )
self._delete_button.Bind( wx.EVT_BUTTON, self.EventDelete )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
import_folders = HG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FOLDER )
for import_folder in import_folders:
( display_tuple, sort_tuple ) = self._ConvertImportFolderToTuples( import_folder )
self._import_folders.Append( display_tuple, sort_tuple, import_folder )
#
file_buttons = wx.BoxSizer( wx.HORIZONTAL )
file_buttons.AddF( self._add_button, CC.FLAGS_VCENTER )
file_buttons.AddF( self._edit_button, CC.FLAGS_VCENTER )
file_buttons.AddF( self._delete_button, CC.FLAGS_VCENTER )
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, CC.FLAGS_VCENTER )
buttons.AddF( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
intro = 'Here you can set the client to regularly check certain folders for new files to import.'
vbox.AddF( ClientGUICommon.BetterStaticText( self, intro ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._import_folders, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( file_buttons, CC.FLAGS_BUTTON_SIZER )
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
#
( x, y ) = self.GetEffectiveMinSize()
if x < 780: x = 780
if y < 480: y = 480
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def _AddImportFolder( self ):
import_folder = ClientImporting.ImportFolder( 'import folder' )
with DialogManageImportFoldersEdit( self, import_folder ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
import_folder = dlg.GetInfo()
self._import_folders.SetNonDupeName( import_folder )
( display_tuple, sort_tuple ) = self._ConvertImportFolderToTuples( import_folder )
self._import_folders.Append( display_tuple, sort_tuple, import_folder )
def _ConvertImportFolderToTuples( self, import_folder ):
sort_tuple = import_folder.ToListBoxTuple()
( name, path, check_period ) = sort_tuple
pretty_check_period = HydrusData.ConvertTimeDeltaToPrettyString( check_period )
display_tuple = ( name, path, pretty_check_period )
return ( display_tuple, sort_tuple )
def Delete( self ):
with ClientGUIDialogs.DialogYesNo( self, 'Remove all selected?' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._import_folders.RemoveAllSelected()
def Edit( self ):
indices = self._import_folders.GetAllSelected()
for index in indices:
import_folder = self._import_folders.GetObject( index )
original_name = import_folder.GetName()
with DialogManageImportFoldersEdit( self, import_folder ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
import_folder = dlg.GetInfo()
if import_folder.GetName() != original_name:
self._import_folders.SetNonDupeName( import_folder )
( display_tuple, sort_tuple ) = self._ConvertImportFolderToTuples( import_folder )
self._import_folders.UpdateRow( index, display_tuple, sort_tuple, import_folder )
def EventAdd( self, event ):
self._AddImportFolder()
def EventDelete( self, event ):
self.Delete()
def EventEdit( self, event ):
self.Edit()
def EventOK( self, event ):
existing_db_names = set( HG.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FOLDER ) )
good_names = set()
import_folders = self._import_folders.GetObjects()
for import_folder in import_folders:
good_names.add( import_folder.GetName() )
HG.client_controller.Write( 'serialisable', import_folder )
names_to_delete = existing_db_names - good_names
for name in names_to_delete:
HG.client_controller.Write( 'delete_serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FOLDER, name )
HG.client_controller.pub( 'notify_new_import_folders' )
self.EndModal( wx.ID_OK )
class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
def __init__( self, parent, import_folder ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'edit import folder' )
self._import_folder = import_folder
( name, path, mimes, import_file_options, import_tag_options, txt_parse_tag_service_keys, actions, action_locations, period, open_popup, paused ) = self._import_folder.ToTuple()
self._panel = wx.ScrolledWindow( self )
self._folder_box = ClientGUICommon.StaticBox( self._panel, 'folder options' )
self._name = wx.TextCtrl( self._folder_box )
self._path = wx.DirPickerCtrl( self._folder_box, style = wx.DIRP_USE_TEXTCTRL )
self._open_popup = wx.CheckBox( self._folder_box )
self._period = ClientGUICommon.TimeDeltaButton( self._folder_box, min = 3 * 60, days = True, hours = True, minutes = True )
self._paused = wx.CheckBox( self._folder_box )
self._seed_cache_button = ClientGUICommon.BetterBitmapButton( self._folder_box, CC.GlobalBMPs.seed_cache, self.ShowSeedCache )
self._seed_cache_button.SetToolTipString( 'open detailed file import status' )
#
self._file_box = ClientGUICommon.StaticBox( self._panel, 'file options' )
self._mimes = ClientGUIOptionsPanels.OptionsPanelMimes( self._file_box, HC.ALLOWED_MIMES )
def create_choice():
choice = ClientGUICommon.BetterChoice( self._file_box )
for if_id in ( CC.IMPORT_FOLDER_DELETE, CC.IMPORT_FOLDER_IGNORE, CC.IMPORT_FOLDER_MOVE ):
choice.Append( CC.import_folder_string_lookup[ if_id ], if_id )
choice.Bind( wx.EVT_CHOICE, self.EventCheckLocations )
return choice
self._action_successful = create_choice()
self._location_successful = wx.DirPickerCtrl( self._file_box, style = wx.DIRP_USE_TEXTCTRL )
self._action_redundant = create_choice()
self._location_redundant = wx.DirPickerCtrl( self._file_box, style = wx.DIRP_USE_TEXTCTRL )
self._action_deleted = create_choice()
self._location_deleted = wx.DirPickerCtrl( self._file_box, style = wx.DIRP_USE_TEXTCTRL )
self._action_failed = create_choice()
self._location_failed = wx.DirPickerCtrl( self._file_box, style = wx.DIRP_USE_TEXTCTRL )
self._import_file_options = ClientGUIOptionsPanels.OptionsPanelImportFiles( self._file_box )
#
self._tag_box = ClientGUICommon.StaticBox( self._panel, 'tag options' )
self._import_tag_options = ClientGUIOptionsPanels.OptionsPanelTags( self._tag_box )
self._import_tag_options.SetNamespaces( [] )
self._txt_parse_st = wx.StaticText( self._tag_box, label = '' )
services_manager = HG.client_controller.GetServicesManager()
self._txt_parse_tag_service_keys = services_manager.FilterValidServiceKeys( txt_parse_tag_service_keys )
self._RefreshTxtParseText()
self._txt_parse_button = wx.Button( self._tag_box, label = 'edit .txt parsing' )
self._txt_parse_button.Bind( wx.EVT_BUTTON, self.EventEditTxtParsing )
txt_files_help_button = ClientGUICommon.BetterBitmapButton( self._tag_box, CC.GlobalBMPs.help, self._ShowTXTHelp )
txt_files_help_button.SetToolTipString( 'Show help regarding importing tags from .txt files.' )
#
self._ok = wx.Button( self, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
self._name.SetValue( name )
self._path.SetPath( path )
self._open_popup.SetValue( open_popup )
self._period.SetValue( period )
self._paused.SetValue( paused )
self._mimes.SetValue( mimes )
self._action_successful.SelectClientData( actions[ CC.STATUS_SUCCESSFUL ] )
if CC.STATUS_SUCCESSFUL in action_locations:
self._location_successful.SetPath( action_locations[ CC.STATUS_SUCCESSFUL ] )
self._action_redundant.SelectClientData( actions[ CC.STATUS_REDUNDANT ] )
if CC.STATUS_REDUNDANT in action_locations:
self._location_redundant.SetPath( action_locations[ CC.STATUS_REDUNDANT ] )
self._action_deleted.SelectClientData( actions[ CC.STATUS_DELETED ] )
if CC.STATUS_DELETED in action_locations:
self._location_deleted.SetPath( action_locations[ CC.STATUS_DELETED ] )
self._action_failed.SelectClientData( actions[ CC.STATUS_FAILED ] )
if CC.STATUS_FAILED in action_locations:
self._location_failed.SetPath( action_locations[ CC.STATUS_FAILED ] )
self._import_file_options.SetOptions( import_file_options )
self._import_tag_options.SetOptions( import_tag_options )
#
rows = []
rows.append( ( 'name: ', self._name ) )
rows.append( ( 'folder path: ', self._path ) )
rows.append( ( 'check period: ', self._period ) )
rows.append( ( 'currently paused: ', self._paused ) )
rows.append( ( 'open a popup if new files imported: ', self._open_popup ) )
rows.append( ( 'review currently cached import paths: ', self._seed_cache_button ) )
gridbox = ClientGUICommon.WrapInGrid( self._folder_box, rows )
self._folder_box.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
rows = []
rows.append( ( 'mimes to import: ', self._mimes ) )
mimes_gridbox = ClientGUICommon.WrapInGrid( self._file_box, rows, expand_text = True )
gridbox = wx.FlexGridSizer( 0, 3 )
gridbox.AddGrowableCol( 1, 1 )
gridbox.AddF( wx.StaticText( self._file_box, label = 'when a file imports successfully: '), CC.FLAGS_VCENTER )
gridbox.AddF( self._action_successful, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( self._location_successful, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._file_box, label = 'when a file is already in the db: '), CC.FLAGS_VCENTER )
gridbox.AddF( self._action_redundant, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( self._location_redundant, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._file_box, label = 'when a file has previously been deleted from the db: '), CC.FLAGS_VCENTER )
gridbox.AddF( self._action_deleted, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( self._location_deleted, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( wx.StaticText( self._file_box, label = 'when a file fails to import: '), CC.FLAGS_VCENTER )
gridbox.AddF( self._action_failed, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.AddF( self._location_failed, CC.FLAGS_EXPAND_BOTH_WAYS )
self._file_box.AddF( mimes_gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._file_box.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._file_box.AddF( self._import_file_options, CC.FLAGS_EXPAND_PERPENDICULAR )
#
txt_hbox = wx.BoxSizer( wx.HORIZONTAL )
txt_hbox.AddF( self._txt_parse_button, CC.FLAGS_EXPAND_BOTH_WAYS )
txt_hbox.AddF( txt_files_help_button, CC.FLAGS_VCENTER )
self._tag_box.AddF( self._import_tag_options, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._tag_box.AddF( self._txt_parse_st, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._tag_box.AddF( txt_hbox, CC.FLAGS_SIZER_VCENTER )
#
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, CC.FLAGS_VCENTER )
buttons.AddF( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._folder_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._file_box, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
self._panel.SetSizer( vbox )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._panel, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
( max_x, max_y ) = ClientGUITopLevelWindows.GetDisplaySize( self )
x = min( x + 25, max_x )
y = min( y + 25, max_y )
self._panel.SetScrollRate( 20, 20 )
self.SetInitialSize( ( x, y ) )
self._CheckLocations()
wx.CallAfter( self._ok.SetFocus )
def _CheckLocations( self ):
if self._action_successful.GetChoice() == CC.IMPORT_FOLDER_MOVE:
self._location_successful.Enable()
else:
self._location_successful.Disable()
if self._action_redundant.GetChoice() == CC.IMPORT_FOLDER_MOVE:
self._location_redundant.Enable()
else:
self._location_redundant.Disable()
if self._action_deleted.GetChoice() == CC.IMPORT_FOLDER_MOVE:
self._location_deleted.Enable()
else:
self._location_deleted.Disable()
if self._action_failed.GetChoice() == CC.IMPORT_FOLDER_MOVE:
self._location_failed.Enable()
else:
self._location_failed.Disable()
def _RefreshTxtParseText( self ):
services_manager = HG.client_controller.GetServicesManager()
services = [ services_manager.GetService( service_key ) for service_key in self._txt_parse_tag_service_keys ]
service_names = [ service.GetName() for service in services ]
if len( service_names ) > 0:
service_names.sort()
text = 'Loading tags from neighbouring .txt files for ' + ', '.join( service_names ) + '.'
else:
text = 'Not loading tags from neighbouring .txt files for any tag services.'
self._txt_parse_st.SetLabelText( text )
def _ShowTXTHelp( self ):
message = 'If you would like to add custom tags with your files, add a .txt file beside the file like so:'
message += os.linesep * 2
message += 'my_file.jpg'
message += os.linesep
message += 'my_file.jpg.txt'
message += os.linesep * 2
message += 'And include your tags inside the .txt file in a newline-separated list (if you know how to script, generating these files automatically from another source of tags can save a lot of time!).'
message += os.linesep * 2
message += 'If you are not absolutely comfortable with this, practise it through the manual import process.'
wx.MessageBox( message )
def EventCheckLocations( self, event ):
self._CheckLocations()
def EventEditTxtParsing( self, event ):
services_manager = HG.client_controller.GetServicesManager()
tag_services = services_manager.GetServices( HC.TAG_SERVICES )
names_to_service_keys = { service.GetName() : service.GetServiceKey() for service in tag_services }
service_keys_to_names = { service_key : name for ( name, service_key ) in names_to_service_keys.items() }
tag_service_names = names_to_service_keys.keys()
tag_service_names.sort()
selected_names = [ service_keys_to_names[ service_key ] for service_key in self._txt_parse_tag_service_keys ]
with ClientGUIDialogs.DialogCheckFromListOfStrings( self, 'select tag services', tag_service_names, selected_names ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
selected_names = dlg.GetChecked()
self._txt_parse_tag_service_keys = [ names_to_service_keys[ name ] for name in selected_names ]
self._RefreshTxtParseText()
def EventOK( self, event ):
path = self._path.GetPath()
if path in ( '', None ):
wx.MessageBox( 'You must enter a path to import from!' )
return
if HC.BASE_DIR.startswith( path ) or HG.client_controller.GetDBDir().startswith( path ):
wx.MessageBox( 'You cannot set an import path that includes your install or database directory!' )
return
if self._action_successful.GetChoice() == CC.IMPORT_FOLDER_MOVE and self._location_successful.GetPath() in ( '', None ):
wx.MessageBox( 'You must enter a path for your successful file move location!' )
return
if self._action_redundant.GetChoice() == CC.IMPORT_FOLDER_MOVE and self._location_redundant.GetPath() in ( '', None ):
wx.MessageBox( 'You must enter a path for your redundant file move location!' )
return
if self._action_deleted.GetChoice() == CC.IMPORT_FOLDER_MOVE and self._location_deleted.GetPath() in ( '', None ):
wx.MessageBox( 'You must enter a path for your deleted file move location!' )
return
if self._action_failed.GetChoice() == CC.IMPORT_FOLDER_MOVE and self._location_failed.GetPath() in ( '', None ):
wx.MessageBox( 'You must enter a path for your failed file move location!' )
return
self.EndModal( wx.ID_OK )
def GetInfo( self ):
name = self._name.GetValue()
path = HydrusData.ToUnicode( self._path.GetPath() )
mimes = self._mimes.GetValue()
import_file_options = self._import_file_options.GetOptions()
import_tag_options = self._import_tag_options.GetOptions()
actions = {}
action_locations = {}
actions[ CC.STATUS_SUCCESSFUL ] = self._action_successful.GetChoice()
if actions[ CC.STATUS_SUCCESSFUL ] == CC.IMPORT_FOLDER_MOVE:
action_locations[ CC.STATUS_SUCCESSFUL ] = HydrusData.ToUnicode( self._location_successful.GetPath() )
actions[ CC.STATUS_REDUNDANT ] = self._action_redundant.GetChoice()
if actions[ CC.STATUS_REDUNDANT ] == CC.IMPORT_FOLDER_MOVE:
action_locations[ CC.STATUS_REDUNDANT ] = HydrusData.ToUnicode( self._location_redundant.GetPath() )
actions[ CC.STATUS_DELETED ] = self._action_deleted.GetChoice()
if actions[ CC.STATUS_DELETED] == CC.IMPORT_FOLDER_MOVE:
action_locations[ CC.STATUS_DELETED ] = HydrusData.ToUnicode( self._location_deleted.GetPath() )
actions[ CC.STATUS_FAILED ] = self._action_failed.GetChoice()
if actions[ CC.STATUS_FAILED ] == CC.IMPORT_FOLDER_MOVE:
action_locations[ CC.STATUS_FAILED ] = HydrusData.ToUnicode( self._location_failed.GetPath() )
period = self._period.GetValue()
open_popup = self._open_popup.GetValue()
paused = self._paused.GetValue()
self._import_folder.SetTuple( name, path, mimes, import_file_options, import_tag_options, self._txt_parse_tag_service_keys, actions, action_locations, period, open_popup, paused )
return self._import_folder
def ShowSeedCache( self ):
seed_cache = self._import_folder.GetSeedCache()
dupe_seed_cache = seed_cache.Duplicate()
with ClientGUITopLevelWindows.DialogEdit( self, 'file import status' ) as dlg:
panel = ClientGUIScrolledPanelsEdit.EditSeedCachePanel( dlg, HG.client_controller, dupe_seed_cache )
dlg.SetPanel( panel )
if dlg.ShowModal() == wx.ID_OK:
self._import_folder.SetSeedCache( dupe_seed_cache )
class DialogManagePixivAccount( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage pixiv account' )
self._id = wx.TextCtrl( self )
self._password = wx.TextCtrl( self )
self._status = ClientGUICommon.BetterStaticText( self )
self._test = wx.Button( self, label = 'test' )
self._test.Bind( wx.EVT_BUTTON, self.EventTest )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'Ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'Cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
result = HG.client_controller.Read( 'serialisable_simple', 'pixiv_account' )
if result is None:
result = ( '', '' )
( id, password ) = result
self._id.SetValue( id )
self._password.SetValue( password )
#
rows = []
rows.append( ( 'id/email: ', self._id ) )
rows.append( ( 'password: ', self._password ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
b_box = wx.BoxSizer( wx.HORIZONTAL )
b_box.AddF( self._ok, CC.FLAGS_VCENTER )
b_box.AddF( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
text = 'In order to search and download from Pixiv, the client needs to log in to it.'
text += os.linesep
text += 'Until you put something in here, you will not see the option to download from Pixiv.'
text += os.linesep
text += 'You can use a throwaway account if you want--this only needs to log in.'
vbox.AddF( ClientGUICommon.BetterStaticText( self, text ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( self._status, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._test, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( b_box, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
x = max( x, 240 )
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def EventOK( self, event ):
pixiv_id = self._id.GetValue()
password = self._password.GetValue()
if pixiv_id == '' and password == '':
HG.client_controller.Write( 'serialisable_simple', 'pixiv_account', None )
else:
HG.client_controller.Write( 'serialisable_simple', 'pixiv_account', ( pixiv_id, password ) )
self.EndModal( wx.ID_OK )
def EventTest( self, event ):
pixiv_id = self._id.GetValue()
password = self._password.GetValue()
try:
manager = HG.client_controller.GetManager( 'web_sessions' )
cookies = manager.GetPixivCookies( pixiv_id, password )
self._status.SetLabelText( 'OK!' )
wx.CallLater( 5000, self._status.SetLabel, '' )
except HydrusExceptions.ForbiddenException as e:
HydrusData.ShowException( e )
self._status.SetLabelText( 'Did not work! ' + repr( e ) )
class DialogManageRatings( ClientGUIDialogs.Dialog ):
def __init__( self, parent, media ):
self._hashes = set()
for m in media: self._hashes.update( m.GetHashes() )
( remember, position ) = HC.options[ 'rating_dialog_position' ]
if remember and position is not None:
my_position = 'custom'
wx.CallAfter( self.SetPosition, position )
else:
my_position = 'topleft'
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage ratings for ' + HydrusData.ConvertIntToPrettyString( len( self._hashes ) ) + ' files', position = my_position )
#
like_services = HG.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_RATING_LIKE, ), randomised = False )
numerical_services = HG.client_controller.GetServicesManager().GetServices( ( HC.LOCAL_RATING_NUMERICAL, ), randomised = False )
self._panels = []
if len( like_services ) > 0:
self._panels.append( self._LikePanel( self, like_services, media ) )
if len( numerical_services ) > 0:
self._panels.append( self._NumericalPanel( self, numerical_services, media ) )
self._apply = wx.Button( self, id = wx.ID_OK, label = 'apply' )
self._apply.Bind( wx.EVT_BUTTON, self.EventOK )
self._apply.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
buttonbox = wx.BoxSizer( wx.HORIZONTAL )
buttonbox.AddF( self._apply, CC.FLAGS_VCENTER )
buttonbox.AddF( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
for panel in self._panels:
vbox.AddF( panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( buttonbox, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
self.SetInitialSize( ( x, y ) )
#
self.Bind( wx.EVT_CHAR_HOOK, self.EventCharHook )
def _ProcessApplicationCommand( self, command ):
command_processed = True
command_type = command.GetCommandType()
data = command.GetData()
if command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
action = data
if action == 'manage_file_ratings':
self.EventOK( None )
else:
command_processed = False
else:
command_processed = False
return command_processed
def _ProcessShortcut( self, shortcut ):
shortcut_processed = False
command = HG.client_controller.GetCommandFromShortcut( [ 'media' ], shortcut )
if command is not None:
command_processed = self._ProcessApplicationCommand( command )
if command_processed:
shortcut_processed = True
return shortcut_processed
def EventCharHook( self, event ):
shortcut = ClientData.ConvertKeyEventToShortcut( event )
if shortcut is not None:
shortcut_processed = self._ProcessShortcut( shortcut )
if shortcut_processed:
return
event.Skip()
def EventOK( self, event ):
try:
service_keys_to_content_updates = {}
for panel in self._panels:
sub_service_keys_to_content_updates = panel.GetContentUpdates()
service_keys_to_content_updates.update( sub_service_keys_to_content_updates )
if len( service_keys_to_content_updates ) > 0:
HG.client_controller.Write( 'content_updates', service_keys_to_content_updates )
( remember, position ) = HC.options[ 'rating_dialog_position' ]
current_position = self.GetPositionTuple()
if remember and position != current_position:
HC.options[ 'rating_dialog_position' ] = ( remember, current_position )
HG.client_controller.Write( 'save_options', HC.options )
finally:
self.EndModal( wx.ID_OK )
class _LikePanel( wx.Panel ):
def __init__( self, parent, services, media ):
wx.Panel.__init__( self, parent )
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_FRAMEBK ) )
self._services = services
self._media = media
self._service_keys_to_controls = {}
self._service_keys_to_original_ratings_states = {}
rows = []
for service in self._services:
name = service.GetName()
service_key = service.GetServiceKey()
rating_state = ClientRatings.GetLikeStateFromMedia( self._media, service_key )
control = ClientGUICommon.RatingLikeDialog( self, service_key )
control.SetRatingState( rating_state )
self._service_keys_to_controls[ service_key ] = control
self._service_keys_to_original_ratings_states[ service_key ] = rating_state
rows.append( ( name + ': ', control ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows, expand_text = True )
self.SetSizer( gridbox )
def GetContentUpdates( self ):
service_keys_to_content_updates = {}
hashes = { hash for hash in itertools.chain.from_iterable( ( media.GetHashes() for media in self._media ) ) }
for ( service_key, control ) in self._service_keys_to_controls.items():
original_rating_state = self._service_keys_to_original_ratings_states[ service_key ]
rating_state = control.GetRatingState()
if rating_state != original_rating_state:
if rating_state == ClientRatings.LIKE: rating = 1
elif rating_state == ClientRatings.DISLIKE: rating = 0
else: rating = None
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, hashes ) )
service_keys_to_content_updates[ service_key ] = ( content_update, )
return service_keys_to_content_updates
class _NumericalPanel( wx.Panel ):
def __init__( self, parent, services, media ):
wx.Panel.__init__( self, parent )
self._services = services
self._media = media
self._service_keys_to_controls = {}
self._service_keys_to_original_ratings_states = {}
rows = []
for service in self._services:
name = service.GetName()
service_key = service.GetServiceKey()
( rating_state, rating ) = ClientRatings.GetNumericalStateFromMedia( self._media, service_key )
control = ClientGUICommon.RatingNumericalDialog( self, service_key )
if rating_state != ClientRatings.SET:
control.SetRatingState( rating_state )
else:
control.SetRating( rating )
self._service_keys_to_controls[ service_key ] = control
self._service_keys_to_original_ratings_states[ service_key ] = ( rating_state, rating )
rows.append( ( name + ': ', control ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows, expand_text = True )
self.SetSizer( gridbox )
def GetContentUpdates( self ):
service_keys_to_content_updates = {}
hashes = { hash for hash in itertools.chain.from_iterable( ( media.GetHashes() for media in self._media ) ) }
for ( service_key, control ) in self._service_keys_to_controls.items():
( original_rating_state, original_rating ) = self._service_keys_to_original_ratings_states[ service_key ]
rating_state = control.GetRatingState()
if rating_state == ClientRatings.NULL:
rating = None
else:
rating = control.GetRating()
if rating != original_rating:
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, hashes ) )
service_keys_to_content_updates[ service_key ] = ( content_update, )
return service_keys_to_content_updates
class DialogManageRegexFavourites( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage regex favourites' )
self._regexes = ClientGUICommon.SaneListCtrl( self, 200, [ ( 'regex phrase', 120 ), ( 'description', -1 ) ], delete_key_callback = self.Delete, activation_callback = self.Edit )
self._add_button = wx.Button( self, label = 'add' )
self._add_button.Bind( wx.EVT_BUTTON, self.EventAdd )
self._edit_button = wx.Button( self, label = 'edit' )
self._edit_button.Bind( wx.EVT_BUTTON, self.EventEdit )
self._delete_button = wx.Button( self, label = 'delete' )
self._delete_button.Bind( wx.EVT_BUTTON, self.EventDelete )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
for ( regex_phrase, description ) in HC.options[ 'regex_favourites' ]:
self._regexes.Append( ( regex_phrase, description ), ( regex_phrase, description ) )
#
regex_buttons = wx.BoxSizer( wx.HORIZONTAL )
regex_buttons.AddF( self._add_button, CC.FLAGS_VCENTER )
regex_buttons.AddF( self._edit_button, CC.FLAGS_VCENTER )
regex_buttons.AddF( self._delete_button, CC.FLAGS_VCENTER )
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, CC.FLAGS_VCENTER )
buttons.AddF( self._cancel, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._regexes, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( regex_buttons, CC.FLAGS_BUTTON_SIZER )
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
#
( x, y ) = self.GetEffectiveMinSize()
if x < 360: x = 360
if y < 360: y = 360
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def Delete( self ):
with ClientGUIDialogs.DialogYesNo( self, 'Remove all selected?' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._regexes.RemoveAllSelected()
def Edit( self ):
indices = self._regexes.GetAllSelected()
for index in indices:
( regex_phrase, description ) = self._regexes.GetClientData( index )
with ClientGUIDialogs.DialogTextEntry( self, 'Update regex.', default = regex_phrase ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
regex_phrase = dlg.GetValue()
with ClientGUIDialogs.DialogTextEntry( self, 'Update description.', default = description ) as dlg_2:
if dlg_2.ShowModal() == wx.ID_OK:
description = dlg_2.GetValue()
self._regexes.UpdateRow( index, ( regex_phrase, description ), ( regex_phrase, description ) )
def EventAdd( self, event ):
with ClientGUIDialogs.DialogTextEntry( self, 'Enter regex.' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
regex_phrase = dlg.GetValue()
with ClientGUIDialogs.DialogTextEntry( self, 'Enter description.' ) as dlg_2:
if dlg_2.ShowModal() == wx.ID_OK:
description = dlg_2.GetValue()
self._regexes.Append( ( regex_phrase, description ), ( regex_phrase, description ) )
def EventDelete( self, event ):
self.Delete()
def EventEdit( self, event ):
self.Edit()
def EventOK( self, event ):
try:
HC.options[ 'regex_favourites' ] = self._regexes.GetClientData()
HG.client_controller.Write( 'save_options', HC.options )
finally:
self.EndModal( wx.ID_OK )
class DialogManageTagCensorship( ClientGUIDialogs.Dialog ):
def __init__( self, parent, initial_value = None ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'tag censorship' )
self._tag_services = ClientGUICommon.ListBook( self )
self._tag_services.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
services = HG.client_controller.GetServicesManager().GetServices( ( HC.COMBINED_TAG, HC.TAG_REPOSITORY, HC.LOCAL_TAG ) )
for service in services:
service_key = service.GetServiceKey()
name = service.GetName()
page = self._Panel( self._tag_services, service_key, initial_value )
self._tag_services.AddPage( name, service_key, page )
self._tag_services.Select( 'all known tags' )
#
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, CC.FLAGS_SMALL_INDENT )
buttons.AddF( self._cancel, CC.FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
intro = "Here you can set which tags or classes of tags you do not want to see. Input something like 'series:' to censor an entire namespace, or ':' for all namespaced tags, and the empty string (just hit enter with no text added) for all unnamespaced tags. You will have to refresh your current queries to see any changes."
st = ClientGUICommon.BetterStaticText( self, intro )
st.Wrap( 350 )
vbox.AddF( st, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tag_services, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
self.SetInitialSize( ( -1, 480 ) )
def _SetSearchFocus( self ):
page = self._tag_services.GetCurrentPage()
if page is not None:
page.SetTagBoxFocus()
def EventOK( self, event ):
try:
info = [ page.GetInfo() for page in self._tag_services.GetActivePages() if page.HasInfo() ]
HG.client_controller.Write( 'tag_censorship', info )
finally: self.EndModal( wx.ID_OK )
def EventServiceChanged( self, event ):
page = self._tag_services.GetCurrentPage()
if page is not None:
wx.CallAfter( page.SetTagBoxFocus )
class _Panel( wx.Panel ):
def __init__( self, parent, service_key, initial_value = None ):
wx.Panel.__init__( self, parent )
self._service_key = service_key
choice_pairs = [ ( 'blacklist', True ), ( 'whitelist', False ) ]
self._blacklist = ClientGUICommon.RadioBox( self, 'type', choice_pairs )
self._tags = ClientGUIListBoxes.ListBoxTagsCensorship( self )
self._tag_input = wx.TextCtrl( self, style = wx.TE_PROCESS_ENTER )
self._tag_input.Bind( wx.EVT_KEY_DOWN, self.EventKeyDownTag )
#
( blacklist, tags ) = HG.client_controller.Read( 'tag_censorship', service_key )
if blacklist: self._blacklist.SetSelection( 0 )
else: self._blacklist.SetSelection( 1 )
self._tags.AddTags( tags )
if initial_value is not None:
self._tag_input.SetValue( initial_value )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._blacklist, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tags, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._tag_input, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
def EventKeyDownTag( self, event ):
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
tag = self._tag_input.GetValue()
self._tags.EnterTags( { tag } )
self._tag_input.SetValue( '' )
else: event.Skip()
def GetInfo( self ):
blacklist = self._blacklist.GetSelectedClientData()
tags = self._tags.GetClientData()
return ( self._service_key, blacklist, tags )
def HasInfo( self ):
( service_key, blacklist, tags ) = self.GetInfo()
return len( tags ) > 0
def SetTagBoxFocus( self ):
self._tag_input.SetFocus()
class DialogManageTagParents( ClientGUIDialogs.Dialog ):
def __init__( self, parent, tags = None ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'tag parents' )
self._tag_repositories = ClientGUICommon.ListBook( self )
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
services = HG.client_controller.GetServicesManager().GetServices( ( HC.TAG_REPOSITORY, ) )
for service in services:
if service.HasPermission( HC.CONTENT_TYPE_TAG_PARENTS, HC.PERMISSION_ACTION_PETITION ):
name = service.GetName()
service_key = service.GetServiceKey()
self._tag_repositories.AddPageArgs( name, service_key, self._Panel, ( self._tag_repositories, service_key, tags ), {} )
page = self._Panel( self._tag_repositories, CC.LOCAL_TAG_SERVICE_KEY, tags )
name = CC.LOCAL_TAG_SERVICE_KEY
self._tag_repositories.AddPage( name, name, page )
default_tag_repository_key = HC.options[ 'default_tag_repository' ]
self._tag_repositories.Select( default_tag_repository_key )
#
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, CC.FLAGS_SMALL_INDENT )
buttons.AddF( self._cancel, CC.FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._tag_repositories, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
self.SetInitialSize( ( 550, 780 ) )
def _SetSearchFocus( self ):
page = self._tag_repositories.GetCurrentPage()
if page is not None:
page.SetTagBoxFocus()
def EventMenu( self, event ):
action = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
if action is not None:
( command, data ) = action
if command == 'set_search_focus': self._SetSearchFocus()
else: event.Skip()
def EventOK( self, event ):
service_keys_to_content_updates = {}
try:
for page in self._tag_repositories.GetActivePages():
( service_key, content_updates ) = page.GetContentUpdates()
service_keys_to_content_updates[ service_key ] = content_updates
HG.client_controller.Write( 'content_updates', service_keys_to_content_updates )
finally: self.EndModal( wx.ID_OK )
def EventServiceChanged( self, event ):
page = self._tag_repositories.GetCurrentPage()
if page is not None:
wx.CallAfter( page.SetTagBoxFocus )
class _Panel( wx.Panel ):
def __init__( self, parent, service_key, tags = None ):
wx.Panel.__init__( self, parent )
self._service_key = service_key
if service_key != CC.LOCAL_TAG_SERVICE_KEY:
self._service = HG.client_controller.GetServicesManager().GetService( service_key )
self._original_statuses_to_pairs = HG.client_controller.Read( 'tag_parents', service_key )
self._current_statuses_to_pairs = collections.defaultdict( set )
self._current_statuses_to_pairs.update( { key : set( value ) for ( key, value ) in self._original_statuses_to_pairs.items() } )
self._pairs_to_reasons = {}
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._children = ClientGUIListBoxes.ListBoxTagsStringsAddRemove( self, self._service_key, show_sibling_text = False )
self._parents = ClientGUIListBoxes.ListBoxTagsStringsAddRemove( self, self._service_key, show_sibling_text = False )
expand_parents = True
self._child_input = ClientGUIACDropdown.AutoCompleteDropdownTagsWrite( self, self.EnterChildren, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._parent_input = ClientGUIACDropdown.AutoCompleteDropdownTagsWrite( self, self.EnterParents, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAddButton )
self._add.Disable()
#
petitioned_pairs = set( self._original_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ] )
for ( status, pairs ) in self._original_statuses_to_pairs.items():
if status != HC.CONTENT_STATUS_DELETED:
sign = HydrusData.ConvertStatusToPrefix( status )
for ( child, parent ) in pairs:
if status == HC.CONTENT_STATUS_CURRENT and ( child, parent ) in petitioned_pairs:
continue
self._tag_parents.Append( ( sign, child, parent ), ( status, child, parent ) )
self._tag_parents.SortListItems( 2 )
if tags is not None:
self.EnterChildren( tags )
#
intro = 'Files with a tag on the left will also be given the tag on the right.'
tags_box = wx.BoxSizer( wx.HORIZONTAL )
tags_box.AddF( self._children, CC.FLAGS_EXPAND_BOTH_WAYS )
tags_box.AddF( self._parents, CC.FLAGS_EXPAND_BOTH_WAYS )
input_box = wx.BoxSizer( wx.HORIZONTAL )
input_box.AddF( self._child_input, CC.FLAGS_EXPAND_BOTH_WAYS )
input_box.AddF( self._parent_input, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( ClientGUICommon.BetterStaticText( self, intro ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tag_parents, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._add, CC.FLAGS_LONE_BUTTON )
vbox.AddF( tags_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( input_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.SetSizer( vbox )
def _AddPairs( self, children, parent ):
new_pairs = []
current_pairs = []
petitioned_pairs = []
pending_pairs = []
for child in children:
pair = ( child, parent )
if pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ]:
pending_pairs.append( pair )
elif pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ]:
petitioned_pairs.append( pair )
elif pair in self._original_statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ]:
current_pairs.append( pair )
elif self._CanAdd( pair ):
new_pairs.append( pair )
affected_pairs = []
if len( new_pairs ) > 0:
do_it = True
if self._service_key != CC.LOCAL_TAG_SERVICE_KEY:
if self._service.HasPermission( HC.CONTENT_TYPE_TAG_PARENTS, HC.PERMISSION_ACTION_OVERRULE ):
reason = 'admin'
else:
if len( new_pairs ) > 10:
pair_strings = 'The many pairs you entered.'
else:
pair_strings = os.linesep.join( ( child + '->' + parent for ( child, parent ) in new_pairs ) )
message = 'Enter a reason for:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'To be added. A janitor will review your petition.'
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
reason = dlg.GetValue()
else: do_it = False
if do_it:
for pair in new_pairs: self._pairs_to_reasons[ pair ] = reason
if do_it:
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ].update( new_pairs )
affected_pairs.extend( new_pairs )
else:
if len( current_pairs ) > 0:
do_it = True
if self._service_key != CC.LOCAL_TAG_SERVICE_KEY:
if len( current_pairs ) > 10:
pair_strings = 'The many pairs you entered.'
else:
pair_strings = os.linesep.join( ( child + '->' + parent for ( child, parent ) in current_pairs ) )
if len( current_pairs ) > 1: message = 'The pairs:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'Already exist.'
else: message = 'The pair ' + pair_strings + ' already exists.'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'petition it', no_label = 'do nothing' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
if self._service.HasPermission( HC.CONTENT_TYPE_TAG_PARENTS, HC.PERMISSION_ACTION_OVERRULE ):
reason = 'admin'
else:
message = 'Enter a reason for this pair to be removed. A janitor will review your petition.'
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
reason = dlg.GetValue()
else: do_it = False
if do_it:
for pair in current_pairs: self._pairs_to_reasons[ pair ] = reason
else:
do_it = False
if do_it:
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ].update( current_pairs )
affected_pairs.extend( current_pairs )
if len( pending_pairs ) > 0:
if len( pending_pairs ) > 10:
pair_strings = 'The many pairs you entered.'
else:
pair_strings = os.linesep.join( ( child + '->' + parent for ( child, parent ) in pending_pairs ) )
if len( pending_pairs ) > 1: message = 'The pairs:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'Are pending.'
else: message = 'The pair ' + pair_strings + ' is pending.'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'rescind the pend', no_label = 'do nothing' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ].difference_update( pending_pairs )
affected_pairs.extend( pending_pairs )
if len( petitioned_pairs ) > 0:
if len( petitioned_pairs ) > 10:
pair_strings = 'The many pairs you entered.'
else:
pair_strings = os.linesep.join( ( child + '->' + parent for ( child, parent ) in petitioned_pairs ) )
if len( petitioned_pairs ) > 1: message = 'The pairs:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'Are petitioned.'
else: message = 'The pair ' + pair_strings + ' is petitioned.'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'rescind the petition', no_label = 'do nothing' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ].difference_update( petitioned_pairs )
affected_pairs.extend( petitioned_pairs )
if len( affected_pairs ) > 0:
for pair in affected_pairs:
self._RefreshPair( pair )
self._tag_parents.SortListItems()
def _CanAdd( self, potential_pair ):
( potential_child, potential_parent ) = potential_pair
if potential_child == potential_parent: return False
current_pairs = self._current_statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ].union( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ] ).difference( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ] )
current_children = { child for ( child, parent ) in current_pairs }
# test for loops
if potential_parent in current_children:
simple_children_to_parents = ClientCaches.BuildSimpleChildrenToParents( current_pairs )
if ClientCaches.LoopInSimpleChildrenToParents( simple_children_to_parents, potential_child, potential_parent ):
wx.MessageBox( 'Adding ' + potential_child + '->' + potential_parent + ' would create a loop!' )
return False
return True
def _RefreshPair( self, pair ):
( child, parent ) = pair
for status in ( HC.CONTENT_STATUS_CURRENT, HC.CONTENT_STATUS_DELETED, HC.CONTENT_STATUS_PENDING, HC.CONTENT_STATUS_PETITIONED ):
if self._tag_parents.HasClientData( ( status, child, parent ) ):
index = self._tag_parents.GetIndexFromClientData( ( status, child, parent ) )
self._tag_parents.DeleteItem( index )
break
new_status = None
if pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ]:
new_status = HC.CONTENT_STATUS_PENDING
elif pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ]:
new_status = HC.CONTENT_STATUS_PETITIONED
elif pair in self._original_statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ]:
new_status = HC.CONTENT_STATUS_CURRENT
if new_status is not None:
sign = HydrusData.ConvertStatusToPrefix( new_status )
self._tag_parents.Append( ( sign, child, parent ), ( new_status, child, parent ) )
def _SetButtonStatus( self ):
if len( self._children.GetTags() ) == 0 or len( self._parents.GetTags() ) == 0: self._add.Disable()
else: self._add.Enable()
def EnterChildren( self, tags ):
if len( tags ) > 0:
self._parents.RemoveTags( tags )
self._children.EnterTags( tags )
self._SetButtonStatus()
def EnterParents( self, tags ):
if len( tags ) > 0:
self._children.RemoveTags( tags )
self._parents.EnterTags( tags )
self._SetButtonStatus()
def EventActivated( self, event ):
parents_to_children = collections.defaultdict( set )
all_selected = self._tag_parents.GetAllSelected()
for selection in all_selected:
( status, child, parent ) = self._tag_parents.GetClientData( selection )
parents_to_children[ parent ].add( child )
if len( parents_to_children ) > 0:
for ( parent, children ) in parents_to_children.items():
self._AddPairs( children, parent )
def EventAddButton( self, event ):
children = self._children.GetTags()
parents = self._parents.GetTags()
for parent in parents: self._AddPairs( children, parent )
self._children.SetTags( [] )
self._parents.SetTags( [] )
self._SetButtonStatus()
def EventItemSelected( self, event ):
self._SetButtonStatus()
def GetContentUpdates( self ):
# we make it manually here because of the mass pending tags done (but not undone on a rescind) on a pending pair!
# we don't want to send a pend and then rescind it, cause that will spam a thousand bad tags and not undo it
content_updates = []
if self._service_key == CC.LOCAL_TAG_SERVICE_KEY:
for pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ]: content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_ADD, pair ) )
for pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ]: content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_DELETE, pair ) )
else:
current_pending = self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ]
original_pending = self._original_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ]
current_petitioned = self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ]
original_petitioned = self._original_statuses_to_pairs[ HC.CONTENT_STATUS_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( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_PEND, ( pair, self._pairs_to_reasons[ pair ] ) ) for pair in new_pends ) )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_RESCIND_PEND, pair ) for pair in rescinded_pends ) )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_PETITION, ( pair, self._pairs_to_reasons[ pair ] ) ) for pair in new_petitions ) )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_RESCIND_PETITION, pair ) for pair in rescinded_petitions ) )
return ( self._service_key, content_updates )
def SetTagBoxFocus( self ):
if len( self._children.GetTags() ) == 0: self._child_input.SetFocus()
else: self._parent_input.SetFocus()
class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
def __init__( self, parent, tags = None ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'tag siblings' )
self._tag_repositories = ClientGUICommon.ListBook( self )
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
#
page = self._Panel( self._tag_repositories, CC.LOCAL_TAG_SERVICE_KEY, tags )
name = CC.LOCAL_TAG_SERVICE_KEY
self._tag_repositories.AddPage( name, name, page )
services = HG.client_controller.GetServicesManager().GetServices( ( HC.TAG_REPOSITORY, ) )
for service in services:
if service.HasPermission( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.PERMISSION_ACTION_PETITION ):
name = service.GetName()
service_key = service.GetServiceKey()
self._tag_repositories.AddPageArgs( name, service_key, self._Panel, ( self._tag_repositories, service_key, tags ), {} )
default_tag_repository_key = HC.options[ 'default_tag_repository' ]
self._tag_repositories.Select( default_tag_repository_key )
#
buttons = wx.BoxSizer( wx.HORIZONTAL )
buttons.AddF( self._ok, CC.FLAGS_SMALL_INDENT )
buttons.AddF( self._cancel, CC.FLAGS_SMALL_INDENT )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._tag_repositories, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( buttons, CC.FLAGS_BUTTON_SIZER )
self.SetSizer( vbox )
self.SetInitialSize( ( 550, 780 ) )
def _SetSearchFocus( self ):
page = self._tag_repositories.GetCurrentPage()
if page is not None:
page.SetTagBoxFocus()
def EventMenu( self, event ):
action = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
if action is not None:
( command, data ) = action
if command == 'set_search_focus': self._SetSearchFocus()
else: event.Skip()
def EventOK( self, event ):
service_keys_to_content_updates = {}
try:
for page in self._tag_repositories.GetActivePages():
( service_key, content_updates ) = page.GetContentUpdates()
service_keys_to_content_updates[ service_key ] = content_updates
HG.client_controller.Write( 'content_updates', service_keys_to_content_updates )
finally: self.EndModal( wx.ID_OK )
def EventServiceChanged( self, event ):
page = self._tag_repositories.GetCurrentPage()
if page is not None:
wx.CallAfter( page.SetTagBoxFocus )
class _Panel( wx.Panel ):
def __init__( self, parent, service_key, tags = None ):
wx.Panel.__init__( self, parent )
self._service_key = service_key
if self._service_key != CC.LOCAL_TAG_SERVICE_KEY:
self._service = HG.client_controller.GetServicesManager().GetService( service_key )
self._original_statuses_to_pairs = HG.client_controller.Read( 'tag_siblings', service_key )
self._current_statuses_to_pairs = collections.defaultdict( set )
self._current_statuses_to_pairs.update( { key : set( value ) for ( key, value ) in self._original_statuses_to_pairs.items() } )
self._pairs_to_reasons = {}
self._current_new = None
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_siblings = ClientGUIListBoxes.ListBoxTagsStringsAddRemove( self, self._service_key, show_sibling_text = False )
self._new_sibling = ClientGUICommon.BetterStaticText( self )
expand_parents = False
self._old_input = ClientGUIACDropdown.AutoCompleteDropdownTagsWrite( self, self.EnterOlds, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._new_input = ClientGUIACDropdown.AutoCompleteDropdownTagsWrite( self, self.SetNew, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAddButton )
self._add.Disable()
#
petitioned_pairs = set( self._original_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ] )
for ( status, pairs ) in self._original_statuses_to_pairs.items():
if status != HC.CONTENT_STATUS_DELETED:
sign = HydrusData.ConvertStatusToPrefix( status )
for ( old, new ) in pairs:
if status == HC.CONTENT_STATUS_CURRENT and ( old, new ) in petitioned_pairs:
continue
self._tag_siblings.Append( ( sign, old, new ), ( status, old, new ) )
self._tag_siblings.SortListItems( 2 )
if tags is not None:
self.EnterOlds( tags )
#
intro = 'Tags on the left will be replaced by those on the right.'
new_sibling_box = wx.BoxSizer( wx.VERTICAL )
new_sibling_box.AddF( ( 10, 10 ), CC.FLAGS_EXPAND_BOTH_WAYS )
new_sibling_box.AddF( self._new_sibling, CC.FLAGS_EXPAND_PERPENDICULAR )
new_sibling_box.AddF( ( 10, 10 ), CC.FLAGS_EXPAND_BOTH_WAYS )
text_box = wx.BoxSizer( wx.HORIZONTAL )
text_box.AddF( self._old_siblings, CC.FLAGS_EXPAND_BOTH_WAYS )
text_box.AddF( new_sibling_box, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
input_box = wx.BoxSizer( wx.HORIZONTAL )
input_box.AddF( self._old_input, CC.FLAGS_EXPAND_BOTH_WAYS )
input_box.AddF( self._new_input, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( ClientGUICommon.BetterStaticText( self, intro ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tag_siblings, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._add, CC.FLAGS_LONE_BUTTON )
vbox.AddF( text_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( input_box, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.SetSizer( vbox )
def _AddPairs( self, olds, new ):
new_pairs = []
current_pairs = []
petitioned_pairs = []
pending_pairs = []
for old in olds:
pair = ( old, new )
if pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ]:
pending_pairs.append( pair )
elif pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ]:
petitioned_pairs.append( pair )
elif pair in self._original_statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ]:
current_pairs.append( pair )
elif self._CanAdd( pair ):
new_pairs.append( pair )
affected_pairs = []
if len( new_pairs ) > 0:
do_it = True
if self._service_key != CC.LOCAL_TAG_SERVICE_KEY:
if self._service.HasPermission( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.PERMISSION_ACTION_OVERRULE ):
reason = 'admin'
else:
if len( new_pairs ) > 10:
pair_strings = 'The many pairs you entered.'
else:
pair_strings = os.linesep.join( ( old + '->' + new for ( old, new ) in new_pairs ) )
message = 'Enter a reason for:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'To be added. A janitor will review your petition.'
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
reason = dlg.GetValue()
else: do_it = False
if do_it:
for pair in new_pairs: self._pairs_to_reasons[ pair ] = reason
if do_it:
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ].update( new_pairs )
affected_pairs.extend( new_pairs )
else:
if len( current_pairs ) > 0:
do_it = True
if self._service_key != CC.LOCAL_TAG_SERVICE_KEY:
if len( current_pairs ) > 10:
pair_strings = 'The many pairs you entered.'
else:
pair_strings = os.linesep.join( ( old + '->' + new for ( old, new ) in current_pairs ) )
if len( current_pairs ) > 1: message = 'The pairs:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'Already exist.'
else: message = 'The pair ' + pair_strings + ' already exists.'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'petition it', no_label = 'do nothing' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
if self._service.HasPermission( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.PERMISSION_ACTION_OVERRULE ):
reason = 'admin'
else:
message = 'Enter a reason for this pair to be removed. A janitor will review your petition.'
with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_OK: reason = dlg.GetValue()
else: do_it = False
if do_it:
for pair in current_pairs: self._pairs_to_reasons[ pair ] = reason
else:
do_it = False
if do_it:
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ].update( current_pairs )
affected_pairs.extend( current_pairs )
if len( pending_pairs ) > 0:
if len( pending_pairs ) > 10:
pair_strings = 'The many pairs you entered.'
else:
pair_strings = os.linesep.join( ( old + '->' + new for ( old, new ) in pending_pairs ) )
if len( pending_pairs ) > 1: message = 'The pairs:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'Are pending.'
else: message = 'The pair ' + pair_strings + ' is pending.'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'rescind the pend', no_label = 'do nothing' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ].difference_update( pending_pairs )
affected_pairs.extend( pending_pairs )
if len( petitioned_pairs ) > 0:
if len( petitioned_pairs ) > 10:
pair_strings = 'The many pairs you entered.'
else:
pair_strings = ', '.join( ( old + '->' + new for ( old, new ) in petitioned_pairs ) )
if len( petitioned_pairs ) > 1: message = 'The pairs:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'Are petitioned.'
else: message = 'The pair ' + pair_strings + ' is petitioned.'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'rescind the petition', no_label = 'do nothing' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ].difference_update( petitioned_pairs )
affected_pairs.extend( petitioned_pairs )
if len( affected_pairs ) > 0:
for pair in affected_pairs:
self._RefreshPair( pair )
self._tag_siblings.SortListItems()
def _CanAdd( self, potential_pair ):
( potential_old, potential_new ) = potential_pair
current_pairs = self._current_statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ].union( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ] ).difference( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ] )
current_olds = { old for ( old, new ) in current_pairs }
# test for ambiguity
if potential_old in current_olds:
wx.MessageBox( 'There already is a relationship set for the tag ' + potential_old + '.' )
return False
# test for loops
if potential_new in current_olds:
d = dict( current_pairs )
next_new = potential_new
while next_new in d:
next_new = d[ next_new ]
if next_new == potential_old:
wx.MessageBox( 'Adding ' + potential_old + '->' + potential_new + ' would create a loop!' )
return False
return True
def _RefreshPair( self, pair ):
( old, new ) = pair
for status in ( HC.CONTENT_STATUS_CURRENT, HC.CONTENT_STATUS_DELETED, HC.CONTENT_STATUS_PENDING, HC.CONTENT_STATUS_PETITIONED ):
if self._tag_siblings.HasClientData( ( status, old, new ) ):
index = self._tag_siblings.GetIndexFromClientData( ( status, old, new ) )
self._tag_siblings.DeleteItem( index )
break
new_status = None
if pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ]:
new_status = HC.CONTENT_STATUS_PENDING
elif pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ]:
new_status = HC.CONTENT_STATUS_PETITIONED
elif pair in self._original_statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ]:
new_status = HC.CONTENT_STATUS_CURRENT
if new_status is not None:
sign = HydrusData.ConvertStatusToPrefix( new_status )
self._tag_siblings.Append( ( sign, old, new ), ( new_status, old, new ) )
def _SetButtonStatus( self ):
if self._current_new is None or len( self._old_siblings.GetTags() ) == 0: self._add.Disable()
else: self._add.Enable()
def EnterOlds( self, olds ):
potential_olds = olds
olds = set()
for potential_old in potential_olds:
do_it = True
current_pairs = self._current_statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ].union( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ] ).difference( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ] )
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
while potential_old in current_olds:
olds_to_news = dict( current_pairs )
conflicting_new = olds_to_news[ potential_old ]
message = 'There already is a relationship set for ' + potential_old + '! It goes to ' + conflicting_new + '.'
message += os.linesep * 2
message += 'You cannot have two siblings for the same original term.'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Choose what to do.', yes_label = 'I want to overwrite the existing record', no_label = 'do nothing' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
self._AddPairs( [ potential_old ], conflicting_new )
else:
do_it = False
break
current_pairs = self._current_statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ].union( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ] ).difference( self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ] )
current_olds = { current_old for ( current_old, current_new ) in current_pairs }
if do_it:
olds.add( potential_old )
if self._current_new in olds:
self.SetNew( set() )
self._old_siblings.EnterTags( olds )
self._SetButtonStatus()
def EventActivated( self, event ):
news_to_olds = collections.defaultdict( set )
all_selected = self._tag_siblings.GetAllSelected()
for selection in all_selected:
( status, old, new ) = self._tag_siblings.GetClientData( selection )
news_to_olds[ new ].add( old )
if len( news_to_olds ) > 0:
for ( new, olds ) in news_to_olds.items():
self._AddPairs( olds, new )
def EventAddButton( self, event ):
if self._current_new is not None and len( self._old_siblings.GetTags() ) > 0:
olds = self._old_siblings.GetTags()
self._AddPairs( olds, self._current_new )
self._old_siblings.SetTags( set() )
self.SetNew( set() )
self._SetButtonStatus()
def EventItemSelected( self, event ):
self._SetButtonStatus()
def GetContentUpdates( self ):
# we make it manually here because of the mass pending tags done (but not undone on a rescind) on a pending pair!
# we don't want to send a pend and then rescind it, cause that will spam a thousand bad tags and not undo it
# actually, we don't do this for siblings, but we do for parents, and let's have them be the same
content_updates = []
if self._service_key == CC.LOCAL_TAG_SERVICE_KEY:
for pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ]: content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_ADD, pair ) )
for pair in self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ]: content_updates.append( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_DELETE, pair ) )
else:
current_pending = self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ]
original_pending = self._original_statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ]
current_petitioned = self._current_statuses_to_pairs[ HC.CONTENT_STATUS_PETITIONED ]
original_petitioned = self._original_statuses_to_pairs[ HC.CONTENT_STATUS_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( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_PEND, ( pair, self._pairs_to_reasons[ pair ] ) ) for pair in new_pends ) )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_RESCIND_PEND, pair ) for pair in rescinded_pends ) )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_PETITION, ( pair, self._pairs_to_reasons[ pair ] ) ) for pair in new_petitions ) )
content_updates.extend( ( HydrusData.ContentUpdate( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_RESCIND_PETITION, pair ) for pair in rescinded_petitions ) )
return ( self._service_key, content_updates )
def SetNew( self, new_tags ):
if len( new_tags ) == 0:
self._new_sibling.SetLabelText( '' )
self._current_new = None
else:
new = list( new_tags )[0]
self._old_siblings.RemoveTags( { new } )
self._new_sibling.SetLabelText( new )
self._current_new = new
self._SetButtonStatus()
def SetTagBoxFocus( self ):
if len( self._old_siblings.GetTags() ) == 0: self._old_input.SetFocus()
else: self._new_input.SetFocus()
class DialogManageUPnP( ClientGUIDialogs.Dialog ):
def __init__( self, parent ):
title = 'manage local upnp'
ClientGUIDialogs.Dialog.__init__( self, parent, title )
self._hidden_cancel = wx.Button( self, id = wx.ID_CANCEL, size = ( 0, 0 ) )
self._mappings_list_ctrl = ClientGUICommon.SaneListCtrl( self, 480, [ ( 'description', -1 ), ( 'internal ip', 100 ), ( 'internal port', 80 ), ( 'external ip', 100 ), ( 'external port', 80 ), ( 'protocol', 80 ), ( 'lease', 80 ) ], delete_key_callback = self.RemoveMappings, activation_callback = self.EditMappings )
self._add_custom = wx.Button( self, label = 'add custom mapping' )
self._add_custom.Bind( wx.EVT_BUTTON, self.EventAddCustomMapping )
self._edit = wx.Button( self, label = 'edit mapping' )
self._edit.Bind( wx.EVT_BUTTON, self.EventEditMapping )
self._remove = wx.Button( self, label = 'remove mapping' )
self._remove.Bind( wx.EVT_BUTTON, self.EventRemoveMapping )
self._ok = wx.Button( self, id = wx.ID_OK, label = 'ok' )
self._ok.Bind( wx.EVT_BUTTON, self.EventOK )
self._ok.SetForegroundColour( ( 0, 128, 0 ) )
#
self._RefreshMappings()
#
edit_buttons = wx.BoxSizer( wx.HORIZONTAL )
edit_buttons.AddF( self._add_custom, CC.FLAGS_VCENTER )
edit_buttons.AddF( self._edit, CC.FLAGS_VCENTER )
edit_buttons.AddF( self._remove, CC.FLAGS_VCENTER )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._mappings_list_ctrl, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( edit_buttons, CC.FLAGS_BUTTON_SIZER )
vbox.AddF( self._ok, CC.FLAGS_LONE_BUTTON )
self.SetSizer( vbox )
( x, y ) = self.GetEffectiveMinSize()
x = max( x, 760 )
self.SetInitialSize( ( x, y ) )
wx.CallAfter( self._ok.SetFocus )
def _RefreshMappings( self ):
self._mappings_list_ctrl.DeleteAllItems()
self._mappings = HydrusNATPunch.GetUPnPMappings()
for mapping in self._mappings: self._mappings_list_ctrl.Append( mapping, mapping )
self._mappings_list_ctrl.SortListItems( 1 )
def EditMappings( self ):
do_refresh = False
for index in self._mappings_list_ctrl.GetAllSelected():
( description, internal_ip, internal_port, external_ip, external_port, protocol, duration ) = self._mappings_list_ctrl.GetClientData( index )
with ClientGUIDialogs.DialogInputUPnPMapping( self, external_port, protocol, internal_port, description, duration ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( external_port, protocol, internal_port, description, duration ) = dlg.GetInfo()
HydrusNATPunch.RemoveUPnPMapping( external_port, protocol )
internal_client = HydrusNATPunch.GetLocalIP()
HydrusNATPunch.AddUPnPMapping( internal_client, internal_port, external_port, protocol, description, duration = duration )
do_refresh = True
if do_refresh: self._RefreshMappings()
def EventAddCustomMapping( self, event ):
do_refresh = False
external_port = HC.DEFAULT_SERVICE_PORT
protocol = 'TCP'
internal_port = HC.DEFAULT_SERVICE_PORT
description = 'hydrus service'
duration = 0
with ClientGUIDialogs.DialogInputUPnPMapping( self, external_port, protocol, internal_port, description, duration ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( external_port, protocol, internal_port, description, duration ) = dlg.GetInfo()
for ( existing_description, existing_internal_ip, existing_internal_port, existing_external_ip, existing_external_port, existing_protocol, existing_lease ) in self._mappings:
if external_port == existing_external_port and protocol == existing_protocol:
wx.MessageBox( 'That external port already exists!' )
return
internal_client = HydrusNATPunch.GetLocalIP()
HydrusNATPunch.AddUPnPMapping( internal_client, internal_port, external_port, protocol, description, duration = duration )
do_refresh = True
if do_refresh: self._RefreshMappings()
def EventEditMapping( self, event ):
self.EditMappings()
def EventOK( self, event ):
self.EndModal( wx.ID_OK )
def EventRemoveMapping( self, event ):
self.RemoveMappings()
def RemoveMappings( self ):
do_refresh = False
for index in self._mappings_list_ctrl.GetAllSelected():
( description, internal_ip, internal_port, external_ip, external_port, protocol, duration ) = self._mappings_list_ctrl.GetClientData( index )
HydrusNATPunch.RemoveUPnPMapping( external_port, protocol )
do_refresh = True
if do_refresh: self._RefreshMappings()