Version 381
This commit is contained in:
parent
70671ee703
commit
3d3c2533ba
|
@ -97,6 +97,8 @@ try:
|
|||
HydrusPaths.SetEnvTempDir( result.temp_dir )
|
||||
|
||||
|
||||
HydrusPaths.AddBaseDirToEnvPath()
|
||||
|
||||
from include import HydrusPy2To3
|
||||
|
||||
HydrusPy2To3.do_2to3_test()
|
||||
|
|
|
@ -97,6 +97,8 @@ try:
|
|||
HydrusPaths.SetEnvTempDir( result.temp_dir )
|
||||
|
||||
|
||||
HydrusPaths.AddBaseDirToEnvPath()
|
||||
|
||||
from include import HydrusPy2To3
|
||||
|
||||
HydrusPy2To3.do_2to3_test()
|
||||
|
|
|
@ -8,6 +8,52 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 381</h3></li>
|
||||
<ul>
|
||||
<li>mpv:</li>
|
||||
<li>mpv is now available and the default for all windows users</li>
|
||||
<li>I believed I have eliminated the final reported mpv crash</li>
|
||||
<li>mpv load and unload delays are greatly reduced. initial load still takes about half a second, but subsequent loads are now as quick as native renderers</li>
|
||||
<li>mpv seems to work well for gif and apng</li>
|
||||
<li>added a very simple global volume slider and audio mute checkbox to the media viewer top hover window. this was a quick patch--much better controls and shortcuts will come in future</li>
|
||||
<li>mpv windows now properly re-show the cursor on mouse movement</li>
|
||||
<li>unified mpv mouse press/release handling with native animation--click down now does pause/play and starts a drag event</li>
|
||||
<li>unfortunately, in some cases embedding mpv requires overriding local OS number rendering (e.g. 1,234 vs 1.234). hydrus number rendering is now coerced to the english style with commas until we can figure out a better solution--sorry!</li>
|
||||
<li>cleared up an issue where simple clicks on page tabs would trigger micro-page drags that were immediately cancelled. this situation was exacerbated when the page being left had an active mpv window. the flicker of page drag cursor is now gone, and some weird situations where static clicks during busy time could move a tab should be fixed</li>
|
||||
<li>eliminated the recent issue in the media viewer where transitioning from one media type to another through navigation, particularly mpv->other, would flicker a single frame of the last 'other' media shown(!)</li>
|
||||
<li>fixed a bug where repeated mpv views in the preview viewer could disable client file drag and drop</li>
|
||||
<li>the bug where thumbnails may not waterfall in unless the mouse is moving after some mpv videos are loaded for a page is relieved but not completely fixed</li>
|
||||
<li>if the preview window is collapsed and hidden, media will no longer ever load into it</li>
|
||||
<li>fixed an edge-case bug where the mpv window would not like being told to show nothing when it was already showing nothing</li>
|
||||
<li>wrapped mpv load errors in a basic graceful catch</li>
|
||||
<li>fixed an issue some users had with loading mpv's dll</li>
|
||||
<li>.</li>
|
||||
<li>file types:</li>
|
||||
<li>a new file metatype, 'animation', is added, for gif and apng. these are no longer considered 'image' for a variety of purposes</li>
|
||||
<li>the filetype selection panel, which is used in system:filetype and import folder UI, has had an overhaul--it now has tristate 'mime group' checkboxes to represent a half-filled group and expand/collapse buttons to hide the tall filetype lists. individual filetype lists will start hidden unless their default value is a partially filled group</li>
|
||||
<li>the media view options have a similar overhaul: they are now collapsed to general filetypes by default. you set view and zoom options for the generalised 'video' type under options->media, and if you want to set specific options for webm or anything else, you can add/delete those types to override the general default</li>
|
||||
<li>the new default options for a fresh client are just for these general types. if mpv is available, video, animations, and audio now start with mpv as the default viewer. video and animation zoom is now flexible (not fixed to 50%, 100%, 200%) and will fill the media canvas</li>
|
||||
<li>all media view options will be reset to this simple default on update! if you have specific zoom or display preferences, please reset them after the update--but you might like to play with mpv a bit first, as it renders at large and smooth zooms very well</li>
|
||||
<li>.</li>
|
||||
<li>the rest:</li>
|
||||
<li>the new thumbnail right-click file selection routine will now only focus and scroll to the first member of the selection if no other members of the new selection are already in view</li>
|
||||
<li>fixed some caching code and sped up the new select/remove menu count generation (which can lag for very large pages) by two to six times</li>
|
||||
<li>sped up file filter counting code by about ten percent</li>
|
||||
<li>fixed weird layout on: migrate database panel, duplicates page (left and right), edit shortcuts, edit import folder, and the filename tagging panel</li>
|
||||
<li>fixed an issue where the media viewer's hover windows might flicker into view for one frame when the mouse moved over the center of the media viewer for the first time</li>
|
||||
<li>fixed a media viewer shutdown issue that would sometimes lead to the first file in the list being opened in the shutting-down viewer for an instant or highlighted as the new thumb focus</li>
|
||||
<li>the file maintenance system that queues up missing/broken files' urls for redownload will no longer re-select the download page on every new url</li>
|
||||
<li>fixed an issue where a downloader's tag blacklist was not being applied on the child files of certain kinds of multiple-file post (such as with pixiv)</li>
|
||||
<li>deleting a very long tag should no longer create a very wide confirmation dialog in the manage tags dialog</li>
|
||||
<li>fixed some 'the panel grew a bit, but the parent window didn't grow quite enough and now it has scrollbars for two pixels of extra content' sizing issues</li>
|
||||
<li>fixed some dialog sizing calculations when the parent window was borderless fullscreen</li>
|
||||
<li>maybe fixed a rare event processing bug</li>
|
||||
<li>improved quality of some misc data comparison code across the program</li>
|
||||
<li>did some significant backend event/pubsub code cleanup, mostly related to getting mpv working a bit cleaner</li>
|
||||
<li>improved thumbnail rendering time</li>
|
||||
<li>improved smoothness of thumbnail fade animations (at least for when they are working right, ha ha!)</li>
|
||||
<li>misc fixes</li>
|
||||
</ul>
|
||||
<li><h3>version 380</h3></li>
|
||||
<ul>
|
||||
<li>basic mpv support is added. it comes with the windows build this week, and is a prototype meant for initial testing. the library is optional. users who run from source will want 'python-mpv' added via pip and libmpv available on their PATH, more details in running_from_source help</li>
|
||||
|
|
|
@ -955,6 +955,11 @@ class ThumbnailCache( object ):
|
|||
self._magic_mime_thumbnail_ease_score_lookup[ mime ] = 3
|
||||
|
||||
|
||||
for mime in HC.ANIMATIONS:
|
||||
|
||||
self._magic_mime_thumbnail_ease_score_lookup[ mime ] = 3
|
||||
|
||||
|
||||
|
||||
def _RecalcQueues( self ):
|
||||
|
||||
|
@ -1217,7 +1222,10 @@ class ThumbnailCache( object ):
|
|||
|
||||
page_keys_to_rendered_medias = collections.defaultdict( list )
|
||||
|
||||
while not HydrusData.TimeHasPassedPrecise( stop_time ):
|
||||
num_done = 0
|
||||
max_at_once = 16
|
||||
|
||||
while not HydrusData.TimeHasPassedPrecise( stop_time ) and num_done <= max_at_once:
|
||||
|
||||
with self._lock:
|
||||
|
||||
|
@ -1245,6 +1253,8 @@ class ThumbnailCache( object ):
|
|||
page_keys_to_rendered_medias[ page_key ].append( media )
|
||||
|
||||
|
||||
num_done += 1
|
||||
|
||||
|
||||
if len( page_keys_to_rendered_medias ) > 0:
|
||||
|
||||
|
|
|
@ -190,9 +190,15 @@ no_support = ( unsupported_media_actions, False, False )
|
|||
|
||||
media_viewer_capabilities = {}
|
||||
|
||||
media_viewer_capabilities[ HC.GENERAL_ANIMATION ] = animated_full_support
|
||||
media_viewer_capabilities[ HC.GENERAL_IMAGE ] = static_full_support
|
||||
media_viewer_capabilities[ HC.GENERAL_VIDEO ] = animated_full_support
|
||||
media_viewer_capabilities[ HC.GENERAL_AUDIO ] = audio_full_support
|
||||
media_viewer_capabilities[ HC.GENERAL_APPLICATION ] = no_support
|
||||
|
||||
for mime in HC.SEARCHABLE_MIMES:
|
||||
|
||||
if mime in HC.IMAGES_THAT_CAN_HAVE_ANIMATION:
|
||||
if mime in HC.ANIMATIONS:
|
||||
|
||||
media_viewer_capabilities[ mime ] = animated_full_support
|
||||
|
||||
|
|
|
@ -56,6 +56,42 @@ if not HG.twisted_is_broke:
|
|||
|
||||
from twisted.internet import threads, reactor, defer
|
||||
|
||||
class PubSubEvent( QC.QEvent ):
|
||||
|
||||
def __init__( self, pubsub ):
|
||||
|
||||
QC.QEvent.__init__( self, QC.QEvent.User )
|
||||
|
||||
self._pubsub = pubsub
|
||||
|
||||
|
||||
def Execute( self ):
|
||||
|
||||
if self._pubsub.WorkToDo():
|
||||
|
||||
self._pubsub.Process()
|
||||
|
||||
|
||||
|
||||
class PubSubEventFilter( QC.QObject ):
|
||||
|
||||
def __init__( self, parent = None ):
|
||||
|
||||
QC.QObject.__init__( self, parent )
|
||||
|
||||
|
||||
def eventFilter( self, watched, event ):
|
||||
|
||||
if event.type() == QC.QEvent.User and isinstance( event, PubSubEvent ):
|
||||
|
||||
event.Execute()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class App( QW.QApplication ):
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
|
@ -74,6 +110,10 @@ class App( QW.QApplication ):
|
|||
|
||||
self.call_after_catcher.installEventFilter( QP.CallAfterEventFilter( self.call_after_catcher ) )
|
||||
|
||||
self.pubsub_catcher = QC.QObject( self )
|
||||
|
||||
self.pubsub_catcher.installEventFilter( PubSubEventFilter( self.pubsub_catcher ) )
|
||||
|
||||
self.aboutToQuit.connect( self.EventEndSession )
|
||||
|
||||
|
||||
|
@ -281,7 +321,7 @@ class Controller( HydrusController.HydrusController ):
|
|||
raise HydrusExceptions.ShutdownException( 'Application is shutting down!' )
|
||||
|
||||
|
||||
time.sleep( 0.05 )
|
||||
time.sleep( 0.02 )
|
||||
|
||||
|
||||
if job_key.HasVariable( 'result' ):
|
||||
|
@ -1115,6 +1155,16 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
|
||||
|
||||
def do_gui_refs( gui ):
|
||||
|
||||
if self.gui is not None and QP.isValid( self.gui ):
|
||||
|
||||
self.gui.MaintainCanvasFrameReferences()
|
||||
|
||||
|
||||
|
||||
QP.CallAfter( do_gui_refs, self.gui )
|
||||
|
||||
|
||||
def MenubarMenuIsOpen( self ):
|
||||
|
||||
|
@ -1170,6 +1220,9 @@ class Controller( HydrusController.HydrusController ):
|
|||
|
||||
self.CallBlockingToQt( self.app, self._pubsub.Process )
|
||||
|
||||
# this needs to be blocking in some way or the pubsub daemon goes nuts
|
||||
#QW.QApplication.instance().postEvent( QW.QApplication.instance().pubsub_catcher, PubSubEvent( self._pubsub ) )
|
||||
|
||||
|
||||
def RefreshServices( self ):
|
||||
|
||||
|
|
|
@ -13428,6 +13428,28 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
if version == 380:
|
||||
|
||||
try:
|
||||
|
||||
new_options = self._GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS )
|
||||
|
||||
default_view_options = new_options.GetDefaultMediaViewOptions()
|
||||
|
||||
new_options.SetMediaViewOptions( default_view_options )
|
||||
|
||||
self._SetJSONDump( new_options )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
HydrusData.PrintException( e )
|
||||
|
||||
message = 'Trying to update the media view options failed! Please let hydrus dev know!'
|
||||
|
||||
self.pub_initial_message( message )
|
||||
|
||||
|
||||
|
||||
self._controller.pub( 'splash_set_title_text', 'updated db to v' + str( version + 1 ) )
|
||||
|
||||
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
|
||||
|
|
|
@ -631,7 +631,15 @@ class Credentials( HydrusData.HydrusYAMLBase ):
|
|||
self._access_key = access_key
|
||||
|
||||
|
||||
def __eq__( self, other ): return self.__hash__() == other.__hash__()
|
||||
def __eq__( self, other ):
|
||||
|
||||
if isinstance( other, Credentials ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ): return ( self._host, self._port, self._access_key ).__hash__()
|
||||
|
||||
|
|
|
@ -269,7 +269,12 @@ class GalleryIdentifier( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
def __eq__( self, other ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
if isinstance( other, GalleryIdentifier ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ):
|
||||
|
|
|
@ -390,6 +390,8 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
|
|||
|
||||
self._canvas_frames = [] # Keep references to canvas frames so they won't get garbage collected (canvas frames don't have a parent)
|
||||
|
||||
self._persistent_mpv_widgets = []
|
||||
|
||||
self._closed_pages = []
|
||||
|
||||
self._lock = threading.Lock()
|
||||
|
@ -473,6 +475,7 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
|
|||
self._ui_update_windows = set()
|
||||
|
||||
self._animation_update_timer = QC.QTimer( self )
|
||||
self._animation_update_timer.setTimerType( QC.Qt.PreciseTimer )
|
||||
self._animation_update_timer.timeout.connect( self.TIMEREventAnimationUpdate )
|
||||
|
||||
self._animation_update_windows = set()
|
||||
|
@ -4906,6 +4909,25 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
return self._notebook.GetSessionAPIInfoDict( is_selected = True )
|
||||
|
||||
|
||||
def GetMPVWidget( self, parent ):
|
||||
|
||||
if len( self._persistent_mpv_widgets ) == 0:
|
||||
|
||||
mpv_widget = ClientGUIMPV.mpvWidget( parent )
|
||||
|
||||
self._persistent_mpv_widgets.append( mpv_widget )
|
||||
|
||||
|
||||
mpv_widget = self._persistent_mpv_widgets.pop()
|
||||
|
||||
if mpv_widget.parent() is self:
|
||||
|
||||
mpv_widget.setParent( parent )
|
||||
|
||||
|
||||
return mpv_widget
|
||||
|
||||
|
||||
def GetPageAPIInfoDict( self, page_key, simple ):
|
||||
|
||||
page = self._notebook.GetPageFromPageKey( page_key )
|
||||
|
@ -4995,7 +5017,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
try:
|
||||
|
||||
self._ImportURL( url, destination_page_name = destination_page_name )
|
||||
self._ImportURL( url, destination_page_name = destination_page_name, show_destination_page = False )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
@ -5148,7 +5170,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
if tlw.isMinimized() and not self._notebook.HasMediaPageName( page_name ):
|
||||
|
||||
self._controller.CallLaterQtSafe(self, 10.0, self.PresentImportedFilesToPage, hashes, page_name)
|
||||
self._controller.CallLaterQtSafe( self, 10.0, self.PresentImportedFilesToPage, hashes, page_name )
|
||||
|
||||
return
|
||||
|
||||
|
@ -5414,6 +5436,13 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
|
||||
|
||||
def ReleaseMPVWidget( self, mpv_widget ):
|
||||
|
||||
mpv_widget.setParent( self )
|
||||
|
||||
self._persistent_mpv_widgets.append( mpv_widget )
|
||||
|
||||
|
||||
def REPEATINGBandwidth( self ):
|
||||
|
||||
if not QP.isValid( self ) or self.isMinimized():
|
||||
|
|
|
@ -240,7 +240,7 @@ def ShouldHaveAnimationBar( media, show_action ):
|
|||
return True
|
||||
|
||||
|
||||
is_animated_image = media.GetMime() in HC.IMAGES_THAT_CAN_HAVE_ANIMATION and media.HasDuration()
|
||||
is_animated_image = media.GetMime() in HC.ANIMATIONS and media.HasDuration()
|
||||
|
||||
is_video = media.GetMime() in HC.VIDEO
|
||||
|
||||
|
@ -1187,7 +1187,7 @@ class Canvas( QW.QWidget ):
|
|||
|
||||
if self._current_media is not None:
|
||||
|
||||
if self._current_media.GetMime() in HC.IMAGES and self._current_media.GetDuration() is None:
|
||||
if self._current_media.GetMime() in HC.IMAGES:
|
||||
|
||||
HG.client_controller.pub( 'clipboard', 'bmp', self._current_media )
|
||||
|
||||
|
@ -2357,6 +2357,11 @@ class Canvas( QW.QWidget ):
|
|||
|
||||
def SetMedia( self, media ):
|
||||
|
||||
if not self.isVisible():
|
||||
|
||||
return
|
||||
|
||||
|
||||
if media is not None:
|
||||
|
||||
media = media.GetDisplayMedia()
|
||||
|
@ -2586,7 +2591,7 @@ class CanvasPanel( Canvas ):
|
|||
|
||||
ClientGUIMenus.AppendMenu( copy_menu, copy_hash_menu, 'hash' )
|
||||
|
||||
if self._current_media.GetMime() in HC.IMAGES and self._current_media.GetDuration() is None:
|
||||
if self._current_media.GetMime() in HC.IMAGES:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, 'image (bitmap)', 'Copy this file to your clipboard as a bmp.', self._CopyBMPToClipboard )
|
||||
|
||||
|
@ -4211,6 +4216,13 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithHovers ):
|
|||
|
||||
def ProcessContentUpdates( self, service_keys_to_content_updates ):
|
||||
|
||||
if self._current_media is None:
|
||||
|
||||
# probably a file view stats update as we close down--ignore it
|
||||
|
||||
return
|
||||
|
||||
|
||||
if self.HasMedia( self._current_media ):
|
||||
|
||||
next_media = self._GetNext( self._current_media )
|
||||
|
@ -5011,8 +5023,10 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
|
|||
|
||||
ClientGUIMenus.AppendMenu( copy_menu, copy_hash_menu, 'hash' )
|
||||
|
||||
if self._current_media.GetMime() in HC.IMAGES and self._current_media.GetDuration() is None:
|
||||
if self._current_media.GetMime() in HC.IMAGES:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, 'image (bitmap)', 'Copy this file to your clipboard as a BMP image.', self._CopyBMPToClipboard )
|
||||
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, 'path', 'Copy this file\'s path to your clipboard.', self._CopyPathToClipboard )
|
||||
|
||||
|
@ -5126,8 +5140,11 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
# If I do not set this, macOS goes 100% CPU endless repaint events!
|
||||
# My guess is it due to the borked layout
|
||||
# it means 'I guarantee to cover my whole viewport with pixels, no need for automatic background clear'
|
||||
self.setAttribute( QC.Qt.WA_OpaquePaintEvent, True )
|
||||
|
||||
self.setSizePolicy( QW.QSizePolicy.Fixed, QW.QSizePolicy.Fixed )
|
||||
|
||||
self._media = None
|
||||
self._show_action = CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE
|
||||
self._start_paused = False
|
||||
|
@ -5161,6 +5178,7 @@ class MediaContainer( QW.QWidget ):
|
|||
if isinstance( media_window, ( Animation, StaticImage ) ):
|
||||
|
||||
media_window.SetNoneMedia()
|
||||
|
||||
media_window.hide()
|
||||
|
||||
elif isinstance( media_window, ClientGUIMPV.mpvWidget ):
|
||||
|
@ -5169,11 +5187,9 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
media_window.SetNoneMedia()
|
||||
|
||||
time.sleep( 0.1 ) # anti crash wew
|
||||
media_window.hide()
|
||||
|
||||
media_window.deleteLater()
|
||||
|
||||
time.sleep( 0.1 ) # anti crash wew
|
||||
HG.client_controller.gui.ReleaseMPVWidget( media_window )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -5219,8 +5235,6 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
else:
|
||||
|
||||
self._static_image_window.show()
|
||||
|
||||
self._media_window = self._static_image_window
|
||||
|
||||
|
||||
|
@ -5234,8 +5248,6 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
else:
|
||||
|
||||
self._animation_window.show()
|
||||
|
||||
self._media_window = self._animation_window
|
||||
|
||||
|
||||
|
@ -5252,23 +5264,21 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
if self._mpv_window is None:
|
||||
|
||||
self._mpv_window = ClientGUIMPV.mpvWidget( self )
|
||||
self._mpv_window = HG.client_controller.gui.GetMPVWidget( self )
|
||||
|
||||
|
||||
self._mpv_window.show()
|
||||
|
||||
self._media_window = self._mpv_window
|
||||
|
||||
|
||||
self._mpv_window.SetMedia( self._media, start_paused = self._start_paused )
|
||||
self._media_window.SetMedia( self._media, start_paused = self._start_paused )
|
||||
|
||||
|
||||
if ShouldHaveAnimationBar( self._media, self._show_action ):
|
||||
|
||||
self._animation_bar.show()
|
||||
|
||||
self._animation_bar.SetMediaAndWindow( self._media, self._media_window )
|
||||
|
||||
self._animation_bar.show()
|
||||
|
||||
else:
|
||||
|
||||
self._HideAnimationBar()
|
||||
|
@ -5278,6 +5288,11 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
self._DestroyOrHideThisMediaWindow( old_media_window )
|
||||
|
||||
# this forces a flush of the last valid background bmp, so we don't get a flicker of a file from five files ago when we last saw a static image
|
||||
self.repaint()
|
||||
|
||||
|
||||
self._media_window.show()
|
||||
|
||||
|
||||
def _SizeAndPositionChildren( self ):
|
||||
|
@ -5288,8 +5303,8 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
if self._media_window is None:
|
||||
|
||||
self._embed_button.setFixedSize( QP.TupleToQSize( (my_width,my_height) ) )
|
||||
self._embed_button.move( QP.TupleToQPoint( (0,0) ) )
|
||||
self._embed_button.setFixedSize( QP.TupleToQSize( ( my_width, my_height ) ) )
|
||||
self._embed_button.move( QP.TupleToQPoint( ( 0, 0 ) ) )
|
||||
|
||||
else:
|
||||
|
||||
|
@ -5309,6 +5324,7 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
self._media_window.setFixedSize( QP.TupleToQSize( ( media_width, media_height ) ) )
|
||||
self._media_window.move( QP.TupleToQPoint( ( 0, 0 ) ) )
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -5404,6 +5420,26 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
|
||||
|
||||
def paintEvent( self, event ):
|
||||
|
||||
if self._media_window is not None and self._media_window.isVisible():
|
||||
|
||||
return
|
||||
|
||||
|
||||
# this only happens when we are transitioning from one media to another. in the brief period when one media type is going to another, we'll get flicker of the last valid bmp
|
||||
# mpv embed fun aggravates this
|
||||
# so instead we do an explicit repaint after the hide and before the new show, to clear our window
|
||||
|
||||
painter = QG.QPainter( self )
|
||||
|
||||
background_colour = HG.client_controller.new_options.GetColour( CC.COLOUR_MEDIA_BACKGROUND )
|
||||
|
||||
painter.setBrush( QG.QBrush( background_colour ) )
|
||||
|
||||
painter.drawRect( painter.viewport() )
|
||||
|
||||
|
||||
def Pause( self ):
|
||||
|
||||
if self._media is not None:
|
||||
|
@ -5471,8 +5507,6 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
self._media = media
|
||||
|
||||
self.hide()
|
||||
|
||||
self._show_action = show_action
|
||||
self._start_paused = start_paused
|
||||
self._start_with_embed = start_with_embed
|
||||
|
@ -5707,6 +5741,8 @@ class StaticImage( QW.QWidget ):
|
|||
|
||||
QW.QWidget.__init__( self, parent )
|
||||
|
||||
self.setAttribute( QC.Qt.WA_OpaquePaintEvent, True )
|
||||
|
||||
self.setMouseTracking( True )
|
||||
|
||||
self._media = None
|
||||
|
@ -5726,6 +5762,8 @@ class StaticImage( QW.QWidget ):
|
|||
|
||||
self._is_rendered = False
|
||||
|
||||
self._first_background_drawn = False
|
||||
|
||||
|
||||
def _DrawBackground( self, painter ):
|
||||
|
||||
|
@ -5815,7 +5853,7 @@ class StaticImage( QW.QWidget ):
|
|||
|
||||
self._ClearCanvasBitmap()
|
||||
|
||||
self._first_background_drawn = False
|
||||
self.update()
|
||||
|
||||
|
||||
def TIMERAnimationUpdate( self ):
|
||||
|
|
|
@ -7,6 +7,7 @@ from . import ClientGUIDialogs
|
|||
from . import ClientGUIFunctions
|
||||
from . import ClientGUIListBoxes
|
||||
from . import ClientGUIMenus
|
||||
from . import ClientGUIMPV
|
||||
from . import ClientGUITopLevelWindows
|
||||
from . import ClientGUIScrolledPanelsEdit
|
||||
from . import ClientGUIScrolledPanelsManagement
|
||||
|
@ -47,7 +48,7 @@ class FullscreenHoverFrame( QW.QFrame ):
|
|||
|
||||
self._hide_until = None
|
||||
|
||||
self._SizeAndPosition()
|
||||
self._position_initialised = False
|
||||
|
||||
HG.client_controller.sub( self, 'SetDisplayMedia', 'canvas_new_display_media' )
|
||||
|
||||
|
@ -72,6 +73,8 @@ class FullscreenHoverFrame( QW.QFrame ):
|
|||
|
||||
self.move( QP.TupleToQPoint( my_ideal_position ) )
|
||||
|
||||
self._position_initialised = True
|
||||
|
||||
|
||||
|
||||
def keyPressEvent( self, event ):
|
||||
|
@ -91,6 +94,11 @@ class FullscreenHoverFrame( QW.QFrame ):
|
|||
|
||||
def TIMERUIUpdate( self ):
|
||||
|
||||
if not self._position_initialised:
|
||||
|
||||
self._SizeAndPosition()
|
||||
|
||||
|
||||
current_focus_tlw = QW.QApplication.activeWindow()
|
||||
|
||||
focus_is_on_descendant = ClientGUIFunctions.IsQtAncestor( current_focus_tlw, self._my_canvas.window(), through_tlws = True )
|
||||
|
@ -241,7 +249,6 @@ class FullscreenHoverFrame( QW.QFrame ):
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
class FullscreenHoverFrameRightDuplicates( FullscreenHoverFrame ):
|
||||
|
||||
|
@ -554,6 +561,8 @@ class FullscreenHoverFrameTop( FullscreenHoverFrame ):
|
|||
HG.client_controller.sub( self, 'ProcessContentUpdates', 'content_updates_gui' )
|
||||
HG.client_controller.sub( self, 'SetCurrentZoom', 'canvas_new_zoom' )
|
||||
HG.client_controller.sub( self, 'SetIndexString', 'canvas_new_index_string' )
|
||||
HG.client_controller.sub( self, 'UpdateGlobalAudioMute', 'new_global_audio_mute' )
|
||||
HG.client_controller.sub( self, 'UpdateGlobalAudioVolume', 'new_global_audio_volume' )
|
||||
|
||||
|
||||
def _Archive( self ):
|
||||
|
@ -570,6 +579,13 @@ class FullscreenHoverFrameTop( FullscreenHoverFrame ):
|
|||
HG.client_controller.pub( 'canvas_application_command', command, self._canvas_key )
|
||||
|
||||
|
||||
def _FlipGlobalMute( self ):
|
||||
|
||||
HG.client_controller.new_options.FlipBoolean( 'global_audio_mute' )
|
||||
|
||||
HG.client_controller.pub( 'new_global_audio_mute' )
|
||||
|
||||
|
||||
def _GetIdealSizeAndPosition( self ):
|
||||
|
||||
# clip this and friends to availableScreenGeometry for size and position, not rely 100% on parent
|
||||
|
@ -643,6 +659,29 @@ class FullscreenHoverFrameTop( FullscreenHoverFrame ):
|
|||
zoom_switch = ClientGUICommon.BetterBitmapButton( self, CC.GlobalPixmaps.zoom_switch, HG.client_controller.pub, 'canvas_application_command', ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'switch_between_100_percent_and_canvas_zoom' ), self._canvas_key )
|
||||
zoom_switch.SetToolTipWithShortcuts( 'zoom switch', 'switch_between_100_percent_and_canvas_zoom' )
|
||||
|
||||
self._global_audio_volume_slider = QW.QSlider( self )
|
||||
|
||||
self._global_audio_volume_slider.setOrientation( QC.Qt.Horizontal )
|
||||
self._global_audio_volume_slider.setTickInterval( 1 )
|
||||
self._global_audio_volume_slider.setTickPosition( QW.QSlider.TicksBothSides )
|
||||
self._global_audio_volume_slider.setRange( 0, 100 )
|
||||
|
||||
self._global_audio_volume_slider.setValue( HG.client_controller.new_options.GetInteger( 'global_audio_volume' ) )
|
||||
|
||||
self._global_audio_volume_slider.valueChanged.connect( self._VolumeSliderMoved )
|
||||
|
||||
self._global_audio_mute_checkbox = QW.QCheckBox( 'mute', self )
|
||||
|
||||
self._global_audio_mute_checkbox.setChecked( HG.client_controller.new_options.GetBoolean( 'global_audio_mute' ) )
|
||||
|
||||
self._global_audio_mute_checkbox.clicked.connect( self._FlipGlobalMute )
|
||||
|
||||
if not ClientGUIMPV.MPV_IS_AVAILABLE:
|
||||
|
||||
self._global_audio_mute_checkbox.hide()
|
||||
self._global_audio_volume_slider.hide()
|
||||
|
||||
|
||||
shortcuts = ClientGUICommon.BetterBitmapButton( self, CC.GlobalPixmaps.keyboard, self._ShowShortcutMenu )
|
||||
shortcuts.setToolTip( 'shortcuts' )
|
||||
|
||||
|
@ -670,6 +709,8 @@ class FullscreenHoverFrameTop( FullscreenHoverFrame ):
|
|||
QP.AddToLayout( self._top_hbox, zoom_in, CC.FLAGS_VCENTER )
|
||||
QP.AddToLayout( self._top_hbox, zoom_out, CC.FLAGS_VCENTER )
|
||||
QP.AddToLayout( self._top_hbox, zoom_switch, CC.FLAGS_VCENTER )
|
||||
QP.AddToLayout( self._top_hbox, self._global_audio_volume_slider, CC.FLAGS_VCENTER )
|
||||
QP.AddToLayout( self._top_hbox, self._global_audio_mute_checkbox, CC.FLAGS_VCENTER )
|
||||
QP.AddToLayout( self._top_hbox, shortcuts, CC.FLAGS_VCENTER )
|
||||
QP.AddToLayout( self._top_hbox, fullscreen_switch, CC.FLAGS_VCENTER )
|
||||
QP.AddToLayout( self._top_hbox, open_externally, CC.FLAGS_VCENTER )
|
||||
|
@ -801,6 +842,13 @@ class FullscreenHoverFrameTop( FullscreenHoverFrame ):
|
|||
HG.client_controller.PopupMenu( self, menu )
|
||||
|
||||
|
||||
def _VolumeSliderMoved( self ):
|
||||
|
||||
HG.client_controller.new_options.SetInteger( 'global_audio_volume', self._global_audio_volume_slider.value() )
|
||||
|
||||
HG.client_controller.pub( 'new_global_audio_volume' )
|
||||
|
||||
|
||||
def EventDragButton( self ):
|
||||
|
||||
if self._current_media is None:
|
||||
|
@ -899,6 +947,16 @@ class FullscreenHoverFrameTop( FullscreenHoverFrame ):
|
|||
|
||||
|
||||
|
||||
def UpdateGlobalAudioMute( self ):
|
||||
|
||||
self._global_audio_mute_checkbox.setChecked( HG.client_controller.new_options.GetBoolean( 'global_audio_mute' ) )
|
||||
|
||||
|
||||
def UpdateGlobalAudioVolume( self ):
|
||||
|
||||
self._global_audio_volume_slider.setValue( HG.client_controller.new_options.GetInteger( 'global_audio_volume' ) )
|
||||
|
||||
|
||||
class FullscreenHoverFrameTopArchiveDeleteFilter( FullscreenHoverFrameTop ):
|
||||
|
||||
def _Archive( self ):
|
||||
|
|
|
@ -617,6 +617,8 @@ class FilenameTaggingOptionsPanel( QW.QWidget ):
|
|||
self._checkboxes_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
|
||||
|
||||
self._checkboxes_panel.Add( QW.QWidget( self._checkboxes_panel ), CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
hbox = QP.HBoxLayout()
|
||||
|
||||
QP.AddToLayout( hbox, self._tags_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
@ -1183,7 +1185,7 @@ class EditImportFolderPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
|
||||
QP.AddToLayout( vbox, self._folder_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._file_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._tag_box, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._tag_box, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.widget().setLayout( vbox )
|
||||
|
||||
|
|
|
@ -952,9 +952,18 @@ class BetterListCtrlPanel( QW.QWidget ):
|
|||
self.AddMenuButton( 'add defaults', import_menu_items )
|
||||
|
||||
|
||||
def AddDeleteButton( self ):
|
||||
def AddDeleteButton( self, enabled_check_func = None ):
|
||||
|
||||
self.AddButton( 'delete', self._listctrl.ProcessDeleteAction, enabled_only_on_selection = True )
|
||||
if enabled_check_func is None:
|
||||
|
||||
enabled_only_on_selection = True
|
||||
|
||||
else:
|
||||
|
||||
enabled_only_on_selection = False
|
||||
|
||||
|
||||
self.AddButton( 'delete', self._listctrl.ProcessDeleteAction, enabled_check_func = enabled_check_func, enabled_only_on_selection = enabled_only_on_selection )
|
||||
|
||||
|
||||
def AddImportExportButtons( self, permitted_object_types, import_add_callable ):
|
||||
|
|
|
@ -40,7 +40,8 @@ class mpvWidget( QW.QWidget ):
|
|||
|
||||
# This is necessary since PyQT stomps over the locale settings needed by libmpv.
|
||||
# This needs to happen after importing PyQT before creating the first mpv.MPV instance.
|
||||
locale.setlocale( locale.LC_NUMERIC, 'C' )
|
||||
# hydev is commenting this out for now since Windows doesn't seem to need it and it is set in the import for non-Windows
|
||||
#locale.setlocale( locale.LC_NUMERIC, 'C' )
|
||||
|
||||
self.setAttribute( QC.Qt.WA_DontCreateNativeAncestors )
|
||||
self.setAttribute( QC.Qt.WA_NativeWindow )
|
||||
|
@ -63,9 +64,10 @@ class mpvWidget( QW.QWidget ):
|
|||
|
||||
self._player.loop = True
|
||||
|
||||
# this makes black screen for audio (rather than transparent)
|
||||
self._player.force_window = True
|
||||
|
||||
#self.setMouseTracking( True )#Needed to get mouse move events
|
||||
self.setMouseTracking( True )#Needed to get mouse move events
|
||||
#self.setFocusPolicy(QC.Qt.StrongFocus)#Needed to get key events
|
||||
self._player.input_cursor = False#Disable mpv mouse move/click event capture
|
||||
self._player.input_vo_keyboard = False#Disable mpv key event capture, might also need to set input_x11_keyboard
|
||||
|
@ -76,6 +78,9 @@ class mpvWidget( QW.QWidget ):
|
|||
|
||||
self.destroyed.connect( self._player.terminate )
|
||||
|
||||
HG.client_controller.sub( self, 'UpdateGlobalAudioMute', 'new_global_audio_mute' )
|
||||
HG.client_controller.sub( self, 'UpdateGlobalAudioMute', 'new_global_audio_volume' )
|
||||
|
||||
|
||||
def GetAnimationBarStatus( self ):
|
||||
|
||||
|
@ -181,34 +186,40 @@ class mpvWidget( QW.QWidget ):
|
|||
event.ignore()
|
||||
|
||||
|
||||
#def mouseMoveEvent( self, event ):
|
||||
def mouseMoveEvent( self, event ):
|
||||
|
||||
# same deal here as with mousereleaseevent--osc is non-interactable with commands, so let's not use it for now
|
||||
#self._player.command( 'mouse', event.x(), event.y() )
|
||||
|
||||
#event.ignore()
|
||||
event.ignore()
|
||||
|
||||
|
||||
def mousePressEvent( self, event ):
|
||||
|
||||
if not ( event.modifiers() & ( QC.Qt.ShiftModifier | QC.Qt.ControlModifier | QC.Qt.AltModifier) ):
|
||||
|
||||
if event.button() == QC.Qt.LeftButton:
|
||||
|
||||
self.PausePlay()
|
||||
|
||||
self.parentWidget().BeginDrag()
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
event.ignore()
|
||||
|
||||
|
||||
def mouseReleaseEvent( self, event ):
|
||||
|
||||
if event.button() == QC.Qt.LeftButton:
|
||||
|
||||
self.PausePlay()
|
||||
|
||||
else:
|
||||
|
||||
event.ignore()
|
||||
|
||||
return
|
||||
|
||||
|
||||
# left index = 0
|
||||
# right index = 2
|
||||
# the issue with using this guy is it sends a mouse press or mouse down event, and the OSC only responds to mouse up
|
||||
|
||||
#self._player.command( 'mouse', event.x(), event.y(), index, 'single' )
|
||||
|
||||
event.accept()
|
||||
event.ignore()
|
||||
|
||||
|
||||
def Pause( self ):
|
||||
|
@ -234,7 +245,10 @@ class mpvWidget( QW.QWidget ):
|
|||
|
||||
self._player.pause = True
|
||||
|
||||
self._player.command( 'playlist-remove', 'current' )
|
||||
if len( self._player.playlist ) > 0:
|
||||
|
||||
self._player.command( 'playlist-remove', 'current' )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
|
@ -249,16 +263,20 @@ class mpvWidget( QW.QWidget ):
|
|||
|
||||
self._player.visibility = 'always'
|
||||
|
||||
volume = 70
|
||||
mute = False
|
||||
|
||||
self._player.pause = True
|
||||
|
||||
self._player.loadfile( path )
|
||||
try:
|
||||
|
||||
self._player.loadfile( path )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
HydrusData.ShowException( e )
|
||||
|
||||
|
||||
self._player.volume = HG.client_controller.new_options.GetInteger( 'global_audio_volume' )
|
||||
self._player.mute = HG.client_controller.new_options.GetBoolean( 'global_audio_mute' )
|
||||
self._player.pause = start_paused
|
||||
self._player.volume = volume
|
||||
self._player.mute = mute
|
||||
|
||||
|
||||
|
||||
|
@ -267,3 +285,12 @@ class mpvWidget( QW.QWidget ):
|
|||
self.SetMedia( None )
|
||||
|
||||
|
||||
def UpdateGlobalAudioMute( self ):
|
||||
|
||||
self._player.mute = HG.client_controller.new_options.GetBoolean( 'global_audio_mute' )
|
||||
|
||||
|
||||
def UpdateGlobalAudioVolume( self ):
|
||||
|
||||
self._player.volume = HG.client_controller.new_options.GetInteger( 'global_audio_volume' )
|
||||
|
||||
|
|
|
@ -966,6 +966,7 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
|
|||
|
||||
QP.AddToLayout( vbox, hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._searching_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, QW.QWidget( self._main_left_panel ), CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self._main_left_panel.setLayout( vbox )
|
||||
|
||||
|
@ -998,6 +999,7 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
|
|||
QP.AddToLayout( vbox, self._edit_merge_options, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._filtering_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, random_filtering_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, QW.QWidget( self._main_right_panel ), CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self._main_right_panel.setLayout( vbox )
|
||||
|
||||
|
|
|
@ -633,7 +633,7 @@ class MediaPanel( ClientMedia.ListeningMediaList, QW.QScrollArea ):
|
|||
|
||||
media = self._focused_media.GetDisplayMedia()
|
||||
|
||||
if media.GetMime() in HC.IMAGES and media.GetDuration() is None:
|
||||
if media.GetMime() in HC.IMAGES:
|
||||
|
||||
HG.client_controller.pub( 'clipboard', 'bmp', media )
|
||||
|
||||
|
@ -1181,6 +1181,10 @@ class MediaPanel( ClientMedia.ListeningMediaList, QW.QScrollArea ):
|
|||
|
||||
return 'image'
|
||||
|
||||
elif len( classes.difference( HC.ANIMATIONS ) ) == 0:
|
||||
|
||||
return 'animation'
|
||||
|
||||
elif len( classes.difference( HC.VIDEO ) ) == 0:
|
||||
|
||||
return 'video'
|
||||
|
@ -1438,6 +1442,11 @@ class MediaPanel( ClientMedia.ListeningMediaList, QW.QScrollArea ):
|
|||
|
||||
|
||||
|
||||
def _MediaIsVisible( self, media ):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _ModifyUploaders( self, file_service_key ):
|
||||
|
||||
QW.QMessageBox.information( self, 'Information', 'this does not work yet!' )
|
||||
|
@ -1777,18 +1786,25 @@ class MediaPanel( ClientMedia.ListeningMediaList, QW.QScrollArea ):
|
|||
|
||||
else:
|
||||
|
||||
for m in self._sorted_media:
|
||||
# let's not focus if one of the selectees is already visible
|
||||
|
||||
media_visible = True in ( self._MediaIsVisible( media ) for media in self._selected_media )
|
||||
|
||||
if not media_visible:
|
||||
|
||||
if m in self._selected_media:
|
||||
for m in self._sorted_media:
|
||||
|
||||
ctrl = False
|
||||
shift = False
|
||||
|
||||
self._HitMedia( m, ctrl, shift )
|
||||
|
||||
self._ScrollToMedia( m )
|
||||
|
||||
break
|
||||
if m in self._selected_media:
|
||||
|
||||
ctrl = False
|
||||
shift = False
|
||||
|
||||
self._HitMedia( m, ctrl, shift )
|
||||
|
||||
self._ScrollToMedia( m )
|
||||
|
||||
break
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -3006,6 +3022,52 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
return y_start
|
||||
|
||||
|
||||
def _MediaIsInCleanPage( self, thumbnail ):
|
||||
|
||||
try:
|
||||
|
||||
index = self._sorted_media.index( thumbnail )
|
||||
|
||||
except HydrusExceptions.DataMissing:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
if self._GetPageIndexFromThumbnailIndex( index ) in self._clean_canvas_pages:
|
||||
|
||||
return True
|
||||
|
||||
else:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def _MediaIsVisible( self, media ):
|
||||
|
||||
if media is not None:
|
||||
|
||||
( x, y ) = self._GetMediaCoordinates( media )
|
||||
|
||||
visible_rect = QP.ScrollAreaVisibleRect( self )
|
||||
|
||||
visible_rect_y = visible_rect.y()
|
||||
|
||||
visible_rect_height = visible_rect.height()
|
||||
|
||||
( thumbnail_span_width, thumbnail_span_height ) = self._GetThumbnailSpanDimensions()
|
||||
|
||||
bottom_edge_below_top_of_view = visible_rect_y < y + thumbnail_span_height
|
||||
top_edge_above_bottom_of_view = y < visible_rect_y + visible_rect_height
|
||||
|
||||
is_visible = bottom_edge_below_top_of_view and top_edge_above_bottom_of_view
|
||||
|
||||
return is_visible
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _MoveFocusedThumbnail( self, rows, columns, shift ):
|
||||
|
||||
if self._focused_media is not None:
|
||||
|
@ -3106,7 +3168,7 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
def _RedrawMedia( self, thumbnails ):
|
||||
|
||||
visible_thumbnails = [ thumbnail for thumbnail in thumbnails if self._ThumbnailIsVisible( thumbnail ) ]
|
||||
visible_thumbnails = [ thumbnail for thumbnail in thumbnails if self._MediaIsInCleanPage( thumbnail ) ]
|
||||
|
||||
thumbnail_cache = HG.client_controller.GetCache( 'thumbnail' )
|
||||
|
||||
|
@ -3263,27 +3325,6 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
|
||||
|
||||
def _ThumbnailIsVisible( self, thumbnail ):
|
||||
|
||||
try:
|
||||
|
||||
index = self._sorted_media.index( thumbnail )
|
||||
|
||||
except HydrusExceptions.DataMissing:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
if self._GetPageIndexFromThumbnailIndex( index ) in self._clean_canvas_pages:
|
||||
|
||||
return True
|
||||
|
||||
else:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def _UpdateBackgroundColour( self ):
|
||||
|
||||
MediaPanel._UpdateBackgroundColour( self )
|
||||
|
@ -4371,7 +4412,7 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
if focused_is_local:
|
||||
|
||||
if self._focused_media.GetMime() in HC.IMAGES and self._focused_media.GetDuration() is None:
|
||||
if self._focused_media.GetMime() in HC.IMAGES:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, 'image (bitmap)', 'Copy the selected file\'s image data to the clipboard (as a bmp).', self._CopyBMPToClipboard )
|
||||
|
||||
|
@ -4929,6 +4970,11 @@ class Thumbnail( Selectable ):
|
|||
self._dump_status = CC.DUMPER_NOT_DUMPED
|
||||
self._file_service_key = file_service_key
|
||||
|
||||
self._last_tags = None
|
||||
|
||||
self._last_upper_summary = None
|
||||
self._last_lower_summary = None
|
||||
|
||||
|
||||
def _ScaleUpThumbnailDimensions( self, thumbnail_dimensions, scale_up_dimensions ):
|
||||
|
||||
|
@ -5044,12 +5090,24 @@ class Thumbnail( Selectable ):
|
|||
if len( tags ) > 0:
|
||||
|
||||
upper_tag_summary_generator = new_options.GetTagSummaryGenerator( 'thumbnail_top' )
|
||||
|
||||
upper_summary = upper_tag_summary_generator.GenerateSummary( tags )
|
||||
|
||||
lower_tag_summary_generator = new_options.GetTagSummaryGenerator( 'thumbnail_bottom_right' )
|
||||
|
||||
lower_summary = lower_tag_summary_generator.GenerateSummary( tags )
|
||||
if self._last_tags is not None and self._last_tags == tags:
|
||||
|
||||
upper_summary = self._last_upper_summary
|
||||
lower_summary = self._last_lower_summary
|
||||
|
||||
else:
|
||||
|
||||
upper_summary = upper_tag_summary_generator.GenerateSummary( tags )
|
||||
|
||||
lower_summary = lower_tag_summary_generator.GenerateSummary( tags )
|
||||
|
||||
self._last_tags = set( tags )
|
||||
|
||||
self._last_upper_summary = upper_summary
|
||||
self._last_lower_summary = lower_summary
|
||||
|
||||
|
||||
if len( upper_summary ) > 0 or len( lower_summary ) > 0:
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
from . import ClientConstants as CC
|
||||
from . import ClientCaches
|
||||
from . import ClientDefaults
|
||||
from . import ClientGUICommon
|
||||
from . import ClientGUIDialogs
|
||||
from . import ClientGUIFunctions
|
||||
from . import ClientImporting
|
||||
from . import ClientTags
|
||||
import collections
|
||||
|
@ -26,6 +28,9 @@ class OptionsPanel( QW.QWidget ):
|
|||
|
||||
class OptionsPanelMimes( OptionsPanel ):
|
||||
|
||||
BUTTON_CURRENTLY_HIDDEN = '\u25B6'
|
||||
BUTTON_CURRENTLY_SHOWING = '\u25BC'
|
||||
|
||||
def __init__( self, parent, selectable_mimes ):
|
||||
|
||||
OptionsPanel.__init__( self, parent )
|
||||
|
@ -33,56 +38,54 @@ class OptionsPanelMimes( OptionsPanel ):
|
|||
self._selectable_mimes = set( selectable_mimes )
|
||||
|
||||
self._mimes_to_checkboxes = {}
|
||||
self._mime_groups_to_checkboxes = {}
|
||||
self._mime_groups_to_values = {}
|
||||
self._general_mime_types_to_checkboxes = {}
|
||||
self._general_mime_types_to_buttons = {}
|
||||
|
||||
mime_groups = []
|
||||
general_mime_types = []
|
||||
|
||||
mime_groups.append( ( HC.APPLICATIONS, HC.GENERAL_APPLICATION ) )
|
||||
mime_groups.append( ( HC.AUDIO, HC.GENERAL_AUDIO ) )
|
||||
mime_groups.append( ( HC.IMAGES, HC.GENERAL_IMAGE ) )
|
||||
mime_groups.append( ( HC.VIDEO, HC.GENERAL_VIDEO ) )
|
||||
general_mime_types.append( HC.GENERAL_IMAGE )
|
||||
general_mime_types.append( HC.GENERAL_ANIMATION )
|
||||
general_mime_types.append( HC.GENERAL_VIDEO )
|
||||
general_mime_types.append( HC.GENERAL_AUDIO )
|
||||
general_mime_types.append( HC.GENERAL_APPLICATION )
|
||||
|
||||
mime_groups_to_mimes = collections.defaultdict( list )
|
||||
gridbox = QP.GridLayout( cols = 3 )
|
||||
|
||||
for mime in self._selectable_mimes:
|
||||
gridbox.setColumnStretch( 2, 1 )
|
||||
|
||||
for general_mime_type in general_mime_types:
|
||||
|
||||
for ( mime_group, mime_group_type ) in mime_groups:
|
||||
mimes_in_type = self._GetMimesForGeneralMimeType( general_mime_type )
|
||||
|
||||
if len( mimes_in_type ) == 0:
|
||||
|
||||
if mime in mime_group:
|
||||
|
||||
mime_groups_to_mimes[ mime_group ].append( mime )
|
||||
|
||||
break
|
||||
|
||||
continue
|
||||
|
||||
|
||||
|
||||
gridbox = QP.GridLayout( cols = 2 )
|
||||
|
||||
gridbox.setColumnStretch( 1, 1 )
|
||||
|
||||
for ( mime_group, mime_group_type ) in mime_groups:
|
||||
general_mime_checkbox = QW.QCheckBox( HC.mime_string_lookup[ general_mime_type ], self )
|
||||
general_mime_checkbox.clicked.connect( self.EventMimeGroupCheckbox )
|
||||
|
||||
mimes = mime_groups_to_mimes[ mime_group ]
|
||||
self._general_mime_types_to_checkboxes[ general_mime_type ] = general_mime_checkbox
|
||||
|
||||
mg_checkbox = QW.QCheckBox( HC.mime_string_lookup[ mime_group_type ], self )
|
||||
mg_checkbox.clicked.connect( self.EventMimeGroupCheckbox )
|
||||
QP.AddToLayout( gridbox, general_mime_checkbox, CC.FLAGS_VCENTER )
|
||||
|
||||
self._mime_groups_to_checkboxes[ mime_group ] = mg_checkbox
|
||||
self._mime_groups_to_values[ mime_group ] = mg_checkbox.isChecked()
|
||||
show_hide_button = ClientGUICommon.BetterButton( self, self.BUTTON_CURRENTLY_SHOWING, self._ButtonShowHide, general_mime_type )
|
||||
|
||||
QP.AddToLayout( gridbox, mg_checkbox, CC.FLAGS_VCENTER )
|
||||
max_width = ClientGUIFunctions.ConvertTextToPixelWidth( show_hide_button, 5 )
|
||||
|
||||
show_hide_button.setMaximumWidth( max_width )
|
||||
|
||||
self._general_mime_types_to_buttons[ general_mime_type ] = show_hide_button
|
||||
|
||||
QP.AddToLayout( gridbox, show_hide_button, CC.FLAGS_VCENTER )
|
||||
|
||||
vbox = QP.VBoxLayout()
|
||||
|
||||
for mime in mimes:
|
||||
for mime in mimes_in_type:
|
||||
|
||||
m_checkbox = QW.QCheckBox( HC.mime_string_lookup[ mime ], self )
|
||||
m_checkbox.clicked.connect( self.EventMimeCheckbox )
|
||||
|
||||
#m_checkbox.hide()
|
||||
|
||||
self._mimes_to_checkboxes[ mime ] = m_checkbox
|
||||
|
||||
QP.AddToLayout( vbox, m_checkbox, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
@ -93,46 +96,90 @@ class OptionsPanelMimes( OptionsPanel ):
|
|||
|
||||
self.setLayout( gridbox )
|
||||
|
||||
self._HideShowSubCheckboxes()
|
||||
|
||||
def _DoInitialHideShow( self ):
|
||||
|
||||
for ( general_mime_type, general_mime_checkbox ) in list( self._general_mime_types_to_checkboxes.items() ):
|
||||
|
||||
mimes_in_type = self._GetMimesForGeneralMimeType( general_mime_type )
|
||||
|
||||
should_show = general_mime_checkbox.checkState() == QC.Qt.PartiallyChecked
|
||||
|
||||
if not should_show:
|
||||
|
||||
self._ButtonShowHide( general_mime_type )
|
||||
|
||||
|
||||
|
||||
|
||||
def _HideShowSubCheckboxes( self ):
|
||||
def _GetMimesForGeneralMimeType( self, general_mime_type ):
|
||||
|
||||
pass
|
||||
mimes_in_type = HC.general_mimetypes_to_mime_groups[ general_mime_type ]
|
||||
|
||||
# this is insufficient. we need to have mime groups done by three-state checkboxes or something.
|
||||
# and it'd be nice to have a border around groups or something.
|
||||
mimes_in_type = [ mime for mime in mimes_in_type if mime in self._selectable_mimes ]
|
||||
|
||||
'''
|
||||
for ( mime_group, all_true ) in self._mime_groups_to_values.items():
|
||||
return mimes_in_type
|
||||
|
||||
|
||||
def _ButtonShowHide( self, general_mime_type ):
|
||||
|
||||
button = self._general_mime_types_to_buttons[ general_mime_type ]
|
||||
|
||||
mimes_in_type = self._GetMimesForGeneralMimeType( general_mime_type )
|
||||
|
||||
should_show = button.text() == self.BUTTON_CURRENTLY_HIDDEN
|
||||
|
||||
for mime in mimes_in_type:
|
||||
|
||||
should_show = not all_true
|
||||
self._mimes_to_checkboxes[ mime ].setVisible( should_show )
|
||||
|
||||
for mime in mime_group:
|
||||
|
||||
if mime not in self._selectable_mimes:
|
||||
|
||||
continue
|
||||
|
||||
|
||||
self._mimes_to_checkboxes[ mime ].setVisible( should_show )
|
||||
|
||||
|
||||
if should_show:
|
||||
|
||||
'''
|
||||
button.setText( self.BUTTON_CURRENTLY_SHOWING )
|
||||
|
||||
else:
|
||||
|
||||
button.setText( self.BUTTON_CURRENTLY_HIDDEN )
|
||||
|
||||
|
||||
|
||||
def _UpdateMimeGroupCheckboxes( self ):
|
||||
|
||||
for ( mime_group, mg_checkbox ) in self._mime_groups_to_checkboxes.items():
|
||||
for ( general_mime_type, general_mime_checkbox ) in self._general_mime_types_to_checkboxes.items():
|
||||
|
||||
respective_checkbox_values = [ m_checkbox.isChecked() for ( mime, m_checkbox ) in list( self._mimes_to_checkboxes.items() ) if mime in mime_group ]
|
||||
mimes_in_type = self._GetMimesForGeneralMimeType( general_mime_type )
|
||||
|
||||
all_true = False not in respective_checkbox_values
|
||||
all_checkbox_values = { self._mimes_to_checkboxes[ mime ].isChecked() for mime in mimes_in_type }
|
||||
|
||||
mg_checkbox.setChecked( all_true )
|
||||
self._mime_groups_to_values[ mime_group ] = all_true
|
||||
all_false = True not in all_checkbox_values
|
||||
all_true = False not in all_checkbox_values
|
||||
|
||||
if all_false:
|
||||
|
||||
check_state = QC.Qt.Unchecked
|
||||
|
||||
elif all_true:
|
||||
|
||||
check_state = QC.Qt.Checked
|
||||
|
||||
else:
|
||||
|
||||
check_state = QC.Qt.PartiallyChecked
|
||||
|
||||
|
||||
if check_state == QC.Qt.PartiallyChecked:
|
||||
|
||||
general_mime_checkbox.setTristate( True )
|
||||
|
||||
|
||||
general_mime_checkbox.setCheckState( check_state )
|
||||
|
||||
if check_state != QC.Qt.PartiallyChecked:
|
||||
|
||||
general_mime_checkbox.setTristate( False )
|
||||
|
||||
|
||||
|
||||
self._HideShowSubCheckboxes()
|
||||
|
||||
|
||||
def EventMimeCheckbox( self ):
|
||||
|
@ -142,43 +189,45 @@ class OptionsPanelMimes( OptionsPanel ):
|
|||
|
||||
def EventMimeGroupCheckbox( self ):
|
||||
|
||||
# Obsolote comment from before the Qt port:
|
||||
# this is a commandevent, which won't give up the checkbox object, so we have to do some jiggery pokery
|
||||
|
||||
for ( mime_group, mg_checkbox ) in list(self._mime_groups_to_checkboxes.items()):
|
||||
for ( general_mime_type, general_mime_checkbox ) in list( self._general_mime_types_to_checkboxes.items() ):
|
||||
|
||||
expected_value = self._mime_groups_to_values[ mime_group ]
|
||||
actual_value = mg_checkbox.isChecked()
|
||||
check_state = general_mime_checkbox.checkState()
|
||||
|
||||
if actual_value != expected_value:
|
||||
mime_check_state = None
|
||||
|
||||
if check_state == QC.Qt.Unchecked:
|
||||
|
||||
for ( mime, m_checkbox ) in list(self._mimes_to_checkboxes.items()):
|
||||
mime_check_state = False
|
||||
|
||||
elif check_state == QC.Qt.Checked:
|
||||
|
||||
mime_check_state = True
|
||||
|
||||
|
||||
if mime_check_state is not None:
|
||||
|
||||
mimes_in_type = self._GetMimesForGeneralMimeType( general_mime_type )
|
||||
|
||||
for mime in mimes_in_type:
|
||||
|
||||
if mime in mime_group:
|
||||
|
||||
m_checkbox.setChecked( actual_value )
|
||||
|
||||
self._mimes_to_checkboxes[ mime ].setChecked( mime_check_state )
|
||||
|
||||
|
||||
self._mime_groups_to_values[ mime_group ] = actual_value
|
||||
|
||||
|
||||
|
||||
self._HideShowSubCheckboxes()
|
||||
|
||||
|
||||
def GetValue( self ):
|
||||
|
||||
mimes = tuple( [ mime for ( mime, checkbox ) in list(self._mimes_to_checkboxes.items()) if checkbox.isChecked() == True ] )
|
||||
mimes = tuple( [ mime for ( mime, checkbox ) in list( self._mimes_to_checkboxes.items() ) if checkbox.isChecked() ] )
|
||||
|
||||
return mimes
|
||||
|
||||
|
||||
def SetValue( self, mimes ):
|
||||
def SetValue( self, checked_mimes ):
|
||||
|
||||
for ( mime, checkbox ) in list(self._mimes_to_checkboxes.items()):
|
||||
for ( mime, checkbox ) in self._mimes_to_checkboxes.items():
|
||||
|
||||
if mime in mimes:
|
||||
if mime in checked_mimes:
|
||||
|
||||
checkbox.setChecked( True )
|
||||
|
||||
|
@ -190,4 +239,6 @@ class OptionsPanelMimes( OptionsPanel ):
|
|||
|
||||
self._UpdateMimeGroupCheckboxes()
|
||||
|
||||
self._DoInitialHideShow()
|
||||
|
||||
|
||||
|
|
|
@ -117,14 +117,16 @@ class ResizingScrolledPanel( QW.QScrollArea ):
|
|||
width_increase = 0
|
||||
height_increase = 0
|
||||
|
||||
# + 2 because it is late and that seems to stop scrollbars lmao
|
||||
|
||||
if width_larger:
|
||||
|
||||
width_increase = max( 0, widget_size_hint.width() - my_size.width() )
|
||||
width_increase = max( 0, widget_size_hint.width() - my_size.width() + 2 )
|
||||
|
||||
|
||||
if height_larger:
|
||||
|
||||
height_increase = max( 0, widget_size_hint.height() - my_size.height() )
|
||||
height_increase = max( 0, widget_size_hint.height() - my_size.height() + 2 )
|
||||
|
||||
|
||||
if width_increase > 0 or height_increase > 0:
|
||||
|
|
|
@ -2680,11 +2680,9 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
self._preview_start_paused = QW.QCheckBox( self )
|
||||
self._preview_start_with_embed = QW.QCheckBox( self )
|
||||
|
||||
advanced_mode = HG.client_controller.new_options.GetBoolean( 'advanced_mode' )
|
||||
|
||||
for action in possible_show_actions:
|
||||
|
||||
if action == CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV and ( not advanced_mode or not ClientGUIMPV.MPV_IS_AVAILABLE ):
|
||||
if action == CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV and not ClientGUIMPV.MPV_IS_AVAILABLE:
|
||||
|
||||
continue
|
||||
|
||||
|
@ -2764,11 +2762,6 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
text += ' MPV is not available for this client.'
|
||||
|
||||
|
||||
if not advanced_mode:
|
||||
|
||||
text += ' Only advanced mode users can select MPV for now.'
|
||||
|
||||
|
||||
QP.AddToLayout( vbox, ClientGUICommon.BetterStaticText(self,text), CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
rows = []
|
||||
|
|
|
@ -3282,10 +3282,15 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
self._media_viewer_panel = ClientGUICommon.StaticBox( self, 'media viewer mime handling' )
|
||||
|
||||
self._media_viewer_options = ClientGUIListCtrl.BetterListCtrl( self._media_viewer_panel, 'media_viewer_options', 20, 20, [ ( 'mime', 21 ), ( 'media show action', 20 ), ( 'preview show action', 20 ), ( 'zoom info', -1 ) ], data_to_tuples_func = self._GetListCtrlData, activation_callback = self.EditMediaViewerOptions )
|
||||
media_viewer_list_panel = ClientGUIListCtrl.BetterListCtrlPanel( self._media_viewer_panel )
|
||||
|
||||
self._media_viewer_edit_button = QW.QPushButton( 'edit', self._media_viewer_panel )
|
||||
self._media_viewer_edit_button.clicked.connect( self.EditMediaViewerOptions )
|
||||
self._media_viewer_options = ClientGUIListCtrl.BetterListCtrl( media_viewer_list_panel, 'media_viewer_options', 20, 20, [ ( 'mime', 21 ), ( 'media show action', 20 ), ( 'preview show action', 20 ), ( 'zoom info', -1 ) ], data_to_tuples_func = self._GetListCtrlData, activation_callback = self.EditMediaViewerOptions, use_simple_delete = True )
|
||||
|
||||
media_viewer_list_panel.SetListCtrl( self._media_viewer_options )
|
||||
|
||||
media_viewer_list_panel.AddButton( 'add', self.AddMediaViewerOptions, enabled_check_func = self._CanAddMediaViewOption )
|
||||
media_viewer_list_panel.AddButton( 'edit', self.EditMediaViewerOptions, enabled_only_on_selection = True )
|
||||
media_viewer_list_panel.AddDeleteButton( enabled_check_func = self._CanDeleteMediaViewOptions )
|
||||
|
||||
#
|
||||
|
||||
|
@ -3302,13 +3307,11 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
self._media_zooms.setText( ','.join( ( str( media_zoom ) for media_zoom in media_zooms ) ) )
|
||||
|
||||
mimes_in_correct_order = ( HC.IMAGE_JPEG, HC.IMAGE_PNG, HC.IMAGE_APNG, HC.IMAGE_GIF, HC.IMAGE_WEBP, HC.IMAGE_TIFF, HC.IMAGE_ICON, HC.APPLICATION_FLASH, HC.APPLICATION_PDF, HC.APPLICATION_PSD, HC.APPLICATION_ZIP, HC.APPLICATION_RAR, HC.APPLICATION_7Z, HC.APPLICATION_HYDRUS_UPDATE_CONTENT, HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS, HC.VIDEO_AVI, HC.VIDEO_FLV, HC.VIDEO_MOV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_WEBM, HC.VIDEO_WMV, HC.VIDEO_MPEG, HC.VIDEO_REALMEDIA, HC.AUDIO_MP3, HC.AUDIO_M4A, HC.AUDIO_OGG, HC.AUDIO_FLAC, HC.AUDIO_WMA, HC.AUDIO_REALMEDIA, HC.AUDIO_TRUEAUDIO )
|
||||
all_media_view_options = self._new_options.GetMediaViewOptions()
|
||||
|
||||
for mime in mimes_in_correct_order:
|
||||
for ( mime, view_options ) in all_media_view_options.items():
|
||||
|
||||
items = self._new_options.GetMediaViewOptions( mime )
|
||||
|
||||
data = QP.ListsToTuples( [ mime ] + list( items ) )
|
||||
data = QP.ListsToTuples( [ mime ] + list( view_options ) )
|
||||
|
||||
self._media_viewer_options.AddDatas( ( data, ) )
|
||||
|
||||
|
@ -3335,31 +3338,75 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
|
||||
self._media_viewer_panel.Add( self._media_viewer_options, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
self._media_viewer_panel.Add( self._media_viewer_edit_button, CC.FLAGS_LONE_BUTTON )
|
||||
self._media_viewer_panel.Add( media_viewer_list_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
QP.AddToLayout( vbox, self._media_viewer_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
||||
def _CanAddMediaViewOption( self ):
|
||||
|
||||
return len( self._GetUnsetMediaViewFiletypes() ) > 0
|
||||
|
||||
|
||||
def _CanDeleteMediaViewOptions( self ):
|
||||
|
||||
deletable_mimes = set( HC.SEARCHABLE_MIMES )
|
||||
|
||||
selected_mimes = set()
|
||||
|
||||
for ( mime, media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) in self._media_viewer_options.GetData( only_selected = True ):
|
||||
|
||||
selected_mimes.add( mime )
|
||||
|
||||
|
||||
if len( selected_mimes ) == 0:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
all_selected_are_deletable = selected_mimes.issubset( deletable_mimes )
|
||||
|
||||
return all_selected_are_deletable
|
||||
|
||||
|
||||
def _GetCopyOfGeneralMediaViewOptions( self, desired_mime ):
|
||||
|
||||
general_mime_type = HC.mimes_to_general_mimetypes[ desired_mime ]
|
||||
|
||||
for ( mime, media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) in self._media_viewer_options.GetData():
|
||||
|
||||
if mime == general_mime_type:
|
||||
|
||||
view_options = ( desired_mime, media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info )
|
||||
|
||||
return view_options
|
||||
|
||||
|
||||
|
||||
|
||||
def _GetUnsetMediaViewFiletypes( self ):
|
||||
|
||||
editable_mimes = set( HC.SEARCHABLE_MIMES )
|
||||
|
||||
set_mimes = set()
|
||||
|
||||
for ( mime, media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) in self._media_viewer_options.GetData():
|
||||
|
||||
set_mimes.add( mime )
|
||||
|
||||
|
||||
unset_mimes = editable_mimes.difference( set_mimes )
|
||||
|
||||
return unset_mimes
|
||||
|
||||
|
||||
def _GetListCtrlData( self, data ):
|
||||
|
||||
( mime, media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = data
|
||||
|
||||
pretty_mime = HC.mime_string_lookup[ mime ]
|
||||
|
||||
for general_mime in HC.GENERAL_FILETYPES:
|
||||
|
||||
mime_group = HC.general_mimetypes_to_mime_groups[ general_mime ]
|
||||
|
||||
if mime in mime_group:
|
||||
|
||||
pretty_mime = '{}: {}'.format( HC.mime_string_lookup[ general_mime ], pretty_mime )
|
||||
|
||||
break
|
||||
|
||||
|
||||
pretty_mime = self._GetPrettyMime( mime )
|
||||
|
||||
pretty_media_show_action = CC.media_viewer_action_string_lookup[ media_show_action ]
|
||||
|
||||
|
@ -3402,11 +3449,64 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
return ( display_tuple, sort_tuple )
|
||||
|
||||
|
||||
def _GetPrettyMime( self, mime ):
|
||||
|
||||
pretty_mime = HC.mime_string_lookup[ mime ]
|
||||
|
||||
if mime not in HC.GENERAL_FILETYPES:
|
||||
|
||||
pretty_mime = '{}: {}'.format( HC.mime_string_lookup[ HC.mimes_to_general_mimetypes[ mime ] ], pretty_mime )
|
||||
|
||||
|
||||
return pretty_mime
|
||||
|
||||
|
||||
def AddMediaViewerOptions( self ):
|
||||
|
||||
unset_filetypes = self._GetUnsetMediaViewFiletypes()
|
||||
|
||||
if len( unset_filetypes ) == 0:
|
||||
|
||||
QW.QMessageBox.warning( self, 'Warning', 'You cannot add any more specific filetype options!' )
|
||||
|
||||
return
|
||||
|
||||
|
||||
choice_tuples = [ ( self._GetPrettyMime( mime ), mime ) for mime in unset_filetypes ]
|
||||
|
||||
try:
|
||||
|
||||
mime = ClientGUIDialogsQuick.SelectFromList( self, 'select the filetype to add', choice_tuples, sort_tuples = True )
|
||||
|
||||
except HydrusExceptions.CancelledException:
|
||||
|
||||
return
|
||||
|
||||
|
||||
data = self._GetCopyOfGeneralMediaViewOptions( mime )
|
||||
|
||||
title = 'add media view options information'
|
||||
|
||||
with ClientGUITopLevelWindows.DialogEdit( self, title ) as dlg:
|
||||
|
||||
panel = ClientGUIScrolledPanelsEdit.EditMediaViewOptionsPanel( dlg, data )
|
||||
|
||||
dlg.SetPanel( panel )
|
||||
|
||||
if dlg.exec() == QW.QDialog.Accepted:
|
||||
|
||||
new_data = panel.GetValue()
|
||||
|
||||
self._media_viewer_options.AddDatas( ( new_data, ) )
|
||||
|
||||
|
||||
|
||||
|
||||
def EditMediaViewerOptions( self ):
|
||||
|
||||
for data in self._media_viewer_options.GetData( only_selected = True ):
|
||||
|
||||
title = 'set media view options information'
|
||||
title = 'edit media view options information'
|
||||
|
||||
with ClientGUITopLevelWindows.DialogEdit( self, title ) as dlg:
|
||||
|
||||
|
@ -3469,6 +3569,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
HydrusData.ShowText( 'Could not parse those zooms, so they were not saved!' )
|
||||
|
||||
|
||||
mimes_to_media_view_options = {}
|
||||
|
||||
for data in self._media_viewer_options.GetData():
|
||||
|
||||
data = list( data )
|
||||
|
@ -3477,9 +3579,11 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
value = data[1:]
|
||||
|
||||
self._new_options.SetMediaViewOptions( mime, value )
|
||||
mimes_to_media_view_options[ mime ] = value
|
||||
|
||||
|
||||
self._new_options.SetMediaViewOptions( mimes_to_media_view_options )
|
||||
|
||||
|
||||
|
||||
class _RegexPanel( QW.QWidget ):
|
||||
|
@ -4912,7 +5016,7 @@ class ManageShortcutsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
reserved_panel = ClientGUICommon.StaticBox( self, 'built-in hydrus shortcut sets' )
|
||||
|
||||
self._reserved_shortcuts = ClientGUIListCtrl.BetterListCtrl( reserved_panel, 'reserved_shortcuts', 20, 30, [ ( 'name', -1 ), ( 'number of shortcuts', 20 ) ], data_to_tuples_func = self._GetTuples, activation_callback = self._EditReserved )
|
||||
self._reserved_shortcuts = ClientGUIListCtrl.BetterListCtrl( reserved_panel, 'reserved_shortcuts', 6, 30, [ ( 'name', -1 ), ( 'number of shortcuts', 20 ) ], data_to_tuples_func = self._GetTuples, activation_callback = self._EditReserved )
|
||||
|
||||
self._reserved_shortcuts.setMinimumSize( QP.TupleToQSize( (320,200) ) )
|
||||
|
||||
|
@ -4922,7 +5026,7 @@ class ManageShortcutsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
custom_panel = ClientGUICommon.StaticBox( self, 'custom user sets' )
|
||||
|
||||
self._custom_shortcuts = ClientGUIListCtrl.BetterListCtrl( custom_panel, 'custom_shortcuts', 20, 30, [ ( 'name', -1 ), ( 'number of shortcuts', 20 ) ], data_to_tuples_func = self._GetTuples, delete_key_callback = self._Delete, activation_callback = self._EditCustom )
|
||||
self._custom_shortcuts = ClientGUIListCtrl.BetterListCtrl( custom_panel, 'custom_shortcuts', 6, 30, [ ( 'name', -1 ), ( 'number of shortcuts', 20 ) ], data_to_tuples_func = self._GetTuples, delete_key_callback = self._Delete, activation_callback = self._EditCustom )
|
||||
|
||||
self._add_button = ClientGUICommon.BetterButton( custom_panel, 'add', self._Add )
|
||||
self._edit_custom_button = ClientGUICommon.BetterButton( custom_panel, 'edit', self._EditCustom )
|
||||
|
@ -4967,7 +5071,10 @@ class ManageShortcutsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
custom_panel_message = 'Custom shortcuts are advanced. They apply to the media viewer and must be turned on to take effect.'
|
||||
|
||||
custom_panel.Add( ClientGUICommon.BetterStaticText( custom_panel, custom_panel_message ), CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
st = ClientGUICommon.BetterStaticText( custom_panel, custom_panel_message )
|
||||
st.setWordWrap( True )
|
||||
|
||||
custom_panel.Add( st, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
custom_panel.Add( self._custom_shortcuts, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
||||
custom_panel.Add( button_hbox, CC.FLAGS_BUTTON_SIZER )
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ):
|
|||
vbox = QP.VBoxLayout()
|
||||
|
||||
QP.AddToLayout( vbox, help_hbox, CC.FLAGS_BUTTON_SIZER )
|
||||
QP.AddToLayout( vbox, info_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, info_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
QP.AddToLayout( vbox, migration_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
self.widget().setLayout( vbox )
|
||||
|
|
|
@ -250,7 +250,12 @@ class Shortcut( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
def __eq__( self, other ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
if isinstance( other, Shortcut ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ):
|
||||
|
|
|
@ -1579,7 +1579,7 @@ class ManageTagsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
[ ( tag, count ) ] = tag_counts
|
||||
|
||||
text = choice_text_prefix + ' "' + tag + '" for ' + HydrusData.ToHumanInt( count ) + ' files'
|
||||
text = choice_text_prefix + ' "' + HydrusText.ElideText( tag, 64 ) + '" for ' + HydrusData.ToHumanInt( count ) + ' files'
|
||||
|
||||
else:
|
||||
|
||||
|
@ -2127,7 +2127,7 @@ class ManageTagsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
message = 'Are you sure you want to remove these tags:'
|
||||
message += os.linesep * 2
|
||||
message += os.linesep.join( tags )
|
||||
message += os.linesep.join( ( HydrusText.ElideText( tag, 64 ) for tag in tags ) )
|
||||
|
||||
else:
|
||||
|
||||
|
|
|
@ -71,15 +71,27 @@ def GetSafeSize( tlw, min_size, gravity ):
|
|||
parent_window = parent.window()
|
||||
|
||||
# when we initialise, we might not have a frame yet because we haven't done show() yet
|
||||
# so borrow parent's
|
||||
# so borrow main gui's
|
||||
if frame_padding.isEmpty():
|
||||
|
||||
frame_padding = parent_window.frameGeometry().size() - parent_window.size()
|
||||
main_gui = HG.client_controller.gui
|
||||
|
||||
if main_gui is not None and QP.isValid( main_gui ) and not main_gui.isFullScreen():
|
||||
|
||||
frame_padding = main_gui.frameGeometry().size() - main_gui.size()
|
||||
|
||||
|
||||
|
||||
parent_frame_size = parent_window.frameGeometry().size()
|
||||
|
||||
parent_available_size = parent_frame_size - frame_padding
|
||||
if parent_window.isFullScreen():
|
||||
|
||||
parent_available_size = parent_window.size()
|
||||
|
||||
else:
|
||||
|
||||
parent_frame_size = parent_window.frameGeometry().size()
|
||||
|
||||
parent_available_size = parent_frame_size - frame_padding
|
||||
|
||||
|
||||
parent_available_width = parent_available_size.width()
|
||||
parent_available_height = parent_available_size.height()
|
||||
|
|
|
@ -445,7 +445,12 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
def __eq__( self, other ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
if isinstance( other, FileSeed ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ):
|
||||
|
@ -1357,6 +1362,8 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
if should_download_file:
|
||||
|
||||
self.CheckPreFetchMetadata( tag_import_options )
|
||||
|
||||
did_substantial_work = True
|
||||
|
||||
file_url = self.file_seed_data
|
||||
|
|
|
@ -100,7 +100,12 @@ class GallerySeed( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
def __eq__( self, other ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
if isinstance( other, GallerySeed ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ):
|
||||
|
|
|
@ -411,7 +411,7 @@ class HydrusResourceBooruPage( HydrusResourceBooru ):
|
|||
|
||||
mime = media_result.GetMime()
|
||||
|
||||
if mime in HC.IMAGES:
|
||||
if mime in HC.IMAGES or mime in HC.ANIMATIONS:
|
||||
|
||||
( width, height ) = media_result.GetResolution()
|
||||
|
||||
|
|
|
@ -546,7 +546,7 @@ class MediaResult( object ):
|
|||
|
||||
def GetInbox( self ):
|
||||
|
||||
return self._locations_manager.GetInbox()
|
||||
return self._locations_manager.inbox
|
||||
|
||||
|
||||
def GetLocationsManager( self ):
|
||||
|
@ -596,7 +596,10 @@ class MediaResult( object ):
|
|||
|
||||
def IsStaticImage( self ):
|
||||
|
||||
return self._file_info_manager.mime in HC.IMAGES and self._file_info_manager.duration in ( None, 0 )
|
||||
image = self._file_info_manager.mime in HC.IMAGES
|
||||
static_animation = self._file_info_manager.mime in HC.ANIMATIONS and self._file_info_manager.duration in ( 0, None )
|
||||
|
||||
return image or static_animation
|
||||
|
||||
|
||||
def ProcessContentUpdate( self, service_key, content_update ):
|
||||
|
@ -761,7 +764,7 @@ class LocationsManager( object ):
|
|||
self._pending = pending
|
||||
self._petitioned = petitioned
|
||||
|
||||
self._inbox = inbox
|
||||
self.inbox = inbox
|
||||
|
||||
if urls is None:
|
||||
|
||||
|
@ -803,7 +806,7 @@ class LocationsManager( object ):
|
|||
service_keys_to_filenames = dict( self._service_keys_to_filenames )
|
||||
current_to_timestamps = dict( self._current_to_timestamps )
|
||||
|
||||
return LocationsManager( current, deleted, pending, petitioned, self._inbox, urls, service_keys_to_filenames, current_to_timestamps, self._file_modified_timestamp )
|
||||
return LocationsManager( current, deleted, pending, petitioned, self.inbox, urls, service_keys_to_filenames, current_to_timestamps, self._file_modified_timestamp )
|
||||
|
||||
|
||||
def GetCDPP( self ): return ( self._current, self._deleted, self._pending, self._petitioned )
|
||||
|
@ -827,7 +830,7 @@ class LocationsManager( object ):
|
|||
|
||||
def GetInbox( self ):
|
||||
|
||||
return self._inbox
|
||||
return self.inbox
|
||||
|
||||
|
||||
def GetPending( self ): return self._pending
|
||||
|
@ -931,11 +934,11 @@ class LocationsManager( object ):
|
|||
|
||||
if action == HC.CONTENT_UPDATE_ARCHIVE:
|
||||
|
||||
self._inbox = False
|
||||
self.inbox = False
|
||||
|
||||
elif action == HC.CONTENT_UPDATE_INBOX:
|
||||
|
||||
self._inbox = True
|
||||
self.inbox = True
|
||||
|
||||
elif action == HC.CONTENT_UPDATE_ADD:
|
||||
|
||||
|
@ -951,7 +954,7 @@ class LocationsManager( object ):
|
|||
|
||||
if CC.COMBINED_LOCAL_FILE_SERVICE_KEY not in self._current:
|
||||
|
||||
self._inbox = True
|
||||
self.inbox = True
|
||||
|
||||
self._current.add( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
|
||||
|
||||
|
@ -978,7 +981,7 @@ class LocationsManager( object ):
|
|||
|
||||
elif service_key == CC.TRASH_SERVICE_KEY:
|
||||
|
||||
self._inbox = False
|
||||
self.inbox = False
|
||||
|
||||
self._current.discard( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
|
||||
self._deleted.add( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
|
||||
|
@ -1047,7 +1050,15 @@ class Media( object ):
|
|||
self._id_hash = self._id.__hash__()
|
||||
|
||||
|
||||
def __eq__( self, other ): return self.__hash__() == other.__hash__()
|
||||
def __eq__( self, other ):
|
||||
|
||||
if isinstance( other, Media ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ):
|
||||
|
||||
|
@ -1447,6 +1458,68 @@ class MediaList( object ):
|
|||
|
||||
|
||||
|
||||
def GetFilteredFileCount( self, file_filter ):
|
||||
|
||||
if file_filter.filter_type == FILE_FILTER_ALL:
|
||||
|
||||
return self.GetNumFiles()
|
||||
|
||||
elif file_filter.filter_type == FILE_FILTER_SELECTED:
|
||||
|
||||
return sum( ( m.GetNumFiles() for m in self._selected_media ) )
|
||||
|
||||
elif file_filter.filter_type == FILE_FILTER_NOT_SELECTED:
|
||||
|
||||
return self.GetNumFiles() - sum( ( m.GetNumFiles() for m in self._selected_media ) )
|
||||
|
||||
elif file_filter.filter_type == FILE_FILTER_NONE:
|
||||
|
||||
return 0
|
||||
|
||||
elif file_filter.filter_type == FILE_FILTER_INBOX:
|
||||
|
||||
return sum( ( m.GetNumInbox() for m in self._selected_media ) )
|
||||
|
||||
elif file_filter.filter_type == FILE_FILTER_ARCHIVE:
|
||||
|
||||
return self.GetNumFiles() - sum( ( m.GetNumInbox() for m in self._selected_media ) )
|
||||
|
||||
else:
|
||||
|
||||
flat_media = self.GetFlatMedia()
|
||||
|
||||
if file_filter.filter_type == FILE_FILTER_FILE_SERVICE:
|
||||
|
||||
file_service_key = file_filter.filter_data
|
||||
|
||||
return sum( ( 1 for m in flat_media if file_service_key in m.GetLocationsManager().GetCurrent() ) )
|
||||
|
||||
elif file_filter.filter_type == FILE_FILTER_LOCAL:
|
||||
|
||||
return sum( ( 1 for m in flat_media if m.GetLocationsManager().IsLocal() ) )
|
||||
|
||||
elif file_filter.filter_type == FILE_FILTER_REMOTE:
|
||||
|
||||
return sum( ( 1 for m in flat_media if m.GetLocationsManager().IsRemote() ) )
|
||||
|
||||
elif file_filter.filter_type == FILE_FILTER_TAGS:
|
||||
|
||||
( and_or_or, select_tags ) = file_filter.filter_data
|
||||
|
||||
if and_or_or == 'AND':
|
||||
|
||||
return sum( ( 1 for m in flat_media if len( m.GetTagsManager().GetCurrentAndPending( CC.COMBINED_TAG_SERVICE_KEY, ClientTags.TAG_DISPLAY_SIBLINGS_AND_PARENTS ).intersection( select_tags ) ) == len( select_tags ) ) )
|
||||
|
||||
elif and_or_or == 'OR':
|
||||
|
||||
return sum( ( 1 for m in flat_media if len( m.GetTagsManager().GetCurrentAndPending( CC.COMBINED_TAG_SERVICE_KEY, ClientTags.TAG_DISPLAY_SIBLINGS_AND_PARENTS ).intersection( select_tags ) ) > 0 ) )
|
||||
|
||||
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def GetFilteredHashes( self, file_filter ):
|
||||
|
||||
if file_filter.filter_type == FILE_FILTER_ALL:
|
||||
|
@ -1527,6 +1600,8 @@ class MediaList( object ):
|
|||
return hashes
|
||||
|
||||
|
||||
return set()
|
||||
|
||||
|
||||
def GetFilteredMedia( self, file_filter ):
|
||||
|
||||
|
@ -1587,6 +1662,8 @@ class MediaList( object ):
|
|||
return filtered_media
|
||||
|
||||
|
||||
return set()
|
||||
|
||||
|
||||
def GenerateMediaResults( self, has_location = None, discriminant = None, selected_media = None, unrated = None, for_media_viewer = False ):
|
||||
|
||||
|
@ -1977,6 +2054,16 @@ class FileFilter( object ):
|
|||
self.filter_data = filter_data
|
||||
|
||||
|
||||
def __eq__( self, other ):
|
||||
|
||||
if isinstance( other, FileFilter ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ):
|
||||
|
||||
if self.filter_data is None:
|
||||
|
@ -2023,7 +2110,7 @@ class FileFilter( object ):
|
|||
|
||||
|
||||
|
||||
count = len( media_list.GetFilteredHashes( self ) )
|
||||
count = media_list.GetFilteredFileCount( self )
|
||||
|
||||
filter_counts[ self ] = count
|
||||
|
||||
|
@ -2483,7 +2570,10 @@ class MediaSingleton( Media ):
|
|||
|
||||
|
||||
|
||||
def GetLocationsManager( self ): return self._media_result.GetLocationsManager()
|
||||
def GetLocationsManager( self ):
|
||||
|
||||
return self._media_result.GetLocationsManager()
|
||||
|
||||
|
||||
def GetMediaResult( self ): return self._media_result
|
||||
|
||||
|
@ -2685,7 +2775,7 @@ class MediaSingleton( Media ):
|
|||
|
||||
def IsImage( self ):
|
||||
|
||||
return self._media_result.GetMime() in HC.IMAGES and not self.HasDuration()
|
||||
return self._media_result.GetMime() in HC.IMAGES
|
||||
|
||||
|
||||
def IsSizeDefinite( self ): return self._media_result.GetSize() is not None
|
||||
|
|
|
@ -20,7 +20,12 @@ class NetworkContext( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
def __eq__( self, other ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
if isinstance( other, NetworkContext ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ):
|
||||
|
|
|
@ -31,6 +31,79 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
self._InitialiseDefaults()
|
||||
|
||||
|
||||
def _GetDefaultMediaViewOptions( self ):
|
||||
|
||||
media_view = HydrusSerialisable.SerialisableDictionary() # integer keys, so got to be cleverer dict
|
||||
|
||||
# media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) )
|
||||
|
||||
from . import ClientGUIMPV
|
||||
|
||||
if ClientGUIMPV.MPV_IS_AVAILABLE:
|
||||
|
||||
video_action = CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV
|
||||
audio_action = CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV
|
||||
|
||||
video_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
|
||||
|
||||
else:
|
||||
|
||||
video_action = CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE
|
||||
audio_action = CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON
|
||||
|
||||
video_zoom_info = ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, True, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
|
||||
|
||||
|
||||
image_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
|
||||
gif_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
|
||||
flash_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LINEAR, CC.ZOOM_LINEAR )
|
||||
|
||||
null_zoom_info = ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, False, CC.ZOOM_LINEAR, CC.ZOOM_LINEAR )
|
||||
|
||||
media_start_paused = False
|
||||
media_start_with_embed = False
|
||||
preview_start_paused = False
|
||||
preview_start_with_embed = False
|
||||
|
||||
media_view[ HC.GENERAL_IMAGE ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, preview_start_paused, preview_start_with_embed, image_zoom_info )
|
||||
|
||||
media_view[ HC.GENERAL_ANIMATION ] = ( video_action, media_start_paused, media_start_with_embed, video_action, preview_start_paused, preview_start_with_embed, gif_zoom_info )
|
||||
|
||||
media_view[ HC.GENERAL_VIDEO ] = ( video_action, media_start_paused, media_start_with_embed, video_action, preview_start_paused, preview_start_with_embed, video_zoom_info )
|
||||
|
||||
media_view[ HC.GENERAL_AUDIO ] = ( audio_action, media_start_paused, media_start_with_embed, audio_action, preview_start_paused, preview_start_with_embed, null_zoom_info )
|
||||
|
||||
media_view[ HC.GENERAL_APPLICATION ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, null_zoom_info )
|
||||
|
||||
return media_view
|
||||
|
||||
|
||||
def _GetMediaViewOptions( self, mime ):
|
||||
|
||||
if mime in self._dictionary[ 'media_view' ]:
|
||||
|
||||
return self._dictionary[ 'media_view' ][ mime ]
|
||||
|
||||
else:
|
||||
|
||||
if mime not in HC.mimes_to_general_mimetypes:
|
||||
|
||||
null_result = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, False, False, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, False, False, ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, False, CC.ZOOM_LINEAR, CC.ZOOM_LINEAR ) )
|
||||
|
||||
return null_result
|
||||
|
||||
|
||||
general_mime_type = HC.mimes_to_general_mimetypes[ mime ]
|
||||
|
||||
if general_mime_type not in self._dictionary[ 'media_view' ]:
|
||||
|
||||
self._dictionary[ 'media_view' ] = self._GetDefaultMediaViewOptions()
|
||||
|
||||
|
||||
return self._dictionary[ 'media_view' ][ general_mime_type ]
|
||||
|
||||
|
||||
|
||||
def _GetSerialisableInfo( self ):
|
||||
|
||||
serialisable_info = self._dictionary.GetSerialisableTuple()
|
||||
|
@ -137,6 +210,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
self._dictionary[ 'booleans' ][ 'autocomplete_float_main_gui' ] = True
|
||||
self._dictionary[ 'booleans' ][ 'autocomplete_float_frames' ] = False
|
||||
|
||||
self._dictionary[ 'booleans' ][ 'global_audio_mute' ] = False
|
||||
|
||||
#
|
||||
|
||||
self._dictionary[ 'colours' ] = HydrusSerialisable.SerialisableDictionary()
|
||||
|
@ -229,6 +304,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
self._dictionary[ 'integers' ][ 'video_thumbnail_percentage_in' ] = 35
|
||||
|
||||
self._dictionary[ 'integers' ][ 'global_audio_volume' ] = 70
|
||||
|
||||
self._dictionary[ 'integers' ][ 'duplicate_comparison_score_higher_jpeg_quality' ] = 10
|
||||
self._dictionary[ 'integers' ][ 'duplicate_comparison_score_much_higher_jpeg_quality' ] = 20
|
||||
self._dictionary[ 'integers' ][ 'duplicate_comparison_score_higher_filesize' ] = 10
|
||||
|
@ -468,47 +545,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
#
|
||||
|
||||
self._dictionary[ 'media_view' ] = HydrusSerialisable.SerialisableDictionary() # integer keys, so got to be cleverer dict
|
||||
|
||||
# media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) )
|
||||
|
||||
image_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
|
||||
gif_zoom_info = ( CC.MEDIA_VIEWER_SCALE_MAX_REGULAR, CC.MEDIA_VIEWER_SCALE_MAX_REGULAR, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, True, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
|
||||
flash_zoom_info = ( CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, False, CC.ZOOM_LINEAR, CC.ZOOM_LINEAR )
|
||||
video_zoom_info = ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_MAX_REGULAR, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, CC.MEDIA_VIEWER_SCALE_TO_CANVAS, True, CC.ZOOM_LANCZOS4, CC.ZOOM_AREA )
|
||||
null_zoom_info = ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_100, False, CC.ZOOM_LINEAR, CC.ZOOM_LINEAR )
|
||||
|
||||
media_start_paused = False
|
||||
media_start_with_embed = False
|
||||
preview_start_paused = False
|
||||
preview_start_with_embed = False
|
||||
|
||||
for mime in HC.SEARCHABLE_MIMES:
|
||||
|
||||
if mime in HC.IMAGES_THAT_CAN_HAVE_ANIMATION:
|
||||
|
||||
self._dictionary[ 'media_view' ][ mime ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, preview_start_paused, preview_start_with_embed, gif_zoom_info )
|
||||
|
||||
elif mime in HC.IMAGES:
|
||||
|
||||
self._dictionary[ 'media_view' ][ mime ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, preview_start_paused, preview_start_with_embed, image_zoom_info )
|
||||
|
||||
elif mime in HC.VIDEO:
|
||||
|
||||
self._dictionary[ 'media_view' ][ mime ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, preview_start_paused, preview_start_with_embed, video_zoom_info )
|
||||
|
||||
elif mime in HC.AUDIO:
|
||||
|
||||
self._dictionary[ 'media_view' ][ mime ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, null_zoom_info )
|
||||
|
||||
elif mime in HC.APPLICATIONS:
|
||||
|
||||
self._dictionary[ 'media_view' ][ mime ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, preview_start_paused, preview_start_with_embed, null_zoom_info )
|
||||
|
||||
|
||||
|
||||
self._dictionary[ 'media_view' ][ HC.APPLICATION_HYDRUS_UPDATE_CONTENT ] = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, preview_start_paused, preview_start_with_embed, null_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, media_start_paused, media_start_with_embed, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, preview_start_paused, preview_start_with_embed, null_zoom_info )
|
||||
self._dictionary[ 'media_view' ] = self._GetDefaultMediaViewOptions()
|
||||
|
||||
self._dictionary[ 'media_zooms' ] = [ 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 ]
|
||||
|
||||
|
@ -749,6 +786,14 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def GetDefaultMediaViewOptions( self ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
return self._GetDefaultMediaViewOptions()
|
||||
|
||||
|
||||
|
||||
def GetDefaultSort( self ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -848,7 +893,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
with self._lock:
|
||||
|
||||
( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
|
||||
( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._GetMediaViewOptions( mime )
|
||||
|
||||
( possible_show_actions, can_start_paused, can_start_with_embed ) = CC.media_viewer_capabilities[ mime ]
|
||||
|
||||
|
@ -861,11 +906,11 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def GetMediaViewOptions( self, mime ):
|
||||
def GetMediaViewOptions( self ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
return self._dictionary[ 'media_view' ][ mime ]
|
||||
return dict( self._dictionary[ 'media_view' ] )
|
||||
|
||||
|
||||
|
||||
|
@ -873,7 +918,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
with self._lock:
|
||||
|
||||
( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
|
||||
( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._GetMediaViewOptions( mime )
|
||||
|
||||
return zoom_info
|
||||
|
||||
|
@ -891,7 +936,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
with self._lock:
|
||||
|
||||
( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
|
||||
( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._GetMediaViewOptions( mime )
|
||||
|
||||
( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) = zoom_info
|
||||
|
||||
|
@ -932,7 +977,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
with self._lock:
|
||||
|
||||
( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._dictionary[ 'media_view' ][ mime ]
|
||||
( media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info ) = self._GetMediaViewOptions( mime )
|
||||
|
||||
( possible_show_actions, can_start_paused, can_start_with_embed ) = CC.media_viewer_capabilities[ mime ]
|
||||
|
||||
|
@ -1125,11 +1170,11 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def SetMediaViewOptions( self, mime, value_tuple ):
|
||||
def SetMediaViewOptions( self, mimes_to_media_view_options ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
self._dictionary[ 'media_view' ][ mime ] = value_tuple
|
||||
self._dictionary[ 'media_view' ] = HydrusSerialisable.SerialisableDictionary( mimes_to_media_view_options )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -819,7 +819,12 @@ class Predicate( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
def __eq__( self, other ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
if isinstance( other, Predicate ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ):
|
||||
|
@ -1461,6 +1466,10 @@ class Predicate( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
mime_text = 'image'
|
||||
|
||||
elif set( mimes ) == set( HC.SEARCHABLE_MIMES ).intersection( set( HC.ANIMATIONS ) ):
|
||||
|
||||
mime_text = 'animation'
|
||||
|
||||
elif set( mimes ) == set( HC.SEARCHABLE_MIMES ).intersection( set( HC.VIDEO ) ):
|
||||
|
||||
mime_text = 'video'
|
||||
|
|
|
@ -405,7 +405,12 @@ class TagFilter( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
def __eq__( self, other ):
|
||||
|
||||
return self._tag_slices_to_rules == other._tag_slices_to_rules
|
||||
if isinstance( other, TagFilter ):
|
||||
|
||||
return self._tag_slices_to_rules == other._tag_slices_to_rules
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def _GetTagSlices( self, tag ):
|
||||
|
|
|
@ -48,11 +48,20 @@ class JobKey( object ):
|
|||
self._variables = dict()
|
||||
|
||||
|
||||
def __eq__( self, other ): return self.__hash__() == other.__hash__()
|
||||
def __eq__( self, other ):
|
||||
|
||||
if isinstance( other, JobKey ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ): return self._key.__hash__()
|
||||
|
||||
def __ne__( self, other ): return self.__hash__() != other.__hash__()
|
||||
def __hash__( self ):
|
||||
|
||||
return self._key.__hash__()
|
||||
|
||||
|
||||
def _CheckCancelTests( self ):
|
||||
|
||||
|
@ -535,19 +544,19 @@ class QtAwareRepeatingJob(HydrusThreading.RepeatingJob):
|
|||
self._window = window
|
||||
|
||||
|
||||
def _QTWork( self ):
|
||||
|
||||
if not self._window or not QP.isValid( self._window ):
|
||||
|
||||
return
|
||||
|
||||
|
||||
self.Work()
|
||||
|
||||
|
||||
def _BootWorker( self ):
|
||||
|
||||
def qt_code():
|
||||
|
||||
if not self._window or not QP.isValid( self._window ):
|
||||
|
||||
return
|
||||
|
||||
|
||||
self.Work()
|
||||
|
||||
|
||||
QP.CallAfter( qt_code )
|
||||
QP.CallAfter( self._QTWork )
|
||||
|
||||
|
||||
def _MyWindowDead( self ):
|
||||
|
|
|
@ -18,6 +18,9 @@ if getattr( sys, 'frozen', False ):
|
|||
|
||||
else:
|
||||
|
||||
# maybe this is better?
|
||||
# os.path.dirname( __file__ )
|
||||
|
||||
RUNNING_FROM_FROZEN_BUILD = False
|
||||
|
||||
BASE_DIR = sys.path[0]
|
||||
|
@ -67,7 +70,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 18
|
||||
SOFTWARE_VERSION = 380
|
||||
SOFTWARE_VERSION = 381
|
||||
CLIENT_API_VERSION = 11
|
||||
|
||||
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
@ -488,20 +491,23 @@ GENERAL_AUDIO = 40
|
|||
GENERAL_IMAGE = 41
|
||||
GENERAL_VIDEO = 42
|
||||
GENERAL_APPLICATION = 43
|
||||
GENERAL_ANIMATION = 44
|
||||
APPLICATION_OCTET_STREAM = 100
|
||||
APPLICATION_UNKNOWN = 101
|
||||
|
||||
GENERAL_FILETYPES = ( GENERAL_APPLICATION, GENERAL_AUDIO, GENERAL_IMAGE, GENERAL_VIDEO )
|
||||
GENERAL_FILETYPES = { GENERAL_APPLICATION, GENERAL_AUDIO, GENERAL_IMAGE, GENERAL_VIDEO, GENERAL_ANIMATION }
|
||||
|
||||
SEARCHABLE_MIMES = { IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, APPLICATION_FLASH, VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_MKV, VIDEO_REALMEDIA, VIDEO_WEBM, VIDEO_MPEG, APPLICATION_PSD, APPLICATION_PDF, APPLICATION_ZIP, APPLICATION_RAR, APPLICATION_7Z, AUDIO_M4A, AUDIO_MP3, AUDIO_REALMEDIA, AUDIO_OGG, AUDIO_FLAC, AUDIO_TRUEAUDIO, AUDIO_WMA, VIDEO_WMV }
|
||||
|
||||
ALLOWED_MIMES = set( SEARCHABLE_MIMES ).union( { IMAGE_BMP, APPLICATION_HYDRUS_UPDATE_CONTENT, APPLICATION_HYDRUS_UPDATE_DEFINITIONS } )
|
||||
STORABLE_MIMES = set( SEARCHABLE_MIMES ).union( { APPLICATION_HYDRUS_UPDATE_CONTENT, APPLICATION_HYDRUS_UPDATE_DEFINITIONS } )
|
||||
|
||||
ALLOWED_MIMES = set( STORABLE_MIMES ).union( { IMAGE_BMP } )
|
||||
|
||||
DECOMPRESSION_BOMB_IMAGES = ( IMAGE_JPEG, IMAGE_PNG )
|
||||
|
||||
IMAGES = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON )
|
||||
IMAGES = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON )
|
||||
|
||||
IMAGES_THAT_CAN_HAVE_ANIMATION = ( IMAGE_GIF, IMAGE_APNG )
|
||||
ANIMATIONS = ( IMAGE_GIF, IMAGE_APNG )
|
||||
|
||||
AUDIO = ( AUDIO_M4A, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA, AUDIO_REALMEDIA, AUDIO_TRUEAUDIO )
|
||||
|
||||
|
@ -515,6 +521,17 @@ general_mimetypes_to_mime_groups[ GENERAL_APPLICATION ] = APPLICATIONS
|
|||
general_mimetypes_to_mime_groups[ GENERAL_AUDIO ] = AUDIO
|
||||
general_mimetypes_to_mime_groups[ GENERAL_IMAGE ] = IMAGES
|
||||
general_mimetypes_to_mime_groups[ GENERAL_VIDEO ] = VIDEO
|
||||
general_mimetypes_to_mime_groups[ GENERAL_ANIMATION ] = ANIMATIONS
|
||||
|
||||
mimes_to_general_mimetypes = {}
|
||||
|
||||
for ( general_mime_type, mimes_in_type ) in general_mimetypes_to_mime_groups.items():
|
||||
|
||||
for mime in mimes_in_type:
|
||||
|
||||
mimes_to_general_mimetypes[ mime ] = general_mime_type
|
||||
|
||||
|
||||
|
||||
MIMES_THAT_DEFINITELY_HAVE_AUDIO = tuple( [ APPLICATION_FLASH ] + list( AUDIO ) )
|
||||
MIMES_THAT_MAY_HAVE_AUDIO = tuple( list( MIMES_THAT_DEFINITELY_HAVE_AUDIO ) + list( VIDEO ) )
|
||||
|
@ -627,6 +644,7 @@ mime_string_lookup[ GENERAL_APPLICATION ] = 'application'
|
|||
mime_string_lookup[ GENERAL_AUDIO ] = 'audio'
|
||||
mime_string_lookup[ GENERAL_IMAGE ] = 'image'
|
||||
mime_string_lookup[ GENERAL_VIDEO ] = 'video'
|
||||
mime_string_lookup[ GENERAL_ANIMATION ] = 'animation'
|
||||
|
||||
mime_mimetype_string_lookup = {}
|
||||
|
||||
|
@ -675,6 +693,7 @@ mime_mimetype_string_lookup[ GENERAL_APPLICATION ] = 'application'
|
|||
mime_mimetype_string_lookup[ GENERAL_AUDIO ] = 'audio'
|
||||
mime_mimetype_string_lookup[ GENERAL_IMAGE ] = 'image'
|
||||
mime_mimetype_string_lookup[ GENERAL_VIDEO ] = 'video'
|
||||
mime_mimetype_string_lookup[ GENERAL_ANIMATION ] = 'animation'
|
||||
|
||||
mime_ext_lookup = {}
|
||||
|
||||
|
|
|
@ -1325,7 +1325,10 @@ def ToHumanBytes( size ):
|
|||
|
||||
def ToHumanInt( num ):
|
||||
|
||||
text = locale.format_string( '%d', num, grouping = True )
|
||||
# this got stomped on by mpv, which resets locale
|
||||
#text = locale.format_string( '%d', num, grouping = True )
|
||||
|
||||
text = '{:,}'.format( num )
|
||||
|
||||
return text
|
||||
|
||||
|
@ -1375,12 +1378,18 @@ class AccountIdentifier( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def __eq__( self, other ): return self.__hash__() == other.__hash__()
|
||||
def __eq__( self, other ):
|
||||
|
||||
if isinstance( other, AccountIdentifier ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ): return ( self._type, self._data ).__hash__()
|
||||
|
||||
def __ne__( self, other ): return self.__hash__() != other.__hash__()
|
||||
|
||||
def __repr__( self ): return 'Account Identifier: ' + str( ( self._type, self._data ) )
|
||||
|
||||
def _GetSerialisableInfo( self ):
|
||||
|
@ -1539,10 +1548,13 @@ class ContentUpdate( object ):
|
|||
|
||||
def __eq__( self, other ):
|
||||
|
||||
return hash( self ) == hash( other )
|
||||
if isinstance( other, ContentUpdate ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __ne__( self, other ): return not self.__eq__( other )
|
||||
|
||||
def __hash__( self ):
|
||||
|
||||
|
|
|
@ -1021,12 +1021,18 @@ class Content( HydrusSerialisable.SerialisableBase ):
|
|||
self._content_data = content_data
|
||||
|
||||
|
||||
def __eq__( self, other ): return self.__hash__() == other.__hash__()
|
||||
def __eq__( self, other ):
|
||||
|
||||
if isinstance( other, Content ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ): return ( self._content_type, self._content_data ).__hash__()
|
||||
|
||||
def __ne__( self, other ): return self.__hash__() != other.__hash__()
|
||||
|
||||
def __repr__( self ): return 'Content: ' + self.ToString()
|
||||
|
||||
def _GetSerialisableInfo( self ):
|
||||
|
@ -1378,7 +1384,12 @@ class Credentials( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
def __eq__( self, other ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
if isinstance( other, Credentials ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
return NotImplemented
|
||||
|
||||
|
||||
def __hash__( self ):
|
||||
|
@ -1386,11 +1397,6 @@ class Credentials( HydrusSerialisable.SerialisableBase ):
|
|||
return ( self._host, self._port, self._access_key ).__hash__()
|
||||
|
||||
|
||||
def __ne__( self, other ):
|
||||
|
||||
return self.__hash__() != other.__hash__()
|
||||
|
||||
|
||||
def __repr__( self ):
|
||||
|
||||
if self._access_key is None:
|
||||
|
|
|
@ -20,6 +20,15 @@ import traceback
|
|||
TEMP_PATH_LOCK = threading.Lock()
|
||||
IN_USE_TEMP_PATHS = set()
|
||||
|
||||
def AddBaseDirToEnvPath():
|
||||
|
||||
# this is a thing to get mpv working, loading the dll/so from the base dir using ctypes
|
||||
|
||||
if 'PATH' in os.environ:
|
||||
|
||||
os.environ[ 'PATH' ] = HC.BASE_DIR + os.pathsep + os.environ[ 'PATH' ]
|
||||
|
||||
|
||||
def AppendPathUntilNoConflicts( path ):
|
||||
|
||||
( path_absent_ext, ext ) = os.path.splitext( path )
|
||||
|
|
|
@ -360,6 +360,7 @@ class TabBar( QW.QTabBar ):
|
|||
self._supplementary_drop_target = None
|
||||
|
||||
self._last_clicked_tab_index = -1
|
||||
self._last_clicked_global_pos = None
|
||||
|
||||
|
||||
def AddSupplementaryTabBarDropTarget( self, drop_target ):
|
||||
|
@ -367,10 +368,12 @@ class TabBar( QW.QTabBar ):
|
|||
self._supplementary_drop_target = drop_target
|
||||
|
||||
|
||||
def clearLastClickedTabIndex( self ):
|
||||
def clearLastClickedTabInfo( self ):
|
||||
|
||||
self._last_clicked_tab_index = -1
|
||||
|
||||
self._last_clicked_global_pos = None
|
||||
|
||||
|
||||
def mouseMoveEvent( self, e ):
|
||||
|
||||
|
@ -383,6 +386,8 @@ class TabBar( QW.QTabBar ):
|
|||
|
||||
self._last_clicked_tab_index = self.tabAt( event.pos() )
|
||||
|
||||
self._last_clicked_global_pos = event.globalPos()
|
||||
|
||||
|
||||
QW.QTabBar.mousePressEvent( self, event )
|
||||
|
||||
|
@ -416,9 +421,9 @@ class TabBar( QW.QTabBar ):
|
|||
|
||||
|
||||
|
||||
def lastClickedTabIndex( self ):
|
||||
def lastClickedTabInfo( self ):
|
||||
|
||||
return self._last_clicked_tab_index
|
||||
return ( self._last_clicked_tab_index, self._last_clicked_global_pos )
|
||||
|
||||
|
||||
def dropEvent( self, event ):
|
||||
|
@ -504,7 +509,7 @@ class TabWidgetWithDnD( QW.QTabWidget ):
|
|||
|
||||
|
||||
def mouseMoveEvent( self, e ):
|
||||
|
||||
|
||||
if self.currentWidget() and self.currentWidget().rect().contains( self.currentWidget().mapFromGlobal( self.mapToGlobal( e.pos() ) ) ):
|
||||
|
||||
QW.QTabWidget.mouseMoveEvent( self, e )
|
||||
|
@ -529,13 +534,20 @@ class TabWidgetWithDnD( QW.QTabWidget ):
|
|||
return
|
||||
|
||||
|
||||
clicked_tab_index = self._tab_bar.lastClickedTabIndex()
|
||||
( clicked_tab_index, clicked_global_pos ) = self._tab_bar.lastClickedTabInfo()
|
||||
|
||||
if clicked_tab_index == -1:
|
||||
|
||||
return
|
||||
|
||||
|
||||
if e.globalPos() == clicked_global_pos:
|
||||
|
||||
# don't start a drag until movement
|
||||
|
||||
return
|
||||
|
||||
|
||||
tab_rect = self._tab_bar.tabRect( clicked_tab_index )
|
||||
|
||||
pixmap = QG.QPixmap( tab_rect.size() )
|
||||
|
@ -655,9 +667,9 @@ class TabWidgetWithDnD( QW.QTabWidget ):
|
|||
return
|
||||
|
||||
|
||||
source_page_index = source_tab_bar.lastClickedTabIndex()
|
||||
( source_page_index, source_page_click_global_pos ) = source_tab_bar.lastClickedTabInfo()
|
||||
|
||||
source_tab_bar.clearLastClickedTabIndex()
|
||||
source_tab_bar.clearLastClickedTabInfo()
|
||||
|
||||
source_notebook = source_tab_bar.parentWidget()
|
||||
source_page = source_notebook.widget( source_page_index )
|
||||
|
@ -1183,11 +1195,15 @@ class CallAfterEvent( QC.QEvent ):
|
|||
self._args = args
|
||||
self._kwargs = kwargs
|
||||
|
||||
|
||||
def Execute( self ):
|
||||
|
||||
self._fn( *self._args, **self._kwargs )
|
||||
|
||||
|
||||
if self._fn is not None:
|
||||
|
||||
self._fn( *self._args, **self._kwargs )
|
||||
|
||||
|
||||
|
||||
class CallAfterEventFilter( QC.QObject ):
|
||||
|
||||
def __init__( self, parent = None ):
|
||||
|
|
|
@ -836,7 +836,7 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
( mr_hash_id, mr_hash, mr_size, mr_mime, mr_width, mr_height, mr_duration, mr_num_frames, mr_has_audio, mr_num_words ) = mr_file_info_manager.ToTuple()
|
||||
|
||||
mr_inbox = mr_locations_manager.GetInbox()
|
||||
mr_inbox = mr_locations_manager.inbox
|
||||
|
||||
now = HydrusData.GetNow()
|
||||
|
||||
|
@ -985,7 +985,7 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
( mr_hash_id, mr_hash, mr_size, mr_mime, mr_width, mr_height, mr_duration, mr_num_frames, mr_has_audio, mr_num_words ) = mr_file_info_manager.ToTuple()
|
||||
|
||||
mr_inbox = mr_locations_manager.GetInbox()
|
||||
mr_inbox = mr_locations_manager.inbox
|
||||
|
||||
now = HydrusData.GetNow()
|
||||
|
||||
|
@ -1007,7 +1007,7 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
( mr_hash_id, mr_hash, mr_size, mr_mime, mr_width, mr_height, mr_duration, mr_num_frames, mr_has_audio, mr_num_words ) = mr_file_info_manager.ToTuple()
|
||||
|
||||
mr_inbox = mr_locations_manager.GetInbox()
|
||||
mr_inbox = mr_locations_manager.inbox
|
||||
|
||||
now = HydrusData.GetNow()
|
||||
|
||||
|
|
|
@ -573,6 +573,11 @@ class Controller( object ):
|
|||
return True
|
||||
|
||||
|
||||
def isFullScreen( self ):
|
||||
|
||||
return True # hackery for another test
|
||||
|
||||
|
||||
def IShouldRegularlyUpdate( self, window ):
|
||||
|
||||
return True
|
||||
|
|
Loading…
Reference in New Issue