import os
import threading

from qtpy import QtGui as QG

from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusData
from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTags

from hydrus.client import ClientConstants as CC
from hydrus.client import ClientDefaults
from hydrus.client import ClientDuplicates
from hydrus.client import ClientGlobals as CG
from hydrus.client.importing.options import FileImportOptions

class ClientOptions( HydrusSerialisable.SerialisableBase ):
    
    SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS
    SERIALISABLE_NAME = 'Client Options'
    SERIALISABLE_VERSION = 6
    
    def __init__( self ):
        
        HydrusSerialisable.SerialisableBase.__init__( self )
        
        self._dictionary = HydrusSerialisable.SerialisableDictionary()
        
        self._lock = threading.Lock()
        
        self._InitialiseDefaults()
        
    
    def _GetDefaultMediaViewOptions( self ):
        
        media_view = HydrusSerialisable.SerialisableDictionary() # integer keys, so got to be cleverer dict
        
        # media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) )
        
        from hydrus.client.gui.canvas import ClientGUIMPV
        
        if ClientGUIMPV.MPV_IS_AVAILABLE:
            
            video_action = CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV
            audio_action = CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV
            
            video_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
            
        else:
            
            video_action = CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE
            audio_action = CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON
            
            video_zoom_info = ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, True, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
            
        
        image_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
        gif_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
        flash_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LINEAR, CC.ZOOM_LINEAR )
        
        null_zoom_info = ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, False, CC.ZOOM_LINEAR, CC.ZOOM_LINEAR )
        
        media_start_paused = False
        media_start_with_embed = False
        preview_start_paused = False
        preview_start_with_embed = False
        
        media_view[ HC.GENERAL_IMAGE ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, preview_start_paused, preview_start_with_embed, image_zoom_info )
        
        media_view[ HC.GENERAL_ANIMATION ] = ( video_action, media_start_paused, media_start_with_embed, video_action, preview_start_paused, preview_start_with_embed, gif_zoom_info )
        
        media_view[ HC.GENERAL_VIDEO ] = ( video_action, media_start_paused, media_start_with_embed, video_action, preview_start_paused, preview_start_with_embed, video_zoom_info )
        
        media_view[ HC.GENERAL_AUDIO ] = ( audio_action, media_start_paused, media_start_with_embed, audio_action, preview_start_paused, preview_start_with_embed, null_zoom_info )
        
        media_view[ HC.GENERAL_APPLICATION ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, null_zoom_info )
        
        media_view[ HC.GENERAL_APPLICATION_ARCHIVE ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, null_zoom_info )
        
        media_view[ HC.GENERAL_IMAGE_PROJECT ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, null_zoom_info )
        
        media_view[ HC.APPLICATION_PSD ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, image_zoom_info )

        media_view[ HC.APPLICATION_KRITA ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, image_zoom_info )

        return media_view
        
    
    def _GetMediaViewOptions( self, mime ):
        
        if mime in self._dictionary[ 'media_view' ]:
            
            return self._dictionary[ 'media_view' ][ mime ]
            
        else:
            
            if mime not in HC.mimes_to_general_mimetypes:
                
                null_result = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, False, False, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, False, False, ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, False, CC.ZOOM_LINEAR, CC.ZOOM_LINEAR ) )
                
                return null_result
                
            
            general_mime_type = HC.mimes_to_general_mimetypes[ mime ]
            
            if general_mime_type not in self._dictionary[ 'media_view' ]:
                
                self._dictionary[ 'media_view' ] = self._GetDefaultMediaViewOptions()
                
            
            return self._dictionary[ 'media_view' ][ general_mime_type ]
            
        
    
    def _GetSerialisableInfo( self ):
        
        serialisable_info = self._dictionary.GetSerialisableTuple()
        
        return serialisable_info
        
    
    def _InitialiseDefaults( self ):
        
        self._dictionary[ 'booleans' ] = {}
        
        self._dictionary[ 'booleans' ][ 'advanced_mode' ] = False
        
        self._dictionary[ 'booleans' ][ 'filter_inbox_and_archive_predicates' ] = False
        
        self._dictionary[ 'booleans' ][ 'discord_dnd_fix' ] = False
        self._dictionary[ 'booleans' ][ 'secret_discord_dnd_fix' ] = False
        
        self._dictionary[ 'booleans' ][ 'show_unmatched_urls_in_media_viewer' ] = False
        
        self._dictionary[ 'booleans' ][ 'set_search_focus_on_page_change' ] = False
        
        self._dictionary[ 'booleans' ][ 'allow_remove_on_manage_tags_input' ] = True
        self._dictionary[ 'booleans' ][ 'yes_no_on_remove_on_manage_tags' ] = True
        
        self._dictionary[ 'booleans' ][ 'activate_window_on_tag_search_page_activation' ] = False
        
        self._dictionary[ 'booleans' ][ 'show_related_tags' ] = True
        self._dictionary[ 'booleans' ][ 'show_file_lookup_script_tags' ] = False
        
        self._dictionary[ 'booleans' ][ 'use_native_menubar' ] = HC.PLATFORM_MACOS
        
        self._dictionary[ 'booleans' ][ 'shortcuts_merge_non_number_numpad' ] = True
        
        self._dictionary[ 'booleans' ][ 'disable_get_safe_position_test' ] = False
        
        self._dictionary[ 'booleans' ][ 'freeze_message_manager_when_mouse_on_other_monitor' ] = False
        self._dictionary[ 'booleans' ][ 'freeze_message_manager_when_main_gui_minimised' ] = False
        
        self._dictionary[ 'booleans' ][ 'load_images_with_pil' ] = False
        
        self._dictionary[ 'booleans' ][ 'use_system_ffmpeg' ] = False
        
        self._dictionary[ 'booleans' ][ 'elide_page_tab_names' ] = True
        
        self._dictionary[ 'booleans' ][ 'maintain_similar_files_duplicate_pairs_during_idle' ] = False
        
        self._dictionary[ 'booleans' ][ 'show_namespaces' ] = True
        self._dictionary[ 'booleans' ][ 'show_number_namespaces' ] = True
        self._dictionary[ 'booleans' ][ 'show_subtag_number_namespaces' ] = True
        self._dictionary[ 'booleans' ][ 'replace_tag_underscores_with_spaces' ] = False
        
        self._dictionary[ 'booleans' ][ 'verify_regular_https' ] = True
        
        self._dictionary[ 'booleans' ][ 'page_drop_chase_normally' ] = True
        self._dictionary[ 'booleans' ][ 'page_drop_chase_with_shift' ] = False
        self._dictionary[ 'booleans' ][ 'page_drag_change_tab_normally' ] = True
        self._dictionary[ 'booleans' ][ 'page_drag_change_tab_with_shift' ] = True
        self._dictionary[ 'booleans' ][ 'wheel_scrolls_tab_bar' ] = False
        
        self._dictionary[ 'booleans' ][ 'remove_local_domain_moved_files' ] = False
        
        self._dictionary[ 'booleans' ][ 'anchor_and_hide_canvas_drags' ] = HC.PLATFORM_WINDOWS
        self._dictionary[ 'booleans' ][ 'touchscreen_canvas_drags_unanchor' ] = False
        
        self._dictionary[ 'booleans' ][ 'import_page_progress_display' ] = True
        
        self._dictionary[ 'booleans' ][ 'process_subs_in_random_order' ] = True
        
        self._dictionary[ 'booleans' ][ 'ac_select_first_with_count' ] = False
        
        self._dictionary[ 'booleans' ][ 'saving_sash_positions_on_exit' ] = True
        
        self._dictionary[ 'booleans' ][ 'database_deferred_delete_maintenance_during_idle' ] = True
        self._dictionary[ 'booleans' ][ 'database_deferred_delete_maintenance_during_active' ] = True
        
        self._dictionary[ 'booleans' ][ 'file_maintenance_during_idle' ] = True
        self._dictionary[ 'booleans' ][ 'file_maintenance_during_active' ] = True
        
        self._dictionary[ 'booleans' ][ 'tag_display_maintenance_during_idle' ] = True
        self._dictionary[ 'booleans' ][ 'tag_display_maintenance_during_active' ] = True
        
        self._dictionary[ 'booleans' ][ 'save_page_sort_on_change' ] = False
        self._dictionary[ 'booleans' ][ 'disable_page_tab_dnd' ] = False
        self._dictionary[ 'booleans' ][ 'force_hide_page_signal_on_new_page' ] = False
        
        self._dictionary[ 'booleans' ][ 'pause_export_folders_sync' ] = False
        self._dictionary[ 'booleans' ][ 'pause_import_folders_sync' ] = False
        self._dictionary[ 'booleans' ][ 'pause_repo_sync' ] = False
        self._dictionary[ 'booleans' ][ 'pause_subs_sync' ] = False
        
        self._dictionary[ 'booleans' ][ 'pause_all_new_network_traffic' ] = False
        self._dictionary[ 'booleans' ][ 'boot_with_network_traffic_paused' ] = False
        self._dictionary[ 'booleans' ][ 'pause_all_file_queues' ] = False
        self._dictionary[ 'booleans' ][ 'pause_all_watcher_checkers' ] = False
        self._dictionary[ 'booleans' ][ 'pause_all_gallery_searches' ] = False
        
        self._dictionary[ 'booleans' ][ 'popup_message_force_min_width' ] = False
        
        self._dictionary[ 'booleans' ][ 'always_show_iso_time' ] = False
        
        self._dictionary[ 'booleans' ][ 'confirm_multiple_local_file_services_move' ] = True
        self._dictionary[ 'booleans' ][ 'confirm_multiple_local_file_services_copy' ] = True
        
        self._dictionary[ 'booleans' ][ 'use_advanced_file_deletion_dialog' ] = False
        
        self._dictionary[ 'booleans' ][ 'show_new_on_file_seed_short_summary' ] = False
        self._dictionary[ 'booleans' ][ 'show_deleted_on_file_seed_short_summary' ] = False
        
        self._dictionary[ 'booleans' ][ 'only_save_last_session_during_idle' ] = False
        
        self._dictionary[ 'booleans' ][ 'do_human_sort_on_hdd_file_import_paths' ] = True
        
        self._dictionary[ 'booleans' ][ 'highlight_new_watcher' ] = True
        self._dictionary[ 'booleans' ][ 'highlight_new_query' ] = True
        
        self._dictionary[ 'booleans' ][ 'delete_files_after_export' ] = False
        
        self._dictionary[ 'booleans' ][ 'file_viewing_statistics_active' ] = True
        self._dictionary[ 'booleans' ][ 'file_viewing_statistics_active_on_archive_delete_filter' ] = True
        self._dictionary[ 'booleans' ][ 'file_viewing_statistics_active_on_dupe_filter' ] = False
        
        self._dictionary[ 'booleans' ][ 'prefix_hash_when_copying' ] = False
        self._dictionary[ 'booleans' ][ 'file_system_waits_on_wakeup' ] = False
        
        self._dictionary[ 'booleans' ][ 'always_show_system_everything' ] = False
        
        self._dictionary[ 'booleans' ][ 'watch_clipboard_for_watcher_urls' ] = False
        self._dictionary[ 'booleans' ][ 'watch_clipboard_for_other_recognised_urls' ] = False
        
        self._dictionary[ 'booleans' ][ 'default_search_synchronised' ] = True
        self._dictionary[ 'booleans' ][ 'autocomplete_float_main_gui' ] = True
        
        self._dictionary[ 'booleans' ][ 'global_audio_mute' ] = False
        self._dictionary[ 'booleans' ][ 'media_viewer_audio_mute' ] = False
        self._dictionary[ 'booleans' ][ 'media_viewer_uses_its_own_audio_volume' ] = False
        self._dictionary[ 'booleans' ][ 'preview_audio_mute' ] = False
        self._dictionary[ 'booleans' ][ 'preview_uses_its_own_audio_volume' ] = True
        
        self._dictionary[ 'booleans' ][ 'always_loop_gifs' ] = True
        
        self._dictionary[ 'booleans' ][ 'always_show_system_tray_icon' ] = False
        self._dictionary[ 'booleans' ][ 'minimise_client_to_system_tray' ] = False
        self._dictionary[ 'booleans' ][ 'close_client_to_system_tray' ] = False
        self._dictionary[ 'booleans' ][ 'start_client_in_system_tray' ] = False
        
        self._dictionary[ 'booleans' ][ 'use_qt_file_dialogs' ] = False
        
        self._dictionary[ 'booleans' ][ 'notify_client_api_cookies' ] = False
        
        self._dictionary[ 'booleans' ][ 'expand_parents_on_storage_taglists' ] = True
        self._dictionary[ 'booleans' ][ 'expand_parents_on_storage_autocomplete_taglists' ] = True
        
        self._dictionary[ 'booleans' ][ 'show_parent_decorators_on_storage_taglists' ] = True
        self._dictionary[ 'booleans' ][ 'show_parent_decorators_on_storage_autocomplete_taglists' ] = True
        
        self._dictionary[ 'booleans' ][ 'show_sibling_decorators_on_storage_taglists' ] = True
        self._dictionary[ 'booleans' ][ 'show_sibling_decorators_on_storage_autocomplete_taglists' ] = True
        
        self._dictionary[ 'booleans' ][ 'show_session_size_warnings' ] = True
        
        self._dictionary[ 'booleans' ][ 'delete_lock_for_archived_files' ] = False
        
        self._dictionary[ 'booleans' ][ 'remember_last_advanced_file_deletion_reason' ] = True
        self._dictionary[ 'booleans' ][ 'remember_last_advanced_file_deletion_special_action' ] = False
        
        self._dictionary[ 'booleans' ][ 'do_macos_debug_dialog_menus' ] = False
        
        self._dictionary[ 'booleans' ][ 'save_default_tag_service_tab_on_change' ] = True
        
        self._dictionary[ 'booleans' ][ 'force_animation_scanbar_show' ] = False
        
        self._dictionary[ 'booleans' ][ 'call_mouse_buttons_primary_secondary' ] = False
        
        self._dictionary[ 'booleans' ][ 'start_note_editing_at_end' ] = True
        
        self._dictionary[ 'booleans' ][ 'draw_transparency_checkerboard_media_canvas' ] = False
        self._dictionary[ 'booleans' ][ 'draw_transparency_checkerboard_media_canvas_duplicates' ] = True
        
        self._dictionary[ 'booleans' ][ 'menu_choice_buttons_can_mouse_scroll' ] = True
        
        self._dictionary[ 'booleans' ][ 'focus_preview_on_ctrl_click' ] = False
        self._dictionary[ 'booleans' ][ 'focus_preview_on_ctrl_click_only_static' ] = False
        self._dictionary[ 'booleans' ][ 'focus_preview_on_shift_click' ] = False
        self._dictionary[ 'booleans' ][ 'focus_preview_on_shift_click_only_static' ] = False
        
        self._dictionary[ 'booleans' ][ 'fade_sibling_connector' ] = True
        self._dictionary[ 'booleans' ][ 'use_custom_sibling_connector_colour' ] = False
        
        self._dictionary[ 'booleans' ][ 'hide_uninteresting_local_import_time' ] = True
        self._dictionary[ 'booleans' ][ 'hide_uninteresting_modified_time' ] = True
        
        self._dictionary[ 'booleans' ][ 'allow_blurhash_fallback' ] = True
        
        self._dictionary[ 'booleans' ][ 'fade_thumbnails' ] = True
        
        self._dictionary[ 'booleans' ][ 'slideshow_always_play_duration_media_once_through' ] = False
        
        from hydrus.client.gui.canvas import ClientGUIMPV
        
        self._dictionary[ 'booleans' ][ 'mpv_available_at_start' ] = ClientGUIMPV.MPV_IS_AVAILABLE
        
        #
        
        self._dictionary[ 'colours' ] = HydrusSerialisable.SerialisableDictionary()
        
        self._dictionary[ 'colours' ][ 'default' ] = HydrusSerialisable.SerialisableDictionary()
        
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BACKGROUND ] = ( 255, 255, 255 )
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BACKGROUND_SELECTED ] = ( 217, 242, 255 ) # light blue
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BACKGROUND_REMOTE ] = ( 32, 32, 36 ) # 50% Payne's Gray
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BACKGROUND_REMOTE_SELECTED ] = ( 64, 64, 72 ) # Payne's Gray
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BORDER ] = ( 223, 227, 230 ) # light grey
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BORDER_SELECTED ] = ( 1, 17, 26 ) # dark grey
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BORDER_REMOTE ] = ( 248, 208, 204 ) # 25% Vermillion, 75% White
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMB_BORDER_REMOTE_SELECTED ] = ( 227, 66, 52 ) # Vermillion, lol
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_THUMBGRID_BACKGROUND ] = ( 255, 255, 255 )
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_AUTOCOMPLETE_BACKGROUND ] = ( 235, 248, 255 ) # very light blue
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_MEDIA_BACKGROUND ] = ( 255, 255, 255 )
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_MEDIA_TEXT ] = ( 0, 0, 0 )
        self._dictionary[ 'colours' ][ 'default' ][ CC.COLOUR_TAGS_BOX ] = ( 255, 255, 255 )
        
        self._dictionary[ 'colours' ][ 'darkmode' ] = HydrusSerialisable.SerialisableDictionary()
        
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BACKGROUND ] = ( 64, 64, 72 ) # Payne's Gray
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BACKGROUND_SELECTED ] = ( 112, 128, 144 ) # Slate Gray
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BACKGROUND_REMOTE ] = ( 64, 13, 2 ) # Black Bean
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BACKGROUND_REMOTE_SELECTED ] = ( 171, 39, 79 ) # Amaranth Purple
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BORDER ] = ( 145, 163, 176 ) # Cadet Grey
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BORDER_SELECTED ] = ( 223, 227, 230 ) # light grey
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BORDER_REMOTE ] = ( 248, 208, 204 ) # 25% Vermillion, 75% White
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMB_BORDER_REMOTE_SELECTED ] = ( 227, 66, 52 ) # Vermillion, lol
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_THUMBGRID_BACKGROUND ] = ( 52, 52, 52 ) # 20% flat gray
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_AUTOCOMPLETE_BACKGROUND ] = ( 83, 98, 103 ) # Gunmetal
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_MEDIA_BACKGROUND ] = ( 52, 52, 52 ) # 20% flat gray
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_MEDIA_TEXT ] = ( 112, 128, 144 ) # Slate Gray
        self._dictionary[ 'colours' ][ 'darkmode' ][ CC.COLOUR_TAGS_BOX ] = ( 35, 38, 41 )
        
        #
        
        self._dictionary[ 'recent_petition_reasons' ] = HydrusSerialisable.SerialisableDictionary()
        
        #
        
        self._dictionary[ 'duplicate_action_options' ] = HydrusSerialisable.SerialisableDictionary()
        
        duplicate_content_merge_options = ClientDuplicates.DuplicateContentMergeOptions()
        
        duplicate_content_merge_options.SetTagServiceActions( [ ( CC.DEFAULT_LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE, HydrusTags.TagFilter() ), ( CC.DEFAULT_LOCAL_DOWNLOADER_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE, HydrusTags.TagFilter() ) ] )
        duplicate_content_merge_options.SetRatingServiceActions( [ ( CC.DEFAULT_FAVOURITES_RATING_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE ) ] )
        duplicate_content_merge_options.SetSyncArchiveAction( ClientDuplicates.SYNC_ARCHIVE_DO_BOTH_REGARDLESS )
        duplicate_content_merge_options.SetSyncURLsAction( HC.CONTENT_MERGE_ACTION_COPY )
        duplicate_content_merge_options.SetSyncFileModifiedDateAction( HC.CONTENT_MERGE_ACTION_COPY )
        duplicate_content_merge_options.SetSyncNotesAction( HC.CONTENT_MERGE_ACTION_COPY )
        
        from hydrus.client.importing.options import NoteImportOptions
        
        note_import_options = NoteImportOptions.NoteImportOptions()
        
        note_import_options.SetIsDefault( False )
        note_import_options.SetGetNotes( True )
        note_import_options.SetExtendExistingNoteIfPossible( True )
        note_import_options.SetConflictResolution( NoteImportOptions.NOTE_IMPORT_CONFLICT_RENAME )
        
        duplicate_content_merge_options.SetSyncNoteImportOptions( note_import_options )
        
        self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_BETTER ] = duplicate_content_merge_options
        
        duplicate_content_merge_options = ClientDuplicates.DuplicateContentMergeOptions()
        
        duplicate_content_merge_options.SetTagServiceActions( [ ( CC.DEFAULT_LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE, HydrusTags.TagFilter() ), ( CC.DEFAULT_LOCAL_DOWNLOADER_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE, HydrusTags.TagFilter() ) ] )
        duplicate_content_merge_options.SetRatingServiceActions( [ ( CC.DEFAULT_FAVOURITES_RATING_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE ) ] )
        duplicate_content_merge_options.SetSyncArchiveAction( ClientDuplicates.SYNC_ARCHIVE_DO_BOTH_REGARDLESS )
        duplicate_content_merge_options.SetSyncURLsAction( HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE )
        duplicate_content_merge_options.SetSyncFileModifiedDateAction( HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE )
        duplicate_content_merge_options.SetSyncNotesAction( HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE )
        
        note_import_options = NoteImportOptions.NoteImportOptions()
        
        note_import_options.SetIsDefault( False )
        note_import_options.SetGetNotes( True )
        note_import_options.SetExtendExistingNoteIfPossible( True )
        note_import_options.SetConflictResolution( NoteImportOptions.NOTE_IMPORT_CONFLICT_RENAME )
        
        duplicate_content_merge_options.SetSyncNoteImportOptions( note_import_options )
        
        self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_SAME_QUALITY ] = duplicate_content_merge_options
        
        duplicate_content_merge_options = ClientDuplicates.DuplicateContentMergeOptions()
        
        self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_ALTERNATE ] = duplicate_content_merge_options
        
        #
        
        self._dictionary[ 'integers' ] = {}
        
        self._dictionary[ 'integers' ][ 'notebook_tab_alignment' ] = CC.DIRECTION_UP
        
        self._dictionary[ 'integers' ][ 'video_buffer_size' ] = 96 * 1024 * 1024
        
        self._dictionary[ 'integers' ][ 'related_tags_search_1_duration_ms' ] = 250
        self._dictionary[ 'integers' ][ 'related_tags_search_2_duration_ms' ] = 2000
        self._dictionary[ 'integers' ][ 'related_tags_search_3_duration_ms' ] = 6000
        self._dictionary[ 'integers' ][ 'related_tags_concurrence_threshold_percent' ] = 6
        
        self._dictionary[ 'integers' ][ 'suggested_tags_width' ] = 300
        
        self._dictionary[ 'integers' ][ 'similar_files_duplicate_pairs_search_distance' ] = 0
        
        self._dictionary[ 'integers' ][ 'default_new_page_goes' ] = CC.NEW_PAGE_GOES_FAR_RIGHT
        
        self._dictionary[ 'integers' ][ 'num_recent_petition_reasons' ] = 5
        
        self._dictionary[ 'integers' ][ 'max_page_name_chars' ] = 20
        self._dictionary[ 'integers' ][ 'page_file_count_display' ] = CC.PAGE_FILE_COUNT_DISPLAY_ALL
        
        self._dictionary[ 'integers' ][ 'network_timeout' ] = 10
        self._dictionary[ 'integers' ][ 'connection_error_wait_time' ] = 15
        self._dictionary[ 'integers' ][ 'serverside_bandwidth_wait_time' ] = 60
        
        self._dictionary[ 'integers' ][ 'thumbnail_visibility_scroll_percent' ] = 75
        self._dictionary[ 'integers' ][ 'ideal_tile_dimension' ] = 768
        
        self._dictionary[ 'integers' ][ 'total_pages_warning' ] = 165
        
        self._dictionary[ 'integers' ][ 'wake_delay_period' ] = 15
        
        from hydrus.client.gui.canvas import ClientGUICanvasMedia
        
        self._dictionary[ 'integers' ][ 'media_viewer_zoom_center' ] = ClientGUICanvasMedia.ZOOM_CENTERPOINT_MOUSE
        
        self._dictionary[ 'integers' ][ 'last_session_save_period_minutes' ] = 5
        
        self._dictionary[ 'integers' ][ 'shutdown_work_period' ] = 86400
        
        self._dictionary[ 'integers' ][ 'max_network_jobs' ] = 15
        self._dictionary[ 'integers' ][ 'max_network_jobs_per_domain' ] = 3
        
        self._dictionary[ 'integers' ][ 'max_connection_attempts_allowed' ] = 5
        self._dictionary[ 'integers' ][ 'max_request_attempts_allowed_get' ] = 5
        
        from hydrus.core.files.images import HydrusImageHandling
        
        self._dictionary[ 'integers' ][ 'thumbnail_scale_type' ] = HydrusImageHandling.THUMBNAIL_SCALE_DOWN_ONLY
        
        self._dictionary[ 'integers' ][ 'max_simultaneous_subscriptions' ] = 1
        
        self._dictionary[ 'integers' ][ 'gallery_page_wait_period_pages' ] = 15
        self._dictionary[ 'integers' ][ 'gallery_page_wait_period_subscriptions' ] = 5
        self._dictionary[ 'integers' ][ 'watcher_page_wait_period' ] = 5
        
        self._dictionary[ 'integers' ][ 'popup_message_character_width' ] = 56
        
        self._dictionary[ 'integers' ][ 'duplicate_filter_max_batch_size' ] = 250
        
        self._dictionary[ 'integers' ][ 'video_thumbnail_percentage_in' ] = 35
        
        self._dictionary[ 'integers' ][ 'global_audio_volume' ] = 70
        self._dictionary[ 'integers' ][ 'media_viewer_audio_volume' ] = 70
        self._dictionary[ 'integers' ][ 'preview_audio_volume' ] = 70
        
        self._dictionary[ 'integers' ][ 'duplicate_comparison_score_higher_jpeg_quality' ] = 10
        self._dictionary[ 'integers' ][ 'duplicate_comparison_score_much_higher_jpeg_quality' ] = 20
        self._dictionary[ 'integers' ][ 'duplicate_comparison_score_higher_filesize' ] = 10
        self._dictionary[ 'integers' ][ 'duplicate_comparison_score_much_higher_filesize' ] = 20
        self._dictionary[ 'integers' ][ 'duplicate_comparison_score_higher_resolution' ] = 20
        self._dictionary[ 'integers' ][ 'duplicate_comparison_score_much_higher_resolution' ] = 50
        self._dictionary[ 'integers' ][ 'duplicate_comparison_score_more_tags' ] = 8
        self._dictionary[ 'integers' ][ 'duplicate_comparison_score_older' ] = 4
        self._dictionary[ 'integers' ][ 'duplicate_comparison_score_nicer_ratio' ] = 10
        self._dictionary[ 'integers' ][ 'duplicate_comparison_score_has_audio' ] = 20
        
        self._dictionary[ 'integers' ][ 'thumbnail_cache_size' ] = 1024 * 1024 * 32
        self._dictionary[ 'integers' ][ 'image_cache_size' ] = 1024 * 1024 * 384
        self._dictionary[ 'integers' ][ 'image_tile_cache_size' ] = 1024 * 1024 * 256
        
        self._dictionary[ 'integers' ][ 'thumbnail_cache_timeout' ] = 86400
        self._dictionary[ 'integers' ][ 'image_cache_timeout' ] = 600
        self._dictionary[ 'integers' ][ 'image_tile_cache_timeout' ] = 300
        
        self._dictionary[ 'integers' ][ 'image_cache_storage_limit_percentage' ] = 25
        self._dictionary[ 'integers' ][ 'image_cache_prefetch_limit_percentage' ] = 10
        
        self._dictionary[ 'integers' ][ 'media_viewer_prefetch_delay_base_ms' ] = 100
        self._dictionary[ 'integers' ][ 'media_viewer_prefetch_num_previous' ] = 2
        self._dictionary[ 'integers' ][ 'media_viewer_prefetch_num_next' ] = 3
        
        self._dictionary[ 'integers' ][ 'thumbnail_border' ] = 1
        self._dictionary[ 'integers' ][ 'thumbnail_margin' ] = 2
        
        self._dictionary[ 'integers' ][ 'thumbnail_dpr_percent' ] = 100
        
        self._dictionary[ 'integers' ][ 'file_maintenance_idle_throttle_files' ] = 1
        self._dictionary[ 'integers' ][ 'file_maintenance_idle_throttle_time_delta' ] = 2
        
        self._dictionary[ 'integers' ][ 'file_maintenance_active_throttle_files' ] = 1
        self._dictionary[ 'integers' ][ 'file_maintenance_active_throttle_time_delta' ] = 20
        
        self._dictionary[ 'integers' ][ 'subscription_network_error_delay' ] = 12 * 3600
        self._dictionary[ 'integers' ][ 'subscription_other_error_delay' ] = 36 * 3600
        self._dictionary[ 'integers' ][ 'downloader_network_error_delay' ] = 90 * 60
        
        self._dictionary[ 'integers' ][ 'file_viewing_stats_menu_display' ] = CC.FILE_VIEWING_STATS_MENU_DISPLAY_MEDIA_AND_PREVIEW_IN_SUBMENU
        
        self._dictionary[ 'integers' ][ 'number_of_gui_session_backups' ] = 10
        
        self._dictionary[ 'integers' ][ 'animated_scanbar_height' ] = 20
        self._dictionary[ 'integers' ][ 'animated_scanbar_nub_width' ] = 10
        
        self._dictionary[ 'integers' ][ 'domain_network_infrastructure_error_number' ] = 3
        self._dictionary[ 'integers' ][ 'domain_network_infrastructure_error_time_delta' ] = 600
        
        self._dictionary[ 'integers' ][ 'ac_read_list_height_num_chars' ] = 21
        self._dictionary[ 'integers' ][ 'ac_write_list_height_num_chars' ] = 11
        
        self._dictionary[ 'integers' ][ 'system_busy_cpu_percent' ] = 50
        
        self._dictionary[ 'integers' ][ 'human_bytes_sig_figs' ] = 3
        
        self._dictionary[ 'integers' ][ 'ms_to_wait_between_physical_file_deletes' ] = 250
        
        self._dictionary[ 'integers' ][ 'potential_duplicates_search_work_time_ms' ] = 500
        self._dictionary[ 'integers' ][ 'potential_duplicates_search_rest_percentage' ] = 100
        
        self._dictionary[ 'integers' ][ 'repository_processing_work_time_ms_very_idle' ] = 30000
        self._dictionary[ 'integers' ][ 'repository_processing_rest_percentage_very_idle' ] = 3
        
        self._dictionary[ 'integers' ][ 'repository_processing_work_time_ms_idle' ] = 10000
        self._dictionary[ 'integers' ][ 'repository_processing_rest_percentage_idle' ] = 5
        
        self._dictionary[ 'integers' ][ 'repository_processing_work_time_ms_normal' ] = 500
        self._dictionary[ 'integers' ][ 'repository_processing_rest_percentage_normal' ] = 10
        
        self._dictionary[ 'integers' ][ 'tag_display_processing_work_time_ms_idle' ] = 15000
        self._dictionary[ 'integers' ][ 'tag_display_processing_rest_percentage_idle' ] = 3
        
        self._dictionary[ 'integers' ][ 'tag_display_processing_work_time_ms_normal' ] = 100
        self._dictionary[ 'integers' ][ 'tag_display_processing_rest_percentage_normal' ] = 9900
        
        self._dictionary[ 'integers' ][ 'tag_display_processing_work_time_ms_work_hard' ] = 5000
        self._dictionary[ 'integers' ][ 'tag_display_processing_rest_percentage_work_hard' ] = 5
        
        self._dictionary[ 'integers' ][ 'deferred_table_delete_work_time_ms_idle' ] = 20000
        self._dictionary[ 'integers' ][ 'deferred_table_delete_rest_percentage_idle' ] = 10
        
        self._dictionary[ 'integers' ][ 'deferred_table_delete_work_time_ms_normal' ] = 250
        self._dictionary[ 'integers' ][ 'deferred_table_delete_rest_percentage_normal' ] = 1000
        
        self._dictionary[ 'integers' ][ 'deferred_table_delete_work_time_ms_work_hard' ] = 5000
        self._dictionary[ 'integers' ][ 'deferred_table_delete_rest_percentage_work_hard' ] = 10
        
        #
        
        self._dictionary[ 'keys' ] = {}
        
        self._dictionary[ 'keys' ][ 'default_tag_service_tab' ] = CC.DEFAULT_LOCAL_TAG_SERVICE_KEY.hex()
        self._dictionary[ 'keys' ][ 'default_tag_service_search_page' ] = CC.COMBINED_TAG_SERVICE_KEY.hex()
        self._dictionary[ 'keys' ][ 'default_gug_key' ] = HydrusData.GenerateKey().hex()
        
        self._dictionary[ 'key_list' ] = {}
        
        #
        
        self._dictionary[ 'noneable_integers' ] = {}
        
        self._dictionary[ 'noneable_integers' ][ 'forced_search_limit' ] = None
        
        self._dictionary[ 'noneable_integers' ][ 'num_recent_tags' ] = 20
        
        self._dictionary[ 'noneable_integers' ][ 'duplicate_background_switch_intensity_a' ] = 0
        self._dictionary[ 'noneable_integers' ][ 'duplicate_background_switch_intensity_b' ] = 3
        
        self._dictionary[ 'noneable_integers' ][ 'last_review_bandwidth_search_distance' ] = 7 * 86400
        
        self._dictionary[ 'noneable_integers' ][ 'file_viewing_statistics_media_min_time' ] = 2
        self._dictionary[ 'noneable_integers' ][ 'file_viewing_statistics_media_max_time' ] = 600
        self._dictionary[ 'noneable_integers' ][ 'file_viewing_statistics_preview_min_time' ] = 5
        self._dictionary[ 'noneable_integers' ][ 'file_viewing_statistics_preview_max_time' ] = 60
        
        self._dictionary[ 'noneable_integers' ][ 'subscription_file_error_cancel_threshold' ] = 5
        
        self._dictionary[ 'noneable_integers' ][ 'media_viewer_cursor_autohide_time_ms' ] = 700
        
        self._dictionary[ 'noneable_integers' ][ 'idle_mode_client_api_timeout' ] = None
        
        self._dictionary[ 'noneable_integers' ][ 'system_busy_cpu_count' ] = 1
        
        self._dictionary[ 'noneable_integers' ][ 'animated_scanbar_hide_height' ] = 5
        
        self._dictionary[ 'noneable_integers' ][ 'last_backup_time' ] = None
        
        self._dictionary[ 'noneable_integers' ][ 'slideshow_short_duration_loop_percentage' ] = 20
        self._dictionary[ 'noneable_integers' ][ 'slideshow_short_duration_loop_seconds' ] = 10
        
        self._dictionary[ 'noneable_integers' ][ 'slideshow_short_duration_cutoff_percentage' ] = 75
        
        self._dictionary[ 'noneable_integers' ][ 'slideshow_long_duration_overspill_percentage' ] = 50
        
        #
        
        self._dictionary[ 'simple_downloader_formulae' ] = HydrusSerialisable.SerialisableList()
        
        #
        
        self._dictionary[ 'noneable_strings' ] = {}
        
        self._dictionary[ 'noneable_strings' ][ 'favourite_file_lookup_script' ] = 'gelbooru md5'
        self._dictionary[ 'noneable_strings' ][ 'suggested_tags_layout' ] = 'notebook'
        self._dictionary[ 'noneable_strings' ][ 'backup_path' ] = None
        self._dictionary[ 'noneable_strings' ][ 'web_browser_path' ] = None
        self._dictionary[ 'noneable_strings' ][ 'last_png_export_dir' ] = None
        self._dictionary[ 'noneable_strings' ][ 'media_background_bmp_path' ] = None
        self._dictionary[ 'noneable_strings' ][ 'http_proxy' ] = None
        self._dictionary[ 'noneable_strings' ][ 'https_proxy' ] = None
        self._dictionary[ 'noneable_strings' ][ 'no_proxy' ] = '127.0.0.1'
        self._dictionary[ 'noneable_strings' ][ 'qt_style_name' ] = None
        self._dictionary[ 'noneable_strings' ][ 'qt_stylesheet_name' ] = None
        self._dictionary[ 'noneable_strings' ][ 'last_advanced_file_deletion_reason' ] = None
        self._dictionary[ 'noneable_strings' ][ 'last_advanced_file_deletion_special_action' ] = None
        self._dictionary[ 'noneable_strings' ][ 'sibling_connector_custom_namespace_colour' ] = 'system'
        self._dictionary[ 'noneable_strings' ][ 'or_connector_custom_namespace_colour' ] = 'system'
        
        self._dictionary[ 'strings' ] = {}
        
        self._dictionary[ 'strings' ][ 'app_display_name' ] = 'hydrus client'
        self._dictionary[ 'strings' ][ 'namespace_connector' ] = ':'
        self._dictionary[ 'strings' ][ 'sibling_connector' ] = ' \u2192 '
        self._dictionary[ 'strings' ][ 'or_connector' ] = ' OR '
        self._dictionary[ 'strings' ][ 'export_phrase' ] = '{hash}'
        self._dictionary[ 'strings' ][ 'current_colourset' ] = 'default'
        self._dictionary[ 'strings' ][ 'favourite_simple_downloader_formula' ] = 'all files linked by images in page'
        self._dictionary[ 'strings' ][ 'thumbnail_scroll_rate' ] = '1.0'
        self._dictionary[ 'strings' ][ 'pause_character' ] = '\u23F8'
        self._dictionary[ 'strings' ][ 'stop_character' ] = '\u23F9'
        self._dictionary[ 'strings' ][ 'default_gug_name' ] = 'safebooru tag search'
        self._dictionary[ 'strings' ][ 'has_audio_label' ] = '\U0001F50A'
        self._dictionary[ 'strings' ][ 'has_duration_label' ] = ' \u23F5 '
        self._dictionary[ 'strings' ][ 'discord_dnd_filename_pattern' ] = '{hash}'
        self._dictionary[ 'strings' ][ 'default_suggested_tags_notebook_page' ] = 'related'
        
        self._dictionary[ 'string_list' ] = {}
        
        self._dictionary[ 'string_list' ][ 'default_media_viewer_custom_shortcuts' ] = []
        self._dictionary[ 'string_list' ][ 'favourite_tags' ] = []
        self._dictionary[ 'string_list' ][ 'advanced_file_deletion_reasons' ] = [ 'I do not like it.', 'It is bad quality.', 'It is not appropriate for this client.', 'Temporary delete--I want to bring it back later.' ]
        
        #
        
        from hydrus.client import ClientStrings
        
        self._dictionary[ 'last_used_string_conversion_step' ] = ClientStrings.StringConverter( [ ( ClientStrings.STRING_CONVERSION_APPEND_TEXT, 'extra text' ) ] )
        
        self._dictionary[ 'custom_default_predicates' ] = HydrusSerialisable.SerialisableList()
        
        self._dictionary[ 'predicate_types_to_recent_predicates' ] = HydrusSerialisable.SerialisableDictionary()
        
        from hydrus.client import ClientLocation
        
        self._dictionary[ 'default_local_location_context' ] = ClientLocation.LocationContext.STATICCreateSimple( CC.LOCAL_FILE_SERVICE_KEY )
        
        #
        
        self._dictionary[ 'favourite_tag_filters' ] = HydrusSerialisable.SerialisableDictionary()
        
        #
        
        from hydrus.client.media import ClientMedia
        from hydrus.client.metadata import ClientTags
        
        default_namespace_sorts = HydrusSerialisable.SerialisableList()
        
        default_namespace_sorts.append( ClientMedia.MediaSort( sort_type = ( 'namespaces', ( ( 'series', 'creator', 'title', 'volume', 'chapter', 'page' ), ClientTags.TAG_DISPLAY_DISPLAY_ACTUAL ) ) ) )
        default_namespace_sorts.append( ClientMedia.MediaSort( sort_type = ( 'namespaces', ( ( 'creator', 'series', 'title', 'volume', 'chapter', 'page' ), ClientTags.TAG_DISPLAY_DISPLAY_ACTUAL ) ) ) )
        
        self._dictionary[ 'default_namespace_sorts' ] = default_namespace_sorts
        
        #
        
        self._dictionary[ 'related_tags_search_tag_slices_weight_percent' ] = HydrusSerialisable.SerialisableList( [
            [ '', 10 ],
            [ ':', 100 ],
            [ 'character:', 300 ],
            [ 'creator:', 300 ],
            [ 'series:', 300 ],
            [ 'filename:', 0 ],
            [ 'page:', 0 ],
            [ 'chapter:', 0 ],
            [ 'volume:', 0 ],
            [ 'title:', 0 ]
        ] )
        
        self._dictionary[ 'related_tags_result_tag_slices_weight_percent' ] = HydrusSerialisable.SerialisableList( [
            [ '', 50 ],
            [ ':', 100 ],
            [ 'character:', 400 ],
            [ 'creator:', 200 ],
            [ 'series:', 200 ],
            [ 'filename:', 0 ],
            [ 'page:', 0 ],
            [ 'chapter:', 0 ],
            [ 'volume:', 0 ],
            [ 'title:', 0 ]
        ] )
        
        #
        
        self._dictionary[ 'tag_summary_generators' ] = HydrusSerialisable.SerialisableDictionary()
        
        namespace_info = []
        
        namespace_info.append( ( 'creator', '', ', ' ) )
        namespace_info.append( ( 'series', '', ', ' ) )
        namespace_info.append( ( 'title', '', ', ' ) )
        
        separator = ' - '
        
        # the cleantags here converts to unicode, which is important!
        
        example_tags = HydrusTags.CleanTags( [ 'creator:creator', 'series:series', 'title:title' ] )
        
        from hydrus.client.gui import ClientGUITags
        
        tsg = ClientGUITags.TagSummaryGenerator( namespace_info = namespace_info, separator = separator, example_tags = example_tags )
        
        self._dictionary[ 'tag_summary_generators' ][ 'thumbnail_top' ] = tsg
        
        namespace_info = []
        
        namespace_info.append( ( 'volume', 'v', '-' ) )
        namespace_info.append( ( 'chapter', 'c', '-' ) )
        namespace_info.append( ( 'page', 'p', '-' ) )
        
        separator = '-'
        
        example_tags = HydrusTags.CleanTags( [ 'volume:3', 'chapter:10', 'page:330', 'page:331' ] )
        
        tsg = ClientGUITags.TagSummaryGenerator( namespace_info = namespace_info, separator = separator, example_tags = example_tags )
        
        self._dictionary[ 'tag_summary_generators' ][ 'thumbnail_bottom_right' ] = tsg
        
        namespace_info = []
        
        namespace_info.append( ( 'creator', '', ', ' ) )
        namespace_info.append( ( 'series', '', ', ' ) )
        namespace_info.append( ( 'title', '', ', ' ) )
        namespace_info.append( ( 'volume', 'v', '-' ) )
        namespace_info.append( ( 'chapter', 'c', '-' ) )
        namespace_info.append( ( 'page', 'p', '-' ) )
        
        separator = ' - '
        
        example_tags = HydrusTags.CleanTags( [ 'creator:creator', 'series:series', 'title:title', 'volume:1', 'chapter:1', 'page:1' ] )
        
        tsg = ClientGUITags.TagSummaryGenerator( namespace_info = namespace_info, separator = separator, example_tags = example_tags )
        
        self._dictionary[ 'tag_summary_generators' ][ 'media_viewer_top' ] = tsg
        
        #
        
        self._dictionary[ 'default_file_import_options' ] = HydrusSerialisable.SerialisableDictionary()
        
        from hydrus.client.importing.options import FileImportOptions
        
        exclude_deleted = True
        preimport_hash_check_type = FileImportOptions.DO_CHECK_AND_MATCHES_ARE_DISPOSITIVE
        preimport_url_check_type = FileImportOptions.DO_CHECK
        preimport_url_check_looks_for_neighbours = True
        allow_decompression_bombs = True
        min_size = None
        max_size = None
        max_gif_size = None
        min_resolution = None
        max_resolution = None
        
        automatic_archive = False
        associate_primary_urls = True
        associate_source_urls = True
        
        from hydrus.client.importing.options import PresentationImportOptions
        
        presentation_import_options = PresentationImportOptions.PresentationImportOptions()
        
        presentation_import_options.SetPresentationStatus( PresentationImportOptions.PRESENTATION_STATUS_NEW_ONLY )
        
        quiet_file_import_options = FileImportOptions.FileImportOptions()
        
        quiet_file_import_options.SetPreImportOptions( exclude_deleted, preimport_hash_check_type, preimport_url_check_type, allow_decompression_bombs, min_size, max_size, max_gif_size, min_resolution, max_resolution )
        quiet_file_import_options.SetPreImportURLCheckLooksForNeighbours( preimport_url_check_looks_for_neighbours )
        quiet_file_import_options.SetPostImportOptions( automatic_archive, associate_primary_urls, associate_source_urls )
        quiet_file_import_options.SetPresentationImportOptions( presentation_import_options )
        quiet_file_import_options.SetDestinationLocationContext( ClientLocation.LocationContext.STATICCreateSimple( CC.LOCAL_FILE_SERVICE_KEY ) )
        
        self._dictionary[ 'default_file_import_options' ][ 'quiet' ] = quiet_file_import_options
        
        loud_file_import_options = FileImportOptions.FileImportOptions()
        
        loud_file_import_options.SetPreImportOptions( exclude_deleted, preimport_hash_check_type, preimport_url_check_type, allow_decompression_bombs, min_size, max_size, max_gif_size, min_resolution, max_resolution )
        loud_file_import_options.SetPreImportURLCheckLooksForNeighbours( preimport_url_check_looks_for_neighbours )
        loud_file_import_options.SetPostImportOptions( automatic_archive, associate_primary_urls, associate_source_urls )
        loud_file_import_options.SetDestinationLocationContext( ClientLocation.LocationContext.STATICCreateSimple( CC.LOCAL_FILE_SERVICE_KEY ) )
        
        self._dictionary[ 'default_file_import_options' ][ 'loud' ] = loud_file_import_options
        
        #
        
        self._dictionary[ 'frame_locations' ] = {}
        
        # remember size, remember position, last_size, last_pos, default gravity, default position, maximised, fullscreen
        self._dictionary[ 'frame_locations' ][ 'file_import_status' ] = ( True, True, None, None, ( -1, -1 ), 'topleft', False, False )
        self._dictionary[ 'frame_locations' ][ 'gallery_import_log' ] = ( True, True, None, None, ( -1, -1 ), 'topleft', False, False )
        self._dictionary[ 'frame_locations' ][ 'local_import_filename_tagging' ] = ( True, False, None, None, ( -1, -1 ), 'topleft', False, False )
        self._dictionary[ 'frame_locations' ][ 'main_gui' ] = ( True, True, ( 800, 600 ), ( 20, 20 ), ( -1, -1 ), 'topleft', True, False )
        self._dictionary[ 'frame_locations' ][ 'manage_options_dialog' ] = ( False, False, None, None, ( -1, -1 ), 'topleft', False, False )
        self._dictionary[ 'frame_locations' ][ 'manage_subscriptions_dialog' ] = ( True, True, None, None, ( 1, -1 ), 'topleft', False, False )
        self._dictionary[ 'frame_locations' ][ 'edit_subscription_dialog' ] = ( True, True, None, None, ( 1, -1 ), 'topleft', False, False )
        self._dictionary[ 'frame_locations' ][ 'manage_tags_dialog' ] = ( False, False, None, None, ( -1, 1 ), 'topleft', False, False )
        self._dictionary[ 'frame_locations' ][ 'manage_tags_frame' ] = ( False, False, None, None, ( -1, 1 ), 'topleft', False, False )
        self._dictionary[ 'frame_locations' ][ 'media_viewer' ] = ( True, True, ( 640, 480 ), ( 70, 70 ), ( -1, -1 ), 'topleft', True, True )
        self._dictionary[ 'frame_locations' ][ 'regular_dialog' ] = ( False, False, None, None, ( -1, -1 ), 'topleft', False, False )
        self._dictionary[ 'frame_locations' ][ 'review_services' ] = ( False, True, None, None, ( -1, -1 ), 'topleft', False, False )
        self._dictionary[ 'frame_locations' ][ 'deeply_nested_dialog' ] = ( False, False, None, None, ( -1, -1 ), 'topleft', False, False )
        self._dictionary[ 'frame_locations' ][ 'regular_center_dialog' ] = ( False, False, None, None, ( -1, -1 ), 'center', False, False )
        self._dictionary[ 'frame_locations' ][ 'file_history_chart' ] = ( True, True, ( 960, 720 ), None, ( -1, -1 ), 'topleft', False, False )
        
        #
        
        self._dictionary[ 'media_launch' ] = HydrusSerialisable.SerialisableDictionary() # integer keys, so got to be cleverer dict
        
        for mime in HC.SEARCHABLE_MIMES:
            
            self._dictionary[ 'media_launch' ][ mime ] = None
            
        
        #
        
        self._dictionary[ 'media_view' ] = self._GetDefaultMediaViewOptions()
        
        self._dictionary[ 'media_zooms' ] = [ 0.01, 0.05, 0.1, 0.15, 0.2, 0.3, 0.5, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.5, 2.0, 3.0, 5.0, 10.0, 20.0 ]
        
        self._dictionary[ 'slideshow_durations' ] = [ 1.0, 5.0, 10.0, 30.0, 60.0 ]
        
        #
        
        self._dictionary[ 'misc' ] = HydrusSerialisable.SerialisableDictionary()
        
        self._dictionary[ 'misc' ][ 'default_thread_watcher_options' ] = ClientDefaults.GetDefaultCheckerOptions( 'thread' )
        self._dictionary[ 'misc' ][ 'default_subscription_checker_options' ] = ClientDefaults.GetDefaultCheckerOptions( 'artist subscription' )
        
        #
        
        self._dictionary[ 'suggested_tags' ] = HydrusSerialisable.SerialisableDictionary()
        
        self._dictionary[ 'suggested_tags' ][ 'favourites' ] = {}
        
        #
        
        from hydrus.client.media import ClientMedia
        
        self._dictionary[ 'default_sort' ] = ClientMedia.MediaSort( ( 'system', CC.SORT_FILES_BY_FILESIZE ), CC.SORT_ASC )
        self._dictionary[ 'fallback_sort' ] = ClientMedia.MediaSort( ( 'system', CC.SORT_FILES_BY_IMPORT_TIME ), CC.SORT_ASC )
        
        self._dictionary[ 'default_collect' ] = ClientMedia.MediaCollect()
        
        #
        
        from hydrus.client.metadata import ClientTagSorting
        
        self._dictionary[ 'default_tag_sorts' ] = HydrusSerialisable.SerialisableDictionary()
        
        self._dictionary[ 'default_tag_sorts' ][ CC.TAG_PRESENTATION_SEARCH_PAGE ] = ClientTagSorting.TagSort.STATICGetTextASCDefault()
        self._dictionary[ 'default_tag_sorts' ][ CC.TAG_PRESENTATION_SEARCH_PAGE_MANAGE_TAGS ] = ClientTagSorting.TagSort.STATICGetTextASCDefault()
        self._dictionary[ 'default_tag_sorts' ][ CC.TAG_PRESENTATION_MEDIA_VIEWER ] = ClientTagSorting.TagSort.STATICGetTextASCDefault()
        self._dictionary[ 'default_tag_sorts' ][ CC.TAG_PRESENTATION_MEDIA_VIEWER_MANAGE_TAGS ] = ClientTagSorting.TagSort.STATICGetTextASCDefault()
        
        #
        
        self._dictionary[ 'default_export_files_metadata_routers' ] = HydrusSerialisable.SerialisableList()
        
    
    def _InitialiseFromSerialisableInfo( self, serialisable_info ):
        
        loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_info )
        
        for ( key, value ) in list(loaded_dictionary.items()):
            
            if key in self._dictionary and isinstance( self._dictionary[ key ], dict ) and isinstance( value, dict ):
                
                self._dictionary[ key ].update( value )
                
            else:
                
                self._dictionary[ key ] = value
                
            
        
    
    def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
        
        if version == 1:
            
            loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( old_serialisable_info )
            
            if 'media_view' in loaded_dictionary:
                
                mimes = list(loaded_dictionary[ 'media_view' ].keys())
                
                for mime in mimes:
                    
                    if mime in self._dictionary[ 'media_view' ]:
                        
                        ( default_media_show_action, default_preview_show_action, default_zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
                        
                        ( media_show_action, preview_show_action, zoom_in_to_fit, exact_zooms_only, scale_up_quality, scale_down_quality ) = loaded_dictionary[ 'media_view' ][ mime ]
                        
                        loaded_dictionary[ 'media_view' ][ mime ] = ( media_show_action, preview_show_action, default_zoom_info )
                        
                    else:
                        
                        # while devving this, I discovered some u'20' stringified keys had snuck in and hung around. let's nuke them here, in case anyone else got similar
                        
                        del loaded_dictionary[ 'media_view' ][ mime ]
                        
                    
                
            
            new_serialisable_info = loaded_dictionary.GetSerialisableTuple()
            
            return ( 2, new_serialisable_info )
            
        
        if version == 2:
            
            # as db_dir is now moveable, let's move portable base from base_dir to db_dir
            
            def update_portable_path( p ):
                
                if p is None:
                    
                    return p
                    
                
                p = os.path.normpath( p ) # collapses .. stuff and converts / to \\ for windows only
                
                if os.path.isabs( p ):
                    
                    a_p = p
                    
                else:
                    
                    a_p = os.path.normpath( os.path.join( HC.BASE_DIR, p ) )
                    
                
                if not HC.PLATFORM_WINDOWS and not os.path.exists( a_p ):
                    
                    a_p = a_p.replace( '\\', '/' )
                    
                
                try:
                    
                    db_dir = HG.controller.GetDBDir()
                    
                    p = os.path.relpath( a_p, db_dir )
                    
                    if p.startswith( '..' ):
                        
                        p = a_p
                        
                    
                except:
                    
                    p = a_p
                    
                
                if HC.PLATFORM_WINDOWS:
                    
                    p = p.replace( '\\', '/' ) # store seps as /, to maintain multiplatform uniformity
                    
                
                return p
                
            
            loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( old_serialisable_info )
            
            if 'client_files_locations_ideal_weights' in loaded_dictionary:
                
                updated_cfliw = []
                
                for ( old_portable_path, weight ) in loaded_dictionary[ 'client_files_locations_ideal_weights' ]:
                    
                    new_portable_path = update_portable_path( old_portable_path )
                    
                    updated_cfliw.append( ( new_portable_path, weight ) )
                    
                
                loaded_dictionary[ 'client_files_locations_ideal_weights' ] = updated_cfliw
                
            
            if 'client_files_locations_resized_thumbnail_override' in loaded_dictionary:
                
                loaded_dictionary[ 'client_files_locations_resized_thumbnail_override' ] = update_portable_path( loaded_dictionary[ 'client_files_locations_resized_thumbnail_override' ] )
                
            
            if 'client_files_locations_full_size_thumbnail_override' in loaded_dictionary:
                
                loaded_dictionary[ 'client_files_locations_full_size_thumbnail_override' ] = update_portable_path( loaded_dictionary[ 'client_files_locations_full_size_thumbnail_override' ] )
                
            
            new_serialisable_info = loaded_dictionary.GetSerialisableTuple()
            
            return ( 3, new_serialisable_info )
            
        
        if version == 3:
            
            loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( old_serialisable_info )
            
            if 'media_view' in loaded_dictionary:
                
                def convert_show_action( show_action ):
                    
                    start_paused = show_action in ( CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE_PAUSED )
                    start_with_embed = show_action in ( CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED )
                    
                    if start_paused or start_with_embed:
                        
                        show_action = CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE
                        
                    
                    return ( show_action, start_paused, start_with_embed )
                    
                
                for ( mime, ( media_show_action, preview_show_action, zoom_info ) ) in list( loaded_dictionary[ 'media_view' ].items() ):
                    
                    ( media_show_action, media_start_paused, media_start_with_embed ) = convert_show_action( media_show_action )
                    ( preview_show_action, preview_start_paused, preview_start_with_embed ) = convert_show_action( preview_show_action )
                    
                    loaded_dictionary[ 'media_view' ][ mime ] = ( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info )
                    
                
            
            new_serialisable_info = loaded_dictionary.GetSerialisableTuple()
            
            return ( 4, new_serialisable_info )
            
        
        if version == 4:
            
            serialisable_dictionary = old_serialisable_info
            
            loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_dictionary )
            
            if 'key_list' in loaded_dictionary and 'default_neighbouring_txt_tag_service_keys' in loaded_dictionary[ 'key_list' ]:
                
                encoded_default_neighbouring_txt_tag_service_keys = loaded_dictionary[ 'key_list' ][ 'default_neighbouring_txt_tag_service_keys' ]
                
                default_neighbouring_txt_tag_service_keys = [ bytes.fromhex( hex_key ) for hex_key in encoded_default_neighbouring_txt_tag_service_keys ]
                
                from hydrus.client.metadata import ClientMetadataMigration
                from hydrus.client.metadata import ClientMetadataMigrationExporters
                from hydrus.client.metadata import ClientMetadataMigrationImporters
                
                importers = [ ClientMetadataMigrationImporters.SingleFileMetadataImporterMediaTags( service_key = service_key ) for service_key in default_neighbouring_txt_tag_service_keys ]
                exporter = ClientMetadataMigrationExporters.SingleFileMetadataExporterTXT()
                
                metadata_router = ClientMetadataMigration.SingleFileMetadataRouter( importers = importers, exporter = exporter )
                
                metadata_routers = [ metadata_router ]
                
                loaded_dictionary[ 'default_export_files_metadata_routers' ] = HydrusSerialisable.SerialisableList( metadata_routers )
                
                del loaded_dictionary[ 'key_list' ][ 'default_neighbouring_txt_tag_service_keys' ]
                
            
            new_serialisable_info = loaded_dictionary.GetSerialisableTuple()
            
            return ( 5, new_serialisable_info )
            
        
        if version == 5:
            
            serialisable_dictionary = old_serialisable_info
            
            loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_dictionary )
            
            if 'default_tag_sort' in loaded_dictionary:
                
                from hydrus.client.metadata import ClientTagSorting
                
                loaded_dictionary[ 'default_tag_sorts' ] = HydrusSerialisable.SerialisableDictionary()
                
                loaded_dictionary[ 'default_tag_sorts' ][ CC.TAG_PRESENTATION_SEARCH_PAGE ] = loaded_dictionary[ 'default_tag_sort' ]
                loaded_dictionary[ 'default_tag_sorts' ][ CC.TAG_PRESENTATION_SEARCH_PAGE_MANAGE_TAGS ] = ClientTagSorting.TagSort.STATICGetTextASCDefault()
                loaded_dictionary[ 'default_tag_sorts' ][ CC.TAG_PRESENTATION_MEDIA_VIEWER ] = ClientTagSorting.TagSort.STATICGetTextASCDefault()
                loaded_dictionary[ 'default_tag_sorts' ][ CC.TAG_PRESENTATION_MEDIA_VIEWER_MANAGE_TAGS ] = ClientTagSorting.TagSort.STATICGetTextASCDefault()
                
                del loaded_dictionary[ 'default_tag_sort' ]
                
            
            new_serialisable_info = loaded_dictionary.GetSerialisableTuple()
            
            return ( 6, new_serialisable_info )
            
        
    
    def ClearCustomDefaultSystemPredicates( self, predicate_type = None, comparable_predicate = None ):
        
        with self._lock:
            
            custom_default_predicates = self._dictionary[ 'custom_default_predicates' ]
            
            if predicate_type is not None:
                
                new_custom_default_predicates = HydrusSerialisable.SerialisableList( [ pred for pred in custom_default_predicates if pred.GetType() != predicate_type ] )
                
                self._dictionary[ 'custom_default_predicates' ] = new_custom_default_predicates
                
                return
                
            
            if comparable_predicate is not None:
                
                new_custom_default_predicates = HydrusSerialisable.SerialisableList( [ pred for pred in custom_default_predicates if not pred.IsUIEditable( comparable_predicate ) ] )
                
                self._dictionary[ 'custom_default_predicates' ] = new_custom_default_predicates
                
                return
                
            
        
    
    def FlipBoolean( self, name ):
        
        with self._lock:
            
            self._dictionary[ 'booleans' ][ name ] = not self._dictionary[ 'booleans' ][ name ]
            
        
    
    def GetBoolean( self, name ):
        
        with self._lock:
            
            return self._dictionary[ 'booleans' ][ name ]
            
        
    
    def GetAllBooleans( self ):
        
        with self._lock:
            
            return self._dictionary[ 'booleans' ]
            
        
    
    def GetDefaultCollect( self ):
        
        with self._lock:
            
            return self._dictionary[ 'default_collect' ]
            
        
    
    def GetColour( self, colour_type, colourset = None ):
        
        with self._lock:
            
            if colourset is None:
                
                colourset = self._dictionary[ 'strings' ][ 'current_colourset' ]
                
            
            ( r, g, b ) = self._dictionary[ 'colours' ][ colourset ][ colour_type ]
            
            return QG.QColor( r, g, b )
            
        
    
    def GetAllColours( self ):
        
        with self._lock:
            
            return self._dictionary[ 'colours' ]
            
        
    
    def GetCustomDefaultSystemPredicates( self, predicate_type = None, comparable_predicate = None ):
        
        with self._lock:
            
            custom_default_predicates = self._dictionary[ 'custom_default_predicates' ]
            
            if predicate_type is not None:
                
                return [ pred for pred in custom_default_predicates if pred.GetType() == predicate_type ]
                
            
            if comparable_predicate is not None:
                
                return [ pred for pred in custom_default_predicates if pred.IsUIEditable( comparable_predicate ) ]
                
            
            return []
            
        
    
    def GetDefaultExportFilesMetadataRouters( self ):
        
        with self._lock:
            
            return list( self._dictionary[ 'default_export_files_metadata_routers' ] )
            
        
    
    def GetDefaultFileImportOptions( self, options_type ) -> FileImportOptions.FileImportOptions:
        
        with self._lock:
            
            if options_type == FileImportOptions.IMPORT_TYPE_LOUD:
                
                key = 'loud'
                
            else:
                
                key = 'quiet'
                
                
            
            return self._dictionary[ 'default_file_import_options' ][ key ]
            
        
    
    def GetDefaultLocalLocationContext( self ):
        
        with self._lock:
            
            location_context = self._dictionary[ 'default_local_location_context' ]
            
            try:
                
                location_context.FixMissingServices( CG.client_controller.services_manager.FilterValidServiceKeys )
                
                if location_context.IsEmpty():
                    
                    from hydrus.client import ClientLocation
                    
                    location_context = ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY )
                    
                
            except:
                
                pass
                
            
            return location_context
            
        
    
    def GetDefaultMediaViewOptions( self ):
        
        with self._lock:
            
            return self._GetDefaultMediaViewOptions()
            
        
    
    def GetDefaultNamespaceSorts( self ):
        
        with self._lock:
            
            return list( self._dictionary[ 'default_namespace_sorts' ] )
            
        
    
    def GetDefaultSort( self ):
        
        with self._lock:
            
            return self._dictionary[ 'default_sort' ]
            
        
    
    def GetDefaultSubscriptionCheckerOptions( self ):
        
        with self._lock:
            
            return self._dictionary[ 'misc' ][ 'default_subscription_checker_options' ]
            
        
    
    def GetDefaultTagSort( self, tag_presentation_location: int ):
        
        with self._lock:
            
            return self._dictionary[ 'default_tag_sorts' ][ tag_presentation_location ]
            
        
    
    def GetDefaultWatcherCheckerOptions( self ):
        
        with self._lock:
            
            return self._dictionary[ 'misc' ][ 'default_thread_watcher_options' ]
            
        
    
    def GetDuplicateContentMergeOptions( self, duplicate_type ) -> ClientDuplicates.DuplicateContentMergeOptions:
        
        with self._lock:
            
            if duplicate_type in self._dictionary[ 'duplicate_action_options' ]:
                
                return self._dictionary[ 'duplicate_action_options' ][ duplicate_type ]
                
            else:
                
                return ClientDuplicates.DuplicateContentMergeOptions()
                
            
        
    
    def GetFallbackSort( self ):
        
        with self._lock:
            
            return self._dictionary[ 'fallback_sort' ]
            
        
    
    def GetFavouriteTagFilters( self ):
        
        with self._lock:
            
            return dict( self._dictionary[ 'favourite_tag_filters' ] )
            
        
    
    def GetFrameLocation( self, frame_key ):
        
        with self._lock:
            
            return self._dictionary[ 'frame_locations' ][ frame_key ]
            
        
    
    def GetFrameLocations( self ):
        
        with self._lock:
            
            return list(self._dictionary[ 'frame_locations' ].items())
            
        
    
    def GetInteger( self, name ):
        
        with self._lock:
            
            return self._dictionary[ 'integers' ][ name ]
            
        

    def GetAllIntegers( self):
        
        with self._lock:
            
            return self._dictionary[ 'integers' ]

        
    
    def GetKeyHex( self, name ):
        
        with self._lock:
            
            return self._dictionary[ 'keys' ][ name ]
            
        
    
    def GetKey( self, name ):
        
        return bytes.fromhex( self.GetKeyHex( name ) )
        
    
    def GetAllKeysHex( self ):
        
        with self._lock:
            
            return self._dictionary[ 'keys' ]
            
        
    
    def GetKeyList( self, name ):
        
        with self._lock:
            
            return [ bytes.fromhex( hex_key ) for hex_key in self._dictionary[ 'key_list' ][ name ] ]
            
        
    
    def GetMediaShowAction( self, mime ):
        
        with self._lock:
            
            if mime == HC.APPLICATION_UNKNOWN:
                
                return ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, False, False )
                
            
            ( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._GetMediaViewOptions( mime )
            
            ( possible_show_actions, can_start_paused, can_start_with_embed ) = CC.media_viewer_capabilities[ mime ]
            
            if media_show_action not in possible_show_actions:
                
                return ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, False, False )
                
            
            return ( media_show_action, media_start_paused, media_start_with_embed )
            
        
    
    def GetMediaViewOptions( self ):
        
        with self._lock:
            
            return dict( self._dictionary[ 'media_view' ] )
            
        
    
    def GetMediaZoomOptions( self, mime ):
        
        with self._lock:
            
            ( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._GetMediaViewOptions( mime )
            
            return zoom_info
            
        
    
    def GetMediaZooms( self ):
        
        with self._lock:
            
            return list( self._dictionary[ 'media_zooms' ] )
            
        
    
    def GetMediaZoomQuality( self, mime ):
        
        with self._lock:
            
            ( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._GetMediaViewOptions( mime )
            
            ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) = zoom_info
            
            return ( scale_up_quality, scale_down_quality )
            
        
    
    def GetMimeLaunch( self, mime ):
        
        with self._lock:
            
            if mime not in self._dictionary[ 'media_launch' ]:
                
                self._dictionary[ 'media_launch' ][ mime ] = None
                
            
            return self._dictionary[ 'media_launch' ][ mime ]
            
        
    
    def GetNoneableInteger( self, name ):
        
        with self._lock:
            
            return self._dictionary[ 'noneable_integers' ][ name ]
            
        
    
    def GetAllNoneableIntegers( self ):
        
        with self._lock:
            
            return self._dictionary[ 'noneable_integers' ]
            
        
    
    def GetNoneableString( self, name ):
        
        with self._lock:
            
            return self._dictionary[ 'noneable_strings' ][ name ]
            
        
    
    def GetAllNoneableStrings( self ):
        
        with self._lock:
            
            return self._dictionary[ 'noneable_strings' ]
            
        
    
    def GetPreviewShowAction( self, mime ):
        
        with self._lock:
            
            if mime == HC.APPLICATION_UNKNOWN:
                
                return ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, False, False )
                
            
            ( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._GetMediaViewOptions( mime )
            
            ( possible_show_actions, can_start_paused, can_start_with_embed ) = CC.media_viewer_capabilities[ mime ]
            
            if preview_show_action not in possible_show_actions:
                
                return ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, False, False )
                
            
            return ( preview_show_action, preview_start_paused, preview_start_with_embed )
            
        
    
    def GetRawSerialisable( self, name ):
        
        with self._lock:
            
            return self._dictionary[ name ]
        
    
    def GetRecentPetitionReasons( self, content_type, action ):
        
        with self._lock:
            
            return list( self._dictionary[ 'recent_petition_reasons' ].get( ( content_type, action ), [] ) )[ : self._dictionary[ 'integers' ][ 'num_recent_petition_reasons' ] ]
            
        
    
    def GetRecentPredicates( self, predicate_types ):
        
        with self._lock:
            
            result = []
            
            predicate_types_to_recent_predicates = self._dictionary[ 'predicate_types_to_recent_predicates' ]
            
            for predicate_type in predicate_types:
                
                if predicate_type in predicate_types_to_recent_predicates:
                    
                    result.extend( predicate_types_to_recent_predicates[ predicate_type ] )
                    
                
            
            return result
            
        
    
    def GetRelatedTagsTagSliceWeights( self ):
        
        with self._lock:
            
            return (
                [ tuple( row ) for row in self._dictionary[ 'related_tags_search_tag_slices_weight_percent' ] ],
                [ tuple( row ) for row in self._dictionary[ 'related_tags_result_tag_slices_weight_percent' ] ]
            )
            
        
    
    def GetSimpleDownloaderFormulae( self ):
        
        with self._lock:
            
            return self._dictionary[ 'simple_downloader_formulae' ]
            
        
    
    def GetSlideshowDurations( self ):
        
        with self._lock:
            
            return list( self._dictionary[ 'slideshow_durations' ] )
            
        
    
    def GetString( self, name ):
        
        with self._lock:
            
            return self._dictionary[ 'strings' ][ name ]
            
        
    
    def GetAllStrings( self ):
        
        with self._lock:
            
            return self._dictionary[ 'strings' ]
            
        
    
    def GetStringList( self, name ):
        
        with self._lock:
            
            return self._dictionary[ 'string_list' ][ name ]
            
        
    
    def GetSuggestedTagsFavourites( self, service_key ):
        
        with self._lock:
            
            service_key_hex = service_key.hex()
            
            stf = self._dictionary[ 'suggested_tags' ][ 'favourites' ]
            
            if service_key_hex in stf:
                
                return set( stf[ service_key_hex ] )
                
            else:
                
                return set()
                
            
        
    
    def GetAllSuggestedTagsFavourites( self ):
        
        with self._lock:
            
            return self._dictionary[ 'suggested_tags' ][ 'favourites' ]
                
            
        
    
    def GetTagSummaryGenerator( self, name ):
        
        with self._lock:
            
            return self._dictionary[ 'tag_summary_generators' ][ name ]
            
        
    
    def InvertBoolean( self, name ):
        
        with self._lock:
            
            self._dictionary[ 'booleans' ][ name ] = not self._dictionary[ 'booleans' ][ name ]
            
        
    
    def PushRecentPetitionReason( self, content_type, action, reason ):
        
        with self._lock:
            
            key = ( content_type, action )
            
            if key not in self._dictionary[ 'recent_petition_reasons' ]:
                
                self._dictionary[ 'recent_petition_reasons' ][ key ] = []
                
            
            reasons = self._dictionary[ 'recent_petition_reasons' ][ key ]
            
            if reason in reasons:
                
                reasons.remove( reason )
                
            
            reasons.insert( 0, reason )
            
            self._dictionary[ 'recent_petition_reasons' ][ key ] = reasons[ : self._dictionary[ 'integers' ][ 'num_recent_petition_reasons' ] ]
            
        
    
    def PushRecentPredicates( self, predicates ):
        
        with self._lock:
            
            predicate_types_to_recent_predicates = self._dictionary[ 'predicate_types_to_recent_predicates' ]
            
            for predicate in predicates:
                
                predicate_type = predicate.GetType()
                
                if predicate_type not in predicate_types_to_recent_predicates:
                    
                    predicate_types_to_recent_predicates[ predicate_type ] = HydrusSerialisable.SerialisableList()
                    
                
                recent_predicates = predicate_types_to_recent_predicates[ predicate_type ]
                
                if predicate in recent_predicates:
                    
                    recent_predicates.remove( predicate )
                    
                
                recent_predicates.insert( 0, predicate )
                
                while len( recent_predicates ) > 5:
                    
                    recent_predicates.pop( 5 )
                    
                
            
        
    
    def RemoveRecentPredicate( self, predicate ):
        
        with self._lock:
            
            predicate_types_to_recent_predicates = self._dictionary[ 'predicate_types_to_recent_predicates' ]
            
            for recent_predicates in predicate_types_to_recent_predicates.values():
                
                if predicate in recent_predicates:
                    
                    recent_predicates.remove( predicate )
                    
                    return
                    
                
            
        
    
    def SetBoolean( self, name, value ):
        
        with self._lock:
            
            self._dictionary[ 'booleans' ][ name ] = value
            
        
    
    def SetDefaultCollect( self, media_collect ):
        
        with self._lock:
            
            self._dictionary[ 'default_collect' ] = media_collect
            
        
    
    def SetColour( self, colour_type, colourset, colour ):
        
        with self._lock:
            
            if isinstance( colour, QG.QColor ):
                
                c = colour
                
                ( r, g, b ) = ( c.red(), c.green(), c.blue() )
                
            else:
                
                ( r, g, b ) = colour
                
            
            self._dictionary[ 'colours' ][ colourset ][ colour_type ] = ( r, g, b )
            
        
    
    def SetCustomDefaultSystemPredicates( self, predicate_type = None, predicates = None, comparable_predicates = None ):
        
        with self._lock:
            
            custom_default_predicates = self._dictionary[ 'custom_default_predicates' ]
            
            if predicate_type is not None and predicates is not None:
                
                new_custom_default_predicates = HydrusSerialisable.SerialisableList( [ pred for pred in custom_default_predicates if pred.GetType() != predicate_type ] )
                
                new_custom_default_predicates.extend( predicates )
                
                self._dictionary[ 'custom_default_predicates' ] = new_custom_default_predicates
                
                return
                
            
            if comparable_predicates is not None:
                
                new_custom_default_predicates = HydrusSerialisable.SerialisableList()
                
                for pred in custom_default_predicates:
                    
                    if True not in ( pred.IsUIEditable( comparable_predicate ) for comparable_predicate in comparable_predicates ):
                        
                        new_custom_default_predicates.append( pred )
                        
                    
                
                new_custom_default_predicates.extend( comparable_predicates )
                
                self._dictionary[ 'custom_default_predicates' ] = new_custom_default_predicates
                
                return
                
            
        
    
    def SetDefaultExportFilesMetadataRouters( self, metadata_routers ):
        
        with self._lock:
            
            self._dictionary[ 'default_export_files_metadata_routers' ] = HydrusSerialisable.SerialisableList( metadata_routers )
            
        
    
    def SetDefaultFileImportOptions( self, options_type, file_import_options ):
        
        with self._lock:
            
            if options_type == FileImportOptions.IMPORT_TYPE_LOUD:
                
                key = 'loud'
                
            else:
                
                key = 'quiet'
                
            
            self._dictionary[ 'default_file_import_options' ][ key ] = file_import_options
            
        
    
    def SetDefaultLocalLocationContext( self, location_context ):
        
        with self._lock:
            
            self._dictionary[ 'default_local_location_context' ] = location_context
            
        
    
    def SetDefaultNamespaceSorts( self, namespace_sorts ):
        
        with self._lock:
            
            default_namespace_sorts = HydrusSerialisable.SerialisableList()
            
            for namespace_sort in namespace_sorts:
                
                default_namespace_sorts.append( namespace_sort )
                
            
            self._dictionary[ 'default_namespace_sorts' ] = default_namespace_sorts
            
        
    
    def SetDefaultTagSort( self, tag_presentation_location, tag_sort ):
        
        with self._lock:
            
            self._dictionary[ 'default_tag_sorts' ][ tag_presentation_location ] = tag_sort
            
        
    
    def SetDefaultSort( self, media_sort ):
        
        with self._lock:
            
            self._dictionary[ 'default_sort' ] = media_sort
            
        
    
    def SetDefaultSubscriptionCheckerOptions( self, checker_options ):
        
        with self._lock:
            
            self._dictionary[ 'misc' ][ 'default_subscription_checker_options' ] = checker_options
            
        
    
    def SetDefaultWatcherCheckerOptions( self, checker_options ):
        
        with self._lock:
            
            self._dictionary[ 'misc' ][ 'default_thread_watcher_options' ] = checker_options
            
        
    
    def SetDuplicateContentMergeOptions( self, duplicate_type, duplicate_content_merge_options ):
        
        with self._lock:
            
            self._dictionary[ 'duplicate_action_options' ][ duplicate_type ] = duplicate_content_merge_options
            
        
    
    def SetFallbackSort( self, media_sort ):
        
        with self._lock:
            
            self._dictionary[ 'fallback_sort' ] = media_sort
            
        
    
    def SetFavouriteTagFilters( self, names_to_tag_filters ):
        
        with self._lock:
            
            self._dictionary[ 'favourite_tag_filters' ] = HydrusSerialisable.SerialisableDictionary( names_to_tag_filters )
            
        
    
    def SetFrameLocation( self, frame_key, remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ):
        
        with self._lock:
            
            self._dictionary[ 'frame_locations' ][ frame_key ] = ( remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen )
            
        
    
    def SetInteger( self, name, value ):
        
        with self._lock:
            
            self._dictionary[ 'integers' ][ name ] = value
            
        
    
    def SetKey( self, name, value ):
        
        with self._lock:
            
            self._dictionary[ 'keys' ][ name ] = value.hex()
            
        
    
    def SetKeyList( self, name, value ):
        
        with self._lock:
            
            self._dictionary[ 'key_list' ][ name ] = [ key.hex() for key in value ]
            
        
    
    def SetMediaViewOptions( self, mimes_to_media_view_options ):
        
        with self._lock:
            
            self._dictionary[ 'media_view' ] = HydrusSerialisable.SerialisableDictionary( mimes_to_media_view_options )
            
        
    
    def SetMediaZooms( self, zooms ):
        
        with self._lock:
            
            self._dictionary[ 'media_zooms' ] = zooms
            
        
    
    def SetMimeLaunch( self, mime, launch_path ):
        
        with self._lock:
            
            self._dictionary[ 'media_launch' ][ mime ] = launch_path
            
        
    
    def SetNoneableInteger( self, name, value ):
        
        with self._lock:
            
            self._dictionary[ 'noneable_integers' ][ name ] = value
            
        
    
    def SetNoneableString( self, name, value ):
        
        with self._lock:
            
            self._dictionary[ 'noneable_strings' ][ name ] = value
            
        
    
    def SetRawSerialisable( self, name, value ):
        
        with self._lock:
            
            self._dictionary[ name ] = value
            
        
    
    def SetRelatedTagsTagSliceWeights( self, related_tags_search_tag_slices_weight_percent, related_tags_result_tag_slices_weight_percent ):
        
        with self._lock:
            
            self._dictionary[ 'related_tags_search_tag_slices_weight_percent' ] = HydrusSerialisable.SerialisableList( related_tags_search_tag_slices_weight_percent )
            self._dictionary[ 'related_tags_result_tag_slices_weight_percent' ] = HydrusSerialisable.SerialisableList( related_tags_result_tag_slices_weight_percent )
            
        
    
    def SetSimpleDownloaderFormulae( self, simple_downloader_formulae ):
        
        with self._lock:
            
            self._dictionary[ 'simple_downloader_formulae' ] = HydrusSerialisable.SerialisableList( simple_downloader_formulae )
            
        
    
    def SetSlideshowDurations( self, slideshow_durations ):
        
        with self._lock:
            
            self._dictionary[ 'slideshow_durations' ] = slideshow_durations
            
        
    
    def SetString( self, name, value ):
        
        with self._lock:
            
            it_changed = False
            
            if value is not None and value != '':
                
                if self._dictionary[ 'strings' ][ name ] != value:
                    
                    self._dictionary[ 'strings' ][ name ] = value
                    
                    it_changed = True
                    
                
            
        
    
    def SetStringList( self, name, value ):
        
        with self._lock:
            
            self._dictionary[ 'string_list' ][ name ] = list( value )
            
        
    
    def SetSuggestedTagsFavourites( self, service_key, tags ):
        
        with self._lock:
            
            service_key_hex = service_key.hex()
            
            self._dictionary[ 'suggested_tags' ][ 'favourites' ][ service_key_hex ] = list( tags )
            
        
    
    def SetTagSummaryGenerator( self, name, tag_summary_generator ):
        
        with self._lock:
            
            self._dictionary[ 'tag_summary_generators' ][ name ] = tag_summary_generator
            
        
    
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS ] = ClientOptions
