Version 212

This commit is contained in:
Hydrus Network Developer 2016-06-29 14:55:46 -05:00
parent aae8d9cc97
commit 0beb3c6e62
21 changed files with 734 additions and 469 deletions

View File

@ -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>

View File

@ -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>

View File

@ -773,7 +773,7 @@ class ClientFilesManager( object ):
try:
HydrusPaths.DeletePath( path )
HydrusPaths.DeletePath( full_size_path )
except:

View File

@ -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' ) )

View File

@ -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:

View File

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

View File

@ -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

View File

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

View File

@ -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

View File

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

View File

@ -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

View File

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

View File

@ -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 = []

View File

@ -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

View File

@ -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

View File

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

View File

@ -48,7 +48,7 @@ options = {}
# Misc
NETWORK_VERSION = 17
SOFTWARE_VERSION = 211
SOFTWARE_VERSION = 212
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

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

View File

@ -17,3 +17,5 @@ do_idle_shutdown_work = False
shutdown_complete = False
restart = False
emergency_exit = False
do_not_catch_char_hook = False

View File

@ -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

View File

@ -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 ):