Version 212
This commit is contained in:
parent
aae8d9cc97
commit
0beb3c6e62
|
@ -44,11 +44,11 @@
|
|||
<p>A user told me he had scraped all of danbooru's tags, and we got to discussing if his data could be integrated with hydrus.</p>
|
||||
<p>There are some technical limitations that prohibit rawly importing that data into a hydrus tag repository (our different systems use different file hash standards), but I figured it would be possible to import his data into a hydrus <i>client</i>, at least for any locally stored files.</p>
|
||||
<p>I developed <i>hydrus tag archives</i>, which are just efficiently indexed databases of hashes and tags. They are normal files, external to hydrus, that you can ftp or move around like any other. Putting a tag archive in the right directory in your client's database allows you to <i>synchronise</i> with that archive, importing whatever tags it thinks your files should have to whatever tag service you want.</p>
|
||||
<p>The folder to place archives is <i>install_dir/db/client_archives</i>. You can get some archives other users have created <a href="https://www.mediafire.com/folder/yoy1dx6or0tnr/tag_archives">here</a>.</p>
|
||||
<p>Once you have put something in your archive folder, start the client, and then go <i>services->manage services</i> and browse to your local tag service or a remote tag repository. You should see a box for managing tag archives.</p>
|
||||
<p>To sync with an archive, go <i>services->manage services</i> and browse to your local tag service or a remote tag repository:</p>
|
||||
<p><img src="tag_archives.png" /></p>
|
||||
<p>Tag archive sync works a bit like the import tag options when you download from a booru or other gallery service; you select the namespaces the archive offers that you are interested in, and when you click OK, the client will search the archive for your local files. It will fetch the archive's tags for those files, filter according to your namespace selection, and then add or pend the tags as appropriate, just as if you had entered them in the manage tags dialog.</p>
|
||||
<p>Tag archive sync works a bit like the import tag options when you download from a booru or other gallery; you select which namespaces the archive offers that you are interested in, and when you click OK, the client will check if the archive has any tags for your local files. If so, it will filter them according to your namespace selection and then add or pend the tags to that tag service, just as if you had entered them in the manage tags dialog.</p>
|
||||
<p>New files that you import will also be checked against your archive syncs, keeping you up to date.</p>
|
||||
<p>You can get some archives users have created <a href="https://www.mediafire.com/folder/yoy1dx6or0tnr/tag_archives">here</a>.</p>
|
||||
<p>Be careful with this tool! If you have tens of thousands of files and sync with an archive that has tens of millions of mappings, you may end up adding a whole lot of tags. It may take several minutes to initialise the sync as well. Make sure your namespaces are set exactly how you want before you click OK on the dialog.</p>
|
||||
<h3>custom filter</h3>
|
||||
<p>Once you are comfortable with the client's tagging and rating, you may be interested in performing a <i>custom filter</i>, which is essentially the fullscreen browser with custom shortcuts. You select it from the regular thumbnail right-click menu. First, it will show you a dialog:</p>
|
||||
|
|
|
@ -8,6 +8,27 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 212</h3></li>
|
||||
<ul>
|
||||
<li>wrote a neat flexible system for recording and restoring a variety of window size and position information</li>
|
||||
<li>moved the manage tags dialog to the new size and position system</li>
|
||||
<li>moved the main gui and the media viewer to the new size and position system</li>
|
||||
<li>the manage tags window launched from a media viewer is now a non-modal frame, allowing interation with the underlying media viewer while floating on top of it</li>
|
||||
<li>the new manage tags frame commits changes immediately!</li>
|
||||
<li>review services and file import status will now stay on top of the main gui</li>
|
||||
<li>fixed some bad advanced content update dialog launch code</li>
|
||||
<li>the hydrus tag archives in hta syncs can now be anywhere, not just in the client_archives folder</li>
|
||||
<li>whole bunch of hta sync cleanup</li>
|
||||
<li>paletted images are now dequantized to RGB(A) before thumbnail generation, enhancing scale quality</li>
|
||||
<li>'LA' (greyscale+transparency) images will now render with correct transparency</li>
|
||||
<li>png files will now always generate png thumbnails (previously it was just ones with transparency)</li>
|
||||
<li>trivial options changes are less frequently saved</li>
|
||||
<li>rule34@booru.org now parses namespaced tags</li>
|
||||
<li>fixed a bug in thumbnail resize error recovery code</li>
|
||||
<li>fixed a bug in file repository petitions</li>
|
||||
<li>updated to Pillow 3.2.0</li>
|
||||
<li>misc cleanup</li>
|
||||
</ul>
|
||||
<li><h3>version 211</h3></li>
|
||||
<ul>
|
||||
<li>added options for disk cache init and maintenance to 'speed and memory' page of options</li>
|
||||
|
|
|
@ -773,7 +773,7 @@ class ClientFilesManager( object ):
|
|||
|
||||
try:
|
||||
|
||||
HydrusPaths.DeletePath( path )
|
||||
HydrusPaths.DeletePath( full_size_path )
|
||||
|
||||
except:
|
||||
|
||||
|
|
|
@ -1384,10 +1384,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
HydrusPaths.MirrorFile( source, dest )
|
||||
|
||||
|
||||
job_key.SetVariable( 'popup_text_1', 'copying archives directory' )
|
||||
|
||||
HydrusPaths.MirrorTree( os.path.join( self._db_dir, 'client_archives' ), os.path.join( path, 'client_archives' ) )
|
||||
|
||||
job_key.SetVariable( 'popup_text_1', 'copying files directory' )
|
||||
|
||||
HydrusPaths.MirrorTree( client_files_default, os.path.join( path, 'client_files' ) )
|
||||
|
@ -1956,8 +1952,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
self._subscriptions_cache = {}
|
||||
self._service_cache = {}
|
||||
|
||||
self._tag_archives = {}
|
||||
|
||||
|
||||
def _CreateDB( self ):
|
||||
|
||||
|
@ -1965,16 +1959,21 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
client_files_default = os.path.join( self._db_dir, 'client_files' )
|
||||
|
||||
if not os.path.exists( client_files_default ): os.makedirs( client_files_default )
|
||||
if not os.path.exists( client_files_default ):
|
||||
|
||||
os.makedirs( client_files_default )
|
||||
|
||||
|
||||
other_dirs = []
|
||||
|
||||
other_dirs.append( os.path.join( self._db_dir, 'client_archives' ) )
|
||||
other_dirs.append( os.path.join( self._db_dir, 'client_updates' ) )
|
||||
|
||||
for path in other_dirs:
|
||||
|
||||
if not os.path.exists( path ): os.makedirs( path )
|
||||
if not os.path.exists( path ):
|
||||
|
||||
os.makedirs( path )
|
||||
|
||||
|
||||
|
||||
HydrusDB.SetupDBCreatePragma( self._c, no_wal = self._no_wal )
|
||||
|
@ -4240,7 +4239,9 @@ class DB( HydrusDB.HydrusDB ):
|
|||
return media_result
|
||||
|
||||
|
||||
petitioned = [ ( hash_ids, reason ) for ( reason, hash_ids ) in HydrusData.BuildKeyToListDict( self._c.execute( 'SELECT reason, hash_id FROM reasons, file_petitions USING ( reason_id ) WHERE service_id = ? ORDER BY reason_id LIMIT 100;', ( service_id, ) ) ).items() ]
|
||||
petitioned = [ ( hash_ids, reason_id ) for ( reason_id, hash_ids ) in HydrusData.BuildKeyToListDict( self._c.execute( 'SELECT reason_id, hash_id FROM file_petitions WHERE service_id = ? ORDER BY reason_id LIMIT 100;', ( service_id, ) ) ).items() ]
|
||||
|
||||
petitioned = [ ( hash_ids, self._GetText( reason_id ) ) for ( hash_ids, reason_id ) in petitioned ]
|
||||
|
||||
if len( petitioned ) > 0:
|
||||
|
||||
|
@ -4566,49 +4567,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
return site_id
|
||||
|
||||
|
||||
def _GetTagArchiveInfo( self ):
|
||||
|
||||
return { archive_name : hta.GetNamespaces() for ( archive_name, ( hta_path, hta ) ) in self._tag_archives.items() }
|
||||
|
||||
|
||||
def _GetTagArchiveTags( self, hashes ):
|
||||
|
||||
result = {}
|
||||
|
||||
for ( archive_name, ( hta_path, hta ) ) in self._tag_archives.items():
|
||||
|
||||
hash_type = hta.GetHashType()
|
||||
|
||||
sha256_to_archive_hashes = {}
|
||||
|
||||
if hash_type == HydrusTagArchive.HASH_TYPE_SHA256:
|
||||
|
||||
sha256_to_archive_hashes = { hash : hash for hash in hashes }
|
||||
|
||||
else:
|
||||
|
||||
if hash_type == HydrusTagArchive.HASH_TYPE_MD5: h = 'md5'
|
||||
elif hash_type == HydrusTagArchive.HASH_TYPE_SHA1: h = 'sha1'
|
||||
elif hash_type == HydrusTagArchive.HASH_TYPE_SHA512: h = 'sha512'
|
||||
|
||||
for hash in hashes:
|
||||
|
||||
hash_id = self._GetHashId( hash )
|
||||
|
||||
( archive_hash, ) = self._c.execute( 'SELECT ' + h + ' FROM local_hashes WHERE hash_id = ?;', ( hash_id, ) ).fetchone()
|
||||
|
||||
sha256_to_archive_hashes[ hash ] = archive_hash
|
||||
|
||||
|
||||
|
||||
hashes_to_tags = { hash : hta.GetTags( sha256_to_archive_hashes[ hash ] ) for hash in hashes }
|
||||
|
||||
result[ archive_name ] = hashes_to_tags
|
||||
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _GetTagCensorship( self, service_key = None ):
|
||||
|
||||
if service_key is None:
|
||||
|
@ -4995,17 +4953,14 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
tag_archive_sync = info[ 'tag_archive_sync' ]
|
||||
|
||||
for ( archive_name, namespaces ) in tag_archive_sync.items():
|
||||
for ( portable_hta_path, namespaces ) in tag_archive_sync.items():
|
||||
|
||||
if archive_name in self._tag_archives:
|
||||
|
||||
( hta_path, hta ) = self._tag_archives[ archive_name ]
|
||||
|
||||
adding = True
|
||||
|
||||
try: self._SyncHashesToTagArchive( [ hash ], hta_path, service_key, adding, namespaces )
|
||||
except: pass
|
||||
|
||||
hta_path = HydrusPaths.ConvertPortablePathToAbsPath( portable_hta_path )
|
||||
|
||||
adding = True
|
||||
|
||||
try: self._SyncHashesToTagArchive( [ hash ], hta_path, service_key, adding, namespaces )
|
||||
except: pass
|
||||
|
||||
|
||||
|
||||
|
@ -5030,40 +4985,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
def _InitArchives( self ):
|
||||
|
||||
self._tag_archives = {}
|
||||
|
||||
archives_dir = os.path.join( self._db_dir, 'client_archives' )
|
||||
|
||||
if not os.path.exists( archives_dir ):
|
||||
|
||||
os.makedirs( archives_dir )
|
||||
|
||||
|
||||
for filename in os.listdir( archives_dir ):
|
||||
|
||||
if filename.endswith( '.db' ):
|
||||
|
||||
try:
|
||||
|
||||
hta_path = os.path.join( archives_dir, filename )
|
||||
|
||||
hta = HydrusTagArchive.HydrusTagArchive( hta_path )
|
||||
|
||||
archive_name = filename[:-3]
|
||||
|
||||
self._tag_archives[ archive_name ] = ( hta_path, hta )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
HydrusData.ShowText( 'An archive failed to load on boot.' )
|
||||
HydrusData.ShowException( e )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _InitCaches( self ):
|
||||
|
||||
new_options = self._GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS )
|
||||
|
@ -5094,8 +5015,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
self._inbox_hash_ids = { id for ( id, ) in self._c.execute( 'SELECT hash_id FROM file_inbox;' ) }
|
||||
|
||||
self._InitArchives()
|
||||
|
||||
|
||||
def _InitExternalDatabases( self ):
|
||||
|
||||
|
@ -6037,9 +5956,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
def _Read( self, action, *args, **kwargs ):
|
||||
|
||||
if action == 'tag_archive_info': result = self._GetTagArchiveInfo( *args, **kwargs )
|
||||
elif action == 'tag_archive_tags': result = self._GetTagArchiveTags( *args, **kwargs )
|
||||
elif action == 'autocomplete_predicates': result = self._GetAutocompletePredicates( *args, **kwargs )
|
||||
if action == 'autocomplete_predicates': result = self._GetAutocompletePredicates( *args, **kwargs )
|
||||
elif action == 'client_files_locations': result = self._GetClientFilesLocations( *args, **kwargs )
|
||||
elif action == 'downloads': result = self._GetDownloads( *args, **kwargs )
|
||||
elif action == 'file_hashes': result = self._GetFileHashes( *args, **kwargs )
|
||||
|
@ -6381,20 +6298,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
def _SyncHashesToTagArchive( self, hashes, hta_path, tag_service_key, adding, namespaces ):
|
||||
|
||||
hta = None
|
||||
|
||||
for ( potential_hta_path, potential_hta ) in self._tag_archives.items():
|
||||
|
||||
if hta_path == potential_hta_path:
|
||||
|
||||
hta = potential_hta
|
||||
|
||||
|
||||
|
||||
if hta is None:
|
||||
|
||||
hta = HydrusTagArchive.HydrusTagArchive( hta_path )
|
||||
|
||||
hta = HydrusTagArchive.HydrusTagArchive( hta_path )
|
||||
|
||||
hash_type = hta.GetHashType()
|
||||
|
||||
|
@ -7813,6 +7717,38 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
if version == 211:
|
||||
|
||||
self._c.execute( 'REPLACE INTO yaml_dumps VALUES ( ?, ?, ? );', ( YAML_DUMP_ID_REMOTE_BOORU, 'rule34@booru.org', ClientDefaults.GetDefaultBoorus()[ 'rule34@booru.org' ] ) )
|
||||
|
||||
#
|
||||
|
||||
try:
|
||||
|
||||
service_data = self._c.execute( 'SELECT service_id, info FROM services;' ).fetchall()
|
||||
|
||||
client_archives_folder = os.path.join( self._db_dir, 'client_archives' )
|
||||
|
||||
for ( service_id, info ) in service_data:
|
||||
|
||||
if 'tag_archive_sync' in info:
|
||||
|
||||
tas_flat = info[ 'tag_archive_sync' ].items()
|
||||
|
||||
info[ 'tag_archive_sync' ] = { HydrusPaths.ConvertAbsPathToPortablePath( os.path.join( client_archives_folder, archive_name + '.db' ) ) : namespaces for ( archive_name, namespaces ) in tas_flat }
|
||||
|
||||
self._c.execute( 'UPDATE services SET info = ? WHERE service_id = ?;', ( info, service_id ) )
|
||||
|
||||
|
||||
|
||||
except Exception as e:
|
||||
|
||||
HydrusData.Print( 'Trying to update tag archive location caused the following problem:' )
|
||||
|
||||
HydrusData.PrintException( e )
|
||||
|
||||
|
||||
|
||||
self._controller.pub( 'splash_set_title_text', 'updated db to v' + str( version + 1 ) )
|
||||
|
||||
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
|
||||
|
@ -8256,13 +8192,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
old_tag_archive_sync = old_info[ 'tag_archive_sync' ]
|
||||
new_tag_archive_sync = info_update[ 'tag_archive_sync' ]
|
||||
|
||||
for archive_name in new_tag_archive_sync.keys():
|
||||
for portable_hta_path in new_tag_archive_sync.keys():
|
||||
|
||||
namespaces = set( new_tag_archive_sync[ archive_name ] )
|
||||
namespaces = set( new_tag_archive_sync[ portable_hta_path ] )
|
||||
|
||||
if archive_name in old_tag_archive_sync:
|
||||
if portable_hta_path in old_tag_archive_sync:
|
||||
|
||||
old_namespaces = old_tag_archive_sync[ archive_name ]
|
||||
old_namespaces = old_tag_archive_sync[ portable_hta_path ]
|
||||
|
||||
namespaces.difference_update( old_namespaces )
|
||||
|
||||
|
@ -8272,7 +8208,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
( hta_path, hta ) = self._tag_archives[ archive_name ]
|
||||
hta_path = HydrusPaths.ConvertPortablePathToAbsPath( portable_hta_path )
|
||||
|
||||
file_service_key = CC.LOCAL_FILE_SERVICE_KEY
|
||||
|
||||
|
@ -8492,7 +8428,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
client_files_default = os.path.join( self._db_dir, 'client_files' )
|
||||
|
||||
HydrusPaths.MirrorTree( os.path.join( path, 'client_archives' ), os.path.join( self._db_dir, 'client_archives' ) )
|
||||
HydrusPaths.MirrorTree( os.path.join( path, 'client_files' ), client_files_default )
|
||||
HydrusPaths.MirrorTree( os.path.join( path, 'client_updates' ), os.path.join( self._db_dir, 'client_updates' ) )
|
||||
|
||||
|
|
|
@ -441,6 +441,18 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
self._dictionary[ 'default_import_tag_options' ] = HydrusSerialisable.SerialisableDictionary()
|
||||
|
||||
#
|
||||
|
||||
self._dictionary[ 'frame_locations' ] = {}
|
||||
|
||||
# remember size, remember position, last_size, last_pos, default gravity, default position, maximised, fullscreen
|
||||
self._dictionary[ 'frame_locations' ][ 'main_gui' ] = ( True, True, ( 640, 480 ), ( 20, 20 ), ( -1, -1 ), 'topleft', True, False )
|
||||
self._dictionary[ 'frame_locations' ][ 'media_viewer' ] = ( True, True, ( 640, 480 ), ( 70, 70 ), ( -1, -1 ), 'topleft', True, True )
|
||||
self._dictionary[ 'frame_locations' ][ 'manage_tags_dialog' ] = ( False, False, None, None, ( -1, 1 ), 'topleft', False, False )
|
||||
self._dictionary[ 'frame_locations' ][ 'manage_tags_frame' ] = ( False, False, None, None, ( -1, 1 ), 'topleft', False, False )
|
||||
|
||||
#
|
||||
|
||||
self._dictionary[ 'booleans' ] = {}
|
||||
|
||||
self._dictionary[ 'booleans' ][ 'apply_all_parents_to_all_services' ] = False
|
||||
|
@ -455,6 +467,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
self._dictionary[ 'booleans' ][ 'replace_siblings_on_manage_tags' ] = True
|
||||
|
||||
#
|
||||
|
||||
self._dictionary[ 'noneable_integers' ] = {}
|
||||
|
||||
self._dictionary[ 'noneable_integers' ][ 'forced_search_limit' ] = None
|
||||
|
@ -462,6 +476,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
self._dictionary[ 'noneable_integers' ][ 'disk_cache_maintenance_mb' ] = 256
|
||||
self._dictionary[ 'noneable_integers' ][ 'disk_cache_init_period' ] = 4
|
||||
|
||||
#
|
||||
|
||||
client_files_default = os.path.join( HC.DB_DIR, 'client_files' )
|
||||
|
||||
self._dictionary[ 'client_files_locations_ideal_weights' ] = [ ( HydrusPaths.ConvertAbsPathToPortablePath( client_files_default ), 1.0 ) ]
|
||||
|
@ -593,6 +609,11 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def GetFrameLocation( self, frame_key ):
|
||||
|
||||
return self._dictionary[ 'frame_locations' ][ frame_key ]
|
||||
|
||||
|
||||
def GetNoneableInteger( self, name ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -627,6 +648,11 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def SetFrameLocation( self, frame_key, remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ):
|
||||
|
||||
self._dictionary[ 'frame_locations' ][ frame_key ] = ( remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen )
|
||||
|
||||
|
||||
def SetNoneableInteger( self, name, value ):
|
||||
|
||||
with self._lock:
|
||||
|
|
|
@ -204,21 +204,6 @@ def GetClientDefaultOptions():
|
|||
|
||||
options[ 'processing_phase' ] = 0
|
||||
|
||||
client_size = {}
|
||||
|
||||
client_size[ 'gui_fullscreen' ] = False
|
||||
client_size[ 'gui_maximised' ] = True
|
||||
client_size[ 'gui_restored_size' ] = [ 640, 480 ]
|
||||
client_size[ 'gui_restored_position' ] = [ 20, 20 ]
|
||||
client_size[ 'fs_fullscreen' ] = True
|
||||
client_size[ 'fs_maximised' ] = True
|
||||
client_size[ 'fs_restored_size' ] = [ 640, 480 ]
|
||||
client_size[ 'fs_restored_position' ] = [ 20, 20 ]
|
||||
|
||||
options[ 'client_size' ] = client_size
|
||||
|
||||
options[ 'tag_dialog_size' ] = ( False, None )
|
||||
options[ 'tag_dialog_position' ] = ( False, None )
|
||||
options[ 'rating_dialog_position' ] = ( False, None )
|
||||
|
||||
options[ 'local_port' ] = None
|
||||
|
@ -428,7 +413,7 @@ def GetDefaultBoorus():
|
|||
thumb_classname = 'thumb'
|
||||
image_id = None
|
||||
image_data = 'Original image'
|
||||
tag_classnames_to_namespaces = { 'tag-type-general' : '' }
|
||||
tag_classnames_to_namespaces = { 'tag-type-general' : '', 'tag-type-character' : 'character', 'tag-type-copyright' : 'series', 'tag-type-artist' : 'creator' }
|
||||
|
||||
boorus[ 'rule34@booru.org' ] = ClientData.Booru( name, search_url, search_separator, advance_by_page_num, thumb_classname, image_id, image_data, tag_classnames_to_namespaces )
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
title = self._controller.PrepStringForDisplay( 'Hydrus Client' )
|
||||
|
||||
ClientGUICommon.FrameThatResizes.__init__( self, None, resize_option_prefix = 'gui_', title = title )
|
||||
ClientGUICommon.FrameThatResizes.__init__( self, None, title, 'main_gui' )
|
||||
|
||||
self.SetDropTarget( ClientDragDrop.FileDropTarget( self.ImportFiles ) )
|
||||
|
||||
|
@ -113,18 +113,20 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
self._menus = {}
|
||||
|
||||
self._InitialiseMenubar()
|
||||
|
||||
self._RefreshStatusBar()
|
||||
|
||||
vbox = wx.BoxSizer( wx.HORIZONTAL )
|
||||
|
||||
vbox.AddF( self._notebook, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
||||
|
||||
self.SetSizer( vbox )
|
||||
|
||||
self._SetSizeAndPosition( self._frame_key )
|
||||
|
||||
self.Show( True )
|
||||
|
||||
self._InitialiseMenubar()
|
||||
|
||||
self._RefreshStatusBar()
|
||||
|
||||
# as we are in oninit, callafter and calllater( 0 ) are different
|
||||
# later waits until the mainloop is running, I think.
|
||||
# after seems to execute synchronously
|
||||
|
@ -1712,7 +1714,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
def _ReviewServices( self ):
|
||||
|
||||
FrameReviewServices( self._controller )
|
||||
FrameReviewServices( self, self._controller )
|
||||
|
||||
|
||||
def _SaveGUISession( self, name = None ):
|
||||
|
@ -2552,6 +2554,8 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
self._controller.WriteSynchronous( 'save_options', HC.options )
|
||||
|
||||
self._controller.WriteSynchronous( 'serialisable', self._new_options )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
HydrusData.PrintException( e )
|
||||
|
@ -2786,7 +2790,7 @@ class FrameComposeMessage( ClientGUICommon.Frame ):
|
|||
|
||||
def __init__( self, empty_draft_message ):
|
||||
|
||||
ClientGUICommon.Frame.__init__( self, None, title = HC.app.PrepStringForDisplay( 'Compose Message' ) )
|
||||
ClientGUICommon.Frame.__init__( self, None, HC.app.PrepStringForDisplay( 'Compose Message' ) )
|
||||
|
||||
self.SetInitialSize( ( 920, 600 ) )
|
||||
|
||||
|
@ -2816,17 +2820,11 @@ class FrameComposeMessage( ClientGUICommon.Frame ):
|
|||
'''
|
||||
class FrameReviewServices( ClientGUICommon.Frame ):
|
||||
|
||||
def __init__( self, controller ):
|
||||
def __init__( self, parent, controller ):
|
||||
|
||||
self._controller = controller
|
||||
|
||||
( pos_x, pos_y ) = self._controller.GetGUI().GetPositionTuple()
|
||||
|
||||
pos = ( pos_x + 25, pos_y + 50 )
|
||||
|
||||
tlp = wx.GetApp().GetTopWindow()
|
||||
|
||||
ClientGUICommon.Frame.__init__( self, tlp, title = self._controller.PrepStringForDisplay( 'Review Services' ), pos = pos )
|
||||
ClientGUICommon.Frame.__init__( self, parent, self._controller.PrepStringForDisplay( 'Review Services' ) )
|
||||
|
||||
self._notebook = wx.Notebook( self )
|
||||
|
||||
|
@ -3859,13 +3857,9 @@ class FrameSeedCache( ClientGUICommon.Frame ):
|
|||
|
||||
self._controller = controller
|
||||
|
||||
( pos_x, pos_y ) = self._controller.GetGUI().GetPositionTuple()
|
||||
|
||||
pos = ( pos_x + 25, pos_y + 50 )
|
||||
|
||||
tlp = wx.GetApp().GetTopWindow()
|
||||
|
||||
ClientGUICommon.Frame.__init__( self, tlp, title = self._controller.PrepStringForDisplay( 'File Import Status' ), pos = pos )
|
||||
ClientGUICommon.Frame.__init__( self, tlp, self._controller.PrepStringForDisplay( 'File Import Status' ) )
|
||||
|
||||
self._seed_cache = seed_cache
|
||||
|
||||
|
|
|
@ -854,9 +854,19 @@ class Canvas( wx.Window ):
|
|||
|
||||
def _HydrusShouldNotProcessInput( self ):
|
||||
|
||||
if HydrusGlobals.do_not_catch_char_hook:
|
||||
|
||||
HydrusGlobals.do_not_catch_char_hook = False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if self._current_display_media.GetMime() == HC.APPLICATION_FLASH:
|
||||
|
||||
if self.MouseIsOverMedia(): return True
|
||||
if self.MouseIsOverMedia():
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
return False
|
||||
|
@ -885,16 +895,13 @@ class Canvas( wx.Window ):
|
|||
if self._current_display_media is not None:
|
||||
|
||||
title = 'manage tags'
|
||||
dialog_key = 'manage_tags'
|
||||
frame_key = 'manage_tags_frame'
|
||||
|
||||
with ClientGUIDialogs.DialogManageApply( self, title, dialog_key ) as dlg:
|
||||
|
||||
panel = ClientGUIPanels.ManageTagsPanel( dlg, self._file_service_key, ( self._current_display_media, ), canvas_key = self._canvas_key )
|
||||
|
||||
dlg.SetPanel( panel )
|
||||
|
||||
dlg.ShowModal()
|
||||
|
||||
manage_tags = ClientGUICommon.FrameThatResizesAndTakesPanel( self, title, frame_key )
|
||||
|
||||
panel = ClientGUIPanels.ManageTagsPanel( manage_tags, self._file_service_key, ( self._current_display_media, ), immediate_commit = True, canvas_key = self._canvas_key )
|
||||
|
||||
manage_tags.SetPanel( panel )
|
||||
|
||||
|
||||
|
||||
|
@ -918,7 +925,10 @@ class Canvas( wx.Window ):
|
|||
|
||||
|
||||
|
||||
def _PrefetchNeighbours( self ): pass
|
||||
def _PrefetchNeighbours( self ):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def _RecalcZoom( self ):
|
||||
|
||||
|
@ -1692,7 +1702,7 @@ class CanvasFrame( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
def __init__( self, parent ):
|
||||
|
||||
ClientGUICommon.FrameThatResizes.__init__( self, parent, resize_option_prefix = 'fs_', title = 'hydrus client media viewer' )
|
||||
ClientGUICommon.FrameThatResizes.__init__( self, parent, 'hydrus client media viewer', 'media_viewer', float_on_parent = False )
|
||||
|
||||
|
||||
def Close( self ):
|
||||
|
@ -1727,6 +1737,8 @@ class CanvasFrame( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
self.SetSizer( vbox )
|
||||
|
||||
self._SetSizeAndPosition( self._frame_key )
|
||||
|
||||
self.Show( True )
|
||||
|
||||
wx.GetApp().SetTopWindow( self )
|
||||
|
|
|
@ -156,8 +156,16 @@ class AutoCompleteDropdown( wx.Panel ):
|
|||
# There's a big bug in wx where FRAME_FLOAT_ON_PARENT Frames don't get passed their mouse events if their parent is a Dialog jej
|
||||
# I think it is something to do with the initialisation order; if the frame is init'ed before the ShowModal call, but whatever.
|
||||
|
||||
if isinstance( tlp, wx.Dialog ) or HC.options[ 'always_embed_autocompletes' ]: self._float_mode = False
|
||||
else: self._float_mode = True
|
||||
# This turned out to be ugly when I added the manage tags frame, so I've set it to if the tlp has a parent, which basically means "not the main gui"
|
||||
|
||||
if tlp.GetParent() is not None or HC.options[ 'always_embed_autocompletes' ]:
|
||||
|
||||
self._float_mode = False
|
||||
|
||||
else:
|
||||
|
||||
self._float_mode = True
|
||||
|
||||
|
||||
self._text_ctrl = wx.TextCtrl( self, style=wx.TE_PROCESS_ENTER )
|
||||
|
||||
|
@ -478,7 +486,12 @@ class AutoCompleteDropdown( wx.Panel ):
|
|||
|
||||
self._text_ctrl.ProcessEvent( new_event )
|
||||
|
||||
else: self._dropdown_list.ProcessEvent( event )
|
||||
wx.CallLater( 50, self._text_ctrl.SetFocus )
|
||||
|
||||
else:
|
||||
|
||||
self._dropdown_list.ProcessEvent( event )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
|
@ -1791,17 +1804,197 @@ class FitResistantStaticText( wx.StaticText ):
|
|||
|
||||
class Frame( wx.Frame ):
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
def __init__( self, parent, title, float_on_parent = True ):
|
||||
|
||||
HydrusGlobals.client_controller.ResetIdleTimer()
|
||||
|
||||
wx.Frame.__init__( self, *args, **kwargs )
|
||||
if parent is None:
|
||||
|
||||
pos = wx.DefaultPosition
|
||||
style = wx.DEFAULT_FRAME_STYLE
|
||||
|
||||
else:
|
||||
|
||||
if isinstance( parent, wx.TopLevelWindow ):
|
||||
|
||||
parent_tlp = parent
|
||||
|
||||
else:
|
||||
|
||||
parent_tlp = parent.GetTopLevelParent()
|
||||
|
||||
|
||||
( tlp_x, tlp_y ) = parent_tlp.GetPositionTuple()
|
||||
|
||||
pos = ( tlp_x + 50, tlp_y + 50 )
|
||||
|
||||
if float_on_parent:
|
||||
|
||||
style = wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT
|
||||
|
||||
else:
|
||||
|
||||
style = wx.DEFAULT_FRAME_STYLE
|
||||
|
||||
|
||||
|
||||
wx.Frame.__init__( self, parent, title = title, pos = pos, style = style )
|
||||
|
||||
self._new_options = HydrusGlobals.client_controller.GetNewOptions()
|
||||
|
||||
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
||||
|
||||
self.SetIcon( wx.Icon( os.path.join( HC.STATIC_DIR, 'hydrus.ico' ), wx.BITMAP_TYPE_ICO ) )
|
||||
|
||||
|
||||
def _GetSafePosition( self, position ):
|
||||
|
||||
display_index = wx.Display.GetFromPoint( position )
|
||||
|
||||
if display_index == wx.NOT_FOUND:
|
||||
|
||||
position = wx.DefaultPosition
|
||||
|
||||
else:
|
||||
|
||||
display = wx.Display( display_index )
|
||||
|
||||
geometry = display.GetGeometry()
|
||||
|
||||
( p_x, p_y ) = position
|
||||
|
||||
x_bad = p_x < geometry.x or p_x > geometry.x + geometry.width
|
||||
y_bad = p_y < geometry.y or p_y > geometry.y + geometry.height
|
||||
|
||||
if x_bad or y_bad:
|
||||
|
||||
position = wx.DefaultPosition
|
||||
|
||||
|
||||
|
||||
return position
|
||||
|
||||
|
||||
def _SaveSizeAndPosition( self, frame_key ):
|
||||
|
||||
( remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ) = self._new_options.GetFrameLocation( frame_key )
|
||||
|
||||
maximised = self.IsMaximized()
|
||||
fullscreen = self.IsFullScreen()
|
||||
|
||||
if not ( maximised or fullscreen ):
|
||||
|
||||
# when dragging window up to be maximised, reported position is sometimes a bit dodgy
|
||||
# so, filter out the invalid answers
|
||||
|
||||
display_index = wx.Display.GetFromPoint( self.GetPosition() )
|
||||
|
||||
if display_index != wx.NOT_FOUND:
|
||||
|
||||
last_size = self.GetSizeTuple()
|
||||
last_position = self._GetSafePosition( self.GetPositionTuple() )
|
||||
|
||||
|
||||
|
||||
self._new_options.SetFrameLocation( frame_key, remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen )
|
||||
|
||||
|
||||
def _SetSizeAndPosition( self, frame_key ):
|
||||
traceback.print_stack()
|
||||
( remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ) = self._new_options.GetFrameLocation( frame_key )
|
||||
|
||||
parent = self.GetParent()
|
||||
|
||||
if remember_size and last_size is not None:
|
||||
|
||||
( width, height ) = last_size
|
||||
|
||||
else:
|
||||
|
||||
( min_width, min_height ) = self.GetEffectiveMinSize()
|
||||
|
||||
if parent is None:
|
||||
|
||||
width = min_width + 20
|
||||
height = min_height + 20
|
||||
|
||||
else:
|
||||
|
||||
( parent_window_width, parent_window_height ) = self.GetParent().GetTopLevelParent().GetSize()
|
||||
|
||||
max_width = parent_window_width - 100
|
||||
max_height = parent_window_height - 100
|
||||
|
||||
( width_gravity, height_gravity ) = default_gravity
|
||||
|
||||
if width_gravity == -1:
|
||||
|
||||
width = min_width
|
||||
|
||||
else:
|
||||
|
||||
width = int( width_gravity * max_width )
|
||||
|
||||
|
||||
if height_gravity == -1:
|
||||
|
||||
height = min_height
|
||||
|
||||
else:
|
||||
|
||||
height = int( height_gravity * max_height )
|
||||
|
||||
|
||||
|
||||
|
||||
self.SetInitialSize( ( width, height ) )
|
||||
|
||||
if maximised:
|
||||
|
||||
self.Maximize()
|
||||
|
||||
|
||||
if fullscreen:
|
||||
|
||||
wx.CallAfter( self.ShowFullScreen, True, wx.FULLSCREEN_ALL )
|
||||
|
||||
|
||||
#
|
||||
|
||||
pos = None
|
||||
|
||||
if remember_position and last_position is not None:
|
||||
|
||||
pos = last_position
|
||||
|
||||
elif default_position == 'topleft' and parent is not None:
|
||||
|
||||
if isinstance( parent, wx.TopLevelWindow ):
|
||||
|
||||
parent_tlp = parent
|
||||
|
||||
else:
|
||||
|
||||
parent_tlp = parent.GetTopLevelParent()
|
||||
|
||||
|
||||
( pos_x, pos_y ) = parent_tlp.GetPositionTuple()
|
||||
|
||||
pos = ( pos_x + 50, pos_y + 50 )
|
||||
|
||||
elif default_position == 'center':
|
||||
|
||||
wx.CallAfter( self.Center )
|
||||
|
||||
|
||||
if pos is not None:
|
||||
|
||||
pos = self._GetSafePosition( pos )
|
||||
|
||||
self.SetPosition( pos )
|
||||
|
||||
|
||||
|
||||
def SetInitialSize( self, ( width, height ) ):
|
||||
|
||||
( display_width, display_height ) = wx.GetDisplaySize()
|
||||
|
@ -1819,88 +2012,87 @@ class Frame( wx.Frame ):
|
|||
|
||||
class FrameThatResizes( Frame ):
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
def __init__( self, parent, title, frame_key, float_on_parent = True ):
|
||||
|
||||
self._resize_option_prefix = kwargs[ 'resize_option_prefix' ]
|
||||
self._frame_key = frame_key
|
||||
|
||||
del kwargs[ 'resize_option_prefix' ]
|
||||
|
||||
Frame.__init__( self, *args, **kwargs )
|
||||
|
||||
self._InitialiseSizeAndPosition()
|
||||
Frame.__init__( self, parent, title, float_on_parent )
|
||||
|
||||
self.Bind( wx.EVT_SIZE, self.EventSpecialResize )
|
||||
self.Bind( wx.EVT_MOVE, self.EventSpecialMove )
|
||||
|
||||
|
||||
def _InitialiseSizeAndPosition( self ):
|
||||
|
||||
client_size = HC.options[ 'client_size' ]
|
||||
|
||||
self.SetInitialSize( client_size[ self._resize_option_prefix + 'restored_size' ] )
|
||||
|
||||
position = client_size[ self._resize_option_prefix + 'restored_position' ]
|
||||
|
||||
display_index = wx.Display.GetFromPoint( position )
|
||||
|
||||
if display_index == wx.NOT_FOUND: client_size[ self._resize_option_prefix + 'restored_position' ] = ( 20, 20 )
|
||||
else:
|
||||
|
||||
display = wx.Display( display_index )
|
||||
|
||||
geometry = display.GetGeometry()
|
||||
|
||||
( p_x, p_y ) = position
|
||||
|
||||
x_bad = p_x < geometry.x or p_x > geometry.x + geometry.width
|
||||
y_bad = p_y < geometry.y or p_y > geometry.y + geometry.height
|
||||
|
||||
if x_bad or y_bad: client_size[ self._resize_option_prefix + 'restored_position' ] = ( 20, 20 )
|
||||
|
||||
|
||||
self.SetPosition( client_size[ self._resize_option_prefix + 'restored_position' ] )
|
||||
|
||||
if client_size[ self._resize_option_prefix + 'maximised' ]: self.Maximize()
|
||||
|
||||
if client_size[ self._resize_option_prefix + 'fullscreen' ]: wx.CallAfter( self.ShowFullScreen, True, wx.FULLSCREEN_ALL )
|
||||
|
||||
|
||||
def _RecordSizeAndPosition( self ):
|
||||
|
||||
client_size = HC.options[ 'client_size' ]
|
||||
|
||||
client_size[ self._resize_option_prefix + 'maximised' ] = self.IsMaximized()
|
||||
client_size[ self._resize_option_prefix + 'fullscreen' ] = self.IsFullScreen()
|
||||
|
||||
if not ( self.IsMaximized() or self.IsFullScreen() ):
|
||||
|
||||
# when dragging window up to be maximised, reported position is sometimes a bit dodgy
|
||||
|
||||
display_index = wx.Display.GetFromPoint( self.GetPosition() )
|
||||
|
||||
if display_index != wx.NOT_FOUND:
|
||||
|
||||
client_size[ self._resize_option_prefix + 'restored_size' ] = tuple( self.GetSize() )
|
||||
|
||||
client_size[ self._resize_option_prefix + 'restored_position' ] = tuple( self.GetPosition() )
|
||||
|
||||
|
||||
|
||||
|
||||
def EventSpecialMove( self, event ):
|
||||
|
||||
self._RecordSizeAndPosition()
|
||||
self._SaveSizeAndPosition( self._frame_key )
|
||||
|
||||
event.Skip()
|
||||
|
||||
|
||||
def EventSpecialResize( self, event ):
|
||||
|
||||
self._RecordSizeAndPosition()
|
||||
self._SaveSizeAndPosition( self._frame_key )
|
||||
|
||||
event.Skip()
|
||||
|
||||
|
||||
class FrameThatResizesAndTakesPanel( FrameThatResizes ):
|
||||
|
||||
def __init__( self, parent, title, frame_key, float_on_parent = True ):
|
||||
|
||||
FrameThatResizes.__init__( self, parent, title, frame_key, float_on_parent )
|
||||
|
||||
self._ok = wx.Button( self, label = 'close' )
|
||||
self._ok.Bind( wx.EVT_BUTTON, self.EventClose )
|
||||
|
||||
self.Bind( wx.EVT_MENU, self.EventMenu )
|
||||
self.Bind( wx.EVT_CLOSE, self.EventClose )
|
||||
|
||||
|
||||
def EventMenu( self, event ):
|
||||
|
||||
action = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
|
||||
|
||||
if action is not None:
|
||||
|
||||
( command, data ) = action
|
||||
|
||||
if command == 'ok':
|
||||
|
||||
self.EventClose( None )
|
||||
|
||||
else:
|
||||
|
||||
event.Skip()
|
||||
|
||||
|
||||
|
||||
|
||||
def EventClose( self, event ):
|
||||
|
||||
self._SaveSizeAndPosition( self._frame_key )
|
||||
|
||||
self.Destroy()
|
||||
|
||||
|
||||
def SetPanel( self, panel ):
|
||||
|
||||
self._panel = panel
|
||||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
vbox.AddF( self._panel, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
vbox.AddF( self._ok, CC.FLAGS_LONE_BUTTON )
|
||||
|
||||
self.SetSizer( vbox )
|
||||
|
||||
self._SetSizeAndPosition( self._frame_key )
|
||||
|
||||
self.Show( True )
|
||||
|
||||
self._panel.SetupScrolling()
|
||||
|
||||
|
||||
class Gauge( wx.Gauge ):
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
|
@ -6518,7 +6710,7 @@ class ShowKeys( Frame ):
|
|||
elif key_type == 'access': title = 'Access Keys'
|
||||
|
||||
# give it no parent, so this doesn't close when the dialog is closed!
|
||||
Frame.__init__( self, None, title = HydrusGlobals.client_controller.PrepStringForDisplay( title ) )
|
||||
Frame.__init__( self, None, HydrusGlobals.client_controller.PrepStringForDisplay( title ) )
|
||||
|
||||
self._key_type = key_type
|
||||
self._keys = keys
|
||||
|
|
|
@ -105,9 +105,9 @@ def ExportToHTA( parent, service_key, hashes ):
|
|||
HydrusGlobals.client_controller.Write( 'export_mappings', path, service_key, hash_type, hashes )
|
||||
|
||||
|
||||
def ImportFromHTA( parent, path, tag_service_key ):
|
||||
def ImportFromHTA( parent, hta_path, tag_service_key ):
|
||||
|
||||
hta = HydrusTagArchive.HydrusTagArchive( path )
|
||||
hta = HydrusTagArchive.HydrusTagArchive( hta_path )
|
||||
|
||||
potential_namespaces = hta.GetNamespaces()
|
||||
|
||||
|
@ -213,7 +213,7 @@ def ImportFromHTA( parent, path, tag_service_key ):
|
|||
|
||||
if dlg_final.ShowModal() == wx.ID_YES:
|
||||
|
||||
HydrusGlobals.client_controller.pub( 'sync_to_tag_archive', path, tag_service_key, file_service_key, adding, namespaces )
|
||||
HydrusGlobals.client_controller.pub( 'sync_to_tag_archive', hta_path, tag_service_key, file_service_key, adding, namespaces )
|
||||
|
||||
|
||||
|
||||
|
@ -283,6 +283,8 @@ class Dialog( wx.Dialog ):
|
|||
|
||||
wx.Dialog.__init__( self, parent, title = title, style = style, pos = pos )
|
||||
|
||||
self._new_options = HydrusGlobals.client_controller.GetNewOptions()
|
||||
|
||||
#self.SetDoubleBuffered( True )
|
||||
|
||||
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
||||
|
@ -299,6 +301,141 @@ class Dialog( wx.Dialog ):
|
|||
HydrusGlobals.client_controller.ResetIdleTimer()
|
||||
|
||||
|
||||
def _GetSafePosition( self, position ):
|
||||
|
||||
display_index = wx.Display.GetFromPoint( position )
|
||||
|
||||
if display_index == wx.NOT_FOUND:
|
||||
|
||||
position = wx.DefaultPosition
|
||||
|
||||
else:
|
||||
|
||||
display = wx.Display( display_index )
|
||||
|
||||
geometry = display.GetGeometry()
|
||||
|
||||
( p_x, p_y ) = position
|
||||
|
||||
x_bad = p_x < geometry.x or p_x > geometry.x + geometry.width
|
||||
y_bad = p_y < geometry.y or p_y > geometry.y + geometry.height
|
||||
|
||||
if x_bad or y_bad:
|
||||
|
||||
position = wx.DefaultPosition
|
||||
|
||||
|
||||
|
||||
return position
|
||||
|
||||
|
||||
def _SaveSizeAndPosition( self, frame_key ):
|
||||
|
||||
( remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ) = self._new_options.GetFrameLocation( frame_key )
|
||||
|
||||
last_size = self.GetSizeTuple()
|
||||
|
||||
last_position = self._GetSafePosition( self.GetPositionTuple() )
|
||||
|
||||
self._new_options.SetFrameLocation( frame_key, remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen )
|
||||
|
||||
|
||||
def _SetSizeAndPosition( self, frame_key ):
|
||||
|
||||
( remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ) = self._new_options.GetFrameLocation( frame_key )
|
||||
|
||||
parent = self.GetParent()
|
||||
|
||||
if remember_size and last_size is not None:
|
||||
|
||||
( width, height ) = last_size
|
||||
|
||||
else:
|
||||
|
||||
( min_width, min_height ) = self.GetEffectiveMinSize()
|
||||
|
||||
if parent is None:
|
||||
|
||||
width = min_width + 20
|
||||
height = min_height + 20
|
||||
|
||||
else:
|
||||
|
||||
( parent_window_width, parent_window_height ) = self.GetParent().GetTopLevelParent().GetSize()
|
||||
|
||||
max_width = parent_window_width - 100
|
||||
max_height = parent_window_height - 100
|
||||
|
||||
( width_gravity, height_gravity ) = default_gravity
|
||||
|
||||
if width_gravity == -1:
|
||||
|
||||
width = min_width
|
||||
|
||||
else:
|
||||
|
||||
width = int( width_gravity * max_width )
|
||||
|
||||
|
||||
if height_gravity == -1:
|
||||
|
||||
height = min_height
|
||||
|
||||
else:
|
||||
|
||||
height = int( height_gravity * max_height )
|
||||
|
||||
|
||||
|
||||
|
||||
self.SetInitialSize( ( width, height ) )
|
||||
|
||||
if maximised:
|
||||
|
||||
self.Maximize()
|
||||
|
||||
|
||||
if fullscreen:
|
||||
|
||||
wx.CallAfter( self.ShowFullScreen, True, wx.FULLSCREEN_ALL )
|
||||
|
||||
|
||||
#
|
||||
|
||||
pos = None
|
||||
|
||||
if remember_position and last_position is not None:
|
||||
|
||||
pos = last_position
|
||||
|
||||
elif default_position == 'topleft' and parent is not None:
|
||||
|
||||
if isinstance( parent, wx.TopLevelWindow ):
|
||||
|
||||
parent_tlp = parent
|
||||
|
||||
else:
|
||||
|
||||
parent_tlp = parent.GetTopLevelParent()
|
||||
|
||||
|
||||
( pos_x, pos_y ) = parent_tlp.GetPositionTuple()
|
||||
|
||||
pos = ( pos_x + 50, pos_y + 50 )
|
||||
|
||||
elif default_position == 'center':
|
||||
|
||||
wx.CallAfter( self.Center )
|
||||
|
||||
|
||||
if pos is not None:
|
||||
|
||||
pos = self._GetSafePosition( pos )
|
||||
|
||||
self.SetPosition( pos )
|
||||
|
||||
|
||||
|
||||
def EventDialogButton( self, event ): self.EndModal( event.GetId() )
|
||||
|
||||
def SetInitialSize( self, ( width, height ) ):
|
||||
|
@ -318,20 +455,11 @@ class Dialog( wx.Dialog ):
|
|||
|
||||
class DialogManageApply( Dialog ):
|
||||
|
||||
def __init__( self, parent, title, dialog_key ):
|
||||
def __init__( self, parent, title, frame_key ):
|
||||
|
||||
self._dialog_key = dialog_key
|
||||
self._frame_key = frame_key
|
||||
|
||||
# use the dialog key to figure out default position and size from options
|
||||
|
||||
( remember, position ) = HC.options[ 'tag_dialog_position' ]
|
||||
|
||||
if not remember:
|
||||
|
||||
position = 'topleft'
|
||||
|
||||
|
||||
Dialog.__init__( self, parent, title, position = position )
|
||||
Dialog.__init__( self, parent, title )
|
||||
|
||||
self._apply = wx.Button( self, id = wx.ID_OK, label = 'apply' )
|
||||
self._apply.Bind( wx.EVT_BUTTON, self.EventOk )
|
||||
|
@ -366,29 +494,7 @@ class DialogManageApply( Dialog ):
|
|||
|
||||
self._panel.CommitChanges()
|
||||
|
||||
# use dialog key to figure this out
|
||||
|
||||
( remember, size ) = HC.options[ 'tag_dialog_size' ]
|
||||
|
||||
current_size = self.GetSizeTuple()
|
||||
|
||||
if remember and size != current_size:
|
||||
|
||||
HC.options[ 'tag_dialog_size' ] = ( remember, current_size )
|
||||
|
||||
HydrusGlobals.client_controller.Write( 'save_options', HC.options )
|
||||
|
||||
|
||||
( remember, position ) = HC.options[ 'tag_dialog_position' ]
|
||||
|
||||
current_position = self.GetPositionTuple()
|
||||
|
||||
if remember and position != current_position:
|
||||
|
||||
HC.options[ 'tag_dialog_position' ] = ( remember, current_position )
|
||||
|
||||
HydrusGlobals.client_controller.Write( 'save_options', HC.options )
|
||||
|
||||
self._SaveSizeAndPosition( self._frame_key )
|
||||
|
||||
self.EndModal( wx.ID_OK )
|
||||
|
||||
|
@ -403,41 +509,13 @@ class DialogManageApply( Dialog ):
|
|||
buttonbox.AddF( self._cancel, CC.FLAGS_MIXED )
|
||||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
|
||||
vbox.AddF( self._panel, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
vbox.AddF( buttonbox, CC.FLAGS_BUTTON_SIZER )
|
||||
|
||||
self.SetSizer( vbox )
|
||||
|
||||
# use the dialog key to figure out default position and size from options
|
||||
|
||||
( remember, size ) = HC.options[ 'tag_dialog_size' ]
|
||||
|
||||
if size is not None:
|
||||
|
||||
( ideal_width, ideal_height ) = size
|
||||
|
||||
else:
|
||||
|
||||
( ideal_width, ideal_height ) = self.GetEffectiveMinSize()
|
||||
|
||||
ideal_width += 20
|
||||
ideal_height += 20
|
||||
|
||||
|
||||
( parent_window_width, parent_window_height ) = self.GetParent().GetTopLevelParent().GetSize()
|
||||
|
||||
width = min( ideal_width, parent_window_width - 100 )
|
||||
height = min( ideal_height, parent_window_height - 100 )
|
||||
|
||||
self.SetInitialSize( ( width, height ) )
|
||||
|
||||
( remember, position ) = HC.options[ 'tag_dialog_position' ]
|
||||
|
||||
if remember:
|
||||
|
||||
self.SetPosition( position )
|
||||
|
||||
self._SetSizeAndPosition( self._frame_key )
|
||||
|
||||
self._panel.SetupScrolling()
|
||||
|
||||
|
@ -1639,7 +1717,7 @@ class DialogInputLocalFiles( Dialog ):
|
|||
|
||||
self._import_file_options = ClientGUICollapsible.CollapsibleOptionsImportFiles( self )
|
||||
|
||||
self._delete_after_success = wx.CheckBox( self, label = 'delete files after successful import' )
|
||||
self._delete_after_success = wx.CheckBox( self, label = 'delete original files after successful import' )
|
||||
|
||||
self._add_button = wx.Button( self, label = 'Import now' )
|
||||
self._add_button.Bind( wx.EVT_BUTTON, self.EventOK )
|
||||
|
|
|
@ -22,6 +22,7 @@ import HydrusGlobals
|
|||
import HydrusNATPunch
|
||||
import HydrusPaths
|
||||
import HydrusSerialisable
|
||||
import HydrusTagArchive
|
||||
import HydrusTags
|
||||
import itertools
|
||||
import multipart
|
||||
|
@ -4216,13 +4217,11 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self._gui_capitalisation.SetValue( HC.options[ 'gui_capitalisation' ] )
|
||||
|
||||
( remember, size ) = HC.options[ 'tag_dialog_size' ]
|
||||
remember_tuple = self._new_options.GetFrameLocation( 'manage_tags_dialog' )
|
||||
|
||||
self._tag_dialog_size.SetValue( remember )
|
||||
self._tag_dialog_size.SetValue( remember_tuple[0] )
|
||||
|
||||
( remember, position ) = HC.options[ 'tag_dialog_position' ]
|
||||
|
||||
self._tag_dialog_position.SetValue( remember )
|
||||
self._tag_dialog_position.SetValue( remember_tuple[1] )
|
||||
|
||||
( remember, position ) = HC.options[ 'rating_dialog_position' ]
|
||||
|
||||
|
@ -4288,31 +4287,13 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
HC.options[ 'always_embed_autocompletes' ] = self._always_embed_autocompletes.GetValue()
|
||||
HC.options[ 'gui_capitalisation' ] = self._gui_capitalisation.GetValue()
|
||||
|
||||
( remember, size ) = HC.options[ 'tag_dialog_size' ]
|
||||
( remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ) = self._new_options.GetFrameLocation( 'manage_tags_dialog' )
|
||||
|
||||
remember = self._tag_dialog_size.GetValue()
|
||||
remember_size = self._tag_dialog_size.GetValue()
|
||||
|
||||
if remember:
|
||||
|
||||
HC.options[ 'tag_dialog_size' ] = ( remember, size )
|
||||
|
||||
else:
|
||||
|
||||
HC.options[ 'tag_dialog_size' ] = ( remember, None )
|
||||
|
||||
remember_position = self._tag_dialog_position.GetValue()
|
||||
|
||||
( remember, position ) = HC.options[ 'tag_dialog_position' ]
|
||||
|
||||
remember = self._tag_dialog_position.GetValue()
|
||||
|
||||
if remember:
|
||||
|
||||
HC.options[ 'tag_dialog_position' ] = ( remember, position )
|
||||
|
||||
else:
|
||||
|
||||
HC.options[ 'tag_dialog_position' ] = ( remember, None )
|
||||
|
||||
self._new_options.SetFrameLocation( 'manage_tags_dialog', remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen )
|
||||
|
||||
( remember, position ) = HC.options[ 'rating_dialog_position' ]
|
||||
|
||||
|
@ -6481,8 +6462,6 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
|
||||
if service_type in HC.TAG_SERVICES:
|
||||
|
||||
self._archive_info = HydrusGlobals.client_controller.Read( 'tag_archive_info' )
|
||||
|
||||
self._archive_panel = ClientGUICommon.StaticBox( self, 'archive synchronisation' )
|
||||
|
||||
self._archive_sync = wx.ListBox( self._archive_panel, size = ( -1, 100 ) )
|
||||
|
@ -6522,15 +6501,13 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
|
||||
if service_type in HC.TAG_SERVICES:
|
||||
|
||||
for ( archive_name, namespaces ) in info[ 'tag_archive_sync' ].items():
|
||||
for ( portable_hta_path, namespaces ) in info[ 'tag_archive_sync' ].items():
|
||||
|
||||
name_to_display = self._GetArchiveNameToDisplay( archive_name, namespaces )
|
||||
name_to_display = self._GetArchiveNameToDisplay( portable_hta_path, namespaces )
|
||||
|
||||
self._archive_sync.Append( name_to_display, ( archive_name, namespaces ) )
|
||||
self._archive_sync.Append( name_to_display, ( portable_hta_path, namespaces ) )
|
||||
|
||||
|
||||
self._UpdateArchiveButtons()
|
||||
|
||||
|
||||
if service_type in HC.RATINGS_SERVICES:
|
||||
|
||||
|
@ -6683,38 +6660,16 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
self.SetSizer( vbox )
|
||||
|
||||
|
||||
def _GetArchiveNameToDisplay( self, archive_name, namespaces ):
|
||||
def _GetArchiveNameToDisplay( self, portable_hta_path, namespaces ):
|
||||
|
||||
if len( namespaces ) == 0: name_to_display = archive_name
|
||||
else: name_to_display = archive_name + ' (' + ', '.join( HydrusData.ConvertUglyNamespacesToPrettyStrings( namespaces ) ) + ')'
|
||||
hta_path = HydrusPaths.ConvertPortablePathToAbsPath( portable_hta_path )
|
||||
|
||||
if len( namespaces ) == 0: name_to_display = hta_path
|
||||
else: name_to_display = hta_path + ' (' + ', '.join( HydrusData.ConvertUglyNamespacesToPrettyStrings( namespaces ) ) + ')'
|
||||
|
||||
return name_to_display
|
||||
|
||||
|
||||
def _GetPotentialArchives( self ):
|
||||
|
||||
existing_syncs = set()
|
||||
|
||||
for i in range( self._archive_sync.GetCount() ):
|
||||
|
||||
( archive_name, namespaces ) = self._archive_sync.GetClientData( i )
|
||||
|
||||
existing_syncs.add( archive_name )
|
||||
|
||||
|
||||
potential_archives = { archive_name for archive_name in self._archive_info.keys() if archive_name not in existing_syncs }
|
||||
|
||||
return potential_archives
|
||||
|
||||
|
||||
def _UpdateArchiveButtons( self ):
|
||||
|
||||
potential_archives = self._GetPotentialArchives()
|
||||
|
||||
if len( potential_archives ) == 0: self._archive_sync_add.Disable()
|
||||
else: self._archive_sync_add.Enable()
|
||||
|
||||
|
||||
def DoOnOKStuff( self ):
|
||||
|
||||
( service_key, service_type, name, info ) = self._original_info
|
||||
|
@ -6736,26 +6691,23 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
wx.MessageBox( 'Be careful with this tool! Syncing a lot of files to a large archive can take a very long time to initialise.' )
|
||||
|
||||
|
||||
potential_archives = self._GetPotentialArchives()
|
||||
text = 'Select the Hydrus Tag Archive\'s location.'
|
||||
|
||||
if len( potential_archives ) == 1:
|
||||
with wx.FileDialog( self, message = text, style = wx.FD_OPEN ) as dlg_file:
|
||||
|
||||
( archive_name, ) = potential_archives
|
||||
|
||||
wx.MessageBox( 'There is only one tag archive, ' + archive_name + ', to select, so I am selecting it for you.' )
|
||||
|
||||
else:
|
||||
|
||||
with ClientGUIDialogs.DialogSelectFromListOfStrings( self, 'Select the tag archive to add', potential_archives ) as dlg:
|
||||
if dlg_file.ShowModal() == wx.ID_OK:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK: archive_name = dlg.GetString()
|
||||
else: return
|
||||
hta_path = HydrusData.ToUnicode( dlg_file.GetPath() )
|
||||
|
||||
portable_hta_path = HydrusPaths.ConvertAbsPathToPortablePath( hta_path )
|
||||
|
||||
|
||||
|
||||
potential_namespaces = self._archive_info[ archive_name ]
|
||||
hta = HydrusTagArchive.HydrusTagArchive( hta_path )
|
||||
|
||||
with ClientGUIDialogs.DialogCheckFromListOfStrings( self, 'Select namespaces', HydrusData.ConvertUglyNamespacesToPrettyStrings( potential_namespaces ) ) as dlg:
|
||||
archive_namespaces = hta.GetNamespaces()
|
||||
|
||||
with ClientGUIDialogs.DialogCheckFromListOfStrings( self, 'Select namespaces', HydrusData.ConvertUglyNamespacesToPrettyStrings( archive_namespaces ) ) as dlg:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
|
@ -6767,11 +6719,9 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
|
||||
|
||||
|
||||
name_to_display = self._GetArchiveNameToDisplay( archive_name, namespaces )
|
||||
name_to_display = self._GetArchiveNameToDisplay( portable_hta_path, namespaces )
|
||||
|
||||
self._archive_sync.Append( name_to_display, ( archive_name, namespaces ) )
|
||||
|
||||
self._UpdateArchiveButtons()
|
||||
self._archive_sync.Append( name_to_display, ( portable_hta_path, namespaces ) )
|
||||
|
||||
|
||||
def EventArchiveEdit( self, event ):
|
||||
|
@ -6780,16 +6730,20 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
|
||||
if selection != wx.NOT_FOUND:
|
||||
|
||||
( archive_name, existing_namespaces ) = self._archive_sync.GetClientData( selection )
|
||||
( portable_hta_path, existing_namespaces ) = self._archive_sync.GetClientData( selection )
|
||||
|
||||
if archive_name not in self._archive_info.keys():
|
||||
hta_path = HydrusPaths.ConvertPortablePathToAbsPath( portable_hta_path )
|
||||
|
||||
if not os.path.exists( hta_path ):
|
||||
|
||||
wx.MessageBox( 'This archive does not seem to exist any longer!' )
|
||||
|
||||
return
|
||||
|
||||
|
||||
archive_namespaces = self._archive_info[ archive_name ]
|
||||
hta = HydrusTagArchive.HydrusTagArchive( hta_path )
|
||||
|
||||
archive_namespaces = hta.GetNamespaces()
|
||||
|
||||
with ClientGUIDialogs.DialogCheckFromListOfStrings( self, 'Select namespaces', HydrusData.ConvertUglyNamespacesToPrettyStrings( archive_namespaces ), HydrusData.ConvertUglyNamespacesToPrettyStrings( existing_namespaces ) ) as dlg:
|
||||
|
||||
|
@ -6797,13 +6751,16 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
|
||||
namespaces = HydrusData.ConvertPrettyStringsToUglyNamespaces( dlg.GetChecked() )
|
||||
|
||||
else: return
|
||||
else:
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
name_to_display = self._GetArchiveNameToDisplay( archive_name, namespaces )
|
||||
name_to_display = self._GetArchiveNameToDisplay( portable_hta_path, namespaces )
|
||||
|
||||
self._archive_sync.SetString( selection, name_to_display )
|
||||
self._archive_sync.SetClientData( selection, ( archive_name, namespaces ) )
|
||||
self._archive_sync.SetClientData( selection, ( portable_hta_path, namespaces ) )
|
||||
|
||||
|
||||
|
||||
|
@ -6811,9 +6768,10 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
|
||||
selection = self._archive_sync.GetSelection()
|
||||
|
||||
if selection != wx.NOT_FOUND: self._archive_sync.Delete( selection )
|
||||
|
||||
self._UpdateArchiveButtons()
|
||||
if selection != wx.NOT_FOUND:
|
||||
|
||||
self._archive_sync.Delete( selection )
|
||||
|
||||
|
||||
|
||||
def EventCheckIPFS( self, event ):
|
||||
|
@ -6976,9 +6934,9 @@ class DialogManageServices( ClientGUIDialogs.Dialog ):
|
|||
|
||||
for i in range( self._archive_sync.GetCount() ):
|
||||
|
||||
( archive_name, namespaces ) = self._archive_sync.GetClientData( i )
|
||||
( portable_hta_path, namespaces ) = self._archive_sync.GetClientData( i )
|
||||
|
||||
tag_archives[ archive_name ] = namespaces
|
||||
tag_archives[ portable_hta_path ] = namespaces
|
||||
|
||||
|
||||
info[ 'tag_archive_sync' ] = tag_archives
|
||||
|
|
|
@ -783,9 +783,9 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
|
|||
num_files = self._GetNumSelected()
|
||||
|
||||
title = 'manage tags for ' + HydrusData.ConvertIntToPrettyString( num_files ) + ' files'
|
||||
dialog_key = 'manage_tags'
|
||||
frame_key = 'manage_tags_dialog'
|
||||
|
||||
with ClientGUIDialogs.DialogManageApply( self, title, dialog_key ) as dlg:
|
||||
with ClientGUIDialogs.DialogManageApply( self, title, frame_key ) as dlg:
|
||||
|
||||
panel = ClientGUIPanels.ManageTagsPanel( dlg, self._file_service_key, self._selected_media )
|
||||
|
||||
|
|
|
@ -21,12 +21,13 @@ import wx.lib.scrolledpanel
|
|||
|
||||
class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
||||
|
||||
def __init__( self, parent, file_service_key, media, canvas_key = None ):
|
||||
def __init__( self, parent, file_service_key, media, immediate_commit = False, canvas_key = None ):
|
||||
|
||||
wx.lib.scrolledpanel.ScrolledPanel.__init__( self, parent )
|
||||
|
||||
self._file_service_key = file_service_key
|
||||
|
||||
self._immediate_commit = immediate_commit
|
||||
self._canvas_key = canvas_key
|
||||
|
||||
media = ClientMedia.FlattenMedia( media )
|
||||
|
@ -52,7 +53,7 @@ class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
|||
service_key = service.GetServiceKey()
|
||||
name = service.GetName()
|
||||
|
||||
page = self._Panel( self._tag_repositories, self._file_service_key, service.GetServiceKey(), self._current_media )
|
||||
page = self._Panel( self._tag_repositories, self._file_service_key, service.GetServiceKey(), self._current_media, self._immediate_commit )
|
||||
|
||||
self._tag_repositories.AddPage( name, service_key, page )
|
||||
|
||||
|
@ -70,6 +71,7 @@ class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
|||
self.SetSizer( vbox )
|
||||
|
||||
self.Bind( wx.EVT_MENU, self.EventMenu )
|
||||
self.Bind( wx.EVT_CHAR_HOOK, self.EventCharHook )
|
||||
|
||||
self.RefreshAcceleratorTable()
|
||||
|
||||
|
@ -79,23 +81,6 @@ class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
|||
|
||||
|
||||
|
||||
def _CommitCurrentChanges( self ):
|
||||
|
||||
service_keys_to_content_updates = {}
|
||||
|
||||
for page in self._tag_repositories.GetActivePages():
|
||||
|
||||
( service_key, content_updates ) = page.GetContentUpdates()
|
||||
|
||||
if len( content_updates ) > 0: service_keys_to_content_updates[ service_key ] = content_updates
|
||||
|
||||
|
||||
if len( service_keys_to_content_updates ) > 0:
|
||||
|
||||
HydrusGlobals.client_controller.WriteSynchronous( 'content_updates', service_keys_to_content_updates )
|
||||
|
||||
|
||||
|
||||
def _SetSearchFocus( self ):
|
||||
|
||||
page = self._tag_repositories.GetCurrentPage()
|
||||
|
@ -107,8 +92,6 @@ class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
|||
|
||||
if canvas_key == self._canvas_key:
|
||||
|
||||
self._CommitCurrentChanges()
|
||||
|
||||
self._current_media = ( new_media_singleton.Duplicate(), )
|
||||
|
||||
for page in self._tag_repositories.GetActivePages():
|
||||
|
@ -120,7 +103,41 @@ class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
|||
|
||||
def CommitChanges( self ):
|
||||
|
||||
self._CommitCurrentChanges()
|
||||
service_keys_to_content_updates = {}
|
||||
|
||||
for page in self._tag_repositories.GetActivePages():
|
||||
|
||||
( service_key, content_updates ) = page.GetContentUpdates()
|
||||
|
||||
if len( content_updates ) > 0:
|
||||
|
||||
service_keys_to_content_updates[ service_key ] = content_updates
|
||||
|
||||
|
||||
|
||||
if len( service_keys_to_content_updates ) > 0:
|
||||
|
||||
HydrusGlobals.client_controller.WriteSynchronous( 'content_updates', service_keys_to_content_updates )
|
||||
|
||||
|
||||
|
||||
def EventCharHook( self, event ):
|
||||
|
||||
if not HC.PLATFORM_LINUX:
|
||||
|
||||
# If I let this go uncaught, it propagates to the media viewer above, so an Enter or a '+' closes the window or zooms in!
|
||||
# The DoAllowNextEvent tells wx to gen regular key_down/char events so our text box gets them like normal, despite catching the event here
|
||||
|
||||
event.DoAllowNextEvent()
|
||||
|
||||
else:
|
||||
|
||||
# Top jej, the events weren't being generated after all in Linux, so here's a possibly borked patch for that:
|
||||
|
||||
HydrusGlobals.do_not_catch_char_hook = True
|
||||
|
||||
event.Skip()
|
||||
|
||||
|
||||
|
||||
def EventMenu( self, event ):
|
||||
|
@ -135,8 +152,28 @@ class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
|||
|
||||
wx.PostEvent( self.GetParent(), wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetTemporaryId( 'ok' ) ) )
|
||||
|
||||
elif command == 'set_search_focus': self._SetSearchFocus()
|
||||
else: event.Skip()
|
||||
elif command == 'set_search_focus':
|
||||
|
||||
self._SetSearchFocus()
|
||||
|
||||
elif command == 'canvas_show_next':
|
||||
|
||||
if self._canvas_key is not None:
|
||||
|
||||
HydrusGlobals.client_controller.pub( 'canvas_show_next', self._canvas_key )
|
||||
|
||||
|
||||
elif command == 'canvas_show_previous':
|
||||
|
||||
if self._canvas_key is not None:
|
||||
|
||||
HydrusGlobals.client_controller.pub( 'canvas_show_previous', self._canvas_key )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
event.Skip()
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -163,12 +200,15 @@ class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
|||
|
||||
class _Panel( wx.Panel ):
|
||||
|
||||
def __init__( self, parent, file_service_key, tag_service_key, media ):
|
||||
def __init__( self, parent, file_service_key, tag_service_key, media, immediate_commit ):
|
||||
|
||||
wx.Panel.__init__( self, parent )
|
||||
|
||||
self._file_service_key = file_service_key
|
||||
self._tag_service_key = tag_service_key
|
||||
self._immediate_commit = immediate_commit
|
||||
|
||||
self._content_updates = []
|
||||
|
||||
self._i_am_local_tag_service = self._tag_service_key == CC.LOCAL_TAG_SERVICE_KEY
|
||||
|
||||
|
@ -514,7 +554,19 @@ class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
|||
|
||||
|
||||
|
||||
self._content_updates.extend( content_updates )
|
||||
if self._immediate_commit:
|
||||
|
||||
if len( content_updates ) > 0:
|
||||
|
||||
service_keys_to_content_updates = { self._tag_service_key : content_updates }
|
||||
|
||||
HydrusGlobals.client_controller.WriteSynchronous( 'content_updates', service_keys_to_content_updates )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
self._content_updates.extend( content_updates )
|
||||
|
||||
|
||||
|
||||
self._tags_box.SetTagsByMedia( self._media, force_reload = True )
|
||||
|
@ -538,10 +590,12 @@ class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
|||
|
||||
|
||||
self.Ok()
|
||||
|
||||
parent = self.GetTopLevelParent().GetParent()
|
||||
|
||||
def do_it():
|
||||
|
||||
with ClientGUIDialogs.DialogAdvancedContentUpdate( self, self._tag_service_key, hashes ) as dlg:
|
||||
with ClientGUIDialogs.DialogAdvancedContentUpdate( parent, self._tag_service_key, hashes ) as dlg:
|
||||
|
||||
dlg.ShowModal()
|
||||
|
||||
|
@ -554,8 +608,6 @@ class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
|||
|
||||
self._new_options.SetBoolean( 'replace_siblings_on_manage_tags', self._collapse_siblings_checkbox.GetValue() )
|
||||
|
||||
HydrusGlobals.client_controller.Write( 'serialisable', self._new_options )
|
||||
|
||||
|
||||
def EventCopyTags( self, event ):
|
||||
|
||||
|
@ -636,9 +688,10 @@ class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
|||
|
||||
def GetContentUpdates( self ): return ( self._tag_service_key, self._content_updates )
|
||||
|
||||
def GetServiceKey( self ): return self._tag_service_key
|
||||
|
||||
def HasChanges( self ): return len( self._content_updates ) > 0
|
||||
def HasChanges( self ):
|
||||
|
||||
return len( self._content_updates ) > 0
|
||||
|
||||
|
||||
def Ok( self ):
|
||||
|
||||
|
@ -647,8 +700,6 @@ class ManageTagsPanel( wx.lib.scrolledpanel.ScrolledPanel ):
|
|||
|
||||
def SetMedia( self, media ):
|
||||
|
||||
self._content_updates = []
|
||||
|
||||
if media is None:
|
||||
|
||||
media = []
|
||||
|
|
|
@ -45,14 +45,7 @@ def GenerateNumpyImage( path ):
|
|||
|
||||
def GenerateNumPyImageFromPILImage( pil_image ):
|
||||
|
||||
if pil_image.mode == 'RGBA' or ( pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ) ):
|
||||
|
||||
if pil_image.mode == 'P': pil_image = pil_image.convert( 'RGBA' )
|
||||
|
||||
else:
|
||||
|
||||
if pil_image.mode != 'RGB': pil_image = pil_image.convert( 'RGB' )
|
||||
|
||||
pil_image = HydrusImageHandling.Dequantize( pil_image )
|
||||
|
||||
( w, h ) = pil_image.size
|
||||
|
||||
|
|
|
@ -38,15 +38,13 @@ def GenerateHydrusBitmapFromNumPyImage( numpy_image, compressed = True ):
|
|||
|
||||
def GenerateHydrusBitmapFromPILImage( pil_image, compressed = True ):
|
||||
|
||||
if pil_image.mode == 'RGBA' or ( pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ) ):
|
||||
|
||||
if pil_image.mode == 'P': pil_image = pil_image.convert( 'RGBA' )
|
||||
pil_image = HydrusImageHandling.Dequantize( pil_image )
|
||||
|
||||
if pil_image.mode == 'RGBA':
|
||||
|
||||
buffer_format = wx.BitmapBufferFormat_RGBA
|
||||
|
||||
else:
|
||||
|
||||
if pil_image.mode != 'RGB': pil_image = pil_image.convert( 'RGB' )
|
||||
elif pil_image.mode == 'RGB':
|
||||
|
||||
buffer_format = wx.BitmapBufferFormat_RGB
|
||||
|
||||
|
|
|
@ -89,19 +89,23 @@ class GIFRenderer( object ):
|
|||
|
||||
else:
|
||||
|
||||
if self._pil_image.mode == 'P' and 'transparency' in self._pil_image.info:
|
||||
current_frame = pil_image = HydrusImageHandling.Dequantize( self._pil_image )
|
||||
|
||||
if current_frame.mode == 'RGBA':
|
||||
|
||||
# The gif problems seem to be here.
|
||||
# I think that while some transparent animated gifs expect their frames to be pasted over each other, the others expect them to be fresh every time.
|
||||
# Determining which is which doesn't seem to be available in PIL, and PIL's internal calculations seem to not be 100% correct.
|
||||
# Just letting PIL try to do it on its own with P rather than converting to RGBA sometimes produces artifacts
|
||||
if self._pil_canvas is None:
|
||||
|
||||
self._pil_canvas = current_frame
|
||||
|
||||
else:
|
||||
|
||||
self._pil_canvas.paste( current_frame, None, current_frame ) # use the rgba image as its own mask
|
||||
|
||||
|
||||
current_frame = self._pil_image.convert( 'RGBA' )
|
||||
elif current_frame.mode == 'RGB':
|
||||
|
||||
if self._pil_canvas is None: self._pil_canvas = current_frame
|
||||
else: self._pil_canvas.paste( current_frame, None, current_frame ) # use the rgba image as its own mask
|
||||
self._pil_canvas = current_frame
|
||||
|
||||
else: self._pil_canvas = self._pil_image
|
||||
|
||||
numpy_image = ClientImageHandling.GenerateNumPyImageFromPILImage( self._pil_canvas )
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 17
|
||||
SOFTWARE_VERSION = 211
|
||||
SOFTWARE_VERSION = 212
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
|
|
@ -43,20 +43,20 @@ header_and_mime = [
|
|||
|
||||
def SaveThumbnailToStream( pil_image, dimensions, f ):
|
||||
|
||||
# when the palette is limited, the thumbnail antialias won't add new colours, so you get nearest-neighbour-like behaviour
|
||||
|
||||
save_to_png = pil_image.format == 'PNG'
|
||||
|
||||
pil_image = HydrusImageHandling.Dequantize( pil_image )
|
||||
|
||||
HydrusImageHandling.EfficientlyThumbnailPILImage( pil_image, dimensions )
|
||||
|
||||
if pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ):
|
||||
|
||||
pil_image.save( f, 'PNG', transparency = pil_image.info[ 'transparency' ] )
|
||||
|
||||
elif pil_image.mode == 'RGBA':
|
||||
if save_to_png:
|
||||
|
||||
pil_image.save( f, 'PNG' )
|
||||
|
||||
else:
|
||||
|
||||
pil_image = pil_image.convert( 'RGB' )
|
||||
|
||||
pil_image.save( f, 'JPEG', quality = 92 )
|
||||
|
||||
|
||||
|
|
|
@ -17,3 +17,5 @@ do_idle_shutdown_work = False
|
|||
shutdown_complete = False
|
||||
restart = False
|
||||
emergency_exit = False
|
||||
|
||||
do_not_catch_char_hook = False
|
||||
|
|
|
@ -43,6 +43,22 @@ def ConvertToPngIfBmp( path ):
|
|||
|
||||
|
||||
|
||||
def Dequantize( pil_image ):
|
||||
|
||||
if pil_image.mode not in ( 'RGBA', 'RGB' ):
|
||||
|
||||
if pil_image.mode == 'LA' or ( pil_image.mode == 'P' and pil_image.info.has_key( 'transparency' ) ):
|
||||
|
||||
pil_image = pil_image.convert( 'RGBA' )
|
||||
|
||||
else:
|
||||
|
||||
pil_image = pil_image.convert( 'RGB' )
|
||||
|
||||
|
||||
|
||||
return pil_image
|
||||
|
||||
def EfficientlyResizePILImage( pil_image, ( target_x, target_y ) ):
|
||||
|
||||
( im_x, im_y ) = pil_image.size
|
||||
|
|
|
@ -273,7 +273,7 @@ def LaunchFile( path ):
|
|||
|
||||
def MakeFileWritable( path ):
|
||||
|
||||
try: os.chmod( dest_path, stat.S_IWRITE | stat.S_IREAD )
|
||||
try: os.chmod( path, stat.S_IWRITE | stat.S_IREAD )
|
||||
except: pass
|
||||
|
||||
def MergeFile( source, dest ):
|
||||
|
|
Loading…
Reference in New Issue