code:
import os, wx

from lib.GUI.Common import BetterBitmapButton, BetterButton, BetterHyperLink, \
	BetterStaticText, BufferedWindowIcon, ClientToScreen, MenuBitmapButton, \
	RatingLikeCanvas, RatingNumericalCanvas, SetBitmapButtonBitmap, \
	TLPHasFocus
from lib.GUI.DragDrop import DoFileExportDragDrop
from lib.GUI.ListBoxes import ListBoxTagsSelectionHoverFrame
from lib.GUI.Menus import AppendMenu, AppendMenuItem, AppendMenuCheckItem
from lib.GUI.ScrolledPanels.Edit import EditDuplicateActionOptionsPanel, \
	EditNoneableIntegerPanel
from lib.GUI.ScrolledPanels.Management import ManageShortcutsPanel
from lib.GUI.TopLevelWindows import DialogEdit, DialogManage

from lib.Hydrus.Data import Call, ShowText, TimeHasPassed
from lib.Hydrus.Data import ApplicationCommand, ConvertZoomToPercentage
from lib.Hydrus.Media import GetDuplicateComparisonStatements
from lib.Hydrus.Serialisable import HG.SerialisableType["SHORTCUTS"]

### CONSTANTS ###

import lib.Hydrus.Globals as HG
import lib.Hydrus.Constants as HC
import lib.Hydrus.Constants.Local as CC

### FUNCTIONS ###

class FullscreenHoverFrame( wx.Frame ):
	def __init__( self, parent, my_canvas, canvas_key ):
		if HC.PLATFORM_WINDOWS:
			border_style = wx.BORDER_RAISED

		else:
			border_style = wx.BORDER_SIMPLE

		wx.Frame.__init__( self, parent, style = wx.FRAME_TOOL_WINDOW | wx.FRAME_NO_TASKBAR | wx.FRAME_FLOAT_ON_PARENT | border_style )

		self._my_canvas = my_canvas
		self._canvas_key = canvas_key
		self._current_media = None

		self._last_ideal_position = None

		self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_FRAMEBK ) )
		self.SetCursor( wx.Cursor( wx.CURSOR_ARROW ) )

		self._hide_until = None

		HG.client_controller.sub( self, 'SetDisplayMedia', 'canvas_new_display_media' )

		HG.client_controller.gui.RegisterUIUpdateWindow( self )

	def _GetIdealSizeAndPosition( self ):
		raise NotImplementedError()

	def _SizeAndPosition( self ):
		if self.GetParent().IsShown():
			( should_resize, my_ideal_size, my_ideal_position ) = self._GetIdealSizeAndPosition()

			if should_resize:
				self.Fit()

				self.SetSize( my_ideal_size )

			self.SetPosition( my_ideal_position )

	def SetDisplayMedia( self, canvas_key, media ):
		if canvas_key == self._canvas_key:
			self._current_media = media

	def TIMERUIUpdate( self ):
		new_options = HG.client_controller.new_options

		if new_options.GetBoolean( 'always_show_hover_windows' ):
			self._SizeAndPosition()

			self.Show()

			return

		if self._hide_until is not None:
			if TimeHasPassed( self._hide_until ):
				self._hide_until = None

			else:
				return

		if self._current_media is None or not self.GetParent().IsShown():
			if self.IsShown():
				if HG.hover_window_report_mode:
					ShowText( repr( self ) + ' - hiding because nothing to show or parent hidden.' )

				self.Hide()

		else:
			( mouse_x, mouse_y ) = wx.GetMousePosition()

			( my_width, my_height ) = self.GetSize()

			( should_resize, ( my_ideal_width, my_ideal_height ), ( my_ideal_x, my_ideal_y ) ) = self._GetIdealSizeAndPosition()

			if my_ideal_width == -1:
				my_ideal_width = max( my_width, 50 )

			if my_ideal_height == -1:
				my_ideal_height = max( my_height, 50 )

			( my_x, my_y ) = self.GetPosition()

			in_ideal_x = my_ideal_x <= mouse_x and mouse_x <= my_ideal_x + my_ideal_width
			in_ideal_y = my_ideal_y <= mouse_y and mouse_y <= my_ideal_y + my_ideal_height

			in_actual_x = my_x <= mouse_x and mouse_x <= my_x + my_width
			in_actual_y = my_y <= mouse_y and mouse_y <= my_y + my_height

			# we test both ideal and actual here because setposition is not always honoured by the OS
			# for instance, in Linux on a fullscreen view, the top taskbar is hidden, but when hover window is shown, it takes focus and causes taskbar to reappear
			# the reappearance shuffles the screen coordinates down a bit so the hover sits +20px y despite wanting to be lined up with the underlying fullscreen viewer
			# wew lad

			in_position = ( in_ideal_x or in_actual_x ) and ( in_ideal_y or in_actual_y )

			menu_open = HG.client_controller.MenuIsOpen()

			dialog_open = False

			tlps = wx.GetTopLevelWindows()

			for tlp in tlps:
				if isinstance( tlp, wx.Dialog ):
					dialog_open = True

			mime = self._current_media.GetMime()

			mouse_is_over_interactable_media = mime == HC.APPLICATION_FLASH and self.GetParent().MouseIsOverMedia()

			mouse_is_near_animation_bar = self.GetParent().MouseIsNearAnimationBar()

			mouse_is_over_something_important = mouse_is_over_interactable_media or mouse_is_near_animation_bar

			focus_is_good = TLPHasFocus( self ) or TLPHasFocus( self.GetParent() )

			ready_to_show = in_position and not mouse_is_over_something_important and focus_is_good and not dialog_open and not menu_open
			ready_to_hide = not menu_open and ( not in_position or dialog_open or not focus_is_good )

			def get_logic_report_string():
				tuples = []

				tuples.append( ( 'in position: ', in_position ) )
				tuples.append( ( 'menu open: ', menu_open ) )
				tuples.append( ( 'dialog open: ', dialog_open ) )
				tuples.append( ( 'mouse over interactable media: ', mouse_is_over_interactable_media ) )
				tuples.append( ( 'mouse near animation bar: ', mouse_is_near_animation_bar ) )
				tuples.append( ( 'focus is good: ', focus_is_good ) )

				message = os.linesep * 2 + os.linesep.join( ( a + str( b ) for ( a, b ) in tuples ) ) + os.linesep

				return message

			if ready_to_show:
				self._SizeAndPosition()

				if not self.IsShown():
					if HG.hover_window_report_mode:
						ShowText( repr( self ) + ' - showing.' + get_logic_report_string() )

					self.Show()

			elif ready_to_hide:
				if self.IsShown():
					if HG.hover_window_report_mode:
						ShowText( repr( self ) + ' - hiding.' + get_logic_report_string() )

					self.Hide()

class FullscreenHoverFrameTop( FullscreenHoverFrame ):
	def __init__( self, parent, my_canvas, canvas_key ):
		FullscreenHoverFrame.__init__( self, parent, my_canvas, canvas_key )

		self._current_zoom = 1.0
		self._current_index_string = ''

		self._top_hbox = wx.BoxSizer( wx.HORIZONTAL )
		self._title_text = BetterStaticText( self, 'title' )
		self._info_text = BetterStaticText( self, 'info' )
		self._additional_info_text = BetterStaticText( self, '', style = wx.ALIGN_CENTER )
		self._button_hbox = wx.BoxSizer( wx.HORIZONTAL )

		self._PopulateLeftButtons()
		self._top_hbox.Add( ( 20, 20 ), CC.FLAGS_EXPAND_BOTH_WAYS )
		self._PopulateCenterButtons()
		self._top_hbox.Add( ( 20, 20 ), CC.FLAGS_EXPAND_BOTH_WAYS )
		self._PopulateRightButtons()

		vbox = wx.BoxSizer( wx.VERTICAL )

		vbox.Add( self._top_hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( self._title_text, CC.FLAGS_CENTER )
		vbox.Add( self._info_text, CC.FLAGS_CENTER )
		vbox.Add( self._additional_info_text, CC.FLAGS_CENTER )
		vbox.Add( self._button_hbox, CC.FLAGS_CENTER )

		self.SetSizer( vbox )

		HG.client_controller.sub( self, 'ProcessContentUpdates', 'content_updates_gui' )
		HG.client_controller.sub( self, 'SetCurrentZoom', 'canvas_new_zoom' )
		HG.client_controller.sub( self, 'SetIndexString', 'canvas_new_index_string' )

		self.Bind( wx.EVT_MOUSEWHEEL, self.EventMouseWheel )

	def _Archive( self ):
		if self._current_media.HasInbox():
			command = ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'archive_file' )

		else:
			command = ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'inbox_file' )

		HG.client_controller.pub( 'canvas_application_command', command, self._canvas_key )

	def _GetIdealSizeAndPosition( self ):
		parent = self.GetParent()

		( parent_width, parent_height ) = parent.GetClientSize()

		( my_width, my_height ) = self.GetSize()

		my_ideal_width = int( parent_width * 0.6 )

		should_resize = my_ideal_width != my_width

		ideal_size = ( my_ideal_width, -1 )
		ideal_position = ClientToScreen( parent, ( int( parent_width * 0.2 ), 0 ) )

		return ( should_resize, ideal_size, ideal_position )

	def _ManageShortcuts( self ):
		with DialogManage( self, 'manage shortcuts' ) as dlg:
			panel = ManageShortcutsPanel( dlg )

			dlg.SetPanel( panel )

			dlg.ShowModal()

	def _PopulateCenterButtons( self ):
		self._archive_button = BetterBitmapButton( self, CC.GlobalBMPs.archive, self._Archive )

		self._trash_button = BetterBitmapButton( self, CC.GlobalBMPs.delete, HG.client_controller.pub, 'canvas_delete', self._canvas_key )
		self._trash_button.SetToolTip( 'send to trash' )

		self._delete_button = BetterBitmapButton( self, CC.GlobalBMPs.trash_delete, HG.client_controller.pub, 'canvas_delete', self._canvas_key )
		self._delete_button.SetToolTip( 'delete completely' )

		self._undelete_button = BetterBitmapButton( self, CC.GlobalBMPs.undelete, HG.client_controller.pub, 'canvas_undelete', self._canvas_key )
		self._undelete_button.SetToolTip( 'undelete' )

		self._top_hbox.Add( self._archive_button, CC.FLAGS_VCENTER )
		self._top_hbox.Add( self._trash_button, CC.FLAGS_VCENTER )
		self._top_hbox.Add( self._delete_button, CC.FLAGS_VCENTER )
		self._top_hbox.Add( self._undelete_button, CC.FLAGS_VCENTER )

	def _PopulateLeftButtons( self ):
		self._index_text = BetterStaticText( self, 'index' )

		self._top_hbox.Add( self._index_text, CC.FLAGS_VCENTER )

	def _PopulateRightButtons( self ):
		self._zoom_text = BetterStaticText( self, 'zoom' )

		zoom_in = BetterBitmapButton( self, CC.GlobalBMPs.zoom_in, HG.client_controller.pub, 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'zoom_in' ), self._canvas_key )
		zoom_in.SetToolTip( 'zoom in' )

		zoom_out = BetterBitmapButton( self, CC.GlobalBMPs.zoom_out, HG.client_controller.pub, 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'zoom_out' ), self._canvas_key )
		zoom_out.SetToolTip( 'zoom out' )

		zoom_switch = BetterBitmapButton( self, CC.GlobalBMPs.zoom_switch, HG.client_controller.pub, 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'switch_between_100_percent_and_canvas_zoom' ), self._canvas_key )
		zoom_switch.SetToolTip( 'zoom switch' )

		shortcuts = BetterBitmapButton( self, CC.GlobalBMPs.keyboard, self._ShowShortcutMenu )
		shortcuts.SetToolTip( 'shortcuts' )

		fullscreen_switch = BetterBitmapButton( self, CC.GlobalBMPs.fullscreen_switch, HG.client_controller.pub, 'canvas_fullscreen_switch', self._canvas_key )
		fullscreen_switch.SetToolTip( 'fullscreen switch' )

		open_externally = BetterBitmapButton( self, CC.GlobalBMPs.open_externally, HG.client_controller.pub, 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'open_file_in_external_program' ), self._canvas_key )
		open_externally.SetToolTip( 'open externally' )

		drag_button = wx.BitmapButton( self, bitmap = CC.GlobalBMPs.drag )
		drag_button.SetToolTip( 'drag from here to export file' )
		drag_button.Bind( wx.EVT_LEFT_DOWN, self.EventDragButton )

		close = BetterButton( self, 'X', HG.client_controller.pub, 'canvas_close', self._canvas_key )
		close.SetToolTip( 'close' )

		self._top_hbox.Add( self._zoom_text, CC.FLAGS_VCENTER )
		self._top_hbox.Add( zoom_in, CC.FLAGS_VCENTER )
		self._top_hbox.Add( zoom_out, CC.FLAGS_VCENTER )
		self._top_hbox.Add( zoom_switch, CC.FLAGS_VCENTER )
		self._top_hbox.Add( shortcuts, CC.FLAGS_VCENTER )
		self._top_hbox.Add( fullscreen_switch, CC.FLAGS_VCENTER )
		self._top_hbox.Add( open_externally, CC.FLAGS_VCENTER )
		self._top_hbox.Add( drag_button, CC.FLAGS_VCENTER )
		self._top_hbox.Add( close, CC.FLAGS_VCENTER )

	def _ResetArchiveButton( self ):
		if self._current_media.HasInbox():
			SetBitmapButtonBitmap( self._archive_button, CC.GlobalBMPs.archive )
			self._archive_button.SetToolTip( 'archive' )

		else:
			SetBitmapButtonBitmap( self._archive_button, CC.GlobalBMPs.to_inbox )
			self._archive_button.SetToolTip( 'return to inbox' )

	def _ResetButtons( self ):
		if self._current_media is not None:
			self._ResetArchiveButton()

			current_locations = self._current_media.GetLocationsManager().GetCurrent()

			if CC.LOCAL_FILE_SERVICE_KEY in current_locations:
				self._trash_button.Show()
				self._delete_button.Hide()
				self._undelete_button.Hide()

			elif CC.TRASH_SERVICE_KEY in current_locations:
				self._trash_button.Hide()
				self._delete_button.Show()
				self._undelete_button.Show()

			self.Fit()

			self._SizeAndPosition()

	def _ResetText( self ):
		if self._current_media is None:
			self._title_text.Hide()
			self._info_text.Hide()

		else:
			label = self._current_media.GetTitleString()

			if len( label ) > 0:
				self._title_text.SetLabelText( label )

				self._title_text.Show()

			else:
				self._title_text.Hide()

			lines = self._current_media.GetPrettyInfoLines()

			label = ' | '.join( lines )

			self._info_text.SetLabelText( label )

			self._info_text.Show()

			if self._additional_info_text.GetLabelText() == '':
				self._additional_info_text.Hide()

			else:
				self._additional_info_text.Show()

	def _FlipActiveDefaultCustomShortcut( self, name ):
		new_options = HG.client_controller.new_options

		default_media_viewer_custom_shortcuts = list( new_options.GetStringList( 'default_media_viewer_custom_shortcuts' ) )

		if name in default_media_viewer_custom_shortcuts:
			default_media_viewer_custom_shortcuts.remove( name )

		else:
			default_media_viewer_custom_shortcuts.append( name )

			default_media_viewer_custom_shortcuts.sort()

		new_options.SetStringList( 'default_media_viewer_custom_shortcuts', default_media_viewer_custom_shortcuts )

	def _ShowShortcutMenu( self ):
		all_shortcut_names = HG.client_controller.Read( 'serialisable_names', HG.SerialisableType["SHORTCUTS"] )

		custom_shortcuts_names = [ name for name in all_shortcut_names if name not in CC.SHORTCUTS_RESERVED_NAMES ]

		menu = wx.Menu()

		AppendMenuItem( self, menu, 'edit shortcuts', 'edit your sets of shortcuts, and change what shortcuts are currently active on this media viewer', self._ManageShortcuts )

		if len( custom_shortcuts_names ) > 0:
			my_canvas_active_custom_shortcuts = self._my_canvas.GetActiveCustomShortcutNames()
			default_media_viewer_custom_shortcuts = HG.client_controller.new_options.GetStringList( 'default_media_viewer_custom_shortcuts' )

			current_menu = wx.Menu()

			for name in custom_shortcuts_names:
				AppendMenuCheckItem( self, current_menu, name, 'turn this shortcut set on/off', name in my_canvas_active_custom_shortcuts, self._my_canvas.FlipActiveCustomShortcutName, name )

			AppendMenu( menu, current_menu, 'set current shortcuts' )

			defaults_menu = wx.Menu()

			for name in custom_shortcuts_names:
				AppendMenuCheckItem( self, defaults_menu, name, 'turn this shortcut set on/off by default', name in default_media_viewer_custom_shortcuts, self._FlipActiveDefaultCustomShortcut, name )

			AppendMenu( menu, defaults_menu, 'set default shortcuts' )

		HG.client_controller.PopupMenu( self, menu )

	def EventDragButton( self, event ):
		if self._current_media is None:
			event.Skip()

			return

		page_key = None

		media = [ self._current_media ]

		alt_down = event.AltDown()

		result = DoFileExportDragDrop( self, page_key, media, alt_down )

		if result not in ( wx.DragError, wx.DragNone ):
			HG.client_controller.pub( 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'pause_media' ), self._canvas_key )

	def EventMouseWheel( self, event ):
		event.ResumePropagation( 1 )
		event.Skip()

	def ProcessContentUpdates( self, service_keys_to_content_updates ):
		if self._current_media is not None:
			my_hash = self._current_media.GetHash()

			do_redraw = False

			for ( service_key, content_updates ) in list( service_keys_to_content_updates.items() ):
				if True in ( my_hash in content_update.GetHashes() for content_update in content_updates ):
					do_redraw = True

					break

			if do_redraw:
				self._ResetButtons()

	def SetCurrentZoom( self, canvas_key, zoom ):
		if canvas_key == self._canvas_key:
			self._current_zoom = zoom

			label = ConvertZoomToPercentage( self._current_zoom )

			self._zoom_text.SetLabelText( label )

			self._top_hbox.Layout()

	def SetDisplayMedia( self, canvas_key, media ):
		if canvas_key == self._canvas_key:
			FullscreenHoverFrame.SetDisplayMedia( self, canvas_key, media )

			self._ResetText()

			self._ResetButtons()

	def SetIndexString( self, canvas_key, text ):
		if canvas_key == self._canvas_key:
			self._current_index_string = text

			self._index_text.SetLabelText( self._current_index_string )

			self._top_hbox.Layout()

