code:
import wx

from lib.GUI.Common import BetterStaticText, WindowOrSameTLPChildHasFocus

from lib.Hydrus.Data import ShowText
from lib.Hydrus.Data import ApplicationCommand, OrdIsAlphaUpper, \
	OrdIsSensibleASCII
from lib.Hydrus.Serialisable import CreateFromSerialisableTuple, SerialisableBase, \
	SerialisableBaseNamed

### CONSTANTS ###

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

from lib.Hydrus.Serialisable import HG.SerialisableType["SHORTCUT"], \
	HG.SerialisableType["SHORTCUTS"], SERIALISABLE_TYPES_TO_OBJECT_TYPES

FLASHWIN_OK = False

if HC.PLATFORM_WINDOWS:
	try:
		import wx.lib.flashwin

		FLASHWIN_OK = True

	except Exception:
		pass

### FUNCTIONS ###

def ConvertKeyEventToShortcut( event ):
	key = event.KeyCode

	if OrdIsSensibleASCII( key ) or key in list( CC.wxk_code_string_lookup.keys() ):
		modifiers = []

		if event.AltDown():
			modifiers.append( CC.SHORTCUT_MODIFIER_ALT )

		if event.CmdDown():
			modifiers.append( CC.SHORTCUT_MODIFIER_CTRL )

		if event.ShiftDown():
			modifiers.append( CC.SHORTCUT_MODIFIER_SHIFT )

		shortcut = Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, key, modifiers )

		if HG.gui_report_mode:
			ShowText( 'key event caught: ' + repr( shortcut ) )

		return shortcut

	return None

def ConvertKeyEventToSimpleTuple( event ):
	modifier = wx.ACCEL_NORMAL

	if event.AltDown():
		modifier = wx.ACCEL_ALT
	elif event.CmdDown():
		modifier = wx.ACCEL_CTRL
	elif event.ShiftDown():
		modifier = wx.ACCEL_SHIFT

	key = event.KeyCode

	return ( modifier, key )

def ConvertMouseEventToShortcut( event ):
	key = None

	if event.LeftDown() or event.LeftDClick():
		key = CC.SHORTCUT_MOUSE_LEFT

	elif event.MiddleDown() or event.MiddleDClick():
		key = CC.SHORTCUT_MOUSE_MIDDLE

	elif event.RightDown() or event.RightDClick():
		key = CC.SHORTCUT_MOUSE_RIGHT

	elif event.GetWheelRotation() > 0:
		key = CC.SHORTCUT_MOUSE_SCROLL_UP

	elif event.GetWheelRotation() < 0:
		key = CC.SHORTCUT_MOUSE_SCROLL_DOWN

	if key is not None:
		modifiers = []

		if event.AltDown():
			modifiers.append( CC.SHORTCUT_MODIFIER_ALT )

		if event.CmdDown():
			modifiers.append( CC.SHORTCUT_MODIFIER_CTRL )

		if event.ShiftDown():
			modifiers.append( CC.SHORTCUT_MODIFIER_SHIFT )

		shortcut = Shortcut( CC.SHORTCUT_TYPE_MOUSE, key, modifiers )

		if HG.gui_report_mode:
			ShowText( 'mouse event caught: ' + repr( shortcut ) )

		return shortcut

	return None

def IShouldCatchCharHook( evt_handler ):
	if HC.PLATFORM_WINDOWS and FLASHWIN_OK:
		window = wx.FindWindowAtPointer()

		if window is not None and isinstance( window, wx.lib.flashwin.FlashWindow ):
			return False

	if HG.client_controller.MenuIsOpen():
		return False

	if not WindowOrSameTLPChildHasFocus( evt_handler ):
		return False

	return True

