import collections import os import re from qtpy import QtCore as QC from qtpy import QtWidgets as QW from hydrus.core import HydrusConstants as HC from hydrus.core import HydrusData from hydrus.core import HydrusExceptions from hydrus.core import HydrusGlobals as HG from hydrus.core import HydrusTags from hydrus.core import HydrusText from hydrus.core import HydrusTime from hydrus.client import ClientConstants as CC from hydrus.client import ClientGlobals as CG from hydrus.client import ClientTime from hydrus.client.gui import ClientGUIDialogs from hydrus.client.gui import ClientGUIDialogsMessage from hydrus.client.gui import ClientGUIDialogsQuick from hydrus.client.gui import ClientGUIFileSeedCache from hydrus.client.gui import ClientGUIFunctions from hydrus.client.gui import ClientGUIGallerySeedLog from hydrus.client.gui import ClientGUIScrolledPanels from hydrus.client.gui import ClientGUITime from hydrus.client.gui import ClientGUITopLevelWindowsPanels from hydrus.client.gui import QtPorting as QP from hydrus.client.gui.importing import ClientGUIImportOptions from hydrus.client.gui.lists import ClientGUIListBoxes from hydrus.client.gui.lists import ClientGUIListConstants as CGLC from hydrus.client.gui.lists import ClientGUIListCtrl from hydrus.client.gui.metadata import ClientGUIMetadataMigration from hydrus.client.gui.networking import ClientGUINetworkJobControl from hydrus.client.gui.search import ClientGUIACDropdown from hydrus.client.gui.widgets import ClientGUICommon from hydrus.client.importing import ClientImporting from hydrus.client.importing.options import ClientImportOptions from hydrus.client.importing.options import FileImportOptions from hydrus.client.importing.options import NoteImportOptions from hydrus.client.importing.options import TagImportOptions from hydrus.client.metadata import ClientTags from hydrus.client.metadata import ClientMetadataMigrationExporters from hydrus.client.metadata import ClientMetadataMigrationImporters class CheckerOptionsButton( ClientGUICommon.BetterButton ): valueChanged = QC.Signal( ClientImportOptions.CheckerOptions ) def __init__( self, parent, checker_options: ClientImportOptions.CheckerOptions ): ClientGUICommon.BetterButton.__init__( self, parent, 'checker options', self._EditOptions ) self._checker_options = checker_options self._SetToolTip() def _EditOptions( self ): with ClientGUITopLevelWindowsPanels.DialogEdit( self, 'edit checker options' ) as dlg: panel = ClientGUITime.EditCheckerOptions( dlg, self._checker_options ) dlg.SetPanel( panel ) if dlg.exec() == QW.QDialog.Accepted: checker_options = panel.GetValue() self._SetValue( checker_options ) def _SetToolTip( self ): self.setToolTip( ClientGUIFunctions.WrapToolTip( self._checker_options.GetSummary() ) ) def _SetValue( self, checker_options ): self._checker_options = checker_options self._SetToolTip() self.valueChanged.emit( self._checker_options ) def GetValue( self ): return self._checker_options def SetValue( self, checker_options ): self._SetValue( checker_options ) class FilenameTaggingOptionsPanel( QW.QWidget ): movePageLeft = QC.Signal() movePageRight = QC.Signal() tagsChanged = QC.Signal() def __init__( self, parent, service_key, filename_tagging_options = None, present_for_accompanying_file_list = False ): if filename_tagging_options is None: # pull from an options default filename_tagging_options = TagImportOptions.FilenameTaggingOptions() QW.QWidget.__init__( self, parent ) self._service_key = service_key self._notebook = QW.QTabWidget( self ) # eventually these will take 'regexoptions' or whatever object and 'showspecificfiles' as well self._simple_panel = self._SimplePanel( self._notebook, self._service_key, filename_tagging_options, present_for_accompanying_file_list ) self._advanced_panel = self._AdvancedPanel( self._notebook, self._service_key, filename_tagging_options, present_for_accompanying_file_list ) self._simple_panel.movePageLeft.connect( self.movePageLeft ) self._simple_panel.movePageRight.connect( self.movePageRight ) self._simple_panel.tagsChanged.connect( self.tagsChanged ) self._advanced_panel.tagsChanged.connect( self.tagsChanged ) self._notebook.addTab( self._simple_panel, 'simple' ) self._notebook.setCurrentWidget( self._simple_panel ) self._notebook.addTab( self._advanced_panel, 'advanced' ) vbox = QP.VBoxLayout() QP.AddToLayout( vbox, self._notebook, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS ) self.setLayout( vbox ) def GetFilenameTaggingOptions( self ): filename_tagging_options = TagImportOptions.FilenameTaggingOptions() self._advanced_panel.UpdateFilenameTaggingOptions( filename_tagging_options ) self._simple_panel.UpdateFilenameTaggingOptions( filename_tagging_options ) return filename_tagging_options def GetTags( self, index, path ): tags = set() tags.update( self._simple_panel.GetTags( index, path ) ) tags.update( self._advanced_panel.GetTags( index, path ) ) tags = HydrusTags.CleanTags( tags ) tags = CG.client_controller.tag_display_manager.FilterTags( ClientTags.TAG_DISPLAY_STORAGE, self._service_key, tags ) return tags def SetSearchFocus( self ): if self._notebook.currentWidget() == self._simple_panel: self._simple_panel.SetSearchFocus() def SetSelectedPaths( self, paths ): self._simple_panel.SetSelectedPaths( paths ) class _AdvancedPanel( QW.QWidget ): tagsChanged = QC.Signal() def __init__( self, parent, service_key, filename_tagging_options, present_for_accompanying_file_list ): QW.QWidget.__init__( self, parent ) self._service_key = service_key self._present_for_accompanying_file_list = present_for_accompanying_file_list # self._quick_namespaces_panel = ClientGUICommon.StaticBox( self, 'quick namespaces' ) quick_namespaces_listctrl_panel = ClientGUIListCtrl.BetterListCtrlPanel( self._quick_namespaces_panel ) self._quick_namespaces_list = ClientGUIListCtrl.BetterListCtrl( quick_namespaces_listctrl_panel, CGLC.COLUMN_LIST_QUICK_NAMESPACES.ID, 4, self._ConvertQuickRegexDataToListCtrlTuples, use_simple_delete = True, activation_callback = self.EditQuickNamespaces ) quick_namespaces_listctrl_panel.SetListCtrl( self._quick_namespaces_list ) quick_namespaces_listctrl_panel.AddButton( 'add', self.AddQuickNamespace ) quick_namespaces_listctrl_panel.AddButton( 'edit', self.EditQuickNamespaces, enabled_only_on_selection = True ) quick_namespaces_listctrl_panel.AddDeleteButton() # self._regexes_panel = ClientGUICommon.StaticBox( self, 'regexes' ) self._regexes = ClientGUIListBoxes.BetterQListWidget( self._regexes_panel ) self._regexes.itemDoubleClicked.connect( self.EventRemoveRegex ) self._regex_box = QW.QLineEdit() self._regex_box.installEventFilter( ClientGUICommon.TextCatchEnterEventFilter( self._regexes, self.AddRegex ) ) self._regex_shortcuts = ClientGUICommon.RegexButton( self._regexes_panel ) self._regex_intro_link = ClientGUICommon.BetterHyperLink( self._regexes_panel, 'a good regex introduction', 'https://www.aivosto.com/vbtips/regex.html' ) self._regex_practise_link = ClientGUICommon.BetterHyperLink( self._regexes_panel, 'regex practice', 'https://regexr.com/3cvmf' ) # self._num_panel = ClientGUICommon.StaticBox( self, '#' ) self._num_base = ClientGUICommon.BetterSpinBox( self._num_panel, min=-10000000, max=10000000, width = 60 ) self._num_base.setValue( 1 ) self._num_base.valueChanged.connect( self.tagsChanged ) self._num_step = ClientGUICommon.BetterSpinBox( self._num_panel, min=-1000000, max=1000000, width = 60 ) self._num_step.setValue( 1 ) self._num_step.valueChanged.connect( self.tagsChanged ) self._num_namespace = QW.QLineEdit() self._num_namespace.setFixedWidth( 100 ) self._num_namespace.textChanged.connect( self.tagsChanged ) if not self._present_for_accompanying_file_list: self._num_panel.hide() # ( quick_namespaces, regexes ) = filename_tagging_options.AdvancedToTuple() self._quick_namespaces_list.AddDatas( quick_namespaces ) self._quick_namespaces_list.Sort() for regex in regexes: self._regexes.addItem( regex ) # self._quick_namespaces_panel.Add( quick_namespaces_listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS ) # self._regexes_panel.Add( self._regexes, CC.FLAGS_EXPAND_BOTH_WAYS ) self._regexes_panel.Add( self._regex_box, CC.FLAGS_EXPAND_PERPENDICULAR ) self._regexes_panel.Add( self._regex_shortcuts, CC.FLAGS_ON_RIGHT ) self._regexes_panel.Add( self._regex_intro_link, CC.FLAGS_ON_RIGHT ) self._regexes_panel.Add( self._regex_practise_link, CC.FLAGS_ON_RIGHT ) # hbox = QP.HBoxLayout() QP.AddToLayout( hbox, QW.QLabel( '# base/step: ', self._num_panel ), CC.FLAGS_CENTER_PERPENDICULAR ) QP.AddToLayout( hbox, self._num_base, CC.FLAGS_CENTER_PERPENDICULAR ) QP.AddToLayout( hbox, self._num_step, CC.FLAGS_CENTER_PERPENDICULAR ) self._num_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR ) hbox = QP.HBoxLayout() QP.AddToLayout( hbox, QW.QLabel( '# namespace: ', self._num_panel ), CC.FLAGS_CENTER_PERPENDICULAR ) QP.AddToLayout( hbox, self._num_namespace, CC.FLAGS_EXPAND_BOTH_WAYS ) self._num_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR ) second_vbox = QP.VBoxLayout() QP.AddToLayout( second_vbox, self._regexes_panel, CC.FLAGS_EXPAND_BOTH_WAYS ) QP.AddToLayout( second_vbox, self._num_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) # hbox = QP.HBoxLayout() QP.AddToLayout( hbox, self._quick_namespaces_panel, CC.FLAGS_EXPAND_BOTH_WAYS ) QP.AddToLayout( hbox, second_vbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS ) self.setLayout( hbox ) self._quick_namespaces_list.columnListContentsChanged.connect( self.tagsChanged ) def _ConvertQuickRegexDataToListCtrlTuples( self, data ): ( namespace, regex ) = data display_tuple = ( namespace, regex ) sort_tuple = ( namespace, regex ) return ( display_tuple, sort_tuple ) def AddQuickNamespace( self ): with ClientGUIDialogs.DialogInputNamespaceRegex( self ) as dlg: if dlg.exec() == QW.QDialog.Accepted: ( namespace, regex ) = dlg.GetInfo() data = ( namespace, regex ) self._quick_namespaces_list.AddDatas( ( data, ) ) def EditQuickNamespaces( self ): edited_datas = [] data_to_edit = self._quick_namespaces_list.GetData( only_selected = True ) for old_data in data_to_edit: ( namespace, regex ) = old_data with ClientGUIDialogs.DialogInputNamespaceRegex( self, namespace = namespace, regex = regex ) as dlg: if dlg.exec() == QW.QDialog.Accepted: ( new_namespace, new_regex ) = dlg.GetInfo() new_data = ( new_namespace, new_regex ) if new_data != old_data: self._quick_namespaces_list.DeleteDatas( ( old_data, ) ) self._quick_namespaces_list.AddDatas( ( new_data, ) ) edited_datas.append( new_data ) self._quick_namespaces_list.SelectDatas( edited_datas ) def AddRegex( self ): regex = self._regex_box.text() if regex != '': try: re.compile( regex ) except Exception as e: text = 'That regex would not compile!' text += '\n' * 2 text += str( e ) ClientGUIDialogsMessage.ShowWarning( self, text ) return self._regexes.addItem( regex ) self._regex_box.clear() self.tagsChanged.emit() def EventRemoveRegex( self, item ): selection = QP.ListWidgetGetSelection( self._regexes ) if selection != -1: if len( self._regex_box.text() ) == 0: self._regex_box.setText( self._regexes.item( selection ).text() ) QP.ListWidgetDelete( self._regexes, selection ) self.tagsChanged.emit() def GetTags( self, index, path ): tags = set() num_namespace = self._num_namespace.text() if num_namespace != '': num_base = self._num_base.value() num_step = self._num_step.value() tag_num = num_base + index * num_step tags.add( num_namespace + ':' + str( tag_num ) ) return tags def UpdateFilenameTaggingOptions( self, filename_tagging_options ): quick_namespaces = self._quick_namespaces_list.GetData() regexes = QP.ListWidgetGetStrings( self._regexes ) filename_tagging_options.AdvancedSetTuple( quick_namespaces, regexes ) class _SimplePanel( QW.QWidget ): movePageLeft = QC.Signal() movePageRight = QC.Signal() tagsChanged = QC.Signal() def __init__( self, parent, service_key, filename_tagging_options, present_for_accompanying_file_list ): QW.QWidget.__init__( self, parent ) self._service_key = service_key self._present_for_accompanying_file_list = present_for_accompanying_file_list # self._tags_panel = ClientGUICommon.StaticBox( self, 'tags for all' ) self._tags = ClientGUIListBoxes.ListBoxTagsStringsAddRemove( self._tags_panel, self._service_key, tag_display_type = ClientTags.TAG_DISPLAY_STORAGE ) default_location_context = CG.client_controller.new_options.GetDefaultLocalLocationContext() self._tag_autocomplete_all = ClientGUIACDropdown.AutoCompleteDropdownTagsWrite( self._tags_panel, self.EnterTags, default_location_context, service_key, show_paste_button = True ) self._tag_autocomplete_all.movePageLeft.connect( self.movePageLeft ) self._tag_autocomplete_all.movePageRight.connect( self.movePageRight ) self._tags_paste_button = ClientGUICommon.BetterButton( self._tags_panel, 'paste tags', self._PasteTags ) # self._single_tags_panel = ClientGUICommon.StaticBox( self, 'tags just for selected files' ) self._paths_to_single_tags = collections.defaultdict( set ) self._single_tags = ClientGUIListBoxes.ListBoxTagsStringsAddRemove( self._single_tags_panel, self._service_key, tag_display_type = ClientTags.TAG_DISPLAY_STORAGE ) self._single_tags_paste_button = ClientGUICommon.BetterButton( self._single_tags_panel, 'paste tags', self._PasteSingleTags ) self._tag_autocomplete_selection = ClientGUIACDropdown.AutoCompleteDropdownTagsWrite( self._single_tags_panel, self.EnterTagsSingle, default_location_context, service_key, show_paste_button = True ) self._tag_autocomplete_selection.movePageLeft.connect( self.movePageLeft ) self._tag_autocomplete_selection.movePageRight.connect( self.movePageRight ) self.SetSelectedPaths( [] ) if not self._present_for_accompanying_file_list: self._single_tags_panel.hide() # self._checkboxes_panel = ClientGUICommon.StaticBox( self, 'misc' ) self._filename_namespace = QW.QLineEdit( self._checkboxes_panel ) self._filename_namespace.setMinimumWidth( 100 ) self._filename_checkbox = QW.QCheckBox( 'add filename? [namespace]', self._checkboxes_panel ) # TODO: Ok, when we move to arbitrary string processing from filenames, and we scrub this, we will want 'easy-add rule' buttons to do this # When we do, add a thing that adds the nth, including negative n index values. as some users want four deep # there must be a way to wangle this code into String Processors self._directory_namespace_controls = {} directory_items = [] directory_items.append( ( 0, 'first' ) ) directory_items.append( ( 1, 'second' ) ) directory_items.append( ( 2, 'third' ) ) directory_items.append( ( -3, 'third last' ) ) directory_items.append( ( -2, 'second last' ) ) directory_items.append( ( -1, 'last' ) ) for ( index, phrase ) in directory_items: dir_checkbox = QW.QCheckBox( 'add '+phrase+' directory? [namespace]', self._checkboxes_panel ) dir_namespace_textctrl = QW.QLineEdit( self._checkboxes_panel ) dir_namespace_textctrl.setMinimumWidth( 100 ) self._directory_namespace_controls[ index ] = ( dir_checkbox, dir_namespace_textctrl ) # ( tags_for_all, add_filename, directory_dict ) = filename_tagging_options.SimpleToTuple() self._tags.AddTags( tags_for_all ) ( add_filename_boolean, add_filename_namespace ) = add_filename self._filename_checkbox.setChecked( add_filename_boolean ) self._filename_namespace.setText( add_filename_namespace ) for ( index, ( dir_boolean, dir_namespace ) ) in directory_dict.items(): ( dir_checkbox, dir_namespace_textctrl ) = self._directory_namespace_controls[ index ] dir_checkbox.setChecked( dir_boolean ) dir_namespace_textctrl.setText( dir_namespace ) dir_checkbox.clicked.connect( self.tagsChanged ) dir_namespace_textctrl.textChanged.connect( self.tagsChanged ) # self._tags_panel.Add( self._tags, CC.FLAGS_EXPAND_BOTH_WAYS ) self._tags_panel.Add( self._tag_autocomplete_all, CC.FLAGS_EXPAND_PERPENDICULAR ) self._tags_panel.Add( self._tags_paste_button, CC.FLAGS_EXPAND_PERPENDICULAR ) self._single_tags_panel.Add( self._single_tags, CC.FLAGS_EXPAND_BOTH_WAYS ) self._single_tags_panel.Add( self._tag_autocomplete_selection, CC.FLAGS_EXPAND_PERPENDICULAR ) self._single_tags_panel.Add( self._single_tags_paste_button, CC.FLAGS_EXPAND_PERPENDICULAR ) filename_hbox = QP.HBoxLayout() QP.AddToLayout( filename_hbox, self._filename_checkbox, CC.FLAGS_CENTER_PERPENDICULAR ) QP.AddToLayout( filename_hbox, self._filename_namespace, CC.FLAGS_EXPAND_BOTH_WAYS ) self._checkboxes_panel.Add( filename_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR ) for index in ( 0, 1, 2, -3, -2, -1 ): hbox = QP.HBoxLayout() ( dir_checkbox, dir_namespace_textctrl ) = self._directory_namespace_controls[ index ] QP.AddToLayout( hbox, dir_checkbox, CC.FLAGS_CENTER_PERPENDICULAR ) QP.AddToLayout( hbox, dir_namespace_textctrl, CC.FLAGS_EXPAND_BOTH_WAYS ) self._checkboxes_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR ) self._checkboxes_panel.Add( QW.QWidget( self._checkboxes_panel ), CC.FLAGS_EXPAND_BOTH_WAYS ) hbox = QP.HBoxLayout() QP.AddToLayout( hbox, self._tags_panel, CC.FLAGS_EXPAND_BOTH_WAYS ) QP.AddToLayout( hbox, self._single_tags_panel, CC.FLAGS_EXPAND_BOTH_WAYS ) QP.AddToLayout( hbox, self._checkboxes_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) self.setLayout( hbox ) # self._tags.tagsRemoved.connect( self.tagsChanged ) self._single_tags.tagsRemoved.connect( self.SingleTagsRemoved ) self._filename_namespace.textChanged.connect( self.tagsChanged ) self._filename_checkbox.clicked.connect( self.tagsChanged ) self._tag_autocomplete_all.tagsPasted.connect( self.EnterTagsOnlyAdd ) self._tag_autocomplete_selection.tagsPasted.connect( self.EnterTagsSingleOnlyAdd ) def _GetTagsFromClipboard( self ): try: raw_text = CG.client_controller.GetClipboardText() except HydrusExceptions.DataMissing as e: HydrusData.PrintException( e ) ClientGUIDialogsMessage.ShowCritical( self, 'Problem pasting!', str(e) ) return try: tags = HydrusText.DeserialiseNewlinedTexts( raw_text ) tags = HydrusTags.CleanTags( tags ) return tags except Exception as e: ClientGUIDialogsQuick.PresentClipboardParseError( self, raw_text, 'Lines of tags', e ) raise def _PasteTags( self ): try: tags = self._GetTagsFromClipboard() except Exception as e: HydrusData.PrintException( e ) ClientGUIDialogsMessage.ShowCritical( self, 'Problem pasting!', str(e) ) return self.EnterTags( tags ) def _PasteSingleTags( self ): try: tags = self._GetTagsFromClipboard() except Exception as e: HydrusData.PrintException( e ) ClientGUIDialogsMessage.ShowCritical( self, 'Problem pasting!', str(e) ) return self.EnterTagsSingle( tags ) def EnterTags( self, tags ): CG.client_controller.Write( 'push_recent_tags', self._service_key, tags ) if len( tags ) > 0: self._tags.AddTags( tags ) self.tagsChanged.emit() def EnterTagsOnlyAdd( self, tags ): current_tags = self._tags.GetTags() tags = { tag for tag in tags if tag not in current_tags } if len( tags ) > 0: self.EnterTags( tags ) def EnterTagsSingle( self, tags ): CG.client_controller.Write( 'push_recent_tags', self._service_key, tags ) if len( tags ) > 0: self._single_tags.AddTags( tags ) for path in self._selected_paths: current_tags = self._paths_to_single_tags[ path ] current_tags.update( tags ) self.tagsChanged.emit() def EnterTagsSingleOnlyAdd( self, tags ): current_tags = self._single_tags.GetTags() tags = { tag for tag in tags if tag not in current_tags } if len( tags ) > 0: self.EnterTagsSingle( tags ) def GetTags( self, index, path ): tags = set() if path in self._paths_to_single_tags: tags.update( self._paths_to_single_tags[ path ] ) return tags def SingleTagsRemoved( self ): tags = self._single_tags.GetTags() for path in self._selected_paths: self._paths_to_single_tags[ path ].intersection_update( tags ) self.tagsChanged.emit() def SetSearchFocus( self ): ClientGUIFunctions.SetFocusLater( self._tag_autocomplete_all ) def SetSelectedPaths( self, paths ): self._selected_paths = paths single_tags = set() if len( paths ) > 0: for path in self._selected_paths: if path in self._paths_to_single_tags: single_tags.update( self._paths_to_single_tags[ path ] ) self._tag_autocomplete_selection.setEnabled( True ) self._single_tags_paste_button.setEnabled( True ) else: self._tag_autocomplete_selection.setEnabled( False ) self._single_tags_paste_button.setEnabled( False ) self._single_tags.SetTags( single_tags ) def TagsRemoved( self, *args ): self.tagsChanged.emit() def UpdateFilenameTaggingOptions( self, filename_tagging_options ): tags_for_all = self._tags.GetTags() add_filename = ( self._filename_checkbox.isChecked(), self._filename_namespace.text() ) directories_dict = {} for ( index, ( dir_checkbox, dir_namespace_textctrl ) ) in self._directory_namespace_controls.items(): directories_dict[ index ] = ( dir_checkbox.isChecked(), dir_namespace_textctrl.text() ) filename_tagging_options.SimpleSetTuple( tags_for_all, add_filename, directories_dict ) class EditLocalImportFilenameTaggingPanel( ClientGUIScrolledPanels.EditPanel ): def __init__( self, parent, paths ): # TODO: a really nice rewrite for all this, perhaps when I go for string conversions here, would be to eliminate the multi-page format and instead update the controls. # an option here is to mutate the sidecars a little and just have a 'filename' source. this could wangle everything neatly and send to whatever service # but only if the UI can stay helpful. maybe we shouldn't replace the easy UI, but we can replace the guts behind the scenes with metadata routers # however, changing service while maintaining focus and list selection would be great ClientGUIScrolledPanels.EditPanel.__init__( self, parent ) self._paths = paths self._filename_tagging_option_pages = [] self._notebook = ClientGUICommon.BetterNotebook( self ) # # TODO: could have default import here and favourites system metadata_routers = [] self._metadata_router_page = self._MetadataRoutersPanel( self._notebook, metadata_routers, paths ) self._notebook.addTab( self._metadata_router_page, 'sidecars' ) services = CG.client_controller.services_manager.GetServices( HC.REAL_TAG_SERVICES ) default_tag_service_key = CG.client_controller.new_options.GetKey( 'default_tag_service_tab' ) for service in services: service_key = service.GetServiceKey() name = service.GetName() page = self._FilenameTaggingOptionsPanel( self._notebook, service_key, paths ) self._filename_tagging_option_pages.append( page ) page.movePageLeft.connect( self.MovePageLeft ) page.movePageRight.connect( self.MovePageRight ) tab_index = self._notebook.addTab( page, name ) if service_key == default_tag_service_key: # Py 3.11/PyQt6 6.5.0/two tabs/total tab characters > ~12/select second tab = first tab disappears bug QP.CallAfter( self._notebook.setCurrentWidget, page ) # vbox = QP.VBoxLayout() QP.AddToLayout( vbox, self._notebook, CC.FLAGS_EXPAND_BOTH_WAYS ) self.widget().setLayout( vbox ) self._notebook.currentChanged.connect( self._SaveDefaultTagServiceKey ) QP.CallAfter( self._SetSearchFocus ) def _SaveDefaultTagServiceKey( self ): if self.sender() != self._notebook: return if CG.client_controller.new_options.GetBoolean( 'save_default_tag_service_tab_on_change' ): current_page = self._notebook.currentWidget() if current_page in self._filename_tagging_option_pages: CG.client_controller.new_options.SetKey( 'default_tag_service_tab', current_page.GetServiceKey() ) def _SetSearchFocus( self ): self._notebook.currentWidget().SetSearchFocus() def GetValue( self ): metadata_routers = self._metadata_router_page.GetValue() paths_to_additional_service_keys_to_tags = collections.defaultdict( ClientTags.ServiceKeysToTags ) for page in self._filename_tagging_option_pages: ( service_key, paths_to_tags ) = page.GetInfo() for ( path, tags ) in paths_to_tags.items(): if len( tags ) == 0: continue paths_to_additional_service_keys_to_tags[ path ][ service_key ] = tags return ( metadata_routers, paths_to_additional_service_keys_to_tags ) def MovePageLeft( self ): self._notebook.SelectLeft() self._notebook.currentWidget().SetSearchFocus() def MovePageRight( self ): self._notebook.SelectRight() self._notebook.currentWidget().SetSearchFocus() class _FilenameTaggingOptionsPanel( QW.QWidget ): movePageLeft = QC.Signal() movePageRight = QC.Signal() def __init__( self, parent, service_key, paths ): QW.QWidget.__init__( self, parent ) self._service_key = service_key self._paths = paths self._paths_list = ClientGUIListCtrl.BetterListCtrl( self, CGLC.COLUMN_LIST_PATHS_TO_TAGS.ID, 10, self._ConvertDataToListCtrlTuples ) self._paths_list.itemSelectionChanged.connect( self.EventItemSelected ) # self._filename_tagging_panel = FilenameTaggingOptionsPanel( self, self._service_key, present_for_accompanying_file_list = True ) self._filename_tagging_panel.movePageLeft.connect( self.movePageLeft ) self._filename_tagging_panel.movePageRight.connect( self.movePageRight ) self._schedule_refresh_file_list_job = None # # i.e. ( index, path ) self._paths_list.AddDatas( list( enumerate( self._paths ) ) ) # vbox = QP.VBoxLayout() QP.AddToLayout( vbox, self._paths_list, CC.FLAGS_EXPAND_BOTH_WAYS ) QP.AddToLayout( vbox, self._filename_tagging_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) self.setLayout( vbox ) self._filename_tagging_panel.tagsChanged.connect( self.ScheduleRefreshFileList ) def _ConvertDataToListCtrlTuples( self, data ): ( index, path ) = data tags = self._GetTags( index, path ) pretty_index = HydrusData.ToHumanInt( index + 1 ) pretty_path = path pretty_tags = ', '.join( tags ) display_tuple = ( pretty_index, pretty_path, pretty_tags ) sort_tuple = ( index, path, tags ) return ( display_tuple, sort_tuple ) def _GetTags( self, index, path ): filename_tagging_options = self._filename_tagging_panel.GetFilenameTaggingOptions() tags = filename_tagging_options.GetTags( self._service_key, path ) tags.update( self._filename_tagging_panel.GetTags( index, path ) ) tags = sorted( tags ) return tags def EventItemSelected( self ): paths = [ path for ( index, path ) in self._paths_list.GetData( only_selected = True ) ] self._filename_tagging_panel.SetSelectedPaths( paths ) def GetInfo( self ): paths_to_tags = { path : self._GetTags( index, path ) for ( index, path ) in self._paths_list.GetData() } return ( self._service_key, paths_to_tags ) def GetServiceKey( self ): return self._service_key def RefreshFileList( self ): self._paths_list.UpdateDatas() def ScheduleRefreshFileList( self ): if self._schedule_refresh_file_list_job is not None: self._schedule_refresh_file_list_job.Cancel() self._schedule_refresh_file_list_job = None self._schedule_refresh_file_list_job = CG.client_controller.CallLaterQtSafe( self, 0.5, 'refresh path list', self.RefreshFileList ) def SetSearchFocus( self ): self._filename_tagging_panel.SetSearchFocus() class _MetadataRoutersPanel( QW.QWidget ): def __init__( self, parent, metadata_routers, paths ): QW.QWidget.__init__( self, parent ) self._paths = paths self._paths_list = ClientGUIListCtrl.BetterListCtrl( self, CGLC.COLUMN_LIST_PATHS_TO_TAGS.ID, 10, self._ConvertDataToListCtrlTuples ) allowed_importer_classes = [ ClientMetadataMigrationImporters.SingleFileMetadataImporterTXT, ClientMetadataMigrationImporters.SingleFileMetadataImporterJSON ] allowed_exporter_classes = [ ClientMetadataMigrationExporters.SingleFileMetadataExporterMediaTags, ClientMetadataMigrationExporters.SingleFileMetadataExporterMediaNotes, ClientMetadataMigrationExporters.SingleFileMetadataExporterMediaURLs, ClientMetadataMigrationExporters.SingleFileMetadataExporterMediaTimestamps ] self._metadata_routers_panel = ClientGUIMetadataMigration.SingleFileMetadataRoutersControl( self, metadata_routers, allowed_importer_classes, allowed_exporter_classes ) # self._schedule_refresh_file_list_job = None # # i.e. ( index, path ) self._paths_list.AddDatas( list( enumerate( self._paths ) ) ) # vbox = QP.VBoxLayout() QP.AddToLayout( vbox, self._paths_list, CC.FLAGS_EXPAND_BOTH_WAYS ) QP.AddToLayout( vbox, self._metadata_routers_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) self.setLayout( vbox ) self._metadata_routers_panel.listBoxChanged.connect( self.ScheduleRefreshFileList ) def _ConvertDataToListCtrlTuples( self, data ): ( index, path ) = data strings = self._GetStrings( path ) pretty_index = HydrusData.ToHumanInt( index + 1 ) pretty_path = path pretty_strings = ', '.join( strings ) display_tuple = ( pretty_index, pretty_path, pretty_strings ) sort_tuple = ( index, path, strings ) return ( display_tuple, sort_tuple ) def _GetStrings( self, path ): strings = [] metadata_routers = self._metadata_routers_panel.GetValue() for router in metadata_routers: pre_processed_strings = set() for importer in router.GetImporters(): if isinstance( importer, ClientMetadataMigrationImporters.SingleFileMetadataImporterSidecar ): pre_processed_strings.update( importer.Import( path ) ) else: continue if len( pre_processed_strings ) == 0: continue processed_strings = router.GetStringProcessor().ProcessStrings( pre_processed_strings ) exporter = router.GetExporter() if isinstance( exporter, ClientMetadataMigrationExporters.SingleFileMetadataExporterMediaTags ): processed_strings = HydrusTags.CleanTags( processed_strings ) strings.extend( sorted( processed_strings ) ) return strings def GetValue( self ): return self._metadata_routers_panel.GetValue() def RefreshFileList( self ): self._paths_list.UpdateDatas() def ScheduleRefreshFileList( self ): if self._schedule_refresh_file_list_job is not None: self._schedule_refresh_file_list_job.Cancel() self._schedule_refresh_file_list_job = None self._schedule_refresh_file_list_job = CG.client_controller.CallLaterQtSafe( self, 0.5, 'refresh path list', self.RefreshFileList ) def SetSearchFocus( self ): pass class EditFilenameTaggingOptionPanel( ClientGUIScrolledPanels.EditPanel ): def __init__( self, parent, service_key, filename_tagging_options ): ClientGUIScrolledPanels.EditPanel.__init__( self, parent ) self._service_key = service_key self._example_path_input = QW.QLineEdit( self ) self._example_output = QW.QLineEdit( self ) self._filename_tagging_options_panel = FilenameTaggingOptionsPanel( self, self._service_key, filename_tagging_options = filename_tagging_options, present_for_accompanying_file_list = False ) self._schedule_refresh_tags_job = None # self._example_path_input.setPlaceholderText( 'enter example path here' ) self._example_output.setEnabled( False ) # vbox = QP.VBoxLayout() QP.AddToLayout( vbox, self._example_path_input, CC.FLAGS_EXPAND_PERPENDICULAR ) QP.AddToLayout( vbox, self._example_output, CC.FLAGS_EXPAND_PERPENDICULAR ) QP.AddToLayout( vbox, self._filename_tagging_options_panel, CC.FLAGS_EXPAND_BOTH_WAYS ) self.widget().setLayout( vbox ) # self._example_path_input.textChanged.connect( self.ScheduleRefreshTags ) self._filename_tagging_options_panel.tagsChanged.connect( self.ScheduleRefreshTags ) def GetValue( self ): return self._filename_tagging_options_panel.GetFilenameTaggingOptions() def RefreshTags( self ): example_path_input = self._example_path_input.text() filename_tagging_options = self.GetValue() try: tags = filename_tagging_options.GetTags( self._service_key, example_path_input ) except: tags = [ 'could not parse' ] self._example_output.setText( ', '.join( tags ) ) def ScheduleRefreshTags( self ): path = self._example_path_input.text() if path.startswith( '"' ) and path.endswith( '"' ): path = path[1:-1] self._example_path_input.setText( path ) if path.startswith( 'file:///' ): path = path.replace( 'file:///', '', 1 ) path = os.path.normpath( path ) self._example_path_input.setText( path ) if self._schedule_refresh_tags_job is not None: self._schedule_refresh_tags_job.Cancel() self._schedule_refresh_tags_job = None self._schedule_refresh_tags_job = CG.client_controller.CallLaterQtSafe( self, 0.5, 'refresh tags', self.RefreshTags ) class GalleryImportPanel( ClientGUICommon.StaticBox ): def __init__( self, parent, page_key, name = 'gallery query' ): ClientGUICommon.StaticBox.__init__( self, parent, name ) self._page_key = page_key self._gallery_import = None # self._query_text = QW.QLineEdit( self ) self._query_text.setReadOnly( True ) self._import_queue_panel = ClientGUICommon.StaticBox( self, 'imports' ) self._file_status = ClientGUICommon.BetterStaticText( self._import_queue_panel, ellipsize_end = True ) self._file_seed_cache_control = ClientGUIFileSeedCache.FileSeedCacheStatusControl( self._import_queue_panel, CG.client_controller, self._page_key ) self._file_download_control = ClientGUINetworkJobControl.NetworkJobControl( self._import_queue_panel ) self._files_pause_button = ClientGUICommon.BetterBitmapButton( self._import_queue_panel, CC.global_pixmaps().file_pause, self.PauseFiles ) self._files_pause_button.setToolTip( ClientGUIFunctions.WrapToolTip( 'pause/play files' ) ) self._gallery_panel = ClientGUICommon.StaticBox( self, 'search' ) self._gallery_status = ClientGUICommon.BetterStaticText( self._gallery_panel, ellipsize_end = True ) self._gallery_pause_button = ClientGUICommon.BetterBitmapButton( self._gallery_panel, CC.global_pixmaps().gallery_pause, self.PauseGallery ) self._gallery_pause_button.setToolTip( ClientGUIFunctions.WrapToolTip( 'pause/play search' ) ) self._gallery_seed_log_control = ClientGUIGallerySeedLog.GallerySeedLogStatusControl( self._gallery_panel, CG.client_controller, False, True, 'search', page_key = self._page_key ) self._gallery_download_control = ClientGUINetworkJobControl.NetworkJobControl( self._gallery_panel ) self._file_limit = ClientGUICommon.NoneableSpinCtrl( self, 'stop after this many files', min = 1, none_phrase = 'no limit' ) self._file_limit.valueChanged.connect( self.EventFileLimit ) self._file_limit.setToolTip( ClientGUIFunctions.WrapToolTip( 'stop searching the gallery once this many files has been reached' ) ) file_import_options = FileImportOptions.FileImportOptions() file_import_options.SetIsDefault( True ) tag_import_options = TagImportOptions.TagImportOptions( is_default = True ) note_import_options = NoteImportOptions.NoteImportOptions() note_import_options.SetIsDefault( True ) show_downloader_options = True allow_default_selection = True self._import_options_button = ClientGUIImportOptions.ImportOptionsButton( self, show_downloader_options, allow_default_selection ) self._import_options_button.SetFileImportOptions( file_import_options ) self._import_options_button.SetTagImportOptions( tag_import_options ) self._import_options_button.SetNoteImportOptions( note_import_options ) # hbox = QP.HBoxLayout() QP.AddToLayout( hbox, self._gallery_status, CC.FLAGS_CENTER_PERPENDICULAR_EXPAND_DEPTH ) QP.AddToLayout( hbox, self._gallery_pause_button, CC.FLAGS_CENTER_PERPENDICULAR ) self._gallery_panel.Add( hbox, CC.FLAGS_EXPAND_PERPENDICULAR ) self._gallery_panel.Add( self._gallery_seed_log_control, CC.FLAGS_EXPAND_PERPENDICULAR ) self._gallery_panel.Add( self._gallery_download_control, CC.FLAGS_EXPAND_PERPENDICULAR ) # hbox = QP.HBoxLayout() QP.AddToLayout( hbox, self._file_status, CC.FLAGS_CENTER_PERPENDICULAR_EXPAND_DEPTH ) QP.AddToLayout( hbox, self._files_pause_button, CC.FLAGS_CENTER_PERPENDICULAR ) self._import_queue_panel.Add( hbox, CC.FLAGS_EXPAND_PERPENDICULAR ) self._import_queue_panel.Add( self._file_seed_cache_control, CC.FLAGS_EXPAND_PERPENDICULAR ) self._import_queue_panel.Add( self._file_download_control, CC.FLAGS_EXPAND_PERPENDICULAR ) self.Add( self._query_text, CC.FLAGS_EXPAND_PERPENDICULAR ) self.Add( self._import_queue_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) self.Add( self._gallery_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) self.Add( self._file_limit, CC.FLAGS_EXPAND_PERPENDICULAR ) self.Add( self._import_options_button, CC.FLAGS_EXPAND_PERPENDICULAR ) # self._import_options_button.fileImportOptionsChanged.connect( self._SetFileImportOptions ) self._import_options_button.noteImportOptionsChanged.connect( self._SetNoteImportOptions ) self._import_options_button.tagImportOptionsChanged.connect( self._SetTagImportOptions ) self._UpdateControlsForNewGalleryImport() CG.client_controller.gui.RegisterUIUpdateWindow( self ) def _SetFileImportOptions( self, file_import_options: FileImportOptions.FileImportOptions ): if self._gallery_import is not None: self._gallery_import.SetFileImportOptions( file_import_options ) def _SetNoteImportOptions( self, note_import_options: NoteImportOptions.NoteImportOptions ): if self._gallery_import is not None: self._gallery_import.SetNoteImportOptions( note_import_options ) def _SetTagImportOptions( self, tag_import_options: TagImportOptions.TagImportOptions ): if self._gallery_import is not None: self._gallery_import.SetTagImportOptions( tag_import_options ) def _UpdateControlsForNewGalleryImport( self ): if self._gallery_import is None: self._import_queue_panel.setEnabled( False ) self._gallery_panel.setEnabled( False ) self._file_limit.setEnabled( False ) self._import_options_button.setEnabled( False ) self._query_text.clear() self._file_status.clear() self._gallery_status.clear() self._file_seed_cache_control.SetFileSeedCache( None ) self._gallery_seed_log_control.SetGallerySeedLog( None ) self._file_download_control.ClearNetworkJob() self._gallery_download_control.ClearNetworkJob() else: self._import_queue_panel.setEnabled( True ) self._gallery_panel.setEnabled( True ) self._file_limit.setEnabled( True ) self._import_options_button.setEnabled( True ) query = self._gallery_import.GetQueryText() self._query_text.setText( query ) file_limit = self._gallery_import.GetFileLimit() self._file_limit.SetValue( file_limit ) file_import_options = self._gallery_import.GetFileImportOptions() tag_import_options = self._gallery_import.GetTagImportOptions() note_import_options = self._gallery_import.GetNoteImportOptions() self._import_options_button.SetFileImportOptions( file_import_options ) self._import_options_button.SetTagImportOptions( tag_import_options ) self._import_options_button.SetNoteImportOptions( note_import_options ) file_seed_cache = self._gallery_import.GetFileSeedCache() self._file_seed_cache_control.SetFileSeedCache( file_seed_cache ) gallery_seed_log = self._gallery_import.GetGallerySeedLog() self._gallery_seed_log_control.SetGallerySeedLog( gallery_seed_log ) def _UpdateStatus( self ): if self._gallery_import is not None: ( gallery_status, file_status, files_paused, gallery_paused ) = self._gallery_import.GetStatus() if files_paused: ClientGUIFunctions.SetBitmapButtonBitmap( self._files_pause_button, CC.global_pixmaps().file_play ) else: ClientGUIFunctions.SetBitmapButtonBitmap( self._files_pause_button, CC.global_pixmaps().file_pause ) if gallery_paused: ClientGUIFunctions.SetBitmapButtonBitmap( self._gallery_pause_button, CC.global_pixmaps().gallery_play ) else: ClientGUIFunctions.SetBitmapButtonBitmap( self._gallery_pause_button, CC.global_pixmaps().gallery_pause ) self._gallery_status.setText( gallery_status ) self._file_status.setText( file_status ) ( file_network_job, gallery_network_job ) = self._gallery_import.GetNetworkJobs() if file_network_job is None: self._file_download_control.ClearNetworkJob() else: self._file_download_control.SetNetworkJob( file_network_job ) if gallery_network_job is None: self._gallery_download_control.ClearNetworkJob() else: self._gallery_download_control.SetNetworkJob( gallery_network_job ) def EventFileLimit( self ): if self._gallery_import is not None: self._gallery_import.SetFileLimit( self._file_limit.GetValue() ) def PauseFiles( self ): if self._gallery_import is not None: self._gallery_import.PausePlayFiles() self._UpdateStatus() def PauseGallery( self ): if self._gallery_import is not None: self._gallery_import.PausePlayGallery() self._UpdateStatus() def SetGalleryImport( self, gallery_import ): self._gallery_import = gallery_import self._UpdateControlsForNewGalleryImport() def TIMERUIUpdate( self ): if CG.client_controller.gui.IShouldRegularlyUpdate( self ): self._UpdateStatus() class GUGKeyAndNameSelector( ClientGUICommon.BetterButton ): def __init__( self, parent, gug_key_and_name, update_callable = None ): ClientGUICommon.BetterButton.__init__( self, parent, 'gallery selector', self._Edit ) gug = CG.client_controller.network_engine.domain_manager.GetGUG( gug_key_and_name ) if gug is not None: gug_key_and_name = gug.GetGUGKeyAndName() self._gug_key_and_name = gug_key_and_name self._update_callable = update_callable self._SetLabel() def _Edit( self ): domain_manager = CG.client_controller.network_engine.domain_manager # maybe relegate to hidden page and something like "(does not work)" if no gallery url class match my_gug = domain_manager.GetGUG( self._gug_key_and_name ) gugs = list( domain_manager.GetGUGs() ) gug_keys_to_display = domain_manager.GetGUGKeysToDisplay() gugs.sort( key = lambda g: g.GetName() ) functional_gugs = [] non_functional_gugs = [] for gug in gugs: if gug.IsFunctional(): functional_gugs.append( gug ) else: non_functional_gugs.append( gug ) choice_tuples = [ ( gug.GetName(), gug ) for gug in functional_gugs if gug.GetGUGKey() in gug_keys_to_display ] second_choice_tuples = [ ( gug.GetName(), gug ) for gug in functional_gugs if gug.GetGUGKey() not in gug_keys_to_display ] if len( second_choice_tuples ) > 0: choice_tuples.append( ( '--other galleries', -1 ) ) if len( non_functional_gugs ) > 0: non_functional_choice_tuples = [] for gug in non_functional_gugs: s = gug.GetName() try: gug.CheckFunctional() except HydrusExceptions.ParseException as e: s = '{} ({})'.format( gug.GetName(), e ) non_functional_choice_tuples.append( ( s, gug ) ) choice_tuples.append( ( '--non-functional galleries', -2 ) ) try: gug = ClientGUIDialogsQuick.SelectFromList( self, 'select gallery', choice_tuples, value_to_select = my_gug, sort_tuples = False ) except HydrusExceptions.CancelledException: return if gug == -1: try: gug = ClientGUIDialogsQuick.SelectFromList( self, 'select gallery', second_choice_tuples, value_to_select = my_gug, sort_tuples = False ) except HydrusExceptions.CancelledException: return elif gug == -2: try: gug = ClientGUIDialogsQuick.SelectFromList( self, 'select gallery', non_functional_choice_tuples, value_to_select = my_gug, sort_tuples = False ) except HydrusExceptions.CancelledException: return gug_key_and_name = gug.GetGUGKeyAndName() self._SetValue( gug_key_and_name ) def _SetLabel( self ): label = self._gug_key_and_name[1] gug = CG.client_controller.network_engine.domain_manager.GetGUG( self._gug_key_and_name ) if gug is None: label = 'not found: ' + label self.setText( label ) def _SetValue( self, gug_key_and_name ): self._gug_key_and_name = gug_key_and_name self._SetLabel() if self._update_callable is not None: self._update_callable( gug_key_and_name ) def GetValue( self ): return self._gug_key_and_name def SetValue( self, gug_key_and_name ): self._SetValue( gug_key_and_name ) class WatcherReviewPanel( ClientGUICommon.StaticBox ): def __init__( self, parent, page_key, name = 'watcher' ): ClientGUICommon.StaticBox.__init__( self, parent, name ) self._page_key = page_key self._watcher = None self._watcher_subject = ClientGUICommon.BetterStaticText( self, ellipsize_end = True ) self._watcher_url = QW.QLineEdit( self ) self._watcher_url.setReadOnly( True ) self._options_panel = QW.QWidget( self ) # imports_panel = ClientGUICommon.StaticBox( self._options_panel, 'imports' ) self._files_pause_button = ClientGUICommon.BetterBitmapButton( imports_panel, CC.global_pixmaps().file_pause, self.PauseFiles ) self._files_pause_button.setToolTip( ClientGUIFunctions.WrapToolTip( 'pause/play files' ) ) self._file_status = ClientGUICommon.BetterStaticText( imports_panel, ellipsize_end = True ) self._file_seed_cache_control = ClientGUIFileSeedCache.FileSeedCacheStatusControl( imports_panel, CG.client_controller, self._page_key ) self._file_download_control = ClientGUINetworkJobControl.NetworkJobControl( imports_panel ) # checker_panel = ClientGUICommon.StaticBox( self._options_panel, 'checker' ) self._file_velocity_status = ClientGUICommon.BetterStaticText( checker_panel, ellipsize_end = True ) self._checking_pause_button = ClientGUICommon.BetterBitmapButton( checker_panel, CC.global_pixmaps().gallery_pause, self.PauseChecking ) self._checking_pause_button.setToolTip( ClientGUIFunctions.WrapToolTip( 'pause/play checking' ) ) self._watcher_status = ClientGUICommon.BetterStaticText( checker_panel, ellipsize_end = True ) self._check_now_button = QW.QPushButton( 'check now', checker_panel ) self._check_now_button.clicked.connect( self.EventCheckNow ) self._gallery_seed_log_control = ClientGUIGallerySeedLog.GallerySeedLogStatusControl( checker_panel, CG.client_controller, True, False, 'check', page_key = self._page_key ) checker_options = ClientImportOptions.CheckerOptions() self._checker_options_button = CheckerOptionsButton( checker_panel, checker_options ) self._checker_download_control = ClientGUINetworkJobControl.NetworkJobControl( checker_panel ) file_import_options = FileImportOptions.FileImportOptions() file_import_options.SetIsDefault( True ) tag_import_options = TagImportOptions.TagImportOptions( is_default = True ) note_import_options = NoteImportOptions.NoteImportOptions() note_import_options.SetIsDefault( True ) show_downloader_options = True allow_default_selection = True self._import_options_button = ClientGUIImportOptions.ImportOptionsButton( self, show_downloader_options, allow_default_selection ) self._import_options_button.SetFileImportOptions( file_import_options ) self._import_options_button.SetTagImportOptions( tag_import_options ) self._import_options_button.SetNoteImportOptions( note_import_options ) # hbox = QP.HBoxLayout() QP.AddToLayout( hbox, self._file_status, CC.FLAGS_CENTER_PERPENDICULAR_EXPAND_DEPTH ) QP.AddToLayout( hbox, self._files_pause_button, CC.FLAGS_CENTER_PERPENDICULAR ) imports_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR ) imports_panel.Add( self._file_seed_cache_control, CC.FLAGS_EXPAND_PERPENDICULAR ) imports_panel.Add( self._file_download_control, CC.FLAGS_EXPAND_PERPENDICULAR ) # hbox_1 = QP.HBoxLayout() QP.AddToLayout( hbox_1, self._file_velocity_status, CC.FLAGS_CENTER_PERPENDICULAR_EXPAND_DEPTH ) QP.AddToLayout( hbox_1, self._checking_pause_button, CC.FLAGS_CENTER_PERPENDICULAR ) hbox_2 = QP.HBoxLayout() QP.AddToLayout( hbox_2, self._watcher_status, CC.FLAGS_CENTER_PERPENDICULAR_EXPAND_DEPTH ) QP.AddToLayout( hbox_2, self._check_now_button, CC.FLAGS_CENTER_PERPENDICULAR ) checker_panel.Add( hbox_1, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR ) checker_panel.Add( hbox_2, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR ) checker_panel.Add( self._gallery_seed_log_control, CC.FLAGS_EXPAND_PERPENDICULAR ) checker_panel.Add( self._checker_download_control, CC.FLAGS_EXPAND_PERPENDICULAR ) checker_panel.Add( self._checker_options_button, CC.FLAGS_EXPAND_PERPENDICULAR ) vbox = QP.VBoxLayout() QP.AddToLayout( vbox, imports_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) QP.AddToLayout( vbox, checker_panel, CC.FLAGS_EXPAND_PERPENDICULAR ) self._options_panel.setLayout( vbox ) self.Add( self._watcher_subject, CC.FLAGS_EXPAND_PERPENDICULAR ) self.Add( self._watcher_url, CC.FLAGS_EXPAND_PERPENDICULAR ) self.Add( self._options_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS ) self.Add( self._import_options_button, CC.FLAGS_EXPAND_PERPENDICULAR ) # self._import_options_button.fileImportOptionsChanged.connect( self._SetFileImportOptions ) self._import_options_button.noteImportOptionsChanged.connect( self._SetNoteImportOptions ) self._import_options_button.tagImportOptionsChanged.connect( self._SetTagImportOptions ) self._checker_options_button.valueChanged.connect( self._SetCheckerOptions ) self._UpdateControlsForNewWatcher() CG.client_controller.gui.RegisterUIUpdateWindow( self ) def _SetCheckerOptions( self, checker_options ): if self._watcher is not None: self._watcher.SetCheckerOptions( checker_options ) def _SetFileImportOptions( self, file_import_options ): if self._watcher is not None: self._watcher.SetFileImportOptions( file_import_options ) def _SetNoteImportOptions( self, note_import_options ): if self._watcher is not None: self._watcher.SetNoteImportOptions( note_import_options ) def _SetTagImportOptions( self, tag_import_options ): if self._watcher is not None: self._watcher.SetTagImportOptions( tag_import_options ) def _UpdateControlsForNewWatcher( self ): if self._watcher is None: self._options_panel.setEnabled( False ) self._import_options_button.setEnabled( False ) self._watcher_subject.clear() self._watcher_url.clear() self._file_status.clear() self._file_velocity_status.clear() self._watcher_status.clear() self._file_seed_cache_control.SetFileSeedCache( None ) self._gallery_seed_log_control.SetGallerySeedLog( None ) self._file_download_control.ClearNetworkJob() self._checker_download_control.ClearNetworkJob() else: self._options_panel.setEnabled( True ) self._import_options_button.setEnabled( True ) if self._watcher.HasURL(): url = self._watcher.GetURL() self._watcher_url.setText( url ) else: self._watcher_url.clear() checker_options = self._watcher.GetCheckerOptions() self._checker_options_button.SetValue( checker_options ) file_import_options = self._watcher.GetFileImportOptions() tag_import_options = self._watcher.GetTagImportOptions() note_import_options = self._watcher.GetNoteImportOptions() self._import_options_button.SetFileImportOptions( file_import_options ) self._import_options_button.SetTagImportOptions( tag_import_options ) self._import_options_button.SetNoteImportOptions( note_import_options ) file_seed_cache = self._watcher.GetFileSeedCache() self._file_seed_cache_control.SetFileSeedCache( file_seed_cache ) gallery_seed_log = self._watcher.GetGallerySeedLog() self._gallery_seed_log_control.SetGallerySeedLog( gallery_seed_log ) def _UpdateStatus( self ): if self._watcher is not None: ( file_status, files_paused, file_velocity_status, next_check_time, watcher_status, subject, checking_status, check_now, checking_paused ) = self._watcher.GetStatus() if files_paused: ClientGUIFunctions.SetBitmapButtonBitmap( self._files_pause_button, CC.global_pixmaps().file_play ) else: ClientGUIFunctions.SetBitmapButtonBitmap( self._files_pause_button, CC.global_pixmaps().file_pause ) self._file_status.setText( file_status ) self._file_velocity_status.setText( file_velocity_status ) if checking_paused: ClientGUIFunctions.SetBitmapButtonBitmap( self._checking_pause_button, CC.global_pixmaps().gallery_play ) else: if watcher_status == '' and next_check_time is not None: if HydrusTime.TimeHasPassed( next_check_time ): watcher_status = 'checking imminently' else: watcher_status = 'next check ' + ClientTime.TimestampToPrettyTimeDelta( next_check_time, just_now_threshold = 0 ) ClientGUIFunctions.SetBitmapButtonBitmap( self._checking_pause_button, CC.global_pixmaps().gallery_pause ) self._watcher_status.setText( watcher_status ) if checking_status == ClientImporting.CHECKER_STATUS_404: self._checking_pause_button.setEnabled( False ) elif checking_status == ClientImporting.CHECKER_STATUS_DEAD: self._checking_pause_button.setEnabled( False ) else: self._checking_pause_button.setEnabled( True ) if subject in ( '', 'unknown subject' ): subject = 'no subject' self._watcher_subject.setText( subject ) if check_now: self._check_now_button.setEnabled( False ) else: self._check_now_button.setEnabled( True ) ( file_network_job, checker_network_job ) = self._watcher.GetNetworkJobs() if file_network_job is None: self._file_download_control.ClearNetworkJob() else: self._file_download_control.SetNetworkJob( file_network_job ) if checker_network_job is None: self._checker_download_control.ClearNetworkJob() else: self._checker_download_control.SetNetworkJob( checker_network_job ) def EventCheckNow( self ): if self._watcher is not None: self._watcher.CheckNow() self._UpdateStatus() def PauseChecking( self ): if self._watcher is not None: self._watcher.PausePlayChecking() self._UpdateStatus() def PauseFiles( self ): if self._watcher is not None: self._watcher.PausePlayFiles() self._UpdateStatus() def SetWatcher( self, watcher ): self._watcher = watcher self._UpdateControlsForNewWatcher() def TIMERUIUpdate( self ): if CG.client_controller.gui.IShouldRegularlyUpdate( self ): self._UpdateStatus()