class FullscreenHoverFrameTopArchiveDeleteFilter( FullscreenHoverFrameTop ):
	def _Archive( self ):
		HG.client_controller.pub( 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'archive_file' ), self._canvas_key )

	def _PopulateLeftButtons( self ):
		self._back_button = BetterBitmapButton( self, CC.GlobalBMPs.previous, HG.client_controller.pub, 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'archive_delete_filter_back' ), self._canvas_key )
		self._back_button.SetToolTip( 'back' )

		self._top_hbox.Add( self._back_button, CC.FLAGS_VCENTER )

		FullscreenHoverFrameTop._PopulateLeftButtons( self )

		self._skip_button = BetterBitmapButton( self, CC.GlobalBMPs.next_bmp, HG.client_controller.pub, 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'archive_delete_filter_skip' ), self._canvas_key )
		self._skip_button.SetToolTip( 'skip' )

		self._top_hbox.Add( self._skip_button, CC.FLAGS_VCENTER )

	def _ResetArchiveButton( self ):
		SetBitmapButtonBitmap( self._archive_button, CC.GlobalBMPs.archive )
		self._archive_button.SetToolTip( 'archive' )

class FullscreenHoverFrameTopNavigable( FullscreenHoverFrameTop ):
	def _PopulateLeftButtons( self ):
		self._previous_button = BetterBitmapButton( self, CC.GlobalBMPs.previous, HG.client_controller.pub, 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'view_previous' ), self._canvas_key )
		self._previous_button.SetToolTip( 'previous' )

		self._index_text = BetterStaticText( self, 'index' )

		self._next_button = BetterBitmapButton( self, CC.GlobalBMPs.next_bmp, HG.client_controller.pub, 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'view_next' ), self._canvas_key )
		self._next_button.SetToolTip( 'next' )

		self._top_hbox.Add( self._previous_button, CC.FLAGS_VCENTER )
		self._top_hbox.Add( self._index_text, CC.FLAGS_VCENTER )
		self._top_hbox.Add( self._next_button, CC.FLAGS_VCENTER )