class Shortcut( SerialisableBase ):
	SERIALISABLE_TYPE_NUM = HG.SerialisableType["SHORTCUT"]
	SERIALISABLE_NAME = 'Shortcut'
	SERIALISABLE_VERSION = 1

	def __init__( self, shortcut_type = None, shortcut_key = None, modifiers = None ):
		if shortcut_type is None:
			shortcut_type = CC.SHORTCUT_TYPE_KEYBOARD

		if shortcut_key is None:
			shortcut_key = wx.WXK_F7

		if modifiers is None:
			modifiers = []

		modifiers.sort()

		SerialisableBase.__init__( self )

		self._shortcut_type = shortcut_type
		self._shortcut_key = shortcut_key
		self._modifiers = modifiers

	def __eq__( self, other ):
		return self.__hash__() == other.__hash__()

	def __hash__( self ):
		return ( self._shortcut_type, self._shortcut_key, tuple( self._modifiers ) ).__hash__()

	def __repr__( self ):
		return 'Shortcut: ' + self.ToString()

	def _GetSerialisableInfo( self ):
		return ( self._shortcut_type, self._shortcut_key, self._modifiers )

	def _InitialiseFromSerialisableInfo( self, serialisable_info ):
		( self._shortcut_type, self._shortcut_key, self._modifiers ) = serialisable_info

	def GetShortcutType( self ):
		return self._shortcut_type

	def ToString( self ):
		components = []

		if CC.SHORTCUT_MODIFIER_CTRL in self._modifiers:
			components.append( 'ctrl' )

		if CC.SHORTCUT_MODIFIER_ALT in self._modifiers:
			components.append( 'alt' )

		if CC.SHORTCUT_MODIFIER_SHIFT in self._modifiers:
			components.append( 'shift' )

		if self._shortcut_type == CC.SHORTCUT_TYPE_KEYBOARD:
			if self._shortcut_key in CC.wxk_code_string_lookup:
				components.append( CC.wxk_code_string_lookup[ self._shortcut_key ] )

			elif OrdIsAlphaUpper( self._shortcut_key ):
				components.append( chr( self._shortcut_key + 32 ) ) # + 32 for converting ascii A -> a

			elif OrdIsSensibleASCII( self._shortcut_key ):
				components.append( chr( self._shortcut_key ) )

			else:
				components.append( 'unknown key' )

		elif self._shortcut_type == CC.SHORTCUT_TYPE_MOUSE:
			components.append( CC.shortcut_mouse_string_lookup[ self._shortcut_key ] )

		return '+'.join( components )

HG.LookupTables[ "SERIALISABLE_TYPES_TO_OBJECT_TYPES" ][ HG.SerialisableType["SHORTCUT"] ] = Shortcut

class ShortcutPanel( wx.Panel ):
	def __init__( self, parent ):
		wx.Panel.__init__( self, parent )

		self._mouse_radio = wx.RadioButton( self, style = wx.RB_GROUP, label = 'mouse' )
		self._mouse_shortcut = ShortcutMouse( self, self._mouse_radio )

		self._keyboard_radio = wx.RadioButton( self, label = 'keyboard' )
		self._keyboard_shortcut = ShortcutKeyboard( self, self._keyboard_radio )

		#

		vbox = wx.BoxSizer( wx.VERTICAL )

		vbox.Add( BetterStaticText( self, 'Mouse events only work for the duplicate and archive/delete filters atm!' ), CC.FLAGS_EXPAND_PERPENDICULAR )

		gridbox = wx.FlexGridSizer( 2 )

		gridbox.AddGrowableCol( 1, 1 )

		gridbox.Add( self._mouse_radio, CC.FLAGS_VCENTER )
		gridbox.Add( self._mouse_shortcut, CC.FLAGS_EXPAND_BOTH_WAYS )
		gridbox.Add( self._keyboard_radio, CC.FLAGS_VCENTER )
		gridbox.Add( self._keyboard_shortcut, CC.FLAGS_EXPAND_BOTH_WAYS )

		vbox.Add( gridbox, CC.FLAGS_EXPAND_BOTH_WAYS )

		self.SetSizer( vbox )

	def GetValue( self ):
		if self._mouse_radio.GetValue() is True:
			return self._mouse_shortcut.GetValue()

		else:
			return self._keyboard_shortcut.GetValue()

	def SetValue( self, shortcut ):
		if shortcut.GetShortcutType() == CC.SHORTCUT_TYPE_MOUSE:
			self._mouse_radio.SetValue( True )
			self._mouse_shortcut.SetValue( shortcut )

		else:
			self._keyboard_radio.SetValue( True )
			self._keyboard_shortcut.SetValue( shortcut )

