import typing 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 HydrusGlobals as HG from hydrus.core import HydrusSerialisable from hydrus.client import ClientApplicationCommand as CAC from hydrus.client import ClientConstants as CC from hydrus.client import ClientParsing from hydrus.client import ClientSearch from hydrus.client import ClientThreading from hydrus.client.gui import ClientGUIDialogs from hydrus.client.gui import QtPorting as QP from hydrus.client.gui.lists import ClientGUIListBoxes from hydrus.client.gui.lists import ClientGUIListBoxesData from hydrus.client.gui.parsing import ClientGUIParsingLegacy from hydrus.client.gui.widgets import ClientGUICommon from hydrus.client.media import ClientMedia from hydrus.client.metadata import ClientTags from hydrus.client.metadata import ClientTagSorting def FilterSuggestedPredicatesForMedia( predicates: typing.Sequence[ ClientSearch.Predicate ], medias: typing.Collection[ ClientMedia.Media ], service_key: bytes ) -> typing.List[ ClientSearch.Predicate ]: tags = [ predicate.GetValue() for predicate in predicates ] filtered_tags = FilterSuggestedTagsForMedia( tags, medias, service_key ) predicates = [ predicate for predicate in predicates if predicate.GetValue() in filtered_tags ] return predicates def FilterSuggestedTagsForMedia( tags: typing.Sequence[ str ], medias: typing.Collection[ ClientMedia.Media ], service_key: bytes ) -> typing.List[ str ]: tags_filtered_set = set( tags ) ( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = ClientMedia.GetMediasTagCount( medias, service_key, ClientTags.TAG_DISPLAY_STORAGE ) current_tags_to_count.update( pending_tags_to_count ) num_media = len( medias ) for ( tag, count ) in current_tags_to_count.items(): if count == num_media: tags_filtered_set.discard( tag ) tags_filtered = [ tag for tag in tags if tag in tags_filtered_set ] return tags_filtered class ListBoxTagsSuggestionsFavourites( ClientGUIListBoxes.ListBoxTagsStrings ): def __init__( self, parent, service_key, activate_callable, sort_tags = True ): ClientGUIListBoxes.ListBoxTagsStrings.__init__( self, parent, service_key = service_key, sort_tags = sort_tags, tag_display_type = ClientTags.TAG_DISPLAY_STORAGE ) self._activate_callable = activate_callable width = HG.client_controller.new_options.GetInteger( 'suggested_tags_width' ) if width is not None: self.setMinimumWidth( width ) def _Activate( self, ctrl_down, shift_down ) -> bool: if len( self._selected_terms ) > 0: tags = set( self._GetTagsFromTerms( self._selected_terms ) ) self._activate_callable( tags, only_add = True ) self._RemoveSelectedTerms() self._DataHasChanged() return True return False def _Sort( self ): self._RegenTermsToIndices() def ActivateAll( self ): self._activate_callable( self.GetTags(), only_add = True ) def TakeFocusForUser( self ): self.SelectTopItem() self.setFocus( QC.Qt.OtherFocusReason ) class ListBoxTagsSuggestionsRelated( ClientGUIListBoxes.ListBoxTagsPredicates ): def __init__( self, parent, service_key, activate_callable ): ClientGUIListBoxes.ListBoxTagsPredicates.__init__( self, parent, tag_display_type = ClientTags.TAG_DISPLAY_STORAGE ) self._activate_callable = activate_callable width = HG.client_controller.new_options.GetInteger( 'suggested_tags_width' ) self.setMinimumWidth( width ) def _Activate( self, ctrl_down, shift_down ) -> bool: if len( self._selected_terms ) > 0: tags = set( self._GetTagsFromTerms( self._selected_terms ) ) self._activate_callable( tags, only_add = True ) self._RemoveSelectedTerms() self._DataHasChanged() return True return False def _GenerateTermFromPredicate( self, predicate: ClientSearch.Predicate ) -> ClientGUIListBoxesData.ListBoxItemPredicate: predicate = predicate.GetCountlessCopy() return ClientGUIListBoxesData.ListBoxItemPredicate( predicate ) def _Sort( self ): self._RegenTermsToIndices() def TakeFocusForUser( self ): self.SelectTopItem() self.setFocus( QC.Qt.OtherFocusReason ) class FavouritesTagsPanel( QW.QWidget ): mouseActivationOccurred = QC.Signal() def __init__( self, parent, service_key, media, activate_callable ): QW.QWidget.__init__( self, parent ) self._service_key = service_key self._media = media vbox = QP.VBoxLayout() self._favourite_tags = ListBoxTagsSuggestionsFavourites( self, self._service_key, activate_callable, sort_tags = False ) QP.AddToLayout( vbox, self._favourite_tags, CC.FLAGS_EXPAND_BOTH_WAYS ) self.setLayout( vbox ) self._UpdateTagDisplay() self._favourite_tags.mouseActivationOccurred.connect( self.mouseActivationOccurred ) def _UpdateTagDisplay( self ): favourites = list( HG.client_controller.new_options.GetSuggestedTagsFavourites( self._service_key ) ) ClientTagSorting.SortTags( HG.client_controller.new_options.GetDefaultTagSort(), favourites ) tags = FilterSuggestedTagsForMedia( favourites, self._media, self._service_key ) self._favourite_tags.SetTags( tags ) def MediaUpdated( self ): self._UpdateTagDisplay() def SetMedia( self, media ): self._media = media self._UpdateTagDisplay() def TakeFocusForUser( self ): self._favourite_tags.TakeFocusForUser() class RecentTagsPanel( QW.QWidget ): mouseActivationOccurred = QC.Signal() def __init__( self, parent, service_key, media, activate_callable ): QW.QWidget.__init__( self, parent ) self._service_key = service_key self._media = media self._last_fetched_tags = [] self._new_options = HG.client_controller.new_options vbox = QP.VBoxLayout() clear_button = QW.QPushButton( 'clear', self ) clear_button.clicked.connect( self.EventClear ) self._recent_tags = ListBoxTagsSuggestionsFavourites( self, self._service_key, activate_callable, sort_tags = False ) QP.AddToLayout( vbox, clear_button, CC.FLAGS_EXPAND_PERPENDICULAR ) QP.AddToLayout( vbox, self._recent_tags, CC.FLAGS_EXPAND_BOTH_WAYS ) self.setLayout( vbox ) self._RefreshRecentTags() self._recent_tags.mouseActivationOccurred.connect( self.mouseActivationOccurred ) def _RefreshRecentTags( self ): def do_it( service_key ): def qt_code( recent_tags ): if not self or not QP.isValid( self ): return self._last_fetched_tags = recent_tags self._UpdateTagDisplay() if len( self._recent_tags.GetTags() ) > 0: self._recent_tags.SelectTopItem() recent_tags = HG.client_controller.Read( 'recent_tags', service_key ) QP.CallAfter( qt_code, recent_tags ) HG.client_controller.CallToThread( do_it, self._service_key ) def _UpdateTagDisplay( self ): tags = FilterSuggestedTagsForMedia( self._last_fetched_tags, self._media, self._service_key ) self._recent_tags.SetTags( tags ) def EventClear( self ): from hydrus.client.gui import ClientGUIDialogsQuick result = ClientGUIDialogsQuick.GetYesNo( self, 'Clear recent tags?' ) if result == QW.QDialog.Accepted: HG.client_controller.Write( 'push_recent_tags', self._service_key, None ) self._last_fetched_tags = [] self._UpdateTagDisplay() def RefreshRecentTags( self ): self._RefreshRecentTags() def MediaUpdated( self ): self._UpdateTagDisplay() def SetMedia( self, media ): self._media = media self._UpdateTagDisplay() self._RefreshRecentTags() def TakeFocusForUser( self ): self._recent_tags.TakeFocusForUser() class RelatedTagsPanel( QW.QWidget ): mouseActivationOccurred = QC.Signal() def __init__( self, parent, service_key, media, activate_callable ): QW.QWidget.__init__( self, parent ) self._service_key = service_key self._media = media self._last_fetched_predicates = [] self._have_fetched = False self._new_options = HG.client_controller.new_options vbox = QP.VBoxLayout() self._button_2 = QW.QPushButton( 'medium', self ) self._button_2.clicked.connect( self.RefreshMedium ) self._button_2.setMinimumWidth( 30 ) self._button_3 = QW.QPushButton( 'thorough', self ) self._button_3.clicked.connect( self.RefreshThorough ) self._button_3.setMinimumWidth( 30 ) self._related_tags = ListBoxTagsSuggestionsRelated( self, service_key, activate_callable ) button_hbox = QP.HBoxLayout() QP.AddToLayout( button_hbox, self._button_2, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS ) QP.AddToLayout( button_hbox, self._button_3, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS ) QP.AddToLayout( vbox, button_hbox, CC.FLAGS_EXPAND_PERPENDICULAR ) QP.AddToLayout( vbox, self._related_tags, CC.FLAGS_EXPAND_BOTH_WAYS ) self.setLayout( vbox ) self._related_tags.mouseActivationOccurred.connect( self.mouseActivationOccurred ) def _FetchRelatedTags( self, max_time_to_take ): def do_it( service_key, hash, search_tags, max_results, max_time_to_take ): def qt_code( predicates ): if not self or not QP.isValid( self ): return self._last_fetched_predicates = predicates self._UpdateTagDisplay() self._have_fetched = True predicates = HG.client_controller.Read( 'related_tags', service_key, hash, search_tags, max_results, max_time_to_take ) predicates = ClientSearch.SortPredicates( predicates ) QP.CallAfter( qt_code, predicates ) self._related_tags.SetPredicates( [] ) ( m, ) = self._media hash = m.GetHash() search_tags = ClientMedia.GetMediasTags( self._media, self._service_key, ClientTags.TAG_DISPLAY_STORAGE, ( HC.CONTENT_STATUS_CURRENT, HC.CONTENT_STATUS_PENDING ) ) max_results = 100 HG.client_controller.CallToThread( do_it, self._service_key, hash, search_tags, max_results, max_time_to_take ) def _QuickSuggestedRelatedTags( self ): max_time_to_take = self._new_options.GetInteger( 'related_tags_search_1_duration_ms' ) / 1000.0 self._FetchRelatedTags( max_time_to_take ) def _UpdateTagDisplay( self ): predicates = FilterSuggestedPredicatesForMedia( self._last_fetched_predicates, self._media, self._service_key ) self._related_tags.SetPredicates( predicates ) def RefreshMedium( self ): max_time_to_take = self._new_options.GetInteger( 'related_tags_search_2_duration_ms' ) / 1000.0 self._FetchRelatedTags( max_time_to_take ) def RefreshThorough( self ): max_time_to_take = self._new_options.GetInteger( 'related_tags_search_3_duration_ms' ) / 1000.0 self._FetchRelatedTags( max_time_to_take ) def MediaUpdated( self ): self._UpdateTagDisplay() def SetMedia( self, media ): self._media = media self._QuickSuggestedRelatedTags() def TakeFocusForUser( self ): self._related_tags.TakeFocusForUser() class FileLookupScriptTagsPanel( QW.QWidget ): mouseActivationOccurred = QC.Signal() def __init__( self, parent, service_key, media, activate_callable ): QW.QWidget.__init__( self, parent ) self._service_key = service_key self._media = media self._last_fetched_tags = [] self._script_choice = ClientGUICommon.BetterChoice( self ) self._script_choice.setEnabled( False ) self._have_fetched = False self._fetch_button = ClientGUICommon.BetterButton( self, 'fetch tags', self.FetchTags ) self._fetch_button.setEnabled( False ) self._script_management = ClientGUIParsingLegacy.ScriptManagementControl( self ) self._tags = ListBoxTagsSuggestionsFavourites( self, self._service_key, activate_callable, sort_tags = True ) self._add_all = ClientGUICommon.BetterButton( self, 'add all', self._tags.ActivateAll ) vbox = QP.VBoxLayout() QP.AddToLayout( vbox, self._script_choice, CC.FLAGS_EXPAND_PERPENDICULAR ) QP.AddToLayout( vbox, self._fetch_button, CC.FLAGS_EXPAND_PERPENDICULAR ) QP.AddToLayout( vbox, self._script_management, CC.FLAGS_EXPAND_PERPENDICULAR ) QP.AddToLayout( vbox, self._add_all, CC.FLAGS_EXPAND_PERPENDICULAR ) QP.AddToLayout( vbox, self._tags, CC.FLAGS_EXPAND_BOTH_WAYS ) self._SetTags( [] ) self.setLayout( vbox ) self._FetchScripts() self._tags.mouseActivationOccurred.connect( self.mouseActivationOccurred ) def _FetchScripts( self ): def do_it(): def qt_code(): if not self or not QP.isValid( self ): return script_names_to_scripts = { script.GetName() : script for script in scripts } for ( name, script ) in list(script_names_to_scripts.items()): self._script_choice.addItem( script.GetName(), script ) new_options = HG.client_controller.new_options favourite_file_lookup_script = new_options.GetNoneableString( 'favourite_file_lookup_script' ) if favourite_file_lookup_script in script_names_to_scripts: self._script_choice.SetValue( script_names_to_scripts[ favourite_file_lookup_script ] ) else: self._script_choice.setCurrentIndex( 0 ) self._script_choice.setEnabled( True ) self._fetch_button.setEnabled( True ) scripts = HG.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_PARSE_ROOT_FILE_LOOKUP ) QP.CallAfter( qt_code ) HG.client_controller.CallToThread( do_it ) def _SetTags( self, tags ): self._last_fetched_tags = tags self._UpdateTagDisplay() def _UpdateTagDisplay( self ): tags = FilterSuggestedTagsForMedia( self._last_fetched_tags, self._media, self._service_key ) self._tags.SetTags( tags ) if len( tags ) == 0: self._add_all.setEnabled( False ) else: self._add_all.setEnabled( True ) def FetchTags( self ): script = self._script_choice.GetValue() if script.UsesUserInput(): message = 'Enter the custom input for the file lookup script.' with ClientGUIDialogs.DialogTextEntry( self, message ) as dlg: if dlg.exec() != QW.QDialog.Accepted: return file_identifier = dlg.GetValue() else: ( m, ) = self._media file_identifier = script.ConvertMediaToFileIdentifier( m ) stop_time = HydrusData.GetNow() + 30 job_key = ClientThreading.JobKey( cancellable = True, stop_time = stop_time ) self._script_management.SetJobKey( job_key ) self._SetTags( [] ) HG.client_controller.CallToThread( self.THREADFetchTags, script, job_key, file_identifier ) def MediaUpdated( self ): self._UpdateTagDisplay() def SetMedia( self, media ): self._media = media self._UpdateTagDisplay() def TakeFocusForUser( self ): if self._have_fetched: self._tags.TakeFocusForUser() else: self._fetch_button.setFocus( QC.Qt.OtherFocusReason ) def THREADFetchTags( self, script, job_key, file_identifier ): def qt_code( tags ): if not self or not QP.isValid( self ): return self._SetTags( tags ) self._have_fetched = True parse_results = script.DoQuery( job_key, file_identifier ) tags = list( ClientParsing.GetTagsFromParseResults( parse_results ) ) tag_sort = ClientTagSorting.TagSort( ClientTagSorting.SORT_BY_HUMAN_TAG, sort_order = CC.SORT_ASC ) ClientTagSorting.SortTags( tag_sort, tags ) QP.CallAfter( qt_code, tags ) class SuggestedTagsPanel( QW.QWidget ): mouseActivationOccurred = QC.Signal() def __init__( self, parent, service_key, media, activate_callable ): QW.QWidget.__init__( self, parent ) self._service_key = service_key self._media = media self._new_options = HG.client_controller.new_options layout_mode = self._new_options.GetNoneableString( 'suggested_tags_layout' ) self._notebook = None if layout_mode == 'notebook': self._notebook = ClientGUICommon.BetterNotebook( self ) panel_parent = self._notebook else: panel_parent = self panels = [] self._favourite_tags = None favourites = HG.client_controller.new_options.GetSuggestedTagsFavourites( self._service_key ) if len( favourites ) > 0: self._favourite_tags = FavouritesTagsPanel( panel_parent, service_key, media, activate_callable ) self._favourite_tags.mouseActivationOccurred.connect( self.mouseActivationOccurred ) panels.append( ( 'favourites', self._favourite_tags ) ) self._related_tags = None if self._new_options.GetBoolean( 'show_related_tags' ) and len( media ) == 1: self._related_tags = RelatedTagsPanel( panel_parent, service_key, media, activate_callable ) self._related_tags.mouseActivationOccurred.connect( self.mouseActivationOccurred ) panels.append( ( 'related', self._related_tags ) ) self._file_lookup_script_tags = None if self._new_options.GetBoolean( 'show_file_lookup_script_tags' ) and len( media ) == 1: self._file_lookup_script_tags = FileLookupScriptTagsPanel( panel_parent, service_key, media, activate_callable ) self._file_lookup_script_tags.mouseActivationOccurred.connect( self.mouseActivationOccurred ) panels.append( ( 'file lookup scripts', self._file_lookup_script_tags ) ) self._recent_tags = None if self._new_options.GetNoneableInteger( 'num_recent_tags' ) is not None: self._recent_tags = RecentTagsPanel( panel_parent, service_key, media, activate_callable ) self._recent_tags.mouseActivationOccurred.connect( self.mouseActivationOccurred ) panels.append( ( 'recent', self._recent_tags ) ) hbox = QP.HBoxLayout() if layout_mode == 'notebook': for ( name, panel ) in panels: self._notebook.addTab( panel, name ) QP.AddToLayout( hbox, self._notebook, CC.FLAGS_EXPAND_BOTH_WAYS ) elif layout_mode == 'columns': for ( name, panel ) in panels: QP.AddToLayout( hbox, panel, CC.FLAGS_EXPAND_PERPENDICULAR ) self.setLayout( hbox ) if len( panels ) == 0: self.hide() def MediaUpdated( self ): if self._favourite_tags is not None: self._favourite_tags.MediaUpdated() if self._recent_tags is not None: self._recent_tags.MediaUpdated() if self._file_lookup_script_tags is not None: self._file_lookup_script_tags.MediaUpdated() if self._related_tags is not None: self._related_tags.MediaUpdated() def RefreshRelatedThorough( self ): if self._related_tags is not None: self._related_tags.RefreshThorough() def SetMedia( self, media ): self._media = media if self._favourite_tags is not None: self._favourite_tags.SetMedia( media ) if self._recent_tags is not None: self._recent_tags.SetMedia( media ) if self._file_lookup_script_tags is not None: self._file_lookup_script_tags.SetMedia( media ) if self._related_tags is not None: self._related_tags.SetMedia( media ) def TakeFocusForUser( self, command ): if command == CAC.SIMPLE_SHOW_AND_FOCUS_MANAGE_TAGS_FAVOURITE_TAGS: panel = self._favourite_tags elif command == CAC.SIMPLE_SHOW_AND_FOCUS_MANAGE_TAGS_RELATED_TAGS: panel = self._related_tags elif command == CAC.SIMPLE_SHOW_AND_FOCUS_MANAGE_TAGS_FILE_LOOKUP_SCRIPT_TAGS: panel = self._file_lookup_script_tags elif command == CAC.SIMPLE_SHOW_AND_FOCUS_MANAGE_TAGS_RECENT_TAGS: panel = self._recent_tags if panel is not None: if self._notebook is not None: self._notebook.SelectPage( panel ) panel.TakeFocusForUser()