Version 270
This commit is contained in:
parent
9b22a0ac22
commit
fac67766eb
|
@ -8,8 +8,37 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 270</h3></li>
|
||||
<ul>
|
||||
<li>?added page tab drag and drop (windows only, have to iron out critical bugs for linux/os x).</li>
|
||||
<li>dragging onto the middle of a normal tab will put the source tab there</li>
|
||||
<li>dragging onto the edge of a tab will try to insert the tab there</li>
|
||||
<li>dragging onto the middle of a page of pages tab will insert the tab onto the end of that page's list</li>
|
||||
<li>dragging a page up a notebook level will work ok</li>
|
||||
<li>dragging a notebook into itself will do nothing and not crash the client :^)</li>
|
||||
<li>new pages that come from the main gui level (such as from pages menu or file import) now open in the deepest open notebook (previously, they would always appear in the top row)</li>
|
||||
<li>fixed some misc page of pages bugs</li>
|
||||
<li>fixed a bandwidth calculation that meant 1s time delta rules were working at 50% capacity (e.g. 1rq per 1s rule for domains were actually running at 1rq per 2s)</li>
|
||||
<li>improved a bandwidth estimate calculation that was cutting out early in some situations for large time deltas</li>
|
||||
<li>tag manager's page up/down shortcuts no longer mistakenly navigate the archive/delete filter</li>
|
||||
<li>added a network timeout option to the 'connection' options page</li>
|
||||
<li>reduced some hover window show/hide flicker when the media viewer is fullscreen and the OS has a taskbar that pops in for non-fullscreen windows (this mostly affected Linux)</li>
|
||||
<li>fixed a long-time issue with yes/no dialog layout</li>
|
||||
<li>improved some rendering of EXIF-rotated and -flipped jpegs. I expect to add more support for this (and retroactive image metadata-parsing to figure out correct reversed resolution--atm rotated images remain stretched) in the coming weeks</li>
|
||||
<li>fixed an error when cancelling the booru-picker dialog from the page chooser dialog</li>
|
||||
<li>if no files or all files are selected, the 'invert' select choice will no longer be shown (in this case, it redundantly does the same job as 'all' and 'none')</li>
|
||||
<li>fixed an issue where setting namespace sort as default would persist through a client reboot</li>
|
||||
<li>fixed an issue where force idle debug mode was not waking sleeping daemons</li>
|
||||
<li>increased frequency of mappings processing reporting</li>
|
||||
<li>wrote an exception and the needed maintenance code for the 'repairfilesystem' dialog to allow a proceed action if the only remaining incorrect paths are thumbnail paths--in this case, the client will create empty prefix folders and prompt the user to regen thumbnails</li>
|
||||
<li>export filenames are now clipped to make <255 total characters in path</li>
|
||||
<li>removed the old 'processing phase' option, which was no longer in use</li>
|
||||
<li>removed the proxy settings from the 'connection' options page--the new engine does not use this old system, but if there is demand, a more flexible system will return</li>
|
||||
<li>misc image rendering pipeline updates</li>
|
||||
<li>misc improvements</li>
|
||||
</ul>
|
||||
<li><h3>version 269</h3></li>
|
||||
<ul>
|
||||
<li>nested pages now supported!</li>
|
||||
<li>moved all page management (session load/save, new/close page, page navigation, page name maintenance, etc...) code from the main gui to the new PagesNotebook object</li>
|
||||
<li>expanded the session object to hold nested page information</li>
|
||||
|
|
|
@ -1840,9 +1840,11 @@ class ThumbnailCache( object ):
|
|||
|
||||
|
||||
|
||||
mime = display_media.GetMime()
|
||||
|
||||
try:
|
||||
|
||||
hydrus_bitmap = ClientRendering.GenerateHydrusBitmap( path )
|
||||
hydrus_bitmap = ClientRendering.GenerateHydrusBitmap( path, mime )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
@ -1854,7 +1856,7 @@ class ThumbnailCache( object ):
|
|||
|
||||
try:
|
||||
|
||||
hydrus_bitmap = ClientRendering.GenerateHydrusBitmap( path )
|
||||
hydrus_bitmap = ClientRendering.GenerateHydrusBitmap( path, mime )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
@ -1887,7 +1889,7 @@ class ThumbnailCache( object ):
|
|||
|
||||
self._controller.client_files_manager.RegenerateResizedThumbnail( hash )
|
||||
|
||||
hydrus_bitmap = ClientRendering.GenerateHydrusBitmap( path )
|
||||
hydrus_bitmap = ClientRendering.GenerateHydrusBitmap( path, mime )
|
||||
|
||||
|
||||
return hydrus_bitmap
|
||||
|
@ -1937,7 +1939,7 @@ class ThumbnailCache( object ):
|
|||
f.write( thumbnail )
|
||||
|
||||
|
||||
hydrus_bitmap = ClientRendering.GenerateHydrusBitmap( temp_path )
|
||||
hydrus_bitmap = ClientRendering.GenerateHydrusBitmap( temp_path, HC.IMAGE_PNG )
|
||||
|
||||
self._special_thumbs[ name ] = hydrus_bitmap
|
||||
|
||||
|
|
|
@ -466,14 +466,6 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
|
||||
|
||||
def ForceIdle( self ):
|
||||
|
||||
HG.force_idle_mode = not HG.force_idle_mode
|
||||
|
||||
self.pub( 'wake_daemons' )
|
||||
self.pubimmediate( 'refresh_status' )
|
||||
|
||||
|
||||
def GetApp( self ):
|
||||
|
||||
return self._app
|
||||
|
|
|
@ -1502,7 +1502,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
try:
|
||||
|
||||
phashes = ClientImageHandling.GenerateShapePerceptualHashes( path )
|
||||
phashes = ClientImageHandling.GenerateShapePerceptualHashes( path, mime )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
@ -7315,7 +7315,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
def _ProcessRepositoryContentUpdate( self, job_key, service_id, content_update ):
|
||||
|
||||
FILES_CHUNK_SIZE = 20
|
||||
MAPPINGS_CHUNK_SIZE = 10000
|
||||
MAPPINGS_CHUNK_SIZE = 1000
|
||||
NEW_TAG_PARENTS_CHUNK_SIZE = 5
|
||||
|
||||
total_rows = content_update.GetNumRows()
|
||||
|
@ -8054,6 +8054,10 @@ class DB( HydrusDB.HydrusDB ):
|
|||
portable_incorrect_location = HydrusPaths.ConvertAbsPathToPortablePath( incorrect_location )
|
||||
portable_correct_location = HydrusPaths.ConvertAbsPathToPortablePath( correct_location )
|
||||
|
||||
full_abs_correct_location = os.path.join( correct_location, prefix )
|
||||
|
||||
HydrusPaths.MakeSureDirectoryExists( full_abs_correct_location )
|
||||
|
||||
self._c.execute( 'UPDATE client_files_locations SET location = ? WHERE location = ? AND prefix = ?;', ( portable_correct_location, portable_incorrect_location, prefix ) )
|
||||
|
||||
|
||||
|
|
|
@ -392,7 +392,10 @@ def GetSortTypeChoices():
|
|||
|
||||
sort_choices = list( CC.SORT_CHOICES )
|
||||
|
||||
sort_choices.extend( HC.options[ 'sort_by' ] )
|
||||
for ( namespaces_text, namespaces_list ) in HC.options[ 'sort_by' ]:
|
||||
|
||||
sort_choices.append( ( namespaces_text, tuple( namespaces_list ) ) )
|
||||
|
||||
|
||||
service_keys = HG.client_controller.services_manager.GetServiceKeys( ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) )
|
||||
|
||||
|
@ -852,6 +855,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
self._dictionary[ 'integers' ][ 'max_page_name_chars' ] = 20
|
||||
self._dictionary[ 'integers' ][ 'page_file_count_display' ] = CC.PAGE_FILE_COUNT_DISPLAY_ALL
|
||||
|
||||
self._dictionary[ 'integers' ][ 'network_timeout' ] = 10
|
||||
|
||||
#
|
||||
|
||||
self._dictionary[ 'keys' ] = {}
|
||||
|
|
|
@ -196,8 +196,6 @@ def GetClientDefaultOptions():
|
|||
options[ 'pause_repo_sync' ] = False
|
||||
options[ 'pause_subs_sync' ] = False
|
||||
|
||||
options[ 'processing_phase' ] = 0
|
||||
|
||||
options[ 'rating_dialog_position' ] = ( False, None )
|
||||
|
||||
return options
|
||||
|
|
|
@ -3,20 +3,23 @@ import wx
|
|||
|
||||
class FileDropTarget( wx.PyDropTarget ):
|
||||
|
||||
def __init__( self, filenames_callable = None, url_callable = None ):
|
||||
def __init__( self, filenames_callable = None, url_callable = None, page_callable = None ):
|
||||
|
||||
wx.PyDropTarget.__init__( self )
|
||||
|
||||
self._filenames_callable = filenames_callable
|
||||
self._url_callable = url_callable
|
||||
self._page_callable = page_callable
|
||||
|
||||
self._receiving_data_object = wx.DataObjectComposite()
|
||||
|
||||
self._hydrus_media_data_object = wx.CustomDataObject( 'application/hydrus-media' )
|
||||
self._hydrus_page_tab_data_object = wx.CustomDataObject( 'application/hydrus-page-tab' )
|
||||
self._file_data_object = wx.FileDataObject()
|
||||
self._text_data_object = wx.TextDataObject()
|
||||
|
||||
self._receiving_data_object.Add( self._hydrus_media_data_object, True )
|
||||
self._receiving_data_object.Add( self._hydrus_page_tab_data_object )
|
||||
self._receiving_data_object.Add( self._file_data_object )
|
||||
self._receiving_data_object.Add( self._text_data_object )
|
||||
|
||||
|
@ -59,6 +62,13 @@ class FileDropTarget( wx.PyDropTarget ):
|
|||
pass
|
||||
|
||||
|
||||
if format_id == 'application/hydrus-page-tab' and self._page_callable is not None:
|
||||
|
||||
page_key = self._hydrus_page_tab_data_object.GetData()
|
||||
|
||||
self._page_callable( page_key )
|
||||
|
||||
|
||||
|
||||
|
||||
return result
|
||||
|
|
|
@ -11,9 +11,14 @@ import os
|
|||
import re
|
||||
import stat
|
||||
|
||||
def GenerateExportFilename( media, terms ):
|
||||
MAX_PATH_LENGTH = 245 # bit of padding from 255 for .txt neigbouring and other surprises
|
||||
|
||||
def GenerateExportFilename( destination_directory, media, terms ):
|
||||
|
||||
mime = media.GetMime()
|
||||
if len( destination_directory ) > ( MAX_PATH_LENGTH - 10 ):
|
||||
|
||||
raise Exception( 'The destination directory is too long!' )
|
||||
|
||||
|
||||
filename = ''
|
||||
|
||||
|
@ -84,13 +89,28 @@ def GenerateExportFilename( media, terms ):
|
|||
filename = re.sub( '/', '_', filename, flags = re.UNICODE )
|
||||
|
||||
|
||||
#
|
||||
|
||||
mime = media.GetMime()
|
||||
|
||||
ext = HC.mime_ext_lookup[ mime ]
|
||||
|
||||
if not filename.endswith( ext ):
|
||||
if filename.endswith( ext ):
|
||||
|
||||
filename += ext
|
||||
filename = filename[ : - len( ext ) ]
|
||||
|
||||
|
||||
example_dest_path = os.path.join( destination_directory, filename + ext )
|
||||
|
||||
excess_chars = len( example_dest_path ) - MAX_PATH_LENGTH
|
||||
|
||||
if excess_chars > 0:
|
||||
|
||||
filename = filename[ : - excess_chars ]
|
||||
|
||||
|
||||
filename = filename + ext
|
||||
|
||||
return filename
|
||||
|
||||
def GetExportPath():
|
||||
|
@ -299,7 +319,7 @@ class ExportFolder( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
source_path = client_files_manager.GetFilePath( hash, mime )
|
||||
|
||||
filename = GenerateExportFilename( media_result, terms )
|
||||
filename = GenerateExportFilename( folder_path, media_result, terms )
|
||||
|
||||
dest_path = os.path.join( folder_path, filename )
|
||||
|
||||
|
|
|
@ -71,8 +71,6 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
|
||||
ClientGUITopLevelWindows.FrameThatResizes.__init__( self, None, title, 'main_gui', float_on_parent = False )
|
||||
|
||||
self.SetDropTarget( ClientDragDrop.FileDropTarget( self.ImportFiles, self.ImportURL ) )
|
||||
|
||||
bandwidth_width = ClientData.ConvertTextToPixelWidth( self, 17 )
|
||||
idle_width = ClientData.ConvertTextToPixelWidth( self, 6 )
|
||||
system_busy_width = ClientData.ConvertTextToPixelWidth( self, 13 )
|
||||
|
@ -92,6 +90,8 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
|
||||
self._notebook = ClientGUIPages.PagesNotebook( self, self._controller, 'top page notebook' )
|
||||
|
||||
self.SetDropTarget( ClientDragDrop.FileDropTarget( self.ImportFiles, self.ImportURL, self._notebook.PageDragAndDropDropped ) )
|
||||
|
||||
wx.GetApp().SetTopWindow( self )
|
||||
|
||||
self._message_manager = ClientGUIPopupMessages.PopupMessageManager( self )
|
||||
|
@ -278,7 +278,11 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
|
||||
self._controller.SetServices( all_services )
|
||||
|
||||
HydrusData.ShowText( 'Auto repo setup done! Check services->review services to see your new services.' )
|
||||
message = 'Auto repo setup done! Check services->review services to see your new services.'
|
||||
message += os.linesep * 2
|
||||
message += 'The PTR has a lot of tags and will sync a little bit at a time when you are not using the client. Expect it to take a few weeks to sync fully.'
|
||||
|
||||
HydrusData.ShowText( message )
|
||||
|
||||
|
||||
text = 'This will attempt to set up your client with my repositories\' credentials, letting you tag on the public tag repository and see some files.'
|
||||
|
@ -1010,12 +1014,12 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
|
||||
petition_resolvable_repositories = [ repository for repository in repositories if True in ( repository.HasPermission( content_type, action ) for ( content_type, action ) in petition_permissions ) ]
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, search_menu, 'my files', 'Open a new search tab for your files.', self._notebook.NewPageQuery, CC.LOCAL_FILE_SERVICE_KEY )
|
||||
ClientGUIMenus.AppendMenuItem( self, search_menu, 'trash', 'Open a new search tab for your recently deleted files.', self._notebook.NewPageQuery, CC.TRASH_SERVICE_KEY )
|
||||
ClientGUIMenus.AppendMenuItem( self, search_menu, 'my files', 'Open a new search tab for your files.', self._notebook.NewPageQuery, CC.LOCAL_FILE_SERVICE_KEY, on_deepest_notebook = True )
|
||||
ClientGUIMenus.AppendMenuItem( self, search_menu, 'trash', 'Open a new search tab for your recently deleted files.', self._notebook.NewPageQuery, CC.TRASH_SERVICE_KEY, on_deepest_notebook = True )
|
||||
|
||||
for service in file_repositories:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, search_menu, service.GetName(), 'Open a new search tab for ' + service.GetName() + '.', self._notebook.NewPageQuery, service.GetServiceKey() )
|
||||
ClientGUIMenus.AppendMenuItem( self, search_menu, service.GetName(), 'Open a new search tab for ' + service.GetName() + '.', self._notebook.NewPageQuery, service.GetServiceKey(), on_deepest_notebook = True )
|
||||
|
||||
|
||||
ClientGUIMenus.AppendMenu( menu, search_menu, 'new search page' )
|
||||
|
@ -1028,7 +1032,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
|
||||
for service in petition_resolvable_repositories:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, petition_menu, service.GetName(), 'Open a new petition page for ' + service.GetName() + '.', self._notebook.NewPagePetitions, service.GetServiceKey() )
|
||||
ClientGUIMenus.AppendMenuItem( self, petition_menu, service.GetName(), 'Open a new petition page for ' + service.GetName() + '.', self._notebook.NewPagePetitions, service.GetServiceKey(), on_deepest_notebook = True )
|
||||
|
||||
|
||||
ClientGUIMenus.AppendMenu( menu, petition_menu, 'new petition page' )
|
||||
|
@ -1038,23 +1042,23 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
|
||||
download_menu = wx.Menu()
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, download_menu, 'url download', 'Open a new tab to download some raw urls.', self._notebook.NewPageImportURLs )
|
||||
ClientGUIMenus.AppendMenuItem( self, download_menu, 'thread watcher', 'Open a new tab to watch a thread.', self._notebook.NewPageImportThreadWatcher )
|
||||
ClientGUIMenus.AppendMenuItem( self, download_menu, 'webpage of images', 'Open a new tab to download files from generic galleries or threads.', self._notebook.NewPageImportPageOfImages )
|
||||
ClientGUIMenus.AppendMenuItem( self, download_menu, 'url download', 'Open a new tab to download some raw urls.', self._notebook.NewPageImportURLs, on_deepest_notebook = True )
|
||||
ClientGUIMenus.AppendMenuItem( self, download_menu, 'thread watcher', 'Open a new tab to watch a thread.', self._notebook.NewPageImportThreadWatcher, on_deepest_notebook = True )
|
||||
ClientGUIMenus.AppendMenuItem( self, download_menu, 'webpage of images', 'Open a new tab to download files from generic galleries or threads.', self._notebook.NewPageImportPageOfImages, on_deepest_notebook = True )
|
||||
|
||||
gallery_menu = wx.Menu()
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, gallery_menu, 'booru', 'Open a new tab to download files from a booru.', self._notebook.NewPageImportBooru )
|
||||
ClientGUIMenus.AppendMenuItem( self, gallery_menu, 'deviant art', 'Open a new tab to download files from Deviant Art.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_DEVIANT_ART ) )
|
||||
ClientGUIMenus.AppendMenuItem( self, gallery_menu, 'booru', 'Open a new tab to download files from a booru.', self._notebook.NewPageImportBooru, on_deepest_notebook = True )
|
||||
ClientGUIMenus.AppendMenuItem( self, gallery_menu, 'deviant art', 'Open a new tab to download files from Deviant Art.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_DEVIANT_ART ), on_deepest_notebook = True )
|
||||
|
||||
hf_submenu = wx.Menu()
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, hf_submenu, 'by artist', 'Open a new tab to download files from Hentai Foundry.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_HENTAI_FOUNDRY_ARTIST ) )
|
||||
ClientGUIMenus.AppendMenuItem( self, hf_submenu, 'by tags', 'Open a new tab to download files from Hentai Foundry.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_HENTAI_FOUNDRY_TAGS ) )
|
||||
ClientGUIMenus.AppendMenuItem( self, hf_submenu, 'by artist', 'Open a new tab to download files from Hentai Foundry.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_HENTAI_FOUNDRY_ARTIST ), on_deepest_notebook = True )
|
||||
ClientGUIMenus.AppendMenuItem( self, hf_submenu, 'by tags', 'Open a new tab to download files from Hentai Foundry.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_HENTAI_FOUNDRY_TAGS ), on_deepest_notebook = True )
|
||||
|
||||
ClientGUIMenus.AppendMenu( gallery_menu, hf_submenu, 'hentai foundry' )
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, gallery_menu, 'newgrounds', 'Open a new tab to download files from Newgrounds.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_NEWGROUNDS ) )
|
||||
ClientGUIMenus.AppendMenuItem( self, gallery_menu, 'newgrounds', 'Open a new tab to download files from Newgrounds.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_NEWGROUNDS ), on_deepest_notebook = True )
|
||||
|
||||
result = self._controller.Read( 'serialisable_simple', 'pixiv_account' )
|
||||
|
||||
|
@ -1062,13 +1066,13 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
|
||||
pixiv_submenu = wx.Menu()
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, pixiv_submenu, 'by artist id', 'Open a new tab to download files from Pixiv.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_PIXIV_ARTIST_ID ) )
|
||||
ClientGUIMenus.AppendMenuItem( self, pixiv_submenu, 'by tag', 'Open a new tab to download files from Pixiv.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_PIXIV_TAG ) )
|
||||
ClientGUIMenus.AppendMenuItem( self, pixiv_submenu, 'by artist id', 'Open a new tab to download files from Pixiv.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_PIXIV_ARTIST_ID ), on_deepest_notebook = True )
|
||||
ClientGUIMenus.AppendMenuItem( self, pixiv_submenu, 'by tag', 'Open a new tab to download files from Pixiv.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_PIXIV_TAG ), on_deepest_notebook = True )
|
||||
|
||||
ClientGUIMenus.AppendMenu( gallery_menu, pixiv_submenu, 'pixiv' )
|
||||
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, gallery_menu, 'tumblr', 'Open a new tab to download files from tumblr.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_TUMBLR ) )
|
||||
ClientGUIMenus.AppendMenuItem( self, gallery_menu, 'tumblr', 'Open a new tab to download files from tumblr.', self._notebook.NewPageImportGallery, ClientDownloading.GalleryIdentifier( HC.SITE_TYPE_TUMBLR ), on_deepest_notebook = True )
|
||||
|
||||
ClientGUIMenus.AppendMenu( download_menu, gallery_menu, 'gallery' )
|
||||
ClientGUIMenus.AppendMenu( menu, download_menu, 'new download page' )
|
||||
|
@ -1092,8 +1096,8 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
|
||||
special_menu = wx.Menu()
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, special_menu, 'page of pages', 'Open a new tab that can hold more tabs.', self._notebook.NewPagesNotebook )
|
||||
ClientGUIMenus.AppendMenuItem( self, special_menu, 'duplicates processing', 'Open a new tab to discover and filter duplicate files.', self._notebook.NewPageDuplicateFilter )
|
||||
ClientGUIMenus.AppendMenuItem( self, special_menu, 'page of pages', 'Open a new tab that can hold more tabs.', self._notebook.NewPagesNotebook, on_deepest_notebook = True )
|
||||
ClientGUIMenus.AppendMenuItem( self, special_menu, 'duplicates processing', 'Open a new tab to discover and filter duplicate files.', self._notebook.NewPageDuplicateFilter, on_deepest_notebook = True )
|
||||
|
||||
ClientGUIMenus.AppendMenu( menu, special_menu, 'new special page' )
|
||||
|
||||
|
@ -1417,6 +1421,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
ClientGUIMenus.AppendMenuCheckItem( self, debug_modes, 'network report mode', 'Have the network engine report new jobs.', HG.network_report_mode, self._SwitchBoolean, 'network_report_mode' )
|
||||
ClientGUIMenus.AppendMenuCheckItem( self, debug_modes, 'pubsub profile mode', 'Run detailed \'profiles\' on every internal publisher/subscriber message and dump this information to the log. This can hammer your log with dozens of large dumps every second. Don\'t run it unless you know you need to.', HG.pubsub_profile_mode, self._SwitchBoolean, 'pubsub_profile_mode' )
|
||||
ClientGUIMenus.AppendMenuCheckItem( self, debug_modes, 'force idle mode', 'Make the client consider itself idle and fire all maintenance routines right now. This may hang the gui for a while.', HG.force_idle_mode, self._SwitchBoolean, 'force_idle_mode' )
|
||||
ClientGUIMenus.AppendMenuCheckItem( self, debug_modes, 'no page limit mode', 'Let the user create as many pages as they want with no warnings or prohibitions.', HG.no_page_limit_mode, self._SwitchBoolean, 'no_page_limit_mode' )
|
||||
|
||||
ClientGUIMenus.AppendMenu( debug, debug_modes, 'modes' )
|
||||
|
||||
|
@ -1637,7 +1642,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
|
||||
if load_a_blank_page:
|
||||
|
||||
self._notebook.NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY )
|
||||
self._notebook.NewPageQuery( CC.LOCAL_FILE_SERVICE_KEY, on_deepest_notebook = True )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -2356,6 +2361,13 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
HG.force_idle_mode = not HG.force_idle_mode
|
||||
|
||||
self._controller.pub( 'wake_daemons' )
|
||||
self._controller.pubimmediate( 'refresh_status' )
|
||||
|
||||
elif name == 'no_page_limit_mode':
|
||||
|
||||
HG.no_page_limit_mode = not HG.no_page_limit_mode
|
||||
|
||||
|
||||
|
||||
def _UnclosePage( self, closed_page_index = None ):
|
||||
|
@ -2813,17 +2825,15 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
def EventFrameNewPage( self, event ):
|
||||
|
||||
screen_position = self.ClientToScreen( event.GetPosition() )
|
||||
new_position = self._notebook.ScreenToClient( screen_position )
|
||||
|
||||
self._notebook.EventNewPageFromMousePosition( new_position )
|
||||
self._notebook.EventNewPageFromScreenPosition( screen_position )
|
||||
|
||||
|
||||
def EventFrameNotebookMenu( self, event ):
|
||||
|
||||
screen_position = self.ClientToScreen( event.GetPosition() )
|
||||
new_position = self._notebook.ScreenToClient( screen_position )
|
||||
|
||||
self._notebook.EventMenuFromMousePosition( new_position )
|
||||
self._notebook.EventMenuFromScreenPosition( screen_position )
|
||||
|
||||
|
||||
def TIMEREventBandwidth( self, event ):
|
||||
|
@ -3020,7 +3030,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
# change this when thread watchers can support multiple sub-pages etc...
|
||||
|
||||
self._notebook.NewPageImportThreadWatcher( url )
|
||||
self._notebook.NewPageImportThreadWatcher( url, on_deepest_notebook = True )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -3055,7 +3065,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
management_controller = ClientGUIManagement.CreateManagementControllerImportHDD( paths, import_file_options, paths_to_tags, delete_after_success )
|
||||
|
||||
self._notebook.NewPage( management_controller )
|
||||
self._notebook.NewPage( management_controller, on_deepest_notebook = True )
|
||||
|
||||
|
||||
def NewPageQuery( self, service_key, initial_hashes = None, initial_predicates = None, page_name = None ):
|
||||
|
@ -3070,7 +3080,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
initial_predicates = []
|
||||
|
||||
|
||||
self._notebook.NewPageQuery( service_key, initial_hashes = initial_hashes, initial_predicates = initial_predicates, page_name = page_name )
|
||||
self._notebook.NewPageQuery( service_key, initial_hashes = initial_hashes, initial_predicates = initial_predicates, page_name = page_name, on_deepest_notebook = True )
|
||||
|
||||
|
||||
def NotifyClosedPage( self, page ):
|
||||
|
|
|
@ -4027,9 +4027,7 @@ class CanvasMediaListFilterArchiveDelete( CanvasMediaList ):
|
|||
self.Bind( wx.EVT_MENU, self.EventMenu )
|
||||
|
||||
HG.client_controller.sub( self, 'Delete', 'canvas_delete' )
|
||||
HG.client_controller.sub( self, 'Skip', 'canvas_show_next' )
|
||||
HG.client_controller.sub( self, 'Undelete', 'canvas_undelete' )
|
||||
HG.client_controller.sub( self, 'Back', 'canvas_show_previous' )
|
||||
|
||||
wx.CallAfter( self.SetMedia, self._GetFirst() ) # don't set this until we have a size > (20, 20)!
|
||||
|
||||
|
@ -5418,10 +5416,11 @@ class EmbedButton( wx.Window ):
|
|||
if needs_thumb:
|
||||
|
||||
hash = self._media.GetHash()
|
||||
mime = self._media.GetMime()
|
||||
|
||||
thumbnail_path = HG.client_controller.client_files_manager.GetFullSizeThumbnailPath( hash )
|
||||
|
||||
self._thumbnail_bmp = ClientRendering.GenerateHydrusBitmap( thumbnail_path ).GetWxBitmap()
|
||||
self._thumbnail_bmp = ClientRendering.GenerateHydrusBitmap( thumbnail_path, mime ).GetWxBitmap()
|
||||
|
||||
self._SetDirty()
|
||||
|
||||
|
@ -5446,10 +5445,11 @@ class OpenExternallyPanel( wx.Panel ):
|
|||
if self._media.GetLocationsManager().IsLocal() and self._media.GetMime() in HC.MIMES_WITH_THUMBNAILS:
|
||||
|
||||
hash = self._media.GetHash()
|
||||
mime = self._media.GetMime()
|
||||
|
||||
thumbnail_path = HG.client_controller.client_files_manager.GetFullSizeThumbnailPath( hash )
|
||||
|
||||
bmp = ClientRendering.GenerateHydrusBitmap( thumbnail_path ).GetWxBitmap()
|
||||
bmp = ClientRendering.GenerateHydrusBitmap( thumbnail_path, mime ).GetWxBitmap()
|
||||
|
||||
thumbnail = ClientGUICommon.BufferedWindowIcon( self, bmp )
|
||||
|
||||
|
|
|
@ -830,7 +830,7 @@ class DialogInputLocalFiles( Dialog ):
|
|||
|
||||
Dialog.__init__( self, parent, 'importing files' )
|
||||
|
||||
self.SetDropTarget( ClientDragDrop.FileDropTarget( self._AddPathsToList, None ) )
|
||||
self.SetDropTarget( ClientDragDrop.FileDropTarget( filenames_callable = self._AddPathsToList ) )
|
||||
|
||||
listctrl_panel = ClientGUIListCtrl.SaneListCtrlPanel( self )
|
||||
|
||||
|
@ -3229,7 +3229,7 @@ class DialogSetupExport( Dialog ):
|
|||
|
||||
directory = HydrusData.ToUnicode( self._directory_picker.GetPath() )
|
||||
|
||||
filename = ClientExporting.GenerateExportFilename( media, terms )
|
||||
filename = ClientExporting.GenerateExportFilename( directory, media, terms )
|
||||
|
||||
return os.path.join( directory, filename )
|
||||
|
||||
|
@ -3595,7 +3595,7 @@ class DialogYesNo( Dialog ):
|
|||
|
||||
text.Wrap( 480 )
|
||||
|
||||
vbox.AddF( text, CC.FLAGS_BIG_INDENT )
|
||||
vbox.AddF( text, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
vbox.AddF( hbox, CC.FLAGS_BUTTON_SIZER )
|
||||
|
||||
self.SetSizer( vbox )
|
||||
|
|
|
@ -120,7 +120,7 @@ class DialogManageBoorus( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self.SetSizer( vbox )
|
||||
|
||||
self.SetDropTarget( ClientDragDrop.FileDropTarget( self.Import, None ) )
|
||||
self.SetDropTarget( ClientDragDrop.FileDropTarget( filenames_callable = self.Import ) )
|
||||
|
||||
( x, y ) = self.GetEffectiveMinSize()
|
||||
|
||||
|
@ -617,7 +617,7 @@ class DialogManageContacts( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self.SetInitialSize( ( 980, y ) )
|
||||
|
||||
self.SetDropTarget( ClientDragDrop.FileDropTarget( self.Import, None ) )
|
||||
self.SetDropTarget( ClientDragDrop.FileDropTarget( filenames_callable = self.Import ) )
|
||||
|
||||
self.EventContactChanged( None )
|
||||
|
||||
|
@ -1517,7 +1517,7 @@ class DialogManageImageboards( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self.SetInitialSize( ( 980, y ) )
|
||||
|
||||
self.SetDropTarget( ClientDragDrop.FileDropTarget( self.Import, None ) )
|
||||
self.SetDropTarget( ClientDragDrop.FileDropTarget( filenames_callable = self.Import ) )
|
||||
|
||||
wx.CallAfter( self._ok.SetFocus )
|
||||
|
||||
|
|
|
@ -108,11 +108,30 @@ class FullscreenHoverFrame( wx.Frame ):
|
|||
|
||||
( should_resize, ( my_ideal_width, my_ideal_height ), ( my_ideal_x, my_ideal_y ) ) = self._GetIdealSizeAndPosition()
|
||||
|
||||
if my_ideal_width == -1: my_ideal_width = my_width
|
||||
if my_ideal_height == -1: my_ideal_height = my_height
|
||||
if my_ideal_width == -1:
|
||||
|
||||
my_ideal_width = max( my_width, 50 )
|
||||
|
||||
|
||||
in_x = my_ideal_x <= mouse_x and mouse_x <= my_ideal_x + my_ideal_width
|
||||
in_y = my_ideal_y <= mouse_y and mouse_y <= my_ideal_y + my_ideal_height
|
||||
if my_ideal_height == -1:
|
||||
|
||||
my_ideal_height = max( my_height, 50 )
|
||||
|
||||
|
||||
( my_x, my_y ) = self.GetPosition()
|
||||
|
||||
in_ideal_x = my_ideal_x <= mouse_x and mouse_x <= my_ideal_x + my_ideal_width
|
||||
in_ideal_y = my_ideal_y <= mouse_y and mouse_y <= my_ideal_y + my_ideal_height
|
||||
|
||||
in_actual_x = my_x <= mouse_x and mouse_x <= my_x + my_width
|
||||
in_actual_y = my_y <= mouse_y and mouse_y <= my_y + my_height
|
||||
|
||||
# we test both ideal and actual here because setposition is not always honoured by the OS
|
||||
# for instance, in Linux on a fullscreen view, the top taskbar is hidden, but when hover window is shown, it takes focus and causes taskbar to reappear
|
||||
# the reappearance shuffles the screen coordinates down a bit so the hover sits +20px y despite wanting to be lined up with the underlying fullscreen viewer
|
||||
# wew lad
|
||||
|
||||
in_position = ( in_ideal_x or in_actual_x ) and ( in_ideal_y or in_actual_y )
|
||||
|
||||
menu_open = HG.client_controller.MenuIsOpen()
|
||||
|
||||
|
@ -130,8 +149,6 @@ class FullscreenHoverFrame( wx.Frame ):
|
|||
|
||||
mime = self._current_media.GetMime()
|
||||
|
||||
in_position = in_x and in_y
|
||||
|
||||
mouse_is_over_interactable_media = mime == HC.APPLICATION_FLASH and self.GetParent().MouseIsOverMedia()
|
||||
|
||||
mouse_is_near_animation_bar = self.GetParent().MouseIsNearAnimationBar()
|
||||
|
|
|
@ -436,7 +436,7 @@ def GenerateDumpMultipartFormDataCTAndBody( fields ):
|
|||
|
||||
with open( temp_path, 'wb' ) as f: f.write( jpeg )
|
||||
|
||||
self._bitmap = ClientRendering.GenerateHydrusBitmap( temp_path )
|
||||
self._bitmap = ClientRendering.GenerateHydrusBitmap( temp_path, HC.IMAGE_JPEG )
|
||||
|
||||
finally:
|
||||
|
||||
|
|
|
@ -1730,7 +1730,7 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
self.SetScrollRate( 0, thumbnail_span_height )
|
||||
|
||||
self.Bind( wx.EVT_LEFT_DOWN, self.EventSelection )
|
||||
self.Bind( wx.EVT_MOTION, self.EventDragTest )
|
||||
self.Bind( wx.EVT_MOTION, self.EventDrag )
|
||||
self.Bind( wx.EVT_RIGHT_DOWN, self.EventShowMenu )
|
||||
self.Bind( wx.EVT_LEFT_DCLICK, self.EventMouseFullScreen )
|
||||
self.Bind( wx.EVT_MIDDLE_DOWN, self.EventMouseFullScreen )
|
||||
|
@ -2342,7 +2342,7 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
|
||||
|
||||
def EventDragTest( self, event ):
|
||||
def EventDrag( self, event ):
|
||||
|
||||
if event.LeftIsDown() and self._drag_init_coordinates is not None:
|
||||
|
||||
|
@ -2656,8 +2656,11 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
ClientGUIMenus.AppendMenuItem( self, select_menu, 'all', 'Select everything.', self._Select, 'all' )
|
||||
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, select_menu, 'invert', 'Swap what is and is not selected.', self._Select, 'invert' )
|
||||
if len( self._selected_media ) > 0:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, select_menu, 'invert', 'Swap what is and is not selected.', self._Select, 'invert' )
|
||||
|
||||
|
||||
|
||||
if media_has_archive and media_has_inbox:
|
||||
|
||||
|
@ -3297,8 +3300,11 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
ClientGUIMenus.AppendMenuItem( self, select_menu, 'all', 'Select everything.', self._Select, 'all' )
|
||||
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, select_menu, 'invert', 'Swap what is and is not selected.', self._Select, 'invert' )
|
||||
if len( self._selected_media ) > 0:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( self, select_menu, 'invert', 'Swap what is and is not selected.', self._Select, 'invert' )
|
||||
|
||||
|
||||
|
||||
if media_has_archive and media_has_inbox:
|
||||
|
||||
|
|
|
@ -184,6 +184,8 @@ class DialogPageChooser( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self.EndModal( wx.ID_CANCEL )
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
elif entry_type == 'page_import_gallery':
|
||||
|
@ -719,6 +721,8 @@ class PagesNotebook( wx.Notebook ):
|
|||
|
||||
self._next_new_page_index = None
|
||||
|
||||
self._potential_drag_page = None
|
||||
|
||||
self._closed_pages = []
|
||||
|
||||
self._page_key = HydrusData.GenerateKey()
|
||||
|
@ -726,6 +730,12 @@ class PagesNotebook( wx.Notebook ):
|
|||
self._controller.sub( self, 'RefreshPageName', 'refresh_page_name' )
|
||||
self._controller.sub( self, 'NotifyPageUnclosed', 'notify_page_unclosed' )
|
||||
|
||||
if HC.PLATFORM_WINDOWS:
|
||||
|
||||
self.Bind( wx.EVT_MOTION, self.EventDrag )
|
||||
|
||||
|
||||
self.Bind( wx.EVT_LEFT_DOWN, self.EventLeftDown )
|
||||
self.Bind( wx.EVT_LEFT_DCLICK, self.EventLeftDoubleClick )
|
||||
self.Bind( wx.EVT_MIDDLE_DOWN, self.EventMiddleClick )
|
||||
self.Bind( wx.EVT_RIGHT_DOWN, self.EventMenu )
|
||||
|
@ -819,10 +829,14 @@ class PagesNotebook( wx.Notebook ):
|
|||
|
||||
page.PrepareToHide()
|
||||
|
||||
self._closed_pages.append( ( index, page.GetPageKey() ) )
|
||||
page_key = page.GetPageKey()
|
||||
|
||||
self._closed_pages.append( ( index, page_key ) )
|
||||
|
||||
self.RemovePage( index )
|
||||
|
||||
self._controller.pub( 'refresh_page_name', self._page_key )
|
||||
|
||||
self._controller.pub( 'notify_closed_page', page )
|
||||
self._controller.pub( 'notify_new_undo' )
|
||||
|
||||
|
@ -916,35 +930,6 @@ class PagesNotebook( wx.Notebook ):
|
|||
return results
|
||||
|
||||
|
||||
def _GetNotebookFromMousePosition( self, position ):
|
||||
|
||||
current_page = self.GetCurrentPage()
|
||||
|
||||
if current_page is None or not isinstance( current_page, PagesNotebook ):
|
||||
|
||||
return ( self, position )
|
||||
|
||||
else:
|
||||
|
||||
( tab_index, flags ) = self.HitTest( position )
|
||||
|
||||
if tab_index != wx.NOT_FOUND:
|
||||
|
||||
return ( self, position )
|
||||
|
||||
|
||||
if flags & wx.NB_HITTEST_NOWHERE and flags & wx.NB_HITTEST_ONPAGE: # not on a label but inside my client area
|
||||
|
||||
screen_position = self.ClientToScreen( position )
|
||||
new_position = current_page.ScreenToClient( screen_position )
|
||||
|
||||
return current_page._GetNotebookFromMousePosition( new_position )
|
||||
|
||||
|
||||
|
||||
return ( self, position )
|
||||
|
||||
|
||||
def _GetIndex( self, page_key ):
|
||||
|
||||
for ( page, index ) in ( ( self.GetPage( index ), index ) for index in range( self.GetPageCount() ) ):
|
||||
|
@ -958,11 +943,60 @@ class PagesNotebook( wx.Notebook ):
|
|||
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:
|
||||
|
||||
position = self.ScreenToClient( screen_position )
|
||||
|
||||
( tab_index, flags ) = self.HitTest( position )
|
||||
|
||||
if tab_index != wx.NOT_FOUND:
|
||||
|
||||
return self
|
||||
|
||||
|
||||
if flags & wx.NB_HITTEST_NOWHERE and flags & wx.NB_HITTEST_ONPAGE: # not on a label but inside my client area
|
||||
|
||||
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 _MovePage( self, page_index, delta = None, new_index = None ):
|
||||
|
||||
new_page_index = page_index
|
||||
|
@ -1056,7 +1090,9 @@ class PagesNotebook( wx.Notebook ):
|
|||
|
||||
|
||||
|
||||
def _ShowMenu( self, position ):
|
||||
def _ShowMenu( self, screen_position ):
|
||||
|
||||
position = self.ScreenToClient( screen_position )
|
||||
|
||||
( tab_index, flags ) = self.HitTest( position )
|
||||
|
||||
|
@ -1258,6 +1294,50 @@ class PagesNotebook( wx.Notebook ):
|
|||
|
||||
|
||||
|
||||
def EventDrag( self, event ):
|
||||
|
||||
if event.LeftIsDown() and self._potential_drag_page is not None:
|
||||
|
||||
drop_source = wx.DropSource( self )
|
||||
|
||||
data_object = wx.DataObjectComposite()
|
||||
|
||||
#
|
||||
|
||||
hydrus_page_tab_data_object = wx.CustomDataObject( 'application/hydrus-page-tab' )
|
||||
|
||||
data = self._potential_drag_page.GetPageKey()
|
||||
|
||||
hydrus_page_tab_data_object.SetData( data )
|
||||
|
||||
data_object.Add( hydrus_page_tab_data_object, True )
|
||||
|
||||
#
|
||||
|
||||
drop_source.SetData( data_object )
|
||||
|
||||
drop_source.DoDragDrop()
|
||||
|
||||
self._potential_drag_page = None
|
||||
|
||||
|
||||
|
||||
def EventLeftDown( self, event ):
|
||||
|
||||
position = event.GetPosition()
|
||||
|
||||
( tab_index, flags ) = self.HitTest( position )
|
||||
|
||||
if tab_index != -1:
|
||||
|
||||
page = self.GetPage( tab_index )
|
||||
|
||||
self._potential_drag_page = page
|
||||
|
||||
|
||||
event.Skip()
|
||||
|
||||
|
||||
def EventLeftDoubleClick( self, event ):
|
||||
|
||||
position = event.GetPosition()
|
||||
|
@ -1268,9 +1348,11 @@ class PagesNotebook( wx.Notebook ):
|
|||
|
||||
if flags & wx.NB_HITTEST_NOWHERE and flags & wx.NB_HITTEST_ONPAGE:
|
||||
|
||||
( notebook, new_position ) = self._GetNotebookFromMousePosition( position )
|
||||
screen_position = self.ClientToScreen( position )
|
||||
|
||||
notebook.EventNewPageMousePosition( new_position )
|
||||
notebook = self._GetNotebookFromScreenPosition( screen_position )
|
||||
|
||||
notebook.EventNewPageFromScreenPosition( screen_position )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -1281,27 +1363,33 @@ class PagesNotebook( wx.Notebook ):
|
|||
|
||||
def EventMenu( self, event ):
|
||||
|
||||
self._ShowMenu( event.GetPosition() )
|
||||
screen_position = self.ClientToScreen( event.GetPosition() )
|
||||
|
||||
self._ShowMenu( screen_position )
|
||||
|
||||
|
||||
def EventMenuFromMousePosition( self, position ):
|
||||
def EventMenuFromScreenPosition( self, position ):
|
||||
|
||||
( notebook, new_position ) = self._GetNotebookFromMousePosition( position )
|
||||
notebook = self._GetNotebookFromScreenPosition( position )
|
||||
|
||||
notebook._ShowMenu( position )
|
||||
|
||||
|
||||
def EventMiddleClick( self, event ):
|
||||
|
||||
( tab_index, flags ) = self.HitTest( event.GetPosition() )
|
||||
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:
|
||||
|
||||
( notebook, new_position ) = self._GetNotebookFromMousePosition( event.GetPosition() )
|
||||
screen_position = self.ClientToScreen( position )
|
||||
|
||||
notebook.EventNewPageFromMousePosition( new_position )
|
||||
notebook = self._GetNotebookFromScreenPosition( screen_position )
|
||||
|
||||
notebook.EventNewPageFromScreenPosition( screen_position )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -1314,9 +1402,9 @@ class PagesNotebook( wx.Notebook ):
|
|||
|
||||
|
||||
|
||||
def EventNewPageFromMousePosition( self, position ):
|
||||
def EventNewPageFromScreenPosition( self, position ):
|
||||
|
||||
( notebook, new_position ) = self._GetNotebookFromMousePosition( position )
|
||||
notebook = self._GetNotebookFromScreenPosition( position )
|
||||
|
||||
notebook._ChooseNewPage()
|
||||
|
||||
|
@ -1421,7 +1509,7 @@ class PagesNotebook( wx.Notebook ):
|
|||
|
||||
# import page does not exist
|
||||
|
||||
return self.NewPageImportURLs()
|
||||
return self.NewPageImportURLs( on_deepest_notebook = True )
|
||||
|
||||
|
||||
def GetPageKey( self ):
|
||||
|
@ -1500,27 +1588,39 @@ class PagesNotebook( wx.Notebook ):
|
|||
self.AppendGUISession( name )
|
||||
|
||||
|
||||
def NewPage( self, management_controller, initial_hashes = None, forced_insertion_index = None ):
|
||||
def NewPage( self, management_controller, initial_hashes = None, forced_insertion_index = None, on_deepest_notebook = False ):
|
||||
|
||||
MAX_TOTAL_PAGES = 150
|
||||
current_page = self.GetCurrentPage()
|
||||
|
||||
( total_active_page_count, total_closed_page_count ) = self._controller.gui.GetTotalPageCounts()
|
||||
|
||||
if total_active_page_count + total_closed_page_count >= MAX_TOTAL_PAGES:
|
||||
if on_deepest_notebook and isinstance( current_page, PagesNotebook ):
|
||||
|
||||
self._controller.gui.DeleteAllClosedPages()
|
||||
|
||||
|
||||
if total_active_page_count >= MAX_TOTAL_PAGES:
|
||||
|
||||
HydrusData.ShowText( 'The client cannot have more than ' + str( MAX_TOTAL_PAGES ) + ' pages open! For system stability reasons, please close some now!' )
|
||||
current_page.NewPage( management_controller, initial_hashes = initial_hashes, forced_insertion_index = forced_insertion_index, on_deepest_notebook = on_deepest_notebook )
|
||||
|
||||
return
|
||||
|
||||
|
||||
if total_active_page_count == MAX_TOTAL_PAGES - 5:
|
||||
if not HG.no_page_limit_mode:
|
||||
|
||||
HydrusData.ShowText( 'You have ' + str( total_active_page_count ) + ' pages open! You can only open a few more before system stability is affected! Please close some now!' )
|
||||
MAX_TOTAL_PAGES = 150
|
||||
|
||||
( total_active_page_count, total_closed_page_count ) = self._controller.gui.GetTotalPageCounts()
|
||||
|
||||
if total_active_page_count + total_closed_page_count >= MAX_TOTAL_PAGES:
|
||||
|
||||
self._controller.gui.DeleteAllClosedPages()
|
||||
|
||||
|
||||
if total_active_page_count >= MAX_TOTAL_PAGES:
|
||||
|
||||
HydrusData.ShowText( 'The client cannot have more than ' + str( MAX_TOTAL_PAGES ) + ' pages open! For system stability reasons, please close some now!' )
|
||||
|
||||
return
|
||||
|
||||
|
||||
if total_active_page_count == MAX_TOTAL_PAGES - 5:
|
||||
|
||||
HydrusData.ShowText( 'You have ' + str( total_active_page_count ) + ' pages open! You can only open a few more before system stability is affected! Please close some now!' )
|
||||
|
||||
|
||||
|
||||
self._controller.ResetIdleTimer()
|
||||
|
@ -1563,14 +1663,14 @@ class PagesNotebook( wx.Notebook ):
|
|||
return page
|
||||
|
||||
|
||||
def NewPageDuplicateFilter( self ):
|
||||
def NewPageDuplicateFilter( self, on_deepest_notebook = False ):
|
||||
|
||||
management_controller = ClientGUIManagement.CreateManagementControllerDuplicateFilter()
|
||||
|
||||
return self.NewPage( management_controller )
|
||||
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )
|
||||
|
||||
|
||||
def NewPageImportBooru( self ):
|
||||
def NewPageImportBooru( self, on_deepest_notebook = False ):
|
||||
|
||||
with ClientGUIDialogs.DialogSelectBooru( self ) as dlg:
|
||||
|
||||
|
@ -1578,47 +1678,47 @@ class PagesNotebook( wx.Notebook ):
|
|||
|
||||
gallery_identifier = dlg.GetGalleryIdentifier()
|
||||
|
||||
return self.NewPageImportGallery( gallery_identifier )
|
||||
return self.NewPageImportGallery( gallery_identifier, on_deepest_notebook = on_deepest_notebook )
|
||||
|
||||
|
||||
|
||||
|
||||
def NewPageImportGallery( self, gallery_identifier ):
|
||||
def NewPageImportGallery( self, gallery_identifier, on_deepest_notebook = False ):
|
||||
|
||||
management_controller = ClientGUIManagement.CreateManagementControllerImportGallery( gallery_identifier )
|
||||
|
||||
return self.NewPage( management_controller )
|
||||
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )
|
||||
|
||||
|
||||
def NewPageImportPageOfImages( self ):
|
||||
def NewPageImportPageOfImages( self, on_deepest_notebook = False ):
|
||||
|
||||
management_controller = ClientGUIManagement.CreateManagementControllerImportPageOfImages()
|
||||
|
||||
return self.NewPage( management_controller )
|
||||
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )
|
||||
|
||||
|
||||
def NewPageImportThreadWatcher( self, thread_url = None ):
|
||||
def NewPageImportThreadWatcher( self, thread_url = None, on_deepest_notebook = False ):
|
||||
|
||||
management_controller = ClientGUIManagement.CreateManagementControllerImportThreadWatcher( thread_url )
|
||||
|
||||
return self.NewPage( management_controller )
|
||||
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )
|
||||
|
||||
|
||||
def NewPageImportURLs( self ):
|
||||
def NewPageImportURLs( self, on_deepest_notebook = False ):
|
||||
|
||||
management_controller = ClientGUIManagement.CreateManagementControllerImportURLs()
|
||||
|
||||
return self.NewPage( management_controller )
|
||||
return self.NewPage( management_controller, on_deepest_notebook = on_deepest_notebook )
|
||||
|
||||
|
||||
def NewPagePetitions( self, service_key ):
|
||||
def NewPagePetitions( self, service_key, on_deepest_notebook = False ):
|
||||
|
||||
management_controller = ClientGUIManagement.CreateManagementControllerPetitions( service_key )
|
||||
|
||||
return self.NewPage( management_controller )
|
||||
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 ):
|
||||
def NewPageQuery( self, file_service_key, initial_hashes = None, initial_predicates = None, page_name = None, on_deepest_notebook = False ):
|
||||
|
||||
if initial_hashes is None:
|
||||
|
||||
|
@ -1650,10 +1750,19 @@ class PagesNotebook( wx.Notebook ):
|
|||
|
||||
management_controller = ClientGUIManagement.CreateManagementControllerQuery( page_name, file_service_key, file_search_context, search_enabled )
|
||||
|
||||
return self.NewPage( management_controller, initial_hashes = initial_hashes )
|
||||
return self.NewPage( management_controller, initial_hashes = initial_hashes, on_deepest_notebook = on_deepest_notebook )
|
||||
|
||||
|
||||
def NewPagesNotebook( self, name = 'pages', forced_insertion_index = None ):
|
||||
def NewPagesNotebook( self, name = 'pages', forced_insertion_index = None, on_deepest_notebook = False ):
|
||||
|
||||
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 )
|
||||
|
||||
return
|
||||
|
||||
|
||||
self._controller.ResetIdleTimer()
|
||||
self._controller.ResetPageChangeTimer()
|
||||
|
@ -1732,6 +1841,135 @@ class PagesNotebook( wx.Notebook ):
|
|||
|
||||
|
||||
|
||||
def PageDragAndDropDropped( self, page_key ):
|
||||
|
||||
page = self._GetPageFromPageKey( page_key )
|
||||
|
||||
if page is None:
|
||||
|
||||
return
|
||||
|
||||
|
||||
source_notebook = page.GetParent()
|
||||
|
||||
screen_position = wx.GetMousePosition()
|
||||
|
||||
dest_notebook = self._GetNotebookFromScreenPosition( screen_position )
|
||||
|
||||
( x, y ) = dest_notebook.ScreenToClient( screen_position )
|
||||
|
||||
( tab_index, flags ) = dest_notebook.HitTest( ( x, y ) )
|
||||
|
||||
EDGE_PADDING = 10
|
||||
|
||||
( left_tab_index, gumpf ) = dest_notebook.HitTest( ( x - EDGE_PADDING, y ) )
|
||||
( right_tab_index, gumpf ) = dest_notebook.HitTest( ( 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
|
||||
|
||||
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:
|
||||
|
||||
# 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
|
||||
|
||||
for ( index, p ) in enumerate( source_notebook._GetPages() ):
|
||||
|
||||
if p == page:
|
||||
|
||||
if source_notebook == dest_notebook and index + 1 < insertion_tab_index:
|
||||
|
||||
# we are just about to remove it from earlier in the same list, which shuffles the inserting index up one
|
||||
|
||||
insertion_tab_index -= 1
|
||||
|
||||
|
||||
source_notebook.RemovePage( index )
|
||||
|
||||
break
|
||||
|
||||
|
||||
|
||||
if source_notebook != dest_notebook:
|
||||
|
||||
page.Reparent( dest_notebook )
|
||||
|
||||
|
||||
dest_notebook.InsertPage( insertion_tab_index, page, 'page' )
|
||||
|
||||
self.ShowPage( page )
|
||||
|
||||
self._controller.pub( 'refresh_page_name', source_notebook.GetPageKey() )
|
||||
self._controller.pub( 'refresh_page_name', page.GetPageKey() )
|
||||
|
||||
|
||||
def PrepareToHide( self ):
|
||||
|
||||
for page in self._GetPages():
|
||||
|
|
|
@ -1220,12 +1220,12 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
self._listbook = ClientGUICommon.ListBook( self )
|
||||
|
||||
self._listbook.AddPage( 'gui', 'gui', self._GUIPanel( self._listbook ) ) # leave this at the top, to make it default page
|
||||
self._listbook.AddPage( 'connection', 'connection', self._ConnectionPanel( self._listbook ) )
|
||||
self._listbook.AddPage( 'files and trash', 'files and trash', self._FilesAndTrashPanel( self._listbook ) )
|
||||
self._listbook.AddPage( 'speed and memory', 'speed and memory', self._SpeedAndMemoryPanel( self._listbook, self._new_options ) )
|
||||
self._listbook.AddPage( 'maintenance and processing', 'maintenance and processing', self._MaintenanceAndProcessingPanel( self._listbook ) )
|
||||
self._listbook.AddPage( 'media', 'media', self._MediaPanel( self._listbook ) )
|
||||
self._listbook.AddPage( 'gui', 'gui', self._GUIPanel( self._listbook ) )
|
||||
#self._listbook.AddPage( 'sound', 'sound', self._SoundPanel( self._listbook ) )
|
||||
self._listbook.AddPage( 'default file system predicates', 'default file system predicates', self._DefaultFileSystemPredicatesPanel( self._listbook, self._new_options ) )
|
||||
self._listbook.AddPage( 'default tag import options', 'default tag import options', self._DefaultTagImportOptionsPanel( self._listbook, self._new_options ) )
|
||||
|
@ -1663,6 +1663,9 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._external_host = wx.TextCtrl( self )
|
||||
self._external_host.SetToolTipString( 'If you have trouble parsing your external ip using UPnP, you can force it to be this.' )
|
||||
|
||||
self._network_timeout = wx.SpinCtrl( self, min = 3, max = 300 )
|
||||
self._network_timeout.SetToolTipString( 'If a network connection experiences any uninterrupted inactivity for this duration, it will throw an error.' )
|
||||
|
||||
proxy_panel = ClientGUICommon.StaticBox( self, 'proxy settings' )
|
||||
|
||||
self._proxy_type = ClientGUICommon.BetterChoice( proxy_panel )
|
||||
|
@ -1680,6 +1683,10 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._external_host.SetValue( HC.options[ 'external_host' ] )
|
||||
|
||||
|
||||
self._new_options = HG.client_controller.GetNewOptions()
|
||||
|
||||
self._network_timeout.SetValue( self._new_options.GetInteger( 'network_timeout' ) )
|
||||
|
||||
self._proxy_type.Append( 'http', 'http' )
|
||||
self._proxy_type.Append( 'socks4', 'socks4' )
|
||||
self._proxy_type.Append( 'socks5', 'socks5' )
|
||||
|
@ -1730,11 +1737,14 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
proxy_panel.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
||||
|
||||
proxy_panel.Hide() # proxy settings no longer in use for new engine
|
||||
|
||||
#
|
||||
|
||||
rows = []
|
||||
|
||||
rows.append( ( 'external ip/host override: ', self._external_host ) )
|
||||
rows.append( ( 'network timeout (seconds): ', self._network_timeout ) )
|
||||
|
||||
gridbox = ClientGUICommon.WrapInGrid( self, rows )
|
||||
|
||||
|
@ -1775,6 +1785,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
HC.options[ 'external_host' ] = external_host
|
||||
|
||||
self._new_options.SetInteger( 'network_timeout', self._network_timeout.GetValue() )
|
||||
|
||||
|
||||
|
||||
class _DownloadingPanel( wx.Panel ):
|
||||
|
@ -1871,7 +1883,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
self._jobs_panel = ClientGUICommon.StaticBox( self, 'when to run high cpu jobs' )
|
||||
self._maintenance_panel = ClientGUICommon.StaticBox( self, 'maintenance period' )
|
||||
self._processing_panel = ClientGUICommon.StaticBox( self, 'processing' )
|
||||
|
||||
self._idle_panel = ClientGUICommon.StaticBox( self._jobs_panel, 'idle' )
|
||||
self._shutdown_panel = ClientGUICommon.StaticBox( self._jobs_panel, 'shutdown' )
|
||||
|
@ -1904,11 +1915,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
#
|
||||
|
||||
self._processing_phase = wx.SpinCtrl( self._processing_panel, min = 0, max = 100000 )
|
||||
self._processing_phase.SetToolTipString( 'how long this client will delay processing updates after they are due. useful if you have multiple clients and do not want them to process at the same time' )
|
||||
|
||||
#
|
||||
|
||||
self._idle_normal.SetValue( HC.options[ 'idle_normal' ] )
|
||||
self._idle_period.SetValue( HC.options[ 'idle_period' ] )
|
||||
self._idle_mouse_period.SetValue( HC.options[ 'idle_mouse_period' ] )
|
||||
|
@ -1919,8 +1925,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
self._maintenance_vacuum_period_days.SetValue( self._new_options.GetNoneableInteger( 'maintenance_vacuum_period_days' ) )
|
||||
|
||||
self._processing_phase.SetValue( HC.options[ 'processing_phase' ] )
|
||||
|
||||
#
|
||||
|
||||
rows = []
|
||||
|
@ -1975,21 +1979,10 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
#
|
||||
|
||||
rows = []
|
||||
|
||||
rows.append( ( 'Delay repository update processing by (s): ', self._processing_phase ) )
|
||||
|
||||
gridbox = ClientGUICommon.WrapInGrid( self._processing_panel, rows )
|
||||
|
||||
self._processing_panel.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
|
||||
#
|
||||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
vbox.AddF( self._jobs_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
vbox.AddF( self._maintenance_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
vbox.AddF( self._processing_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
self.SetSizer( vbox )
|
||||
|
||||
|
@ -2046,8 +2039,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
HC.options[ 'idle_shutdown' ] = self._idle_shutdown.GetChoice()
|
||||
HC.options[ 'idle_shutdown_max_minutes' ] = self._idle_shutdown_max_minutes.GetValue()
|
||||
|
||||
HC.options[ 'processing_phase' ] = self._processing_phase.GetValue()
|
||||
|
||||
self._new_options.SetNoneableInteger( 'maintenance_vacuum_period_days', self._maintenance_vacuum_period_days.GetValue() )
|
||||
|
||||
|
||||
|
@ -2563,7 +2554,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._disable_cv_for_gifs.SetToolTipString( 'OpenCV is good at rendering gifs, but if you have problems with it and your graphics card, check this and the less reliable and slower PIL will be used instead.' )
|
||||
|
||||
self._load_images_with_pil = wx.CheckBox( self )
|
||||
self._load_images_with_pil.SetToolTipString( 'OpenCV is much faster than PIL, but the current release crashes on certain images. You can try turning this off, but switch it back on if you have any problems.' )
|
||||
self._load_images_with_pil.SetToolTipString( 'OpenCV is much faster than PIL, but it is sometimes less reliable. Switch this on if you experience crashes or other unusual problems while importing or viewing certain images.' )
|
||||
|
||||
self._use_system_ffmpeg = wx.CheckBox( self )
|
||||
self._use_system_ffmpeg.SetToolTipString( 'Check this to always default to the system ffmpeg in your path, rather than using the static ffmpeg in hydrus\'s bin directory. (requires restart)' )
|
||||
|
@ -6093,14 +6084,30 @@ class RepairFileSystemPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
ClientGUIScrolledPanels.ManagePanel.__init__( self, parent )
|
||||
|
||||
self._only_thumbs = True
|
||||
|
||||
for ( incorrect_location, prefix ) in missing_locations:
|
||||
|
||||
if prefix.startswith( 'f' ):
|
||||
|
||||
self._only_thumbs = False
|
||||
|
||||
|
||||
|
||||
text = 'This dialog has launched because some expected file storage directories were not found. This is a serious error. You have two options:'
|
||||
text += os.linesep * 2
|
||||
text += '1) If you know what these should be (e.g. you recently remapped their external drive to another location), update the paths here manually. For most users, this will likely be a simple ctrl+a->correct, but if you have a more complicated system or store your thumbnails different to your files, make sure you skim the whole list. Check everything reports _ok!_'
|
||||
text += os.linesep * 2
|
||||
text += 'Then hit \'apply\', and the client will launch. You should double-check your \'preferred\' file storage locations under options->file storage locations immediately.'
|
||||
text += 'Then hit \'apply\', and the client will launch. You should double-check your \'preferred\' file storage locations under database->migrate database immediately.'
|
||||
text += os.linesep * 2
|
||||
text += '2) If the locations are not available, or you do not know what they should be, or you wish to fix this outside of the program, hit \'cancel\' to gracefully cancel client boot. Feel free to contact hydrus dev for help.'
|
||||
|
||||
if self._only_thumbs:
|
||||
|
||||
text += os.linesep * 2
|
||||
text += 'SPECIAL NOTE FOR YOUR SITUATION: The only paths missing are thumbnail paths. If you cannot recover these folders, you can hit apply to create empty paths at the original or corrected locations and then run a maintenance routine to regenerate the thumbnails from their originals.'
|
||||
|
||||
|
||||
st = ClientGUICommon.BetterStaticText( self, text )
|
||||
|
||||
st.Wrap( 640 )
|
||||
|
@ -6120,6 +6127,8 @@ class RepairFileSystemPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._locations.Append( t, t )
|
||||
|
||||
|
||||
# sort by prefix
|
||||
|
||||
#self._locations.SortListItems( 1 ) # subdirs secondary
|
||||
#self._locations.SortListItems( 0 ) # missing location primary
|
||||
|
||||
|
@ -6170,29 +6179,67 @@ class RepairFileSystemPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
def CommitChanges( self ):
|
||||
|
||||
user_was_warned = False
|
||||
|
||||
correct_rows = []
|
||||
|
||||
for ( incorrect_location, prefix, correct_location, ok ) in self._locations.GetClientData():
|
||||
if self._only_thumbs:
|
||||
|
||||
if correct_location == '':
|
||||
problems = False
|
||||
|
||||
for ( incorrect_location, prefix, correct_location, ok ) in self._locations.GetClientData():
|
||||
|
||||
wx.MessageBox( 'You did not correct all the locations!' )
|
||||
|
||||
raise HydrusExceptions.VetoException()
|
||||
|
||||
elif ok != 'ok!':
|
||||
|
||||
wx.MessageBox( 'You did not find all the correct locations!' )
|
||||
|
||||
raise HydrusExceptions.VetoException()
|
||||
|
||||
else:
|
||||
if correct_location == '':
|
||||
|
||||
problems = True
|
||||
|
||||
correct_location = incorrect_location
|
||||
|
||||
elif ok != 'ok!':
|
||||
|
||||
problems = True
|
||||
|
||||
|
||||
correct_rows.append( ( incorrect_location, prefix, correct_location ) )
|
||||
|
||||
|
||||
if problems:
|
||||
|
||||
message = 'Some or all of your incorrect paths have not been corrected, but they are all thumbnail paths.'
|
||||
message += os.linesep * 2
|
||||
message += 'Would you like instead to create new empty folders at the previous (or corrected, if you have entered them) locations?'
|
||||
message += os.linesep * 2
|
||||
message += 'You can run database->regenerate->all thumbnails to fill them up again.'
|
||||
|
||||
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
|
||||
|
||||
if dlg.ShowModal() != wx.ID_YES:
|
||||
|
||||
raise HydrusExceptions.VetoException()
|
||||
|
||||
|
||||
|
||||
|
||||
else:
|
||||
|
||||
for ( incorrect_location, prefix, correct_location, ok ) in self._locations.GetClientData():
|
||||
|
||||
if correct_location == '':
|
||||
|
||||
wx.MessageBox( 'You did not correct all the locations!' )
|
||||
|
||||
raise HydrusExceptions.VetoException()
|
||||
|
||||
elif ok != 'ok!':
|
||||
|
||||
wx.MessageBox( 'You did not find all the correct locations!' )
|
||||
|
||||
raise HydrusExceptions.VetoException()
|
||||
|
||||
else:
|
||||
|
||||
correct_rows.append( ( incorrect_location, prefix, correct_location ) )
|
||||
|
||||
|
||||
|
||||
|
||||
HG.client_controller.WriteSynchronous( 'repair_client_files', correct_rows )
|
||||
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
import numpy.core.multiarray # important this comes before cv!
|
||||
import ClientConstants as CC
|
||||
import cv2
|
||||
import HydrusConstants as HC
|
||||
import HydrusImageHandling
|
||||
import HydrusGlobals as HG
|
||||
|
||||
if cv2.__version__.startswith( '2' ):
|
||||
|
||||
IMREAD_UNCHANGED = cv2.CV_LOAD_IMAGE_UNCHANGED
|
||||
CV_IMREAD_FLAGS_SUPPORTS_ALPHA = cv2.CV_LOAD_IMAGE_UNCHANGED
|
||||
CV_IMREAD_FLAGS_SUPPORTS_EXIF_REORIENTATION = cv2.CV_LOAD_IMAGE_ANYDEPTH | cv2.CV_LOAD_IMAGE_ANYCOLOR
|
||||
|
||||
else:
|
||||
|
||||
IMREAD_UNCHANGED = cv2.IMREAD_UNCHANGED
|
||||
CV_IMREAD_FLAGS_SUPPORTS_ALPHA = cv2.IMREAD_UNCHANGED
|
||||
CV_IMREAD_FLAGS_SUPPORTS_EXIF_REORIENTATION = cv2.IMREAD_ANYDEPTH | cv2.IMREAD_ANYCOLOR # this preserves colour info but does EXIF reorientation and flipping
|
||||
|
||||
cv_interpolation_enum_lookup = {}
|
||||
|
||||
|
@ -41,7 +44,7 @@ def EfficientlyThumbnailNumpyImage( numpy_image, ( target_x, target_y ) ):
|
|||
|
||||
return cv2.resize( numpy_image, ( target_x, target_y ), interpolation = cv2.INTER_AREA )
|
||||
|
||||
def GenerateNumpyImage( path ):
|
||||
def GenerateNumpyImage( path, mime ):
|
||||
|
||||
if HG.client_controller.GetNewOptions().GetBoolean( 'load_images_with_pil' ):
|
||||
|
||||
|
@ -54,7 +57,16 @@ def GenerateNumpyImage( path ):
|
|||
|
||||
else:
|
||||
|
||||
numpy_image = cv2.imread( path, flags = IMREAD_UNCHANGED )
|
||||
if mime == HC.IMAGE_JPEG:
|
||||
|
||||
flags = CV_IMREAD_FLAGS_SUPPORTS_EXIF_REORIENTATION
|
||||
|
||||
else:
|
||||
|
||||
flags = CV_IMREAD_FLAGS_SUPPORTS_ALPHA
|
||||
|
||||
|
||||
numpy_image = cv2.imread( path, flags = flags )
|
||||
|
||||
if numpy_image is None: # doesn't support static gifs and some random other stuff
|
||||
|
||||
|
@ -109,9 +121,9 @@ def GenerateNumPyImageFromPILImage( pil_image ):
|
|||
|
||||
return numpy.fromstring( s, dtype = 'uint8' ).reshape( ( h, w, len( s ) // ( w * h ) ) )
|
||||
|
||||
def GenerateShapePerceptualHashes( path ):
|
||||
def GenerateShapePerceptualHashes( path, mime ):
|
||||
|
||||
numpy_image = GenerateNumpyImage( path )
|
||||
numpy_image = GenerateNumpyImage( path, mime )
|
||||
|
||||
( y, x, depth ) = numpy_image.shape
|
||||
|
||||
|
|
|
@ -365,7 +365,7 @@ class FileImportJob( object ):
|
|||
|
||||
if mime in HC.MIMES_WE_CAN_PHASH:
|
||||
|
||||
self._phashes = ClientImageHandling.GenerateShapePerceptualHashes( self._temp_path )
|
||||
self._phashes = ClientImageHandling.GenerateShapePerceptualHashes( self._temp_path, mime )
|
||||
|
||||
|
||||
self._extra_hashes = HydrusFileHandling.GetExtraHashesFromPath( self._temp_path )
|
||||
|
|
|
@ -1793,7 +1793,14 @@ class NetworkEngine( object ):
|
|||
self._jobs_downloading = filter( ProcessDownloadingJob, self._jobs_downloading )
|
||||
|
||||
|
||||
self._new_work_to_do.wait( 1 )
|
||||
# we want to catch the rollover of the second for bandwidth jobs
|
||||
|
||||
now_with_subsecond = time.time()
|
||||
subsecond_part = now_with_subsecond % 1
|
||||
|
||||
time_until_next_second = 1.0 - subsecond_part
|
||||
|
||||
self._new_work_to_do.wait( time_until_next_second )
|
||||
|
||||
self._new_work_to_do.clear()
|
||||
|
||||
|
@ -1900,7 +1907,9 @@ class NetworkJob( object ):
|
|||
self._status_text = u'sending request\u2026'
|
||||
|
||||
|
||||
response = session.request( method, url, data = data, headers = headers, stream = True, timeout = 10 )
|
||||
timeout = HG.client_controller.GetNewOptions().GetInteger( 'network_timeout' )
|
||||
|
||||
response = session.request( method, url, data = data, headers = headers, stream = True, timeout = timeout )
|
||||
|
||||
connection_successful = True
|
||||
|
||||
|
@ -2083,7 +2092,7 @@ class NetworkJob( object ):
|
|||
|
||||
waiting_duration = self.engine.bandwidth_manager.GetWaitingEstimate( self._network_contexts )
|
||||
|
||||
if waiting_duration < 1:
|
||||
if waiting_duration < 2:
|
||||
|
||||
self._status_text = u'bandwidth free imminently\u2026'
|
||||
|
||||
|
|
|
@ -14,9 +14,9 @@ import threading
|
|||
import time
|
||||
import wx
|
||||
|
||||
def GenerateHydrusBitmap( path, compressed = True ):
|
||||
def GenerateHydrusBitmap( path, mime, compressed = True ):
|
||||
|
||||
numpy_image = ClientImageHandling.GenerateNumpyImage( path )
|
||||
numpy_image = ClientImageHandling.GenerateNumpyImage( path, mime )
|
||||
|
||||
return GenerateHydrusBitmapFromNumPyImage( numpy_image, compressed = compressed )
|
||||
|
||||
|
@ -57,12 +57,12 @@ class ImageRenderer( object ):
|
|||
self._media = media
|
||||
self._numpy_image = None
|
||||
|
||||
hash = self._media.GetHash()
|
||||
mime = self._media.GetMime()
|
||||
self._hash = self._media.GetHash()
|
||||
self._mime = self._media.GetMime()
|
||||
|
||||
client_files_manager = HG.client_controller.client_files_manager
|
||||
|
||||
self._path = client_files_manager.GetFilePath( hash, mime )
|
||||
self._path = client_files_manager.GetFilePath( self._hash, self._mime )
|
||||
|
||||
HG.client_controller.CallToThread( self._Initialise )
|
||||
|
||||
|
@ -71,7 +71,7 @@ class ImageRenderer( object ):
|
|||
|
||||
time.sleep( 0.00001 )
|
||||
|
||||
self._numpy_image = ClientImageHandling.GenerateNumpyImage( self._path )
|
||||
self._numpy_image = ClientImageHandling.GenerateNumpyImage( self._path, self._mime )
|
||||
|
||||
|
||||
def GetEstimatedMemoryFootprint( self ):
|
||||
|
|
|
@ -49,7 +49,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 18
|
||||
SOFTWARE_VERSION = 269
|
||||
SOFTWARE_VERSION = 270
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ gui_report_mode = False
|
|||
network_report_mode = False
|
||||
pubsub_profile_mode = False
|
||||
force_idle_mode = False
|
||||
no_page_limit_mode = False
|
||||
server_busy = False
|
||||
|
||||
do_idle_shutdown_work = False
|
||||
|
|
|
@ -205,7 +205,10 @@ def GetResolutionAndNumFrames( path ):
|
|||
except: break
|
||||
|
||||
|
||||
except: num_frames = 1
|
||||
except:
|
||||
|
||||
num_frames = 1
|
||||
|
||||
|
||||
return ( ( x, y ), num_frames )
|
||||
|
||||
|
|
|
@ -429,14 +429,35 @@ class BandwidthTracker( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
( window, counter ) = self._GetWindowAndCounter( bandwidth_type, time_delta )
|
||||
|
||||
# we need the 'window' because this tracks brackets from the first timestamp and we want to include if 'since' lands anywhere in the bracket
|
||||
# e.g. if it is 1200 and we want the past 1,000, we also need the bracket starting at 0, which will include 200-999
|
||||
|
||||
time_delta += window
|
||||
|
||||
since = HydrusData.GetNow() - time_delta
|
||||
|
||||
return sum( ( value for ( key, value ) in counter.items() if key >= since ) )
|
||||
if time_delta == 1:
|
||||
|
||||
# the case of 1 poses a problem as our min block width is also 1. we can't have a window of 0.1s to make the transition smooth
|
||||
# if we include the last second's data in an effort to span the whole previous 1000ms, we end up not doing anything until the next second rolls over
|
||||
# this causes 50% consumption as we consume in the second after the one we verified was clear
|
||||
# so, let's just check the current second and be happy with it
|
||||
|
||||
now = HydrusData.GetNow()
|
||||
|
||||
if now in counter:
|
||||
|
||||
return counter[ now ]
|
||||
|
||||
else:
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
else:
|
||||
|
||||
# we need the 'window' because this tracks brackets from the first timestamp and we want to include if 'since' lands anywhere in the bracket
|
||||
# e.g. if it is 1200 and we want the past 1,000, we also need the bracket starting at 0, which will include 200-999
|
||||
|
||||
search_time_delta = time_delta + window
|
||||
|
||||
since = HydrusData.GetNow() - search_time_delta
|
||||
|
||||
return sum( ( value for ( timestamp, value ) in counter.items() if timestamp >= since ) )
|
||||
|
||||
|
||||
|
||||
def _GetTimes( self, dt ):
|
||||
|
@ -615,6 +636,8 @@ class BandwidthTracker( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
( window, counter ) = self._GetWindowAndCounter( bandwidth_type, time_delta )
|
||||
|
||||
time_delta_in_which_bandwidth_counts = time_delta + window
|
||||
|
||||
time_and_values = counter.items()
|
||||
|
||||
time_and_values.sort( reverse = True )
|
||||
|
@ -626,7 +649,7 @@ class BandwidthTracker( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
current_search_time_delta = now - timestamp
|
||||
|
||||
if current_search_time_delta > time_delta: # we are searching beyond our time delta. no need to wait
|
||||
if current_search_time_delta > time_delta_in_which_bandwidth_counts: # we are searching beyond our time delta. no need to wait
|
||||
|
||||
break
|
||||
|
||||
|
@ -635,7 +658,7 @@ class BandwidthTracker( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
if usage >= max_allowed:
|
||||
|
||||
return time_delta - current_search_time_delta
|
||||
return time_delta_in_which_bandwidth_counts - current_search_time_delta
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class TestImageHandling( unittest.TestCase ):
|
|||
|
||||
def test_phash( self ):
|
||||
|
||||
phashes = ClientImageHandling.GenerateShapePerceptualHashes( os.path.join( HC.STATIC_DIR, 'hydrus.png' ) )
|
||||
phashes = ClientImageHandling.GenerateShapePerceptualHashes( os.path.join( HC.STATIC_DIR, 'hydrus.png' ), HC.IMAGE_PNG )
|
||||
|
||||
self.assertEqual( phashes, set( [ '\xb4M\xc7\xb2M\xcb8\x1c' ] ) )
|
||||
|
||||
|
|
Loading…
Reference in New Issue