class ShortcutKeyboard( wx.TextCtrl ):
	def __init__( self, parent, related_radio = None ):
		self._shortcut = Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_F7, [] )

		self._related_radio = related_radio

		wx.TextCtrl.__init__( self, parent, style = wx.TE_PROCESS_ENTER )

		self.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown )

		self._SetShortcutString()

	def _SetShortcutString( self ):
		display_string = self._shortcut.ToString()

		wx.TextCtrl.SetValue( self, display_string )

	def EventKeyDown( self, event ):
		shortcut = ConvertKeyEventToShortcut( event )

		if shortcut is not None:
			self._shortcut = shortcut

			if self._related_radio is not None:
				self._related_radio.SetValue( True )

			self._SetShortcutString()

	def GetValue( self ):
		return self._shortcut

	def SetValue( self, shortcut ):
		self._shortcut = shortcut

		self._SetShortcutString()

class ShortcutMouse( wx.Button ):
	def __init__( self, parent, related_radio = None ):
		self._shortcut = Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_LEFT, [] )

		self._related_radio = related_radio

		wx.Button.__init__( self, parent )

		self.Bind( wx.EVT_MOUSE_EVENTS, self.EventMouse )

		self._SetShortcutString()

	def _SetShortcutString( self ):
		display_string = self._shortcut.ToString()

		self.SetLabel( display_string )

	def EventMouse( self, event ):
		self.SetFocus()

		shortcut = ConvertMouseEventToShortcut( event )

		if shortcut is not None:
			self._shortcut = shortcut

			if self._related_radio is not None:
				self._related_radio.SetValue( True )

			self._SetShortcutString()

	def GetValue( self ):
		return self._shortcut

	def SetValue( self, shortcut ):
		self._shortcut = shortcut

		self._SetShortcutString()

class Shortcuts( SerialisableBaseNamed ):
	SERIALISABLE_TYPE_NUM = HG.SerialisableType["SHORTCUTS"]
	SERIALISABLE_NAME = 'Shortcuts'
	SERIALISABLE_VERSION = 2

	def __init__( self, name ):
		SerialisableBaseNamed.__init__( self, name )

		self._shortcuts_to_commands = {}

	def __iter__( self ):
		for ( shortcut, command ) in list( self._shortcuts_to_commands.items() ):
			yield ( shortcut, command )

	def __len__( self ):
		return len( self._shortcuts_to_commands )

	def _GetSerialisableInfo( self ):
		return [ ( shortcut.GetSerialisableTuple(), command.GetSerialisableTuple() ) for ( shortcut, command ) in list(self._shortcuts_to_commands.items() ) ]

	def _InitialiseFromSerialisableInfo( self, serialisable_info ):
		for ( serialisable_shortcut, serialisable_command ) in serialisable_info:
			shortcut = CreateFromSerialisableTuple( serialisable_shortcut )
			command = CreateFromSerialisableTuple( serialisable_command )

			self._shortcuts_to_commands[ shortcut ] = command

	def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
		if version == 1:
			( serialisable_mouse_actions, serialisable_keyboard_actions ) = old_serialisable_info

			shortcuts_to_commands = {}

			# this never stored mouse actions, so skip

			services_manager = HG.client_controller.services_manager

			for ( modifier, key, ( serialisable_service_key, data ) ) in serialisable_keyboard_actions:
				if modifier not in CC.shortcut_wx_to_hydrus_lookup:
					modifiers = []

				else:
					modifiers = [ CC.shortcut_wx_to_hydrus_lookup[ modifier ] ]

				shortcut = Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, key, modifiers )

				if serialisable_service_key is None:
					command = ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, data )

				else:
					service_key = bytes.fromhex( serialisable_service_key )

					if not services_manager.ServiceExists( service_key ):
						continue

					action = HC.CONTENT_UPDATE_FLIP

					value = data

					service = services_manager.GetService( service_key )

					service_type = service.GetServiceType()

					if service_type in HC.TAG_SERVICES:
						content_type = HC.CONTENT_TYPE_MAPPINGS

					elif service_type in HC.RATINGS_SERVICES:
						content_type = HC.CONTENT_TYPE_RATINGS

					else:
						continue

					command = ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( service_key, content_type, action, value ) )

				shortcuts_to_commands[ shortcut ] = command

			new_serialisable_info = ( ( shortcut.GetSerialisableTuple(), command.GetSerialisableTuple() ) for ( shortcut, command ) in list(shortcuts_to_commands.items() ) )

			return ( 2, new_serialisable_info )

	def GetCommand( self, shortcut ):
		if shortcut in self._shortcuts_to_commands:
			return self._shortcuts_to_commands[ shortcut ]

		else:
			return None

	def SetCommand( self, shortcut, command ):
		self._shortcuts_to_commands[ shortcut ] = command

