import HydrusConstants as HC import ClientConstants as CC import ClientGUICommon import ClientGUIDialogs import ClientGUIDialogsManage import ClientGUIMixins import collections import os import Queue import random import shutil import subprocess import threading import time import traceback import urllib import wx import wx.lib.flashwin import wx.media ID_TIMER_ANIMATED = wx.NewId() ID_TIMER_FLASH = wx.NewId() ID_TIMER_SLIDESHOW = wx.NewId() ID_TIMER_CURSOR_HIDE = wx.NewId() ANIMATED_SCANBAR_HEIGHT = 20 ANIMATED_SCANBAR_CARET_WIDTH = 10 # Zooms ZOOMINS = [ 0.01, 0.05, 0.1, 0.15, 0.2, 0.3, 0.5, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.5, 2.0, 3.0, 5.0, 10.0, 20.0 ] ZOOMOUTS = [ 20.0, 10.0, 5.0, 3.0, 2.0, 1.5, 1.2, 1.1, 1.0, 0.9, 0.8, 0.7, 0.5, 0.3, 0.2, 0.15, 0.1, 0.05, 0.01 ] NON_ZOOMABLE_MIMES = [ HC.APPLICATION_PDF ] NON_ZOOMABLE_MIMES.extend( HC.AUDIO ) NON_LARGABLY_ZOOMABLE_MIMES = [ HC.VIDEO_FLV, HC.APPLICATION_FLASH ] # Sizer Flags FLAGS_NONE = wx.SizerFlags( 0 ) FLAGS_SMALL_INDENT = wx.SizerFlags( 0 ).Border( wx.ALL, 2 ) FLAGS_EXPAND_PERPENDICULAR = wx.SizerFlags( 0 ).Border( wx.ALL, 2 ).Expand() FLAGS_EXPAND_BOTH_WAYS = wx.SizerFlags( 2 ).Border( wx.ALL, 2 ).Expand() FLAGS_EXPAND_SIZER_PERPENDICULAR = wx.SizerFlags( 0 ).Expand() FLAGS_EXPAND_SIZER_BOTH_WAYS = wx.SizerFlags( 2 ).Expand() FLAGS_BUTTON_SIZERS = wx.SizerFlags( 0 ).Align( wx.ALIGN_RIGHT ) FLAGS_LONE_BUTTON = wx.SizerFlags( 0 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_RIGHT ) FLAGS_MIXED = wx.SizerFlags( 0 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_CENTER_VERTICAL ) def ShouldHaveAnimationBar( media ): is_animated_gif = media.GetMime() == HC.IMAGE_GIF and media.HasDuration() is_animated_flash = media.GetMime() == HC.APPLICATION_FLASH and media.HasDuration() return is_animated_gif or is_animated_flash def GetExtraDimensions( media ): extra_width = 0 extra_height = 0 if ShouldHaveAnimationBar( media ): extra_height += ANIMATED_SCANBAR_HEIGHT return ( extra_width, extra_height ) class Canvas(): def __init__( self, file_service_identifier, image_cache ): self._file_service_identifier = file_service_identifier self._image_cache = image_cache self._service_identifiers_to_services = {} self._focus_holder = wx.Window( self ) self._focus_holder.Hide() self._focus_holder.SetEventHandler( self ) self._options = HC.app.Read( 'options' ) self._current_media = None self._current_display_media = None self._media_container = None self._current_zoom = 1.0 self._last_drag_coordinates = None self._total_drag_delta = ( 0, 0 ) self.SetBackgroundColour( wx.WHITE ) self._canvas_bmp = wx.EmptyBitmap( 0, 0, 24 ) self.Bind( wx.EVT_SIZE, self.EventResize ) self.Bind( wx.EVT_PAINT, self.EventPaint ) def _DrawBackgroundBitmap( self ): ( client_width, client_height ) = self.GetClientSize() cdc = wx.ClientDC( self ) dc = wx.BufferedDC( cdc, self._canvas_bmp ) dc.SetBackground( wx.Brush( wx.WHITE ) ) dc.Clear() if self._current_media is not None: # tags on the top left dc.SetFont( wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT ) ) tags_manager = self._current_media.GetDisplayMedia().GetTagsManager() siblings_manager = HC.app.GetTagSiblingsManager() current = siblings_manager.CollapseTags( tags_manager.GetCurrent() ) pending = siblings_manager.CollapseTags( tags_manager.GetPending() ) tags_i_want_to_display = list( current.union( pending ) ) tags_i_want_to_display.sort() current_y = 3 namespace_colours = self._options[ 'namespace_colours' ] for tag in tags_i_want_to_display: if tag in current: display_string = tag elif tag in pending: display_string = '(+) ' + tag if ':' in tag: ( namespace, sub_tag ) = tag.split( ':', 1 ) if namespace in namespace_colours: ( r, g, b ) = namespace_colours[ namespace ] else: ( r, g, b ) = namespace_colours[ None ] else: ( r, g, b ) = namespace_colours[ '' ] dc.SetTextForeground( wx.Colour( r, g, b ) ) ( x, y ) = dc.GetTextExtent( display_string ) dc.DrawText( display_string, 5, current_y ) current_y += y dc.SetTextForeground( wx.BLACK ) # icons icons_to_show = [] if self._current_media.HasInbox(): icons_to_show.append( CC.GlobalBMPs.inbox_bmp ) file_service_identifiers = self._current_media.GetFileServiceIdentifiersCDPP() if self._file_service_identifier.GetType() == HC.LOCAL_FILE: if len( file_service_identifiers.GetPendingRemote() ) > 0: icons_to_show.append( CC.GlobalBMPs.file_repository_pending_bmp ) elif len( file_service_identifiers.GetCurrentRemote() ) > 0: icons_to_show.append( CC.GlobalBMPs.file_repository_bmp ) elif self._file_service_identifier in file_service_identifiers.GetCurrentRemote(): if self._file_service_identifier in file_service_identifiers.GetPetitionedRemote(): icons_to_show.append( CC.GlobalBMPs.file_repository_petitioned_bmp ) current_x = client_width - 18 for icon_bmp in icons_to_show: dc.DrawBitmap( icon_bmp, current_x, 2 ) current_x -= 20 # top right top_right_strings = [] collections_string = self._GetCollectionsString() if len( collections_string ) > 0: top_right_strings.append( collections_string ) ( local_ratings, remote_ratings ) = self._current_display_media.GetRatings() service_identifiers_to_ratings = local_ratings.GetServiceIdentifiersToRatings() for ( service_identifier, rating ) in service_identifiers_to_ratings.items(): if rating is None: continue service_type = service_identifier.GetType() if service_identifier in self._service_identifiers_to_services: service = self._service_identifiers_to_services[ service_identifier ] else: service = HC.app.Read( 'service', service_identifier ) self._service_identifiers_to_services[ service_identifier ] = service if service_type == HC.LOCAL_RATING_LIKE: ( like, dislike ) = service.GetExtraInfo() if rating == 1: s = like elif rating == 0: s = dislike elif service_type == HC.LOCAL_RATING_NUMERICAL: ( lower, upper ) = service.GetExtraInfo() s = HC.ConvertNumericalRatingToPrettyString( lower, upper, rating ) top_right_strings.append( s ) if len( top_right_strings ) > 0: current_y = 3 if len( icons_to_show ) > 0: current_y += 16 for s in top_right_strings: ( x, y ) = dc.GetTextExtent( s ) dc.DrawText( s, client_width - x - 3, current_y ) current_y += y info_string = self._GetInfoString() ( x, y ) = dc.GetTextExtent( info_string ) dc.DrawText( info_string, ( client_width - x ) / 2, client_height - y - 3 ) index_string = self._GetIndexString() if len( index_string ) > 0: ( x, y ) = dc.GetTextExtent( index_string ) dc.DrawText( index_string, client_width - x - 3, client_height - y - 3 ) def _DrawCurrentMedia( self ): ( my_width, my_height ) = self.GetClientSize() if my_width > 0 and my_height > 0: if self._current_media is not None: self._SizeAndPositionMediaContainer() def _GetCollectionsString( self ): return '' def _GetInfoString( self ): info_string = self._current_media.GetPrettyInfo() + ' | ' + self._current_media.GetPrettyAge() return info_string def _GetIndexString( self ): return '' def _GetMediaContainerSizeAndPosition( self ): ( my_width, my_height ) = self.GetClientSize() if self._current_display_media.GetMime() == HC.APPLICATION_PDF: ( original_width, original_height ) = ( min( my_width, 200 ), min( my_height, 45 ) ) elif self._current_display_media.GetMime() in HC.AUDIO: ( original_width, original_height ) = ( min( my_width, 360 ) , min( my_height, 240 ) ) else: ( original_width, original_height ) = self._current_display_media.GetResolution() media_width = int( round( original_width * self._current_zoom ) ) media_height = int( round( original_height * self._current_zoom ) ) ( extra_width, extra_height ) = GetExtraDimensions( self._current_display_media ) media_width += extra_width media_height += extra_height ( drag_x, drag_y ) = self._total_drag_delta x_offset = ( my_width - media_width ) / 2 + drag_x y_offset = ( my_height - media_height ) / 2 + drag_y new_size = ( media_width, media_height ) new_position = ( x_offset, y_offset ) return ( new_size, new_position ) def _ManageRatings( self ): if self._current_media is not None: try: with ClientGUIDialogsManage.DialogManageRatings( self, ( self._current_media, ) ) as dlg: dlg.ShowModal() except: wx.MessageBox( 'Had a problem displaying the manage ratings dialog from fullscreen.' ) def _ManageTags( self ): if self._current_media is not None: try: with ClientGUIDialogsManage.DialogManageTags( self, self._file_service_identifier, ( self._current_media, ) ) as dlg: dlg.ShowModal() except: wx.MessageBox( 'Had a problem displaying the manage tags dialog from fullscreen.' ) def _PrefetchImages( self ): pass def _RecalcZoom( self ): if self._current_display_media is None: self._current_zoom = 1.0 else: ( my_width, my_height ) = self.GetClientSize() ( media_width, media_height ) = self._current_display_media.GetResolution() if self._current_display_media.GetMime() in ( HC.APPLICATION_FLASH, HC.VIDEO_FLV ): my_width -= 1 if media_width > my_width or media_height > my_height: width_zoom = my_width / float( media_width ) height_zoom = my_height / float( media_height ) self._current_zoom = min( ( width_zoom, height_zoom ) ) else: self._current_zoom = 1.0 def _ShouldSkipInputDueToFlash( self ): if self._current_display_media.GetMime() in ( HC.APPLICATION_FLASH, HC.VIDEO_FLV ): ( x, y ) = self._media_container.GetPosition() ( width, height ) = self._media_container.GetSize() ( mouse_x, mouse_y ) = self.ScreenToClient( wx.GetMousePosition() ) if mouse_x > x and mouse_x < x + width and mouse_y > y and mouse_y < y + height: return True return False def _SizeAndPositionMediaContainer( self ): ( new_size, new_position ) = self._GetMediaContainerSizeAndPosition() if new_size != self._media_container.GetSize(): self._media_container.SetSize( new_size ) if new_position != self._media_container.GetPosition(): self._media_container.SetPosition( new_position ) def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp, wx.BUFFER_VIRTUAL_AREA ) def EventResize( self, event ): ( my_width, my_height ) = self.GetClientSize() self._canvas_bmp.Destroy() self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 ) if self._media_container is not None: ( media_width, media_height ) = self._media_container.GetClientSize() if my_width != media_width or my_height != media_height: with wx.FrozenWindow( self ): self._RecalcZoom() self._DrawBackgroundBitmap() self._DrawCurrentMedia() else: self._DrawBackgroundBitmap() def KeepCursorAlive( self ): pass def SetMedia( self, media ): initial_image = self._current_media == None if media != self._current_media: with wx.FrozenWindow( self ): self._current_media = media self._current_display_media = None self._total_drag_delta = ( 0, 0 ) self._last_drag_coordinates = None if self._media_container is not None: self._media_container.Destroy() self._media_container = None if self._current_media is not None: self._current_display_media = self._current_media.GetDisplayMedia() if self._current_display_media.GetFileServiceIdentifiersCDPP().HasLocal(): self._RecalcZoom() ( initial_size, initial_position ) = self._GetMediaContainerSizeAndPosition() self._media_container = MediaContainer( self, self._image_cache, self._current_display_media, initial_size, initial_position ) if not initial_image: self._PrefetchImages() else: self._current_media = None self._DrawBackgroundBitmap() self._DrawCurrentMedia() class CanvasPanel( Canvas, wx.Window ): def __init__( self, parent, page_key, file_service_identifier ): wx.Window.__init__( self, parent, style = wx.SIMPLE_BORDER ) Canvas.__init__( self, file_service_identifier, HC.app.GetPreviewImageCache() ) self._page_key = page_key HC.pubsub.sub( self, 'FocusChanged', 'focus_changed' ) HC.pubsub.sub( self, 'ProcessContentUpdates', 'content_updates_gui' ) wx.CallAfter( self.Refresh ) def FocusChanged( self, page_key, media ): if page_key == self._page_key: self.SetMedia( media ) def ProcessContentUpdates( self, service_identifiers_to_content_updates ): if self._current_display_media is not None: my_hash = self._current_display_media.GetHash() do_redraw = False for ( service_identifier, content_updates ) in service_identifiers_to_content_updates.items(): if True in ( my_hash in content_update.GetHashes() for content_update in content_updates ): do_redraw = True break if do_redraw: self._DrawBackgroundBitmap() self._DrawCurrentMedia() class CanvasFullscreenMediaList( ClientGUIMixins.ListeningMediaList, Canvas, ClientGUICommon.Frame ): def __init__( self, my_parent, page_key, file_service_identifier, predicates, media_results ): ClientGUICommon.Frame.__init__( self, my_parent, title = 'hydrus client fullscreen media viewer' ) Canvas.__init__( self, file_service_identifier, HC.app.GetFullscreenImageCache() ) ClientGUIMixins.ListeningMediaList.__init__( self, file_service_identifier, predicates, media_results ) self._page_key = page_key self._menu_open = False self._just_started = True self.SetIcon( wx.Icon( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', wx.BITMAP_TYPE_ICO ) ) self.SetCursor( wx.StockCursor( wx.CURSOR_BLANK ) ) if self._options[ 'fullscreen_borderless' ]: self.ShowFullScreen( True, wx.FULLSCREEN_ALL ) else: self.Maximize() self.Show( True ) HC.app.SetTopWindow( self ) self._timer_cursor_hide = wx.Timer( self, id = ID_TIMER_CURSOR_HIDE ) self.Bind( wx.EVT_TIMER, self.EventTimerCursorHide, id = ID_TIMER_CURSOR_HIDE ) self.Bind( wx.EVT_CLOSE, self.EventClose ) self.Bind( wx.EVT_MOTION, self.EventDrag ) self.Bind( wx.EVT_LEFT_DOWN, self.EventDragBegin ) self.Bind( wx.EVT_LEFT_UP, self.EventDragEnd ) HC.pubsub.pub( 'set_focus', self._page_key, None ) def _DoManualPan( self, delta_x, delta_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._DrawCurrentMedia() def _FullscreenSwitch( self ): if self.IsFullScreen(): self.ShowFullScreen( False ) self.Maximize() else: self.ShowFullScreen( True, wx.FULLSCREEN_ALL ) def _GetCollectionsString( self ): collections_string = '' siblings_manager = HC.app.GetTagSiblingsManager() namespaces = self._current_media.GetDisplayMedia().GetTagsManager().GetCombinedNamespaces( ( 'creator', 'series', 'title', 'volume', 'chapter', 'page' ) ) creators = namespaces[ 'creator' ] series = namespaces[ 'series' ] titles = namespaces[ 'title' ] volumes = namespaces[ 'volume' ] chapters = namespaces[ 'chapter' ] pages = namespaces[ 'page' ] if len( creators ) > 0: creators = siblings_manager.CollapseNamespacedTags( 'creator', creators ) collections_string_append = ', '.join( creators ) if len( collections_string ) > 0: collections_string += ' - ' + collections_string_append else: collections_string = collections_string_append if len( series ) > 0: series = siblings_manager.CollapseNamespacedTags( 'series', series ) collections_string_append = ', '.join( series ) if len( collections_string ) > 0: collections_string += ' - ' + collections_string_append else: collections_string = collections_string_append if len( titles ) > 0: titles = siblings_manager.CollapseNamespacedTags( 'title', titles ) collections_string_append = ', '.join( titles ) if len( collections_string ) > 0: collections_string += ' - ' + collections_string_append else: collections_string = collections_string_append if len( volumes ) > 0: if len( volumes ) == 1: ( volume, ) = volumes collections_string_append = 'volume ' + HC.u( volume ) else: collections_string_append = 'volumes ' + HC.u( min( volumes ) ) + '-' + HC.u( max( volumes ) ) if len( collections_string ) > 0: collections_string += ' - ' + collections_string_append else: collections_string = collections_string_append if len( chapters ) > 0: if len( chapters ) == 1: ( chapter, ) = chapters collections_string_append = 'chapter ' + HC.u( chapter ) else: collections_string_append = 'chapters ' + HC.u( min( chapters ) ) + '-' + HC.u( max( chapters ) ) if len( collections_string ) > 0: collections_string += ' - ' + collections_string_append else: collections_string = collections_string_append if len( pages ) > 0: if len( pages ) == 1: ( page, ) = pages collections_string_append = 'page ' + HC.u( page ) else: collections_string_append = 'pages ' + HC.u( min( pages ) ) + '-' + HC.u( max( pages ) ) if len( collections_string ) > 0: collections_string += ' - ' + collections_string_append else: collections_string = collections_string_append return collections_string def _GetInfoString( self ): info_string = self._current_media.GetPrettyInfo() + ' | ' + HC.ConvertZoomToPercentage( self._current_zoom ) + ' | ' + self._current_media.GetPrettyAge() return info_string def _GetIndexString( self ): index_string = HC.ConvertIntToPrettyString( self._sorted_media_to_indices[ self._current_media ] + 1 ) + os.path.sep + HC.ConvertIntToPrettyString( len( self._sorted_media ) ) return index_string def _PrefetchImages( self ): to_render = [] previous = self._current_media next = self._current_media if self._just_started: extra_delay_base = 800 self._just_started = False else: extra_delay_base = 200 for i in range( 10 ): previous = self._GetPrevious( previous ) next = self._GetNext( next ) to_render.append( ( previous, 100 + ( extra_delay_base * 2 * i * i ) ) ) to_render.append( ( next, 100 + ( extra_delay_base * i * i ) ) ) ( my_width, my_height ) = self.GetClientSize() for ( media, delay ) in to_render: hash = media.GetHash() if media.GetMime() in ( HC.IMAGE_JPEG, HC.IMAGE_PNG ): ( media_width, media_height ) = media.GetResolution() if media_width > my_width or media_height > my_height: width_zoom = my_width / float( media_width ) height_zoom = my_height / float( media_height ) zoom = min( ( width_zoom, height_zoom ) ) else: zoom = 1.0 resolution_to_request = ( int( round( zoom * media_width ) ), int( round( zoom * media_height ) ) ) if not self._image_cache.HasImage( hash, resolution_to_request ): wx.CallLater( delay, self._image_cache.GetImage, hash, resolution_to_request ) def _ShowFirst( self ): self.SetMedia( self._GetFirst() ) def _ShowLast( self ): self.SetMedia( self._GetLast() ) def _ShowNext( self ): self.SetMedia( self._GetNext( self._current_media ) ) def _ShowPrevious( self ): self.SetMedia( self._GetPrevious( self._current_media ) ) def _StartSlideshow( self, interval ): pass def _ZoomIn( self ): if self._current_media is not None: if self._current_media.GetMime() in NON_ZOOMABLE_MIMES: return for zoom in ZOOMINS: if self._current_zoom < zoom: if self._current_media.GetMime() in ( HC.APPLICATION_FLASH, HC.VIDEO_FLV ): # because of the event passing under mouse, we want to preserve whitespace around flash ( original_width, original_height ) = self._current_display_media.GetResolution() ( my_width, my_height ) = self.GetClientSize() new_media_width = int( round( original_width * zoom ) ) new_media_height = int( round( original_height * zoom ) ) ( extra_width, extra_height ) = GetExtraDimensions( self._current_display_media ) new_media_width += extra_width new_media_height += extra_height if new_media_width >= my_width or new_media_height >= my_height: return with wx.FrozenWindow( self ): ( drag_x, drag_y ) = self._total_drag_delta zoom_ratio = zoom / self._current_zoom self._total_drag_delta = ( int( drag_x * zoom_ratio ), int( drag_y * zoom_ratio ) ) self._current_zoom = zoom self._DrawBackgroundBitmap() self._DrawCurrentMedia() break def _ZoomOut( self ): if self._current_media is not None: if self._current_media.GetMime() in NON_ZOOMABLE_MIMES: return for zoom in ZOOMOUTS: if self._current_zoom > zoom: with wx.FrozenWindow( self ): ( drag_x, drag_y ) = self._total_drag_delta zoom_ratio = zoom / self._current_zoom self._total_drag_delta = ( int( drag_x * zoom_ratio ), int( drag_y * zoom_ratio ) ) self._current_zoom = zoom self._DrawBackgroundBitmap() self._DrawCurrentMedia() break def _ZoomSwitch( self ): ( my_width, my_height ) = self.GetClientSize() ( media_width, media_height ) = self._current_display_media.GetResolution() if self._current_media.GetMime() in NON_ZOOMABLE_MIMES: return if self._current_media.GetMime() not in NON_LARGABLY_ZOOMABLE_MIMES or self._current_zoom > 1.0 or ( media_width < my_width and media_height < my_height ): new_zoom = self._current_zoom if self._current_zoom == 1.0: if media_width > my_width or media_height > my_height: width_zoom = my_width / float( media_width ) height_zoom = my_height / float( media_height ) new_zoom = min( ( width_zoom, height_zoom ) ) else: new_zoom = 1.0 if new_zoom != self._current_zoom: ( drag_x, drag_y ) = self._total_drag_delta zoom_ratio = new_zoom / self._current_zoom self._total_drag_delta = ( int( drag_x * zoom_ratio ), int( drag_y * zoom_ratio ) ) self._current_zoom = new_zoom self._DrawBackgroundBitmap() self._DrawCurrentMedia() def AddMediaResult( self, page_key, media_result ): if page_key == self._page_key: ClientGUIMixins.ListeningMediaList.AddMediaResult( self, media_result ) self._DrawBackgroundBitmap() self._DrawCurrentMedia() def Archive( self, hashes ): next_media = self._GetNext( self._current_media ) if next_media == self._current_media: next_media = None ClientGUIMixins.ListeningMediaList.Archive( self, hashes ) if self.HasNoMedia(): self.EventClose( None ) elif self.HasMedia( self._current_media ): self._DrawCurrentMedia() else: self.SetMedia( next_media ) def EventClose( self, event ): HC.pubsub.pub( 'set_focus', self._page_key, self._current_media ) self.Destroy() def EventDrag( self, event ): self._focus_holder.SetFocus() 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 ) try: self.WarpPointer( old_x, old_y ) except: self._last_drag_coordinates = ( x, 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._DrawCurrentMedia() self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) ) self._timer_cursor_hide.Start( 800, wx.TIMER_ONE_SHOT ) def EventDragBegin( self, event ): ( x, y ) = event.GetPosition() ( client_x, client_y ) = self.GetClientSize() if x < 20 or x > client_x - 20 or y < 20 or y > client_y -20: try: better_x = x better_y = y if x < 20: better_x = 20 if y < 20: better_y = 20 if x > client_x - 20: better_x = client_x - 20 if y > client_y - 20: better_y = client_y - 20 self.WarpPointer( better_x, better_y ) x = better_x y = better_y except: pass self._last_drag_coordinates = ( x, y ) event.Skip() def EventDragEnd( self, event ): self._last_drag_coordinates = None event.Skip() def EventTimerCursorHide( self, event ): if self._menu_open: self._timer_cursor_hide.Start( 800, wx.TIMER_ONE_SHOT ) else: self.SetCursor( wx.StockCursor( wx.CURSOR_BLANK ) ) def KeepCursorAlive( self ): self._timer_cursor_hide.Start( 800, wx.TIMER_ONE_SHOT ) def ProcessContentUpdates( self, service_identifiers_to_content_updates ): next_media = self._GetNext( self._current_media ) if next_media == self._current_media: next_media = None ClientGUIMixins.ListeningMediaList.ProcessContentUpdates( self, service_identifiers_to_content_updates ) if self.HasNoMedia(): self.EventClose( None ) elif self.HasMedia( self._current_media ): self._DrawBackgroundBitmap() self._DrawCurrentMedia() else: self.SetMedia( next_media ) class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaList ): def __init__( self, my_parent, page_key, file_service_identifier, predicates, media_results, first_hash ): CanvasFullscreenMediaList.__init__( self, my_parent, page_key, file_service_identifier, predicates, media_results ) self._timer_slideshow = wx.Timer( self, id = ID_TIMER_SLIDESHOW ) self.Bind( wx.EVT_TIMER, self.EventTimerSlideshow, id = ID_TIMER_SLIDESHOW ) self.Bind( wx.EVT_LEFT_DCLICK, self.EventClose ) self.Bind( wx.EVT_MIDDLE_DOWN, self.EventClose ) self.Bind( wx.EVT_MOUSEWHEEL, self.EventMouseWheel ) self.Bind( wx.EVT_RIGHT_UP, self.EventShowMenu ) self.Bind( wx.EVT_MENU, self.EventMenu ) self.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown ) if first_hash is None: self.SetMedia( self._GetFirst() ) else: self.SetMedia( self._GetMedia( { first_hash } )[0] ) HC.pubsub.sub( self, 'AddMediaResult', 'add_media_result' ) def _Archive( self ): HC.app.Write( 'content_updates', { HC.LOCAL_FILE_SERVICE_IDENTIFIER : [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, ( self._current_media.GetHash(), ) ) ] } ) def _CopyLocalUrlToClipboard( self ): if wx.TheClipboard.Open(): data = wx.TextDataObject( 'http://127.0.0.1:45865/file?hash=' + self._current_media.GetHash().encode( 'hex' ) ) wx.TheClipboard.SetData( data ) wx.TheClipboard.Close() else: wx.MessageBox( 'I could not get permission to access the clipboard.' ) def _CopyPathToClipboard( self ): if wx.TheClipboard.Open(): data = wx.TextDataObject( CC.GetFilePath( self._current_media.GetHash(), self._current_media.GetMime() ) ) wx.TheClipboard.SetData( data ) wx.TheClipboard.Close() else: wx.MessageBox( 'I could not get permission to access the clipboard.' ) def _Delete( self ): with ClientGUIDialogs.DialogYesNo( self, 'Delete this file from the database?' ) as dlg: if dlg.ShowModal() == wx.ID_YES: HC.app.Write( 'content_updates', { HC.LOCAL_FILE_SERVICE_IDENTIFIER : [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, ( self._current_media.GetHash(), ) ) ] } ) self.SetFocus() # annoying bug because of the modal dialog def _Inbox( self ): HC.app.Write( 'content_updates', { HC.LOCAL_FILE_SERVICE_IDENTIFIER : [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_INBOX, ( self._current_media.GetHash(), ) ) ] } ) def _PausePlaySlideshow( self ): if self._timer_slideshow.IsRunning(): self._timer_slideshow.Stop() elif self._timer_slideshow.GetInterval() > 0: self._timer_slideshow.Start() def _StartSlideshow( self, interval = None ): self._timer_slideshow.Stop() if interval is None: with wx.TextEntryDialog( self, 'Enter the interval, in seconds', defaultValue='15' ) as dlg: if dlg.ShowModal() == wx.ID_OK: try: interval = int( float( dlg.GetValue() ) * 1000 ) except: return if interval > 0: self._timer_slideshow.Start( interval, wx.TIMER_CONTINUOUS ) def EventKeyDown( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: if event.KeyCode in ( wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE ): self._Delete() elif event.KeyCode in ( wx.WXK_SPACE, wx.WXK_NUMPAD_SPACE ): self._PausePlaySlideshow() elif event.KeyCode in ( ord( '+' ), wx.WXK_ADD, wx.WXK_NUMPAD_ADD ): self._ZoomIn() elif event.KeyCode in ( ord( '-' ), wx.WXK_SUBTRACT, wx.WXK_NUMPAD_SUBTRACT ): self._ZoomOut() elif event.KeyCode == ord( 'Z' ): self._ZoomSwitch() elif event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_ESCAPE ): self.EventClose( event ) elif event.KeyCode == ord( 'C' ) and event.CmdDown(): with wx.BusyCursor(): HC.app.Write( 'copy_files', ( self._current_media.GetHash(), ) ) else: ( modifier, key ) = HC.GetShortcutFromEvent( event ) key_dict = self._options[ 'shortcuts' ][ modifier ] if key in key_dict: action = key_dict[ key ] self.ProcessEvent( wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) ) else: event.Skip() def EventMenu( self, event ): # is None bit means this is prob from a keydown->menu event if event.GetEventObject() is None and self._ShouldSkipInputDueToFlash(): event.Skip() else: action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() ) if action is not None: try: ( command, data ) = action if command == 'archive': self._Archive() elif command == 'copy_files': with wx.BusyCursor(): HC.app.Write( 'copy_files', ( self._current_media.GetHash(), ) ) elif command == 'copy_local_url': self._CopyLocalUrlToClipboard() elif command == 'copy_path': self._CopyPathToClipboard() elif command == 'delete': self._Delete() elif command == 'fullscreen_switch': self._FullscreenSwitch() elif command == 'first': self._ShowFirst() elif command == 'last': self._ShowLast() elif command == 'previous': self._ShowPrevious() elif command == 'next': self._ShowNext() elif command == 'frame_back': self._media_container.GotoPreviousOrNextFrame( -1 ) elif command == 'frame_next': self._media_container.GotoPreviousOrNextFrame( 1 ) elif command == 'inbox': self._Inbox() elif command == 'manage_ratings': self._ManageRatings() elif command == 'manage_tags': self._ManageTags() elif command in ( 'pan_up', 'pan_down', 'pan_left', 'pan_right' ): distance = 20 if command == 'pan_up': self._DoManualPan( 0, -distance ) elif command == 'pan_down': self._DoManualPan( 0, distance ) elif command == 'pan_left': self._DoManualPan( -distance, 0 ) elif command == 'pan_right': self._DoManualPan( distance, 0 ) elif command == 'slideshow': self._StartSlideshow( data ) elif command == 'slideshow_pause_play': self._PausePlaySlideshow() elif command == 'zoom_in': self._ZoomIn() elif command == 'zoom_out': self._ZoomOut() elif command == 'zoom_switch': self._ZoomSwitch() else: event.Skip() except Exception as e: HC.pubsub.pub( 'message', HC.Message( HC.MESSAGE_TYPE_ERROR, e ) ) wx.MessageBox( HC.u( e ) ) print( HC.u( e ) ) def EventMouseWheel( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: if event.CmdDown(): if event.GetWheelRotation() > 0: self._ZoomIn() else: self._ZoomOut() else: if event.GetWheelRotation() > 0: self._ShowPrevious() else: self._ShowNext() def EventShowMenu( self, event ): services = HC.app.Read( 'services' ) local_ratings_services = [ service for service in services if service.GetServiceIdentifier().GetType() in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) ] i_can_post_ratings = len( local_ratings_services ) > 0 self._last_drag_coordinates = None # to stop successive right-click drag warp bug menu = wx.Menu() menu.Append( CC.ID_NULL, self._current_media.GetPrettyInfo() ) menu.Append( CC.ID_NULL, self._current_media.GetPrettyAge() ) menu.AppendSeparator() menu.Append( CC.ID_NULL, 'current zoom: ' + HC.ConvertZoomToPercentage( self._current_zoom ) ) menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'zoom_in' ), 'zoom in' ) menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'zoom_out' ), 'zoom out' ) # if self._current_media.GetMime() not in NON_LARGABLY_ZOOMABLE_MIMES + NON_ZOOMABLE_MIMES: ( my_width, my_height ) = self.GetClientSize() ( media_width, media_height ) = self._current_display_media.GetResolution() if self._current_zoom == 1.0: if media_width > my_width or media_height > my_height: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'zoom_switch' ), 'zoom fit' ) else: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'zoom_switch' ), 'zoom full' ) # menu.AppendSeparator() menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_tags' ), 'manage tags' ) if i_can_post_ratings: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'manage_ratings' ), 'manage ratings' ) menu.AppendSeparator() if self._current_media.HasInbox(): menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'archive' ), '&archive' ) if self._current_media.HasArchive(): menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'inbox' ), 'return to &inbox' ) menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', HC.LOCAL_FILE_SERVICE_IDENTIFIER ), '&delete' ) menu.AppendSeparator() copy_menu = wx.Menu() copy_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_files' ) , 'file' ) copy_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_path' ) , 'path' ) copy_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_local_url' ) , 'local url' ) menu.AppendMenu( CC.ID_NULL, 'copy', copy_menu ) menu.AppendSeparator() slideshow = wx.Menu() slideshow.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'slideshow', 1000 ), '1 second' ) slideshow.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'slideshow', 5000 ), '5 seconds' ) slideshow.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'slideshow', 10000 ), '10 seconds' ) slideshow.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'slideshow', 30000 ), '30 seconds' ) slideshow.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'slideshow', 60000 ), '60 seconds' ) slideshow.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'slideshow', 80 ), 'william gibson' ) slideshow.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'slideshow' ), 'custom interval' ) menu.AppendMenu( CC.ID_NULL, 'Start Slideshow', slideshow ) if self._timer_slideshow.IsRunning(): menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'slideshow_pause_play' ), 'stop slideshow' ) self._menu_open = True self.PopupMenu( menu ) self._menu_open = False menu.Destroy() event.Skip() def EventTimerSlideshow( self, event ): self._ShowNext() class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaList ): def __init__( self, my_parent, page_key, file_service_identifier, predicates, media_results, actions ): CanvasFullscreenMediaList.__init__( self, my_parent, page_key, file_service_identifier, predicates, media_results ) self._actions = actions self.Bind( wx.EVT_LEFT_DCLICK, self.EventClose ) self.Bind( wx.EVT_MIDDLE_DOWN, self.EventClose ) self.Bind( wx.EVT_MOUSEWHEEL, self.EventMouseWheel ) self.Bind( wx.EVT_RIGHT_UP, self.EventShowMenu ) self.Bind( wx.EVT_MENU, self.EventMenu ) self.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown ) self.SetMedia( self._GetFirst() ) FullscreenPopoutFilterCustom( self ) HC.pubsub.sub( self, 'AddMediaResult', 'add_media_result' ) def _Archive( self ): HC.app.Write( 'content_updates', { HC.LOCAL_FILE_SERVICE_IDENTIFIER : [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, ( self._current_media.GetHash(), ) ) ] } ) def _CopyLocalUrlToClipboard( self ): if wx.TheClipboard.Open(): data = wx.TextDataObject( 'http://127.0.0.1:45865/file?hash=' + self._current_media.GetHash().encode( 'hex' ) ) wx.TheClipboard.SetData( data ) wx.TheClipboard.Close() else: wx.MessageBox( 'I could not get permission to access the clipboard.' ) def _CopyPathToClipboard( self ): if wx.TheClipboard.Open(): data = wx.TextDataObject( CC.GetFilePath( self._current_media.GetHash(), self._current_media.GetMime() ) ) wx.TheClipboard.SetData( data ) wx.TheClipboard.Close() else: wx.MessageBox( 'I could not get permission to access the clipboard.' ) def _Delete( self ): with ClientGUIDialogs.DialogYesNo( self, 'Delete this file from the database?' ) as dlg: if dlg.ShowModal() == wx.ID_YES: HC.app.Write( 'content_updates', { HC.LOCAL_FILE_SERVICE_IDENTIFIER : [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, ( self._current_media.GetHash(), ) ) ] } ) self.SetFocus() # annoying bug because of the modal dialog def _Inbox( self ): HC.app.Write( 'content_updates', { HC.LOCAL_FILE_SERVICE_IDENTIFIER : [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_INBOX, ( self._current_media.GetHash(), ) ) ] } ) def EventActions( self, event ): with ClientGUIDialogs.DialogSetupCustomFilterActions( self ) as dlg: if dlg.ShowModal() == wx.ID_OK: self._actions = dlg.GetActions() def EventKeyDown( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: ( modifier, key ) = HC.GetShortcutFromEvent( event ) key_dict = self._actions[ modifier ] hashes = set( ( self._current_media.GetHash(), ) ) if key in key_dict: ( service_identifier, action ) = key_dict[ key ] if service_identifier is None: if action == 'archive': self._Archive() elif action == 'delete': self._Delete() elif action == 'frame_back': self._media_container.GotoPreviousOrNextFrame( -1 ) elif action == 'frame_next': self._media_container.GotoPreviousOrNextFrame( 1 ) elif action == 'inbox': self._Inbox() elif action == 'manage_ratings': self._ManageRatings() elif action == 'manage_tags': self._ManageTags() elif action in ( 'pan_up', 'pan_down', 'pan_left', 'pan_right' ): distance = 20 if action == 'pan_up': self._DoManualPan( 0, -distance ) elif action == 'pan_down': self._DoManualPan( 0, distance ) elif action == 'pan_left': self._DoManualPan( -distance, 0 ) elif action == 'pan_right': self._DoManualPan( distance, 0 ) elif action == 'first': self._ShowFirst() elif action == 'last': self._ShowLast() elif action == 'previous': self._ShowPrevious() elif action == 'next': self._ShowNext() else: service_type = service_identifier.GetType() if service_type in ( HC.LOCAL_TAG, HC.TAG_REPOSITORY ): tags_manager = self._current_media.GetDisplayMedia().GetTagsManager() current = tags_manager.GetCurrent() pending = tags_manager.GetPending() petitioned = tags_manager.GetPetitioned() if service_type == HC.LOCAL_TAG: if action in current: content_update_action = HC.CONTENT_UPDATE_DELETE else: content_update_action = HC.CONTENT_UPDATE_ADD row = ( action, hashes ) else: if action in current: if action in petitioned: edit_log = [ ( HC.CONTENT_UPDATE_RESCIND_PETITION, action ) ] else: message = 'Enter a reason for this tag to be removed. A janitor will review your petition.' with wx.TextEntryDialog( self, message ) as dlg: if dlg.ShowModal() == wx.ID_OK: content_update_action = HC.CONTENT_UPDATE_PETITION row = ( dlg.GetValue(), action, hashes ) else: return else: if action in pending: content_update_action = HC.CONTENT_UPDATE_RESCIND_PENDING else: content_update_action = HC.CONTENT_UPDATE_PENDING row = ( action, hashes ) content_update = HC.ContentUpdate( HC.CONTENT_DATA_TYPE_MAPPINGS, content_update_action, row ) elif service_type in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ): # maybe this needs to be more complicated, if action is, say, remove the rating? # ratings needs a good look at anyway row = ( action, hashes ) content_update = HC.ContentUpdate( HC.CONTENT_DATA_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, row ) HC.app.Write( 'content_updates', { service_identifier : [ content_update ] } ) else: if event.KeyCode in ( ord( '+' ), wx.WXK_ADD, wx.WXK_NUMPAD_ADD ): self._ZoomIn() elif event.KeyCode in ( ord( '-' ), wx.WXK_SUBTRACT, wx.WXK_NUMPAD_SUBTRACT ): self._ZoomOut() elif event.KeyCode == ord( 'Z' ): self._ZoomSwitch() elif event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_ESCAPE ): self.EventClose( event ) elif event.KeyCode == ord( 'C' ) and event.CmdDown(): with wx.BusyCursor(): HC.app.Write( 'copy_files', ( self._current_media.GetHash(), ) ) else: event.Skip() def EventMenu( self, event ): # is None bit means this is prob from a keydown->menu event if event.GetEventObject() is None and self._ShouldSkipInputDueToFlash(): event.Skip() else: action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() ) if action is not None: try: ( command, data ) = action if command == 'archive': self._Archive() elif command == 'copy_files': with wx.BusyCursor(): HC.app.Write( 'copy_files', ( self._current_media.GetHash(), ) ) elif command == 'copy_local_url': self._CopyLocalUrlToClipboard() elif command == 'copy_path': self._CopyPathToClipboard() elif command == 'delete': self._Delete() elif command == 'fullscreen_switch': self._FullscreenSwitch() elif command == 'first': self._ShowFirst() elif command == 'last': self._ShowLast() elif command == 'previous': self._ShowPrevious() elif command == 'next': self._ShowNext() elif command == 'frame_back': self._media_container.GotoPreviousOrNextFrame( -1 ) elif command == 'frame_next': self._media_container.GotoPreviousOrNextFrame( 1 ) elif command == 'inbox': self._Inbox() elif command == 'manage_ratings': self._ManageRatings() elif command == 'manage_tags': self._ManageTags() elif command == 'slideshow': self._StartSlideshow( data ) elif command == 'slideshow_pause_play': self._PausePlaySlideshow() elif command == 'zoom_in': self._ZoomIn() elif command == 'zoom_out': self._ZoomOut() elif command == 'zoom_switch': self._ZoomSwitch() else: event.Skip() except Exception as e: HC.pubsub.pub( 'message', HC.Message( HC.MESSAGE_TYPE_ERROR, e ) ) wx.MessageBox( HC.u( e ) ) print( HC.u( e ) ) def EventMouseWheel( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: if event.CmdDown(): if event.GetWheelRotation() > 0: self._ZoomIn() else: self._ZoomOut() else: if event.GetWheelRotation() > 0: self._ShowPrevious() else: self._ShowNext() def EventShowMenu( self, event ): self._last_drag_coordinates = None # to stop successive right-click drag warp bug menu = wx.Menu() menu.Append( CC.ID_NULL, self._current_media.GetPrettyInfo() ) menu.Append( CC.ID_NULL, self._current_media.GetPrettyAge() ) menu.AppendSeparator() menu.Append( CC.ID_NULL, 'current zoom: ' + HC.ConvertZoomToPercentage( self._current_zoom ) ) menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'zoom_in' ), 'zoom in' ) menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'zoom_out' ), 'zoom out' ) # if self._current_media.GetMime() not in NON_LARGABLY_ZOOMABLE_MIMES + NON_ZOOMABLE_MIMES: ( my_width, my_height ) = self.GetClientSize() ( media_width, media_height ) = self._current_display_media.GetResolution() if self._current_zoom == 1.0: if media_width > my_width or media_height > my_height: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'zoom_switch' ), 'zoom fit' ) else: menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'zoom_switch' ), 'zoom full' ) # menu.AppendSeparator() if self._current_media.HasInbox(): menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'archive' ), '&archive' ) if self._current_media.HasArchive(): menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'inbox' ), 'return to &inbox' ) menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', HC.LOCAL_FILE_SERVICE_IDENTIFIER ), '&delete' ) menu.AppendSeparator() copy_menu = wx.Menu() copy_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_files' ) , 'file' ) copy_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_path' ) , 'path' ) copy_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_local_url' ) , 'local url' ) menu.AppendMenu( CC.ID_NULL, 'copy', copy_menu ) menu.AppendSeparator() self._menu_open = True self.PopupMenu( menu ) self._menu_open = False menu.Destroy() event.Skip() class CanvasFullscreenMediaListFilter( CanvasFullscreenMediaList ): def __init__( self, my_parent, page_key, file_service_identifier, predicates, media_results ): CanvasFullscreenMediaList.__init__( self, my_parent, page_key, file_service_identifier, predicates, media_results ) self._kept = set() self._deleted = set() self.Bind( wx.EVT_LEFT_DOWN, self.EventMouseKeep ) self.Bind( wx.EVT_LEFT_DCLICK, self.EventMouseKeep ) self.Bind( wx.EVT_MIDDLE_DOWN, self.EventBack ) self.Bind( wx.EVT_MIDDLE_DCLICK, self.EventBack ) self.Bind( wx.EVT_MOUSEWHEEL, self.EventMouseWheel ) self.Bind( wx.EVT_RIGHT_DOWN, self.EventDelete ) self.Bind( wx.EVT_RIGHT_DCLICK, self.EventDelete ) self.Bind( wx.EVT_MENU, self.EventMenu ) self.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown ) 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 ) if self._current_media == self._GetLast(): self.EventClose( None ) else: self._ShowNext() def EventBack( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: if self._current_media == self._GetFirst(): return else: self._ShowPrevious() self._kept.discard( self._current_media ) self._deleted.discard( self._current_media ) 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 ): if self._current_media == self._GetLast(): self.EventClose( event ) else: self._ShowNext() def EventClose( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: if len( self._kept ) > 0 or len( self._deleted ) > 0: with ClientGUIDialogs.DialogFinishFiltering( self, len( self._kept ), len( self._deleted ) ) as dlg: modal = dlg.ShowModal() if modal == wx.ID_CANCEL: if self._current_media in self._kept: self._kept.remove( self._current_media ) if self._current_media in self._deleted: self._deleted.remove( self._current_media ) else: if modal == wx.ID_YES: try: self._deleted_hashes = [ media.GetHash() for media in self._deleted ] self._kept_hashes = [ media.GetHash() for media in self._kept ] content_updates = [] content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, self._deleted_hashes ) ) content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, self._kept_hashes ) ) HC.app.Write( 'content_updates', { HC.LOCAL_FILE_SERVICE_IDENTIFIER : content_updates } ) self._kept = set() self._deleted = set() except Exception as e: HC.pubsub.pub( 'message', HC.Message( HC.MESSAGE_TYPE_ERROR, e ) ) wx.MessageBox( traceback.format_exc() ) print( traceback.format_exc() ) self._current_media = self._GetFirst() # so the pubsub on close is better CanvasFullscreenMediaList.EventClose( self, event ) else: CanvasFullscreenMediaList.EventClose( self, event ) def EventDelete( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: self._Delete() def EventKeyDown( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: if event.KeyCode == wx.WXK_SPACE: self._Keep() elif event.KeyCode in ( ord( '+' ), wx.WXK_ADD, wx.WXK_NUMPAD_ADD ): self._ZoomIn() elif event.KeyCode in ( ord( '-' ), wx.WXK_SUBTRACT, wx.WXK_NUMPAD_SUBTRACT ): self._ZoomOut() elif event.KeyCode == ord( 'Z' ): self._ZoomSwitch() elif event.KeyCode == wx.WXK_BACK: self.EventBack( event ) elif event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_ESCAPE ): self.EventClose( event ) elif event.KeyCode in ( wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE ): self.EventDelete( event ) elif event.KeyCode == ord( 'C' ) and event.CmdDown(): with wx.BusyCursor(): HC.app.Write( 'copy_files', ( self._current_media.GetHash(), ) ) elif not event.ShiftDown() and event.KeyCode in ( wx.WXK_UP, wx.WXK_NUMPAD_UP ): self.EventSkip( event ) else: ( modifier, key ) = HC.GetShortcutFromEvent( event ) key_dict = self._options[ 'shortcuts' ][ modifier ] if key in key_dict: action = key_dict[ key ] self.ProcessEvent( wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) ) else: event.Skip() def EventMouseKeep( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: if event.ShiftDown(): self.EventDragBegin( event ) else: self._Keep() def EventMenu( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() ) if action is not None: try: ( command, data ) = action if command == 'archive': self._Keep() elif command == 'back': self.EventBack( event ) elif command == 'close': self.EventClose( event ) elif command == 'delete': self.EventDelete( event ) elif command == 'fullscreen_switch': self._FullscreenSwitch() elif command == 'filter': self.EventClose( event ) elif command == 'frame_back': self._media_container.GotoPreviousOrNextFrame( -1 ) elif command == 'frame_next': self._media_container.GotoPreviousOrNextFrame( 1 ) elif command == 'manage_ratings': self._ManageRatings() elif command == 'manage_tags': self._ManageTags() elif command in ( 'pan_up', 'pan_down', 'pan_left', 'pan_right' ): distance = 20 if command == 'pan_up': self._DoManualPan( 0, -distance ) elif command == 'pan_down': self._DoManualPan( 0, distance ) elif command == 'pan_left': self._DoManualPan( -distance, 0 ) elif command == 'pan_right': self._DoManualPan( distance, 0 ) elif command == 'zoom_in': self._ZoomIn() elif command == 'zoom_out': self._ZoomOut() else: event.Skip() except Exception as e: HC.pubsub.pub( 'message', HC.Message( HC.MESSAGE_TYPE_ERROR, e ) ) wx.MessageBox( HC.u( e ) ) print( HC.u( e ) ) def EventMouseWheel( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: if event.CmdDown(): if event.GetWheelRotation() > 0: self._ZoomIn() else: self._ZoomOut() def EventSkip( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: if self._current_media == self._GetLast(): self.EventClose( event ) else: self._ShowNext() 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 FullscreenPopoutFilterCustom( FullscreenPopout ): def _InitialisePopoutWindow( self, sizer ): window = wx.Window( self ) vbox = wx.BoxSizer( wx.VERTICAL ) parent = self.GetParent() actions = wx.Button( window, label = 'actions' ) actions.Bind( wx.EVT_BUTTON, parent.EventActions ) done = wx.Button( window, label = 'done' ) done.Bind( wx.EVT_BUTTON, parent.EventClose ) vbox.AddF( actions, FLAGS_EXPAND_PERPENDICULAR ) vbox.AddF( done, FLAGS_EXPAND_PERPENDICULAR ) window.SetSizer( vbox ) return window 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 = HC.app.Read( 'options' ) accuracy_slider_hbox = wx.BoxSizer( wx.HORIZONTAL ) if 'ratings_filter_accuracy' not in options: options[ 'ratings_filter_accuracy' ] = 1 HC.app.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 HC.app.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' HC.app.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 ) skip = wx.Button( window, label = 'skip' ) skip.Bind( wx.EVT_BUTTON, self._callable_parent.EventButtonSkip ) 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( skip, 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 ): CanvasFullscreenMediaListFilter.__init__( self, my_parent, page_key, HC.LOCAL_FILE_SERVICE_IDENTIFIER, [], media_results ) self._rating_service_identifier = service_identifier self._service = HC.app.Read( 'service', service_identifier ) FullscreenPopoutFilterLike( self ) def EventClose( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: if len( self._kept ) > 0 or len( self._deleted ) > 0: ( like, dislike ) = self._service.GetExtraInfo() with ClientGUIDialogs.DialogFinishFiltering( self, len( self._kept ), len( self._deleted ), keep = like, delete = dislike ) as dlg: modal = dlg.ShowModal() if modal == wx.ID_CANCEL: if self._current_media in self._kept: self._kept.remove( self._current_media ) if self._current_media in self._deleted: self._deleted.remove( self._current_media ) else: if modal == wx.ID_YES: try: self._deleted_hashes = [ media.GetHash() for media in self._deleted ] self._kept_hashes = [ media.GetHash() for media in self._kept ] content_updates = [] content_updates.extend( [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( 0.0, set( ( hash, ) ) ) ) for hash in self._deleted_hashes ] ) content_updates.extend( [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( 1.0, set( ( hash, ) ) ) ) for hash in self._kept_hashes ] ) HC.app.Write( 'content_updates', { self._rating_service_identifier : content_updates } ) self._kept = set() self._deleted = set() except Exception as e: HC.pubsub.pub( 'message', HC.Message( HC.MESSAGE_TYPE_ERROR, e ) ) wx.MessageBox( traceback.format_exc() ) print( traceback.format_exc() ) CanvasFullscreenMediaList.EventClose( self, event ) else: CanvasFullscreenMediaList.EventClose( self, event ) class RatingsFilterFrameNumerical( ClientGUICommon.Frame ): RATINGS_FILTER_INEQUALITY_FULL = 0 RATINGS_FILTER_INEQUALITY_HALF = 1 RATINGS_FILTER_INEQUALITY_QUARTER = 2 RATINGS_FILTER_EQUALITY_FULL = 0 RATINGS_FILTER_EQUALITY_HALF = 1 RATINGS_FILTER_EQUALITY_QUARTER = 2 def __init__( self, parent, page_key, service_identifier, media_results ): ClientGUICommon.Frame.__init__( self, parent, title = 'hydrus client ratings 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 ) if service_identifier.GetType() == HC.LOCAL_RATING_LIKE: self._score_gap = 1.0 else: self._service = HC.app.Read( 'service', service_identifier ) ( self._lower, self._upper ) = self._service.GetExtraInfo() self._score_gap = 1.0 / ( self._upper - self._lower ) hashes_to_min_max = HC.app.Read( 'ratings_filter', service_identifier, [ media_result.GetHash() for media_result in media_results ] ) self._media_to_initial_scores_dict = { media : hashes_to_min_max[ media.GetHash() ] for media in self._media_still_to_rate } self._decision_log = [] self._ReinitialiseCurrentScores() self._inequal_accuracy = self.RATINGS_FILTER_INEQUALITY_FULL self._equal_accuracy = self.RATINGS_FILTER_EQUALITY_FULL self._statusbar = self.CreateStatusBar() self._statusbar.SetFieldsCount( 3 ) self._statusbar.SetStatusWidths( [ -1, 500, -1 ] ) self._splitter = wx.SplitterWindow( self ) self._splitter.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown ) vbox = wx.BoxSizer( wx.VERTICAL ) vbox.AddF( self._splitter, FLAGS_EXPAND_BOTH_WAYS ) self.SetSizer( vbox ) self._splitter.SetMinimumPaneSize( 120 ) self._splitter.SetSashGravity( 0.5 ) # stay in the middle if True: # if borderless fullscreen self.ShowFullScreen( True, wx.FULLSCREEN_ALL ^ wx.FULLSCREEN_NOSTATUSBAR ) else: self.Maximize() self.Show( True ) HC.app.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() self._splitter.SplitVertically( self._left_window, self._right_window, my_width / 2 ) self.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown ) self.Bind( wx.EVT_LEFT_DOWN, self.EventMouseDown ) self.Bind( wx.EVT_RIGHT_DOWN, self.EventMouseDown ) self.Bind( wx.EVT_MENU, self.EventMenu ) self.Bind( wx.EVT_CLOSE, self.EventClose ) self._ShowNewMedia() HC.pubsub.sub( self, 'ProcessContentUpdates', 'content_updates_gui' ) HC.pubsub.sub( self, 'ProcessServiceUpdates', 'service_updates_gui' ) HC.pubsub.pub( 'set_focus', self._page_key, None ) def _FullscreenSwitch( self ): if self.IsFullScreen(): self.ShowFullScreen( False ) self.Maximize() else: self.ShowFullScreen( True, wx.FULLSCREEN_ALL ) def _GoBack( self ): if len( self._decision_log ) > 0: ( action, entry ) = self._decision_log[-1] if action == 'external': ( min, max, self._current_media_to_rate, self._current_media_to_rate_against, self._unrated_is_on_the_left ) = entry elif action == 'internal': ( min, max, self._current_media_to_rate, other_min, other_max, self._current_media_to_rate_against, self._unrated_is_on_the_left ) = entry if self._unrated_is_on_the_left: self._left_window.SetMedia( self._current_media_to_rate ) self._right_window.SetMedia( self._current_media_to_rate_against ) else: self._left_window.SetMedia( self._current_media_to_rate_against ) self._right_window.SetMedia( self._current_media_to_rate ) self._decision_log = self._decision_log[:-1] self._ReinitialiseCurrentScores() self._RefreshStatusBar() def _RefreshStatusBar( self ): certain_ratings = [ ( media, ( min, max ) ) for ( media, ( min, max ) ) in self._media_to_current_scores_dict.items() if max - min < self._score_gap ] uncertain_ratings = [ ( media, ( min, max ) ) for ( media, ( min, max ) ) in self._media_to_current_scores_dict.items() if max - min >= self._score_gap and self._media_to_current_scores_dict[ media ] != self._media_to_initial_scores_dict[ media ] ] service_type = self._service_identifier.GetType() if service_type == HC.LOCAL_RATING_LIKE: current_string = 'uncertain' if self._current_media_to_rate_against in self._media_still_to_rate: against_string = 'uncertain' else: against_string = 'already rated' if self._current_media_to_rate_against in self._media_to_current_scores_dict: ( other_min, other_max ) = self._media_to_current_scores_dict[ self._current_media_to_rate_against ] rating = other_min else: ( local_ratings, remote_ratings ) = self._current_media_to_rate_against.GetRatings() rating = local_ratings.GetRating( self._service_identifier ) if rating == 0.0: against_string += ' - dislike' else: against_string += ' - like' center_string = HC.u( len( self._media_to_initial_scores_dict ) ) + ' files being rated. after ' + HC.u( len( self._decision_log ) ) + ' decisions, ' + HC.u( len( certain_ratings ) ) + ' are certain' elif service_type == HC.LOCAL_RATING_NUMERICAL: ( min, max ) = self._media_to_current_scores_dict[ self._current_media_to_rate ] current_string = 'between ' + HC.ConvertNumericalRatingToPrettyString( self._lower, self._upper, min, out_of = False ) + ' and ' + HC.ConvertNumericalRatingToPrettyString( self._lower, self._upper, max, out_of = False ) if self._current_media_to_rate_against in self._media_still_to_rate: ( other_min, other_max ) = self._media_to_current_scores_dict[ self._current_media_to_rate_against ] against_string = 'between ' + HC.ConvertNumericalRatingToPrettyString( self._lower, self._upper, other_min, out_of = False ) + ' and ' + HC.ConvertNumericalRatingToPrettyString( self._lower, self._upper, other_max, out_of = False ) else: against_string = 'already rated' if self._current_media_to_rate_against in self._media_to_current_scores_dict: ( other_min, other_max ) = self._media_to_current_scores_dict[ self._current_media_to_rate_against ] rating = ( other_min + other_max ) / 2.0 else: ( local_ratings, remote_ratings ) = self._current_media_to_rate_against.GetRatings() rating = local_ratings.GetRating( self._service_identifier ) against_string += ' - ' + HC.ConvertNumericalRatingToPrettyString( self._lower, self._upper, rating ) center_string = HC.u( len( self._media_to_initial_scores_dict ) ) + ' files being rated. after ' + HC.u( len( self._decision_log ) ) + ' decisions, ' + HC.u( len( certain_ratings ) ) + ' are certain and ' + HC.u( len( uncertain_ratings ) ) + ' are uncertain' if self._unrated_is_on_the_left: left_string = current_string right_string = against_string else: left_string = against_string right_string = current_string self._statusbar.SetStatusText( left_string, number = 0 ) self._statusbar.SetStatusText( center_string, number = 1 ) self._statusbar.SetStatusText( right_string, number = 2 ) def _ReinitialiseCurrentScores( self ): self._media_to_current_scores_dict = dict( self._media_to_initial_scores_dict ) self._already_rated_pairs = collections.defaultdict( set ) for ( action, entry ) in self._decision_log: if action == 'external': ( min, max, media_rated, media_rated_against, unrated_was_on_the_left ) = entry elif action == 'internal': ( min, max, media_rated, other_min, other_max, media_rated_against, unrated_was_on_the_left ) = entry self._media_to_current_scores_dict[ media_rated_against ] = ( other_min, other_max ) self._media_to_current_scores_dict[ media_rated ] = ( min, max ) self._already_rated_pairs[ media_rated ].add( media_rated_against ) self._already_rated_pairs[ media_rated_against ].add( media_rated ) self._media_still_to_rate = { media for ( media, ( min, max ) ) in self._media_to_current_scores_dict.items() if max - min >= self._score_gap } def _ShowNewMedia( self ): 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 ] media_result_to_rate_against = HC.app.Read( 'ratings_media_result', self._service_identifier, min, max ) if media_result_to_rate_against is not None: hash = media_result_to_rate_against.GetHash() if hash in self._file_query_result.GetHashes(): media_result_to_rate_against = self._file_query_result.GetMediaResult( hash ) else: self._file_query_result.AddMediaResult( media_result_to_rate_against ) media_to_rate_against = ClientGUIMixins.MediaSingleton( media_result_to_rate_against ) else: media_to_rate_against = None if media_to_rate_against in self._already_rated_pairs[ self._current_media_to_rate ]: media_to_rate_against = None if media_to_rate_against is None: internal_media = list( self._media_to_current_scores_dict.keys() ) random.shuffle( internal_media ) valid_internal_media = [ media for media in internal_media if media != self._current_media_to_rate and media not in self._already_rated_pairs[ self._current_media_to_rate ] and self._current_media_to_rate not in self._already_rated_pairs[ media ] ] best_media_first = Queue.PriorityQueue() for media in valid_internal_media: ( other_min, other_max ) = self._media_to_current_scores_dict[ media ] if not ( other_max < min or other_min > max ): # i.e. there is overlap in the two pairs of min,max # it is best when we have # # ######### # #### # # and better when the gaps are large (increasing the uncertainty) # when we must choose # # ##### # ###### # # saying the second is better gives no change, so we want to minimise the gaps, to increase the likelyhood of a 50-50-ish situation (increasing the uncertainty) # better we move by self._score_gap half the time than 0 most of the time. # the square root stuff prioritises middle-of-the-road results. two fives is more useful than ten and zero # total gap value is in the range 0.0 - 1.0 # we times by -1 to prioritise and simultaneously reverse the overlapping-on-both-ends results for the priority queue min_gap = abs( other_min - min ) max_gap = abs( other_max - max ) total_gap_value = ( min_gap ** 0.5 + max_gap ** 0.5 ) ** 2 if ( other_min < min and other_max > max ) or ( other_min > min and other_max < max ): total_gap_value *= -1 best_media_first.put( ( total_gap_value, media ) ) if best_media_first.qsize() > 0: ( value, media_to_rate_against ) = best_media_first.get() if media_to_rate_against is None: message = 'The client has run out of comparisons to show you, and still cannot deduce what ratings everything should have. Commit what decisions you have made, and then please either rate some more files manually, or ratings filter a larger group.' wx.MessageBox( message ) self.EventClose( None ) else: self._current_media_to_rate_against = media_to_rate_against 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 self._left_window.SetMedia( self._current_media_to_rate ) self._right_window.SetMedia( self._current_media_to_rate_against ) else: self._unrated_is_on_the_left = False self._left_window.SetMedia( self._current_media_to_rate_against ) self._right_window.SetMedia( self._current_media_to_rate ) self._RefreshStatusBar() def _Skip( self ): if len( self._media_still_to_rate ) == 0: self.EventClose() else: self._ShowNewMedia() def _ProcessAction( self, action ): ( min, max ) = self._media_to_current_scores_dict[ self._current_media_to_rate ] if self._current_media_to_rate_against in self._media_to_current_scores_dict: ( other_min, other_max ) = self._media_to_current_scores_dict[ self._current_media_to_rate_against ] rate_other = self._current_media_to_rate_against in self._media_still_to_rate if action in ( 'left', 'right' ): if self._inequal_accuracy == self.RATINGS_FILTER_INEQUALITY_FULL: adjustment = self._score_gap if self._inequal_accuracy == self.RATINGS_FILTER_INEQUALITY_HALF: adjustment = 0 elif self._inequal_accuracy == self.RATINGS_FILTER_INEQUALITY_QUARTER: adjustment = -self._score_gap if ( self._unrated_is_on_the_left and action == 'left' ) or ( not self._unrated_is_on_the_left and action == 'right' ): # unrated is better if min <= other_min: if min < other_min + adjustment: min = other_min + adjustment else: min = other_min + self._score_gap / 2 if other_max >= max: if other_max > max - adjustment: other_max = max - adjustment else: other_max = max - self._score_gap / 2 if min >= max: min = max if other_max <= other_min: other_max = other_min else: # unrated is worse if other_min <= min: if other_min < min + adjustment: other_min = min + adjustment else: other_min = min + self._score_gap / 2 if max >= other_max: if max > other_max - adjustment: max = other_max - adjustment else: max = other_max - self._score_gap / 2 if other_min >= other_max: other_min = other_max if max <= min: max = min elif action == 'equal': if self._equal_accuracy == self.RATINGS_FILTER_EQUALITY_FULL: if min < other_min: min = other_min else: other_min = min if max > other_max: max = other_max else: other_max = max elif self._equal_accuracy == self.RATINGS_FILTER_EQUALITY_HALF: if min < other_min: min = ( min + other_min ) / 2 else: other_min = ( min + other_min ) / 2 if max > other_max: max = ( max + other_max ) / 2 else: other_max = ( max + other_max ) / 2 elif self._equal_accuracy == self.RATINGS_FILTER_EQUALITY_QUARTER: if min < other_min: min = ( ( 3 * min ) + other_min ) / 4 else: other_min = ( min + ( 3 * other_min ) ) / 4 if max > other_max: max = ( ( 3 * max ) + other_max ) / 4 else: other_max = ( max + ( 3 * other_max ) ) / 4 if min < 0.0: min = 0.0 if max > 1.0: max = 1.0 if other_min < 0.0: other_min = 0.0 if other_max > 1.0: other_max = 1.0 if max - min < self._score_gap: self._media_still_to_rate.discard( self._current_media_to_rate ) if rate_other: if other_max - other_min < self._score_gap: self._media_still_to_rate.discard( self._current_media_to_rate_against ) self._media_to_current_scores_dict[ self._current_media_to_rate_against ] = ( other_min, other_max ) decision = ( 'internal', ( min, max, self._current_media_to_rate, other_min, other_max, self._current_media_to_rate_against, self._unrated_is_on_the_left ) ) else: ( local_ratings, remote_ratings ) = self._current_media_to_rate_against.GetRatings() rating = local_ratings.GetRating( self._service_identifier ) if action in ( 'left', 'right' ): if self._inequal_accuracy == self.RATINGS_FILTER_INEQUALITY_FULL: adjustment = self._score_gap if self._inequal_accuracy == self.RATINGS_FILTER_INEQUALITY_HALF: adjustment = 0 elif self._inequal_accuracy == self.RATINGS_FILTER_INEQUALITY_QUARTER: adjustment = -self._score_gap if ( self._unrated_is_on_the_left and action == 'left' ) or ( not self._unrated_is_on_the_left and action == 'right' ): # unrated is better, so set new min if min <= rating: if min < rating + adjustment: min = rating + adjustment else: min = rating + self._score_gap / 2 if min > max: min = max else: # unrated is worse, so set new max if max >= rating: if max > rating - adjustment: max = rating - adjustment else: max = rating - self._score_gap / 2 if max < min: max = min elif action == 'equal': if self._equal_accuracy == self.RATINGS_FILTER_EQUALITY_FULL: min = rating max = rating elif self._equal_accuracy == self.RATINGS_FILTER_EQUALITY_HALF: min = ( min + rating ) / 2 max = ( max + rating ) / 2 elif self._equal_accuracy == self.RATINGS_FILTER_EQUALITY_QUARTER: min = ( ( 3 * min ) + rating ) / 4 max = ( ( 3 * max ) + rating ) / 4 if min < 0.0: min = 0.0 if max > 1.0: max = 1.0 decision = ( 'external', ( min, max, self._current_media_to_rate, self._current_media_to_rate_against, self._unrated_is_on_the_left ) ) self._decision_log.append( decision ) self._already_rated_pairs[ self._current_media_to_rate ].add( self._current_media_to_rate_against ) self._already_rated_pairs[ self._current_media_to_rate_against ].add( self._current_media_to_rate ) if max - min < self._score_gap: self._media_still_to_rate.discard( self._current_media_to_rate ) self._media_to_current_scores_dict[ self._current_media_to_rate ] = ( min, max ) if len( self._media_still_to_rate ) == 0: self.EventClose( None ) 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 EventButtonSkip( self, event ): self._Skip() def EventClose( self, event ): if len( self._decision_log ) > 0: def normalise_rating( rating ): return round( rating / self._score_gap ) * self._score_gap certain_ratings = [ ( normalise_rating( ( min + max ) / 2 ), media.GetHash() ) for ( media, ( min, max ) ) in self._media_to_current_scores_dict.items() if max - min < self._score_gap ] uncertain_ratings = [ ( min, max, media.GetHash() ) for ( media, ( min, max ) ) in self._media_to_current_scores_dict.items() if max - min >= self._score_gap and self._media_to_current_scores_dict[ media ] != self._media_to_initial_scores_dict[ media ] ] with ClientGUIDialogs.DialogFinishRatingFiltering( self, len( certain_ratings ), len( uncertain_ratings ) ) as dlg: modal = dlg.ShowModal() if modal == wx.ID_CANCEL: self._ShowNewMedia() return elif modal == wx.ID_YES: try: content_updates = [] content_updates.extend( [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, set( ( hash, ) ) ) ) for ( rating, hash ) in certain_ratings ] ) content_updates.extend( [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_RATINGS, HC.CONTENT_UPDATE_RATINGS_FILTER, ( min, max, set( ( hash, ) ) ) ) for ( min, max, hash ) in uncertain_ratings ] ) HC.app.Write( 'content_updates', { self._service_identifier : content_updates } ) except Exception as e: HC.pubsub.pub( 'message', HC.Message( HC.MESSAGE_TYPE_ERROR, e ) ) wx.MessageBox( traceback.format_exc() ) print( traceback.format_exc() ) HC.pubsub.pub( 'set_focus', self._page_key, self._current_media_to_rate ) self.Destroy() def EventKeyDown( self, event ): if event.KeyCode in ( wx.WXK_SPACE, wx.WXK_UP, wx.WXK_NUMPAD_UP ): self._Skip() elif event.KeyCode in ( wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN ): self._ProcessAction( 'equal' ) elif event.KeyCode in ( wx.WXK_LEFT, wx.WXK_NUMPAD_LEFT ): self._ProcessAction( 'left' ) elif event.KeyCode in ( wx.WXK_RIGHT, wx.WXK_NUMPAD_RIGHT ): self._ProcessAction( 'right' ) elif event.KeyCode == wx.WXK_BACK: self._GoBack() elif event.KeyCode in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_ESCAPE ): self.EventClose( event ) elif event.KeyCode == ord( 'C' ) and event.CmdDown(): with wx.BusyCursor(): HC.app.Write( 'copy_files', ( self._current_media.GetHash(), ) ) else: event.Skip() def EventMenu( self, event ): action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() ) if action is not None: try: ( command, data ) = action if command == 'fullscreen_switch': self._FullscreenSwitch() else: event.Skip() except Exception as e: HC.pubsub.pub( 'message', HC.Message( HC.MESSAGE_TYPE_ERROR, e ) ) wx.MessageBox( HC.u( e ) ) print( HC.u( e ) ) def EventMouseDown( self, event ): if event.ButtonDown( wx.MOUSE_BTN_LEFT ): self._ProcessAction( 'left' ) elif event.ButtonDown( wx.MOUSE_BTN_RIGHT ): self._ProcessAction( 'right' ) elif event.ButtonDown( wx.MOUSE_BTN_MIDDLE ): self._ProcessAction( 'equal' ) def ProcessContentUpdates( self, service_identifiers_to_content_updates ): redraw = False my_hashes = self._file_query_result.GetHashes() for ( service_identifier, content_updates ) in service_identifiers_to_content_updates.items(): for content_update in content_updates: content_update_hashes = content_update.GetHashes() if len( my_hashes.intersection( content_update_hashes ) ) > 0: redraw = True break if redraw: self._left_window.RefreshBackground() self._right_window.RefreshBackground() def ProcessServiceUpdates( self, service_identifiers_to_service_updates ): self._left_window.RefreshBackground() 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 HC.app.Write( 'save_options' ) def SetCompareSame( self, compare_same ): self._options[ 'ratings_filter_compare_same' ] = compare_same HC.app.Write( 'save_options' ) self._compare_same = compare_same def SetLeftRight( self, left_right ): self._options[ 'ratings_filter_left_right' ] = left_right HC.app.Write( 'save_options' ) self._left_right = left_right class _Panel( Canvas, wx.Window ): def __init__( self, parent ): wx.Window.__init__( self, parent, style = wx.SIMPLE_BORDER | wx.WANTS_CHARS ) Canvas.__init__( self, HC.LOCAL_FILE_SERVICE_IDENTIFIER, HC.app.GetFullscreenImageCache() ) wx.CallAfter( self.Refresh ) self.Bind( wx.EVT_MOTION, self.EventDrag ) self.Bind( wx.EVT_LEFT_DOWN, self.EventDragBegin ) self.Bind( wx.EVT_RIGHT_DOWN, self.GetParent().GetParent().EventMouseDown ) self.Bind( wx.EVT_MIDDLE_DOWN, self.GetParent().GetParent().EventMouseDown ) self.Bind( wx.EVT_LEFT_UP, self.EventDragEnd ) self.Bind( wx.EVT_MOUSEWHEEL, self.EventMouseWheel ) self.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown ) self._timer_cursor_hide = wx.Timer( self, id = ID_TIMER_CURSOR_HIDE ) self.Bind( wx.EVT_TIMER, self.EventTimerCursorHide, id = ID_TIMER_CURSOR_HIDE ) self.Bind( wx.EVT_MENU, self.EventMenu ) def _ZoomIn( self ): if self._current_media is not None: if self._current_media.GetMime() in NON_ZOOMABLE_MIMES: return for zoom in ZOOMINS: if self._current_zoom < zoom: if self._current_media.GetMime() in ( HC.APPLICATION_FLASH, HC.VIDEO_FLV ): # because of the event passing under mouse, we want to preserve whitespace around flash ( original_width, original_height ) = self._current_display_media.GetResolution() ( my_width, my_height ) = self.GetClientSize() new_media_width = int( round( original_width * zoom ) ) new_media_height = int( round( original_height * zoom ) ) if new_media_width >= my_width or new_media_height >= my_height: return with wx.FrozenWindow( self ): ( drag_x, drag_y ) = self._total_drag_delta zoom_ratio = zoom / self._current_zoom self._total_drag_delta = ( int( drag_x * zoom_ratio ), int( drag_y * zoom_ratio ) ) self._current_zoom = zoom self._DrawBackgroundBitmap() self._DrawCurrentMedia() break def _ZoomOut( self ): if self._current_media is not None: if self._current_media.GetMime() in NON_ZOOMABLE_MIMES: return for zoom in ZOOMOUTS: if self._current_zoom > zoom: with wx.FrozenWindow( self ): ( drag_x, drag_y ) = self._total_drag_delta zoom_ratio = zoom / self._current_zoom self._total_drag_delta = ( int( drag_x * zoom_ratio ), int( drag_y * zoom_ratio ) ) self._current_zoom = zoom self._DrawBackgroundBitmap() self._DrawCurrentMedia() break def _ZoomSwitch( self ): ( my_width, my_height ) = self.GetClientSize() ( media_width, media_height ) = self._current_display_media.GetResolution() if self._current_media.GetMime() in NON_ZOOMABLE_MIMES: return if self._current_media.GetMime() not in ( HC.APPLICATION_FLASH, HC.VIDEO_FLV ) or self._current_zoom > 1.0 or ( media_width < my_width and media_height < my_height ): new_zoom = self._current_zoom if self._current_zoom == 1.0: if media_width > my_width or media_height > my_height: width_zoom = my_width / float( media_width ) height_zoom = my_height / float( media_height ) new_zoom = min( ( width_zoom, height_zoom ) ) else: new_zoom = 1.0 if new_zoom != self._current_zoom: ( drag_x, drag_y ) = self._total_drag_delta zoom_ratio = new_zoom / self._current_zoom self._total_drag_delta = ( int( drag_x * zoom_ratio ), int( drag_y * zoom_ratio ) ) self._current_zoom = new_zoom self._DrawBackgroundBitmap() self._DrawCurrentMedia() def EventDrag( self, event ): if wx.Window.FindFocus() != self: self.SetFocus() 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 ) try: self.WarpPointer( old_x, old_y ) except: self._last_drag_coordinates = ( x, 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._DrawCurrentMedia() self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) ) self._timer_cursor_hide.Start( 800, wx.TIMER_ONE_SHOT ) def EventDragBegin( self, event ): if event.ShiftDown(): ( x, y ) = event.GetPosition() ( client_x, client_y ) = self.GetClientSize() if x < 20 or x > client_x - 20 or y < 20 or y > client_y -20: try: better_x = x better_y = y if x < 20: better_x = 20 if y < 20: better_y = 20 if x > client_x - 20: better_x = client_x - 20 if y > client_y - 20: better_y = client_y - 20 self.WarpPointer( better_x, better_y ) x = better_x y = better_y except: pass self._last_drag_coordinates = ( x, y ) else: self.GetParent().GetParent().ProcessEvent( event ) def EventDragEnd( self, event ): self._last_drag_coordinates = None event.Skip() def EventKeyDown( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: keys_i_want_to_bump_up_regardless = [ wx.WXK_SPACE, wx.WXK_UP, wx.WXK_NUMPAD_UP, wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN, wx.WXK_LEFT, wx.WXK_NUMPAD_LEFT, wx.WXK_RIGHT, wx.WXK_NUMPAD_RIGHT, wx.WXK_BACK, wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_ESCAPE ] ( modifier, key ) = HC.GetShortcutFromEvent( event ) key_dict = self._options[ 'shortcuts' ][ modifier ] if event.KeyCode not in keys_i_want_to_bump_up_regardless and key in key_dict: action = key_dict[ key ] self.ProcessEvent( wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) ) else: if event.KeyCode in ( ord( '+' ), wx.WXK_ADD, wx.WXK_NUMPAD_ADD ): self._ZoomIn() elif event.KeyCode in ( ord( '-' ), wx.WXK_SUBTRACT, wx.WXK_NUMPAD_SUBTRACT ): self._ZoomOut() elif event.KeyCode == ord( 'Z' ): self._ZoomSwitch() elif event.KeyCode == ord( 'C' ) and event.CmdDown(): with wx.BusyCursor(): HC.app.Write( 'copy_files', ( self._current_media.GetHash(), ) ) else: self.GetParent().ProcessEvent( event ) def EventMenu( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() ) if action is not None: try: ( command, data ) = action if command == 'frame_back': self._media_container.GotoPreviousOrNextFrame( -1 ) elif command == 'frame_next': self._media_container.GotoPreviousOrNextFrame( 1 ) elif command == 'manage_ratings': self._ManageRatings() elif command == 'manage_tags': self._ManageTags() elif command == 'zoom_in': self._ZoomIn() elif command == 'zoom_out': self._ZoomOut() else: event.Skip() except Exception as e: HC.pubsub.pub( 'message', HC.Message( HC.MESSAGE_TYPE_ERROR, e ) ) wx.MessageBox( HC.u( e ) ) print( HC.u( e ) ) def EventMouseWheel( self, event ): if self._ShouldSkipInputDueToFlash(): event.Skip() else: if event.CmdDown(): if event.GetWheelRotation() > 0: self._ZoomIn() else: self._ZoomOut() def EventTimerCursorHide( self, event ): self.SetCursor( wx.StockCursor( wx.CURSOR_BLANK ) ) def RefreshBackground( self ): self._DrawBackgroundBitmap() class MediaContainer( wx.Window ): def __init__( self, parent, image_cache, media, initial_size, initial_position ): wx.Window.__init__( self, parent, size = initial_size, pos = initial_position ) self._image_cache = image_cache self._media = media self._media_window = None self._embed_button = None self._MakeMediaWindow() self.Bind( wx.EVT_SIZE, self.EventResize ) self.Bind( wx.EVT_MOUSE_EVENTS, self.EventPropagateMouse ) self.EventResize( None ) def _MakeMediaWindow( self, do_embed_button = True ): ( media_initial_size, media_initial_position ) = ( self.GetClientSize(), ( 0, 0 ) ) if do_embed_button and self._media.GetMime() in ( HC.VIDEO_FLV, HC.APPLICATION_FLASH ): self._embed_button = EmbedButton( self, media_initial_size ) self._embed_button.Bind( wx.EVT_LEFT_DOWN, self.EventEmbedButton ) return elif self._embed_button is not None: self._embed_button.Hide() if ShouldHaveAnimationBar( self._media ): ( x, y ) = media_initial_size media_initial_size = ( x, y - ANIMATED_SCANBAR_HEIGHT ) if self._media.GetMime() in HC.IMAGES: self._media_window = Image( self, self._media, self._image_cache, media_initial_size, media_initial_position ) elif self._media.GetMime() == HC.APPLICATION_FLASH: self._media_window = wx.lib.flashwin.FlashWindow( self, size = media_initial_size, pos = media_initial_position ) self._media_window.movie = CC.GetFilePath( self._media.GetHash(), HC.APPLICATION_FLASH ) elif self._media.GetMime() == HC.VIDEO_FLV: self._media_window = wx.lib.flashwin.FlashWindow( self, size = media_initial_size, pos = media_initial_position ) flash_vars = [] flash_vars.append( ( 'flv', CC.GetFilePath( self._media.GetHash(), HC.VIDEO_FLV ) ) ) flash_vars.append( ( 'margin', '0' ) ) flash_vars.append( ( 'autoload', '1' ) ) flash_vars.append( ( 'autoplay', '1' ) ) flash_vars.append( ( 'showvolume', '1' ) ) flash_vars.append( ( 'showtime', '1' ) ) flash_vars.append( ( 'loop', '1' ) ) f = urllib.urlencode( flash_vars ) self._media_window.flashvars = f self._media_window.movie = HC.STATIC_DIR + os.path.sep + 'player_flv_maxi_1.6.0.swf' elif self._media.GetMime() == HC.APPLICATION_PDF: self._media_window = PDFButton( self, self._media.GetHash(), media_initial_size ) elif self._media.GetMime() in HC.AUDIO: self._media_window = EmbedWindowAudio( self, self._media.GetHash(), self._media.GetMime(), media_initial_size ) if ShouldHaveAnimationBar( self._media ): self._animation_bar = AnimationBar( self, self._media, self._media_window ) if self._media.GetMime() == HC.IMAGE_GIF: self._media_window.SetAnimationBar( self._animation_bar ) def EventEmbedButton( self, event ): self._MakeMediaWindow( do_embed_button = False ) def EventPropagateMouse( self, event ): if self._media.GetMime() in HC.IMAGES: screen_position = self.ClientToScreen( event.GetPosition() ) ( x, y ) = self.GetParent().ScreenToClient( screen_position ) event.SetX( x ) event.SetY( y ) event.ResumePropagation( 1 ) event.Skip() def EventResize( self, event ): ( my_width, my_height ) = self.GetClientSize() if self._media_window is None: self._embed_button.SetSize( ( my_width, my_height ) ) else: ( media_width, media_height ) = ( my_width, my_height ) if ShouldHaveAnimationBar( self._media ): media_height -= ANIMATED_SCANBAR_HEIGHT self._animation_bar.SetSize( ( my_width, ANIMATED_SCANBAR_HEIGHT ) ) self._animation_bar.SetPosition( ( 0, my_height - ANIMATED_SCANBAR_HEIGHT ) ) self._media_window.SetSize( ( media_width, media_height ) ) self._media_window.SetPosition( ( 0, 0 ) ) def GotoPreviousOrNextFrame( self, direction ): if self._media_window is not None: if ShouldHaveAnimationBar( self._media ): current_frame_index = self._media_window.CurrentFrame() num_frames = self._media.GetNumFrames() if direction == 1: if current_frame_index == num_frames - 1: current_frame_index = 0 else: current_frame_index += 1 else: if current_frame_index == 0: current_frame_index = num_frames - 1 else: current_frame_index -= 1 self._media_window.GotoFrame( current_frame_index ) self._animation_bar.GotoFrame( current_frame_index ) class AnimationBar( wx.Window ): def __init__( self, parent, media, media_window ): ( parent_width, parent_height ) = parent.GetClientSize() wx.Window.__init__( self, parent, size = ( parent_width, ANIMATED_SCANBAR_HEIGHT ), pos = ( 0, parent_height - ANIMATED_SCANBAR_HEIGHT ) ) self._canvas_bmp = wx.EmptyBitmap( parent_width, ANIMATED_SCANBAR_HEIGHT, 24 ) self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) ) self._media = media self._media_window = media_window self._num_frames = self._media.GetNumFrames() self._current_frame_index = 0 self.Bind( wx.EVT_MOUSE_EVENTS, self.EventMouse ) self.Bind( wx.EVT_TIMER, self.EventTimerFlash, id = ID_TIMER_FLASH ) self.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown ) self.Bind( wx.EVT_PAINT, self.EventPaint ) self.Bind( wx.EVT_SIZE, self.EventResize ) if media.GetMime() == HC.APPLICATION_FLASH: self._timer_flash = wx.Timer( self, id = ID_TIMER_FLASH ) self._timer_flash.Start( 100, wx.TIMER_CONTINUOUS ) self._Draw() def _Draw( self ): ( my_width, my_height ) = self._canvas_bmp.GetSize() dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp ) dc.SetPen( wx.TRANSPARENT_PEN ) dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) ) dc.DrawRectangle( 0, 0, my_width, ANIMATED_SCANBAR_HEIGHT ) dc.SetBrush( wx.Brush( wx.SystemSettings.GetColour( wx.SYS_COLOUR_SCROLLBAR ) ) ) dc.DrawRectangle( int( float( my_width - ANIMATED_SCANBAR_CARET_WIDTH ) * float( self._current_frame_index ) / float( self._num_frames - 1 ) ), 0, ANIMATED_SCANBAR_CARET_WIDTH, ANIMATED_SCANBAR_HEIGHT ) def EventKeyDown( self, event ): self.GetParent().GetParent().ProcessEvent( event ) def EventMouse( self, event ): ( my_width, my_height ) = self.GetClientSize() if event.Dragging() or event.ButtonDown(): ( x, y ) = event.GetPosition() compensated_x_position = x - ( ANIMATED_SCANBAR_CARET_WIDTH / 2 ) proportion = float( compensated_x_position ) / float( my_width - ANIMATED_SCANBAR_CARET_WIDTH ) if proportion < 0: proportion = 0 if proportion > 1: proportion = 1 self._current_frame_index = int( proportion * ( self._num_frames - 1 ) + 0.5 ) self._Draw() should_pause = event.Dragging() self._media_window.GotoFrame( self._current_frame_index ) if not should_pause: self._media_window.Play() self.GetParent().GetParent().KeepCursorAlive() else: screen_position = self.ClientToScreen( event.GetPosition() ) ( x, y ) = self.GetParent().ScreenToClient( screen_position ) event.SetX( x ) event.SetY( y ) event.ResumePropagation( 1 ) event.Skip() def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp ) def EventResize( self, event ): ( my_width, my_height ) = self.GetClientSize() ( current_bmp_width, current_bmp_height ) = self._canvas_bmp.GetSize() if my_width != current_bmp_width or my_height != current_bmp_height: if my_width > 0 and my_height > 0: self._canvas_bmp.Destroy() self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 ) self._Draw() def EventTimerFlash( self, event ): # maybe need to pause this while mouse dragging events are occuring? whatever if self.IsShown() and self._media.GetMime() == HC.APPLICATION_FLASH: frame_index = self._media_window.CurrentFrame() if frame_index != self._current_frame_index: self._current_frame_index = frame_index self._Draw() def GotoFrame( self, frame_index ): self._current_frame_index = frame_index self._Draw() class EmbedButton( wx.Window ): def __init__( self, parent, size ): wx.Window.__init__( self, parent, size = size ) self._Redraw() self.Bind( wx.EVT_PAINT, self.EventPaint ) self.Bind( wx.EVT_SIZE, self.EventResize ) def _Redraw( self ): ( x, y ) = self.GetClientSize() self._canvas_bmp = wx.EmptyBitmap( x, y, 24 ) dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp ) dc.SetBackground( wx.WHITE_BRUSH ) dc.Clear() # gcdc doesn't support clear dc = wx.GCDC( dc ) center_x = x / 2 center_y = y / 2 radius = min( center_x, center_y ) - 5 dc.SetPen( wx.TRANSPARENT_PEN ) dc.SetBrush( wx.Brush( wx.Colour( 215, 215, 215 ) ) ) dc.DrawCircle( center_x, center_y, radius ) dc.SetBrush( wx.WHITE_BRUSH ) m = ( 2 ** 0.5 ) / 2 # 45 degree angle half_radius = radius / 2 angle_half_radius = m * half_radius points = [] points.append( ( center_x - angle_half_radius, center_y - angle_half_radius ) ) points.append( ( center_x + half_radius, center_y ) ) points.append( ( center_x - angle_half_radius, center_y + angle_half_radius ) ) dc.DrawPolygon( points ) def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp ) def EventResize( self, event ): ( my_width, my_height ) = self.GetClientSize() ( current_bmp_width, current_bmp_height ) = self._canvas_bmp.GetSize() if my_width != current_bmp_width or my_height != current_bmp_height: if my_width > 0 and my_height > 0: self._Redraw() class EmbedWindowAudio( wx.Window ): def __init__( self, parent, hash, mime, size ): wx.Window.__init__( self, parent, size = size ) self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) ) self._hash = hash self._mime = mime vbox = wx.BoxSizer( wx.VERTICAL ) ( width, height ) = size media_height = height - 45 self._media_ctrl = wx.media.MediaCtrl( self, size = ( width, media_height ) ) self._media_ctrl.Hide() self._embed_button = EmbedButton( self, size = ( width, media_height ) ) self._embed_button.Bind( wx.EVT_LEFT_DOWN, self.EventEmbedButton ) launch_button = wx.Button( self, label = 'launch ' + HC.mime_string_lookup[ mime ] + ' externally', size = ( width, 45 ), pos = ( 0, media_height ) ) launch_button.Bind( wx.EVT_BUTTON, self.EventLaunchButton ) def EventEmbedButton( self, event ): self._embed_button.Hide() self._media_ctrl.ShowPlayerControls( wx.media.MEDIACTRLPLAYERCONTROLS_DEFAULT ) path = CC.GetFilePath( self._hash, self._mime ) self._media_ctrl.Load( path ) self._media_ctrl.Show() def EventLaunchButton( self, event ): path = CC.GetFilePath( self._hash, self._mime ) # os.system( 'start ' + path ) subprocess.call( 'start "" "' + path + '"', shell = True ) class Image( wx.Window ): def __init__( self, parent, media, image_cache, initial_size, initial_position ): wx.Window.__init__( self, parent, size = initial_size, pos = initial_position ) self.SetDoubleBuffered( True ) self._media = media self._image_container = None self._image_cache = image_cache self._animation_bar = None self._current_frame_index = 0 self._canvas_bmp = wx.EmptyBitmap( 0, 0, 24 ) self._timer_animated = wx.Timer( self, id = ID_TIMER_ANIMATED ) self._yet_to_draw_initial_frame = True self.Bind( wx.EVT_PAINT, self.EventPaint ) self.Bind( wx.EVT_SIZE, self.EventResize ) self.Bind( wx.EVT_TIMER, self.EventTimerAnimated, id = ID_TIMER_ANIMATED ) self.Bind( wx.EVT_MOUSE_EVENTS, self.EventPropagateMouse ) self.Bind( wx.EVT_KEY_DOWN, self.EventKeyDown ) self.EventResize( None ) def _Draw( self ): dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp ) if self._image_container.HasFrame( self._current_frame_index ): current_frame = self._image_container.GetFrame( self._current_frame_index ) ( my_width, my_height ) = self._canvas_bmp.GetSize() ( frame_width, frame_height ) = current_frame.GetSize() x_scale = my_width / float( frame_width ) y_scale = my_height / float( frame_height ) dc.SetUserScale( x_scale, y_scale ) hydrus_bmp = current_frame.CreateWxBmp() dc.DrawBitmap( hydrus_bmp, 0, 0 ) hydrus_bmp.Destroy() dc.SetUserScale( 1.0, 1.0 ) if self._image_container.IsAnimated(): self._timer_animated.Start( self._image_container.GetDuration( self._current_frame_index ), wx.TIMER_ONE_SHOT ) if self._animation_bar is not None: self._animation_bar.GotoFrame( self._current_frame_index ) else: dc.SetBackground( wx.Brush( wx.WHITE ) ) dc.Clear() self._timer_animated.Start( 50, wx.TIMER_ONE_SHOT ) def CurrentFrame( self ): return self._current_frame_index def EventKeyDown( self, event ): self.GetParent().GetParent().ProcessEvent( event ) def EventPaint( self, event ): wx.BufferedPaintDC( self, self._canvas_bmp ) def EventPropagateMouse( self, event ): screen_position = self.ClientToScreen( event.GetPosition() ) ( x, y ) = self.GetParent().ScreenToClient( screen_position ) event.SetX( x ) event.SetY( y ) event.ResumePropagation( 1 ) event.Skip() def EventResize( self, event ): ( my_width, my_height ) = self.GetClientSize() ( current_bmp_width, current_bmp_height ) = self._canvas_bmp.GetSize() if my_width != current_bmp_width or my_height != current_bmp_height: if my_width > 0 and my_height > 0: if self._image_container is None: self._image_container = self._image_cache.GetImage( self._media.GetHash(), ( my_width, my_height ) ) else: ( image_width, image_height ) = self._image_container.GetSize() we_just_zoomed_in = my_width > image_width if we_just_zoomed_in and self._image_container.IsScaled(): full_resolution = self._image_container.GetResolution() self._image_container = self._image_cache.GetImage( self._media.GetHash(), full_resolution ) self._canvas_bmp.Destroy() self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 ) self._Draw() def EventTimerAnimated( self, event ): if self.IsShown(): if self._yet_to_draw_initial_frame: if self._image_container.HasFrame( 0 ): self._yet_to_draw_initial_frame = False else: if self._image_container.HasFrame( self._current_frame_index + 1 ): self._current_frame_index += 1 elif self._image_container.IsFinishedRendering(): self._current_frame_index = 0 self._Draw() def GotoFrame( self, frame_index ): self._current_frame_index = frame_index self._Draw() self._timer_animated.Stop() def Play( self ): self._timer_animated.Start() def SetAnimationBar( self, animation_bar ): self._animation_bar = animation_bar class PDFButton( wx.Button ): def __init__( self, parent, hash, size ): wx.Button.__init__( self, parent, label = 'launch pdf', size = size ) self.SetCursor( wx.StockCursor( wx.CURSOR_ARROW ) ) self._hash = hash self.Bind( wx.EVT_BUTTON, self.EventButton ) def EventButton( self, event ): path = CC.GetFilePath( self._hash, HC.APPLICATION_PDF ) # os.system( 'start ' + path ) subprocess.call( 'start "" "' + path + '"', shell = True )