class FullscreenHoverFrameTopDuplicatesFilter( FullscreenHoverFrameTopNavigable ):
	def __init__( self, parent, my_canvas, canvas_key ):
		FullscreenHoverFrameTopNavigable.__init__( self, parent, my_canvas, canvas_key )

		HG.client_controller.sub( self, 'SetDuplicatePair', 'canvas_new_duplicate_pair' )

	def _PopulateCenterButtons( self ):
		menu_items = []

		menu_items.append( ( 'normal', 'edit duplicate action options for \'this is better\'', 'edit what content is merged when you filter files', Call( self._EditMergeOptions, HC.DUPLICATE_BETTER ) ) )
		menu_items.append( ( 'normal', 'edit duplicate action options for \'same quality\'', 'edit what content is merged when you filter files', Call( self._EditMergeOptions, HC.DUPLICATE_SAME_QUALITY ) ) )
		menu_items.append( ( 'normal', 'edit duplicate action options for \'alternates\'', 'edit what content is merged when you filter files', Call( self._EditMergeOptions, HC.DUPLICATE_ALTERNATE ) ) )
		menu_items.append( ( 'normal', 'edit duplicate action options for \'not duplicates\'', 'edit what content is merged when you filter files', Call( self._EditMergeOptions, HC.DUPLICATE_NOT_DUPLICATE ) ) )
		menu_items.append( ( 'separator', None, None, None ) )
		menu_items.append( ( 'normal', 'edit background lighten/darken switch intensity', 'edit how much the background will brighten or darken as you switch between the pair', self._EditBackgroundSwitchIntensity ) )

		cog_button = MenuBitmapButton( self, CC.GlobalBMPs.cog, menu_items )

		self._top_hbox.Add( cog_button, CC.FLAGS_SIZER_VCENTER )

		FullscreenHoverFrameTopNavigable._PopulateCenterButtons( self )

		dupe_commands = []

		dupe_commands.append( ( 'this is better', 'Set that the current file you are looking at is better than the other in the pair.', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_this_is_better' ) ) )
		dupe_commands.append( ( 'same quality', 'Set that the two files are duplicates of very similar quality.', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_exactly_the_same' ) ) )
		dupe_commands.append( ( 'alternates', 'Set that the files are not duplicates, but that one is derived from the other or that they are both descendants of a common ancestor.', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_alternates' ) ) )
		dupe_commands.append( ( 'not duplicates', 'Set that the files are not duplicates or otherwise related--that this pair is a false-positive match.', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_not_dupes' ) ) )
		dupe_commands.append( ( 'custom action', 'Choose one of the other actions but customise the merge and delete options for this specific decision.', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_custom_action' ) ) )

		for ( label, tooltip, command ) in dupe_commands:
			command_button = BetterButton( self, label, HG.client_controller.pub, 'canvas_application_command', command, self._canvas_key )

			command_button.SetToolTip( tooltip )

			self._button_hbox.Add( command_button, CC.FLAGS_VCENTER )

	def _EditBackgroundSwitchIntensity( self ):
		new_options = HG.client_controller.new_options

		value = new_options.GetNoneableInteger( 'duplicate_background_switch_intensity' )

		with DialogEdit( self, 'edit lighten/darken intensity' ) as dlg:
			panel = EditNoneableIntegerPanel( dlg, value, message = 'intensity: ', none_phrase = 'do not change', min = 1, max = 9 )

			dlg.SetPanel( panel )

			if dlg.ShowModal() == wx.ID_OK:
				new_value = panel.GetValue()

				new_options.SetNoneableInteger( 'duplicate_background_switch_intensity', new_value )

	def _EditMergeOptions( self, duplicate_type ):
		new_options = HG.client_controller.new_options

		duplicate_action_options = new_options.GetDuplicateActionOptions( duplicate_type )

		with DialogEdit( self, 'edit duplicate merge options' ) as dlg:
			panel = EditDuplicateActionOptionsPanel( dlg, duplicate_type, duplicate_action_options )

			dlg.SetPanel( panel )

			if dlg.ShowModal() == wx.ID_OK:
				duplicate_action_options = panel.GetValue()

				new_options.SetDuplicateActionOptions( duplicate_type, duplicate_action_options )

	def _PopulateLeftButtons( self ):
		self._first_button = BetterBitmapButton( self, CC.GlobalBMPs.first, HG.client_controller.pub, 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_back' ), self._canvas_key )
		self._first_button.SetToolTip( 'go back a pair' )

		self._top_hbox.Add( self._first_button, CC.FLAGS_VCENTER )

		FullscreenHoverFrameTopNavigable._PopulateLeftButtons( self )

		self._last_button = BetterBitmapButton( self, CC.GlobalBMPs.last, HG.client_controller.pub, 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_skip' ), self._canvas_key )
		self._last_button.SetToolTip( 'show a different pair' )

		self._top_hbox.Add( self._last_button, CC.FLAGS_VCENTER )

	def SetDisplayMedia( self, canvas_key, media ):
		if canvas_key == self._canvas_key:
			if media is None:
				self._additional_info_text.SetLabelText( '' )

			FullscreenHoverFrameTopNavigable.SetDisplayMedia( self, canvas_key, media )

	def SetDuplicatePair( self, canvas_key, shown_media, comparison_media ):
		if canvas_key == self._canvas_key:
			( statements, score ) = GetDuplicateComparisonStatements( shown_media, comparison_media )

			self._additional_info_text.SetLabelText( os.linesep.join( statements ) )

			self._ResetText()

			self._ResetButtons()

class FullscreenHoverFrameTopNavigableList( FullscreenHoverFrameTopNavigable ):
	def _PopulateLeftButtons( self ):
		self._first_button = BetterBitmapButton( self, CC.GlobalBMPs.first, HG.client_controller.pub, 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'view_first' ), self._canvas_key )
		self._first_button.SetToolTip( 'first' )

		self._top_hbox.Add( self._first_button, CC.FLAGS_VCENTER )

		FullscreenHoverFrameTopNavigable._PopulateLeftButtons( self )

		self._last_button = BetterBitmapButton( self, CC.GlobalBMPs.last, HG.client_controller.pub, 'canvas_application_command', ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'view_last' ), self._canvas_key )
		self._last_button.SetToolTip( 'last' )

		self._top_hbox.Add( self._last_button, CC.FLAGS_VCENTER )

class FullscreenHoverFrameTopRight( FullscreenHoverFrame ):
	def __init__( self, parent, my_canvas, canvas_key ):
		FullscreenHoverFrame.__init__( self, parent, my_canvas, canvas_key )

		vbox = wx.BoxSizer( wx.VERTICAL )

		self._icon_panel = wx.Panel( self )

		self._trash_icon = BufferedWindowIcon( self._icon_panel, CC.GlobalBMPs.trash )
		self._inbox_icon = BufferedWindowIcon( self._icon_panel, CC.GlobalBMPs.inbox )

		icon_hbox = wx.BoxSizer( wx.HORIZONTAL )

		icon_hbox.Add( ( 16, 16 ), CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
		icon_hbox.Add( self._trash_icon, CC.FLAGS_VCENTER )
		icon_hbox.Add( self._inbox_icon, CC.FLAGS_VCENTER )

		self._icon_panel.SetSizer( icon_hbox )

		# repo strings

		self._file_repos = wx.StaticText( self, label = '', style = wx.ALIGN_RIGHT )

		# urls

		self._last_seen_urls = []
		self._urls_vbox = wx.BoxSizer( wx.VERTICAL )

		# likes

		like_hbox = wx.BoxSizer( wx.HORIZONTAL )

		like_services = HG.client_controller.services_manager.GetServices( ( HC.LOCAL_RATING_LIKE, ), randomised = False )

		if len( like_services ) > 0:
			like_hbox.Add( ( 16, 16 ), CC.FLAGS_EXPAND_BOTH_WAYS )

		for service in like_services:
			service_key = service.GetServiceKey()

			control = RatingLikeCanvas( self, service_key, canvas_key )

			like_hbox.Add( control, CC.FLAGS_NONE )

		# each numerical one in turn

		vbox.Add( like_hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )

		numerical_services = HG.client_controller.services_manager.GetServices( ( HC.LOCAL_RATING_NUMERICAL, ), randomised = False )

		for service in numerical_services:
			service_key = service.GetServiceKey()

			control = RatingNumericalCanvas( self, service_key, canvas_key )

			hbox = wx.BoxSizer( wx.HORIZONTAL )

			hbox.Add( ( 16, 16 ), CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
			hbox.Add( control, CC.FLAGS_NONE )

			vbox.Add( hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )

		vbox.Add( self._icon_panel, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
		vbox.Add( self._file_repos, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( self._urls_vbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )

		self.SetSizer( vbox )

		self._ResetData()

		HG.client_controller.sub( self, 'ProcessContentUpdates', 'content_updates_gui' )

		self.Bind( wx.EVT_MOUSEWHEEL, self.EventMouseWheel )

	def _GetIdealSizeAndPosition( self ):
		parent = self.GetParent()

		( parent_width, parent_height ) = parent.GetClientSize()

		( my_width, my_height ) = self.GetSize()

		my_ideal_width = int( parent_width * 0.2 )

		should_resize = my_ideal_width != my_width

		ideal_size = ( my_ideal_width, -1 )
		ideal_position = ClientToScreen( parent, ( int( parent_width * 0.8 ), 0 ) )

		return ( should_resize, ideal_size, ideal_position )

	def _ResetData( self ):
		if self._current_media is not None:
			has_inbox = self._current_media.HasInbox()
			has_trash = CC.TRASH_SERVICE_KEY in self._current_media.GetLocationsManager().GetCurrent()

			if has_inbox or has_trash:
				self._icon_panel.Show()

				if has_inbox:
					self._inbox_icon.Show()

				else:
					self._inbox_icon.Hide()

				if has_trash:
					self._trash_icon.Show()

				else:
					self._trash_icon.Hide()

			else:
				self._icon_panel.Hide()

			remote_strings = self._current_media.GetLocationsManager().GetRemoteLocationStrings()

			if len( remote_strings ) == 0:
				self._file_repos.Hide()

			else:
				remote_string = os.linesep.join( remote_strings )

				self._file_repos.SetLabelText( remote_string )

				self._file_repos.Show()

			# urls

			urls = self._current_media.GetLocationsManager().GetURLs()

			if urls != self._last_seen_urls:
				self._last_seen_urls = list( urls )

				self._urls_vbox.Clear( delete_windows = True )

				url_tuples = HG.client_controller.network_engine.domain_manager.ConvertURLsToMediaViewerTuples( urls )

				for ( display_string, url ) in url_tuples:
					link = BetterHyperLink( self, display_string, url )

					self._urls_vbox.Add( link, CC.FLAGS_EXPAND_PERPENDICULAR )

			self.Fit()

		self._SizeAndPosition()

	def EventMouseWheel( self, event ):
		event.ResumePropagation( 1 )
		event.Skip()

	def ProcessContentUpdates( self, service_keys_to_content_updates ):
		if self._current_media is not None:
			my_hash = self._current_media.GetHash()

			do_redraw = False

			for ( service_key, content_updates ) in list( service_keys_to_content_updates.items() ):
				# ratings updates do not change the shape of this hover but file changes of several kinds do

				if True in ( my_hash in content_update.GetHashes() for content_update in content_updates if content_update.GetDataType() == HC.CONTENT_TYPE_FILES ):
					do_redraw = True

					break

			if do_redraw:
				self._ResetData()

	def SetDisplayMedia( self, canvas_key, media ):
		if canvas_key == self._canvas_key:
			FullscreenHoverFrame.SetDisplayMedia( self, canvas_key, media )

			self._ResetData()

class FullscreenHoverFrameTags( FullscreenHoverFrame ):
	def __init__( self, parent, my_canvas, canvas_key ):
		FullscreenHoverFrame.__init__( self, parent, my_canvas, canvas_key )

		vbox = wx.BoxSizer( wx.VERTICAL )

		self._tags = ListBoxTagsSelectionHoverFrame( self, self._canvas_key )

		vbox.Add( self._tags, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )

		self.SetSizer( vbox )

		HG.client_controller.sub( self, 'ProcessContentUpdates', 'content_updates_gui' )

	def _GetIdealSizeAndPosition( self ):
		parent = self.GetParent()

		( parent_width, parent_height ) = parent.GetClientSize()

		( my_width, my_height ) = self.GetSize()

		my_ideal_width = int( parent_width * 0.2 )

		my_ideal_height = parent_height

		should_resize = my_ideal_width != my_width or my_ideal_height != my_height

		ideal_size = ( my_ideal_width, my_ideal_height )
		ideal_position = ClientToScreen( parent, ( 0, 0 ) )

		return ( should_resize, ideal_size, ideal_position )

	def _ResetTags( self ):
		if self._current_media is not None:
			self._tags.SetTagsByMedia( [ self._current_media ], force_reload = True )

	def ProcessContentUpdates( self, service_keys_to_content_updates ):
		if self._current_media is not None:
			my_hash = self._current_media.GetHash()

			do_redraw = False

			for ( service_key, content_updates ) in list( service_keys_to_content_updates.items() ):
				if True in ( my_hash in content_update.GetHashes() for content_update in content_updates ):
					do_redraw = True

					break

			if do_redraw:
				self._ResetTags()

	def SetDisplayMedia( self, canvas_key, media ):
		if canvas_key == self._canvas_key:
			FullscreenHoverFrame.SetDisplayMedia( self, canvas_key, media )

			self._ResetTags()


code:
import os, re, wx, collections

from lib.Actions.Import.Local import ImportFolder
from lib.Actions.Import.Options import CheckerOptions, FileImportOptions, \
	FilenameTaggingOptions, TagImportOptions

from lib.GUI.ACDropdown import AutoCompleteDropdownTagsWrite
from lib.GUI.Common import BetterBitmapButton, BetterBoxSizer, BetterButton, \
	BetterChoice, BetterHyperLink, BetterNotebook, BetterStaticText, \
	NoneableSpinCtrl, RegexButton, SetBitmapButtonBitmap, StaticBox, \
	WrapInGrid
from lib.GUI.Controls import NetworkJobControl
from lib.GUI.Dialogs import DialogInputNamespaceRegex, SelectServiceKey
from lib.GUI.Dialogs.Quick import SelectFromList
from lib.GUI.FileSeedCache import FileSeedCacheButton, FileSeedCacheStatusControl
from lib.GUI.GallerySeedLog import GallerySeedLogStatusControl
from lib.GUI.ListBoxes import ListBoxTagsStringsAddRemove
from lib.GUI.ListCtrl import BetterListCtrl, BetterListCtrlPanel
from lib.GUI.Menus import AppendMenuItem, AppendSeparator
from lib.GUI.OptionsPanels import OptionsPanelMimes
from lib.GUI.ScrolledPanels import EditPanel
from lib.GUI.ScrolledPanels.Edit import EditFileImportOptions, EditTagImportOptionsPanel
from lib.GUI.Time import EditCheckerOptions, TimeDeltaButton
from lib.GUI.TopLevelWindows import DialogEdit

from lib.Hydrus.Data import ShowException, TimeDeltaToPrettyTimeDelta, TimeHasPassed, \
	TimestampToPrettyTimeDelta, ToHumanInt
from lib.Hydrus.Exceptions import CancelledException, VetoException
from lib.Hydrus.Serialisable import CreateFromString
from lib.Hydrus.Tags import CleanTags
from lib.Hydrus.Tags.Local import ServiceKeysToTags
from lib.Hydrus.Text import DeserialiseNewlinedTexts

### CONSTANTS ###

import lib.Hydrus.Globals as HG
import lib.Hydrus.Constants as HC
import lib.Hydrus.Constants.Local as CC

from lib.Actions.Import import CHECKER_STATUS_404, CHECKER_STATUS_DEAD

### CLASSES ###

class CheckerOptionsButton( BetterButton ):
	def __init__( self, parent, checker_options, update_callable = None ):

		BetterButton.__init__( self, parent, 'checker options', self._EditOptions )

		self._checker_options = checker_options
		self._update_callable = update_callable

		self._SetToolTip()

	def _EditOptions( self ):
		with DialogEdit( self, 'edit checker options' ) as dlg:
			panel = EditCheckerOptions( dlg, self._checker_options )

			dlg.SetPanel( panel )

			if dlg.ShowModal() == wx.ID_OK:
				checker_options = panel.GetValue()

				self._SetValue( checker_options )

	def _SetToolTip( self ):
		self.SetToolTip( self._checker_options.GetSummary() )

	def _SetValue( self, checker_options ):
		self._checker_options = checker_options

		self._SetToolTip()

		if self._update_callable is not None:
			self._update_callable( self._checker_options )

	def GetValue( self ):
		return self._checker_options

	def SetValue( self, checker_options ):
		self._SetValue( checker_options )

class FileImportOptionsButton( BetterButton ):
	def __init__( self, parent, file_import_options, show_downloader_options, update_callable = None ):

		BetterButton.__init__( self, parent, 'file import options', self._EditOptions )

		self._file_import_options = file_import_options
		self._show_downloader_options = show_downloader_options
		self._update_callable = update_callable

		self._SetToolTip()

	def _EditOptions( self ):
		with DialogEdit( self, 'edit file import options' ) as dlg:
			panel = EditFileImportOptions( dlg, self._file_import_options, self._show_downloader_options )

			dlg.SetPanel( panel )

			if dlg.ShowModal() == wx.ID_OK:
				file_import_options = panel.GetValue()

				self._SetValue( file_import_options )

	def _SetToolTip( self ):
		self.SetToolTip( self._file_import_options.GetSummary() )

	def _SetValue( self, file_import_options ):
		self._file_import_options = file_import_options

		self._SetToolTip()

		if self._update_callable is not None:
			self._update_callable( self._file_import_options )

	def GetValue( self ):
		return self._file_import_options

	def SetValue( self, file_import_options ):
		self._SetValue( file_import_options )

class FilenameTaggingOptionsPanel( wx.Panel ):
	def __init__( self, parent, service_key, tag_update_callable, filename_tagging_options = None, present_for_accompanying_file_list = False ):

		if filename_tagging_options is None:
			# pull from an options default

			filename_tagging_options = FilenameTaggingOptions()

		wx.Panel.__init__( self, parent )

		self._service_key = service_key

		self._notebook = wx.Notebook( self )

		# eventually these will take 'regexoptions' or whatever object and 'showspecificfiles' as well

		self._simple_panel = self._SimplePanel( self._notebook, self._service_key, tag_update_callable, filename_tagging_options, present_for_accompanying_file_list )
		self._advanced_panel = self._AdvancedPanel( self._notebook, self._service_key, tag_update_callable, filename_tagging_options, present_for_accompanying_file_list )

		self._notebook.AddPage( self._simple_panel, 'simple', select = True )
		self._notebook.AddPage( self._advanced_panel, 'advanced' )

		vbox = wx.BoxSizer( wx.VERTICAL )

		vbox.Add( self._notebook, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )

		self.SetSizer( vbox )

	def GetFilenameTaggingOptions( self ):
		filename_tagging_options = FilenameTaggingOptions()

		self._advanced_panel.UpdateFilenameTaggingOptions( filename_tagging_options )
		self._simple_panel.UpdateFilenameTaggingOptions( filename_tagging_options )

		return filename_tagging_options

	def GetTags( self, index, path ):
		tags = set()

		tags.update( self._simple_panel.GetTags( index, path ) )
		tags.update( self._advanced_panel.GetTags( index, path ) )

		tags = CleanTags( tags )

		siblings_manager = HG.client_controller.tag_siblings_manager
		parents_manager = HG.client_controller.tag_parents_manager
		tag_censorship_manager = HG.client_controller.tag_censorship_manager

		tags = siblings_manager.CollapseTags( self._service_key, tags )
		tags = parents_manager.ExpandTags( self._service_key, tags )
		tags = tag_censorship_manager.FilterTags( self._service_key, tags )

		return tags

	def SetSelectedPaths( self, paths ):
		self._simple_panel.SetSelectedPaths( paths )

	class _AdvancedPanel( wx.Panel ):
		def __init__( self, parent, service_key, refresh_callable, filename_tagging_options, present_for_accompanying_file_list ):

			wx.Panel.__init__( self, parent )

			self._service_key = service_key
			self._refresh_callable = refresh_callable
			self._present_for_accompanying_file_list = present_for_accompanying_file_list

			#

			self._quick_namespaces_panel = StaticBox( self, 'quick namespaces' )

			quick_namespaces_listctrl_panel = BetterListCtrlPanel( self._quick_namespaces_panel )

			columns = [ ( 'namespace', 12 ), ( 'regex', -1 ) ]

			self._quick_namespaces_list = BetterListCtrl( quick_namespaces_listctrl_panel, 'quick_namespaces', 4, 20, columns, self._ConvertQuickRegexDataToListCtrlTuples, use_simple_delete = True, activation_callback = self.EditQuickNamespaces )

			quick_namespaces_listctrl_panel.SetListCtrl( self._quick_namespaces_list )

			quick_namespaces_listctrl_panel.AddButton( 'add', self.AddQuickNamespace )
			quick_namespaces_listctrl_panel.AddButton( 'edit', self.EditQuickNamespaces, enabled_only_on_selection = True )
			quick_namespaces_listctrl_panel.AddDeleteButton()

			#

			self._regexes_panel = StaticBox( self, 'regexes' )

			self._regexes = wx.ListBox( self._regexes_panel )
			self._regexes.Bind( wx.EVT_LISTBOX_DCLICK, self.EventRemoveRegex )

			self._regex_box = wx.TextCtrl( self._regexes_panel, style=wx.TE_PROCESS_ENTER )
			self._regex_box.Bind( wx.EVT_TEXT_ENTER, self.EventAddRegex )

			self._regex_shortcuts = RegexButton( self._regexes_panel )

			self._regex_intro_link = BetterHyperLink( self._regexes_panel, 'a good regex introduction', 'http://www.aivosto.com/vbtips/regex.html' )
			self._regex_practise_link = BetterHyperLink( self._regexes_panel, 'regex practise', 'http://regexr.com/3cvmf' )

			#

			self._num_panel = StaticBox( self, '#' )

			self._num_base = wx.SpinCtrl( self._num_panel, min = -10000000, max = 10000000, size = ( 60, -1 ) )
			self._num_base.SetValue( 1 )
			self._num_base.Bind( wx.EVT_SPINCTRL, self.EventRecalcNum )

			self._num_step = wx.SpinCtrl( self._num_panel, min = -1000000, max = 1000000, size = ( 60, -1 ) )
			self._num_step.SetValue( 1 )
			self._num_step.Bind( wx.EVT_SPINCTRL, self.EventRecalcNum )

			self._num_namespace = wx.TextCtrl( self._num_panel, size = ( 100, -1 ) )
			self._num_namespace.Bind( wx.EVT_TEXT, self.EventNumNamespaceChanged )

			if not self._present_for_accompanying_file_list:
				self._num_panel.Hide()

			#

			( quick_namespaces, regexes ) = filename_tagging_options.AdvancedToTuple()

			self._quick_namespaces_list.AddDatas( quick_namespaces )

			for regex in regexes:
				self._regexes.Append( regex )

			#

			self._quick_namespaces_panel.Add( quick_namespaces_listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )

			#

			self._regexes_panel.Add( self._regexes, CC.FLAGS_EXPAND_BOTH_WAYS )
			self._regexes_panel.Add( self._regex_box, CC.FLAGS_EXPAND_PERPENDICULAR )
			self._regexes_panel.Add( self._regex_shortcuts, CC.FLAGS_LONE_BUTTON )
			self._regexes_panel.Add( self._regex_intro_link, CC.FLAGS_LONE_BUTTON )
			self._regexes_panel.Add( self._regex_practise_link, CC.FLAGS_LONE_BUTTON )

			#

			hbox = wx.BoxSizer( wx.HORIZONTAL )

			hbox.Add( wx.StaticText( self._num_panel, label = '# base/step: ' ), CC.FLAGS_VCENTER )
			hbox.Add( self._num_base, CC.FLAGS_VCENTER )
			hbox.Add( self._num_step, CC.FLAGS_VCENTER )

			self._num_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )

			hbox = wx.BoxSizer( wx.HORIZONTAL )

			hbox.Add( wx.StaticText( self._num_panel, label = '# namespace: ' ), CC.FLAGS_VCENTER )
			hbox.Add( self._num_namespace, CC.FLAGS_EXPAND_BOTH_WAYS )

			self._num_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )

			second_vbox = wx.BoxSizer( wx.VERTICAL )

			second_vbox.Add( self._regexes_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
			second_vbox.Add( self._num_panel, CC.FLAGS_EXPAND_PERPENDICULAR )

			#

			hbox = wx.BoxSizer( wx.HORIZONTAL )

			hbox.Add( self._quick_namespaces_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
			hbox.Add( second_vbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )

			self.SetSizer( hbox )

		def _ConvertQuickRegexDataToListCtrlTuples( self, data ):
			( namespace, regex ) = data

			display_tuple = ( namespace, regex )
			sort_tuple = ( namespace, regex )

			return ( display_tuple, sort_tuple )

		def AddQuickNamespace( self ):
			with DialogInputNamespaceRegex( self ) as dlg:
				if dlg.ShowModal() == wx.ID_OK:
					( namespace, regex ) = dlg.GetInfo()

					data = ( namespace, regex )

					self._quick_namespaces_list.AddDatas( ( data, ) )

					self._refresh_callable()

		def EditQuickNamespaces( self ):
			data_to_edit = self._quick_namespaces_list.GetData( only_selected = True )

			for old_data in data_to_edit:
				( namespace, regex ) = old_data

				with DialogInputNamespaceRegex( self, namespace = namespace, regex = regex ) as dlg:
					if dlg.ShowModal() == wx.ID_OK:
						( new_namespace, new_regex ) = dlg.GetInfo()

						new_data = ( new_namespace, new_regex )

						if new_data != old_data:
							self._quick_namespaces_list.DeleteDatas( ( old_data, ) )

							self._quick_namespaces_list.AddDatas( ( new_data, ) )

			self._refresh_callable()

		def EventAddRegex( self, event ):
			regex = self._regex_box.GetValue()

			if regex != '':
				try:
					re.compile( regex )

				except Exception as e:
					text = 'That regex would not compile!'
					text += os.linesep * 2
					text += str( e )

					wx.MessageBox( text )

					return

				self._regexes.Append( regex )

				self._regex_box.ChangeValue( '' )

				self._refresh_callable()

		def EventNumNamespaceChanged( self, event ):
			self._refresh_callable()

		def EventRecalcNum( self, event ):
			self._refresh_callable()

		def EventRemoveRegex( self, event ):
			selection = self._regexes.GetSelection()

			if selection != wx.NOT_FOUND:
				if len( self._regex_box.GetValue() ) == 0:
					self._regex_box.SetValue( self._regexes.GetString( selection ) )

				self._regexes.Delete( selection )

				self._refresh_callable()

		def GetTags( self, index, path ):
			tags = set()

			num_namespace = self._num_namespace.GetValue()

			if num_namespace != '':
				num_base = self._num_base.GetValue()
				num_step = self._num_step.GetValue()

				tag_num = num_base + index * num_step

				tags.add( num_namespace + ':' + str( tag_num ) )

			return tags

		def UpdateFilenameTaggingOptions( self, filename_tagging_options ):
			quick_namespaces = self._quick_namespaces_list.GetData()

			regexes = self._regexes.GetStrings()

			filename_tagging_options.AdvancedSetTuple( quick_namespaces, regexes )

	class _SimplePanel( wx.Panel ):
		def __init__( self, parent, service_key, refresh_callable, filename_tagging_options, present_for_accompanying_file_list ):

			wx.Panel.__init__( self, parent )

			self._service_key = service_key
			self._refresh_callable = refresh_callable
			self._present_for_accompanying_file_list = present_for_accompanying_file_list

			#

			self._tags_panel = StaticBox( self, 'tags for all' )

			self._tags = ListBoxTagsStringsAddRemove( self._tags_panel, self._service_key, self.TagsRemoved )

			expand_parents = True

			self._tag_box = AutoCompleteDropdownTagsWrite( self._tags_panel, self.EnterTags, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )

			self._tags_paste_button = BetterButton( self._tags_panel, 'paste tags', self._PasteTags )

			#

			self._single_tags_panel = StaticBox( self, 'tags just for selected files' )

			self._paths_to_single_tags = collections.defaultdict( set )

			self._single_tags = ListBoxTagsStringsAddRemove( self._single_tags_panel, self._service_key, self.SingleTagsRemoved )

			self._single_tags_paste_button = BetterButton( self._single_tags_panel, 'paste tags', self._PasteSingleTags )

			expand_parents = True

			self._single_tag_box = AutoCompleteDropdownTagsWrite( self._single_tags_panel, self.EnterTagsSingle, expand_parents, CC.LOCAL_FILE_SERVICE_KEY, service_key )

			self.SetSelectedPaths( [] )

			if not self._present_for_accompanying_file_list:
				self._single_tags_panel.Hide()

			#

			self._checkboxes_panel = StaticBox( self, 'misc' )

			self._load_from_txt_files_checkbox = wx.CheckBox( self._checkboxes_panel, label = 'try to load tags from neighbouring .txt files' )

			txt_files_help_button = BetterBitmapButton( self._checkboxes_panel, CC.GlobalBMPs.help, self._ShowTXTHelp )
			txt_files_help_button.SetToolTip( 'Show help regarding importing tags from .txt files.' )

			self._filename_namespace = wx.TextCtrl( self._checkboxes_panel )
			self._filename_namespace.SetMinSize( ( 100, -1 ) )

			self._filename_checkbox = wx.CheckBox( self._checkboxes_panel, label = 'add filename? [namespace]' )

			self._directory_namespace_controls = {}

			directory_items = []

			directory_items.append( ( 0, 'first' ) )
			directory_items.append( ( 1, 'second' ) )
			directory_items.append( ( 2, 'third' ) )
			directory_items.append( ( -3, 'third last' ) )
			directory_items.append( ( -2, 'second last' ) )
			directory_items.append( ( -1, 'last' ) )

			for ( index, phrase ) in directory_items:
				dir_checkbox = wx.CheckBox( self._checkboxes_panel, label = 'add ' + phrase + ' directory? [namespace]' )

				dir_namespace_textctrl = wx.TextCtrl( self._checkboxes_panel )
				dir_namespace_textctrl.SetMinSize( ( 100, -1 ) )

				self._directory_namespace_controls[ index ] = ( dir_checkbox, dir_namespace_textctrl )

			#

			( tags_for_all, load_from_neighbouring_txt_files, add_filename, directory_dict ) = filename_tagging_options.SimpleToTuple()

			self._tags.AddTags( tags_for_all )
			self._load_from_txt_files_checkbox.SetValue( load_from_neighbouring_txt_files )

			( add_filename_boolean, add_filename_namespace ) = add_filename

			self._filename_checkbox.SetValue( add_filename_boolean )
			self._filename_namespace.SetValue( add_filename_namespace )

			for ( index, ( dir_boolean, dir_namespace ) ) in list( directory_dict.items() ):
				( dir_checkbox, dir_namespace_textctrl ) = self._directory_namespace_controls[ index ]

				dir_checkbox.SetValue( dir_boolean )
				dir_namespace_textctrl.SetValue( dir_namespace )

				dir_checkbox.Bind( wx.EVT_CHECKBOX, self.EventRefresh )
				dir_namespace_textctrl.Bind( wx.EVT_TEXT, self.EventRefresh )

			#

			self._tags_panel.Add( self._tags, CC.FLAGS_EXPAND_BOTH_WAYS )
			self._tags_panel.Add( self._tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
			self._tags_panel.Add( self._tags_paste_button, CC.FLAGS_EXPAND_PERPENDICULAR )

			self._single_tags_panel.Add( self._single_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
			self._single_tags_panel.Add( self._single_tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
			self._single_tags_panel.Add( self._single_tags_paste_button, CC.FLAGS_EXPAND_PERPENDICULAR )

			txt_hbox = wx.BoxSizer( wx.HORIZONTAL )

			txt_hbox.Add( self._load_from_txt_files_checkbox, CC.FLAGS_EXPAND_BOTH_WAYS )
			txt_hbox.Add( txt_files_help_button, CC.FLAGS_VCENTER )

			filename_hbox = wx.BoxSizer( wx.HORIZONTAL )

			filename_hbox.Add( self._filename_checkbox, CC.FLAGS_VCENTER )
			filename_hbox.Add( self._filename_namespace, CC.FLAGS_EXPAND_BOTH_WAYS )

			self._checkboxes_panel.Add( txt_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
			self._checkboxes_panel.Add( filename_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )

			for index in ( 0, 1, 2, -3, -2, -1 ):
				hbox = wx.BoxSizer( wx.HORIZONTAL )

				( dir_checkbox, dir_namespace_textctrl ) = self._directory_namespace_controls[ index ]

				hbox.Add( dir_checkbox, CC.FLAGS_VCENTER )
				hbox.Add( dir_namespace_textctrl, CC.FLAGS_EXPAND_BOTH_WAYS )

				self._checkboxes_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )

			hbox = wx.BoxSizer( wx.HORIZONTAL )

			hbox.Add( self._tags_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
			hbox.Add( self._single_tags_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
			hbox.Add( self._checkboxes_panel, CC.FLAGS_EXPAND_PERPENDICULAR )

			self.SetSizer( hbox )

			#

			self._load_from_txt_files_checkbox.Bind( wx.EVT_CHECKBOX, self.EventRefresh )
			self._filename_namespace.Bind( wx.EVT_TEXT, self.EventRefresh )
			self._filename_checkbox.Bind( wx.EVT_CHECKBOX, self.EventRefresh )

		def _GetTagsFromClipboard( self ):
			text = HG.client_controller.GetClipboardText()

			try:
				tags = DeserialiseNewlinedTexts( text )

				tags = CleanTags( tags )

				return tags

			except Exception:
				raise Exception( 'I could not understand what was in the clipboard' )

		def _PasteTags( self ):
			try:
				tags = self._GetTagsFromClipboard()

			except Exception as e:
				wx.MessageBox( str( e ) )

				return

			self.EnterTags( tags )

		def _PasteSingleTags( self ):
			try:
				tags = self._GetTagsFromClipboard()

			except Exception as e:
				wx.MessageBox( str( e ) )

				return

			self.EnterTagsSingle( tags )

		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 += 'Make sure you preview the results in the table above to be certain everything is parsing correctly. Until you are comfortable with this, you should test it on just one or two files.'

			wx.MessageBox( message )

		def EnterTags( self, tags ):
			tag_parents_manager = HG.client_controller.tag_parents_manager

			parents = set()

			for tag in tags:
				some_parents = tag_parents_manager.GetParents( self._service_key, tag )

				parents.update( some_parents )

			if len( tags ) > 0:
				self._tags.AddTags( tags )
				self._tags.AddTags( parents )

				self._refresh_callable()

		def EnterTagsSingle( self, tags ):
			tag_parents_manager = HG.client_controller.tag_parents_manager

			parents = set()

			for tag in tags:
				some_parents = tag_parents_manager.GetParents( self._service_key, tag )

				parents.update( some_parents )

			if len( tags ) > 0:
				self._single_tags.AddTags( tags )
				self._single_tags.AddTags( parents )

				for path in self._selected_paths:
					current_tags = self._paths_to_single_tags[ path ]

					current_tags.update( tags )
					current_tags.update( parents )

				self._refresh_callable()

		def EventRefresh( self, event ):
			self._refresh_callable()

		def GetTags( self, index, path ):
			tags = set()

			if path in self._paths_to_single_tags:
				tags.update( self._paths_to_single_tags[ path ] )

			return tags

		def SingleTagsRemoved( self, tags ):
			for path in self._selected_paths:
				current_tags = self._paths_to_single_tags[ path ]

				current_tags.difference_update( tags )

			self._refresh_callable()

		def SetSelectedPaths( self, paths ):
			self._selected_paths = paths

			single_tags = set()

			if len( paths ) > 0:
				for path in self._selected_paths:
					if path in self._paths_to_single_tags:
						single_tags.update( self._paths_to_single_tags[ path ] )

				self._single_tag_box.Enable()
				self._single_tags_paste_button.Enable()

			else:
				self._single_tag_box.Disable()
				self._single_tags_paste_button.Disable()

			self._single_tags.SetTags( single_tags )

		def TagsRemoved( self, tag ):
			self._refresh_callable()

		def UpdateFilenameTaggingOptions( self, filename_tagging_options ):
			tags_for_all = self._tags.GetTags()
			load_from_neighbouring_txt_files = self._load_from_txt_files_checkbox.GetValue()

			add_filename = ( self._filename_checkbox.GetValue(), self._filename_namespace.GetValue() )

			directories_dict = {}

			for ( index, ( dir_checkbox, dir_namespace_textctrl ) ) in list( self._directory_namespace_controls.items() ):
				directories_dict[ index ] = ( dir_checkbox.GetValue(), dir_namespace_textctrl.GetValue() )

			filename_tagging_options.SimpleSetTuple( tags_for_all, load_from_neighbouring_txt_files, add_filename, directories_dict )

class EditImportFoldersPanel( EditPanel ):
	def __init__( self, parent, import_folders ):

		EditPanel.__init__( self, parent )

		import_folders_panel = BetterListCtrlPanel( self )

		columns = [ ( 'name', 24 ), ( 'path', -1 ), ( 'paused', 8 ), ( 'check period', 24 ) ]

		self._import_folders = BetterListCtrl( import_folders_panel, 'import_folders', 8, 36, columns, self._ConvertImportFolderToListCtrlTuples, use_simple_delete = True, activation_callback = self._Edit )

		import_folders_panel.SetListCtrl( self._import_folders )

		import_folders_panel.AddButton( 'add', self._Add )
		import_folders_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
		import_folders_panel.AddDeleteButton()

		#

		self._import_folders.SetData( import_folders )

		self._import_folders.Sort()

		#

		vbox = wx.BoxSizer( wx.VERTICAL )

		intro = 'Here you can set the client to regularly check certain folders for new files to import.'

		vbox.Add( BetterStaticText( self, intro ), CC.FLAGS_EXPAND_PERPENDICULAR )

		warning = 'WARNING: Import folders check (and potentially move/delete!) the contents of all subdirectories as well as the base directory!'

		warning_st = BetterStaticText( self, warning )

		warning_st.SetForegroundColour( ( 128, 0, 0 ) )

		vbox.Add( warning_st, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( import_folders_panel, CC.FLAGS_EXPAND_BOTH_WAYS )

		self.SetSizer( vbox )

	def _Add( self ):
		import_folder = ImportFolder( 'import folder' )

		with DialogEdit( self, 'edit import folder' ) as dlg:
			panel = EditImportFolderPanel( dlg, import_folder )

			dlg.SetPanel( panel )

			if dlg.ShowModal() == wx.ID_OK:
				import_folder = panel.GetValue()

				import_folder.SetNonDupeName( self._GetExistingNames() )

				self._import_folders.AddDatas( ( import_folder, ) )

				self._import_folders.Sort()

	def _ConvertImportFolderToListCtrlTuples( self, import_folder ):
		( name, path, paused, check_regularly, check_period ) = import_folder.ToListBoxTuple()

		if paused:
			pretty_paused = 'yes'

		else:
			pretty_paused = ''

		if not check_regularly:
			pretty_check_period = 'not checking regularly'

		else:
			pretty_check_period = TimeDeltaToPrettyTimeDelta( check_period )

		sort_tuple = ( name, path, paused, check_period )
		display_tuple = ( name, path, pretty_paused, pretty_check_period )

		return ( display_tuple, sort_tuple )

	def _Edit( self ):
		import_folders = self._import_folders.GetData( only_selected = True )

		for import_folder in import_folders:
			with DialogEdit( self, 'edit import folder' ) as dlg:
				panel = EditImportFolderPanel( dlg, import_folder )

				dlg.SetPanel( panel )

				if dlg.ShowModal() == wx.ID_OK:
					edited_import_folder = panel.GetValue()

					self._import_folders.DeleteDatas( ( import_folder, ) )

					edited_import_folder.SetNonDupeName( self._GetExistingNames() )

					self._import_folders.AddDatas( ( edited_import_folder, ) )

				else:
					break

		self._import_folders.Sort()

	def _GetExistingNames( self ):
		import_folders = self._import_folders.GetData()

		names = { import_folder.GetName() for import_folder in import_folders }

		return names

	def GetValue( self ):
		import_folders = self._import_folders.GetData()

		return import_folders

class EditImportFolderPanel( EditPanel ):
	def __init__( self, parent, import_folder ):
		EditPanel.__init__( self, parent )

		self._import_folder = import_folder

		( name, path, mimes, file_import_options, tag_import_options, tag_service_keys_to_filename_tagging_options, actions, action_locations, period, check_regularly, paused, check_now, show_working_popup, publish_files_to_popup_button, publish_files_to_page ) = self._import_folder.ToTuple()

		self._panel = wx.ScrolledWindow( self )

		self._folder_box = 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._check_regularly = wx.CheckBox( self._folder_box )

		self._period = TimeDeltaButton( self._folder_box, min = 3 * 60, days = True, hours = True, minutes = True )

		self._paused = wx.CheckBox( self._folder_box )

		self._check_now = wx.CheckBox( self._folder_box )

		self._show_working_popup = wx.CheckBox( self._folder_box )
		self._publish_files_to_popup_button = wx.CheckBox( self._folder_box )
		self._publish_files_to_page = wx.CheckBox( self._folder_box )

		self._file_seed_cache_button = FileSeedCacheButton( self._folder_box, HG.client_controller, self._import_folder.GetFileSeedCache, file_seed_cache_set_callable = self._import_folder.SetFileSeedCache )

		#

		self._file_box = StaticBox( self._panel, 'file options' )

		self._mimes = OptionsPanelMimes( self._file_box, HC.ALLOWED_MIMES )

		def create_choice():
			choice = 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 )

		show_downloader_options = False

		self._file_import_options = FileImportOptionsButton( self._file_box, file_import_options, show_downloader_options )

		#

		self._tag_box = StaticBox( self._panel, 'tag options' )

		self._tag_import_options = TagImportOptionsButton( self._tag_box, tag_import_options, show_downloader_options )

		self._filename_tagging_options_box = StaticBox( self._tag_box, 'filename tagging' )

		filename_tagging_options_panel = BetterListCtrlPanel( self._filename_tagging_options_box )

		columns = [ ( 'filename tagging options services', -1 ) ]

		self._filename_tagging_options = BetterListCtrl( filename_tagging_options_panel, 'filename_tagging_options', 5, 25, columns, self._ConvertFilenameTaggingOptionsToListCtrlTuples, use_simple_delete = True, activation_callback = self._EditFilenameTaggingOptions )

		filename_tagging_options_panel.SetListCtrl( self._filename_tagging_options )

		filename_tagging_options_panel.AddButton( 'add', self._AddFilenameTaggingOptions )
		filename_tagging_options_panel.AddButton( 'edit', self._EditFilenameTaggingOptions, enabled_only_on_selection = True )
		filename_tagging_options_panel.AddDeleteButton()

		# services_manager = HG.client_controller.services_manager

		#

		self._name.SetValue( name )
		self._path.SetPath( path )

		self._check_regularly.SetValue( check_regularly )

		self._period.SetValue( period )
		self._paused.SetValue( paused )

		self._show_working_popup.SetValue( show_working_popup )
		self._publish_files_to_popup_button.SetValue( publish_files_to_popup_button )
		self._publish_files_to_page.SetValue( publish_files_to_page )

		self._mimes.SetValue( mimes )

		self._action_successful.SelectClientData( actions[ CC.STATUS_SUCCESSFUL_AND_NEW ] )
		if CC.STATUS_SUCCESSFUL_AND_NEW in action_locations:
			self._location_successful.SetPath( action_locations[ CC.STATUS_SUCCESSFUL_AND_NEW ] )

		self._action_redundant.SelectClientData( actions[ CC.STATUS_SUCCESSFUL_BUT_REDUNDANT ] )
		if CC.STATUS_SUCCESSFUL_BUT_REDUNDANT in action_locations:
			self._location_redundant.SetPath( action_locations[ CC.STATUS_SUCCESSFUL_BUT_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_ERROR ] )
		if CC.STATUS_ERROR in action_locations:
			self._location_failed.SetPath( action_locations[ CC.STATUS_ERROR ] )

		good_tag_service_keys_to_filename_tagging_options = { service_key : filename_tagging_options for ( service_key, filename_tagging_options ) in list( tag_service_keys_to_filename_tagging_options.items() ) if HG.client_controller.services_manager.ServiceExists( service_key ) }

		self._filename_tagging_options.AddDatas( list( good_tag_service_keys_to_filename_tagging_options.items() ) )

		self._filename_tagging_options.Sort()

		#

		rows = []

		rows.append( ( 'name: ', self._name ) )
		rows.append( ( 'folder path: ', self._path ) )
		rows.append( ( 'currently paused (if set, will not ever do any work ): ', self._paused ) )
		rows.append( ( 'check regularly?: ', self._check_regularly ) )
		rows.append( ( 'check period: ', self._period ) )
		rows.append( ( 'check on manage dialog ok: ', self._check_now ) )
		rows.append( ( 'show a popup while working: ', self._show_working_popup ) )
		rows.append( ( 'publish new files to a popup button: ', self._publish_files_to_popup_button ) )
		rows.append( ( 'publish new files to a page: ', self._publish_files_to_page ) )
		rows.append( ( 'review currently cached import paths: ', self._file_seed_cache_button ) )

		gridbox = WrapInGrid( self._folder_box, rows )

		self._folder_box.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )

		#

		rows = []

		rows.append( ( 'mimes to import: ', self._mimes ) )

		mimes_gridbox = WrapInGrid( self._file_box, rows, expand_text = True )

		gridbox = wx.FlexGridSizer( 3 )

		gridbox.AddGrowableCol( 1, 1 )

		gridbox.Add( wx.StaticText( self._file_box, label = 'when a file imports successfully: ' ), CC.FLAGS_VCENTER )
		gridbox.Add( self._action_successful, CC.FLAGS_EXPAND_BOTH_WAYS )
		gridbox.Add( self._location_successful, CC.FLAGS_EXPAND_BOTH_WAYS )

		gridbox.Add( wx.StaticText( self._file_box, label = 'when a file is already in the db: ' ), CC.FLAGS_VCENTER )
		gridbox.Add( self._action_redundant, CC.FLAGS_EXPAND_BOTH_WAYS )
		gridbox.Add( self._location_redundant, CC.FLAGS_EXPAND_BOTH_WAYS )

		gridbox.Add( wx.StaticText( self._file_box, label = 'when a file has previously been deleted from the db: ' ), CC.FLAGS_VCENTER )
		gridbox.Add( self._action_deleted, CC.FLAGS_EXPAND_BOTH_WAYS )
		gridbox.Add( self._location_deleted, CC.FLAGS_EXPAND_BOTH_WAYS )

		gridbox.Add( wx.StaticText( self._file_box, label = 'when a file fails to import: ' ), CC.FLAGS_VCENTER )
		gridbox.Add( self._action_failed, CC.FLAGS_EXPAND_BOTH_WAYS )
		gridbox.Add( self._location_failed, CC.FLAGS_EXPAND_BOTH_WAYS )

		self._file_box.Add( mimes_gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
		self._file_box.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
		self._file_box.Add( self._file_import_options, CC.FLAGS_EXPAND_PERPENDICULAR )

		#

		self._filename_tagging_options_box.Add( filename_tagging_options_panel, CC.FLAGS_EXPAND_BOTH_WAYS )

		self._tag_box.Add( self._tag_import_options, CC.FLAGS_EXPAND_PERPENDICULAR )
		self._tag_box.Add( self._filename_tagging_options_box, CC.FLAGS_EXPAND_BOTH_WAYS )

		#

		vbox = wx.BoxSizer( wx.VERTICAL )

		vbox.Add( self._folder_box, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( self._file_box, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( self._tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )

		self._panel.SetSizer( vbox )

		vbox = wx.BoxSizer( wx.VERTICAL )

		vbox.Add( self._panel, CC.FLAGS_EXPAND_BOTH_WAYS )

		self.SetSizer( vbox )

		self._CheckLocations()

		self._check_regularly.Bind( wx.EVT_CHECKBOX, self.EventCheckRegularly )

		self._UpdateCheckRegularly()

	def _AddFilenameTaggingOptions( self ):
		service_key = SelectServiceKey( HC.TAG_SERVICES )

		if service_key is None:
			return

		existing_service_keys = { service_key for ( service_key, filename_tagging_options ) in self._filename_tagging_options.GetData() }

		if service_key in existing_service_keys:
			wx.MessageBox( 'You already have an entry for that service key! Please try editing it instead!' )

			return

		with DialogEdit( self, 'edit filename tagging options' ) as dlg:
			filename_tagging_options = FilenameTaggingOptions()

			panel = EditFilenameTaggingOptionPanel( dlg, service_key, filename_tagging_options )

			dlg.SetPanel( panel )

			if dlg.ShowModal() == wx.ID_OK:
				filename_tagging_options = panel.GetValue()

				self._filename_tagging_options.AddDatas( [ ( service_key, filename_tagging_options ) ] )

				self._filename_tagging_options.Sort()

	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 _CheckValid( self ):
		path = self._path.GetPath()

		if path in ( '', None ):
			raise VetoException( 'You must enter a path to import from!' )

		if not os.path.exists( path ):
			wx.MessageBox( 'The path you have entered--"' + path + '"--does not exist! The dialog will not force you to correct it, but this import folder will do no work as long as the location is missing!' )

		if HC.BASE_DIR.startswith( path ) or HG.client_controller.GetDBDir().startswith( path ):
			raise VetoException( 'You cannot set an import path that includes your install or database directory!' )

		if self._action_successful.GetChoice() == CC.IMPORT_FOLDER_MOVE:
			path = self._location_successful.GetPath()

			if path in ( '', None ):
				raise VetoException( 'You must enter a path for your successful file move location!' )

			if not os.path.exists( path ):
				wx.MessageBox( 'The path you have entered for your successful file move location--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )

		if self._action_redundant.GetChoice() == CC.IMPORT_FOLDER_MOVE:
			path = self._location_redundant.GetPath()

			if path in ( '', None ):
				raise VetoException( 'You must enter a path for your redundant file move location!' )

			if not os.path.exists( path ):
				wx.MessageBox( 'The path you have entered for your redundant file move location--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )

		if self._action_deleted.GetChoice() == CC.IMPORT_FOLDER_MOVE:
			path = self._location_deleted.GetPath()

			if path in ( '', None ):
				raise VetoException( 'You must enter a path for your deleted file move location!' )

			if not os.path.exists( path ):
				wx.MessageBox( 'The path you have entered for your deleted file move location--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )

		if self._action_failed.GetChoice() == CC.IMPORT_FOLDER_MOVE:
			path = self._location_failed.GetPath()

			if path in ( '', None ):
				raise VetoException( 'You must enter a path for your failed file move location!' )

			if not os.path.exists( path ):
				wx.MessageBox( 'The path you have entered for your failed file move location--"' + path + '"--does not exist! The dialog will not force you to correct it, but you should not let this import folder run until you have corrected or created it!' )

	def _ConvertFilenameTaggingOptionsToListCtrlTuples( self, data ):
		( service_key, filename_tagging_options ) = data

		name = HG.client_controller.services_manager.GetName( service_key )

		display_tuple = ( name, )
		sort_tuple = ( name, )

		return ( display_tuple, sort_tuple )

	def _EditFilenameTaggingOptions( self ):
		selected_data = self._filename_tagging_options.GetData( only_selected = True )

		for data in selected_data:
			( service_key, filename_tagging_options ) = data

			with DialogEdit( self, 'edit filename tagging options' ) as dlg:
				panel = EditFilenameTaggingOptionPanel( dlg, service_key, filename_tagging_options )

				dlg.SetPanel( panel )

				if dlg.ShowModal() == wx.ID_OK:
					self._filename_tagging_options.DeleteDatas( ( data, ) )

					filename_tagging_options = panel.GetValue()

					self._filename_tagging_options.AddDatas( [ ( service_key, filename_tagging_options ) ] )

				else:
					break

	def _UpdateCheckRegularly( self ):
		if self._check_regularly.GetValue():
			self._period.Enable()

		else:
			self._period.Disable()

	def EventCheckRegularly( self, event ):
		self._UpdateCheckRegularly()

	def EventCheckLocations( self, event ):
		self._CheckLocations()

	def GetValue( self ):
		self._CheckValid()

		name = self._name.GetValue()
		path = self._path.GetPath()
		mimes = self._mimes.GetValue()
		file_import_options = self._file_import_options.GetValue()
		tag_import_options = self._tag_import_options.GetValue()

		actions = {}
		action_locations = {}

		actions[ CC.STATUS_SUCCESSFUL_AND_NEW ] = self._action_successful.GetChoice()
		if actions[ CC.STATUS_SUCCESSFUL_AND_NEW ] == CC.IMPORT_FOLDER_MOVE:
			action_locations[ CC.STATUS_SUCCESSFUL_AND_NEW ] = self._location_successful.GetPath()

		actions[ CC.STATUS_SUCCESSFUL_BUT_REDUNDANT ] = self._action_redundant.GetChoice()
		if actions[ CC.STATUS_SUCCESSFUL_BUT_REDUNDANT ] == CC.IMPORT_FOLDER_MOVE:
			action_locations[ CC.STATUS_SUCCESSFUL_BUT_REDUNDANT ] = 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 ] = self._location_deleted.GetPath()

		actions[ CC.STATUS_ERROR ] = self._action_failed.GetChoice()
		if actions[ CC.STATUS_ERROR ] == CC.IMPORT_FOLDER_MOVE:
			action_locations[ CC.STATUS_ERROR ] = self._location_failed.GetPath()

		period = self._period.GetValue()
		check_regularly = self._check_regularly.GetValue()

		paused = self._paused.GetValue()

		check_now = self._check_now.GetValue()

		show_working_popup = self._show_working_popup.GetValue()
		publish_files_to_popup_button = self._publish_files_to_popup_button.GetValue()
		publish_files_to_page = self._publish_files_to_page.GetValue()

		tag_service_keys_to_filename_tagging_options = dict( self._filename_tagging_options.GetData() )

		self._import_folder.SetTuple( name, path, mimes, file_import_options, tag_import_options, tag_service_keys_to_filename_tagging_options, actions, action_locations, period, check_regularly, paused, check_now, show_working_popup, publish_files_to_popup_button, publish_files_to_page )

		return self._import_folder

class EditLocalImportFilenameTaggingPanel( EditPanel ):
	def __init__( self, parent, paths ):
		EditPanel.__init__( self, parent )

		self._paths = paths

		self._tag_repositories = BetterNotebook( self )

		#

		services = HG.client_controller.services_manager.GetServices( HC.TAG_SERVICES, randomised = False )

		default_tag_repository_key = HC.options[ 'default_tag_repository' ]

		for service in services:
			service_key = service.GetServiceKey()
			name = service.GetName()

			page = self._Panel( self._tag_repositories, service_key, paths )

			select = service_key == default_tag_repository_key

			self._tag_repositories.AddPage( page, name, select = select )

		#

		vbox = wx.BoxSizer( wx.VERTICAL )

		vbox.Add( self._tag_repositories, CC.FLAGS_EXPAND_BOTH_WAYS )

		self.SetSizer( vbox )

	def GetValue( self ):
		paths_to_service_keys_to_tags = collections.defaultdict( ServiceKeysToTags )

		for page in self._tag_repositories.GetPages():
			( service_key, page_of_paths_to_tags ) = page.GetInfo()

			for ( path, tags ) in page_of_paths_to_tags.items():
				if len( tags ) == 0:
					continue

				paths_to_service_keys_to_tags[ path ][ service_key ] = tags

		return paths_to_service_keys_to_tags

	class _Panel( wx.Panel ):
		def __init__( self, parent, service_key, paths ):
			wx.Panel.__init__( self, parent )

			self._service_key = service_key
			self._paths = paths

			columns = [ ( '#', 4 ), ( 'path', 40 ), ( 'tags', -1 ) ]

			self._paths_list = BetterListCtrl( self, 'paths_to_tags', 10, 40, columns, self._ConvertDataToListCtrlTuples )

			self._paths_list.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected )
			self._paths_list.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected )

			#

			self._filename_tagging_panel = FilenameTaggingOptionsPanel( self, self._service_key, self.ScheduleRefreshFileList, present_for_accompanying_file_list = True )

			self._schedule_refresh_file_list_job = None

			#

			# i.e. ( index, path )
			self._paths_list.AddDatas( list( enumerate( self._paths ) ) )

			#

			vbox = BetterBoxSizer( wx.VERTICAL )

			vbox.Add( self._paths_list, CC.FLAGS_EXPAND_BOTH_WAYS )
			vbox.Add( self._filename_tagging_panel, CC.FLAGS_EXPAND_PERPENDICULAR )

			self.SetSizer( vbox )

		def _ConvertDataToListCtrlTuples( self, data ):
			( index, path ) = data

			tags = self._GetTags( index, path )

			pretty_index = ToHumanInt( index + 1 )

			pretty_path = path
			pretty_tags = ', '.join( tags )

			display_tuple = ( pretty_index, pretty_path, pretty_tags )
			sort_tuple = ( index, path, tags )

			return ( display_tuple, sort_tuple )

		def _GetTags( self, index, path ):
			filename_tagging_options = self._filename_tagging_panel.GetFilenameTaggingOptions()

			tags = filename_tagging_options.GetTags( self._service_key, path )

			tags.update( self._filename_tagging_panel.GetTags( index, path ) )

			tags = list( tags )

			tags.sort()

			return tags

		def EventItemSelected( self, event ):
			paths = [ path for ( index, path ) in self._paths_list.GetData( only_selected = True ) ]

			self._filename_tagging_panel.SetSelectedPaths( paths )

			event.Skip()

		def GetInfo( self ):
			paths_to_tags = { path : self._GetTags( index, path ) for ( index, path ) in self._paths_list.GetData() }

			return ( self._service_key, paths_to_tags )

		def RefreshFileList( self ):
			self._paths_list.UpdateDatas()

		def ScheduleRefreshFileList( self ):
			if self._schedule_refresh_file_list_job is not None:
				self._schedule_refresh_file_list_job.Cancel()

				self._schedule_refresh_file_list_job = None

			self._schedule_refresh_file_list_job = HG.client_controller.CallLaterWXSafe( self, 0.5, self.RefreshFileList )

class EditFilenameTaggingOptionPanel( EditPanel ):
	def __init__( self, parent, service_key, filename_tagging_options ):
		EditPanel.__init__( self, parent )

		self._service_key = service_key

		self._example_path_input = wx.TextCtrl( self )
		self._example_output = wx.TextCtrl( self )

		self._filename_tagging_options_panel = FilenameTaggingOptionsPanel( self, self._service_key, self.ScheduleRefreshTags, filename_tagging_options = filename_tagging_options, present_for_accompanying_file_list = False )

		self._schedule_refresh_tags_job = None

		#

		self._example_path_input.SetValue( 'enter example path here' )
		self._example_output.Disable()

		#

		vbox = wx.BoxSizer( wx.VERTICAL )

		vbox.Add( self._example_path_input, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( self._example_output, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( self._filename_tagging_options_panel, CC.FLAGS_EXPAND_BOTH_WAYS )

		self.SetSizer( vbox )

		#

		self._example_path_input.Bind( wx.EVT_TEXT, self.EventText )

	def EventText( self, event ):
		self.ScheduleRefreshTags()

	def GetValue( self ):
		return self._filename_tagging_options_panel.GetFilenameTaggingOptions()

	def RefreshTags( self ):
		example_path_input = self._example_path_input.GetValue()

		filename_tagging_options = self.GetValue()

		try:
			tags = filename_tagging_options.GetTags( self._service_key, example_path_input )

		except Exception:
			tags = [ 'could not parse' ]

		self._example_output.SetValue( ', '.join( tags ) )

	def ScheduleRefreshTags( self ):
		if self._schedule_refresh_tags_job is not None:
			self._schedule_refresh_tags_job.Cancel()

			self._schedule_refresh_tags_job = None

		self._schedule_refresh_tags_job = HG.client_controller.CallLaterWXSafe( self, 0.5, self.RefreshTags )

class GalleryImportPanel( StaticBox ):
	def __init__( self, parent, page_key, name = 'gallery query' ):
		StaticBox.__init__( self, parent, name )

		self._page_key = page_key

		self._gallery_import = None

		#

		self._query_text = wx.TextCtrl( self )
		self._query_text.SetEditable( False )

		self._import_queue_panel = StaticBox( self, 'import queue' )

		self._file_status = BetterStaticText( self._import_queue_panel, style = wx.ST_ELLIPSIZE_END )
		self._file_seed_cache_control = FileSeedCacheStatusControl( self._import_queue_panel, HG.client_controller, self._page_key )
		self._file_download_control = NetworkJobControl( self._import_queue_panel )

		self._files_pause_button = BetterBitmapButton( self._import_queue_panel, CC.GlobalBMPs.pause, self.PauseFiles )

		self._gallery_panel = StaticBox( self, 'gallery parser' )

		self._gallery_status = BetterStaticText( self._gallery_panel, style = wx.ST_ELLIPSIZE_END )

		self._gallery_pause_button = BetterBitmapButton( self._gallery_panel, CC.GlobalBMPs.pause, self.PauseGallery )

		self._gallery_seed_log_control = GallerySeedLogStatusControl( self._gallery_panel, HG.client_controller, False, True, page_key = self._page_key )

		self._gallery_download_control = NetworkJobControl( self._gallery_panel )

		self._file_limit = NoneableSpinCtrl( self, 'stop after this many files', min = 1, none_phrase = 'no limit' )
		self._file_limit.Bind( wx.EVT_SPINCTRL, self.EventFileLimit )
		self._file_limit.SetToolTip( 'stop searching the gallery once this many files has been reached' )

		file_import_options = FileImportOptions()
		tag_import_options = TagImportOptions( is_default = True )

		show_downloader_options = True

		self._file_import_options = FileImportOptionsButton( self, file_import_options, show_downloader_options, self._SetFileImportOptions )
		self._tag_import_options = TagImportOptionsButton( self, tag_import_options, show_downloader_options, update_callable = self._SetTagImportOptions, allow_default_selection = True )

		#

		hbox = wx.BoxSizer( wx.HORIZONTAL )

		hbox.Add( self._gallery_status, CC.FLAGS_VCENTER_EXPAND_DEPTH_ONLY )
		hbox.Add( self._gallery_pause_button, CC.FLAGS_VCENTER )

		self._gallery_panel.Add( hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
		self._gallery_panel.Add( self._gallery_seed_log_control, CC.FLAGS_EXPAND_PERPENDICULAR )
		self._gallery_panel.Add( self._gallery_download_control, CC.FLAGS_EXPAND_PERPENDICULAR )

		#

		hbox = wx.BoxSizer( wx.HORIZONTAL )

		hbox.Add( self._file_status, CC.FLAGS_VCENTER_EXPAND_DEPTH_ONLY )
		hbox.Add( self._files_pause_button, CC.FLAGS_VCENTER )

		self._import_queue_panel.Add( hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
		self._import_queue_panel.Add( self._file_seed_cache_control, CC.FLAGS_EXPAND_PERPENDICULAR )
		self._import_queue_panel.Add( self._file_download_control, CC.FLAGS_EXPAND_PERPENDICULAR )

		self.Add( self._query_text, CC.FLAGS_EXPAND_PERPENDICULAR )
		self.Add( self._import_queue_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
		self.Add( self._gallery_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
		self.Add( self._file_limit, CC.FLAGS_EXPAND_PERPENDICULAR )
		self.Add( self._file_import_options, CC.FLAGS_EXPAND_PERPENDICULAR )
		self.Add( self._tag_import_options, CC.FLAGS_EXPAND_PERPENDICULAR )

		#

		self._UpdateControlsForNewGalleryImport()

		HG.client_controller.gui.RegisterUIUpdateWindow( self )

	def _SetFileImportOptions( self, file_import_options ):
		if self._gallery_import is not None:
			self._gallery_import.SetFileImportOptions( file_import_options )

	def _SetTagImportOptions( self, tag_import_options ):
		if self._gallery_import is not None:
			self._gallery_import.SetTagImportOptions( tag_import_options )

	def _UpdateControlsForNewGalleryImport( self ):
		if self._gallery_import is None:
			self._import_queue_panel.Disable()
			self._gallery_panel.Disable()

			self._file_limit.Disable()
			self._file_import_options.Disable()
			self._tag_import_options.Disable()

			self._query_text.SetValue( '' )

			self._file_status.SetLabelText( '' )

			self._gallery_status.SetLabelText( '' )

			self._file_seed_cache_control.SetFileSeedCache( None )

			self._gallery_seed_log_control.SetGallerySeedLog( None )

			self._file_download_control.ClearNetworkJob()
			self._gallery_download_control.ClearNetworkJob()

		else:
			self._import_queue_panel.Enable()
			self._gallery_panel.Enable()

			self._file_limit.Enable()
			self._file_import_options.Enable()
			self._tag_import_options.Enable()

			query = self._gallery_import.GetQueryText()

			self._query_text.SetValue( query )

			file_limit = self._gallery_import.GetFileLimit()

			self._file_limit.SetValue( file_limit )

			file_import_options = self._gallery_import.GetFileImportOptions()

			self._file_import_options.SetValue( file_import_options )

			tag_import_options = self._gallery_import.GetTagImportOptions()

			self._tag_import_options.SetValue( tag_import_options )

			file_seed_cache = self._gallery_import.GetFileSeedCache()

			self._file_seed_cache_control.SetFileSeedCache( file_seed_cache )

			gallery_seed_log = self._gallery_import.GetGallerySeedLog()

			self._gallery_seed_log_control.SetGallerySeedLog( gallery_seed_log )

	def _UpdateStatus( self ):
		if self._gallery_import is not None:
			( gallery_status, file_status, files_paused, gallery_paused ) = self._gallery_import.GetStatus()

			if files_paused:
				SetBitmapButtonBitmap( self._files_pause_button, CC.GlobalBMPs.play )

			else:
				SetBitmapButtonBitmap( self._files_pause_button, CC.GlobalBMPs.pause )

			if gallery_paused:
				SetBitmapButtonBitmap( self._gallery_pause_button, CC.GlobalBMPs.play )

			else:
				SetBitmapButtonBitmap( self._gallery_pause_button, CC.GlobalBMPs.pause )

			if gallery_paused:
				if gallery_status == '':
					gallery_status = 'paused'

				else:
					gallery_status = 'paused - ' + gallery_status

			self._gallery_status.SetLabelText( gallery_status )

			if files_paused:
				if file_status == '':
					file_status = 'paused'

				else:
					file_status = 'pausing - ' + file_status

			self._file_status.SetLabelText( file_status )

			( file_network_job, gallery_network_job ) = self._gallery_import.GetNetworkJobs()

			self._file_download_control.SetNetworkJob( file_network_job )

			self._gallery_download_control.SetNetworkJob( gallery_network_job )

	def EventFileLimit( self, event ):
		if self._gallery_import is not None:
			self._gallery_import.SetFileLimit( self._file_limit.GetValue() )

		event.Skip()

	def PauseFiles( self ):
		if self._gallery_import is not None:
			self._gallery_import.PausePlayFiles()

			self._UpdateStatus()

	def PauseGallery( self ):
		if self._gallery_import is not None:
			self._gallery_import.PausePlayGallery()

			self._UpdateStatus()

	def SetGalleryImport( self, gallery_import ):
		self._gallery_import = gallery_import

		self._UpdateControlsForNewGalleryImport()

	def TIMERUIUpdate( self ):
		if HG.client_controller.gui.IShouldRegularlyUpdate( self ):
			self._UpdateStatus()

class GUGKeyAndNameSelector( BetterButton ):
	def __init__( self, parent, gug_key_and_name, update_callable = None ):
		BetterButton.__init__( self, parent, 'gallery selector', self._Edit )

		gug = HG.client_controller.network_engine.domain_manager.GetGUG( gug_key_and_name )

		if gug is not None:
			gug_key_and_name = gug.GetGUGKeyAndName()

		self._gug_key_and_name = gug_key_and_name
		self._update_callable = update_callable

		self._SetLabel()

	def _Edit( self ):
		domain_manager = HG.client_controller.network_engine.domain_manager

		# maybe relegate to hidden page and something like "( does not work )" if no gallery url class match

		my_gug = domain_manager.GetGUG( self._gug_key_and_name )

		gugs = domain_manager.GetGUGs()
		gug_keys_to_display = domain_manager.GetGUGKeysToDisplay()

		functional_gugs = []
		non_functional_gugs = []

		for gug in gugs:
			if gug.IsFunctional():
				functional_gugs.append( gug )

			else:
				non_functional_gugs.append( gug )

		choice_tuples = [ ( gug.GetName(), gug ) for gug in functional_gugs if gug.GetGUGKey() in gug_keys_to_display ]

		choice_tuples.sort()

		second_choice_tuples = [ ( gug.GetName(), gug ) for gug in functional_gugs if gug.GetGUGKey() not in gug_keys_to_display ]

		second_choice_tuples.sort()

		if len( second_choice_tuples ) > 0:
			choice_tuples.append( ( '--other galleries', -1 ) )

		if len( non_functional_gugs ) > 0:
			non_functional_choice_tuples = [ ( gug.GetName(), gug ) for gug in non_functional_gugs ]

			non_functional_choice_tuples.sort()

			choice_tuples.append( ( '--non-functional galleries', -2 ) )

		try:
			gug = SelectFromList( self, 'select gallery', choice_tuples, value_to_select = my_gug, sort_tuples = False )

		except CancelledException:
			return

		if gug == -1:
			try:
				gug = SelectFromList( self, 'select gallery', second_choice_tuples, value_to_select = my_gug, sort_tuples = False )

			except CancelledException:
				return

		elif gug == -2:
			try:
				gug = SelectFromList( self, 'select gallery', non_functional_choice_tuples, value_to_select = my_gug, sort_tuples = False )

			except CancelledException:
				return

		gug_key_and_name = gug.GetGUGKeyAndName()

		self._SetValue( gug_key_and_name )

	def _SetLabel( self ):
		label = self._gug_key_and_name[1]

		gug = HG.client_controller.network_engine.domain_manager.GetGUG( self._gug_key_and_name )

		if gug is None:
			label = 'not found: ' + label

		self.SetLabelText( label )

	def _SetValue( self, gug_key_and_name ):
		self._gug_key_and_name = gug_key_and_name

		self._SetLabel()

		if self._update_callable is not None:
			self._update_callable( gug_key_and_name )

	def GetValue( self ):
		return self._gug_key_and_name

	def SetValue( self, gug_key_and_name ):
		self._SetValue( gug_key_and_name )

class TagImportOptionsButton( BetterButton ):
	def __init__( self, parent, tag_import_options, show_downloader_options, update_callable = None, allow_default_selection = False ):
		BetterButton.__init__( self, parent, 'tag import options', self._EditOptions )

		self._tag_import_options = tag_import_options
		self._show_downloader_options = show_downloader_options
		self._update_callable = update_callable
		self._allow_default_selection = allow_default_selection

		self._SetToolTip()

		#

		self.Bind( wx.EVT_RIGHT_DOWN, self.EventShowMenu )

	def _Copy( self ):
		json_string = self._tag_import_options.DumpToString()

		HG.client_controller.pub( 'clipboard', 'text', json_string )

	def _EditOptions( self ):
		with DialogEdit( self, 'edit tag import options' ) as dlg:
			panel = EditTagImportOptionsPanel( dlg, self._tag_import_options, self._show_downloader_options, allow_default_selection = self._allow_default_selection )

			dlg.SetPanel( panel )

			if dlg.ShowModal() == wx.ID_OK:
				tag_import_options = panel.GetValue()

				self._SetValue( tag_import_options )

	def _Paste( self ):
		raw_text = HG.client_controller.GetClipboardText()

		try:
			tag_import_options = CreateFromString( raw_text )

			if not isinstance( tag_import_options, TagImportOptions ):
				raise Exception( 'Not a Tag Import Options!' )

			self._tag_import_options = tag_import_options

		except Exception as e:
			wx.MessageBox( 'I could not understand what was in the clipboard' )

			ShowException( e )

	def _SetDefault( self ):
		self._tag_import_options.SetDefault()

	def _SetToolTip( self ):
		summary = self._tag_import_options.GetSummary( self._show_downloader_options )

		self.SetToolTip( summary )

	def _SetValue( self, tag_import_options ):
		self._tag_import_options = tag_import_options

		self._SetToolTip()

		if self._update_callable is not None:
			self._update_callable( self._tag_import_options )

	def EventShowMenu( self, event ):
		menu = wx.Menu()

		AppendMenuItem( self, menu, 'copy to clipboard', 'Serialise this tag import options and copy it to clipboard.', self._Copy )

		AppendSeparator( menu )

		AppendMenuItem( self, menu, 'paste from clipboard', 'Try to import serialised tag import options from the clipboard.', self._Paste )

		if not self._tag_import_options.IsDefault():
			AppendSeparator( menu )

			AppendMenuItem( self, menu, 'set to default', 'Set this tag import options to defer to the defaults.', self._SetDefault )

		HG.client_controller.PopupMenu( self, menu )

	def GetValue( self ):
		return self._tag_import_options

	def SetNamespaces( self, namespaces ):
		self._namespaces = namespaces

	def SetValue( self, tag_import_options ):
		self._SetValue( tag_import_options )

class WatcherReviewPanel( StaticBox ):
	def __init__( self, parent, page_key, name = 'watcher' ):
		StaticBox.__init__( self, parent, name )

		self._page_key = page_key
		self._watcher = None

		self._watcher_subject = BetterStaticText( self, style = wx.ST_ELLIPSIZE_END )

		self._watcher_url = wx.TextCtrl( self )
		self._watcher_url.SetEditable( False )

		self._options_panel = wx.Panel( self )

		#

		imports_panel = StaticBox( self._options_panel, 'file imports' )

		self._files_pause_button = BetterBitmapButton( imports_panel, CC.GlobalBMPs.pause, self.PauseFiles )

		self._file_status = BetterStaticText( imports_panel, style = wx.ST_ELLIPSIZE_END )
		self._file_seed_cache_control = FileSeedCacheStatusControl( imports_panel, HG.client_controller, self._page_key )
		self._file_download_control = NetworkJobControl( imports_panel )

		#

		checker_panel = StaticBox( self._options_panel, 'checker' )

		self._file_velocity_status = BetterStaticText( checker_panel, style = wx.ST_ELLIPSIZE_END )

		self._checking_pause_button = BetterBitmapButton( checker_panel, CC.GlobalBMPs.pause, self.PauseChecking )

		self._watcher_status = BetterStaticText( checker_panel, style = wx.ST_ELLIPSIZE_END )

		self._check_now_button = wx.Button( checker_panel, label = 'check now' )
		self._check_now_button.Bind( wx.EVT_BUTTON, self.EventCheckNow )

		self._gallery_seed_log_control = GallerySeedLogStatusControl( checker_panel, HG.client_controller, True, False, page_key = self._page_key )

		checker_options = CheckerOptions()

		self._checker_options_button = CheckerOptionsButton( checker_panel, checker_options, update_callable = self._SetCheckerOptions )

		self._checker_download_control = NetworkJobControl( checker_panel )

		file_import_options = FileImportOptions()
		tag_import_options = TagImportOptions( is_default = True )

		show_downloader_options = True

		self._file_import_options = FileImportOptionsButton( self, file_import_options, show_downloader_options, self._SetFileImportOptions )
		self._tag_import_options = TagImportOptionsButton( self, tag_import_options, show_downloader_options, update_callable = self._SetTagImportOptions, allow_default_selection = True )

		#

		hbox = wx.BoxSizer( wx.HORIZONTAL )

		hbox.Add( self._file_status, CC.FLAGS_VCENTER_EXPAND_DEPTH_ONLY )
		hbox.Add( self._files_pause_button, CC.FLAGS_VCENTER )

		imports_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
		imports_panel.Add( self._file_seed_cache_control, CC.FLAGS_EXPAND_PERPENDICULAR )
		imports_panel.Add( self._file_download_control, CC.FLAGS_EXPAND_PERPENDICULAR )

		#

		hbox_1 = wx.BoxSizer( wx.HORIZONTAL )

		hbox_1.Add( self._file_velocity_status, CC.FLAGS_VCENTER_EXPAND_DEPTH_ONLY )
		hbox_1.Add( self._checking_pause_button, CC.FLAGS_VCENTER )

		hbox_2 = wx.BoxSizer( wx.HORIZONTAL )

		hbox_2.Add( self._watcher_status, CC.FLAGS_VCENTER_EXPAND_DEPTH_ONLY )
		hbox_2.Add( self._check_now_button, CC.FLAGS_VCENTER )

		checker_panel.Add( hbox_1, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
		checker_panel.Add( hbox_2, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
		checker_panel.Add( self._gallery_seed_log_control, CC.FLAGS_EXPAND_PERPENDICULAR )
		checker_panel.Add( self._checker_options_button, CC.FLAGS_EXPAND_PERPENDICULAR )
		checker_panel.Add( self._checker_download_control, CC.FLAGS_EXPAND_PERPENDICULAR )

		vbox = wx.BoxSizer( wx.VERTICAL )

		vbox.Add( imports_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( checker_panel, CC.FLAGS_EXPAND_PERPENDICULAR )

		self._options_panel.SetSizer( vbox )

		self.Add( self._watcher_subject, CC.FLAGS_EXPAND_PERPENDICULAR )
		self.Add( self._watcher_url, CC.FLAGS_EXPAND_PERPENDICULAR )
		self.Add( self._options_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
		self.Add( self._file_import_options, CC.FLAGS_EXPAND_PERPENDICULAR )
		self.Add( self._tag_import_options, CC.FLAGS_EXPAND_PERPENDICULAR )

		#

		self._UpdateControlsForNewWatcher()

		HG.client_controller.gui.RegisterUIUpdateWindow( self )

	def _SetCheckerOptions( self, checker_options ):
		if self._watcher is not None:
			self._watcher.SetCheckerOptions( checker_options )

	def _SetFileImportOptions( self, file_import_options ):
		if self._watcher is not None:
			self._watcher.SetFileImportOptions( file_import_options )

	def _SetTagImportOptions( self, tag_import_options ):
		if self._watcher is not None:
			self._watcher.SetTagImportOptions( tag_import_options )

	def _UpdateControlsForNewWatcher( self ):
		if self._watcher is None:
			self._options_panel.Disable()

			self._file_import_options.Disable()
			self._tag_import_options.Disable()

			self._watcher_subject.SetLabelText( '' )

			self._watcher_url.SetValue( '' )

			self._file_status.SetLabelText( '' )

			self._file_velocity_status.SetLabelText( '' )

			self._watcher_status.SetLabelText( '' )

			self._file_seed_cache_control.SetFileSeedCache( None )

			self._gallery_seed_log_control.SetGallerySeedLog( None )

			self._file_download_control.ClearNetworkJob()
			self._checker_download_control.ClearNetworkJob()

		else:
			self._options_panel.Enable()

			self._file_import_options.Enable()
			self._tag_import_options.Enable()

			if self._watcher.HasURL():
				url = self._watcher.GetURL()

				self._watcher_url.SetValue( url )

			else:
				self._watcher_url.SetValue( '' )

			checker_options = self._watcher.GetCheckerOptions()

			self._checker_options_button.SetValue( checker_options )

			file_import_options = self._watcher.GetFileImportOptions()

			self._file_import_options.SetValue( file_import_options )

			tag_import_options = self._watcher.GetTagImportOptions()

			self._tag_import_options.SetValue( tag_import_options )

			file_seed_cache = self._watcher.GetFileSeedCache()

			self._file_seed_cache_control.SetFileSeedCache( file_seed_cache )

			gallery_seed_log = self._watcher.GetGallerySeedLog()

			self._gallery_seed_log_control.SetGallerySeedLog( gallery_seed_log )

	def _UpdateStatus( self ):
		if self._watcher is not None:
			( file_status, files_paused, file_velocity_status, next_check_time, watcher_status, subject, checking_status, check_now, checking_paused ) = self._watcher.GetStatus()

			if files_paused:
				if file_status == '':
					file_status = 'paused'

				else:
					file_status = 'pausing, ' + file_status

				SetBitmapButtonBitmap( self._files_pause_button, CC.GlobalBMPs.play )

			else:
				SetBitmapButtonBitmap( self._files_pause_button, CC.GlobalBMPs.pause )

			self._file_status.SetLabelText( file_status )

			self._file_velocity_status.SetLabelText( file_velocity_status )

			if checking_paused:
				if watcher_status == '':
					watcher_status = 'paused'

				SetBitmapButtonBitmap( self._checking_pause_button, CC.GlobalBMPs.play )

			else:
				if watcher_status == '' and next_check_time is not None:
					if TimeHasPassed( next_check_time ):
						watcher_status = 'checking imminently'

					else:
						watcher_status = 'next check ' + TimestampToPrettyTimeDelta( next_check_time, just_now_threshold = 0 )

				SetBitmapButtonBitmap( self._checking_pause_button, CC.GlobalBMPs.pause )

			self._watcher_status.SetLabelText( watcher_status )

			if checking_status == CHECKER_STATUS_404:
				self._checking_pause_button.Disable()

			elif checking_status == CHECKER_STATUS_DEAD:
				self._checking_pause_button.Disable()

			else:
				self._checking_pause_button.Enable()

			if subject in ( '', 'unknown subject' ):
				subject = 'no subject'

			self._watcher_subject.SetLabelText( subject )

			if check_now:
				self._check_now_button.Disable()

			else:
				self._check_now_button.Enable()

			( file_network_job, checker_network_job ) = self._watcher.GetNetworkJobs()

			self._file_download_control.SetNetworkJob( file_network_job )

			self._checker_download_control.SetNetworkJob( checker_network_job )

	def EventCheckNow( self, event ):
		if self._watcher is not None:
			self._watcher.CheckNow()

			self._UpdateStatus()

	def PauseChecking( self ):
		if self._watcher is not None:
			self._watcher.PausePlayChecking()

			self._UpdateStatus()

	def PauseFiles( self ):
		if self._watcher is not None:
			self._watcher.PausePlayFiles()

			self._UpdateStatus()

	def SetWatcher( self, watcher ):
		self._watcher = watcher

		self._UpdateControlsForNewWatcher()

	def TIMERUIUpdate( self ):
		if HG.client_controller.gui.IShouldRegularlyUpdate( self ):
			self._UpdateStatus()

license: wtfpl