HG.LookupTables[ "SERIALISABLE_TYPES_TO_OBJECT_TYPES" ][ HG.SerialisableType["SHORTCUTS"] ] = Shortcuts

class ShortcutsHandler( object ):
	def __init__( self, parent, initial_shortcuts_names = None ):
		if initial_shortcuts_names is None:
			initial_shortcuts_names = []

		self._parent = parent
		self._shortcuts_names = list( initial_shortcuts_names )

		self._parent.Bind( wx.EVT_CHAR_HOOK, self.EventCharHook )
		#self._parent.Bind( wx.EVT_MOUSE_EVENTS, self.EventMouse ) # let's not mess with this until we are doing something clever with it

	def _ProcessShortcut( self, shortcut ):
		shortcut_processed = False

		command = HG.client_controller.GetCommandFromShortcut( self._shortcuts_names, shortcut )

		if command is not None:
			command_processed = self._parent.ProcessApplicationCommand( command )

			if command_processed:
				shortcut_processed = True

			if HG.shortcut_report_mode:
				message = 'Shortcut "' + shortcut.ToString() + '" matched to command "' + command.ToString() + '" on ' + repr( self._parent ) + '.'

				if command_processed:
					message += ' It was processed.'

				else:
					message += ' It was not processed.'

				ShowText( message )

		return shortcut_processed

	def EventCharHook( self, event ):
		shortcut = ConvertKeyEventToShortcut( event )

		if shortcut is not None:
			if HG.shortcut_report_mode:
				message = 'Key shortcut "' + shortcut.ToString() + '" passing through ' + repr( self._parent ) + '.'

				if IShouldCatchCharHook( self._parent ):
					message += ' I am in a state to catch it.'

				else:
					message += ' I am not in a state to catch it.'

				ShowText( message )

			if IShouldCatchCharHook( self._parent ):
				shortcut_processed = self._ProcessShortcut( shortcut )

				if shortcut_processed:
					return

		event.Skip()

	def EventMouse( self, event ):
		shortcut = ConvertMouseEventToShortcut( event )

		if shortcut is not None:
			shortcut_processed = self._ProcessShortcut( shortcut )

			if shortcut_processed:
				return

		event.Skip()

	def AddShortcuts( self, shortcuts_name ):
		if shortcuts_name not in self._shortcuts_names:
			self._shortcuts_names.append( shortcuts_name )

	def RemoveShortcuts( self, shortcuts_name ):
		if shortcuts_name in self._shortcuts_names:
			self._shortcuts_names.remove( shortcuts_name )


code:
import wx, collections

from lib.GUI.Common import BetterButton, BetterChoice
from lib.GUI.Dialogs import DialogTextEntry
from lib.GUI.ListBoxes import ListBoxTags, ListBoxTagsPredicates, ListBoxTagsStrings
from lib.GUI.Parsing import ScriptManagementControl

from lib.Handling.Parsing import GetTagsFromParseResults

from lib.Hydrus.Data import GetNow
from lib.Hydrus.Data import GetMediasTagCount
from lib.Hydrus.Search import SortPredicates
from lib.Hydrus.Serialisable import HG.SerialisableType["PARSE_ROOT_FILE_LOOKUP"]
from lib.Hydrus.Threading import JobKey

