diff --git a/help/changelog.html b/help/changelog.html
index b3f6452e..d6ae8b0b 100755
--- a/help/changelog.html
+++ b/help/changelog.html
@@ -8,6 +8,27 @@
changelog
+ version 75
+
+ - fullscreenpopup window added
+ - fullscreenpopup window can be dragged about
+ - fullscreenpopupfilterinbox
+ - fullscreenpopupfilterlike
+ - fullscreenpopupfilternumerical
+ - accuracy slider moved to popup
+ - compare same image until done added to popup
+ - keep on left/random/right added to popup
+ - 'don't ratings filter this' added to popup
+ - that annoying as hell thumbnail selection drawing state bug is fixed
+ - display of flash with only one frame fixed
+ - downloaded tags now work again, sorry for the disruption!
+ - deleted tags will now always show in manage tags dialog
+ - fixed the various problems that were stopping DELETED->PENDING->CURRENT working for tags
+ - fixed the children can only have one parent bug
+ - a little counting logic improved in the special deleted_pending case
+ - fixed a typo re uploading file to a repo
+ - main guis statusbar now has a little 'db locked' indicator
+
version 74
- flash scanbar added
diff --git a/include/ClientConstants.py b/include/ClientConstants.py
index 714a9b2f..7a5c21d9 100755
--- a/include/ClientConstants.py
+++ b/include/ClientConstants.py
@@ -2192,40 +2192,6 @@ class ServiceLocalRatingNumerical( Service ):
def GetExtraInfo( self ): return ( self._lower, self._upper )
-class ServiceManager():
-
- def __init__( self ):
-
- self._lock = threading.Lock()
-
- services = wx.GetApp().Read( 'services' )
-
- self._service_identifiers_to_services = { service.GetServiceIdentifier() : service for service in services }
-
- # pubsub for services changing
- # maybe a variable that says services are not ready, so wait a second (for refresh)
- # maybe refresh should be synchro with db? nah, that makes locking issues
-
- # need to protect the actual service objects with a lock too, although the subobjects make this delicate
-
-
- def GetService( self, service_identifier ):
-
- with self._lock:
-
- if service_identifier in self._service_identifiers_to_services: return self._service_identifiers_to_services[ service_identifier ]
- else: raise Exception( 'Could not find that service!' )
-
-
-
- def GetServices( self, service_types ):
-
- with self._lock:
-
- return { service for ( service_identifier, service ) in self._service_identifiers_to_services.items() if service_idenifier.GetType() in service_types }
-
-
-
class ServiceRemote( Service ):
yaml_tag = u'!ServiceRemote'
diff --git a/include/ClientController.py b/include/ClientController.py
index 8aa57cd0..44149972 100755
--- a/include/ClientController.py
+++ b/include/ClientController.py
@@ -22,7 +22,6 @@ class Controller( wx.App ):
def _Read( self, action, *args, **kwargs ):
if action == 'options': return self._options
- elif action == 'tag_service_precedence': return self._tag_service_precedence
elif action == 'file': return self._db.ReadFile( *args, **kwargs )
elif action == 'thumbnail': return self._db.ReadThumbnail( *args, **kwargs )
else: return self._db.Read( action, HC.HIGH_PRIORITY, *args, **kwargs )
@@ -196,8 +195,6 @@ class Controller( wx.App ):
self._options = self._db.Read( 'options', HC.HIGH_PRIORITY )
- self._tag_service_precedence = self._db.Read( 'tag_service_precedence', HC.HIGH_PRIORITY )
-
self._session_manager = HydrusSessions.HydrusSessionManagerClient()
self._web_session_manager = CC.WebSessionManagerClient()
self._tag_parents_manager = CC.TagParentsManager()
diff --git a/include/ClientDB.py b/include/ClientDB.py
index 4059b790..5ef2c9c8 100755
--- a/include/ClientDB.py
+++ b/include/ClientDB.py
@@ -575,8 +575,8 @@ class MessageDB():
num_inbox = len( convo_ids )
- if num_inbox == 0: inbox_string = 'inbox empty'
- else: inbox_string = str( num_inbox ) + ' in inbox'
+ if num_inbox == 0: inbox_string = 'message inbox empty'
+ else: inbox_string = str( num_inbox ) + ' in message inbox'
self.pub( 'inbox_status', inbox_string )
@@ -1407,7 +1407,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
c.execute( 'INSERT INTO tag_service_precedence ( service_id, precedence ) SELECT ?, CASE WHEN MAX( precedence ) NOT NULL THEN MAX( precedence ) + 1 ELSE 0 END FROM tag_service_precedence;', ( service_id, ) )
- self._RebuildTagServicePrecedence( c )
+ self._RebuildTagServicePrecedenceCache( c )
#
@@ -2547,7 +2547,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
all_hash_ids = { hash_id for hash_id in itertools.chain.from_iterable( ( hash_ids for ( hash_ids, reason ) in petitioned ) ) }
- hash_ids_to_hashes = self._GetHashIdsToHashes( c, hash_ids )
+ hash_ids_to_hashes = self._GetHashIdsToHashes( c, all_hash_ids )
content_data[ HC.CONTENT_DATA_TYPE_FILES ][ HC.CONTENT_UPDATE_PETITION ] = petitioned
@@ -3263,8 +3263,6 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
service_id = self._GetServiceId( c, service_identifier )
- hash_ids = self._GetHashIds( c, hashes )
-
c.executemany( 'INSERT OR IGNORE INTO file_transfers ( service_id, hash_id ) VALUES ( ?, ? );', [ ( service_id, hash_id ) for hash_id in hash_ids ] )
if service_identifier == HC.LOCAL_FILE_SERVICE_IDENTIFIER: notify_new_downloads = True
@@ -3385,8 +3383,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
( parent_namespace_id, parent_tag_id ) = self._GetNamespaceIdTagId( c, parent_tag )
- c.execute( 'DELETE FROM tag_parents WHERE service_id = ? AND child_namespace_id = ? AND child_tag_id = ?;', ( service_id, child_namespace_id, child_tag_id ) )
- c.execute( 'DELETE FROM tag_parent_petitions WHERE service_id = ? AND child_namespace_id = ? AND child_tag_id = ? AND status = ?;', ( service_id, child_namespace_id, child_tag_id, deletee_status ) )
+ c.execute( 'DELETE FROM tag_parents WHERE service_id = ? AND child_namespace_id = ? AND child_tag_id = ? AND parent_namespace_id = ? AND parent_tag_id = ?;', ( service_id, child_namespace_id, child_tag_id, parent_namespace_id, parent_tag_id ) )
+ c.execute( 'DELETE FROM tag_parent_petitions WHERE service_id = ? AND child_namespace_id = ? AND child_tag_id = ? AND parent_namespace_id = ? AND parent_tag_id = ? AND status = ?;', ( service_id, child_namespace_id, child_tag_id, parent_namespace_id, parent_tag_id, deletee_status ) )
c.execute( 'INSERT OR IGNORE INTO tag_parents ( service_id, child_namespace_id, child_tag_id, parent_namespace_id, parent_tag_id, status ) VALUES ( ?, ?, ?, ?, ?, ? );', ( service_id, child_namespace_id, child_tag_id, parent_namespace_id, parent_tag_id, new_status ) )
@@ -3596,7 +3594,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
if do_new_permissions: self.pub( 'notify_new_permissions' )
- def _RebuildTagServicePrecedence( self, c ):
+ def _RebuildTagServicePrecedenceCache( self, c ):
del self._tag_service_precedence[:]
@@ -3665,7 +3663,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
if service_type == HC.TAG_REPOSITORY:
- self._RebuildTagServicePrecedence( c )
+ self._RebuildTagServicePrecedenceCache( c )
self._RecalcCombinedMappings( c )
@@ -3731,7 +3729,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
c.executemany( 'INSERT INTO tag_service_precedence ( service_id, precedence ) VALUES ( ?, ? );', [ ( service_id, precedence ) for ( precedence, service_id ) in enumerate( service_ids ) ] )
- self._RebuildTagServicePrecedence( c )
+ self._RebuildTagServicePrecedenceCache( c )
self._RecalcCombinedMappings( c )
@@ -3769,11 +3767,22 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
def ChangeMappingStatus( namespace_id, tag_id, hash_ids, old_status, new_status ):
+ # when we have a tag both deleted and pending made current, we merge two statuses into one!
+ # in this case, we have to be careful about the counts (decrement twice, but only increment once), hence why this returns two numbers
+
appropriate_hash_ids = [ id for ( id, ) in c.execute( 'SELECT hash_id FROM mappings WHERE service_id = ? AND namespace_id = ? AND tag_id = ? AND hash_id IN ' + HC.SplayListForDB( hash_ids ) + ' AND status = ?;', ( service_id, namespace_id, tag_id, old_status ) ) ]
+ existing_hash_ids = { id for ( id, ) in c.execute( 'SELECT hash_id FROM mappings WHERE service_id = ? AND namespace_id = ? AND tag_id = ? AND hash_id IN ' + HC.SplayListForDB( hash_ids ) + ' AND status = ?;', ( service_id, namespace_id, tag_id, new_status ) ) }
+
+ deletable_hash_ids = existing_hash_ids.intersection( appropriate_hash_ids )
+
+ c.execute( 'DELETE FROM mappings WHERE service_id = ? AND namespace_id = ? AND tag_id = ? AND hash_id IN ' + HC.SplayListForDB( deletable_hash_ids ) + ' AND status = ?;', ( service_id, namespace_id, tag_id, old_status ) )
+
+ num_old_deleted = self._GetRowCount( c )
+
c.execute( 'UPDATE mappings SET status = ? WHERE service_id = ? AND namespace_id = ? AND tag_id = ? AND hash_id IN ' + HC.SplayListForDB( appropriate_hash_ids ) + ' AND status = ?;', ( new_status, service_id, namespace_id, tag_id, old_status ) )
- num_rows_changed = self._GetRowCount( c )
+ num_old_made_new = self._GetRowCount( c )
if old_status != HC.PENDING and new_status == HC.PENDING:
@@ -3803,7 +3812,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
CheckIfCombinedMappingsNeedDeleting( namespace_id, tag_id, appropriate_hash_ids )
- return num_rows_changed
+ return ( num_old_deleted + num_old_made_new, num_old_made_new )
def CheckIfCombinedMappingsNeedDeleting( namespace_id, tag_id, hash_ids ):
@@ -3904,7 +3913,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
def InsertMappings( namespace_id, tag_id, hash_ids, status ):
- existing_hash_ids = [ id for ( id, ) in c.execute( 'SELECT hash_id FROM mappings WHERE service_id = ? AND namespace_id = ? AND tag_id = ? AND hash_id IN ' + HC.SplayListForDB( hash_ids ) + ';', ( service_id, namespace_id, tag_id ) ) ]
+ if status == HC.PENDING: existing_hash_ids = [ id for ( id, ) in c.execute( 'SELECT hash_id FROM mappings WHERE service_id = ? AND namespace_id = ? AND tag_id = ? AND hash_id IN ' + HC.SplayListForDB( hash_ids ) + ' AND status != ?;', ( service_id, namespace_id, tag_id, HC.DELETED ) ) ]
+ else: existing_hash_ids = [ id for ( id, ) in c.execute( 'SELECT hash_id FROM mappings WHERE service_id = ? AND namespace_id = ? AND tag_id = ? AND hash_id IN ' + HC.SplayListForDB( hash_ids ) + ';', ( service_id, namespace_id, tag_id ) ) ]
new_hash_ids = set( hash_ids ).difference( existing_hash_ids )
@@ -4020,23 +4030,23 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
for ( namespace_id, tag_id, hash_ids ) in mappings_ids:
- num_deleted_rescinded = ChangeMappingStatus( namespace_id, tag_id, hash_ids, HC.DELETED, HC.CURRENT )
- num_pending_committed = ChangeMappingStatus( namespace_id, tag_id, hash_ids, HC.PENDING, HC.CURRENT )
+ ( num_deleted_deleted, num_deleted_made_current ) = ChangeMappingStatus( namespace_id, tag_id, hash_ids, HC.DELETED, HC.CURRENT )
+ ( num_pending_deleted, num_pending_made_current ) = ChangeMappingStatus( namespace_id, tag_id, hash_ids, HC.PENDING, HC.CURRENT )
num_raw_adds = InsertMappings( namespace_id, tag_id, hash_ids, HC.CURRENT )
- change_in_num_mappings += num_deleted_rescinded + num_pending_committed + num_raw_adds
- change_in_num_deleted_mappings -= num_deleted_rescinded
- change_in_num_pending_mappings -= num_pending_committed
+ change_in_num_mappings += num_deleted_made_current + num_pending_made_current + num_raw_adds
+ change_in_num_deleted_mappings -= num_deleted_deleted
+ change_in_num_pending_mappings -= num_pending_deleted
for ( namespace_id, tag_id, hash_ids ) in deleted_mappings_ids:
- num_current_deleted = ChangeMappingStatus( namespace_id, tag_id, hash_ids, HC.CURRENT, HC.DELETED )
+ ( num_current_deleted, num_current_made_deleted ) = ChangeMappingStatus( namespace_id, tag_id, hash_ids, HC.CURRENT, HC.DELETED )
num_raw_adds = InsertMappings( namespace_id, tag_id, hash_ids, HC.DELETED )
num_deleted_petitions = DeletePetitions( namespace_id, tag_id, hash_ids )
change_in_num_mappings -= num_current_deleted
- change_in_num_deleted_mappings += num_current_deleted + num_raw_adds
+ change_in_num_deleted_mappings += num_current_made_deleted + num_raw_adds
change_in_num_petitioned_mappings -= num_deleted_petitions
@@ -4170,7 +4180,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
if recalc_combined_mappings:
- self._RebuildTagServicePrecedence( c )
+ self._RebuildTagServicePrecedenceCache( c )
self._RecalcCombinedMappings( c )
@@ -4278,7 +4288,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
if recalc_combined_mappings:
- self._RebuildTagServicePrecedence( c )
+ self._RebuildTagServicePrecedenceCache( c )
self._RecalcCombinedMappings( c )
@@ -4360,7 +4370,9 @@ class DB( ServiceDB ):
( self._options, ) = c.execute( 'SELECT options FROM options;' ).fetchone()
- self._tag_service_precedence = self._GetTagServicePrecedence( c )
+ self._tag_service_precedence = []
+
+ self._RebuildTagServicePrecedenceCache( c )
if not self._CheckPassword(): raise HC.PermissionException( 'No password!' )
@@ -7041,7 +7053,7 @@ class DB( ServiceDB ):
self.WaitUntilGoodTimeToUseDBThread()
- time.sleep( 0.25 )
+ time.sleep( 0.10 )
try: service = wx.GetApp().ReadDaemon( 'service', service_identifier )
except: break
@@ -7364,6 +7376,8 @@ class DB( ServiceDB ):
def _MainLoop_JobInternal( self, c, job ):
+ HC.pubsub.pub( 'db_locked_status', 'db locked' )
+
action = job.GetAction()
job_type = job.GetType()
@@ -7442,6 +7456,8 @@ class DB( ServiceDB ):
+ HC.pubsub.pub( 'db_locked_status', '' )
+
def _MainLoop_Read( self, c, action, args, kwargs ):
@@ -7600,15 +7616,9 @@ class DB( ServiceDB ):
if action != 'do_file_query': return job.GetResult()
- def ReadFile( self, hash ):
-
- return self._GetFile( hash )
-
+ def ReadFile( self, hash ): return self._GetFile( hash )
- def ReadThumbnail( self, hash, full_size = False ):
-
- return self._GetThumbnail( hash, full_size )
-
+ def ReadThumbnail( self, hash, full_size = False ): return self._GetThumbnail( hash, full_size )
def WaitUntilGoodTimeToUseDBThread( self ):
diff --git a/include/ClientGUI.py b/include/ClientGUI.py
index 5c900a3f..3ae1af3f 100755
--- a/include/ClientGUI.py
+++ b/include/ClientGUI.py
@@ -46,13 +46,14 @@ class FrameGUI( ClientGUICommon.Frame ):
self.SetDropTarget( ClientGUICommon.FileDropTarget( self.ImportFiles ) )
self._statusbar = self.CreateStatusBar()
- self._statusbar.SetFieldsCount( 4 )
- self._statusbar.SetStatusWidths( [ -1, 400, 200, 200 ] )
+ self._statusbar.SetFieldsCount( 5 )
+ self._statusbar.SetStatusWidths( [ -1, -1, 100, 120, 50 ] )
self._statusbar_media = ''
self._statusbar_service = ''
self._statusbar_inbox = ''
self._statusbar_downloads = ''
+ self._statusbar_db_locked = ''
self.SetIcon( wx.Icon( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', wx.BITMAP_TYPE_ICO ) )
@@ -90,6 +91,7 @@ class FrameGUI( ClientGUICommon.Frame ):
HC.pubsub.sub( self, 'RefreshMenuBar', 'notify_new_services' )
HC.pubsub.sub( self, 'RefreshAcceleratorTable', 'options_updated' )
HC.pubsub.sub( self, 'RefreshStatusBar', 'refresh_status' )
+ HC.pubsub.sub( self, 'SetDBLockedStatus', 'db_locked_status' )
HC.pubsub.sub( self, 'SetDownloadsStatus', 'downloads_status' )
HC.pubsub.sub( self, 'SetInboxStatus', 'inbox_status' )
HC.pubsub.sub( self, 'SetServiceStatus', 'service_status' )
@@ -349,7 +351,7 @@ class FrameGUI( ClientGUICommon.Frame ):
subprocess.Popen( [ 'explorer', HC.BASE_DIR + os.path.sep + 'server.exe' ] )
- time.sleep( 5 ) # give it time to init its db
+ time.sleep( 10 ) # give it time to init its db
except:
@@ -901,6 +903,7 @@ class FrameGUI( ClientGUICommon.Frame ):
self._statusbar.SetStatusText( self._statusbar_service, number = 1 )
self._statusbar.SetStatusText( self._statusbar_inbox, number = 2 )
self._statusbar.SetStatusText( self._statusbar_downloads, number = 3 )
+ self._statusbar.SetStatusText( self._statusbar_db_locked, number = 4 )
def _ReviewServices( self ):
@@ -1470,6 +1473,16 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
def RefreshStatusBar( self ): self._RefreshStatusBar()
+ def SetDBLockedStatus( self, status ):
+
+ if self.IsShown():
+
+ self._statusbar_db_locked = status
+
+ self._RefreshStatusBar()
+
+
+
def SetDownloadsStatus( self, status ):
if self.IsShown():
diff --git a/include/ClientGUICanvas.py b/include/ClientGUICanvas.py
index 4add95a8..eb24a92d 100755
--- a/include/ClientGUICanvas.py
+++ b/include/ClientGUICanvas.py
@@ -1570,6 +1570,14 @@ class CanvasFullscreenMediaListFilter( CanvasFullscreenMediaList ):
self.SetMedia( self._GetFirst() )
+ def _Delete( self ):
+
+ self._deleted.add( self._current_media )
+
+ if self._current_media == self._GetLast(): self.EventClose( None )
+ else: self._ShowNext()
+
+
def _Keep( self ):
self._kept.add( self._current_media )
@@ -1594,6 +1602,12 @@ class CanvasFullscreenMediaListFilter( CanvasFullscreenMediaList ):
+ def EventButtonBack( self, event ): self.EventBack( event )
+ def EventButtonDelete( self, event ): self._Delete()
+ def EventButtonDone( self, event ): self.EventClose( event )
+ def EventButtonKeep( self, event ): self._Keep()
+ def EventButtonSkip( self, event ): self._ShowNext()
+
def EventClose( self, event ):
if self._ShouldSkipInputDueToFlash(): event.Skip()
@@ -1649,13 +1663,7 @@ class CanvasFullscreenMediaListFilter( CanvasFullscreenMediaList ):
def EventDelete( self, event ):
if self._ShouldSkipInputDueToFlash(): event.Skip()
- else:
-
- self._deleted.add( self._current_media )
-
- if self._current_media == self._GetLast(): self.EventClose( event )
- else: self._ShowNext()
-
+ else: self._Delete()
def EventKeyDown( self, event ):
@@ -1768,6 +1776,374 @@ class CanvasFullscreenMediaListFilter( CanvasFullscreenMediaList ):
+class CanvasFullscreenMediaListFilterInbox( CanvasFullscreenMediaListFilter ):
+
+ def __init__( self, my_parent, page_key, file_service_identifier, predicates, media_results ):
+
+ CanvasFullscreenMediaListFilter.__init__( self, my_parent, page_key, file_service_identifier, predicates, media_results )
+
+ FullscreenPopoutFilterInbox( self )
+
+
+class FullscreenPopout( wx.Frame ):
+
+ def __init__( self, parent ):
+
+ wx.Frame.__init__( self, parent, style = wx.FRAME_TOOL_WINDOW | wx.FRAME_NO_TASKBAR | wx.FRAME_FLOAT_ON_PARENT | wx.BORDER_SIMPLE )
+
+ self._last_drag_coordinates = None
+ self._total_drag_delta = ( 0, 0 )
+
+ self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
+
+ self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) )
+
+ hbox = wx.BoxSizer( wx.HORIZONTAL )
+
+ self._popout_window = self._InitialisePopoutWindow( hbox )
+
+ self._popout_window.Hide()
+
+ self._button_window = self._InitialiseButtonWindow( hbox )
+
+ hbox.AddF( self._popout_window, FLAGS_EXPAND_PERPENDICULAR )
+ hbox.AddF( self._button_window, FLAGS_EXPAND_PERPENDICULAR )
+
+ self.SetSizer( hbox )
+
+ self._SizeAndPosition()
+
+ tlp = self.GetParent().GetTopLevelParent()
+
+ tlp.Bind( wx.EVT_SIZE, self.EventResize )
+ tlp.Bind( wx.EVT_MOVE, self.EventMove )
+
+ self.Show()
+
+
+ def _InitialiseButtonWindow( self, sizer ):
+
+ button_window = wx.Window( self )
+
+ self._move_button = wx.Button( button_window, label = u'\u2022', size = ( 20, 20 ) )
+ self._move_button.SetCursor( wx.StockCursor( wx.CURSOR_SIZING ) )
+ self._move_button.Bind( wx.EVT_MOTION, self.EventDrag )
+ self._move_button.Bind( wx.EVT_LEFT_DOWN, self.EventDragBegin )
+ self._move_button.Bind( wx.EVT_LEFT_UP, self.EventDragEnd )
+
+ self._arrow_button = wx.Button( button_window, label = '>', size = ( 20, 80 ) )
+ self._arrow_button.Bind( wx.EVT_BUTTON, self.EventArrowClicked )
+
+ vbox = wx.BoxSizer( wx.VERTICAL )
+
+ vbox.AddF( self._move_button, FLAGS_MIXED )
+ vbox.AddF( self._arrow_button, FLAGS_EXPAND_BOTH_WAYS )
+
+ button_window.SetSizer( vbox )
+
+ return button_window
+
+
+ def _SizeAndPosition( self ):
+
+ self.Fit()
+
+ parent = self.GetParent()
+
+ ( parent_width, parent_height ) = parent.GetClientSize()
+
+ ( my_width, my_height ) = self.GetClientSize()
+
+ my_y = ( parent_height - my_height ) / 2
+
+ ( offset_x, offset_y ) = self._total_drag_delta
+
+ self.SetPosition( parent.ClientToScreenXY( 0 + offset_x, my_y + offset_y ) )
+
+
+ def EventArrowClicked( self, event ):
+
+ if self._popout_window.IsShown():
+
+ self._popout_window.Hide()
+ self._arrow_button.SetLabel( '>' )
+
+ else:
+
+ self._popout_window.Show()
+ self._arrow_button.SetLabel( '<' )
+
+
+ self._SizeAndPosition()
+
+ self.Layout()
+
+
+ def EventMove( self, event ):
+
+ self._SizeAndPosition()
+
+ event.Skip()
+
+
+ def EventDrag( self, event ):
+
+ if event.Dragging() and self._last_drag_coordinates is not None:
+
+ ( old_x, old_y ) = self._last_drag_coordinates
+
+ ( x, y ) = event.GetPosition()
+
+ ( delta_x, delta_y ) = ( x - old_x, y - old_y )
+
+ ( old_delta_x, old_delta_y ) = self._total_drag_delta
+
+ self._total_drag_delta = ( old_delta_x + delta_x, old_delta_y + delta_y )
+
+ self._SizeAndPosition()
+
+
+
+ def EventDragBegin( self, event ):
+
+ self._last_drag_coordinates = event.GetPosition()
+
+ event.Skip()
+
+
+ def EventDragEnd( self, event ):
+
+ self._last_drag_coordinates = None
+
+ event.Skip()
+
+
+ def EventResize( self, event ):
+
+ self._SizeAndPosition()
+
+ event.Skip()
+
+
+class FullscreenPopoutFilterInbox( FullscreenPopout ):
+
+ def _InitialisePopoutWindow( self, sizer ):
+
+ window = wx.Window( self )
+
+ vbox = wx.BoxSizer( wx.VERTICAL )
+
+ parent = self.GetParent()
+
+ keep = wx.Button( window, label = 'archive' )
+ keep.Bind( wx.EVT_BUTTON, parent.EventButtonKeep )
+
+ delete = wx.Button( window, label = 'delete' )
+ delete.Bind( wx.EVT_BUTTON, parent.EventButtonDelete )
+
+ skip = wx.Button( window, label = 'skip' )
+ skip.Bind( wx.EVT_BUTTON, parent.EventButtonSkip )
+
+ back = wx.Button( window, label = 'back' )
+ back.Bind( wx.EVT_BUTTON, parent.EventButtonBack )
+
+ done = wx.Button( window, label = 'done' )
+ done.Bind( wx.EVT_BUTTON, parent.EventButtonDone )
+
+ vbox.AddF( keep, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( delete, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( skip, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( back, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( done, FLAGS_EXPAND_PERPENDICULAR )
+
+ window.SetSizer( vbox )
+
+ return window
+
+
+class FullscreenPopoutFilterLike( FullscreenPopout ):
+
+ def _InitialisePopoutWindow( self, sizer ):
+
+ window = wx.Window( self )
+
+ vbox = wx.BoxSizer( wx.VERTICAL )
+
+ parent = self.GetParent()
+
+ like = wx.Button( window, label = 'like' )
+ like.Bind( wx.EVT_BUTTON, parent.EventButtonKeep )
+
+ dislike = wx.Button( window, label = 'dislike' )
+ dislike.Bind( wx.EVT_BUTTON, parent.EventButtonDelete )
+
+ skip = wx.Button( window, label = 'skip' )
+ skip.Bind( wx.EVT_BUTTON, parent.EventButtonSkip )
+
+ back = wx.Button( window, label = 'back' )
+ back.Bind( wx.EVT_BUTTON, parent.EventButtonBack )
+
+ done = wx.Button( window, label = 'done' )
+ done.Bind( wx.EVT_BUTTON, parent.EventButtonDone )
+
+ vbox.AddF( like, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( dislike, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( skip, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( back, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( done, FLAGS_EXPAND_PERPENDICULAR )
+
+ window.SetSizer( vbox )
+
+ return window
+
+
+class FullscreenPopoutFilterNumerical( FullscreenPopout ):
+
+ def __init__( self, parent, callable_parent ):
+
+ self._callable_parent = callable_parent
+
+ FullscreenPopout.__init__( self, parent )
+
+
+ def _InitialisePopoutWindow( self, sizer ):
+
+ window = wx.Window( self )
+
+ vbox = wx.BoxSizer( wx.VERTICAL )
+
+ parent = self.GetParent()
+
+ #
+
+ options = wx.GetApp().Read( 'options' )
+
+ accuracy_slider_hbox = wx.BoxSizer( wx.HORIZONTAL )
+
+ if 'ratings_filter_accuracy' not in options:
+
+ options[ 'ratings_filter_accuracy' ] = 1
+
+ wx.GetApp().Write( 'save_options' )
+
+
+ value = options[ 'ratings_filter_accuracy' ]
+
+ self._accuracy_slider = wx.Slider( window, size = ( 50, -1 ), value = value, minValue = 0, maxValue = 4 )
+ self._accuracy_slider.Bind( wx.EVT_SLIDER, self.EventAccuracySlider )
+
+ accuracy_slider_hbox.AddF( wx.StaticText( window, label = 'quick' ), FLAGS_MIXED )
+ accuracy_slider_hbox.AddF( self._accuracy_slider, FLAGS_EXPAND_BOTH_WAYS )
+ accuracy_slider_hbox.AddF( wx.StaticText( window, label = 'accurate' ), FLAGS_MIXED )
+
+ self.EventAccuracySlider( None )
+
+ #
+
+ if 'ratings_filter_compare_same' not in options:
+
+ options[ 'ratings_filter_compare_same' ] = False
+
+ wx.GetApp().Write( 'save_options' )
+
+
+ compare_same = options[ 'ratings_filter_compare_same' ]
+
+ self._compare_same = wx.CheckBox( window, label = 'compare same image until rating is done' )
+ self._compare_same.SetValue( compare_same )
+ self._compare_same.Bind( wx.EVT_CHECKBOX, self.EventCompareSame )
+
+ self.EventCompareSame( None )
+
+ #
+
+ self._left_right_slider_sizer = wx.BoxSizer( wx.HORIZONTAL )
+
+ if 'ratings_filter_left_right' not in options:
+
+ options[ 'ratings_filter_left_right' ] = 'left'
+
+ wx.GetApp().Write( 'save_options' )
+
+
+ left_right = options[ 'ratings_filter_left_right' ]
+
+ if left_right == 'left': left_right_value = 0
+ elif left_right == 'random': left_right_value = 1
+ else: left_right_value = 2
+
+ self._left_right_slider = wx.Slider( window, size = ( 30, -1 ), value = left_right_value, minValue = 0, maxValue = 2 )
+ self._left_right_slider.Bind( wx.EVT_SLIDER, self.EventLeftRight )
+
+ self._left_right_slider_sizer.AddF( wx.StaticText( window, label = 'left' ), FLAGS_MIXED )
+ self._left_right_slider_sizer.AddF( self._left_right_slider, FLAGS_EXPAND_BOTH_WAYS )
+ self._left_right_slider_sizer.AddF( wx.StaticText( window, label = 'right' ), FLAGS_MIXED )
+
+ self.EventLeftRight( None )
+
+ #
+
+ left = wx.Button( window, label = 'left is better' )
+ left.Bind( wx.EVT_BUTTON, self._callable_parent.EventButtonLeft )
+
+ right = wx.Button( window, label = 'right is better' )
+ right.Bind( wx.EVT_BUTTON, self._callable_parent.EventButtonRight )
+
+ equal = wx.Button( window, label = 'they are about the same' )
+ equal.Bind( wx.EVT_BUTTON, self._callable_parent.EventButtonEqual )
+
+ back = wx.Button( window, label = 'back' )
+ back.Bind( wx.EVT_BUTTON, self._callable_parent.EventButtonBack )
+
+ dont_filter = wx.Button( window, label = 'don\'t filter this file' )
+ dont_filter.Bind( wx.EVT_BUTTON, self._callable_parent.EventButtonDontFilter )
+
+ done = wx.Button( window, label = 'done' )
+ done.Bind( wx.EVT_BUTTON, self._callable_parent.EventButtonDone )
+
+ vbox.AddF( accuracy_slider_hbox, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( self._compare_same, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( self._left_right_slider_sizer, FLAGS_EXPAND_PERPENDICULAR )
+
+ vbox.AddF( wx.StaticLine( window, style = wx.LI_HORIZONTAL ), FLAGS_EXPAND_PERPENDICULAR )
+
+ vbox.AddF( left, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( right, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( equal, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( back, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( dont_filter, FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( done, FLAGS_EXPAND_PERPENDICULAR )
+
+ window.SetSizer( vbox )
+
+ return window
+
+
+ def EventAccuracySlider( self, event ):
+
+ value = self._accuracy_slider.GetValue()
+
+ self._callable_parent.SetAccuracy( value )
+
+
+ def EventCompareSame( self, event ):
+
+ compare_same = self._compare_same.GetValue()
+
+ self._callable_parent.SetCompareSame( compare_same )
+
+
+ def EventLeftRight( self, event ):
+
+ value = self._left_right_slider.GetValue()
+
+ if value == 0: left_right = 'left'
+ elif value == 1: left_right = 'random'
+ else: left_right = 'right'
+
+ self._callable_parent.SetLeftRight( left_right )
+
+
class RatingsFilterFrameLike( CanvasFullscreenMediaListFilter ):
def __init__( self, my_parent, page_key, service_identifier, media_results ):
@@ -1777,6 +2153,8 @@ class RatingsFilterFrameLike( CanvasFullscreenMediaListFilter ):
self._rating_service_identifier = service_identifier
self._service = wx.GetApp().Read( 'service', service_identifier )
+ FullscreenPopoutFilterLike( self )
+
def EventClose( self, event ):
@@ -1847,6 +2225,7 @@ class RatingsFilterFrameNumerical( ClientGUICommon.Frame ):
self._page_key = page_key
self._service_identifier = service_identifier
self._media_still_to_rate = { ClientGUIMixins.MediaSingleton( media_result ) for media_result in media_results }
+ self._current_media_to_rate = None
self._file_query_result = CC.FileQueryResult( HC.LOCAL_FILE_SERVICE_IDENTIFIER, [], media_results )
@@ -1871,37 +2250,6 @@ class RatingsFilterFrameNumerical( ClientGUICommon.Frame ):
self._inequal_accuracy = self.RATINGS_FILTER_INEQUALITY_FULL
self._equal_accuracy = self.RATINGS_FILTER_EQUALITY_FULL
- # panel
-
- if service_identifier.GetType() == HC.LOCAL_RATING_NUMERICAL:
-
- top_panel = wx.Panel( self )
-
- hbox = wx.BoxSizer( wx.HORIZONTAL )
-
- if 'ratings_filter_accuracy' not in self._options:
-
- self._options[ 'ratings_filter_accuracy' ] = 1
-
- wx.GetApp().Write( 'save_options' )
-
-
- value = self._options[ 'ratings_filter_accuracy' ]
-
- self._accuracy_slider = wx.Slider( top_panel, value = value, minValue = 0, maxValue = 4 )
- self._accuracy_slider.Bind( wx.EVT_SLIDER, self.EventSlider )
-
- self.EventSlider( None )
-
- hbox.AddF( wx.StaticText( top_panel, label = 'quick' ), FLAGS_MIXED )
- hbox.AddF( self._accuracy_slider, FLAGS_EXPAND_BOTH_WAYS )
- hbox.AddF( wx.StaticText( top_panel, label = 'accurate' ), FLAGS_MIXED )
-
- top_panel.SetSizer( hbox )
-
-
- # end panel
-
self._statusbar = self.CreateStatusBar()
self._statusbar.SetFieldsCount( 3 )
self._statusbar.SetStatusWidths( [ -1, 500, -1 ] )
@@ -1911,7 +2259,6 @@ class RatingsFilterFrameNumerical( ClientGUICommon.Frame ):
vbox = wx.BoxSizer( wx.VERTICAL )
- if service_identifier.GetType() == HC.LOCAL_RATING_NUMERICAL: vbox.AddF( top_panel, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._splitter, FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
@@ -1933,6 +2280,8 @@ class RatingsFilterFrameNumerical( ClientGUICommon.Frame ):
wx.GetApp().SetTopWindow( self )
self._left_window = self._Panel( self._splitter )
+ FullscreenPopoutFilterNumerical( self._left_window, self )
+
self._right_window = self._Panel( self._splitter )
( my_width, my_height ) = self.GetClientSize()
@@ -2110,7 +2459,10 @@ class RatingsFilterFrameNumerical( ClientGUICommon.Frame ):
def _ShowNewMedia( self ):
- ( self._current_media_to_rate, ) = random.sample( self._media_still_to_rate, 1 )
+ if not ( self._compare_same and self._current_media_to_rate in self._media_still_to_rate ):
+
+ ( self._current_media_to_rate, ) = random.sample( self._media_still_to_rate, 1 )
+
( min, max ) = self._media_to_current_scores_dict[ self._current_media_to_rate ]
@@ -2190,7 +2542,11 @@ class RatingsFilterFrameNumerical( ClientGUICommon.Frame ):
self._current_media_to_rate_against = media_to_rate_against
- if random.randint( 0, 1 ) == 0:
+ if self._left_right == 'left': position = 0
+ elif self._left_right == 'random': position = random.randint( 0, 1 )
+ else: position = 1
+
+ if position == 0:
self._unrated_is_on_the_left = True
@@ -2384,6 +2740,19 @@ class RatingsFilterFrameNumerical( ClientGUICommon.Frame ):
else: self._ShowNewMedia()
+ def EventButtonBack( self, event ): self._GoBack()
+ def EventButtonDone( self, event ): self.EventClose( event )
+ def EventButtonDontFilter( self, event ):
+
+ self._media_still_to_rate.discard( self._current_media_to_rate )
+
+ if len( self._media_still_to_rate ) == 0: self.EventClose( None )
+ else: self._ShowNewMedia()
+
+ def EventButtonEqual( self, event ): self._ProcessAction( 'equal' )
+ def EventButtonLeft( self, event ): self._ProcessAction( 'right' )
+ def EventButtonRight( self, event ): self._ProcessAction( 'left' )
+
def EventClose( self, event ):
if len( self._decision_log ) > 0:
@@ -2469,23 +2838,6 @@ class RatingsFilterFrameNumerical( ClientGUICommon.Frame ):
elif event.ButtonDown( wx.MOUSE_BTN_MIDDLE ): self._ProcessAction( 'equal' )
- def EventSlider( self, event ):
-
- value = self._accuracy_slider.GetValue()
-
- if value == 0: self._equal_accuracy = self.RATINGS_FILTER_EQUALITY_FULL
- elif value <= 2: self._equal_accuracy = self.RATINGS_FILTER_EQUALITY_HALF
- else: self._equal_accuracy = self.RATINGS_FILTER_EQUALITY_QUARTER
-
- if value <= 1: self._inequal_accuracy = self.RATINGS_FILTER_INEQUALITY_FULL
- elif value <= 3: self._inequal_accuracy = self.RATINGS_FILTER_INEQUALITY_HALF
- else: self._inequal_accuracy = self.RATINGS_FILTER_INEQUALITY_QUARTER
-
- self._options[ 'ratings_filter_accuracy' ] = value
-
- wx.GetApp().Write( 'save_options' )
-
-
def ProcessContentUpdates( self, service_identifiers_to_content_updates ):
redraw = False
@@ -2520,6 +2872,39 @@ class RatingsFilterFrameNumerical( ClientGUICommon.Frame ):
self._right_window.RefreshBackground()
+ def SetAccuracy( self, accuracy ):
+
+ if accuracy == 0: self._equal_accuracy = self.RATINGS_FILTER_EQUALITY_FULL
+ elif accuracy <= 2: self._equal_accuracy = self.RATINGS_FILTER_EQUALITY_HALF
+ else: self._equal_accuracy = self.RATINGS_FILTER_EQUALITY_QUARTER
+
+ if accuracy <= 1: self._inequal_accuracy = self.RATINGS_FILTER_INEQUALITY_FULL
+ elif accuracy <= 3: self._inequal_accuracy = self.RATINGS_FILTER_INEQUALITY_HALF
+ else: self._inequal_accuracy = self.RATINGS_FILTER_INEQUALITY_QUARTER
+
+ self._options[ 'ratings_filter_accuracy' ] = accuracy
+
+ wx.GetApp().Write( 'save_options' )
+
+
+ def SetCompareSame( self, compare_same ):
+
+ self._options[ 'ratings_filter_compare_same' ] = compare_same
+
+ wx.GetApp().Write( 'save_options' )
+
+ self._compare_same = compare_same
+
+
+ def SetLeftRight( self, left_right ):
+
+ self._options[ 'ratings_filter_left_right' ] = left_right
+
+ wx.GetApp().Write( 'save_options' )
+
+ self._left_right = left_right
+
+
class _Panel( Canvas, wx.Window ):
def __init__( self, parent ):
diff --git a/include/ClientGUICommon.py b/include/ClientGUICommon.py
index 655766f9..322e4a87 100755
--- a/include/ClientGUICommon.py
+++ b/include/ClientGUICommon.py
@@ -3169,8 +3169,6 @@ class TagsBoxManage( TagsBox ):
self._callable = callable
- self._show_deleted_tags = False
-
self._current_tags = set( current_tags )
self._deleted_tags = set( deleted_tags )
self._pending_tags = set( pending_tags )
@@ -3185,8 +3183,7 @@ class TagsBoxManage( TagsBox ):
siblings_manager = wx.GetApp().GetTagSiblingsManager()
- if self._show_deleted_tags: all_tags = self._current_tags | self._deleted_tags | self._pending_tags | self._petitioned_tags
- else: all_tags = self._current_tags | self._pending_tags | self._petitioned_tags
+ all_tags = self._current_tags | self._deleted_tags | self._pending_tags | self._petitioned_tags
self._ordered_strings = []
self._strings_to_terms = {}
@@ -3245,13 +3242,6 @@ class TagsBoxManage( TagsBox ):
self._RebuildTagStrings()
- def SetShowDeletedTags( self, value ):
-
- self._show_deleted_tags = value
-
- self._RebuildTagStrings()
-
-
class TagsBoxOptions( TagsBox ):
def __init__( self, parent, initial_namespace_colours ):
diff --git a/include/ClientGUIDialogs.py b/include/ClientGUIDialogs.py
index bc132bf7..f2c41a3a 100755
--- a/include/ClientGUIDialogs.py
+++ b/include/ClientGUIDialogs.py
@@ -8463,9 +8463,6 @@ class DialogManageTags( Dialog ):
self._add_tag_box = ClientGUICommon.AutoCompleteDropdownTagsWrite( self, self.AddTag, self._file_service_identifier, self._tag_service_identifier )
- self._show_deleted_tags = wx.CheckBox( self, label='Show deleted tags' )
- self._show_deleted_tags.Bind( wx.EVT_CHECKBOX, self.EventShowDeletedTags )
-
self._modify_mappers = wx.Button( self, label='Modify mappers' )
self._modify_mappers.Bind( wx.EVT_BUTTON, self.EventModify )
@@ -8475,11 +8472,7 @@ class DialogManageTags( Dialog ):
self._paste_tags = wx.Button( self, label = 'paste tags' )
self._paste_tags.Bind( wx.EVT_BUTTON, self.EventPasteTags )
- if self._i_am_local_tag_service:
-
- self._show_deleted_tags.Hide()
- self._modify_mappers.Hide()
-
+ if self._i_am_local_tag_service: self._modify_mappers.Hide()
else:
if not self._account.HasPermission( HC.POST_DATA ): self._add_tag_box.Hide()
@@ -8491,11 +8484,6 @@ class DialogManageTags( Dialog ):
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
- special_hbox = wx.BoxSizer( wx.HORIZONTAL )
-
- special_hbox.AddF( self._show_deleted_tags, FLAGS_MIXED )
- special_hbox.AddF( self._modify_mappers, FLAGS_MIXED )
-
copy_paste_hbox = wx.BoxSizer( wx.HORIZONTAL )
copy_paste_hbox.AddF( self._copy_tags, FLAGS_MIXED )
@@ -8506,7 +8494,7 @@ class DialogManageTags( Dialog ):
vbox.AddF( self._tags_box, FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( self._add_tag_box, FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( copy_paste_hbox, FLAGS_BUTTON_SIZERS )
- vbox.AddF( special_hbox, FLAGS_BUTTON_SIZERS )
+ vbox.AddF( self._modify_mappers, FLAGS_BUTTON_SIZERS )
self.SetSizer( vbox )
@@ -8628,17 +8616,6 @@ class DialogManageTags( Dialog ):
- elif tag in self._deleted_tags:
-
- if self._account.HasPermission( HC.RESOLVE_PETITIONS ):
-
- self._content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_PENDING, ( tag, self._hashes ) ) )
-
- self._pending_tags.append( tag )
-
- self._tags_box.PendTag( tag )
-
-
else:
self._content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, HC.CONTENT_UPDATE_PENDING, ( tag, self._hashes ) ) )
@@ -8719,8 +8696,6 @@ class DialogManageTags( Dialog ):
else: wx.MessageBox( 'I could not get permission to access the clipboard.' )
- def EventShowDeletedTags( self, event ): self._tags_box.SetShowDeletedTags( self._show_deleted_tags.GetValue() )
-
def EventTagsBoxAction( self, event ):
tag = self._tags_box.GetSelectedTag()
diff --git a/include/ClientGUIMedia.py b/include/ClientGUIMedia.py
index 6f7d01bf..069be573 100755
--- a/include/ClientGUIMedia.py
+++ b/include/ClientGUIMedia.py
@@ -261,7 +261,7 @@ class MediaPanel( ClientGUIMixins.ListeningMediaList, wx.ScrolledWindow ):
if len( media_results ) > 0:
- try: ClientGUICanvas.CanvasFullscreenMediaListFilter( self.GetTopLevelParent(), self._page_key, self._file_service_identifier, self._predicates, media_results )
+ try: ClientGUICanvas.CanvasFullscreenMediaListFilterInbox( self.GetTopLevelParent(), self._page_key, self._file_service_identifier, self._predicates, media_results )
except: wx.MessageBox( traceback.format_exc() )
@@ -807,7 +807,9 @@ class MediaPanelThumbnails( MediaPanel ):
bmp = thumbnail.GetBmp()
- self._thumbnails_being_faded_in[ ( bmp, x, y ) ] = ( bmp, 0 )
+ hash = thumbnail.GetDisplayMedia().GetHash()
+
+ self._thumbnails_being_faded_in[ hash ] = ( bmp, bmp, x, y, 0 )
if not self._timer_animation.IsRunning(): self._timer_animation.Start( 0, wx.TIMER_ONE_SHOT )
@@ -1543,13 +1545,11 @@ class MediaPanelThumbnails( MediaPanel ):
all_info = self._thumbnails_being_faded_in.items()
- for ( key, ( alpha_bmp, num_frames_rendered ) ) in all_info:
-
- ( bmp, x, y ) = key
+ for ( hash, ( original_bmp, alpha_bmp, x, y, num_frames_rendered ) ) in all_info:
if num_frames_rendered == 0:
- image = bmp.ConvertToImage()
+ image = original_bmp.ConvertToImage()
image.InitAlpha()
@@ -1560,13 +1560,13 @@ class MediaPanelThumbnails( MediaPanel ):
num_frames_rendered += 1
- self._thumbnails_being_faded_in[ key ] = ( alpha_bmp, num_frames_rendered )
+ self._thumbnails_being_faded_in[ hash ] = ( original_bmp, alpha_bmp, x, y, num_frames_rendered )
if y < min_y or y > max_y or num_frames_rendered == 9:
- bmp_to_use = bmp
+ bmp_to_use = original_bmp
- del self._thumbnails_being_faded_in[ key ]
+ del self._thumbnails_being_faded_in[ hash ]
else:
diff --git a/include/ClientGUIMixins.py b/include/ClientGUIMixins.py
index 5cf18176..5850fb0c 100755
--- a/include/ClientGUIMixins.py
+++ b/include/ClientGUIMixins.py
@@ -676,7 +676,7 @@ class MediaSingleton( Media ):
def HasArchive( self ): return not self._media_result.GetInbox()
- def HasDuration( self ): return self._media_result.GetDuration() is not None
+ def HasDuration( self ): return self._media_result.GetDuration() is not None and self._media_result.GetNumFrames() > 1
def HasImages( self ): return self.IsImage()
diff --git a/include/HydrusConstants.py b/include/HydrusConstants.py
index 1e2af24b..3a01b83e 100755
--- a/include/HydrusConstants.py
+++ b/include/HydrusConstants.py
@@ -31,7 +31,7 @@ TEMP_DIR = BASE_DIR + os.path.sep + 'temp'
# Misc
NETWORK_VERSION = 10
-SOFTWARE_VERSION = 74
+SOFTWARE_VERSION = 75
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
diff --git a/include/HydrusDownloading.py b/include/HydrusDownloading.py
index d15eb918..bf530ea5 100644
--- a/include/HydrusDownloading.py
+++ b/include/HydrusDownloading.py
@@ -61,7 +61,7 @@ def ConvertTagsToServiceIdentifiersToTags( tags, advanced_tag_options ):
if len( tags_to_add_here ) > 0:
- tags_to_add_here = siblings_manager.CollapseTagList( tags_to_add_here )
+ tags_to_add_here = siblings_manager.CollapseTags( tags_to_add_here )
tags_to_add_here = parents_manager.ExpandTags( service_identifier, tags_to_add_here )
service_identifiers_to_tags[ service_identifier ] = tags_to_add_here