hydrus/hydrus/client/gui/importing/ClientGUIImport.py

2017 lines
73 KiB
Python

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()