Version 148
|
@ -8,6 +8,26 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 148</h3></li>
|
||||
<ul>
|
||||
<li>rewrote thumbnail canvas and scrolling code from a crashtastic monolithic bmp to a lighter, faster, and more flexible page buffer</li>
|
||||
<li>all custom gui elements should be less flickery</li>
|
||||
<li>the manage tags dialog will now grow significantly taller if its parent window is also tall</li>
|
||||
<li>dialogs launched by the media viewer will initially position themselves according to that, rather than the main gui</li>
|
||||
<li>dialogs launched by controls will initially position themselves according the control's toplevelwindow, as originally intended</li>
|
||||
<li>thumbnail download (for file repositories) no longer happens silently in the background; it will now occur in the repository sync daemon, reporting its progress in the normal repo sync popup</li>
|
||||
<li>missing thumbnails are now replaced with the hydrus psi symbol silently, with a simple statement written to the log</li>
|
||||
<li>fixed a tiny typo in update error code that was reporting version wrong</li>
|
||||
<li>fixed a typo bug in gettagarchivetags</li>
|
||||
<li>fixed a typo that had broken namespace sorting</li>
|
||||
<li>numerous other single-line miscellaneous bug fixes</li>
|
||||
<li>fixed a bug with displaying media with size (0,0)</li>
|
||||
<li>fixed a bug with zooming in flash files</li>
|
||||
<li>improved some buggy tag selection logic that was sometimes desyncing indices between menu popup and selection</li>
|
||||
<li>tags will now stay selected even through changes to the tags list</li>
|
||||
<li>any attempt to close the autocomplete dropdown floating frame should now bump the close event up to the whole program</li>
|
||||
<li>linux release now includes source code alongside executables</li>
|
||||
</ul>
|
||||
<li><h3>version 147</h3></li>
|
||||
<ul>
|
||||
<li>fixed a problem when trying to do a multi-release update that contained the v146 update</li>
|
||||
|
|
|
@ -2681,8 +2681,7 @@ class ThumbnailCache( object ):
|
|||
|
||||
except Exception as e:
|
||||
|
||||
HC.ShowText( path )
|
||||
HC.ShowException( e )
|
||||
print( 'Could not find the thumbnail for ' + hash.encode( 'hex' ) + '!' )
|
||||
|
||||
return self._special_thumbs[ 'hydrus' ]
|
||||
|
||||
|
|
|
@ -247,7 +247,7 @@ class MessageDB( object ):
|
|||
|
||||
temp_path = HC.GetTempPath()
|
||||
|
||||
with open( temp_path, 'wb' ) as f: f.write( temp_path )
|
||||
with open( temp_path, 'wb' ) as f: f.write( file )
|
||||
|
||||
try:
|
||||
|
||||
|
@ -3262,7 +3262,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
for ( archive_name, hta ) in self._tag_archives.items():
|
||||
|
||||
hash_type == hta.GetHashType()
|
||||
hash_type = hta.GetHashType()
|
||||
|
||||
sha256_to_archive_hashes = {}
|
||||
|
||||
|
@ -3654,7 +3654,6 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
notify_new_pending = False
|
||||
notify_new_parents = False
|
||||
notify_new_siblings = False
|
||||
notify_new_thumbnails = False
|
||||
|
||||
for ( service_key, content_updates ) in service_keys_to_content_updates.items():
|
||||
|
||||
|
@ -3690,8 +3689,6 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
self._AddFile( service_id, hash_id, size, mime, timestamp, width, height, duration, num_frames, num_words )
|
||||
|
||||
notify_new_thumbnails = True
|
||||
|
||||
elif action == HC.CONTENT_UPDATE_PENDING:
|
||||
|
||||
hashes = row
|
||||
|
@ -4114,7 +4111,6 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
self.pub_after_commit( 'notify_new_siblings' )
|
||||
self.pub_after_commit( 'notify_new_parents' )
|
||||
|
||||
if notify_new_thumbnails: self.pub_after_commit( 'notify_new_thumbnails' )
|
||||
|
||||
self.pub_content_updates_after_commit( service_keys_to_content_updates )
|
||||
|
||||
|
@ -4898,7 +4894,7 @@ class DB( ServiceDB ):
|
|||
|
||||
self._c.execute( 'ROLLBACK' )
|
||||
|
||||
raise Exception( 'Updating the client db to version ' + HC.u( version ) + ' caused this error:' + os.linesep + traceback.format_exc() )
|
||||
raise Exception( 'Updating the client db to version ' + HC.u( version + 1 ) + ' caused this error:' + os.linesep + traceback.format_exc() )
|
||||
|
||||
|
||||
( version, ) = self._c.execute( 'SELECT version FROM version;' ).fetchone()
|
||||
|
@ -6182,7 +6178,6 @@ class DB( ServiceDB ):
|
|||
HydrusThreading.DAEMONWorker( 'CheckImportFolders', DAEMONCheckImportFolders, ( 'notify_restart_import_folders_daemon', 'notify_new_import_folders' ), period = 180 )
|
||||
HydrusThreading.DAEMONWorker( 'CheckExportFolders', DAEMONCheckExportFolders, ( 'notify_restart_export_folders_daemon', 'notify_new_export_folders' ), period = 180 )
|
||||
HydrusThreading.DAEMONWorker( 'DownloadFiles', DAEMONDownloadFiles, ( 'notify_new_downloads', 'notify_new_permissions' ) )
|
||||
HydrusThreading.DAEMONWorker( 'DownloadThumbnails', DAEMONDownloadThumbnails, ( 'notify_new_permissions', 'notify_new_thumbnails' ) )
|
||||
HydrusThreading.DAEMONWorker( 'ResizeThumbnails', DAEMONResizeThumbnails, period = 3600 * 24, init_wait = 600 )
|
||||
HydrusThreading.DAEMONWorker( 'SynchroniseAccounts', DAEMONSynchroniseAccounts, ( 'permissions_are_stale', ) )
|
||||
HydrusThreading.DAEMONWorker( 'SynchroniseRepositories', DAEMONSynchroniseRepositories, ( 'notify_restart_repo_sync_daemon', 'notify_new_permissions' ) )
|
||||
|
@ -6475,62 +6470,6 @@ def DAEMONDownloadFiles():
|
|||
if num_downloads == 0: HC.pubsub.pub( 'downloads_status', 'no file downloads' )
|
||||
elif num_downloads > 0: HC.pubsub.pub( 'downloads_status', HC.ConvertIntToPrettyString( num_downloads ) + ' inactive file downloads' )
|
||||
|
||||
def DAEMONDownloadThumbnails():
|
||||
|
||||
services = HC.app.ReadDaemon( 'services', ( HC.FILE_REPOSITORY, ) )
|
||||
|
||||
thumbnail_hashes_i_have = CC.GetAllThumbnailHashes()
|
||||
|
||||
for service in services:
|
||||
|
||||
service_key = service.GetServiceKey()
|
||||
|
||||
thumbnail_hashes_i_should_have = HC.app.ReadDaemon( 'thumbnail_hashes_i_should_have', service_key )
|
||||
|
||||
thumbnail_hashes_i_need = list( thumbnail_hashes_i_should_have - thumbnail_hashes_i_have )
|
||||
|
||||
if len( thumbnail_hashes_i_need ) > 0:
|
||||
|
||||
try: file_repository = HC.app.GetManager( 'services' ).GetService( service_key )
|
||||
except: continue
|
||||
|
||||
if file_repository.CanDownload():
|
||||
|
||||
try:
|
||||
|
||||
num_per_round = 50
|
||||
|
||||
for i in range( 0, len( thumbnail_hashes_i_need ), num_per_round ):
|
||||
|
||||
if HC.shutdown: return
|
||||
|
||||
thumbnails = []
|
||||
|
||||
for hash in thumbnail_hashes_i_need[ i : i + num_per_round ]:
|
||||
|
||||
request_args = { 'hash' : hash.encode( 'hex' ) }
|
||||
|
||||
thumbnail = file_repository.Request( HC.GET, 'thumbnail', request_args = request_args )
|
||||
|
||||
thumbnails.append( ( hash, thumbnail ) )
|
||||
|
||||
|
||||
HC.app.WaitUntilGoodTimeToUseGUIThread()
|
||||
|
||||
HC.app.WriteSynchronous( 'thumbnails', thumbnails )
|
||||
|
||||
HC.pubsub.pub( 'add_thumbnail_count', service_key, len( thumbnails ) )
|
||||
|
||||
thumbnail_hashes_i_have.update( { hash for ( hash, thumbnail ) in thumbnails } )
|
||||
|
||||
time.sleep( 0.25 )
|
||||
|
||||
|
||||
except: pass # if bad download, the repo gets dinged an error. no need to do anything here
|
||||
|
||||
|
||||
|
||||
|
||||
def DAEMONFlushServiceUpdates( list_of_service_keys_to_service_updates ):
|
||||
|
||||
service_keys_to_service_updates = HC.MergeKeyToListDicts( list_of_service_keys_to_service_updates )
|
||||
|
@ -7105,6 +7044,89 @@ def DAEMONSynchroniseRepositories():
|
|||
job_key.DeleteVariable( 'popup_message_text_2' )
|
||||
job_key.DeleteVariable( 'popup_message_gauge_2' )
|
||||
|
||||
if service_type == HC.FILE_REPOSITORY and service.CanDownload():
|
||||
|
||||
job_key.SetVariable( 'popup_message_text_1', 'reviewing existing thumbnails' )
|
||||
|
||||
thumbnail_hashes_i_have = CC.GetAllThumbnailHashes()
|
||||
|
||||
job_key.SetVariable( 'popup_message_text_1', 'reviewing service thumbnails' )
|
||||
|
||||
thumbnail_hashes_i_should_have = HC.app.ReadDaemon( 'thumbnail_hashes_i_should_have', service_key )
|
||||
|
||||
thumbnail_hashes_i_need = thumbnail_hashes_i_should_have.difference( thumbnail_hashes_i_have )
|
||||
|
||||
if len( thumbnail_hashes_i_need ) > 0:
|
||||
|
||||
while job_key.IsPaused() or job_key.IsCancelled() or HC.options[ 'pause_repo_sync' ] or HC.shutdown:
|
||||
|
||||
time.sleep( 0.1 )
|
||||
|
||||
if job_key.IsPaused(): job_key.SetVariable( 'popup_message_text_1', 'paused' )
|
||||
|
||||
if HC.options[ 'pause_repo_sync' ]: job_key.SetVariable( 'popup_message_text_1', 'repository synchronisation paused' )
|
||||
|
||||
if HC.shutdown: raise Exception( 'application shutting down!' )
|
||||
|
||||
if job_key.IsCancelled():
|
||||
|
||||
job_key.SetVariable( 'popup_message_text_1', 'cancelled' )
|
||||
|
||||
print( HC.ConvertJobKeyToString( job_key ) )
|
||||
|
||||
return
|
||||
|
||||
|
||||
if HC.repos_changed:
|
||||
|
||||
job_key.SetVariable( 'popup_message_text_1', 'repositories were changed during processing; this job was abandoned' )
|
||||
|
||||
print( HC.ConvertJobKeyToString( job_key ) )
|
||||
|
||||
HC.pubsub.pub( 'notify_restart_repo_sync_daemon' )
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
def SaveThumbnails( batch_of_thumbnails ):
|
||||
|
||||
job_key.SetVariable( 'popup_message_text_1', 'saving thumbnails to database' )
|
||||
|
||||
HC.app.WriteSynchronous( 'thumbnails', batch_of_thumbnails )
|
||||
|
||||
HC.pubsub.pub( 'add_thumbnail_count', service_key, len( batch_of_thumbnails ) )
|
||||
|
||||
|
||||
thumbnails = []
|
||||
|
||||
for ( i, hash ) in enumerate( thumbnail_hashes_i_need ):
|
||||
|
||||
job_key.SetVariable( 'popup_message_text_1', 'downloading thumbnail ' + HC.ConvertIntToPrettyString( i ) + '/' + HC.ConvertIntToPrettyString( len( thumbnail_hashes_i_need ) ) )
|
||||
job_key.SetVariable( 'popup_message_gauge_1', ( i, len( thumbnail_hashes_i_need ) ) )
|
||||
|
||||
request_args = { 'hash' : hash.encode( 'hex' ) }
|
||||
|
||||
thumbnail = service.Request( HC.GET, 'thumbnail', request_args = request_args )
|
||||
|
||||
thumbnails.append( ( hash, thumbnail ) )
|
||||
|
||||
if i % 50 == 0:
|
||||
|
||||
SaveThumbnails( thumbnails )
|
||||
|
||||
thumbnails = []
|
||||
|
||||
|
||||
HC.app.WaitUntilGoodTimeToUseGUIThread()
|
||||
|
||||
|
||||
if len( thumbnails ) > 0: SaveThumbnails( thumbnails )
|
||||
|
||||
job_key.DeleteVariable( 'popup_message_gauge_1' )
|
||||
|
||||
|
||||
|
||||
job_key.SetVariable( 'popup_message_title', 'repository synchronisation - ' + name + ' - finished' )
|
||||
|
||||
updates_text = HC.ConvertIntToPrettyString( num_updates_downloaded ) + ' updates downloaded, ' + HC.ConvertIntToPrettyString( num_updates_processed ) + ' updates processed'
|
||||
|
|
|
@ -1925,7 +1925,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
|
||||
def ImportFiles( self, paths ): self._ImportFiles( paths )
|
||||
|
||||
'''
|
||||
def NewCompose( self, identity ):
|
||||
|
||||
draft_key = os.urandom( 32 )
|
||||
|
@ -1941,7 +1941,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
FrameComposeMessage( empty_draft_message )
|
||||
|
||||
|
||||
'''
|
||||
def NewPageImportGallery( self, gallery_name, gallery_type ): self._NewPageImportGallery( gallery_name, gallery_type )
|
||||
|
||||
def NewPageImportHDD( self, paths_info, advanced_import_options = {}, paths_to_tags = {}, delete_after_success = False ):
|
||||
|
@ -2126,7 +2126,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
for page in [ self._notebook.GetPage( i ) for i in range( self._notebook.GetPageCount() ) ]: page.TestAbleToClose()
|
||||
|
||||
|
||||
'''
|
||||
class FrameComposeMessage( ClientGUICommon.Frame ):
|
||||
|
||||
def __init__( self, empty_draft_message ):
|
||||
|
@ -2158,7 +2158,7 @@ class FrameComposeMessage( ClientGUICommon.Frame ):
|
|||
|
||||
if draft_key == self._draft_panel.GetDraftKey(): self.Close()
|
||||
|
||||
|
||||
'''
|
||||
class FrameReviewServices( ClientGUICommon.Frame ):
|
||||
|
||||
def __init__( self ):
|
||||
|
@ -2974,6 +2974,7 @@ class FrameSplash( ClientGUICommon.Frame ):
|
|||
self.Bind( wx.EVT_MOTION, self.EventDrag )
|
||||
self.Bind( wx.EVT_LEFT_DOWN, self.EventDragBegin )
|
||||
self.Bind( wx.EVT_LEFT_UP, self.EventDragEnd )
|
||||
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
|
||||
|
||||
if action == 'boot':
|
||||
|
||||
|
@ -3074,6 +3075,8 @@ class FrameSplash( ClientGUICommon.Frame ):
|
|||
event.Skip()
|
||||
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._bmp )
|
||||
|
||||
def ExitApp( self ):
|
||||
|
|
|
@ -63,6 +63,8 @@ def CalculateCanvasZoom( media, ( canvas_width, canvas_height ) ):
|
|||
|
||||
( media_width, media_height ) = media.GetResolution()
|
||||
|
||||
if media_width == 0 or media_height == 0: return 1.0
|
||||
|
||||
if ShouldHaveAnimationBar( media ): canvas_height -= ANIMATED_SCANBAR_HEIGHT
|
||||
|
||||
if media.GetMime() in NON_LARGABLY_ZOOMABLE_MIMES: canvas_width -= 2
|
||||
|
@ -143,6 +145,7 @@ class Animation( wx.Window ):
|
|||
self.Bind( wx.EVT_TIMER, self.TIMEREventVideo, id = ID_TIMER_VIDEO )
|
||||
self.Bind( wx.EVT_MOUSE_EVENTS, self.EventPropagateMouse )
|
||||
self.Bind( wx.EVT_KEY_UP, self.EventPropagateKey )
|
||||
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
|
||||
|
||||
self.EventResize( None )
|
||||
|
||||
|
@ -199,6 +202,8 @@ class Animation( wx.Window ):
|
|||
|
||||
def CurrentFrame( self ): return self._current_frame_index
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
def EventPropagateKey( self, event ):
|
||||
|
@ -350,6 +355,7 @@ class AnimationBar( wx.Window ):
|
|||
self.Bind( wx.EVT_TIMER, self.TIMEREventUpdate, id = ID_TIMER_ANIMATION_BAR_UPDATE )
|
||||
self.Bind( wx.EVT_PAINT, self.EventPaint )
|
||||
self.Bind( wx.EVT_SIZE, self.EventResize )
|
||||
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
|
||||
|
||||
self._timer_update = wx.Timer( self, id = ID_TIMER_ANIMATION_BAR_UPDATE )
|
||||
self._timer_update.Start( 100, wx.TIMER_CONTINUOUS )
|
||||
|
@ -388,6 +394,8 @@ class AnimationBar( wx.Window ):
|
|||
dc.DrawText( s, my_width - x - 3, 3 )
|
||||
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventMouse( self, event ):
|
||||
|
||||
CC.CAN_HIDE_MOUSE = False
|
||||
|
@ -503,6 +511,7 @@ class Canvas( object ):
|
|||
self.Bind( wx.EVT_SIZE, self.EventResize )
|
||||
|
||||
self.Bind( wx.EVT_PAINT, self.EventPaint )
|
||||
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
|
||||
|
||||
|
||||
def _CopyHashToClipboard( self ):
|
||||
|
@ -659,6 +668,8 @@ class Canvas( object ):
|
|||
|
||||
# because of the event passing under mouse, we want to preserve whitespace around flash
|
||||
|
||||
( my_width, my_height ) = self.GetClientSize()
|
||||
|
||||
( new_media_width, new_media_height ) = CalculateMediaContainerSize( self._current_display_media, zoom )
|
||||
|
||||
if new_media_width >= my_width or new_media_height >= my_height: return
|
||||
|
@ -749,7 +760,9 @@ class Canvas( object ):
|
|||
|
||||
|
||||
|
||||
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp, wx.BUFFER_VIRTUAL_AREA )
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
def EventResize( self, event ):
|
||||
|
||||
|
@ -3138,7 +3151,7 @@ class RatingsFilterFrameNumerical( ClientGUICommon.FrameThatResizes ):
|
|||
|
||||
def _Skip( self ):
|
||||
|
||||
if len( self._media_still_to_rate ) == 0: self.EventClose()
|
||||
if len( self._media_still_to_rate ) == 0: self.EventClose( None )
|
||||
else: self._ShowNewMedia()
|
||||
|
||||
|
||||
|
@ -3821,6 +3834,7 @@ class EmbedButton( wx.Window ):
|
|||
|
||||
self.Bind( wx.EVT_PAINT, self.EventPaint )
|
||||
self.Bind( wx.EVT_SIZE, self.EventResize )
|
||||
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
|
||||
|
||||
|
||||
def _Redraw( self ):
|
||||
|
@ -3864,6 +3878,8 @@ class EmbedButton( wx.Window ):
|
|||
dc.DrawPolygon( points )
|
||||
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
def EventResize( self, event ):
|
||||
|
@ -4014,6 +4030,7 @@ class StaticImage( wx.Window ):
|
|||
self.Bind( wx.EVT_SIZE, self.EventResize )
|
||||
self.Bind( wx.EVT_TIMER, self.TIMEREventRenderWait, id = ID_TIMER_RENDER_WAIT )
|
||||
self.Bind( wx.EVT_MOUSE_EVENTS, self.EventPropagateMouse )
|
||||
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
|
||||
|
||||
self.EventResize( None )
|
||||
|
||||
|
@ -4056,6 +4073,8 @@ class StaticImage( wx.Window ):
|
|||
|
||||
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
def EventPropagateMouse( self, event ):
|
||||
|
|
|
@ -140,6 +140,8 @@ class AutoCompleteDropdown( wx.Panel ):
|
|||
|
||||
self._dropdown_window.Show()
|
||||
|
||||
self._dropdown_window.Bind( wx.EVT_CLOSE, self.EventCloseDropdown )
|
||||
|
||||
self._dropdown_hidden = True
|
||||
|
||||
self._list_height = 250
|
||||
|
@ -237,6 +239,11 @@ class AutoCompleteDropdown( wx.Panel ):
|
|||
|
||||
def _UpdateList( self ): pass
|
||||
|
||||
def EventCloseDropdown( self, event ):
|
||||
|
||||
HC.app.GetGUI().EventExit( event )
|
||||
|
||||
|
||||
def EventKeyDown( self, event ):
|
||||
|
||||
if event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ) and self._text_ctrl.GetValue() == '' and len( self._dropdown_list ) == 0: self._BroadcastChoice( None )
|
||||
|
@ -997,11 +1004,14 @@ class BufferedWindow( wx.Window ):
|
|||
|
||||
self.Bind( wx.EVT_PAINT, self.EventPaint )
|
||||
self.Bind( wx.EVT_SIZE, self.EventResize )
|
||||
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
|
||||
|
||||
|
||||
|
||||
def GetDC( self ): return wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp )
|
||||
|
||||
def EventResize( self, event ):
|
||||
|
@ -1773,6 +1783,7 @@ class ListBox( wx.ScrolledWindow ):
|
|||
self._canvas_bmp = wx.EmptyBitmap( 0, 0, 24 )
|
||||
|
||||
self._current_selected_index = None
|
||||
self._current_selected_term = None
|
||||
|
||||
dc = self._GetScrolledDC()
|
||||
|
||||
|
@ -1788,6 +1799,7 @@ class ListBox( wx.ScrolledWindow ):
|
|||
|
||||
self.Bind( wx.EVT_PAINT, self.EventPaint )
|
||||
self.Bind( wx.EVT_SIZE, self.EventResize )
|
||||
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
|
||||
|
||||
self.Bind( wx.EVT_LEFT_DOWN, self.EventMouseSelect )
|
||||
self.Bind( wx.EVT_LEFT_DCLICK, self.EventDClick )
|
||||
|
@ -1935,9 +1947,13 @@ class ListBox( wx.ScrolledWindow ):
|
|||
self._current_selected_index = index
|
||||
|
||||
if old_index is not None: self._DrawText( old_index )
|
||||
if self._current_selected_index is not None: self._DrawText( self._current_selected_index )
|
||||
|
||||
if self._current_selected_index is not None:
|
||||
if self._current_selected_index is None: self._current_selected_term = None
|
||||
else:
|
||||
|
||||
self._current_selected_term = self._strings_to_terms[ self._ordered_strings[ self._current_selected_index ] ]
|
||||
|
||||
self._DrawText( self._current_selected_index )
|
||||
|
||||
# scroll to index, if needed
|
||||
|
||||
|
@ -1971,8 +1987,24 @@ class ListBox( wx.ScrolledWindow ):
|
|||
def _TextsHaveChanged( self ):
|
||||
|
||||
self._drawn_up_to = 0
|
||||
|
||||
self._current_selected_index = None
|
||||
|
||||
if self._current_selected_term is not None:
|
||||
|
||||
for ( s, term ) in self._strings_to_terms.items():
|
||||
|
||||
if term == self._current_selected_term:
|
||||
|
||||
self._current_selected_index = self._ordered_strings.index( s )
|
||||
|
||||
break
|
||||
|
||||
|
||||
|
||||
if self._current_selected_index is None: self._current_selected_term = None
|
||||
|
||||
|
||||
total_height = self._text_y * len( self._ordered_strings )
|
||||
|
||||
( my_x, my_y ) = self._canvas_bmp.GetSize()
|
||||
|
@ -1995,6 +2027,8 @@ class ListBox( wx.ScrolledWindow ):
|
|||
|
||||
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
||||
def EventKeyDown( self, event ):
|
||||
|
||||
key_code = event.GetKeyCode()
|
||||
|
|
|
@ -99,7 +99,10 @@ class Dialog( wx.Dialog ):
|
|||
|
||||
if parent is not None and position == 'topleft':
|
||||
|
||||
( pos_x, pos_y ) = HC.app.GetGUI().GetPositionTuple()
|
||||
if issubclass( type( parent ), wx.TopLevelWindow ): parent_tlp = parent
|
||||
else: parent_tlp = parent.GetTopLevelParent()
|
||||
|
||||
( pos_x, pos_y ) = parent_tlp.GetPositionTuple()
|
||||
|
||||
pos = ( pos_x + 50, pos_y + 100 )
|
||||
|
||||
|
|
|
@ -5968,7 +5968,7 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
panel = self._listbook.GetCurrentPage()
|
||||
|
||||
if sub_panel is not None:
|
||||
if panel is not None:
|
||||
|
||||
( name, info ) = panel.GetSubscription()
|
||||
|
||||
|
@ -7690,7 +7690,9 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
|
||||
( x, y ) = self.GetEffectiveMinSize()
|
||||
|
||||
self.SetInitialSize( ( x + 200, 500 ) )
|
||||
( parent_width, parent_height ) = parent.GetSize()
|
||||
|
||||
self.SetInitialSize( ( x + 200, max( 500, parent_height - 200 ) ) )
|
||||
|
||||
|
||||
self._file_service_key = file_service_key
|
||||
|
|
|
@ -389,7 +389,7 @@ class ListeningMediaList( MediaList ):
|
|||
|
||||
if self._collect_by is not None:
|
||||
|
||||
keys_to_medias = self._CalculateCollectionKeysToMedias( collect_by, new_media )
|
||||
keys_to_medias = self._CalculateCollectionKeysToMedias( self._collect_by, new_media )
|
||||
|
||||
new_media = []
|
||||
|
||||
|
@ -548,17 +548,6 @@ class MediaCollection( MediaList, Media ):
|
|||
|
||||
|
||||
|
||||
def GetHashes( self, discriminant = None, not_uploaded_to = None ):
|
||||
|
||||
if discriminant is not None:
|
||||
if ( discriminant == CC.DISCRIMINANT_INBOX and not self._inbox ) or ( discriminant == CC.DISCRIMINANT_ARCHIVE and not self._archive ) or ( discriminant == CC.DISCRIMINANT_LOCAL and not self._locations_manager().HasLocal() ) or ( discriminant == CC.DISCRIMINANT_NOT_LOCAL and self._locations_manager().HasLocal() ): return set()
|
||||
|
||||
if not_uploaded_to is not None:
|
||||
if not_uploaded_to in self._locations_manager.GetCurrentRemote(): return set()
|
||||
|
||||
return self._hashes
|
||||
|
||||
|
||||
def GetLocationsManager( self ): return self._locations_manager
|
||||
|
||||
def GetMime( self ): return HC.APPLICATION_HYDRUS_CLIENT_COLLECTION
|
||||
|
|
|
@ -66,7 +66,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 15
|
||||
SOFTWARE_VERSION = 147
|
||||
SOFTWARE_VERSION = 148
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
@ -2691,7 +2691,7 @@ class ServerToClientUpdate( HydrusYAMLBase ):
|
|||
|
||||
|
||||
|
||||
def GetHashes( self ): return set( hash_ids_to_hashes.values() )
|
||||
def GetHashes( self ): return set( self._hash_ids_to_hashes.values() )
|
||||
|
||||
def GetTags( self ):
|
||||
|
||||
|
|
|
@ -113,6 +113,8 @@ class Downloader( object ):
|
|||
return HC.http.Request( HC.GET, url, request_headers = request_headers, report_hooks = report_hooks, response_to_path = response_to_path )
|
||||
|
||||
|
||||
def _GetNextGalleryPageURL( self ): return ''
|
||||
|
||||
def _GetNextGalleryPageURLs( self ): return ( self._GetNextGalleryPageURL(), )
|
||||
|
||||
def AddReportHook( self, hook ): self._report_hooks.append( hook )
|
||||
|
@ -1449,7 +1451,7 @@ class ImportController( object ):
|
|||
|
||||
with self._lock:
|
||||
|
||||
if s in self._pending_import_queue_jobs:
|
||||
if job in self._pending_import_queue_jobs:
|
||||
|
||||
index = self._pending_import_queue_jobs.index( job )
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ def EfficientlyThumbnailPILImage( pil_image, ( target_x, target_y ) ):
|
|||
|
||||
def GenerateNumpyImage( path ):
|
||||
|
||||
numpy_image = cv2.imread( self._path, flags = -1 ) # flags = -1 loads alpha channel, if present
|
||||
numpy_image = cv2.imread( path, flags = -1 ) # flags = -1 loads alpha channel, if present
|
||||
|
||||
( y, x, depth ) = numpy_image.shape
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import HydrusConstants as HC
|
||||
import HydrusExceptions
|
||||
import httplib
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import urllib
|
||||
|
@ -51,7 +52,7 @@ def CheckHydrusVersion( service_key, service_type, response_headers ):
|
|||
if network_version > HC.NETWORK_VERSION: message = 'Your client is out of date; please download the latest release.'
|
||||
else: message = 'The server is out of date; please ask its admin to update to the latest release.'
|
||||
|
||||
raise HydrusExceptions.NetworkVersionException( 'Network version mismatch! The server\'s network version was ' + u( network_version ) + ', whereas your client\'s is ' + u( HC.NETWORK_VERSION ) + '! ' + message )
|
||||
raise HydrusExceptions.NetworkVersionException( 'Network version mismatch! The server\'s network version was ' + HC.u( network_version ) + ', whereas your client\'s is ' + HC.u( HC.NETWORK_VERSION ) + '! ' + message )
|
||||
|
||||
|
||||
def ConvertHydrusGETArgsToQuery( request_args ):
|
||||
|
@ -325,7 +326,7 @@ class HTTPConnection( object ):
|
|||
elif content_type in HC.mime_enum_lookup and HC.mime_enum_lookup[ content_type ] == HC.APPLICATION_YAML:
|
||||
|
||||
try: parsed_response = yaml.safe_load( data )
|
||||
except Exception as e: raise HydrusExceptions.NetworkVersionException( 'Failed to parse a response object!' + os.linesep + u( e ) )
|
||||
except Exception as e: raise HydrusExceptions.NetworkVersionException( 'Failed to parse a response object!' + os.linesep + HC.u( e ) )
|
||||
|
||||
elif content_type == 'text/html':
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ class HydrusMessagingSessionManagerServer( object ):
|
|||
|
||||
for ( service_key, session_tuples ) in existing_sessions:
|
||||
|
||||
self._service_keys_to_sessions[ service_key ] = { session_key : ( account, name, expires ) for ( session_Key, account, name, expires ) in session_tuples }
|
||||
self._service_keys_to_sessions[ service_key ] = { session_key : ( account, name, expires ) for ( session_key, account, name, expires ) in session_tuples }
|
||||
|
||||
|
||||
self._lock = threading.Lock()
|
||||
|
|
|
@ -324,7 +324,7 @@ class TagsManagerSimple( object ):
|
|||
|
||||
tags = [ tag for tag in combined if tag.startswith( namespace + ':' ) ]
|
||||
|
||||
if collapse: tags = list( siblings_manager.CollapseTags( tags ) )
|
||||
if collapse_siblings: tags = list( siblings_manager.CollapseTags( tags ) )
|
||||
|
||||
tags = [ tag.split( ':', 1 )[1] for tag in tags ]
|
||||
|
||||
|
|
|
@ -693,9 +693,9 @@ class GIFRenderer( object ):
|
|||
|
||||
if self._pil_image.palette == self._pil_global_palette: # for some reason, when pil falls back from local palette to global palette, a bunch of important variables reset!
|
||||
|
||||
pil_image.palette.dirty = self._pil_dirty
|
||||
pil_image.palette.mode = self._pil_mode
|
||||
pil_image.palette.rawmode = self._pil_rawmode
|
||||
self._pil_image.palette.dirty = self._pil_dirty
|
||||
self._pil_image.palette.mode = self._pil_mode
|
||||
self._pil_image.palette.rawmode = self._pil_rawmode
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -957,7 +957,7 @@ class ServiceDB( FileDB, MessageDB, TagDB ):
|
|||
|
||||
if len( mappings_dict ) > 0:
|
||||
|
||||
for ( tag_id, hash_ids ) in mappings_dict.items(): self._DeleteMappings( admin_account_id, tag_id, hash_ids, reason_id )
|
||||
for ( tag_id, hash_ids ) in mappings_dict.items(): self._DeleteMappings( service_id, admin_account_id, tag_id, hash_ids, reason_id )
|
||||
|
||||
|
||||
|
||||
|
@ -1359,7 +1359,7 @@ class ServiceDB( FileDB, MessageDB, TagDB ):
|
|||
|
||||
self._c.execute( 'DELETE FROM messaging_sessions WHERE ? > expiry;', ( now, ) )
|
||||
|
||||
existing_session_ids = HC.BuildKeyToListDict( [ ( service_id, ( session_key, account_id, identifier, name, expires ) ) for ( service_id, identifier, name, expires ) in self._c.execute( 'SELECT service_id, session_key, account_id, identifier, name, expiry FROM messaging_sessions;' ) ] )
|
||||
existing_session_ids = HC.BuildKeyToListDict( [ ( service_id, ( session_key, account_id, identifier, name, expires ) ) for ( service_id, session_key, account_id, identifier, name, expires ) in self._c.execute( 'SELECT service_id, session_key, account_id, identifier, name, expiry FROM messaging_sessions;' ) ] )
|
||||
|
||||
existing_sessions = {}
|
||||
|
||||
|
@ -1594,13 +1594,15 @@ class ServiceDB( FileDB, MessageDB, TagDB ):
|
|||
|
||||
reason_id = self._GetReasonId( reason )
|
||||
|
||||
if lifetime in request_args: lifetime = kwargs[ 'lifetime' ]
|
||||
if 'lifetime' in kwargs: lifetime = kwargs[ 'lifetime' ]
|
||||
else: lifetime = None
|
||||
|
||||
self._Ban( service_id, action, admin_account_id, subject_account_ids, reason_id, lifetime ) # fold ban and superban together, yo
|
||||
|
||||
else:
|
||||
|
||||
admin_account = self._GetAccount( admin_account_key )
|
||||
|
||||
admin_account.CheckPermission( HC.GENERAL_ADMIN ) # special case, don't let manage_users people do these:
|
||||
|
||||
if action == HC.CHANGE_ACCOUNT_TYPE:
|
||||
|
|
|
@ -6,6 +6,7 @@ import os
|
|||
import random
|
||||
import threading
|
||||
import weakref
|
||||
import wx
|
||||
|
||||
tinest_gif = '\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x00\xFF\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x00\x3B'
|
||||
|
||||
|
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 5.6 KiB |