hydrus/include/ClientGUIPages.py

3182 lines
98 KiB
Python
Executable File

import HydrusConstants as HC
import ClientConstants as CC
import ClientData
import ClientGUICommon
import ClientGUIDialogs
import ClientGUIManagement
import ClientGUIMedia
import ClientGUIMenus
import ClientGUICanvas
import ClientDownloading
import ClientSearch
import ClientGUIShortcuts
import ClientThreading
import collections
import hashlib
import HydrusData
import HydrusExceptions
import HydrusSerialisable
import HydrusThreading
import inspect
import os
import sys
import time
import traceback
import wx
import HydrusGlobals as HG
USER_PAGE_NAME_PREFIX = '[USER]'
class DialogPageChooser( ClientGUIDialogs.Dialog ):
def __init__( self, parent, controller ):
ClientGUIDialogs.Dialog.__init__( self, parent, 'new page', position = 'center' )
self._controller = controller
self._result = None
# spawn in this order, so focus precipitates from the graphical top
self._button_7 = wx.Button( self, label = '', id = 7 )
self._button_8 = wx.Button( self, label = '', id = 8 )
self._button_9 = wx.Button( self, label = '', id = 9 )
self._button_4 = wx.Button( self, label = '', id = 4 )
self._button_5 = wx.Button( self, label = '', id = 5 )
self._button_6 = wx.Button( self, label = '', id = 6 )
self._button_1 = wx.Button( self, label = '', id = 1 )
self._button_2 = wx.Button( self, label = '', id = 2 )
self._button_3 = wx.Button( self, label = '', id = 3 )
gridbox = wx.GridSizer( 3 )
gridbox.Add( self._button_7, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( self._button_8, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( self._button_9, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( self._button_4, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( self._button_5, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( self._button_6, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( self._button_1, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( self._button_2, CC.FLAGS_EXPAND_BOTH_WAYS )
gridbox.Add( self._button_3, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( gridbox )
self.SetInitialSize( ( 420, 210 ) )
self._services = HG.client_controller.services_manager.GetServices()
repository_petition_permissions = [ ( content_type, HC.PERMISSION_ACTION_OVERRULE ) for content_type in HC.REPOSITORY_CONTENT_TYPES ]
self._petition_service_keys = [ service.GetServiceKey() for service in self._services if service.GetServiceType() in HC.REPOSITORIES and True in ( service.HasPermission( content_type, action ) for ( content_type, action ) in repository_petition_permissions ) ]
self._InitButtons( 'home' )
self.Bind( wx.EVT_CHAR_HOOK, self.EventCharHook )
self.Bind( wx.EVT_BUTTON, self.EventButton )
#
self.Show( True )
def _AddEntry( self, button, entry ):
id = button.GetId()
self._command_dict[ id ] = entry
( entry_type, obj ) = entry
if entry_type == 'menu':
button.SetLabelText( obj )
elif entry_type == 'page_duplicate_filter':
button.SetLabelText( 'duplicates processing' )
elif entry_type == 'pages_notebook':
button.SetLabelText( 'page of pages' )
elif entry_type in ( 'page_query', 'page_petitions' ):
name = HG.client_controller.services_manager.GetService( obj ).GetName()
button.SetLabelText( name )
elif entry_type == 'page_import_booru':
button.SetLabelText( 'booru' )
elif entry_type == 'page_import_gallery':
site_type = obj
text = HC.site_type_string_lookup[ site_type ]
button.SetLabelText( text )
elif entry_type == 'page_import_simple_downloader':
button.SetLabelText( 'simple downloader' )
elif entry_type == 'page_import_multiple_watcher':
button.SetLabelText( 'multiple watcher' )
elif entry_type == 'page_import_watcher':
button.SetLabelText( 'watcher' )
elif entry_type == 'page_import_urls':
button.SetLabelText( 'urls' )
button.Show()
def _HitButton( self, id ):
if id in self._command_dict:
( entry_type, obj ) = self._command_dict[ 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
file_search_context = ClientSearch.FileSearchContext( file_service_key = file_service_key, tag_service_key = tag_service_key )
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerQuery( page_name, file_service_key, 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_booru':
with ClientGUIDialogs.DialogSelectBooru( self ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
gallery_identifier = dlg.GetGalleryIdentifier()
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportGallery( gallery_identifier ) )
else:
self.EndModal( wx.ID_CANCEL )
return
elif entry_type == 'page_import_gallery':
site_type = obj
gallery_identifier = ClientDownloading.GalleryIdentifier( site_type )
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportGallery( gallery_identifier ) )
elif entry_type == 'page_import_simple_downloader':
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportSimpleDownloader() )
elif entry_type == 'page_import_multiple_watcher':
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportMultipleWatcher() )
elif entry_type == 'page_import_watcher':
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerImportWatcher() )
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.EndModal( wx.ID_OK )
def _InitButtons( self, menu_keyword ):
self._command_dict = {}
entries = []
if menu_keyword == 'home':
entries.append( ( 'menu', 'files' ) )
entries.append( ( 'menu', 'download' ) )
if len( self._petition_service_keys ) > 0:
entries.append( ( 'menu', 'petitions' ) )
entries.append( ( 'menu', 'special' ) )
elif menu_keyword == 'files':
entries.append( ( 'page_query', CC.LOCAL_FILE_SERVICE_KEY ) )
entries.append( ( 'page_query', CC.TRASH_SERVICE_KEY ) )
entries.append( ( 'page_query', CC.COMBINED_LOCAL_FILE_SERVICE_KEY ) )
for service in self._services:
if service.GetServiceType() == HC.FILE_REPOSITORY:
entries.append( ( 'page_query', service.GetServiceKey() ) )
elif menu_keyword == 'download':
entries.append( ( 'page_import_urls', None ) )
entries.append( ( 'page_import_watcher', None ) )
entries.append( ( 'menu', 'gallery' ) )
entries.append( ( 'page_import_simple_downloader', None ) )
elif menu_keyword == 'gallery':
entries.append( ( 'page_import_booru', None ) )
entries.append( ( 'page_import_gallery', HC.SITE_TYPE_DEVIANT_ART ) )
entries.append( ( 'menu', 'hentai foundry' ) )
#entries.append( ( 'page_import_gallery', HC.SITE_TYPE_NEWGROUNDS ) )
result = HG.client_controller.Read( 'serialisable_simple', 'pixiv_account' )
if result is not None:
entries.append( ( 'menu', 'pixiv' ) )
entries.append( ( 'page_import_gallery', HC.SITE_TYPE_TUMBLR ) )
elif menu_keyword == 'hentai foundry':
entries.append( ( 'page_import_gallery', HC.SITE_TYPE_HENTAI_FOUNDRY_ARTIST ) )
entries.append( ( 'page_import_gallery', HC.SITE_TYPE_HENTAI_FOUNDRY_TAGS ) )
elif menu_keyword == 'pixiv':
entries.append( ( 'page_import_gallery', HC.SITE_TYPE_PIXIV_ARTIST_ID ) )
#entries.append( ( 'page_import_gallery', HC.SITE_TYPE_PIXIV_TAG ) )
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_import_multiple_watcher', None ) )
entries.append( ( 'page_duplicate_filter', None ) )
if len( entries ) <= 4:
self._button_1.Hide()
self._button_3.Hide()
self._button_5.Hide()
self._button_7.Hide()
self._button_9.Hide()
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.Hide()
def EventButton( self, event ):
id = event.GetId()
if id == wx.ID_CANCEL:
self.EndModal( wx.ID_CANCEL )
else:
self._HitButton( id )
def EventCharHook( self, event ):
id = None
( modifier, key ) = ClientGUIShortcuts.ConvertKeyEventToSimpleTuple( event )
if key == wx.WXK_UP: id = 8
elif key == wx.WXK_LEFT: id = 4
elif key == wx.WXK_RIGHT: id = 6
elif key == wx.WXK_DOWN: id = 2
elif key == wx.WXK_NUMPAD1: id = 1
elif key == wx.WXK_NUMPAD2: id = 2
elif key == wx.WXK_NUMPAD3: id = 3
elif key == wx.WXK_NUMPAD4: id = 4
elif key == wx.WXK_NUMPAD5: id = 5
elif key == wx.WXK_NUMPAD6: id = 6
elif key == wx.WXK_NUMPAD7: id = 7
elif key == wx.WXK_NUMPAD8: id = 8
elif key == wx.WXK_NUMPAD9: id = 9
elif key == wx.WXK_ESCAPE:
self.EndModal( wx.ID_CANCEL )
return
else:
event.Skip()
if id is not None:
self._HitButton( id )
def GetValue( self ):
return self._result
class Page( wx.SplitterWindow ):
def __init__( self, parent, controller, management_controller, initial_hashes ):
wx.SplitterWindow.__init__( self, parent )
self._controller = controller
self._page_key = self._controller.AcquirePageKey()
self._management_controller = management_controller
self._initial_hashes = initial_hashes
self._management_controller.SetKey( 'page', self._page_key )
self._initialised = False
self._pretty_status = ''
self.SetMinimumPaneSize( 120 )
self.SetSashGravity( 0.0 )
self.Bind( wx.EVT_SPLITTER_DCLICK, self.EventUnsplit )
self._search_preview_split = wx.SplitterWindow( self, style = wx.SP_NOBORDER )
self._search_preview_split.SetMinimumPaneSize( 180 )
self._search_preview_split.SetSashGravity( 1.0 )
self._search_preview_split.Bind( wx.EVT_SPLITTER_DCLICK, self.EventPreviewUnsplit )
self._management_panel = ClientGUIManagement.CreateManagementPanel( self._search_preview_split, self, self._controller, self._management_controller )
file_service_key = self._management_controller.GetKey( 'file_service' )
self._preview_panel = ClientGUICanvas.CanvasPanel( self._search_preview_split, self._page_key )
self._media_panel = ClientGUIMedia.MediaPanelThumbnails( self, self._page_key, file_service_key, [] )
self._search_preview_split.SplitHorizontally( self._management_panel, self._preview_panel, HC.options[ 'vpos' ] )
self.SplitVertically( self._search_preview_split, self._media_panel, HC.options[ 'hpos' ] )
if HC.options[ 'hide_preview' ]:
wx.CallAfter( self._search_preview_split.Unsplit, self._preview_panel )
self._controller.sub( self, 'SetPrettyStatus', 'new_page_status' )
self._controller.sub( self, 'SetSplitterPositions', 'set_splitter_positions' )
def _SetPrettyStatus( self, status ):
self._pretty_status = status
self._controller.gui.SetStatusBarDirty()
def _SwapMediaPanel( self, new_panel ):
# if a new media page comes in while its menu is open, we can enter program instability.
# so let's just put it off.
if self._controller.MenuIsOpen():
self._controller.CallLaterWXSafe( self, 0.5, self._SwapMediaPanel, new_panel )
return
self._preview_panel.SetMedia( None )
self._media_panel.ClearPageKey()
self.ReplaceWindow( self._media_panel, new_panel )
self._media_panel.DestroyLater()
# old messed-up way of doing it
'''
self._media_panel.Hide()
# If this is a CallAfter, OS X segfaults on refresh jej
self._controller.CallLaterWXSafe( self._media_panel, 0.5, self._media_panel.Destroy )
'''
self._media_panel = new_panel
self._controller.pub( 'refresh_page_name', self._page_key )
def CheckAbleToClose( self ):
self._management_panel.CheckAbleToClose()
def CleanBeforeDestroy( self ):
self._management_panel.CleanBeforeDestroy()
self._controller.ReleasePageKey( self._page_key )
def EventPreviewUnsplit( self, event ):
self._search_preview_split.Unsplit( self._preview_panel )
self._controller.pub( 'set_focus', self._page_key, None )
def EventUnsplit( self, event ):
self.Unsplit( self._search_preview_split )
self._controller.pub( 'set_focus', self._page_key, None )
def GetDisplayName( self ):
name = self._management_controller.GetPageName()
if name.startswith( USER_PAGE_NAME_PREFIX ):
return name.replace( USER_PAGE_NAME_PREFIX, '', 1 )
else:
return name
def GetHashes( self ):
if self._initialised:
return self._media_panel.GetHashes( ordered = True )
else:
return self._initial_hashes
def GetManagementController( self ):
return self._management_controller
# 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 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 GetPrettyStatus( self ):
return self._pretty_status
def GetSashPositions( self ):
if self.IsSplit():
x = self.GetSashPosition()
else:
x = HC.options[ 'hpos' ]
if self._search_preview_split.IsSplit():
# I used to do:
# y = -1 * self._preview_panel.GetSize()[1]
# but that crept 4 pixels smaller every time, I assume due to sash caret height
( sps_x, sps_y ) = self._search_preview_split.GetClientSize()
sash_y = self._search_preview_split.GetSashPosition()
y = -1 * ( sps_y - sash_y )
else:
y = HC.options[ 'vpos' ]
return ( x, y )
def IsMultipleWatcherPage( self ):
return self._management_controller.GetType() == ClientGUIManagement.MANAGEMENT_TYPE_IMPORT_MULTIPLE_WATCHER
def IsImporter( self ):
return self._management_controller.IsImporter()
def IsURLImportPage( self ):
return self._management_controller.GetType() == ClientGUIManagement.MANAGEMENT_TYPE_IMPORT_URLS
def PageHidden( self ):
self._management_panel.PageHidden()
self._media_panel.PageHidden()
def PageShown( self ):
self._management_panel.PageShown()
self._media_panel.PageShown()
def PrepareToHide( self ):
self._controller.pub( 'set_focus', self._page_key, None )
def RefreshQuery( self ):
if self._initialised:
self._controller.pub( 'refresh_query', self._page_key )
def ShowHideSplit( self ):
if self.IsSplit():
self.Unsplit( self._search_preview_split )
self._controller.pub( 'set_focus', self._page_key, None )
else:
self.SplitVertically( self._search_preview_split, self._media_panel, HC.options[ 'hpos' ] )
self._search_preview_split.SplitHorizontally( self._management_panel, self._preview_panel, HC.options[ 'vpos' ] )
def SetMediaFocus( self ):
self._media_panel.SetFocus()
def SetMediaResults( self, media_results ):
file_service_key = self._management_controller.GetKey( 'file_service' )
media_panel = ClientGUIMedia.MediaPanelThumbnails( self, self._page_key, file_service_key, media_results )
self._SwapMediaPanel( media_panel )
self._initialised = True
self._initial_hashes = []
wx.CallAfter( self._management_panel.Start ) # importand this is callafter, so it happens after a heavy session load is done
def SetName( self, name, from_user = False ):
if from_user:
name = USER_PAGE_NAME_PREFIX + name
return self._management_controller.SetPageName( name )
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, vpos ):
if self._search_preview_split.IsSplit():
self._search_preview_split.SetSashPosition( vpos )
else:
self._search_preview_split.SplitHorizontally( self._management_panel, self._preview_panel, vpos )
if self.IsSplit():
self.SetSashPosition( hpos )
else:
self.SplitVertically( self._search_preview_split, self._media_panel, hpos )
if HC.options[ 'hide_preview' ]:
wx.CallAfter( self._search_preview_split.Unsplit, self._preview_panel )
def SetSynchronisedWait( self ):
self._controller.pub( 'synchronised_wait_switch', self._page_key )
def Start( self ):
if self._initial_hashes is not None and len( self._initial_hashes ) > 0:
self._controller.CallToThread( self.THREADLoadInitialMediaResults, self._controller, self._initial_hashes )
else:
self._initialised = True
wx.CallAfter( self._management_panel.Start ) # importand this is callafter, so it happens after a heavy session load is done
def SwapMediaPanel( self, new_panel ):
self._SwapMediaPanel( new_panel )
def TestAbleToClose( self ):
try:
self._management_panel.CheckAbleToClose()
except HydrusExceptions.VetoException as e:
reason = HydrusData.ToUnicode( e )
with ClientGUIDialogs.DialogYesNo( self, reason + ' Are you sure you want to close it?' ) as dlg:
if dlg.ShowModal() == wx.ID_NO:
raise HydrusExceptions.VetoException()
def THREADLoadInitialMediaResults( self, controller, initial_hashes ):
def wx_code_status( status ):
if not self:
return
self._SetPrettyStatus( status )
def wx_code_publish( media_results ):
if not self:
return
self.SetMediaResults( media_results )
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 = u'Loading initial files\u2026 ' + HydrusData.ConvertValueRangeToPrettyString( len( initial_media_results ), len( initial_hashes ) )
wx.CallAfter( wx_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 ]
wx.CallAfter( wx_code_publish, sorted_initial_media_results )
def REPEATINGPageUpdate( self ):
self._management_panel.REPEATINGPageUpdate()
class PagesNotebook( wx.Notebook ):
def __init__( self, parent, controller, name ):
if controller.new_options.GetBoolean( 'notebook_tabs_on_left' ):
style = wx.NB_LEFT
else:
style = wx.NB_TOP
wx.Notebook.__init__( self, parent, style = style )
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._last_last_session_hash = None
self._controller.sub( self, 'RefreshPageName', 'refresh_page_name' )
self._controller.sub( self, 'NotifyPageUnclosed', 'notify_page_unclosed' )
self.Bind( wx.EVT_MOTION, self.EventDrag )
self.Bind( wx.EVT_LEFT_DOWN, self.EventLeftDown )
self.Bind( wx.EVT_LEFT_UP, self.EventLeftUp )
self.Bind( wx.EVT_LEFT_DCLICK, self.EventLeftDoubleClick )
self.Bind( wx.EVT_MIDDLE_DOWN, self.EventMiddleClick )
self.Bind( wx.EVT_RIGHT_DOWN, self.EventMenu )
self.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventPageChanged )
def _ChooseNewPage( self, insertion_index = None ):
self._next_new_page_index = insertion_index
with DialogPageChooser( self, self._controller ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
( 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.GetPageCount() ) ]
self._ClosePages( closees, polite, delete_pages = delete_pages )
def _CloseLeftPages( self, from_index ):
message = 'Close all pages to the left?'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
closees = [ index for index in range( self.GetPageCount() ) if index < from_index ]
self._ClosePages( closees )
def _CloseOtherPages( self, except_index ):
message = 'Close all other pages?'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
closees = [ index for index in range( self.GetPageCount() ) 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.GetPageCount() - 1:
return False
page = self.GetPage( index )
if polite:
try:
page.TestAbleToClose()
except HydrusExceptions.VetoException:
return False
page.PrepareToHide()
page_key = page.GetPageKey()
self._closed_pages.append( ( index, page_key ) )
self.RemovePage( index )
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 )
self._controller.pub( 'notify_new_undo' )
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?'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
closees = [ index for index in range( self.GetPageCount() ) if index > from_index ]
self._ClosePages( closees )
def _GatherDeadWatchers( self, insertion_page ):
top_notebook = self._GetTopNotebook()
gathered_pages = top_notebook.GetGatherPages( 'dead_watchers' )
self._MovePages( gathered_pages, insertion_page )
def _GetDefaultPageInsertionIndex( self ):
new_options = self._controller.new_options
new_page_goes = new_options.GetInteger( 'default_new_page_goes' )
current_index = self.GetSelection()
if current_index == wx.NOT_FOUND:
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.GetPageCount()
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.GetPage( index ), index ) for index in range( self.GetPageCount() ) ):
if page.GetPageKey() == page_key:
return index
raise HydrusExceptions.DataMissing()
def _GetNotebookFromScreenPosition( self, screen_position ):
current_page = self.GetCurrentPage()
if current_page is None or not isinstance( current_page, PagesNotebook ):
return self
else:
( tab_index, flags ) = ClientGUICommon.NotebookScreenToHitTest( self, screen_position )
if tab_index != wx.NOT_FOUND:
return self
if HC.PLATFORM_OSX:
( x, y ) = screen_position
( child_x, child_y ) = current_page.GetPosition()
on_child_notebook_somewhere = y > child_y # wew lad, OSX not delivering onpage maybe?
else:
on_child_notebook_somewhere = flags & wx.NB_HITTEST_ONPAGE
if on_child_notebook_somewhere:
return current_page._GetNotebookFromScreenPosition( screen_position )
return self
def _GetPages( self ):
return [ self.GetPage( i ) for i in range( self.GetPageCount() ) ]
def _GetPageFromPageKey( self, page_key ):
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 _GetPageFromName( self, page_name ):
for page in self._GetPages():
if page.GetDisplayName() == page_name:
return page
if isinstance( page, PagesNotebook ):
result = page._GetPageFromName( page_name )
if result is not None:
return result
return None
def _GetTopNotebook( self ):
top_notebook = self
parent = top_notebook.GetParent()
while isinstance( parent, PagesNotebook ):
top_notebook = parent
parent = top_notebook.GetParent()
return top_notebook
def _MovePage( self, page, dest_notebook, insertion_tab_index, follow_dropped_page = False ):
source_notebook = page.GetParent()
for ( index, p ) in enumerate( source_notebook._GetPages() ):
if p == page:
source_notebook.RemovePage( index )
break
if source_notebook != dest_notebook:
page.Reparent( dest_notebook )
self._controller.pub( 'refresh_page_name', source_notebook.GetPageKey() )
insertion_tab_index = min( insertion_tab_index, dest_notebook.GetPageCount() )
dest_notebook.InsertPage( insertion_tab_index, page, page.GetDisplayName(), select = follow_dropped_page )
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.GetParent() != dest_notebook:
self._MovePage( page, dest_notebook, insertion_tab_index )
insertion_tab_index += 1
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.GetPageCount() - 1:
page_is_selected = self.GetSelection() == page_index
page = self.GetPage( page_index )
name = self.GetPageText( page_index )
self.RemovePage( page_index )
self.InsertPage( new_page_index, page, name, page_is_selected )
def _RefreshPageName( self, index ):
if index == -1 or index > self.GetPageCount() - 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.GetPage( index )
page_name = page.GetDisplayName()
page_name = page_name.replace( os.linesep, '' )
if len( page_name ) > max_page_name_chars:
page_name = page_name[ : max_page_name_chars ] + u'\u2026'
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.ConvertIntToPrettyString( 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 = self.EscapeMnemonics( page_name )
existing_page_name = self.GetPageText( index )
if existing_page_name not in ( safe_page_name, page_name ):
self.SetPageText( index, safe_page_name )
def _RenamePage( self, index ):
if index == -1 or index > self.GetPageCount() - 1:
return
page = self.GetPage( index )
current_name = page.GetDisplayName()
with ClientGUIDialogs.DialogTextEntry( self, 'Enter the new name.', default = current_name, allow_blank = False ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
new_name = dlg.GetValue()
page.SetName( new_name, from_user = True )
self._controller.pub( 'refresh_page_name', page.GetPageKey() )
def _SendPageToNewNotebook( self, index ):
if 0 <= index and index <= self.GetPageCount() - 1:
page = self.GetPage( 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?'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
pages_index = self.GetPageCount()
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.GetPage( index )
self._MovePage( page, dest_notebook, 0 )
def _ShowMenu( self, screen_position ):
( tab_index, flags ) = ClientGUICommon.NotebookScreenToHitTest( self, screen_position )
num_pages = self.GetPageCount()
end_index = num_pages - 1
more_than_one_tab = num_pages > 1
click_over_tab = tab_index != -1
can_go_left = tab_index > 0
can_go_right = tab_index < end_index
click_over_page_of_pages = False
existing_session_names = self._controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION )
menu = wx.Menu()
if click_over_tab:
page = self.GetPage( tab_index )
click_over_page_of_pages = isinstance( page, PagesNotebook )
ClientGUIMenus.AppendMenuItem( self, menu, 'close page', 'Close this page.', self._ClosePage, tab_index )
if num_pages > 1:
ClientGUIMenus.AppendMenuItem( self, menu, 'close other pages', 'Close all pages but this one.', self._CloseOtherPages, tab_index )
if can_go_left:
ClientGUIMenus.AppendMenuItem( self, menu, 'close pages to the left', 'Close all pages to the left of this one.', self._CloseLeftPages, tab_index )
if can_go_right:
ClientGUIMenus.AppendMenuItem( self, menu, 'close pages to the right', 'Close all pages to the right of this one.', self._CloseRightPages, tab_index )
ClientGUIMenus.AppendSeparator( menu )
ClientGUIMenus.AppendMenuItem( self, menu, 'rename page', 'Rename this page.', self._RenamePage, tab_index )
ClientGUIMenus.AppendMenuItem( self, menu, 'new page', 'Choose a new page.', self._ChooseNewPage )
if click_over_tab:
ClientGUIMenus.AppendMenuItem( self, menu, 'new page here', 'Choose a new page.', self._ChooseNewPage, tab_index )
if more_than_one_tab:
ClientGUIMenus.AppendSeparator( menu )
can_home = tab_index > 1
can_move_left = tab_index > 0
can_move_right = tab_index < end_index
can_end = tab_index < end_index - 1
if can_home:
ClientGUIMenus.AppendMenuItem( self, menu, 'move to left end', 'Move this page all the way to the left.', self._ShiftPage, tab_index, new_index = 0 )
if can_move_left:
ClientGUIMenus.AppendMenuItem( self, menu, 'move left', 'Move this page one to the left.', self._ShiftPage, tab_index, delta = -1 )
if can_move_right:
ClientGUIMenus.AppendMenuItem( self, menu, 'move right', 'Move this page one to the right.', self._ShiftPage, tab_index, 1 )
if can_end:
ClientGUIMenus.AppendMenuItem( self, menu, 'move to right end', 'Move this page all the way to the right.', self._ShiftPage, tab_index, new_index = end_index )
ClientGUIMenus.AppendSeparator( menu )
ClientGUIMenus.AppendMenuItem( self, 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( self, 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.GetPageCount() > 0:
ClientGUIMenus.AppendSeparator( menu )
ClientGUIMenus.AppendMenuItem( self, menu, 'refresh all this page\'s pages', 'Command every page below this one to refresh.', page.RefreshAllPages )
if click_over_page_of_pages:
ClientGUIMenus.AppendSeparator( menu )
submenu = wx.Menu()
ClientGUIMenus.AppendMenuItem( self, submenu, 'dead watchers', 'Find all currently open dead watchers and move them to this page of pages.', self._GatherDeadWatchers, page )
ClientGUIMenus.AppendMenu( menu, submenu, 'gather on this page of pages' )
if len( existing_session_names ) > 0 or click_over_page_of_pages:
ClientGUIMenus.AppendSeparator( menu )
if len( existing_session_names ) > 0:
submenu = wx.Menu()
for name in existing_session_names:
ClientGUIMenus.AppendMenuItem( self, submenu, name, 'Load this session here.', self.AppendGUISession, name )
ClientGUIMenus.AppendMenu( menu, submenu, 'append session' )
if click_over_page_of_pages:
submenu = wx.Menu()
for name in existing_session_names:
if name == 'last session':
continue
ClientGUIMenus.AppendMenuItem( self, submenu, name, 'Save this page of pages to the session.', page.SaveGUISession, name )
ClientGUIMenus.AppendMenuItem( self, submenu, 'create a new session', 'Save this page of pages to the session.', page.SaveGUISession, suggested_name = page.GetDisplayName() )
ClientGUIMenus.AppendMenu( menu, submenu, 'save this page of pages to a session' )
self._controller.PopupMenu( self, menu )
def AppendGUISession( self, name, load_in_a_page_of_pages = True ):
try:
session = self._controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION, name )
except Exception as e:
HydrusData.ShowText( 'While trying to load session ' + name + ', this error happened:' )
HydrusData.ShowException( e )
self.NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY )
return
if load_in_a_page_of_pages:
destination = self.NewPagesNotebook( name = name, give_it_a_blank_page = False)
else:
destination = self
page_tuples = session.GetPages()
destination.AppendSessionPageTuples( page_tuples )
def AppendSessionPageTuples( self, page_tuples ):
starting_index = self._GetDefaultPageInsertionIndex()
forced_insertion_index = starting_index
done_first_page = False
for page_tuple in page_tuples:
( page_type, page_data ) = page_tuple
if page_type == 'pages':
( name, subpage_tuples ) = page_data
try:
page = self.NewPagesNotebook( name, forced_insertion_index = forced_insertion_index, give_it_a_blank_page = False, select_page = False )
page.AppendSessionPageTuples( subpage_tuples )
except Exception as e:
HydrusData.ShowException( e )
elif page_type == 'page':
( management_controller, initial_hashes ) = page_data
try:
select_page = not done_first_page
self.NewPage( management_controller, initial_hashes = initial_hashes, forced_insertion_index = forced_insertion_index, select_page = select_page )
done_first_page = True
except Exception as e:
HydrusData.ShowException( e )
forced_insertion_index += 1
def ChooseNewPage( self ):
self._ChooseNewPage()
def ChooseNewPageForDeepestNotebook( self ):
current_page = self.GetCurrentPage()
if isinstance( current_page, PagesNotebook ):
current_page.ChooseNewPageForDeepestNotebook()
else:
self._ChooseNewPage()
def CleanBeforeDestroy( self ):
for page in self._GetPages():
page.CleanBeforeDestroy()
self._controller.ReleasePageKey( self._page_key )
def CloseCurrentPage( self, polite = True ):
selection = self.GetSelection()
if selection != wx.NOT_FOUND:
page = self.GetPage( 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 EventDrag( self, event ):
if event.Dragging() and self._potential_drag_page is not None:
drop_source = wx.DropSource( self._controller.gui )
#
hydrus_page_tab_data_object = wx.CustomDataObject( 'application/hydrus-page-tab' )
data = self._potential_drag_page.GetPageKey()
hydrus_page_tab_data_object.SetData( data )
#
drop_source.SetData( hydrus_page_tab_data_object )
drop_source.DoDragDrop( wx.Drag_DefaultMove )
self._potential_drag_page = None
event.Skip()
def EventLeftDown( self, event ):
event_skip_ok = True
position = event.GetPosition()
( tab_index, flags ) = self.HitTest( position )
if tab_index != -1:
page = self.GetPage( tab_index )
if HC.PLATFORM_OSX and page == self.GetCurrentPage():
# drag doesn't work if we allow the event to go ahead
# but we do want the event to go ahead if it is a 'select different page' event
event_skip_ok = False
self._potential_drag_page = page
if event_skip_ok:
event.Skip()
def EventLeftDoubleClick( self, event ):
position = event.GetPosition()
( tab_index, flags ) = self.HitTest( position )
if tab_index == wx.NOT_FOUND:
if flags & wx.NB_HITTEST_NOWHERE and flags & wx.NB_HITTEST_ONPAGE:
screen_position = ClientGUICommon.ClientToScreen( self, position )
notebook = self._GetNotebookFromScreenPosition( screen_position )
notebook.EventNewPageFromScreenPosition( screen_position )
else:
self.ChooseNewPage()
else:
event.Skip()
def EventLeftUp( self, event ):
self._potential_drag_page = None
event.Skip()
def EventMenu( self, event ):
screen_position = ClientGUICommon.ClientToScreen( self, event.GetPosition() )
self._ShowMenu( screen_position )
def EventMenuFromScreenPosition( self, position ):
notebook = self._GetNotebookFromScreenPosition( position )
notebook._ShowMenu( position )
def EventMiddleClick( self, event ):
if self._controller.MenuIsOpen():
return
position = event.GetPosition()
( tab_index, flags ) = self.HitTest( position )
if tab_index == wx.NOT_FOUND:
if flags & wx.NB_HITTEST_NOWHERE and flags & wx.NB_HITTEST_ONPAGE:
screen_position = ClientGUICommon.ClientToScreen( self, position )
notebook = self._GetNotebookFromScreenPosition( screen_position )
notebook.EventNewPageFromScreenPosition( screen_position )
else:
self.ChooseNewPage()
else:
self._ClosePage( tab_index )
def EventNewPageFromScreenPosition( self, position ):
notebook = self._GetNotebookFromScreenPosition( position )
notebook._ChooseNewPage()
def EventPageChanged( self, event ):
if event.EventObject == self: # because OS X wants to bump this up to parent notebooks
old_selection = event.GetOldSelection()
selection = event.GetSelection()
if old_selection != wx.NOT_FOUND:
self.GetPage( old_selection ).PageHidden()
if selection != wx.NOT_FOUND:
self.GetPage( selection ).PageShown()
self._controller.gui.RefreshStatusBar()
if HC.PLATFORM_OSX:
event.Skip() # need this or OS X spergs out and never .Show()s new page, wew
def GetCurrentMediaPage( self ):
page = self.GetCurrentPage()
if isinstance( page, PagesNotebook ):
return page.GetCurrentMediaPage()
else:
return page # this can be None
def GetDisplayName( self ):
if self._name.startswith( USER_PAGE_NAME_PREFIX ):
return self._name.replace( USER_PAGE_NAME_PREFIX, '', 1 )
else:
return self._name
def GetGatherPages( self, gather_type ):
if gather_type == 'dead_watchers':
def test( page ):
management_controller = page.GetManagementController()
return management_controller.IsDeadWatcher()
else:
raise NotImplementedError()
gathered_pages = []
for page in self.GetMediaPages():
if test( page ):
gathered_pages.append( page )
return gathered_pages
def GetMediaPages( self, only_my_level = False ):
return self._GetMediaPages( only_my_level )
def GetName( self ):
return self._name
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.GetPageCount()
else:
total = 0
for page in self._GetPages():
if isinstance( page, PagesNotebook ):
total += page.GetNumPages( False )
else:
total += 1
return total
def GetOrMakeMultipleWatcherPage( self ):
current_page = self.GetCurrentPage()
if current_page is not None and isinstance( current_page, Page ) and current_page.IsMultipleWatcherPage():
return current_page
for page in self._GetPages():
if isinstance( page, PagesNotebook ):
if page.HasMultipleWatcherPage():
return page.GetOrMakeMultipleWatcherPage()
elif page.IsMultipleWatcherPage():
return page
# import page does not exist
return self.NewPageImportMultipleWatcher( on_deepest_notebook = True )
def GetOrMakeURLImportPage( self ):
for page in self._GetPages():
if isinstance( page, PagesNotebook ):
if page.HasURLImportPage():
return page.GetOrMakeURLImportPage()
elif page.IsURLImportPage():
return page
# import page does not exist
return self.NewPageImportURLs( on_deepest_notebook = True )
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 GetPages( self ):
return self._GetPages()
def GetPrettyStatus( self ):
( num_files, ( num_value, num_range ) ) = self.GetNumFileSummary()
num_string = HydrusData.ConvertIntToPrettyString( num_files )
if num_range > 0 and num_value != num_range:
num_string += ', ' + HydrusData.ConvertValueRangeToPrettyString( num_value, num_range )
return HydrusData.ConvertIntToPrettyString( self.GetPageCount() ) + ' pages, ' + num_string + ' files'
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 LoadGUISession( self, name ):
if self.GetPageCount() > 0:
message = 'Close the current pages and load session "' + name + '"?'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Clear and load session?' ) as yn_dlg:
if yn_dlg.ShowModal() != wx.ID_YES:
return
try:
self.TestAbleToClose()
except HydrusExceptions.VetoException:
return
self._CloseAllPages( polite = False, delete_pages = True )
self._controller.CallLaterWXSafe( self, 1.0, self.AppendGUISession, name, load_in_a_page_of_pages = False )
else:
self.AppendGUISession( 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
screen_position = wx.GetMousePosition()
dest_notebook = self._GetNotebookFromScreenPosition( screen_position )
( x, y ) = screen_position
( tab_index, flags ) = ClientGUICommon.NotebookScreenToHitTest( dest_notebook, ( x, y ) )
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
if flags & wx.NB_HITTEST_ONPAGE:
dest_page = dest_notebook.GetCurrentPage()
elif tab_index == wx.NOT_FOUND:
dest_page = dest_notebook.NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY, initial_hashes = hashes )
do_add = False
else:
dest_page = dest_notebook.GetPage( tab_index )
if isinstance( dest_page, PagesNotebook ):
result = dest_page.GetCurrentMediaPage()
if result is None:
dest_page = dest_page.NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY, 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 dest_page.GetPageKey() == source_page_key:
return # we dropped onto the same page we picked up on
if do_add:
unsorted_media_results = self._controller.Read( 'media_results', hashes )
hashes_to_media_results = { media_result.GetHash() : media_result for media_result in unsorted_media_results }
sorted_media_results = [ hashes_to_media_results[ hash ] for hash in hashes ]
dest_page.GetMediaPanel().AddMediaResults( dest_page.GetPageKey(), sorted_media_results )
else:
self.ShowPage( source_page )
ctrl_down = wx.GetKeyState( wx.WXK_COMMAND ) or wx.GetKeyState( wx.WXK_CONTROL )
if not ctrl_down:
source_page.GetMediaPanel().RemoveMedia( source_page.GetPageKey(), hashes )
def NewPage( self, management_controller, initial_hashes = None, forced_insertion_index = None, on_deepest_notebook = False, select_page = True ):
current_page = self.GetCurrentPage()
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 = 200
( total_active_page_count, total_closed_page_count ) = 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:
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?'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Too many pages!', yes_label = 'yes, and do not tell me again', no_label = 'no' ) as dlg:
if dlg.ShowModal() == wx.ID_YES:
HG.no_page_limit_mode = True
self._controller.pub( 'notify_new_options' )
else:
return
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.GetDisplayName()
# in some unusual circumstances, this gets out of whack
insertion_index = min( insertion_index, self.GetPageCount() )
self.InsertPage( insertion_index, page, page_name, select = select_page )
self._controller.pub( 'refresh_page_name', page.GetPageKey() )
self._controller.pub( 'notify_new_pages' )
wx.CallAfter( 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.CallLaterWXSafe( self, 0.5, 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 NewPageImportBooru( self, on_deepest_notebook = False ):
with ClientGUIDialogs.DialogSelectBooru( self ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
gallery_identifier = dlg.GetGalleryIdentifier()
return self.NewPageImportGallery( gallery_identifier, on_deepest_notebook = on_deepest_notebook )
def NewPageImportGallery( self, gallery_identifier, on_deepest_notebook = False ):
management_controller = ClientGUIManagement.CreateManagementControllerImportGallery( gallery_identifier )
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )
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, url = None, on_deepest_notebook = False ):
management_controller = ClientGUIManagement.CreateManagementControllerImportMultipleWatcher( url )
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )
def NewPageImportWatcher( self, url = None, on_deepest_notebook = False ):
management_controller = ClientGUIManagement.CreateManagementControllerImportWatcher( url )
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )
def NewPageImportURLs( self, on_deepest_notebook = False ):
management_controller = ClientGUIManagement.CreateManagementControllerImportURLs()
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )
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, file_service_key, 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
file_search_context = ClientSearch.FileSearchContext( file_service_key = file_service_key, tag_service_key = tag_service_key, predicates = initial_predicates )
management_controller = ClientGUIManagement.CreateManagementControllerQuery( page_name, file_service_key, 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.GetCurrentPage()
if on_deepest_notebook and isinstance( current_page, PagesNotebook ):
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 )
return
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.GetDisplayName()
self.InsertPage( insertion_index, page, page_name, select = select_page )
self._controller.pub( 'refresh_page_name', page.GetPageKey() )
if give_it_a_blank_page:
page.NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY )
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.GetPageCount() )
name = page.GetDisplayName()
self.InsertPage( insert_index, page, name, True )
self._controller.pub( 'refresh_page_name', page.GetPageKey() )
self._closed_pages.remove( ( index, closed_page_key ) )
break
def PageHidden( self ):
result = self.GetCurrentPage()
if result is not None:
result.PageHidden()
def PageShown( self ):
result = self.GetCurrentPage()
if result is not None:
result.PageShown()
def PageDragAndDropDropped( self, page_key ):
page = self._GetPageFromPageKey( page_key )
if page is None:
return
screen_position = wx.GetMousePosition()
dest_notebook = self._GetNotebookFromScreenPosition( screen_position )
( x, y ) = screen_position
( tab_index, flags ) = ClientGUICommon.NotebookScreenToHitTest( dest_notebook, ( x, y ) )
if flags & wx.NB_HITTEST_ONPAGE:
# was not dropped on label area, so ditch DnD
return
if tab_index == wx.NOT_FOUND:
# if it isn't dropped on anything, put it on the end
tab_index = dest_notebook.GetPageCount()
if tab_index > 0:
if dest_notebook.GetPage( tab_index - 1 ) == page:
return
else:
EDGE_PADDING = 10
( left_tab_index, gumpf ) = ClientGUICommon.NotebookScreenToHitTest( dest_notebook, ( x - EDGE_PADDING, y ) )
( right_tab_index, gumpf ) = ClientGUICommon.NotebookScreenToHitTest( dest_notebook, ( x + EDGE_PADDING, y ) )
landed_near_left_edge = left_tab_index != tab_index
landed_near_right_edge = right_tab_index != tab_index
landed_on_edge = landed_near_right_edge or landed_near_left_edge
landed_in_middle = not landed_on_edge
there_is_a_page_to_the_left = tab_index > 0
there_is_a_page_to_the_right = tab_index < dest_notebook.GetPageCount() - 1
page_on_left_is_source = there_is_a_page_to_the_left and dest_notebook.GetPage( tab_index - 1 ) == page
page_on_right_is_source = there_is_a_page_to_the_right and dest_notebook.GetPage( tab_index + 1 ) == page
# dropped on source and not on the right edge: do nothing
landee_page = dest_notebook.GetPage( tab_index )
if landee_page == page:
if landed_near_right_edge and there_is_a_page_to_the_right:
tab_index += 1
else:
return
# dropped just to the left of source: do nothing
if landed_near_right_edge and page_on_right_is_source:
return
# dropped on left side of an edge: insert on right side
if landed_near_right_edge:
tab_index += 1
if landed_in_middle and isinstance( landee_page, PagesNotebook ):
dest_notebook = landee_page
tab_index = dest_notebook.GetPageCount()
if dest_notebook == page or ClientGUICommon.IsWXAncestor( dest_notebook, page ):
# can't drop a notebook beneath itself!
return
insertion_tab_index = tab_index
shift_down = wx.GetKeyState( wx.WXK_SHIFT )
follow_dropped_page = not shift_down
new_options = HG.client_controller.new_options
if new_options.GetBoolean( 'reverse_page_shift_drag_behaviour' ):
follow_dropped_page = not follow_dropped_page
self._MovePage( page, dest_notebook, insertion_tab_index, follow_dropped_page )
def PrepareToHide( self ):
for page in self._GetPages():
page.PrepareToHide()
def PresentImportedFilesToPage( self, hashes, page_name ):
page = self._GetPageFromName( page_name )
if page is None:
page = self.NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY, initial_hashes = hashes, page_name = page_name, on_deepest_notebook = True, select_page = False )
else:
unsorted_media_results = self._controller.Read( 'media_results', hashes )
hashes_to_media_results = { media_result.GetHash() : media_result for media_result in unsorted_media_results }
sorted_media_results = [ hashes_to_media_results[ hash ] for hash in hashes ]
page.GetMediaPanel().AddMediaResults( page.GetPageKey(), sorted_media_results )
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.GetPageCount() ):
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 RenamePage( self, page_key, name ):
for page in self._GetPages():
if page.GetPageKey() == page_key:
existing_name = page.GetName()
# this is the auto-renaming system. if the existing name is user-set, then don't overwrite
if not existing_name.startswith( USER_PAGE_NAME_PREFIX ):
if existing_name != name:
page.SetName( name )
self.RefreshPageName( page_key )
return
elif isinstance( page, PagesNotebook ) and page.HasPageKey( page_key ):
page.RenamePage( page_key, name )
return
def SaveGUISession( self, name = None, suggested_name = '' ):
if name is None:
while True:
with ClientGUIDialogs.DialogTextEntry( self, 'Enter a name for the new session.', default = suggested_name ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
name = dlg.GetValue()
if name in ( '', 'just a blank page', 'last session' ):
wx.MessageBox( 'Sorry, you cannot have that name! Try another.' )
else:
existing_session_names = self._controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION )
if name in existing_session_names:
message = 'Session \'' + name + '\' already exists! Do you want to overwrite it?'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Overwrite existing session?', yes_label = 'yes, overwrite', no_label = 'no, choose another name' ) as yn_dlg:
if yn_dlg.ShowModal() != wx.ID_YES:
continue
break
else:
return
elif name != 'last session':
message = 'Overwrite this session?'
with ClientGUIDialogs.DialogYesNo( self, message, title = 'Overwrite existing session?', yes_label = 'yes, overwrite', no_label = 'no' ) as yn_dlg:
if yn_dlg.ShowModal() != wx.ID_YES:
return
#
session = GUISession( name )
for page in self._GetPages():
session.AddPage( page )
#
if name == 'last session':
session_hash = hashlib.sha256( session.DumpToString() ).digest()
if session_hash == self._last_last_session_hash:
return
self._last_last_session_hash = session_hash
self._controller.WriteSynchronous( 'serialisable', session )
self._controller.pub( 'notify_new_sessions' )
def SetName( self, name, from_user = False ):
if from_user:
name = USER_PAGE_NAME_PREFIX + name
self._name = name
def ShowPage( self, showee ):
for ( i, page ) in enumerate( self._GetPages() ):
if isinstance( page, wx.Notebook ) and page.HasPage( showee ):
self.SetSelection( i )
page.ShowPage( showee )
break
elif page == showee:
self.SetSelection( i )
break
def TestAbleToClose( self ):
count = collections.Counter()
for page in self._GetMediaPages( False ):
try:
page.CheckAbleToClose()
except HydrusExceptions.VetoException as e:
reason = HydrusData.ToUnicode( e )
count[ reason ] += 1
if len( count ) > 0:
total_problems = sum( count.values() )
message = ''
for ( reason, c ) in count.items():
if c == 1:
message = '1 page says: ' + reason
else:
message = HydrusData.ConvertIntToPrettyString( c ) + ' pages say:' + reason
message += os.linesep
message += os.linesep
if total_problems == 1:
message += 'Are you sure you want to close it?'
else:
message += 'Are you sure you want to close them?'
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
if dlg.ShowModal() != wx.ID_YES:
raise HydrusExceptions.VetoException()
def REPEATINGPageUpdate( self ):
pass
class GUISession( HydrusSerialisable.SerialisableBaseNamed ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION
SERIALISABLE_NAME = 'GUI Session'
SERIALISABLE_VERSION = 3
def __init__( self, name ):
HydrusSerialisable.SerialisableBaseNamed.__init__( self, name )
self._pages = []
def _GetPageTuple( self, page ):
if isinstance( page, PagesNotebook ):
name = page.GetName()
page_tuples = [ self._GetPageTuple( subpage ) for subpage in page.GetPages() ]
return ( 'pages', ( name, page_tuples ) )
else:
management_controller = page.GetManagementController()
hashes = list( page.GetHashes() )
return ( 'page', ( management_controller, hashes ) )
def _GetSerialisableInfo( self ):
def handle_e( e ):
HydrusData.ShowText( 'Attempting to save a page to the session failed! Its data tuple and error follows! Please close it or see if you can clear any potentially invalid data from it!' )
HydrusData.ShowText( page_tuple )
HydrusData.ShowException( e )
def GetSerialisablePageTuple( page_tuple ):
( page_type, page_data ) = page_tuple
if page_type == 'pages':
( name, page_tuples ) = page_data
serialisable_page_tuples = []
for pt in page_tuples:
try:
serialisable_page_tuples.append( GetSerialisablePageTuple( pt ) )
except Exception as e:
handle_e( e )
serialisable_page_data = ( name, serialisable_page_tuples )
elif page_type == 'page':
( management_controller, hashes ) = page_data
serialisable_management_controller = management_controller.GetSerialisableTuple()
serialisable_hashes = [ hash.encode( 'hex' ) for hash in hashes ]
serialisable_page_data = ( serialisable_management_controller, serialisable_hashes )
serialisable_tuple = ( page_type, serialisable_page_data )
return serialisable_tuple
serialisable_info = []
for page_tuple in self._pages:
try:
serialisable_page_tuple = GetSerialisablePageTuple( page_tuple )
serialisable_info.append( serialisable_page_tuple )
except Exception as e:
handle_e( e )
return serialisable_info
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
def handle_e( e ):
HydrusData.ShowText( 'A page failed to load! Its serialised data and error follows!' )
HydrusData.ShowText( serialisable_page_tuple )
HydrusData.ShowException( e )
def GetPageTuple( serialisable_page_tuple ):
( page_type, serialisable_page_data ) = serialisable_page_tuple
if page_type == 'pages':
( name, serialisable_page_tuples ) = serialisable_page_data
page_tuples = []
for spt in serialisable_page_tuples:
try:
page_tuples.append( GetPageTuple( spt ) )
except Exception as e:
handle_e( e )
page_data = ( name, page_tuples )
elif page_type == 'page':
( serialisable_management_controller, serialisable_hashes ) = serialisable_page_data
management_controller = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_management_controller )
hashes = [ hash.decode( 'hex' ) for hash in serialisable_hashes ]
page_data = ( management_controller, hashes )
page_tuple = ( page_type, page_data )
return page_tuple
for serialisable_page_tuple in serialisable_info:
try:
page_tuple = GetPageTuple( serialisable_page_tuple )
self._pages.append( page_tuple )
except Exception as e:
handle_e( e )
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
new_serialisable_info = []
for ( page_name, serialisable_management_controller, serialisable_hashes ) in old_serialisable_info:
management_controller = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_management_controller )
management_controller.SetPageName( page_name )
serialisable_management_controller = management_controller.GetSerialisableTuple()
new_serialisable_info.append( ( serialisable_management_controller, serialisable_hashes ) )
return ( 2, new_serialisable_info )
if version == 2:
new_serialisable_info = []
for ( serialisable_management_controller, serialisable_hashes ) in old_serialisable_info:
new_serialisable_info.append( ( 'page', ( serialisable_management_controller, serialisable_hashes ) ) )
return ( 3, new_serialisable_info )
def AddPage( self, page ):
page_tuple = self._GetPageTuple( page )
self._pages.append( page_tuple )
def GetPages( self ):
return self._pages
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION ] = GUISession