### CONSTANTS ###

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

### CLASSES ###

class ListBoxTagsSuggestionsFavourites( ListBoxTagsStrings ):
	def __init__( self, parent, activate_callable, sort_tags = True ):
		ListBoxTagsStrings.__init__( self, parent, sort_tags = sort_tags )

		self._activate_callable = activate_callable

		width = HG.client_controller.new_options.GetInteger( 'suggested_tags_width' )

		if width is not None:
			self.SetMinSize( ( width, -1 ) )

	def _Activate( self ):
		if len( self._selected_terms ) > 0:
			tags = set( self._selected_terms )

			self._activate_callable( tags )

	def ActivateAll( self ):
		self._activate_callable( self.GetTags(), only_add = True )

class ListBoxTagsSuggestionsRelated( ListBoxTagsPredicates ):
	def __init__( self, parent, activate_callable ):
		ListBoxTags.__init__( self, parent )

		self._activate_callable = activate_callable

		width = HG.client_controller.new_options.GetInteger( 'suggested_tags_width' )

		self.SetMinSize( ( width, -1 ) )

	def _Activate( self ):
		if len( self._selected_terms ) > 0:
			tags = { predicate.GetValue() for predicate in self._selected_terms }

			self._activate_callable( tags )

	def _GetTextFromTerm( self, term ):
		predicate = term

		return predicate.ToString( with_count = False )

	def SetPredicates( self, predicates ):
		self._Clear()

		for predicate in predicates:
			self._AppendTerm( predicate )

		self._DataHasChanged()

