3531 lines
114 KiB
Python
3531 lines
114 KiB
Python
import collections
|
|
import hashlib
|
|
import os
|
|
import typing
|
|
|
|
from qtpy import QtCore as QC
|
|
from qtpy import QtWidgets as QW
|
|
from qtpy import QtGui as QG
|
|
|
|
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 HydrusSerialisable
|
|
from hydrus.core import HydrusText
|
|
|
|
from hydrus.client import ClientConstants as CC
|
|
from hydrus.client import ClientLocation
|
|
from hydrus.client import ClientSearch
|
|
from hydrus.client import ClientThreading
|
|
from hydrus.client.gui import ClientGUIAsync
|
|
from hydrus.client.gui import ClientGUICore as CGC
|
|
from hydrus.client.gui import ClientGUIDialogs
|
|
from hydrus.client.gui import ClientGUIDialogsQuick
|
|
from hydrus.client.gui import ClientGUIFunctions
|
|
from hydrus.client.gui import ClientGUIMenus
|
|
from hydrus.client.gui import ClientGUIShortcuts
|
|
from hydrus.client.gui import QtInit
|
|
from hydrus.client.gui import QtPorting as QP
|
|
from hydrus.client.gui.canvas import ClientGUICanvas
|
|
from hydrus.client.gui.pages import ClientGUIManagement
|
|
from hydrus.client.gui.pages import ClientGUIResults
|
|
from hydrus.client.gui.pages import ClientGUISession
|
|
from hydrus.client.gui.pages import ClientGUISessionLegacy # to get serialisable data types loaded
|
|
|
|
def ConvertNumHashesToWeight( num_hashes: int ) -> int:
|
|
|
|
return num_hashes
|
|
|
|
def ConvertNumHashesAndSeedsToWeight( num_hashes: int, num_seeds: int ) -> int:
|
|
|
|
return ConvertNumHashesToWeight( num_hashes ) + ConvertNumSeedsToWeight( num_seeds )
|
|
|
|
def ConvertNumSeedsToWeight( num_seeds: int ) -> int:
|
|
|
|
return num_seeds * 20
|
|
|
|
class DialogPageChooser( ClientGUIDialogs.Dialog ):
|
|
|
|
def __init__( self, parent, controller ):
|
|
|
|
ClientGUIDialogs.Dialog.__init__( self, parent, 'new page', position = 'center' )
|
|
|
|
self._controller = controller
|
|
|
|
self._action_picked = False
|
|
|
|
self._result = None
|
|
|
|
# spawn and add to layout in this order, so focus precipitates from the graphical top
|
|
|
|
self._button_7 = QW.QPushButton( '', self )
|
|
self._button_8 = QW.QPushButton( '', self )
|
|
self._button_9 = QW.QPushButton( '', self )
|
|
self._button_4 = QW.QPushButton( '', self )
|
|
self._button_5 = QW.QPushButton( '', self )
|
|
self._button_6 = QW.QPushButton( '', self )
|
|
self._button_1 = QW.QPushButton( '', self )
|
|
self._button_2 = QW.QPushButton( '', self )
|
|
self._button_3 = QW.QPushButton( '', self )
|
|
|
|
size_policy = self._button_1.sizePolicy()
|
|
size_policy.setVerticalPolicy( QW.QSizePolicy.Expanding )
|
|
size_policy.setRetainSizeWhenHidden( True )
|
|
|
|
self._button_7.setSizePolicy( size_policy )
|
|
self._button_8.setSizePolicy( size_policy )
|
|
self._button_9.setSizePolicy( size_policy )
|
|
self._button_4.setSizePolicy( size_policy )
|
|
self._button_5.setSizePolicy( size_policy )
|
|
self._button_6.setSizePolicy( size_policy )
|
|
self._button_1.setSizePolicy( size_policy )
|
|
self._button_2.setSizePolicy( size_policy )
|
|
self._button_3.setSizePolicy( size_policy )
|
|
|
|
self._button_7.setObjectName('7')
|
|
self._button_8.setObjectName('8')
|
|
self._button_9.setObjectName('9')
|
|
self._button_4.setObjectName('4')
|
|
self._button_5.setObjectName('5')
|
|
self._button_6.setObjectName('6')
|
|
self._button_1.setObjectName('1')
|
|
self._button_2.setObjectName('2')
|
|
self._button_3.setObjectName('3')
|
|
|
|
# this ensures these buttons won't get focus and receive key events, letting dialog handle arrow/number shortcuts
|
|
self._button_7.setFocusPolicy( QC.Qt.NoFocus )
|
|
self._button_8.setFocusPolicy( QC.Qt.NoFocus )
|
|
self._button_9.setFocusPolicy( QC.Qt.NoFocus )
|
|
self._button_4.setFocusPolicy( QC.Qt.NoFocus )
|
|
self._button_5.setFocusPolicy( QC.Qt.NoFocus )
|
|
self._button_6.setFocusPolicy( QC.Qt.NoFocus )
|
|
self._button_1.setFocusPolicy( QC.Qt.NoFocus )
|
|
self._button_2.setFocusPolicy( QC.Qt.NoFocus )
|
|
self._button_3.setFocusPolicy( QC.Qt.NoFocus )
|
|
|
|
gridbox = QP.GridLayout( cols = 3 )
|
|
|
|
QP.AddToLayout( gridbox, self._button_7 )
|
|
QP.AddToLayout( gridbox, self._button_8 )
|
|
QP.AddToLayout( gridbox, self._button_9 )
|
|
QP.AddToLayout( gridbox, self._button_4 )
|
|
QP.AddToLayout( gridbox, self._button_5 )
|
|
QP.AddToLayout( gridbox, self._button_6 )
|
|
QP.AddToLayout( gridbox, self._button_1 )
|
|
QP.AddToLayout( gridbox, self._button_2 )
|
|
QP.AddToLayout( gridbox, self._button_3 )
|
|
|
|
self.setLayout( gridbox )
|
|
|
|
( width, height ) = ClientGUIFunctions.ConvertTextToPixels( self, ( 64, 14 ) )
|
|
|
|
self.setMinimumWidth( width )
|
|
self.setMinimumHeight( height )
|
|
|
|
self._petition_service_keys = [ service.GetServiceKey() for service in HG.client_controller.services_manager.GetServices( HC.REPOSITORIES ) if True in ( service.HasPermission( content_type, HC.PERMISSION_ACTION_MODERATE ) for content_type in HC.SERVICE_TYPES_TO_CONTENT_TYPES[ service.GetServiceType() ] ) ]
|
|
|
|
self._InitButtons( 'home' )
|
|
|
|
self._button_7.clicked.connect( lambda: self._HitButton( 7 ) )
|
|
self._button_8.clicked.connect( lambda: self._HitButton( 8 ) )
|
|
self._button_9.clicked.connect( lambda: self._HitButton( 9 ) )
|
|
self._button_4.clicked.connect( lambda: self._HitButton( 4 ) )
|
|
self._button_5.clicked.connect( lambda: self._HitButton( 5 ) )
|
|
self._button_6.clicked.connect( lambda: self._HitButton( 6 ) )
|
|
self._button_1.clicked.connect( lambda: self._HitButton( 1 ) )
|
|
self._button_2.clicked.connect( lambda: self._HitButton( 2 ) )
|
|
self._button_3.clicked.connect( lambda: self._HitButton( 3 ) )
|
|
|
|
|
|
def _AddEntry( self, button, entry ):
|
|
|
|
button_id = int( button.objectName() )
|
|
|
|
self._command_dict[ button_id ] = entry
|
|
|
|
( entry_type, obj ) = entry
|
|
|
|
if entry_type == 'menu':
|
|
|
|
button.setText( obj )
|
|
|
|
elif entry_type == 'page_duplicate_filter':
|
|
|
|
button.setText( 'duplicates processing' )
|
|
|
|
elif entry_type == 'pages_notebook':
|
|
|
|
button.setText( 'page of pages' )
|
|
|
|
elif entry_type in ( 'page_query', 'page_petitions' ):
|
|
|
|
name = HG.client_controller.services_manager.GetService( obj ).GetName()
|
|
|
|
button.setText( name )
|
|
|
|
elif entry_type == 'page_import_gallery':
|
|
|
|
button.setText( 'gallery' )
|
|
|
|
elif entry_type == 'page_import_simple_downloader':
|
|
|
|
button.setText( 'simple downloader' )
|
|
|
|
elif entry_type == 'page_import_watcher':
|
|
|
|
button.setText( 'watcher' )
|
|
|
|
elif entry_type == 'page_import_urls':
|
|
|
|
button.setText( 'urls' )
|
|
|
|
|
|
button.show()
|
|
|
|
|
|
def _HitButton( self, button_id ):
|
|
|
|
if button_id in self._command_dict:
|
|
|
|
( entry_type, obj ) = self._command_dict[ button_id ]
|
|
|
|
if entry_type == 'menu':
|
|
|
|
self._InitButtons( obj )
|
|
|
|
else:
|
|
|
|
if entry_type == 'page_query':
|
|
|
|
file_service_key = obj
|
|
|
|
page_name = 'files'
|
|
|
|
search_enabled = True
|
|
|
|
new_options = self._controller.new_options
|
|
|
|
tag_service_key = new_options.GetKey( 'default_tag_service_search_page' )
|
|
|
|
if not self._controller.services_manager.ServiceExists( tag_service_key ):
|
|
|
|
tag_service_key = CC.COMBINED_TAG_SERVICE_KEY
|
|
|
|
|
|
location_context = ClientLocation.LocationContext.STATICCreateSimple( file_service_key )
|
|
|
|
tag_context = ClientSearch.TagContext( service_key = tag_service_key )
|
|
|
|
file_search_context = ClientSearch.FileSearchContext( location_context = location_context, tag_context = tag_context )
|
|
|
|
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerQuery( page_name, file_search_context, search_enabled ) )
|
|
|
|
elif entry_type == 'page_duplicate_filter':
|
|
|
|
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerDuplicateFilter() )
|
|
|
|
elif entry_type == 'pages_notebook':
|
|
|
|
self._result = ( 'pages', None )
|
|
|
|
elif entry_type == 'page_import_gallery':
|
|
|
|
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportGallery() )
|
|
|
|
elif entry_type == 'page_import_simple_downloader':
|
|
|
|
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportSimpleDownloader() )
|
|
|
|
elif entry_type == 'page_import_watcher':
|
|
|
|
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportMultipleWatcher() )
|
|
|
|
elif entry_type == 'page_import_urls':
|
|
|
|
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportURLs() )
|
|
|
|
elif entry_type == 'page_petitions':
|
|
|
|
petition_service_key = obj
|
|
|
|
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerPetitions( petition_service_key ) )
|
|
|
|
|
|
self._action_picked = True
|
|
|
|
self.done( QW.QDialog.Accepted )
|
|
|
|
|
|
|
|
|
|
def _InitButtons( self, menu_keyword ):
|
|
|
|
self._command_dict = {}
|
|
|
|
entries = []
|
|
|
|
if menu_keyword == 'home':
|
|
|
|
entries.append( ( 'menu', 'file search' ) )
|
|
entries.append( ( 'menu', 'download' ) )
|
|
|
|
if len( self._petition_service_keys ) > 0:
|
|
|
|
entries.append( ( 'menu', 'petitions' ) )
|
|
|
|
|
|
entries.append( ( 'menu', 'special' ) )
|
|
|
|
elif menu_keyword == 'file search':
|
|
|
|
for service_key in self._controller.services_manager.GetServiceKeys( ( HC.LOCAL_FILE_DOMAIN, ) ):
|
|
|
|
entries.append( ( 'page_query', service_key ) )
|
|
|
|
|
|
if len( entries ) > 1:
|
|
|
|
entries.append( ( 'page_query', CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY ) )
|
|
|
|
|
|
entries.append( ( 'page_query', CC.TRASH_SERVICE_KEY ) )
|
|
|
|
if self._controller.new_options.GetBoolean( 'advanced_mode' ):
|
|
|
|
entries.append( ( 'page_query', CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )
|
|
|
|
|
|
for service_key in self._controller.services_manager.GetServiceKeys( ( HC.FILE_REPOSITORY, ) ):
|
|
|
|
entries.append( ( 'page_query', service_key ) )
|
|
|
|
|
|
elif menu_keyword == 'download':
|
|
|
|
entries.append( ( 'page_import_urls', None ) )
|
|
entries.append( ( 'page_import_watcher', None ) )
|
|
entries.append( ( 'page_import_gallery', None ) )
|
|
entries.append( ( 'page_import_simple_downloader', None ) )
|
|
|
|
elif menu_keyword == 'petitions':
|
|
|
|
entries = [ ( 'page_petitions', service_key ) for service_key in self._petition_service_keys ]
|
|
|
|
elif menu_keyword == 'special':
|
|
|
|
entries.append( ( 'pages_notebook', None ) )
|
|
entries.append( ( 'page_duplicate_filter', None ) )
|
|
|
|
|
|
if len( entries ) <= 4:
|
|
|
|
self._button_1.setVisible( False )
|
|
self._button_3.setVisible( False )
|
|
self._button_5.setVisible( False )
|
|
self._button_7.setVisible( False )
|
|
self._button_9.setVisible( False )
|
|
|
|
potential_buttons = [ self._button_8, self._button_4, self._button_6, self._button_2 ]
|
|
|
|
elif len( entries ) <= 9:
|
|
|
|
potential_buttons = [ self._button_7, self._button_8, self._button_9, self._button_4, self._button_5, self._button_6, self._button_1, self._button_2, self._button_3 ]
|
|
|
|
else:
|
|
|
|
# sort out a multi-page solution? maybe only if this becomes a big thing; the person can always select from the menus, yeah?
|
|
|
|
potential_buttons = [ self._button_7, self._button_8, self._button_9, self._button_4, self._button_5, self._button_6, self._button_1, self._button_2, self._button_3 ]
|
|
entries = entries[:9]
|
|
|
|
|
|
for entry in entries:
|
|
|
|
self._AddEntry( potential_buttons.pop( 0 ), entry )
|
|
|
|
|
|
unused_buttons = potential_buttons
|
|
|
|
for button in unused_buttons:
|
|
|
|
button.setVisible( False )
|
|
|
|
|
|
|
|
def event( self, event ):
|
|
|
|
if event.type() == QC.QEvent.WindowDeactivate and not self._action_picked:
|
|
|
|
self.done( QW.QDialog.Rejected )
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return ClientGUIDialogs.Dialog.event( self, event )
|
|
|
|
|
|
|
|
def keyPressEvent( self, event ):
|
|
|
|
button_id = None
|
|
|
|
( modifier, key ) = ClientGUIShortcuts.ConvertKeyEventToSimpleTuple( event )
|
|
|
|
if key == QC.Qt.Key_Up: button_id = 8
|
|
elif key == QC.Qt.Key_Left: button_id = 4
|
|
elif key == QC.Qt.Key_Right: button_id = 6
|
|
elif key == QC.Qt.Key_Down: button_id = 2
|
|
elif key == QC.Qt.Key_1 and modifier == QC.Qt.KeypadModifier: button_id = 1
|
|
elif key == QC.Qt.Key_2 and modifier == QC.Qt.KeypadModifier: button_id = 2
|
|
elif key == QC.Qt.Key_3 and modifier == QC.Qt.KeypadModifier: button_id = 3
|
|
elif key == QC.Qt.Key_4 and modifier == QC.Qt.KeypadModifier: button_id = 4
|
|
elif key == QC.Qt.Key_5 and modifier == QC.Qt.KeypadModifier: button_id = 5
|
|
elif key == QC.Qt.Key_6 and modifier == QC.Qt.KeypadModifier: button_id = 6
|
|
elif key == QC.Qt.Key_7 and modifier == QC.Qt.KeypadModifier: button_id = 7
|
|
elif key == QC.Qt.Key_8 and modifier == QC.Qt.KeypadModifier: button_id = 8
|
|
elif key == QC.Qt.Key_9 and modifier == QC.Qt.KeypadModifier: button_id = 9
|
|
elif key in ( QC.Qt.Key_Enter, QC.Qt.Key_Return ):
|
|
|
|
# get the 'first', scanning from top-left
|
|
|
|
for possible_id in ( 7, 8, 9, 4, 5, 6, 1, 2, 3 ):
|
|
|
|
if possible_id in self._command_dict:
|
|
|
|
button_id = possible_id
|
|
|
|
break
|
|
|
|
|
|
|
|
elif key == QC.Qt.Key_Escape:
|
|
|
|
self.done( QW.QDialog.Rejected )
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
event.ignore()
|
|
|
|
|
|
if button_id is not None:
|
|
|
|
self._HitButton( button_id )
|
|
|
|
|
|
|
|
def GetValue( self ):
|
|
|
|
return self._result
|
|
|
|
|
|
class Page( QW.QWidget ):
|
|
|
|
def __init__( self, parent, controller, management_controller, initial_hashes ):
|
|
|
|
QW.QWidget.__init__( self, parent )
|
|
|
|
self._parent_notebook = parent
|
|
|
|
self._controller = controller
|
|
|
|
self._page_key = self._controller.AcquirePageKey()
|
|
|
|
self._management_controller = management_controller
|
|
|
|
self._initial_hashes = initial_hashes
|
|
|
|
self._management_controller.SetVariable( 'page_key', self._page_key )
|
|
|
|
self._initialised = len( initial_hashes ) == 0
|
|
self._pre_initialisation_media_results = []
|
|
|
|
self._pretty_status = ''
|
|
|
|
self._management_media_split = QW.QSplitter( self )
|
|
self._search_preview_split = QW.QSplitter( self._management_media_split )
|
|
|
|
self._done_split_setups = False
|
|
|
|
self._management_panel = ClientGUIManagement.CreateManagementPanel( self._search_preview_split, self, self._controller, self._management_controller )
|
|
|
|
self._preview_panel = QW.QFrame( self._search_preview_split )
|
|
self._preview_panel.setFrameStyle( QW.QFrame.Panel | QW.QFrame.Sunken )
|
|
self._preview_panel.setLineWidth( 2 )
|
|
|
|
self._preview_canvas = ClientGUICanvas.CanvasPanel( self._preview_panel, self._page_key, self._management_controller.GetVariable( 'location_context' ) )
|
|
|
|
self._management_panel.locationChanged.connect( self._preview_canvas.SetLocationContext )
|
|
|
|
self._media_panel = self._management_panel.GetDefaultEmptyMediaPanel()
|
|
|
|
self._management_media_split.addWidget( self._media_panel )
|
|
|
|
vbox = QP.VBoxLayout( margin = 0 )
|
|
|
|
QP.AddToLayout( vbox, self._management_media_split, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
|
|
|
self.setLayout( vbox )
|
|
|
|
vbox = QP.VBoxLayout( margin = 0 )
|
|
|
|
QP.AddToLayout( vbox, self._preview_canvas, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
|
|
|
self._preview_panel.setLayout( vbox )
|
|
|
|
self._management_media_split.widget( 0 ).setMinimumWidth( 120 )
|
|
self._management_media_split.widget( 1 ).setMinimumWidth( 120 )
|
|
self._management_media_split.setStretchFactor( 0, 0 )
|
|
self._management_media_split.setStretchFactor( 1, 1 )
|
|
|
|
self._handle_event_filter = QP.WidgetEventFilter( self._management_media_split.handle( 1 ) )
|
|
self._handle_event_filter.EVT_LEFT_DCLICK( self.EventUnsplit )
|
|
|
|
self._search_preview_split.widget( 0 ).setMinimumHeight( 180 )
|
|
self._search_preview_split.widget( 1 ).setMinimumHeight( 180 )
|
|
self._search_preview_split.setStretchFactor( 0, 1 )
|
|
self._search_preview_split.setStretchFactor( 1, 0 )
|
|
|
|
self._search_preview_split._handle_event_filter = QP.WidgetEventFilter( self._search_preview_split.handle( 1 ) )
|
|
self._search_preview_split._handle_event_filter.EVT_LEFT_DCLICK( self.EventPreviewUnsplit )
|
|
|
|
self._controller.sub( self, 'SetSplitterPositions', 'set_splitter_positions' )
|
|
|
|
self._current_session_page_container = None
|
|
self._current_session_page_container_hashes_hash = self._GetCurrentSessionPageHashesHash()
|
|
self._current_session_page_container_timestamp = 0
|
|
|
|
self._ConnectMediaPanelSignals()
|
|
|
|
|
|
def _ConnectMediaPanelSignals( self ):
|
|
|
|
self._media_panel.refreshQuery.connect( self.RefreshQuery )
|
|
self._media_panel.focusMediaChanged.connect( self._preview_canvas.SetMedia )
|
|
self._media_panel.focusMediaCleared.connect( self._preview_canvas.ClearMedia )
|
|
self._media_panel.focusMediaPaused.connect( self._preview_canvas.PauseMedia )
|
|
self._media_panel.statusTextChanged.connect( self._SetPrettyStatus )
|
|
|
|
self._management_panel.ConnectMediaPanelSignals( self._media_panel )
|
|
|
|
|
|
def _GetCurrentSessionPageHashesHash( self ):
|
|
|
|
hashlist = self.GetHashes()
|
|
|
|
hashlist_hashable = tuple( hashlist )
|
|
|
|
return hash( hashlist_hashable )
|
|
|
|
|
|
def _SetCurrentPageContainer( self, page_container: ClientGUISession.GUISessionContainerPageSingle ):
|
|
|
|
self._current_session_page_container = page_container
|
|
self._current_session_page_container_hashes_hash = self._GetCurrentSessionPageHashesHash()
|
|
self._current_session_page_container_timestamp = HydrusData.GetNow()
|
|
|
|
|
|
def _SetPrettyStatus( self, status: str ):
|
|
|
|
self._pretty_status = status
|
|
|
|
self._controller.gui.SetStatusBarDirty()
|
|
|
|
|
|
def _SwapMediaPanel( self, new_panel ):
|
|
|
|
previous_sizes = self._management_media_split.sizes()
|
|
|
|
self._preview_canvas.ClearMedia()
|
|
|
|
self._media_panel.ClearPageKey()
|
|
|
|
media_collect = self._management_panel.GetMediaCollect()
|
|
|
|
if media_collect.DoesACollect():
|
|
|
|
new_panel.Collect( self._page_key, media_collect )
|
|
|
|
media_sort = self._management_panel.GetMediaSort()
|
|
|
|
new_panel.Sort( media_sort )
|
|
|
|
|
|
new_panel.setMinimumWidth( 120 )
|
|
|
|
old_panel = self._media_panel
|
|
self._media_panel = new_panel
|
|
|
|
# note focus isn't on the thumb panel but some innerwidget scroll gubbins
|
|
had_focus_before = ClientGUIFunctions.IsQtAncestor( QW.QApplication.focusWidget(), old_panel )
|
|
|
|
if QtInit.WE_ARE_QT5:
|
|
|
|
# this takes ownership of new_panel
|
|
self._management_media_split.insertWidget( 1, new_panel )
|
|
old_panel.setVisible( False )
|
|
|
|
else:
|
|
|
|
# this sets parent of new panel to self and sets parent of old panel to None
|
|
# rumao, it doesn't work if new_panel is already our child
|
|
self._management_media_split.replaceWidget( 1, new_panel )
|
|
|
|
|
|
self._management_media_split.setSizes( previous_sizes )
|
|
|
|
self._management_media_split.setStretchFactor( 1, 1 )
|
|
|
|
self._ConnectMediaPanelSignals()
|
|
|
|
self._controller.pub( 'refresh_page_name', self._page_key )
|
|
|
|
self._controller.pub( 'notify_new_pages_count' )
|
|
|
|
if had_focus_before:
|
|
|
|
ClientGUIFunctions.SetFocusLater( new_panel )
|
|
|
|
|
|
# if we try to kill a media page while a menu is open on it, we can enter program instability.
|
|
# so let's just put it off.
|
|
def clean_up_old_panel():
|
|
|
|
if CGC.core().MenuIsOpen():
|
|
|
|
self._controller.CallLaterQtSafe( self, 0.5, 'menu closed panel swap loop', clean_up_old_panel )
|
|
|
|
return
|
|
|
|
|
|
old_panel.CleanBeforeDestroy()
|
|
|
|
old_panel.deleteLater()
|
|
|
|
|
|
clean_up_old_panel()
|
|
|
|
|
|
def AddMediaResults( self, media_results ):
|
|
|
|
if self._initialised:
|
|
|
|
self._media_panel.AddMediaResults( self._page_key, media_results )
|
|
|
|
else:
|
|
|
|
self._pre_initialisation_media_results.extend( media_results )
|
|
|
|
|
|
|
|
def CheckAbleToClose( self ):
|
|
|
|
self._management_panel.CheckAbleToClose()
|
|
|
|
|
|
def CleanBeforeClose( self ):
|
|
|
|
self._management_panel.CleanBeforeClose()
|
|
|
|
self._media_panel.SetFocusedMedia( None )
|
|
|
|
|
|
def CleanBeforeDestroy( self ):
|
|
|
|
self._management_panel.CleanBeforeDestroy()
|
|
|
|
self._preview_canvas.CleanBeforeDestroy()
|
|
|
|
self._media_panel.CleanBeforeDestroy()
|
|
|
|
self._controller.ReleasePageKey( self._page_key )
|
|
|
|
|
|
def EventPreviewUnsplit( self, event ):
|
|
|
|
QP.Unsplit( self._search_preview_split, self._preview_panel )
|
|
|
|
self._media_panel.SetFocusedMedia( None )
|
|
|
|
|
|
def EventUnsplit( self, event ):
|
|
|
|
QP.Unsplit( self._management_media_split, self._search_preview_split )
|
|
|
|
self._media_panel.SetFocusedMedia( None )
|
|
|
|
|
|
def GetAPIInfoDict( self, simple ):
|
|
|
|
d = {}
|
|
|
|
d[ 'name' ] = self._management_controller.GetPageName()
|
|
d[ 'page_key' ] = self._page_key.hex()
|
|
d[ 'page_type' ] = self._management_controller.GetType()
|
|
|
|
management_info = self._management_controller.GetAPIInfoDict( simple )
|
|
|
|
d[ 'management' ] = management_info
|
|
|
|
media_info = self._media_panel.GetAPIInfoDict( simple )
|
|
|
|
d[ 'media' ] = media_info
|
|
|
|
return d
|
|
|
|
|
|
def GetHashes( self ):
|
|
|
|
if self._initialised:
|
|
|
|
return self._media_panel.GetHashes( ordered = True )
|
|
|
|
else:
|
|
|
|
hashes = list( self._initial_hashes )
|
|
hashes.extend( ( media_result.GetHash() for media_result in self._pre_initialisation_media_results ) )
|
|
|
|
hashes = HydrusData.DedupeList( hashes )
|
|
|
|
return hashes
|
|
|
|
|
|
|
|
def GetManagementController( self ):
|
|
|
|
return self._management_controller
|
|
|
|
|
|
def GetManagementPanel( self ):
|
|
|
|
return self._management_panel
|
|
|
|
|
|
# used by autocomplete
|
|
def GetMedia( self ):
|
|
|
|
return self._media_panel.GetSortedMedia()
|
|
|
|
|
|
def GetMediaPanel( self ):
|
|
|
|
return self._media_panel
|
|
|
|
|
|
def GetName( self ):
|
|
|
|
return self._management_controller.GetPageName()
|
|
|
|
|
|
def GetNameForMenu( self ) -> str:
|
|
|
|
name_for_menu = self.GetName()
|
|
|
|
( num_files, ( num_value, num_range ) ) = self.GetNumFileSummary()
|
|
|
|
if num_files > 0:
|
|
|
|
name_for_menu = '{} - {} files'.format( name_for_menu, HydrusData.ToHumanInt( num_files ) )
|
|
|
|
|
|
if num_value != num_range:
|
|
|
|
name_for_menu = '{} - {}'.format( name_for_menu, HydrusData.ConvertValueRangeToPrettyString( num_value, num_range ) )
|
|
|
|
|
|
return HydrusText.ElideText( name_for_menu, 32, elide_center = True )
|
|
|
|
|
|
def GetNumFileSummary( self ):
|
|
|
|
if self._initialised:
|
|
|
|
num_files = self._media_panel.GetNumFiles()
|
|
|
|
else:
|
|
|
|
num_files = len( self._initial_hashes )
|
|
|
|
|
|
( num_value, num_range ) = self._management_controller.GetValueRange()
|
|
|
|
if num_value == num_range:
|
|
|
|
( num_value, num_range ) = ( 0, 0 )
|
|
|
|
|
|
return ( num_files, ( num_value, num_range ) )
|
|
|
|
|
|
def GetPageKey( self ):
|
|
|
|
return self._page_key
|
|
|
|
|
|
def GetPageKeys( self ):
|
|
|
|
return { self._page_key }
|
|
|
|
|
|
def GetParentNotebook( self ):
|
|
|
|
return self._parent_notebook
|
|
|
|
|
|
def GetPrettyStatusForStatusBar( self ):
|
|
|
|
return self._pretty_status
|
|
|
|
|
|
def GetSerialisablePage( self, only_changed_page_data, about_to_save ):
|
|
|
|
if only_changed_page_data and not self.IsCurrentSessionPageDirty():
|
|
|
|
hashes_to_page_data = {}
|
|
|
|
skipped_unchanged_page_hashes = { self._current_session_page_container.GetPageDataHash() }
|
|
|
|
return ( self._current_session_page_container, hashes_to_page_data, skipped_unchanged_page_hashes )
|
|
|
|
|
|
name = self.GetName()
|
|
|
|
page_data = ClientGUISession.GUISessionPageData( self._management_controller, self.GetHashes() )
|
|
|
|
# this is the only place this is generated. this will be its key/name/id from now on
|
|
# we won't regen the hash for identifier since it could change due to object updates etc...
|
|
page_data_hash = page_data.GetSerialisedHash()
|
|
|
|
page_container = ClientGUISession.GUISessionContainerPageSingle( name, page_data_hash )
|
|
|
|
hashes_to_page_data = { page_data_hash : page_data }
|
|
|
|
if about_to_save:
|
|
|
|
self._SetCurrentPageContainer( page_container )
|
|
|
|
|
|
skipped_unchanged_page_hashes = set()
|
|
|
|
return ( page_container, hashes_to_page_data, skipped_unchanged_page_hashes )
|
|
|
|
|
|
def GetSessionAPIInfoDict( self, is_selected = False ):
|
|
|
|
root = {}
|
|
|
|
root[ 'name' ] = self.GetName()
|
|
root[ 'page_key' ] = self._page_key.hex()
|
|
root[ 'page_type' ] = self._management_controller.GetType()
|
|
root[ 'focused' ] = is_selected
|
|
|
|
return root
|
|
|
|
|
|
def GetSashPositions( self ):
|
|
|
|
hpos = HC.options[ 'hpos' ]
|
|
|
|
sizes = self._management_media_split.sizes()
|
|
|
|
if len( sizes ) > 1:
|
|
|
|
if sizes[0] != 0:
|
|
|
|
hpos = sizes[0]
|
|
|
|
|
|
|
|
vpos = HC.options[ 'vpos' ]
|
|
|
|
sizes = self._search_preview_split.sizes()
|
|
|
|
if len( sizes ) > 1:
|
|
|
|
if sizes[1] != 0:
|
|
|
|
vpos = - sizes[1]
|
|
|
|
|
|
|
|
return ( hpos, vpos )
|
|
|
|
|
|
def GetTotalFileSize( self ):
|
|
|
|
if self._initialised:
|
|
|
|
return self._media_panel.GetTotalFileSize()
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def GetTotalNumHashesAndSeeds( self ):
|
|
|
|
num_hashes = len( self.GetHashes() )
|
|
num_seeds = self._management_controller.GetNumSeeds()
|
|
|
|
return ( num_hashes, num_seeds )
|
|
|
|
|
|
def GetTotalWeight( self ) -> int:
|
|
|
|
( num_hashes, num_seeds ) = self.GetTotalNumHashesAndSeeds()
|
|
|
|
return ConvertNumHashesAndSeedsToWeight( num_hashes, num_seeds )
|
|
|
|
|
|
def IsCurrentSessionPageDirty( self ):
|
|
|
|
if self._current_session_page_container is None:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
if self._GetCurrentSessionPageHashesHash() != self._current_session_page_container_hashes_hash:
|
|
|
|
return True
|
|
|
|
|
|
return self._management_controller.HasSerialisableChangesSince( self._current_session_page_container_timestamp )
|
|
|
|
|
|
|
|
def IsGalleryDownloaderPage( self ):
|
|
|
|
return self._management_controller.GetType() == ClientGUIManagement.MANAGEMENT_TYPE_IMPORT_MULTIPLE_GALLERY
|
|
|
|
|
|
def IsImporter( self ):
|
|
|
|
return self._management_controller.IsImporter()
|
|
|
|
|
|
def IsInitialised( self ):
|
|
|
|
return self._initialised
|
|
|
|
|
|
def IsMultipleWatcherPage( self ):
|
|
|
|
return self._management_controller.GetType() == ClientGUIManagement.MANAGEMENT_TYPE_IMPORT_MULTIPLE_WATCHER
|
|
|
|
|
|
def IsURLImportPage( self ):
|
|
|
|
return self._management_controller.GetType() == ClientGUIManagement.MANAGEMENT_TYPE_IMPORT_URLS
|
|
|
|
|
|
def PageHidden( self ):
|
|
|
|
self._management_panel.PageHidden()
|
|
self._media_panel.PageHidden()
|
|
self._preview_canvas.PageHidden()
|
|
|
|
|
|
def PageShown( self ):
|
|
|
|
if self.isVisible() and not self._done_split_setups:
|
|
|
|
self.SetSplitterPositions()
|
|
|
|
self._done_split_setups = True
|
|
|
|
|
|
self._management_panel.PageShown()
|
|
self._media_panel.PageShown()
|
|
self._preview_canvas.PageShown()
|
|
|
|
|
|
def RefreshQuery( self ):
|
|
|
|
if self._initialised:
|
|
|
|
self._management_panel.RefreshQuery()
|
|
|
|
|
|
|
|
def SetMediaFocus( self ):
|
|
|
|
self._media_panel.setFocus( QC.Qt.OtherFocusReason )
|
|
|
|
|
|
def SetName( self, name ):
|
|
|
|
return self._management_controller.SetPageName( name )
|
|
|
|
|
|
def SetPageContainerClean( self, page_container: ClientGUISession.GUISessionContainerPageSingle ):
|
|
|
|
self._SetCurrentPageContainer( page_container )
|
|
|
|
|
|
def SetPrettyStatus( self, page_key, status ):
|
|
|
|
if page_key == self._page_key:
|
|
|
|
if self._initialised:
|
|
|
|
self._SetPrettyStatus( status )
|
|
|
|
|
|
|
|
|
|
def SetSearchFocus( self ):
|
|
|
|
self._management_panel.SetSearchFocus()
|
|
|
|
|
|
def SetSplitterPositions( self, hpos = None, vpos = None ):
|
|
|
|
if hpos is None:
|
|
|
|
hpos = HC.options[ 'hpos' ]
|
|
|
|
|
|
if vpos is None:
|
|
|
|
vpos = HC.options[ 'vpos' ]
|
|
|
|
|
|
QP.SplitHorizontally( self._search_preview_split, self._management_panel, self._preview_panel, vpos )
|
|
|
|
QP.SplitVertically( self._management_media_split, self._search_preview_split, self._media_panel, hpos )
|
|
|
|
if HC.options[ 'hide_preview' ]:
|
|
|
|
QP.CallAfter( QP.Unsplit, self._search_preview_split, self._preview_panel )
|
|
|
|
|
|
|
|
def ShowHideSplit( self ):
|
|
|
|
if QP.SplitterVisibleCount( self._management_media_split ) > 1:
|
|
|
|
QP.Unsplit( self._management_media_split, self._search_preview_split )
|
|
|
|
self.SetMediaFocus()
|
|
|
|
self._media_panel.SetFocusedMedia( None )
|
|
|
|
else:
|
|
|
|
self.SetSplitterPositions()
|
|
|
|
self.SetSearchFocus()
|
|
|
|
|
|
|
|
def _StartInitialMediaResultsLoad( self ):
|
|
|
|
def qt_code_status( status ):
|
|
|
|
if not self._initialised:
|
|
|
|
self._SetPrettyStatus( status )
|
|
|
|
|
|
|
|
controller = self._controller
|
|
initial_hashes = HydrusData.DedupeList( self._initial_hashes )
|
|
|
|
def work_callable():
|
|
|
|
initial_media_results = []
|
|
|
|
for group_of_initial_hashes in HydrusData.SplitListIntoChunks( initial_hashes, 256 ):
|
|
|
|
more_media_results = controller.Read( 'media_results', group_of_initial_hashes )
|
|
|
|
initial_media_results.extend( more_media_results )
|
|
|
|
status = 'Loading initial files\u2026 ' + HydrusData.ConvertValueRangeToPrettyString( len( initial_media_results ), len( initial_hashes ) )
|
|
|
|
controller.CallAfterQtSafe( self, 'setting status bar loading string', qt_code_status, status )
|
|
|
|
QP.CallAfter( qt_code_status, status )
|
|
|
|
|
|
hashes_to_media_results = { media_result.GetHash() : media_result for media_result in initial_media_results }
|
|
|
|
sorted_initial_media_results = [ hashes_to_media_results[ hash ] for hash in initial_hashes if hash in hashes_to_media_results ]
|
|
|
|
return sorted_initial_media_results
|
|
|
|
|
|
def publish_callable( media_results ):
|
|
|
|
self._SetPrettyStatus( '' )
|
|
|
|
location_context = self._management_controller.GetVariable( 'location_context' )
|
|
|
|
media_panel = ClientGUIResults.MediaPanelThumbnails( self, self._page_key, location_context, media_results )
|
|
|
|
self._SwapMediaPanel( media_panel )
|
|
|
|
if len( self._pre_initialisation_media_results ) > 0:
|
|
|
|
media_panel.AddMediaResults( self._page_key, self._pre_initialisation_media_results )
|
|
|
|
self._pre_initialisation_media_results = []
|
|
|
|
|
|
# do this 'after' so on a long session setup, it all boots once session loaded
|
|
HG.client_controller.CallAfterQtSafe( self, 'starting page controller', self._management_panel.Start )
|
|
|
|
self._initialised = True
|
|
self._initial_hashes = []
|
|
|
|
|
|
job = ClientGUIAsync.AsyncQtJob( self, work_callable, publish_callable )
|
|
|
|
job.start()
|
|
|
|
|
|
def Start( self ):
|
|
|
|
if self._initial_hashes is not None and len( self._initial_hashes ) > 0:
|
|
|
|
self._StartInitialMediaResultsLoad()
|
|
|
|
else:
|
|
|
|
# do this 'after' so on a long session setup, it all boots once session loaded
|
|
HG.client_controller.CallAfterQtSafe( self, 'starting page controller', self._management_panel.Start )
|
|
|
|
self._initialised = True
|
|
|
|
|
|
|
|
def SwapMediaPanel( self, new_panel ):
|
|
|
|
self._SwapMediaPanel( new_panel )
|
|
|
|
|
|
def TestAbleToClose( self ):
|
|
|
|
try:
|
|
|
|
self._management_panel.CheckAbleToClose()
|
|
|
|
except HydrusExceptions.VetoException as e:
|
|
|
|
reason = str( e )
|
|
|
|
message = '{} Are you sure you want to close it?'.format( str( e ) )
|
|
|
|
result = ClientGUIDialogsQuick.GetYesNo( self, message )
|
|
|
|
if result == QW.QDialog.Rejected:
|
|
|
|
raise HydrusExceptions.VetoException()
|
|
|
|
|
|
|
|
|
|
def REPEATINGPageUpdate( self ):
|
|
|
|
self._management_panel.REPEATINGPageUpdate()
|
|
|
|
|
|
directions_for_notebook_tabs = {}
|
|
|
|
directions_for_notebook_tabs[ CC.DIRECTION_UP ] = QW.QTabWidget.North
|
|
directions_for_notebook_tabs[ CC.DIRECTION_LEFT ] = QW.QTabWidget.West
|
|
directions_for_notebook_tabs[ CC.DIRECTION_RIGHT ] = QW.QTabWidget.East
|
|
directions_for_notebook_tabs[ CC.DIRECTION_DOWN ] = QW.QTabWidget.South
|
|
|
|
class PagesNotebook( QP.TabWidgetWithDnD ):
|
|
|
|
freshSessionLoaded = QC.Signal( ClientGUISession.GUISessionContainer )
|
|
|
|
def __init__( self, parent, controller, name ):
|
|
|
|
QP.TabWidgetWithDnD.__init__( self, parent )
|
|
|
|
self._parent_notebook = parent
|
|
|
|
direction = controller.new_options.GetInteger( 'notebook_tab_alignment' )
|
|
|
|
self.setTabPosition( directions_for_notebook_tabs[ direction ] )
|
|
|
|
self._controller = controller
|
|
|
|
self._page_key = self._controller.AcquirePageKey()
|
|
|
|
self._name = name
|
|
|
|
self._next_new_page_index = None
|
|
|
|
self._potential_drag_page = None
|
|
|
|
self._closed_pages = []
|
|
|
|
self._controller.sub( self, 'RefreshPageName', 'refresh_page_name' )
|
|
self._controller.sub( self, 'NotifyPageUnclosed', 'notify_page_unclosed' )
|
|
self._controller.sub( self, '_UpdateOptions', 'notify_new_options' )
|
|
|
|
self.currentChanged.connect( self.pageJustChanged )
|
|
self.pageDragAndDropped.connect( self._RefreshPageNamesAfterDnD )
|
|
|
|
self.tabBar().tabDoubleLeftClicked.connect( self._RenamePage )
|
|
self.tabBar().tabMiddleClicked.connect( self._ClosePage )
|
|
|
|
self.tabBar().tabSpaceDoubleLeftClicked.connect( self.ChooseNewPage )
|
|
self.tabBar().tabSpaceDoubleMiddleClicked.connect( self.ChooseNewPage )
|
|
|
|
self._previous_page_index = -1
|
|
|
|
self._time_of_last_move_selection_event = 0
|
|
|
|
self._UpdateOptions()
|
|
|
|
self.tabBar().installEventFilter( self )
|
|
self.installEventFilter( self )
|
|
|
|
|
|
def _RefreshPageNamesAfterDnD( self, page_widget, source_widget ):
|
|
|
|
if hasattr( page_widget, 'GetPageKey' ):
|
|
|
|
self._controller.pub( 'refresh_page_name', page_widget.GetPageKey() )
|
|
|
|
|
|
source_notebook = source_widget.parentWidget()
|
|
|
|
if hasattr( source_notebook, 'GetPageKey' ):
|
|
|
|
self._controller.pub( 'refresh_page_name', source_notebook.GetPageKey() )
|
|
|
|
|
|
|
|
def _UpdateOptions( self ):
|
|
|
|
if HG.client_controller.new_options.GetBoolean( 'elide_page_tab_names' ):
|
|
|
|
self.tabBar().setElideMode( QC.Qt.ElideMiddle )
|
|
|
|
else:
|
|
|
|
self.tabBar().setElideMode( QC.Qt.ElideNone )
|
|
|
|
|
|
direction = HG.client_controller.new_options.GetInteger( 'notebook_tab_alignment' )
|
|
|
|
self.setTabPosition( directions_for_notebook_tabs[ direction ] )
|
|
|
|
|
|
def _UpdatePreviousPageIndex( self ):
|
|
|
|
self._previous_page_index = self.currentIndex()
|
|
|
|
|
|
def _ChooseNewPage( self, insertion_index = None ):
|
|
|
|
self._next_new_page_index = insertion_index
|
|
|
|
with DialogPageChooser( self, self._controller ) as dlg:
|
|
|
|
if dlg.exec() == QW.QDialog.Accepted:
|
|
|
|
( page_type, page_data ) = dlg.GetValue()
|
|
|
|
if page_type == 'pages':
|
|
|
|
self.NewPagesNotebook()
|
|
|
|
elif page_type == 'page':
|
|
|
|
management_controller = page_data
|
|
|
|
self.NewPage( management_controller )
|
|
|
|
|
|
|
|
|
|
|
|
def _CloseAllPages( self, polite = True, delete_pages = False ):
|
|
|
|
closees = [ index for index in range( self.count() ) ]
|
|
|
|
self._ClosePages( closees, polite, delete_pages = delete_pages )
|
|
|
|
|
|
def _CloseLeftPages( self, from_index ):
|
|
|
|
message = 'Close all pages to the left?'
|
|
|
|
result = ClientGUIDialogsQuick.GetYesNo( self, message )
|
|
|
|
if result == QW.QDialog.Accepted:
|
|
|
|
closees = [ index for index in range( self.count() ) if index < from_index ]
|
|
|
|
self._ClosePages( closees )
|
|
|
|
|
|
|
|
def _CloseOtherPages( self, except_index ):
|
|
|
|
message = 'Close all other pages?'
|
|
|
|
result = ClientGUIDialogsQuick.GetYesNo( self, message )
|
|
|
|
if result == QW.QDialog.Accepted:
|
|
|
|
closees = [ index for index in range( self.count() ) if index != except_index ]
|
|
|
|
self._ClosePages( closees )
|
|
|
|
|
|
|
|
def _ClosePage( self, index, polite = True, delete_page = False ):
|
|
|
|
self._controller.ResetIdleTimer()
|
|
self._controller.ResetPageChangeTimer()
|
|
|
|
if index == -1 or index > self.count() - 1:
|
|
|
|
return False
|
|
|
|
|
|
page = self.widget( index )
|
|
|
|
if polite:
|
|
|
|
try:
|
|
|
|
page.TestAbleToClose()
|
|
|
|
except HydrusExceptions.VetoException:
|
|
|
|
return False
|
|
|
|
|
|
|
|
page.CleanBeforeClose()
|
|
|
|
page_key = page.GetPageKey()
|
|
|
|
self._closed_pages.append( ( index, page_key ) )
|
|
|
|
self.removeTab( index )
|
|
|
|
self._UpdatePreviousPageIndex()
|
|
|
|
self._controller.pub( 'refresh_page_name', self._page_key )
|
|
|
|
if delete_page:
|
|
|
|
self._controller.pub( 'notify_deleted_page', page )
|
|
|
|
else:
|
|
|
|
self._controller.pub( 'notify_closed_page', page )
|
|
|
|
|
|
return True
|
|
|
|
|
|
def _ClosePages( self, indices, polite = True, delete_pages = False ):
|
|
|
|
indices = list( indices )
|
|
|
|
indices.sort( reverse = True ) # so we are closing from the end first
|
|
|
|
for index in indices:
|
|
|
|
successful = self._ClosePage( index, polite, delete_page = delete_pages )
|
|
|
|
if not successful:
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
def _CloseRightPages( self, from_index ):
|
|
|
|
message = 'Close all pages to the right?'
|
|
|
|
result = ClientGUIDialogsQuick.GetYesNo( self, message )
|
|
|
|
if result == QW.QDialog.Accepted:
|
|
|
|
closees = [ index for index in range( self.count() ) if index > from_index ]
|
|
|
|
self._ClosePages( closees )
|
|
|
|
|
|
|
|
def _DuplicatePage( self, index ):
|
|
|
|
if index == -1 or index > self.count() - 1:
|
|
|
|
return False
|
|
|
|
|
|
page = self.widget( index )
|
|
|
|
only_changed_page_data = False
|
|
about_to_save = False
|
|
|
|
( container, hashes_to_page_data, skipped_unchanged_page_hashes ) = page.GetSerialisablePage( only_changed_page_data, about_to_save )
|
|
|
|
top_notebook_container = ClientGUISession.GUISessionContainerPageNotebook( 'dupe top notebook', page_containers = [ container ] )
|
|
|
|
session = ClientGUISession.GUISessionContainer( 'dupe session', top_notebook_container = top_notebook_container, hashes_to_page_data = hashes_to_page_data )
|
|
|
|
self.InsertSession( index + 1, session, session_is_clean = False )
|
|
|
|
|
|
def _GetDefaultPageInsertionIndex( self ):
|
|
|
|
new_options = self._controller.new_options
|
|
|
|
new_page_goes = new_options.GetInteger( 'default_new_page_goes' )
|
|
|
|
current_index = self.currentIndex()
|
|
|
|
if current_index == -1:
|
|
|
|
new_page_goes = CC.NEW_PAGE_GOES_FAR_LEFT
|
|
|
|
|
|
if new_page_goes == CC.NEW_PAGE_GOES_FAR_LEFT:
|
|
|
|
insertion_index = 0
|
|
|
|
elif new_page_goes == CC.NEW_PAGE_GOES_LEFT_OF_CURRENT:
|
|
|
|
insertion_index = current_index
|
|
|
|
elif new_page_goes == CC.NEW_PAGE_GOES_RIGHT_OF_CURRENT:
|
|
|
|
insertion_index = current_index + 1
|
|
|
|
elif new_page_goes == CC.NEW_PAGE_GOES_FAR_RIGHT:
|
|
|
|
insertion_index = self.count()
|
|
|
|
|
|
return insertion_index
|
|
|
|
|
|
def _GetMediaPages( self, only_my_level ):
|
|
|
|
results = []
|
|
|
|
for page in self._GetPages():
|
|
|
|
if isinstance( page, PagesNotebook ):
|
|
|
|
if not only_my_level:
|
|
|
|
results.extend( page.GetMediaPages() )
|
|
|
|
|
|
else:
|
|
|
|
results.append( page )
|
|
|
|
|
|
|
|
return results
|
|
|
|
|
|
def _GetIndex( self, page_key ):
|
|
|
|
for ( page, index ) in ( ( self.widget( index ), index ) for index in range( self.count() ) ):
|
|
|
|
if page.GetPageKey() == page_key:
|
|
|
|
return index
|
|
|
|
|
|
|
|
raise HydrusExceptions.DataMissing()
|
|
|
|
|
|
def _GetNotebookFromScreenPosition( self, screen_position ) -> "PagesNotebook":
|
|
|
|
current_page = self.currentWidget()
|
|
|
|
if current_page is None or not isinstance( current_page, PagesNotebook ):
|
|
|
|
return self
|
|
|
|
else:
|
|
|
|
on_child_notebook_somewhere = current_page.mapFromGlobal( screen_position ).y() > current_page.pos().y()
|
|
|
|
if on_child_notebook_somewhere:
|
|
|
|
return current_page._GetNotebookFromScreenPosition( screen_position )
|
|
|
|
|
|
|
|
return self
|
|
|
|
|
|
def _GetPages( self ):
|
|
|
|
return [ self.widget( i ) for i in range( self.count() ) ]
|
|
|
|
|
|
def _GetPageFromName( self, page_name, only_media_pages = False ):
|
|
|
|
for page in self._GetPages():
|
|
|
|
if page.GetName() == page_name:
|
|
|
|
do_not_do_it = only_media_pages and isinstance( page, PagesNotebook )
|
|
|
|
if not do_not_do_it:
|
|
|
|
return page
|
|
|
|
|
|
|
|
if isinstance( page, PagesNotebook ):
|
|
|
|
result = page._GetPageFromName( page_name, only_media_pages = only_media_pages )
|
|
|
|
if result is not None:
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
def _MovePage( self, page, dest_notebook, insertion_tab_index, follow_dropped_page = False ):
|
|
|
|
source_notebook = page.GetParentNotebook()
|
|
|
|
for ( index, p ) in enumerate( source_notebook._GetPages() ):
|
|
|
|
if p == page:
|
|
|
|
source_notebook.removeTab( index )
|
|
|
|
source_notebook._UpdatePreviousPageIndex()
|
|
|
|
break
|
|
|
|
|
|
|
|
if source_notebook != dest_notebook:
|
|
|
|
page.setParent( dest_notebook )
|
|
|
|
self._controller.pub( 'refresh_page_name', source_notebook.GetPageKey() )
|
|
|
|
|
|
insertion_tab_index = min( insertion_tab_index, dest_notebook.count() )
|
|
|
|
dest_notebook.insertTab( insertion_tab_index, page, page.GetName() )
|
|
|
|
if follow_dropped_page: dest_notebook.setCurrentIndex( insertion_tab_index )
|
|
|
|
if follow_dropped_page:
|
|
|
|
self.ShowPage( page )
|
|
|
|
|
|
self._controller.pub( 'refresh_page_name', page.GetPageKey() )
|
|
|
|
|
|
def _MovePages( self, pages, dest_notebook ):
|
|
|
|
insertion_tab_index = dest_notebook.GetNumPages( only_my_level = True )
|
|
|
|
for page in pages:
|
|
|
|
if page.GetParentNotebook() != dest_notebook:
|
|
|
|
self._MovePage( page, dest_notebook, insertion_tab_index )
|
|
|
|
insertion_tab_index += 1
|
|
|
|
|
|
|
|
|
|
def _RefreshPageName( self, index ):
|
|
|
|
if index == -1 or index > self.count() - 1:
|
|
|
|
return
|
|
|
|
|
|
new_options = self._controller.new_options
|
|
|
|
max_page_name_chars = new_options.GetInteger( 'max_page_name_chars' )
|
|
|
|
page_file_count_display = new_options.GetInteger( 'page_file_count_display' )
|
|
|
|
import_page_progress_display = new_options.GetBoolean( 'import_page_progress_display' )
|
|
|
|
page = self.widget( index )
|
|
|
|
if isinstance( page, Page ) and not page.IsInitialised():
|
|
|
|
full_page_name = 'initialising'
|
|
|
|
else:
|
|
|
|
full_page_name = page.GetName()
|
|
|
|
full_page_name = full_page_name.replace( os.linesep, '' )
|
|
|
|
|
|
page_name = HydrusText.ElideText( full_page_name, max_page_name_chars )
|
|
|
|
do_tooltip = len( page_name ) != len( full_page_name ) or HG.client_controller.new_options.GetBoolean( 'elide_page_tab_names' )
|
|
|
|
num_string = ''
|
|
|
|
( num_files, ( num_value, num_range ) ) = page.GetNumFileSummary()
|
|
|
|
if page_file_count_display == CC.PAGE_FILE_COUNT_DISPLAY_ALL or ( page_file_count_display == CC.PAGE_FILE_COUNT_DISPLAY_ONLY_IMPORTERS and page.IsImporter() ):
|
|
|
|
num_string += HydrusData.ToHumanInt( num_files )
|
|
|
|
|
|
if import_page_progress_display:
|
|
|
|
if num_range > 0 and num_value != num_range:
|
|
|
|
if len( num_string ) > 0:
|
|
|
|
num_string += ', '
|
|
|
|
|
|
num_string += HydrusData.ConvertValueRangeToPrettyString( num_value, num_range )
|
|
|
|
|
|
|
|
if len( num_string ) > 0:
|
|
|
|
page_name += ' (' + num_string + ')'
|
|
|
|
|
|
safe_page_name = ClientGUIFunctions.EscapeMnemonics( page_name )
|
|
|
|
tab_bar = self.tabBar()
|
|
|
|
existing_page_name = tab_bar.tabText( index )
|
|
|
|
if existing_page_name not in ( safe_page_name, page_name ):
|
|
|
|
tab_bar.setTabText( index, safe_page_name )
|
|
|
|
if do_tooltip:
|
|
|
|
self.setTabToolTip( index, full_page_name )
|
|
|
|
|
|
|
|
|
|
def _RenamePage( self, index ):
|
|
|
|
if index == -1 or index > self.count() - 1:
|
|
|
|
return
|
|
|
|
|
|
page = self.widget( index )
|
|
|
|
current_name = page.GetName()
|
|
|
|
with ClientGUIDialogs.DialogTextEntry( self, 'Enter the new name.', default = current_name, allow_blank = False ) as dlg:
|
|
|
|
if dlg.exec() == QW.QDialog.Accepted:
|
|
|
|
new_name = dlg.GetValue()
|
|
|
|
page.SetName( new_name )
|
|
|
|
self._controller.pub( 'refresh_page_name', page.GetPageKey() )
|
|
|
|
|
|
|
|
|
|
def _SendPageToNewNotebook( self, index ):
|
|
|
|
if 0 <= index and index <= self.count() - 1:
|
|
|
|
page = self.widget( index )
|
|
|
|
dest_notebook = self.NewPagesNotebook( forced_insertion_index = index, give_it_a_blank_page = False )
|
|
|
|
self._MovePage( page, dest_notebook, 0 )
|
|
|
|
|
|
|
|
def _SendRightPagesToNewNotebook( self, from_index ):
|
|
|
|
message = 'Send all pages to the right to a new page of pages?'
|
|
|
|
result = ClientGUIDialogsQuick.GetYesNo( self, message )
|
|
|
|
if result == QW.QDialog.Accepted:
|
|
|
|
pages_index = self.count()
|
|
|
|
dest_notebook = self.NewPagesNotebook( forced_insertion_index = pages_index, give_it_a_blank_page = False )
|
|
|
|
movees = list( range( from_index + 1, pages_index ) )
|
|
|
|
movees.reverse()
|
|
|
|
for index in movees:
|
|
|
|
page = self.widget( index )
|
|
|
|
self._MovePage( page, dest_notebook, 0 )
|
|
|
|
|
|
|
|
|
|
def _ShiftPage( self, page_index, delta = None, new_index = None ):
|
|
|
|
new_page_index = page_index
|
|
|
|
if delta is not None:
|
|
|
|
new_page_index = page_index + delta
|
|
|
|
|
|
if new_index is not None:
|
|
|
|
new_page_index = new_index
|
|
|
|
|
|
if new_page_index == page_index:
|
|
|
|
return
|
|
|
|
|
|
if 0 <= new_page_index and new_page_index <= self.count() - 1:
|
|
|
|
page_is_selected = self.currentIndex() == page_index
|
|
|
|
page = self.widget( page_index )
|
|
name = self.tabText( page_index )
|
|
|
|
self.removeTab( page_index )
|
|
|
|
self._UpdatePreviousPageIndex()
|
|
|
|
self.insertTab( new_page_index, page, name )
|
|
if page_is_selected: self.setCurrentIndex( new_page_index )
|
|
|
|
|
|
|
|
def _ShowMenu( self, screen_position ):
|
|
|
|
tab_index = ClientGUIFunctions.NotebookScreenToHitTest( self, screen_position )
|
|
|
|
num_pages = self.count()
|
|
|
|
end_index = num_pages - 1
|
|
|
|
more_than_one_tab = num_pages > 1
|
|
|
|
click_over_tab = tab_index != -1
|
|
|
|
can_go_home = tab_index > 1
|
|
can_go_left = tab_index > 0
|
|
can_go_right = tab_index < end_index
|
|
can_go_end = tab_index < end_index - 1
|
|
|
|
click_over_page_of_pages = False
|
|
|
|
menu = QW.QMenu()
|
|
|
|
if click_over_tab:
|
|
|
|
page = self.widget( tab_index )
|
|
|
|
click_over_page_of_pages = isinstance( page, PagesNotebook )
|
|
|
|
if HG.client_controller.new_options.GetBoolean( 'advanced_mode' ):
|
|
|
|
label = 'page weight: {}'.format( HydrusData.ToHumanInt( page.GetTotalWeight() ) )
|
|
|
|
ClientGUIMenus.AppendMenuLabel( menu, label, label )
|
|
|
|
ClientGUIMenus.AppendSeparator( menu )
|
|
|
|
|
|
ClientGUIMenus.AppendMenuItem( menu, 'close page', 'Close this page.', self._ClosePage, tab_index )
|
|
|
|
if more_than_one_tab:
|
|
|
|
if not can_go_left or not can_go_right:
|
|
|
|
if num_pages == 2:
|
|
|
|
label = 'close other page'
|
|
description = 'Close the other page.'
|
|
|
|
else:
|
|
|
|
label = 'close other pages'
|
|
description = 'Close all pages but this one.'
|
|
|
|
|
|
ClientGUIMenus.AppendMenuItem( menu, label, description, self._CloseOtherPages, tab_index )
|
|
|
|
else:
|
|
|
|
close_menu = QW.QMenu( menu )
|
|
|
|
ClientGUIMenus.AppendMenuItem( close_menu, 'other pages', 'Close all pages but this one.', self._CloseOtherPages, tab_index )
|
|
|
|
if can_go_left:
|
|
|
|
ClientGUIMenus.AppendMenuItem( close_menu, 'pages to the left', 'Close all pages to the left of this one.', self._CloseLeftPages, tab_index )
|
|
|
|
|
|
if can_go_right:
|
|
|
|
ClientGUIMenus.AppendMenuItem( close_menu, 'pages to the right', 'Close all pages to the right of this one.', self._CloseRightPages, tab_index )
|
|
|
|
|
|
ClientGUIMenus.AppendMenu( menu, close_menu, 'close' )
|
|
|
|
|
|
|
|
ClientGUIMenus.AppendSeparator( menu )
|
|
|
|
|
|
#
|
|
|
|
if click_over_page_of_pages:
|
|
|
|
notebook_to_get_selectable_media_pages_from = self.widget( tab_index )
|
|
|
|
else:
|
|
|
|
notebook_to_get_selectable_media_pages_from = self
|
|
|
|
|
|
selectable_media_pages = notebook_to_get_selectable_media_pages_from.GetMediaPages()
|
|
|
|
if len( selectable_media_pages ) > 0:
|
|
|
|
select_menu = QW.QMenu( menu )
|
|
|
|
for selectable_media_page in selectable_media_pages:
|
|
|
|
label = selectable_media_page.GetNameForMenu()
|
|
|
|
ClientGUIMenus.AppendMenuItem( select_menu, label, 'select this page', self.ShowPage, selectable_media_page )
|
|
|
|
|
|
ClientGUIMenus.AppendMenu( menu, select_menu, 'pages' )
|
|
|
|
|
|
#
|
|
|
|
if more_than_one_tab:
|
|
|
|
selection_index = self.currentIndex()
|
|
|
|
can_select_home = selection_index > 1
|
|
can_select_left = selection_index > 0
|
|
can_select_right = selection_index < end_index
|
|
can_select_end = selection_index < end_index - 1
|
|
|
|
navigate_menu = QW.QMenu( menu )
|
|
|
|
if can_select_home:
|
|
|
|
ClientGUIMenus.AppendMenuItem( navigate_menu, 'first page', 'Select the page at the start of these.', self.MoveSelectionEnd, -1 )
|
|
|
|
|
|
if can_select_left:
|
|
|
|
ClientGUIMenus.AppendMenuItem( navigate_menu, 'page to the left', 'Select the page to the left of this one.', self.MoveSelection, -1 )
|
|
|
|
|
|
if can_select_right:
|
|
|
|
ClientGUIMenus.AppendMenuItem( navigate_menu, 'page to the right', 'Select the page to the right of this one.', self.MoveSelection, 1 )
|
|
|
|
|
|
if can_select_end:
|
|
|
|
ClientGUIMenus.AppendMenuItem( navigate_menu, 'last page', 'Select the page at the end of these.', self.MoveSelectionEnd, 1 )
|
|
|
|
|
|
ClientGUIMenus.AppendMenu( menu, navigate_menu, 'select' )
|
|
|
|
|
|
ClientGUIMenus.AppendSeparator( menu )
|
|
|
|
ClientGUIMenus.AppendMenuItem( menu, 'new page', 'Choose a new page.', self._ChooseNewPage )
|
|
|
|
if click_over_tab:
|
|
|
|
ClientGUIMenus.AppendMenuItem( menu, 'new page here', 'Choose a new page.', self._ChooseNewPage, tab_index )
|
|
|
|
ClientGUIMenus.AppendSeparator( menu )
|
|
|
|
if more_than_one_tab:
|
|
|
|
move_menu = QW.QMenu( menu )
|
|
|
|
if can_go_home:
|
|
|
|
ClientGUIMenus.AppendMenuItem( move_menu, 'to left end', 'Move this page all the way to the left.', self._ShiftPage, tab_index, new_index=0 )
|
|
|
|
|
|
if can_go_left:
|
|
|
|
ClientGUIMenus.AppendMenuItem( move_menu, 'left', 'Move this page one to the left.', self._ShiftPage, tab_index, delta=-1 )
|
|
|
|
|
|
if can_go_right:
|
|
|
|
ClientGUIMenus.AppendMenuItem( move_menu, 'right', 'Move this page one to the right.', self._ShiftPage, tab_index, 1 )
|
|
|
|
|
|
if can_go_end:
|
|
|
|
ClientGUIMenus.AppendMenuItem( move_menu, 'to right end', 'Move this page all the way to the right.', self._ShiftPage, tab_index, new_index=end_index )
|
|
|
|
|
|
ClientGUIMenus.AppendMenu( menu, move_menu, 'move page' )
|
|
|
|
|
|
ClientGUIMenus.AppendMenuItem( menu, 'rename page', 'Rename this page.', self._RenamePage, tab_index )
|
|
|
|
ClientGUIMenus.AppendMenuItem( menu, 'duplicate page', 'Duplicate this page.', self._DuplicatePage, tab_index )
|
|
|
|
if more_than_one_tab:
|
|
|
|
ClientGUIMenus.AppendSeparator( menu )
|
|
|
|
submenu = QW.QMenu( menu )
|
|
|
|
ClientGUIMenus.AppendMenuItem( submenu, 'by most files first', 'Sort these pages according to how many files they have.', self._SortPagesByFileCount, 'desc' )
|
|
ClientGUIMenus.AppendMenuItem( submenu, 'by fewest files first', 'Sort these pages according to how few files they have.', self._SortPagesByFileCount, 'asc' )
|
|
ClientGUIMenus.AppendMenuItem( submenu, 'by largest total file size first', 'Sort these pages according to how large their files are.', self._SortPagesByFileSize, 'desc' )
|
|
ClientGUIMenus.AppendMenuItem( submenu, 'by smallest total file size first', 'Sort these pages according to how small their files are.', self._SortPagesByFileSize, 'asc' )
|
|
ClientGUIMenus.AppendMenuItem( submenu, 'by name a-z', 'Sort these pages according to their names.', self._SortPagesByName, 'asc' )
|
|
ClientGUIMenus.AppendMenuItem( submenu, 'by name z-a', 'Sort these pages according to their names.', self._SortPagesByName, 'desc' )
|
|
|
|
ClientGUIMenus.AppendMenu( menu, submenu, 'sort pages' )
|
|
|
|
|
|
ClientGUIMenus.AppendSeparator( menu )
|
|
|
|
ClientGUIMenus.AppendMenuItem( menu, 'send this page down to a new page of pages', 'Make a new page of pages and put this page in it.', self._SendPageToNewNotebook, tab_index )
|
|
|
|
if can_go_right:
|
|
|
|
ClientGUIMenus.AppendMenuItem( menu, 'send pages to the right to a new page of pages', 'Make a new page of pages and put all the pages to the right into it.', self._SendRightPagesToNewNotebook, tab_index )
|
|
|
|
|
|
if click_over_page_of_pages and page.count() > 0:
|
|
|
|
ClientGUIMenus.AppendSeparator( menu )
|
|
|
|
ClientGUIMenus.AppendMenuItem( menu, 'refresh all this page\'s pages', 'Command every page below this one to refresh.', page.RefreshAllPages )
|
|
|
|
|
|
|
|
existing_session_names = self._controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION_CONTAINER )
|
|
|
|
if len( existing_session_names ) > 0 or click_over_page_of_pages:
|
|
|
|
ClientGUIMenus.AppendSeparator( menu )
|
|
|
|
|
|
if len( existing_session_names ) > 0:
|
|
|
|
submenu = QW.QMenu( menu )
|
|
|
|
for name in existing_session_names:
|
|
|
|
ClientGUIMenus.AppendMenuItem( submenu, name, 'Load this session here.', self.AppendGUISessionFreshest, name )
|
|
|
|
|
|
ClientGUIMenus.AppendMenu( menu, submenu, 'append session' )
|
|
|
|
|
|
if click_over_page_of_pages:
|
|
|
|
submenu = QW.QMenu( menu )
|
|
|
|
for name in existing_session_names:
|
|
|
|
if name in ClientGUISession.RESERVED_SESSION_NAMES:
|
|
|
|
continue
|
|
|
|
|
|
ClientGUIMenus.AppendMenuItem( submenu, name, 'Save this page of pages to the session.', self._controller.gui.ProposeSaveGUISession, notebook = page, name = name )
|
|
|
|
|
|
ClientGUIMenus.AppendMenuItem( submenu, 'create a new session', 'Save this page of pages to the session.', self._controller.gui.ProposeSaveGUISession, notebook = page, suggested_name = page.GetName() )
|
|
|
|
ClientGUIMenus.AppendMenu( menu, submenu, 'save this page of pages to a session' )
|
|
|
|
|
|
CGC.core().PopupMenu( self, menu )
|
|
|
|
|
|
def _SortPagesByFileCount( self, order ):
|
|
|
|
def key( page ):
|
|
|
|
( total_num_files, ( total_num_value, total_num_range ) ) = page.GetNumFileSummary()
|
|
|
|
return ( total_num_files, total_num_range, total_num_value )
|
|
|
|
|
|
ordered_pages = sorted( self.GetPages(), key = key, reverse = order == 'desc' )
|
|
|
|
self._SortPagesSetPages( ordered_pages )
|
|
|
|
|
|
def _SortPagesByFileSize( self, order ):
|
|
|
|
def key( page ):
|
|
|
|
total_file_size = page.GetTotalFileSize()
|
|
|
|
return total_file_size
|
|
|
|
|
|
ordered_pages = sorted( self.GetPages(), key = key, reverse = order == 'desc' )
|
|
|
|
self._SortPagesSetPages( ordered_pages )
|
|
|
|
|
|
def _SortPagesByName( self, order ):
|
|
|
|
def file_count_secondary( page ):
|
|
|
|
( total_num_files, ( total_num_value, total_num_range ) ) = page.GetNumFileSummary()
|
|
|
|
return ( total_num_files, total_num_range, total_num_value )
|
|
|
|
|
|
ordered_pages = sorted( self.GetPages(), key = file_count_secondary, reverse = True )
|
|
|
|
ordered_pages = sorted( ordered_pages, key = lambda page: page.GetName(), reverse = order == 'desc' )
|
|
|
|
self._SortPagesSetPages( ordered_pages )
|
|
|
|
|
|
def _SortPagesSetPages( self, ordered_pages ):
|
|
|
|
selected_page = self.currentWidget()
|
|
|
|
pages_to_names = {}
|
|
|
|
for i in range( self.count() ):
|
|
|
|
page = self.widget( 0 )
|
|
|
|
name = self.tabText( 0 )
|
|
|
|
pages_to_names[ page ] = name
|
|
|
|
self.removeTab( 0 )
|
|
|
|
self._UpdatePreviousPageIndex()
|
|
|
|
|
|
for page in ordered_pages:
|
|
|
|
name = pages_to_names[ page ]
|
|
|
|
self.addTab( page, name )
|
|
|
|
if page == selected_page:
|
|
|
|
self.setCurrentIndex( self.count() - 1 )
|
|
|
|
|
|
|
|
|
|
def AppendGUISession( self, session: ClientGUISession.GUISessionContainer ):
|
|
|
|
starting_index = self._GetDefaultPageInsertionIndex()
|
|
|
|
forced_insertion_index = starting_index
|
|
|
|
self.InsertSession( forced_insertion_index, session )
|
|
|
|
|
|
def AppendGUISessionBackup( self, name, timestamp, load_in_a_page_of_pages = True ):
|
|
|
|
try:
|
|
|
|
session = session = self._controller.Read( 'gui_session', name, timestamp )
|
|
|
|
except Exception as e:
|
|
|
|
HydrusData.ShowText( 'While trying to load session "{}" (ts {}), this error happened:'.format( name, timestamp ) )
|
|
HydrusData.ShowException( e )
|
|
|
|
return
|
|
|
|
|
|
if load_in_a_page_of_pages:
|
|
|
|
destination = self.NewPagesNotebook( name = name, give_it_a_blank_page = False )
|
|
|
|
else:
|
|
|
|
destination = self
|
|
|
|
|
|
destination.AppendGUISession( session )
|
|
|
|
|
|
def AppendGUISessionFreshest( self, name, load_in_a_page_of_pages = True ):
|
|
|
|
job_key = ClientThreading.JobKey()
|
|
|
|
job_key.SetVariable( 'popup_text_1', 'loading session "{}"\u2026'.format( name ) )
|
|
|
|
HG.client_controller.pub( 'message', job_key )
|
|
|
|
# get that message showing before we do the work of loading session
|
|
HG.client_controller.app.processEvents()
|
|
|
|
try:
|
|
|
|
session = self._controller.Read( 'gui_session', name )
|
|
|
|
except Exception as e:
|
|
|
|
HydrusData.ShowText( 'While trying to load session "{}", this error happened:'.format( name ) )
|
|
HydrusData.ShowException( e )
|
|
|
|
return
|
|
|
|
|
|
HG.client_controller.app.processEvents()
|
|
|
|
if load_in_a_page_of_pages:
|
|
|
|
destination = self.NewPagesNotebook( name = name, give_it_a_blank_page = False )
|
|
|
|
else:
|
|
|
|
destination = self
|
|
|
|
|
|
HG.client_controller.app.processEvents()
|
|
|
|
destination.AppendGUISession( session )
|
|
|
|
self.freshSessionLoaded.emit( session )
|
|
|
|
job_key.Delete()
|
|
|
|
|
|
def ChooseNewPage( self ):
|
|
|
|
self._ChooseNewPage()
|
|
|
|
|
|
def ChooseNewPageForDeepestNotebook( self ):
|
|
|
|
current_page = self.currentWidget()
|
|
|
|
if isinstance( current_page, PagesNotebook ):
|
|
|
|
current_page.ChooseNewPageForDeepestNotebook()
|
|
|
|
else:
|
|
|
|
self._ChooseNewPage()
|
|
|
|
|
|
|
|
def CleanBeforeClose( self ):
|
|
|
|
for page in self._GetPages():
|
|
|
|
page.CleanBeforeClose()
|
|
|
|
|
|
|
|
def CleanBeforeDestroy( self ):
|
|
|
|
for page in self._GetPages():
|
|
|
|
page.CleanBeforeDestroy()
|
|
|
|
|
|
self._controller.ReleasePageKey( self._page_key )
|
|
|
|
|
|
def CloseCurrentPage( self, polite = True ):
|
|
|
|
selection = self.currentIndex()
|
|
|
|
if selection != -1:
|
|
|
|
page = self.widget( selection )
|
|
|
|
if isinstance( page, PagesNotebook ):
|
|
|
|
if page.GetNumPages() > 0:
|
|
|
|
page.CloseCurrentPage( polite )
|
|
|
|
else:
|
|
|
|
self._ClosePage( selection, polite = polite )
|
|
|
|
|
|
else:
|
|
|
|
self._ClosePage( selection, polite = polite )
|
|
|
|
|
|
|
|
|
|
def eventFilter( self, watched, event ):
|
|
|
|
if event.type() in ( QC.QEvent.MouseButtonDblClick, QC.QEvent.MouseButtonRelease ):
|
|
|
|
screen_position = QG.QCursor.pos()
|
|
|
|
if watched == self.tabBar():
|
|
|
|
tab_pos = self.tabBar().mapFromGlobal( screen_position )
|
|
|
|
over_a_tab = tab_pos != -1
|
|
over_tab_greyspace = tab_pos == -1
|
|
|
|
else:
|
|
|
|
over_a_tab = False
|
|
|
|
widget_under_mouse = QW.QApplication.instance().widgetAt( screen_position )
|
|
|
|
if widget_under_mouse is None:
|
|
|
|
over_tab_greyspace = None
|
|
|
|
else:
|
|
|
|
if self.count() == 0 and isinstance( widget_under_mouse, QW.QStackedWidget ):
|
|
|
|
over_tab_greyspace = True
|
|
|
|
else:
|
|
|
|
over_tab_greyspace = widget_under_mouse == self
|
|
|
|
|
|
|
|
|
|
if event.type() == QC.QEvent.MouseButtonDblClick:
|
|
|
|
if event.button() == QC.Qt.LeftButton and over_tab_greyspace and not over_a_tab:
|
|
|
|
self.EventNewPageFromScreenPosition( screen_position )
|
|
|
|
return True
|
|
|
|
|
|
elif event.type() == QC.QEvent.MouseButtonRelease:
|
|
|
|
if event.button() == QC.Qt.RightButton and ( over_a_tab or over_tab_greyspace ):
|
|
|
|
self.ShowMenuFromScreenPosition( screen_position )
|
|
|
|
return True
|
|
|
|
elif event.button() == QC.Qt.MiddleButton and over_tab_greyspace and not over_a_tab:
|
|
|
|
self.EventNewPageFromScreenPosition( screen_position )
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
def ShowMenuFromScreenPosition( self, position ):
|
|
|
|
notebook = self._GetNotebookFromScreenPosition( position )
|
|
|
|
notebook._ShowMenu( position )
|
|
|
|
|
|
def EventNewPageFromScreenPosition( self, position ):
|
|
|
|
notebook = self._GetNotebookFromScreenPosition( position )
|
|
|
|
notebook._ChooseNewPage()
|
|
|
|
|
|
def GetAPIInfoDict( self, simple ):
|
|
|
|
return {}
|
|
|
|
|
|
def GetCurrentGUISession( self, name: str, only_changed_page_data: bool, about_to_save: bool ):
|
|
|
|
( page_container, hashes_to_page_data, skipped_unchanged_page_hashes ) = self.GetSerialisablePage( only_changed_page_data, about_to_save )
|
|
|
|
session = ClientGUISession.GUISessionContainer( name, top_notebook_container = page_container, hashes_to_page_data = hashes_to_page_data, skipped_unchanged_page_hashes = skipped_unchanged_page_hashes )
|
|
|
|
return session
|
|
|
|
|
|
def GetCurrentMediaPage( self ):
|
|
|
|
page = self.currentWidget()
|
|
|
|
if isinstance( page, PagesNotebook ):
|
|
|
|
return page.GetCurrentMediaPage()
|
|
|
|
else:
|
|
|
|
return page # this can be None
|
|
|
|
|
|
|
|
def GetMediaPages( self, only_my_level = False ):
|
|
|
|
return self._GetMediaPages( only_my_level )
|
|
|
|
|
|
def GetName( self ):
|
|
|
|
return self._name
|
|
|
|
|
|
def GetNameForMenu( self ) -> str:
|
|
|
|
name_for_menu = self.GetName()
|
|
|
|
( num_files, ( num_value, num_range ) ) = self.GetNumFileSummary()
|
|
|
|
if num_files > 0:
|
|
|
|
name_for_menu = '{} - {} files'.format( name_for_menu, HydrusData.ToHumanInt( num_files ) )
|
|
|
|
|
|
if num_value != num_range:
|
|
|
|
name_for_menu = '{} - {}'.format( name_for_menu, HydrusData.ConvertValueRangeToPrettyString( num_value, num_range ) )
|
|
|
|
|
|
return HydrusText.ElideText( name_for_menu, 32, elide_center = True )
|
|
|
|
|
|
def GetNumFileSummary( self ):
|
|
|
|
total_num_files = 0
|
|
total_num_value = 0
|
|
total_num_range = 0
|
|
|
|
for page in self._GetPages():
|
|
|
|
( num_files, ( num_value, num_range ) ) = page.GetNumFileSummary()
|
|
|
|
total_num_files += num_files
|
|
total_num_value += num_value
|
|
total_num_range += num_range
|
|
|
|
|
|
return ( total_num_files, ( total_num_value, total_num_range ) )
|
|
|
|
|
|
def GetNumPages( self, only_my_level = False ):
|
|
|
|
if only_my_level:
|
|
|
|
return self.count()
|
|
|
|
else:
|
|
|
|
total = 0
|
|
|
|
for page in self._GetPages():
|
|
|
|
if isinstance( page, PagesNotebook ):
|
|
|
|
total += page.GetNumPages( False )
|
|
|
|
else:
|
|
|
|
total += 1
|
|
|
|
|
|
|
|
return total
|
|
|
|
|
|
|
|
def GetOrMakeGalleryDownloaderPage( self, desired_page_name = None, desired_page_key = None, select_page = True ):
|
|
|
|
potential_gallery_downloader_pages = [ page for page in self._GetMediaPages( False ) if page.IsGalleryDownloaderPage() ]
|
|
|
|
if desired_page_key is not None and desired_page_key in ( page.GetPageKey() for page in potential_gallery_downloader_pages ):
|
|
|
|
potential_gallery_downloader_pages = [ page for page in potential_gallery_downloader_pages if page.GetPageKey() == desired_page_key ]
|
|
|
|
elif desired_page_name is not None:
|
|
|
|
potential_gallery_downloader_pages = [ page for page in potential_gallery_downloader_pages if page.GetName() == desired_page_name ]
|
|
|
|
|
|
if len( potential_gallery_downloader_pages ) > 0:
|
|
|
|
# ok, we can use an existing one. should we use the current?
|
|
|
|
current_media_page = self.GetCurrentMediaPage()
|
|
|
|
if current_media_page is not None and current_media_page in potential_gallery_downloader_pages:
|
|
|
|
return current_media_page
|
|
|
|
else:
|
|
|
|
return potential_gallery_downloader_pages[0]
|
|
|
|
|
|
else:
|
|
|
|
return self.NewPageImportGallery( page_name = desired_page_name, on_deepest_notebook = True, select_page = select_page )
|
|
|
|
|
|
|
|
def GetOrMakeMultipleWatcherPage( self, desired_page_name = None, desired_page_key = None, select_page = True ):
|
|
|
|
potential_watcher_pages = [ page for page in self._GetMediaPages( False ) if page.IsMultipleWatcherPage() ]
|
|
|
|
if desired_page_key is not None and desired_page_key in ( page.GetPageKey() for page in potential_watcher_pages ):
|
|
|
|
potential_watcher_pages = [ page for page in potential_watcher_pages if page.GetPageKey() == desired_page_key ]
|
|
|
|
elif desired_page_name is not None:
|
|
|
|
potential_watcher_pages = [ page for page in potential_watcher_pages if page.GetName() == desired_page_name ]
|
|
|
|
|
|
if len( potential_watcher_pages ) > 0:
|
|
|
|
# ok, we can use an existing one. should we use the current?
|
|
|
|
current_media_page = self.GetCurrentMediaPage()
|
|
|
|
if current_media_page is not None and current_media_page in potential_watcher_pages:
|
|
|
|
return current_media_page
|
|
|
|
else:
|
|
|
|
return potential_watcher_pages[0]
|
|
|
|
|
|
else:
|
|
|
|
return self.NewPageImportMultipleWatcher( page_name = desired_page_name, on_deepest_notebook = True, select_page = select_page )
|
|
|
|
|
|
|
|
def GetOrMakeURLImportPage( self, desired_page_name = None, desired_page_key = None, select_page = True ):
|
|
|
|
potential_url_import_pages = [ page for page in self._GetMediaPages( False ) if page.IsURLImportPage() ]
|
|
|
|
if desired_page_key is not None and desired_page_key in ( page.GetPageKey() for page in potential_url_import_pages ):
|
|
|
|
potential_url_import_pages = [ page for page in potential_url_import_pages if page.GetPageKey() == desired_page_key ]
|
|
|
|
elif desired_page_name is not None:
|
|
|
|
potential_url_import_pages = [ page for page in potential_url_import_pages if page.GetName() == desired_page_name ]
|
|
|
|
|
|
if len( potential_url_import_pages ) > 0:
|
|
|
|
# ok, we can use an existing one. should we use the current?
|
|
|
|
current_media_page = self.GetCurrentMediaPage()
|
|
|
|
if current_media_page is not None and current_media_page in potential_url_import_pages:
|
|
|
|
return current_media_page
|
|
|
|
else:
|
|
|
|
return potential_url_import_pages[0]
|
|
|
|
|
|
else:
|
|
|
|
return self.NewPageImportURLs( page_name = desired_page_name, on_deepest_notebook = True, select_page = select_page )
|
|
|
|
|
|
|
|
def GetPageFromPageKey( self, page_key ) -> typing.Optional[ Page ]:
|
|
|
|
if self._page_key == page_key:
|
|
|
|
return self
|
|
|
|
|
|
for page in self._GetPages():
|
|
|
|
if page.GetPageKey() == page_key:
|
|
|
|
return page
|
|
|
|
|
|
if isinstance( page, PagesNotebook ):
|
|
|
|
if page.HasPageKey( page_key ):
|
|
|
|
return page.GetPageFromPageKey( page_key )
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
def GetPageKey( self ):
|
|
|
|
return self._page_key
|
|
|
|
|
|
def GetPageKeys( self ):
|
|
|
|
page_keys = { self._page_key }
|
|
|
|
for page in self._GetPages():
|
|
|
|
page_keys.update( page.GetPageKeys() )
|
|
|
|
|
|
return page_keys
|
|
|
|
|
|
def GetParentNotebook( self ):
|
|
|
|
return self._parent_notebook
|
|
|
|
|
|
def GetPages( self ):
|
|
|
|
return self._GetPages()
|
|
|
|
|
|
def GetPrettyStatusForStatusBar( self ):
|
|
|
|
( num_files, ( num_value, num_range ) ) = self.GetNumFileSummary()
|
|
|
|
num_string = HydrusData.ToHumanInt( num_files )
|
|
|
|
if num_range > 0 and num_value != num_range:
|
|
|
|
num_string += ', ' + HydrusData.ConvertValueRangeToPrettyString( num_value, num_range )
|
|
|
|
|
|
return HydrusData.ToHumanInt( self.count() ) + ' pages, ' + num_string + ' files'
|
|
|
|
|
|
def GetSerialisablePage( self, only_changed_page_data, about_to_save ):
|
|
|
|
page_containers = []
|
|
|
|
hashes_to_page_data = {}
|
|
|
|
skipped_unchanged_page_hashes = set()
|
|
|
|
for page in self._GetPages():
|
|
|
|
( sub_page_container, some_hashes_to_page_data, some_skipped_unchanged_page_hashes ) = page.GetSerialisablePage( only_changed_page_data, about_to_save )
|
|
|
|
page_containers.append( sub_page_container )
|
|
|
|
hashes_to_page_data.update( some_hashes_to_page_data )
|
|
skipped_unchanged_page_hashes.update( some_skipped_unchanged_page_hashes )
|
|
|
|
|
|
page_container = ClientGUISession.GUISessionContainerPageNotebook( self._name, page_containers = page_containers )
|
|
|
|
return ( page_container, hashes_to_page_data, skipped_unchanged_page_hashes )
|
|
|
|
|
|
def GetSessionAPIInfoDict( self, is_selected = True ):
|
|
|
|
current_page = self.currentWidget()
|
|
|
|
my_pages_list = []
|
|
|
|
for page in self._GetPages():
|
|
|
|
page_is_selected = is_selected and page == current_page
|
|
|
|
page_info_dict = page.GetSessionAPIInfoDict( is_selected = page_is_selected )
|
|
|
|
my_pages_list.append( page_info_dict )
|
|
|
|
|
|
root = {}
|
|
|
|
root[ 'name' ] = self.GetName()
|
|
root[ 'page_key' ] = self._page_key.hex()
|
|
root[ 'page_type' ] = ClientGUIManagement.MANAGEMENT_TYPE_PAGE_OF_PAGES
|
|
root[ 'selected' ] = is_selected
|
|
root[ 'pages' ] = my_pages_list
|
|
|
|
return root
|
|
|
|
|
|
def GetTestAbleToCloseStatement( self ):
|
|
|
|
count = collections.Counter()
|
|
|
|
for page in self._GetMediaPages( False ):
|
|
|
|
try:
|
|
|
|
page.CheckAbleToClose()
|
|
|
|
except HydrusExceptions.VetoException as e:
|
|
|
|
reason = str( e )
|
|
|
|
count[ reason ] += 1
|
|
|
|
|
|
|
|
if len( count ) > 0:
|
|
|
|
message = ''
|
|
|
|
for ( reason, c ) in list(count.items()):
|
|
|
|
if c == 1:
|
|
|
|
message = '1 page says: ' + reason
|
|
|
|
else:
|
|
|
|
message = HydrusData.ToHumanInt( c ) + ' pages say:' + reason
|
|
|
|
|
|
message += os.linesep
|
|
|
|
|
|
return message
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def GetTotalFileSize( self ):
|
|
|
|
total_file_size = 0
|
|
|
|
for page in self._GetPages():
|
|
|
|
total_file_size += page.GetTotalFileSize()
|
|
|
|
|
|
return total_file_size
|
|
|
|
|
|
def GetTotalNumHashesAndSeeds( self ) -> int:
|
|
|
|
total_num_hashes = 0
|
|
total_num_seeds = 0
|
|
|
|
for page in self._GetPages():
|
|
|
|
( num_hashes, num_seeds ) = page.GetTotalNumHashesAndSeeds()
|
|
|
|
total_num_hashes += num_hashes
|
|
total_num_seeds += num_seeds
|
|
|
|
|
|
return ( total_num_hashes, total_num_seeds )
|
|
|
|
|
|
def GetTotalWeight( self ) -> int:
|
|
|
|
total_weight = sum( ( page.GetTotalWeight() for page in self._GetPages() ) )
|
|
|
|
return total_weight
|
|
|
|
|
|
def HasMediaPageName( self, page_name, only_my_level = False ):
|
|
|
|
media_pages = self._GetMediaPages( only_my_level )
|
|
|
|
for page in media_pages:
|
|
|
|
if page.GetName() == page_name:
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
def HasPage( self, page ):
|
|
|
|
return self.HasPageKey( page.GetPageKey() )
|
|
|
|
|
|
def HasPageKey( self, page_key ):
|
|
|
|
for page in self._GetPages():
|
|
|
|
if page.GetPageKey() == page_key:
|
|
|
|
return True
|
|
|
|
elif isinstance( page, PagesNotebook ) and page.HasPageKey( page_key ):
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
def HasMultipleWatcherPage( self ):
|
|
|
|
for page in self._GetPages():
|
|
|
|
if isinstance( page, PagesNotebook ):
|
|
|
|
if page.HasMultipleWatcherPage():
|
|
|
|
return True
|
|
|
|
|
|
else:
|
|
|
|
if page.IsMultipleWatcherPage():
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
def HasURLImportPage( self ):
|
|
|
|
for page in self._GetPages():
|
|
|
|
if isinstance( page, PagesNotebook ):
|
|
|
|
if page.HasURLImportPage():
|
|
|
|
return True
|
|
|
|
|
|
else:
|
|
|
|
if page.IsURLImportPage():
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
def InsertSession( self, forced_insertion_index: int, session: ClientGUISession.GUISessionContainer, session_is_clean = True ):
|
|
|
|
# get the top notebook, then for every page in there...
|
|
|
|
top_notebook_container = session.GetTopNotebook()
|
|
|
|
page_containers = top_notebook_container.GetPageContainers()
|
|
select_first_page = True
|
|
|
|
self.InsertSessionNotebookPages( forced_insertion_index, session, page_containers, select_first_page, session_is_clean = session_is_clean )
|
|
|
|
|
|
def InsertSessionNotebook( self, forced_insertion_index: int, session: ClientGUISession.GUISessionContainer, notebook_page_container: ClientGUISession.GUISessionContainerPageNotebook, select_first_page: bool, session_is_clean = True ):
|
|
|
|
name = notebook_page_container.GetName()
|
|
|
|
page = self.NewPagesNotebook( name, forced_insertion_index = forced_insertion_index, give_it_a_blank_page = False, select_page = select_first_page )
|
|
|
|
page_containers = notebook_page_container.GetPageContainers()
|
|
|
|
page.InsertSessionNotebookPages( 0, session, page_containers, select_first_page, session_is_clean = session_is_clean )
|
|
|
|
|
|
def InsertSessionNotebookPages( self, forced_insertion_index: int, session: ClientGUISession.GUISessionContainer, page_containers: typing.Collection[ ClientGUISession.GUISessionContainerPage ], select_first_page: bool, session_is_clean = True ):
|
|
|
|
done_first_page = False
|
|
|
|
for page_container in page_containers:
|
|
|
|
select_page = select_first_page and not done_first_page
|
|
|
|
try:
|
|
|
|
if isinstance( page_container, ClientGUISession.GUISessionContainerPageNotebook ):
|
|
|
|
self.InsertSessionNotebook( forced_insertion_index, session, page_container, select_page, session_is_clean = session_is_clean )
|
|
|
|
else:
|
|
|
|
result = self.InsertSessionPage( forced_insertion_index, session, page_container, select_page, session_is_clean = session_is_clean )
|
|
|
|
if result is None:
|
|
|
|
continue
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
HydrusData.ShowException( e )
|
|
|
|
|
|
forced_insertion_index += 1
|
|
|
|
done_first_page = True
|
|
|
|
|
|
|
|
def InsertSessionPage( self, forced_insertion_index: int, session: ClientGUISession.GUISessionContainer, page_container: ClientGUISession.GUISessionContainerPageSingle, select_page: bool, session_is_clean = True ):
|
|
|
|
try:
|
|
|
|
page_data_hash = page_container.GetPageDataHash()
|
|
|
|
page_data = session.GetPageData( page_data_hash )
|
|
|
|
except HydrusExceptions.DataMissing as e:
|
|
|
|
HydrusData.ShowText( 'The page with name "{}" and hash "{}" failed to load because its data was missing!'.format( page_container.GetName(), page_data_hash.hex() ) )
|
|
|
|
return None
|
|
|
|
|
|
management_controller = page_data.GetManagementController()
|
|
initial_hashes = page_data.GetHashes()
|
|
|
|
page = self.NewPage( management_controller, initial_hashes = initial_hashes, forced_insertion_index = forced_insertion_index, select_page = select_page )
|
|
|
|
if session_is_clean and page is not None:
|
|
|
|
page.SetPageContainerClean( page_container )
|
|
|
|
|
|
return page
|
|
|
|
|
|
def IsMultipleWatcherPage( self ):
|
|
|
|
return False
|
|
|
|
|
|
def IsImporter( self ):
|
|
|
|
return False
|
|
|
|
|
|
def IsURLImportPage( self ):
|
|
|
|
return False
|
|
|
|
|
|
def LoadGUISession( self, name ):
|
|
|
|
if self.count() > 0:
|
|
|
|
message = 'Close the current pages and load session "{}"?'.format( name )
|
|
|
|
result = ClientGUIDialogsQuick.GetYesNo( self, message, title = 'Clear and load session?' )
|
|
|
|
if result != QW.QDialog.Accepted:
|
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
|
self.TestAbleToClose()
|
|
|
|
except HydrusExceptions.VetoException:
|
|
|
|
return
|
|
|
|
|
|
self._CloseAllPages( polite = False, delete_pages = True )
|
|
|
|
self._controller.CallLaterQtSafe( self, 1.0, 'append session', self.AppendGUISessionFreshest, name, load_in_a_page_of_pages = False )
|
|
|
|
else:
|
|
|
|
self.AppendGUISessionFreshest( name, load_in_a_page_of_pages = False )
|
|
|
|
|
|
|
|
def MediaDragAndDropDropped( self, source_page_key, hashes ):
|
|
|
|
source_page = self.GetPageFromPageKey( source_page_key )
|
|
|
|
if source_page is None:
|
|
|
|
return
|
|
|
|
|
|
source_management_controller = source_page.GetManagementController()
|
|
|
|
location_context = source_management_controller.GetVariable( 'location_context' )
|
|
|
|
screen_position = QG.QCursor.pos()
|
|
|
|
dest_notebook = self._GetNotebookFromScreenPosition( screen_position )
|
|
|
|
tab_index = ClientGUIFunctions.NotebookScreenToHitTest( dest_notebook, screen_position )
|
|
|
|
do_add = True
|
|
# do chase - if we need to chase to an existing dest page on which we dropped files
|
|
# do return - if we need to return to source page if we created a new one
|
|
|
|
current_widget = dest_notebook.currentWidget()
|
|
|
|
if tab_index == -1 and current_widget is not None and not isinstance( current_widget, PagesNotebook ) and current_widget.rect().contains( current_widget.mapFromGlobal( screen_position ) ):
|
|
|
|
dest_page = current_widget
|
|
|
|
elif tab_index == -1:
|
|
|
|
dest_page = dest_notebook.NewPageQuery( location_context, initial_hashes = hashes )
|
|
|
|
do_add = False
|
|
|
|
else:
|
|
|
|
dest_page = dest_notebook.widget( tab_index )
|
|
|
|
if isinstance( dest_page, PagesNotebook ):
|
|
|
|
result = dest_page.GetCurrentMediaPage()
|
|
|
|
if result is None:
|
|
|
|
dest_page = dest_page.NewPageQuery( location_context, initial_hashes = hashes )
|
|
|
|
do_add = False
|
|
|
|
else:
|
|
|
|
dest_page = result
|
|
|
|
|
|
|
|
|
|
if dest_page is None:
|
|
|
|
return # we somehow dropped onto a new notebook that has no pages
|
|
|
|
|
|
if isinstance( dest_page, PagesNotebook ):
|
|
|
|
return # dropped on the edge of some notebook somehow
|
|
|
|
|
|
if dest_page.GetPageKey() == source_page_key:
|
|
|
|
return # we dropped onto the same page we picked up on
|
|
|
|
|
|
if do_add:
|
|
|
|
media_results = self._controller.Read( 'media_results', hashes, sorted = True )
|
|
|
|
dest_page.AddMediaResults( media_results )
|
|
|
|
else:
|
|
|
|
self.ShowPage( source_page )
|
|
|
|
|
|
# queryKBM here for instant check, not waiting for event processing to catch up u wot mate
|
|
ctrl_down = QW.QApplication.queryKeyboardModifiers() & QC.Qt.ControlModifier
|
|
|
|
if not ctrl_down:
|
|
|
|
source_page.GetMediaPanel().RemoveMedia( source_page.GetPageKey(), hashes )
|
|
|
|
|
|
|
|
def MoveSelection( self, delta, just_do_test = False ):
|
|
|
|
if self.count() <= 1: # 1 is a no-op
|
|
|
|
return False
|
|
|
|
|
|
current_page = self.currentWidget()
|
|
|
|
i_have_done_a_recent_move = not HydrusData.TimeHasPassed( self._time_of_last_move_selection_event + 3 )
|
|
|
|
if isinstance( current_page, PagesNotebook ) and not i_have_done_a_recent_move:
|
|
|
|
if current_page.MoveSelection( delta, just_do_test = True ):
|
|
|
|
return current_page.MoveSelection( delta, just_do_test = just_do_test )
|
|
|
|
|
|
|
|
new_index = self.currentIndex() + delta
|
|
|
|
if 0 <= new_index <= self.count() - 1:
|
|
|
|
if not just_do_test:
|
|
|
|
self.setCurrentIndex( new_index )
|
|
|
|
self._time_of_last_move_selection_event = HydrusData.GetNow()
|
|
|
|
|
|
return True
|
|
|
|
|
|
return False
|
|
|
|
|
|
def MoveSelectionEnd( self, delta, just_do_test = False ):
|
|
|
|
if self.count() <= 1: # 1 is a no-op
|
|
|
|
return False
|
|
|
|
|
|
current_page = self.currentWidget()
|
|
|
|
i_have_done_a_recent_move = not HydrusData.TimeHasPassed( self._time_of_last_move_selection_event + 3 )
|
|
|
|
if isinstance( current_page, PagesNotebook ) and not i_have_done_a_recent_move:
|
|
|
|
if current_page.MoveSelectionEnd( delta, just_do_test = True ):
|
|
|
|
return current_page.MoveSelectionEnd( delta, just_do_test = just_do_test )
|
|
|
|
|
|
|
|
if delta < 0:
|
|
|
|
new_index = 0
|
|
|
|
else:
|
|
|
|
new_index = self.count() - 1
|
|
|
|
|
|
if not just_do_test:
|
|
|
|
self.setCurrentIndex( new_index )
|
|
|
|
self._time_of_last_move_selection_event = HydrusData.GetNow()
|
|
|
|
|
|
return True
|
|
|
|
|
|
def NewPage( self, management_controller, initial_hashes = None, forced_insertion_index = None, on_deepest_notebook = False, select_page = True ):
|
|
|
|
current_page = self.currentWidget()
|
|
|
|
if on_deepest_notebook and isinstance( current_page, PagesNotebook ):
|
|
|
|
return current_page.NewPage( management_controller, initial_hashes = initial_hashes, forced_insertion_index = forced_insertion_index, on_deepest_notebook = on_deepest_notebook )
|
|
|
|
|
|
WARNING_TOTAL_PAGES = self._controller.new_options.GetInteger( 'total_pages_warning' )
|
|
MAX_TOTAL_PAGES = max( 500, WARNING_TOTAL_PAGES * 2 )
|
|
|
|
(
|
|
total_active_page_count,
|
|
total_active_num_hashes,
|
|
total_active_num_seeds,
|
|
total_closed_page_count,
|
|
total_closed_num_hashes,
|
|
total_closed_num_seeds
|
|
) = self._controller.gui.GetTotalPageCounts()
|
|
|
|
if total_active_page_count + total_closed_page_count >= WARNING_TOTAL_PAGES:
|
|
|
|
self._controller.gui.DeleteAllClosedPages()
|
|
|
|
|
|
if not HG.no_page_limit_mode:
|
|
|
|
if total_active_page_count >= MAX_TOTAL_PAGES and not ClientGUIFunctions.DialogIsOpen():
|
|
|
|
message = 'The client should not have more than ' + str( MAX_TOTAL_PAGES ) + ' pages open, as it leads to program instability! Are you sure you want to open more pages?'
|
|
|
|
result = ClientGUIDialogsQuick.GetYesNo( self, message, title = 'Too many pages!', yes_label = 'yes, and do not tell me again', no_label = 'no' )
|
|
|
|
if result == QW.QDialog.Accepted:
|
|
|
|
HG.no_page_limit_mode = True
|
|
|
|
self._controller.pub( 'notify_new_options' )
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
if total_active_page_count == WARNING_TOTAL_PAGES:
|
|
|
|
HydrusData.ShowText( 'You have ' + str( total_active_page_count ) + ' pages open! You can only open a few more before program stability is affected! Please close some now!' )
|
|
|
|
|
|
|
|
self._controller.ResetIdleTimer()
|
|
self._controller.ResetPageChangeTimer()
|
|
|
|
if initial_hashes is None:
|
|
|
|
initial_hashes = []
|
|
|
|
|
|
page = Page( self, self._controller, management_controller, initial_hashes )
|
|
|
|
if forced_insertion_index is None:
|
|
|
|
if self._next_new_page_index is None:
|
|
|
|
insertion_index = self._GetDefaultPageInsertionIndex()
|
|
|
|
else:
|
|
|
|
insertion_index = self._next_new_page_index
|
|
|
|
self._next_new_page_index = None
|
|
|
|
|
|
else:
|
|
|
|
insertion_index = forced_insertion_index
|
|
|
|
|
|
page_name = page.GetName()
|
|
|
|
# in some unusual circumstances, this gets out of whack
|
|
insertion_index = min( insertion_index, self.count() )
|
|
|
|
if self._controller.new_options.GetBoolean( 'force_hide_page_signal_on_new_page' ):
|
|
|
|
current_gui_page = self._controller.gui.GetCurrentPage()
|
|
|
|
if current_gui_page is not None:
|
|
|
|
current_gui_page.PageHidden()
|
|
|
|
|
|
|
|
self.insertTab( insertion_index, page, page_name )
|
|
|
|
if select_page:
|
|
|
|
self.setCurrentIndex( insertion_index )
|
|
|
|
|
|
self._controller.pub( 'refresh_page_name', page.GetPageKey() )
|
|
self._controller.pub( 'notify_new_pages' )
|
|
|
|
page.Start()
|
|
|
|
if select_page:
|
|
|
|
page.SetSearchFocus()
|
|
|
|
# this is here for now due to the pagechooser having a double-layer dialog on a booru choice, which messes up some focus inheritance
|
|
|
|
self._controller.CallLaterQtSafe( self, 0.5, 'set page focus', page.SetSearchFocus )
|
|
|
|
|
|
return page
|
|
|
|
|
|
def NewPageDuplicateFilter( self, on_deepest_notebook = False ):
|
|
|
|
management_controller = ClientGUIManagement.CreateManagementControllerDuplicateFilter()
|
|
|
|
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )
|
|
|
|
|
|
def NewPageImportGallery( self, page_name = None, on_deepest_notebook = False, select_page = True ):
|
|
|
|
management_controller = ClientGUIManagement.CreateManagementControllerImportGallery( page_name = page_name )
|
|
|
|
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook, select_page = select_page )
|
|
|
|
|
|
def NewPageImportSimpleDownloader( self, on_deepest_notebook = False ):
|
|
|
|
management_controller = ClientGUIManagement.CreateManagementControllerImportSimpleDownloader()
|
|
|
|
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )
|
|
|
|
|
|
def NewPageImportMultipleWatcher( self, page_name = None, url = None, on_deepest_notebook = False, select_page = True ):
|
|
|
|
management_controller = ClientGUIManagement.CreateManagementControllerImportMultipleWatcher( page_name = page_name, url = url )
|
|
|
|
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook, select_page = select_page )
|
|
|
|
|
|
def NewPageImportURLs( self, page_name = None, on_deepest_notebook = False, select_page = True ):
|
|
|
|
management_controller = ClientGUIManagement.CreateManagementControllerImportURLs( page_name = page_name )
|
|
|
|
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook, select_page = select_page )
|
|
|
|
|
|
def NewPagePetitions( self, service_key, on_deepest_notebook = False ):
|
|
|
|
management_controller = ClientGUIManagement.CreateManagementControllerPetitions( service_key )
|
|
|
|
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )
|
|
|
|
|
|
def NewPageQuery( self, location_context: ClientLocation.LocationContext, initial_hashes = None, initial_predicates = None, page_name = None, on_deepest_notebook = False, do_sort = False, select_page = True ):
|
|
|
|
if initial_hashes is None:
|
|
|
|
initial_hashes = []
|
|
|
|
|
|
if initial_predicates is None:
|
|
|
|
initial_predicates = []
|
|
|
|
|
|
if page_name is None:
|
|
|
|
page_name = 'files'
|
|
|
|
|
|
search_enabled = len( initial_hashes ) == 0
|
|
|
|
new_options = self._controller.new_options
|
|
|
|
tag_service_key = new_options.GetKey( 'default_tag_service_search_page' )
|
|
|
|
if not self._controller.services_manager.ServiceExists( tag_service_key ):
|
|
|
|
tag_service_key = CC.COMBINED_TAG_SERVICE_KEY
|
|
|
|
|
|
if location_context.IsAllKnownFiles() and tag_service_key == CC.COMBINED_TAG_SERVICE_KEY:
|
|
|
|
location_context = location_context = ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
|
|
|
|
|
|
tag_context = ClientSearch.TagContext( service_key = tag_service_key )
|
|
|
|
file_search_context = ClientSearch.FileSearchContext( location_context = location_context, tag_context = tag_context, predicates = initial_predicates )
|
|
|
|
management_controller = ClientGUIManagement.CreateManagementControllerQuery( page_name, file_search_context, search_enabled )
|
|
|
|
page = self.NewPage( management_controller, initial_hashes = initial_hashes, on_deepest_notebook = on_deepest_notebook, select_page = select_page )
|
|
|
|
if do_sort:
|
|
|
|
HG.client_controller.pub( 'do_page_sort', page.GetPageKey() )
|
|
|
|
|
|
return page
|
|
|
|
|
|
def NewPagesNotebook( self, name = 'pages', forced_insertion_index = None, on_deepest_notebook = False, give_it_a_blank_page = True, select_page = True ):
|
|
|
|
current_page = self.currentWidget()
|
|
|
|
if on_deepest_notebook and isinstance( current_page, PagesNotebook ):
|
|
|
|
return current_page.NewPagesNotebook( name = name, forced_insertion_index = forced_insertion_index, on_deepest_notebook = on_deepest_notebook, give_it_a_blank_page = give_it_a_blank_page )
|
|
|
|
|
|
self._controller.ResetIdleTimer()
|
|
self._controller.ResetPageChangeTimer()
|
|
|
|
page = PagesNotebook( self, self._controller, name )
|
|
|
|
if forced_insertion_index is None:
|
|
|
|
if self._next_new_page_index is None:
|
|
|
|
insertion_index = self._GetDefaultPageInsertionIndex()
|
|
|
|
else:
|
|
|
|
insertion_index = self._next_new_page_index
|
|
|
|
self._next_new_page_index = None
|
|
|
|
|
|
else:
|
|
|
|
insertion_index = forced_insertion_index
|
|
|
|
|
|
page_name = page.GetName()
|
|
|
|
self.insertTab( insertion_index, page, page_name )
|
|
|
|
if select_page:
|
|
|
|
self.setCurrentIndex( insertion_index )
|
|
|
|
|
|
self._controller.pub( 'refresh_page_name', page.GetPageKey() )
|
|
|
|
if give_it_a_blank_page:
|
|
|
|
default_location_context = HG.client_controller.new_options.GetDefaultLocalLocationContext()
|
|
|
|
page.NewPageQuery( default_location_context )
|
|
|
|
|
|
return page
|
|
|
|
|
|
def NotifyPageUnclosed( self, page ):
|
|
|
|
page_key = page.GetPageKey()
|
|
|
|
for ( index, closed_page_key ) in self._closed_pages:
|
|
|
|
if page_key == closed_page_key:
|
|
|
|
page.show()
|
|
|
|
insert_index = min( index, self.count() )
|
|
|
|
name = page.GetName()
|
|
|
|
self.insertTab( insert_index, page, name )
|
|
self.setCurrentIndex( insert_index )
|
|
|
|
self._controller.pub( 'refresh_page_name', page.GetPageKey() )
|
|
|
|
self._closed_pages.remove( ( index, closed_page_key ) )
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
def PageHidden( self ):
|
|
|
|
result = self.currentWidget()
|
|
|
|
if result is not None:
|
|
|
|
result.PageHidden()
|
|
|
|
|
|
|
|
def pageJustChanged( self, index ):
|
|
|
|
old_selection = self._previous_page_index
|
|
selection = index
|
|
|
|
if old_selection != -1 and old_selection < self.count():
|
|
|
|
self.widget( old_selection ).PageHidden()
|
|
|
|
|
|
if selection != -1:
|
|
|
|
new_page = self.widget( selection )
|
|
|
|
new_page.PageShown()
|
|
|
|
|
|
self._controller.gui.RefreshStatusBar()
|
|
|
|
self._previous_page_index = index
|
|
|
|
self._controller.pub( 'notify_page_change' )
|
|
|
|
|
|
def PageShown( self ):
|
|
|
|
result = self.currentWidget()
|
|
|
|
if result is not None:
|
|
|
|
result.PageShown()
|
|
|
|
|
|
|
|
def PresentImportedFilesToPage( self, hashes, page_name ):
|
|
|
|
hashes = list( hashes )
|
|
|
|
page = self._GetPageFromName( page_name, only_media_pages = True )
|
|
|
|
if page is None:
|
|
|
|
location_context = ClientLocation.LocationContext.STATICCreateSimple( CC.COMBINED_LOCAL_MEDIA_SERVICE_KEY )
|
|
|
|
page = self.NewPageQuery( location_context, initial_hashes = hashes, page_name = page_name, on_deepest_notebook = True, select_page = False )
|
|
|
|
else:
|
|
|
|
def work_callable():
|
|
|
|
media_results = self._controller.Read( 'media_results', hashes, sorted = True )
|
|
|
|
return media_results
|
|
|
|
|
|
def publish_callable( media_results ):
|
|
|
|
page.AddMediaResults( media_results )
|
|
|
|
|
|
job = ClientGUIAsync.AsyncQtJob( page, work_callable, publish_callable )
|
|
|
|
job.start()
|
|
|
|
|
|
return page
|
|
|
|
|
|
def RefreshAllPages( self ):
|
|
|
|
for page in self._GetPages():
|
|
|
|
if isinstance( page, PagesNotebook ):
|
|
|
|
page.RefreshAllPages()
|
|
|
|
else:
|
|
|
|
page.RefreshQuery()
|
|
|
|
|
|
|
|
|
|
def RefreshPageName( self, page_key = None ):
|
|
|
|
if page_key is None:
|
|
|
|
for index in range( self.count() ):
|
|
|
|
self._RefreshPageName( index )
|
|
|
|
|
|
else:
|
|
|
|
for ( index, page ) in enumerate( self._GetPages() ):
|
|
|
|
do_it = False
|
|
|
|
if page.GetPageKey() == page_key:
|
|
|
|
do_it = True
|
|
|
|
elif isinstance( page, PagesNotebook ) and page.HasPageKey( page_key ):
|
|
|
|
do_it = True
|
|
|
|
|
|
if do_it:
|
|
|
|
self._RefreshPageName( index )
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
def SetName( self, name ):
|
|
|
|
self._name = name
|
|
|
|
|
|
def ShowPage( self, showee ):
|
|
|
|
for ( i, page ) in enumerate( self._GetPages() ):
|
|
|
|
if isinstance( page, QW.QTabWidget ) and page.HasPage( showee ):
|
|
|
|
self.setCurrentIndex( i )
|
|
|
|
page.ShowPage( showee )
|
|
|
|
break
|
|
|
|
elif page == showee:
|
|
|
|
self.setCurrentIndex( i )
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
def TestAbleToClose( self ):
|
|
|
|
statement = self.GetTestAbleToCloseStatement()
|
|
|
|
if statement is not None:
|
|
|
|
message = 'Are you sure you want to close this page of pages?'
|
|
message += os.linesep * 2
|
|
message += statement
|
|
|
|
result = ClientGUIDialogsQuick.GetYesNo( self, message )
|
|
|
|
if result == QW.QDialog.Rejected:
|
|
|
|
raise HydrusExceptions.VetoException()
|
|
|
|
|
|
|
|
|
|
def REPEATINGPageUpdate( self ):
|
|
|
|
pass
|
|
|