class RecentTagsPanel( wx.Panel ):
	def __init__( self, parent, service_key, activate_callable, canvas_key = None ):
		wx.Panel.__init__( self, parent )

		self._service_key = service_key
		self._canvas_key = canvas_key

		self._new_options = HG.client_controller.new_options

		vbox = wx.BoxSizer( wx.VERTICAL )

		clear_button = wx.Button( self, label = 'clear' )
		clear_button.Bind( wx.EVT_BUTTON, self.EventClear )

		self._recent_tags = ListBoxTagsSuggestionsFavourites( self, activate_callable, sort_tags = False )

		vbox.Add( clear_button, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( self._recent_tags, CC.FLAGS_EXPAND_BOTH_WAYS )

		self.SetSizer( vbox )

		self._RefreshRecentTags()

		if self._canvas_key is not None:
			HG.client_controller.sub( self, 'CanvasHasNewMedia', 'canvas_new_display_media' )

	def _RefreshRecentTags( self ):
		def do_it( service_key ):
			def wx_code():
				if not self:
					return

				self._recent_tags.SetTags( recent_tags )

			recent_tags = HG.client_controller.Read( 'recent_tags', service_key )

			wx.CallAfter( wx_code )

		HG.client_controller.CallToThread( do_it, self._service_key )

	def CanvasHasNewMedia( self, canvas_key, new_media_singleton ):
		if canvas_key == self._canvas_key:
			self._RefreshRecentTags()

	def EventClear( self, event ):
		HG.client_controller.Write( 'push_recent_tags', self._service_key, None )

		self._RefreshRecentTags()

class RelatedTagsPanel( wx.Panel ):
	def __init__( self, parent, service_key, media, activate_callable, canvas_key = None ):
		wx.Panel.__init__( self, parent )

		self._service_key = service_key
		self._media = media
		self._canvas_key = canvas_key

		self._new_options = HG.client_controller.new_options

		vbox = wx.BoxSizer( wx.VERTICAL )

		button_2 = wx.Button( self, label = 'medium' )
		button_2.Bind( wx.EVT_BUTTON, self.EventSuggestedRelatedTags2 )
		button_2.SetMinSize( ( 30, -1 ) )

		button_3 = wx.Button( self, label = 'thorough' )
		button_3.Bind( wx.EVT_BUTTON, self.EventSuggestedRelatedTags3 )
		button_3.SetMinSize( ( 30, -1 ) )

		self._related_tags = ListBoxTagsSuggestionsRelated( self, activate_callable )

		button_hbox = wx.BoxSizer( wx.HORIZONTAL )

		button_hbox.Add( button_2, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
		button_hbox.Add( button_3, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )

		vbox.Add( button_hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( self._related_tags, CC.FLAGS_EXPAND_BOTH_WAYS )

		self.SetSizer( vbox )

		if self._canvas_key is not None:
			HG.client_controller.sub( self, 'CanvasHasNewMedia', 'canvas_new_display_media' )

		self._QuickSuggestedRelatedTags()

	def _FetchRelatedTags( self, max_time_to_take ):
		def do_it( service_key ):
			def wx_code():
				if not self:
					return

				self._related_tags.SetPredicates( predicates )

			predicates = HG.client_controller.Read( 'related_tags', service_key, hash, search_tags, max_results, max_time_to_take )

			predicates = SortPredicates( predicates )

			wx.CallAfter( wx_code )

		( m, ) = self._media

		hash = m.GetHash()

		( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = GetMediasTagCount( self._media, tag_service_key = self._service_key, collapse_siblings = False )

		tags_to_count = collections.Counter()

		tags_to_count.update( current_tags_to_count )
		tags_to_count.update( pending_tags_to_count )

		search_tags = set( tags_to_count.keys() )

		max_results = 100

		HG.client_controller.CallToThread( do_it, self._service_key )

	def _QuickSuggestedRelatedTags( self ):
		max_time_to_take = self._new_options.GetInteger( 'related_tags_search_1_duration_ms' ) / 1000.0

		self._FetchRelatedTags( max_time_to_take )

	def CanvasHasNewMedia( self, canvas_key, new_media_singleton ):
		if canvas_key == self._canvas_key:
			if new_media_singleton is not None:
				self._media = ( new_media_singleton.Duplicate(), )

				self._QuickSuggestedRelatedTags()

	def EventSuggestedRelatedTags2( self, event ):
		max_time_to_take = self._new_options.GetInteger( 'related_tags_search_2_duration_ms' ) / 1000.0

		self._FetchRelatedTags( max_time_to_take )

	def EventSuggestedRelatedTags3( self, event ):
		max_time_to_take = self._new_options.GetInteger( 'related_tags_search_3_duration_ms' ) / 1000.0

		self._FetchRelatedTags( max_time_to_take )

class FileLookupScriptTagsPanel( wx.Panel ):
	def __init__( self, parent, service_key, media, activate_callable, canvas_key = None ):
		wx.Panel.__init__( self, parent )

		self._service_key = service_key
		self._media = media
		self._canvas_key = canvas_key

		self._script_choice = BetterChoice( self )

		self._script_choice.Disable()

		self._fetch_button = BetterButton( self, 'fetch tags', self.FetchTags )

		self._fetch_button.Disable()

		self._script_management = ScriptManagementControl( self )

		self._tags = ListBoxTagsSuggestionsFavourites( self, activate_callable, sort_tags = True )

		self._add_all = BetterButton( self, 'add all', self._tags.ActivateAll )

		vbox = wx.BoxSizer( wx.VERTICAL )

		vbox.Add( self._script_choice, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( self._fetch_button, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( self._script_management, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( self._add_all, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( self._tags, CC.FLAGS_EXPAND_BOTH_WAYS )

		self._SetTags( [] )

		self.SetSizer( vbox )

		if self._canvas_key is not None:
			HG.client_controller.sub( self, 'CanvasHasNewMedia', 'canvas_new_display_media' )

		self._FetchScripts()

	def _FetchScripts( self ):
		def do_it():
			def wx_code():
				if not self:
					return

				script_names_to_scripts = { script.GetName() : script for script in scripts }

				for ( name, script ) in list( script_names_to_scripts.items() ):
					self._script_choice.Append( script.GetName(), script )

				new_options = HG.client_controller.new_options

				favourite_file_lookup_script = new_options.GetNoneableString( 'favourite_file_lookup_script' )

				if favourite_file_lookup_script in script_names_to_scripts:
					self._script_choice.SelectClientData( script_names_to_scripts[ favourite_file_lookup_script ] )

				else:
					self._script_choice.Select( 0 )

				self._script_choice.Enable()
				self._fetch_button.Enable()

			scripts = HG.client_controller.Read( 'serialisable_named', HG.SerialisableType["PARSE_ROOT_FILE_LOOKUP"] )

			wx.CallAfter( wx_code )

		HG.client_controller.CallToThread( do_it )

	def _SetTags( self, tags ):
		self._tags.SetTags( tags )

		if len( tags ) == 0:
			self._add_all.Disable()

		else:
			self._add_all.Enable()

	def CanvasHasNewMedia( self, canvas_key, new_media_singleton ):
		if canvas_key == self._canvas_key:
			if new_media_singleton is not None:
				self._media = ( new_media_singleton.Duplicate(), )

				self._SetTags( [] )

	def FetchTags( self ):
		script = self._script_choice.GetChoice()

		if script.UsesUserInput():
			message = 'Enter the custom input for the file lookup script.'

			with DialogTextEntry( self, message ) as dlg:
				if dlg.ShowModal() != wx.ID_OK:
					return

				file_identifier = dlg.GetValue()

		else:
			( m, ) = self._media

			file_identifier = script.ConvertMediaToFileIdentifier( m )

		stop_time = GetNow() + 30

		job_key = JobKey( cancellable = True, stop_time = stop_time )

		self._script_management.SetJobKey( job_key )

		HG.client_controller.CallToThread( self.THREADFetchTags, script, job_key, file_identifier )

	def THREADFetchTags( self, script, job_key, file_identifier ):
		def wx_code( tags ):
			if not self:
				return

			self._SetTags( tags )

		parse_results = script.DoQuery( job_key, file_identifier )

		tags = GetTagsFromParseResults( parse_results )

		wx.CallAfter( wx_code, tags )

class SuggestedTagsPanel( wx.Panel ):
	def __init__( self, parent, service_key, media, activate_callable, canvas_key = None ):
		wx.Panel.__init__( self, parent )

		self._service_key = service_key
		self._media = media
		self._canvas_key = canvas_key

		self._new_options = HG.client_controller.new_options

		layout_mode = self._new_options.GetNoneableString( 'suggested_tags_layout' )

		if layout_mode == 'notebook':
			notebook = wx.Notebook( self )

			panel_parent = notebook

		else:
			panel_parent = self

		panels = []

		favourites = self._new_options.GetSuggestedTagsFavourites( service_key )

		if len( favourites ) > 0:
			favourite_tags = ListBoxTagsSuggestionsFavourites( panel_parent, activate_callable )

			favourite_tags.SetTags( favourites )

			panels.append( ( 'favourites', favourite_tags ) )

		if self._new_options.GetBoolean( 'show_related_tags' ) and len( media ) == 1:
			related_tags = RelatedTagsPanel( panel_parent, service_key, media, activate_callable, canvas_key = self._canvas_key )

			panels.append( ( 'related', related_tags ) )

		if self._new_options.GetBoolean( 'show_file_lookup_script_tags' ) and len( media ) == 1:
			file_lookup_script_tags = FileLookupScriptTagsPanel( panel_parent, service_key, media, activate_callable, canvas_key = self._canvas_key )

			panels.append( ( 'file lookup scripts', file_lookup_script_tags ) )

		if self._new_options.GetNoneableInteger( 'num_recent_tags' ) is not None:
			recent_tags = RecentTagsPanel( panel_parent, service_key, activate_callable, canvas_key = self._canvas_key )

			panels.append( ( 'recent', recent_tags ) )

		if layout_mode == 'notebook':
			hbox = wx.BoxSizer( wx.HORIZONTAL )

			for ( name, panel ) in panels:
				notebook.AddPage( panel, name )

			hbox.Add( notebook, CC.FLAGS_EXPAND_BOTH_WAYS )

			self.SetSizer( hbox )

		elif layout_mode == 'columns':
			hbox = wx.BoxSizer( wx.HORIZONTAL )

			for ( name, panel ) in panels:
				hbox.Add( panel, CC.FLAGS_EXPAND_PERPENDICULAR )

			self.SetSizer( hbox )

		if len( panels ) == 0:
			self.Hide()

license: wtfpl