Version 380
|
@ -1,16 +1,16 @@
|
|||
## Hydrus Network (Client and Server)
|
||||
|
||||
The hydrus network client is an application written for Anon and other internet-fluent media nerds who have large image/swf/webm collections. It browses with tags instead of folders, a little like a *booru on your desktop. Tags and files can be anonymously shared through custom servers that any user may run. Everything is free, and the source code is included with the release. It is developed for Windows, but fairly functional builds for Linux and OS X are released at the same time.
|
||||
The hydrus network client is an application written for Anon and other internet-fluent media nerds who have large image/swf/webm collections. It browses with tags instead of folders, a little like a booru on your desktop. Advanced users can share tags and files anonymously through custom servers that any user may run. Everything is free, privacy is the first concern, and the source code is included with the release. Releases are available for Windows, Linux, and macOS.
|
||||
|
||||
I am continually working on the software and try to put out a new release every Wednesday by 8pm Eastern.
|
||||
I am continually working on the software and try to put out a new release every Wednesday by 8pm EST.
|
||||
|
||||
This github repository is currently a weekly sync with my home dev environment, where I work on hydrus by myself. Feel free to fork, but please don't make pull requests at this time. I am also not active on Github, so if you have feedback of any sort, please email me, post on my 8chan board, or message me on tumblr or twitter or the discord.
|
||||
This github repository is currently a weekly sync with my home dev environment, where I work on hydrus by myself. Feel free to fork, but please do not make pull requests. I am also not active on Github, so if you have feedback of any sort, please email me, post on my 8kun or Endchan boards, or message me on tumblr or twitter or the discord.
|
||||
|
||||
The client can do quite a lot! Please check out the help inside the release or [here](http://hydrusnetwork.github.io/hydrus/help), which includes a comprehensive getting started guide.
|
||||
|
||||
* [homepage](http://hydrusnetwork.github.io/hydrus/)
|
||||
* [email](mailto:hydrus.admin@gmail.com)
|
||||
* [8chan board](https://8ch.net/hydrus/index.html)
|
||||
* [8kun board](https://8ch.net/hydrus/index.html)
|
||||
* [endchan bunker](https://endchan.net/hydrus/)
|
||||
* [twitter](https://twitter.com/hydrusnetwork)
|
||||
* [tumblr](http://hydrus.tumblr.com/)
|
||||
|
|
|
@ -8,6 +8,72 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<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>
|
||||
<li>took an qt-mpv example kindly provided by a user, updated it to work with the hydrus environment, and integrated it into the client as a new choosable view type under audio/video filetypes under options->media for advanced users</li>
|
||||
<li>reworked how the 'start paused' and 'start with embed button' media viewer options work under options->media. these are now separate checkboxes, not combined with the underlying 'show action'. existing embed/paused show actions should be converted automatically to the correct new values</li>
|
||||
<li>unfortunately, due to some python/qt/libmpv wrapper mouse interaction issues, mpv's 'on screen controller' overlay is not available</li>
|
||||
<li>for now, left click pause/plays the mpv window, just like the native mpv window.</li>
|
||||
<li>preview/next frame shortcuts should work for the mpv window when playing video</li>
|
||||
<li>no volume/mute controls yet, these will come in the coming weeks, including global mute settings</li>
|
||||
<li>updated media show and sizing code to account for mpv widgets</li>
|
||||
<li>reworked my animation scanbar to talk to mpv, and for my mpv window to talk back to it</li>
|
||||
<li>improved the animation scanbar to be more flexible when frame position and num_frames are not available, both in displaying info and calculating scanbar seek clicks</li>
|
||||
<li>mpv api version added to help->about</li>
|
||||
<li>.</li>
|
||||
<li>new downloader objects:</li>
|
||||
<li>thanks to a user, updated the 'pixiv artist page' url class to a new object that covers more situations. the defunct 'pixiv artist gallery page' url class is removed</li>
|
||||
<li>added 8kun and vch.moe download support. I got started on julay, smug, and endchan, but they were a little more tricky and I couldn't finish them in time--fingers crossed, next week</li>
|
||||
<li>.</li>
|
||||
<li>menu quality of life:</li>
|
||||
<li>a right-click on thumbnail whitespace will now not send a 'deselect all' event! feel free to right-click in empty space to do an easy remove->selected</li>
|
||||
<li>remorked the tag menu layout to move less frequently used actions down:</li>
|
||||
<li>- moved the discard/require/permit/exclude search predicate actions down</li>
|
||||
<li>- moved 'open in a new page' below select and copy</li>
|
||||
<li>- moved copy above select</li>
|
||||
<li>and some misc menu layout improvement on this menu</li>
|
||||
<li>fixed some labelling with the discard/require/permit/exclude verbs on negated tags</li>
|
||||
<li>right-clicking on system search predicates now shows the 'copy' menu correctly</li>
|
||||
<li>system predicates that offer easy inverse versions (like inbox/archive) should now offer the 'exclude' verb</li>
|
||||
<li>when right-clicking on a single tag that has siblings, its siblings and those siblings' subtags will now be listed in the copy menu!</li>
|
||||
<li>copying 'all' tags from a list menu, with or without counts, will now always copy them in the list order</li>
|
||||
<li>across the program, all menu 'labels' (menu text items that do not have a submenu and have no associated action, like 'imported 3 years 7 months ago') will now copy their text to the clipboard. let's see how it goes</li>
|
||||
<li>.</li>
|
||||
<li>other ui quality of life:</li>
|
||||
<li>across the program's UI, filetypes are now referred to with simpler terms rather than technical mimetypes. instead of 'image/jpg', it is now typically just 'jpeg'</li>
|
||||
<li>the 'remove selected' buttons on the gallery and watcher pages are now smaller trash icon buttons</li>
|
||||
<li>the new page chooser will now auto-dismiss if it loses focus--so if you accidentally launch it with a middle-/double-click somewhere, just click again and it'll go away</li>
|
||||
<li>hitting enter or return on the new page chooser now picks the 'first' button, scanning from the top-left. hitting enter twice now typically opens a new 'my files' search page</li>
|
||||
<li>added pause_media and pause_play_media shortcuts to the media_viewer shortcut set. new clients will start with space keypress performing pause_play_media</li>
|
||||
<li>added pause_play_slideshow shortcut to the media_viewer_browser shortcut set. this shortcut is no longer hardcoded by space keypress</li>
|
||||
<li>the six default shortcut sets now have a small description text on their edit panels</li>
|
||||
<li>the options->media edit panels now enable/disable widgets better based on current media/preview action</li>
|
||||
<li>added a checkbox to _options->gui pages_ to set whether middle-clicking a tag in the media viewer or a child tag manager to open a tag search page will switch to the main gui. default is false</li>
|
||||
<li>mr bones now reports total files, total filesize, and average filesize</li>
|
||||
<li>mr bones now loads your fate asynchronously</li>
|
||||
<li>.</li>
|
||||
<li>the rest:</li>
|
||||
<li>added tentative and simple realvideo (.rm) and realaudio (.ra) support--seems to work ok, but some weirder variable bit rate formats may not, and I have collapsed the various different extensions just down to .rm or .ra</li>
|
||||
<li>added trueaudio (.tta) audio support</li>
|
||||
<li>fixed a bug from the recent search optimisations where a bare inbox search would not cross-reference with the file domain (so some trash could show up in a simple inbox/'my files' query)</li>
|
||||
<li>fixed an issue with searching for known urls by url class where the class was for a third-or-higher-level domain and was not set to match subdomains (this hit 4chan file urls for a few users)</li>
|
||||
<li>fixed the issue with 'open externally' button panel not clearing their backgrounds properly</li>
|
||||
<li>fixed some of the new unusual stretchy layouts in the options dialog</li>
|
||||
<li>removed overhead from subscriptions' 'separate' operation, which should stop super CPU hang when trying to split a subscription with hundreds of thousands of urls</li>
|
||||
<li>fixed an issue where the advanced file delete dialog would not show the simple 'permanent delete' option when launched from the media viewer's right-click menu</li>
|
||||
<li>fixed the select/remove actions for local/remote</li>
|
||||
<li>fixed 'set_media_focus' from manage tags to correctly activate the underlying media viewer as well as set focus</li>
|
||||
<li>stopped the 'file lookup script' status control from resizing so wide when it fetches a url</li>
|
||||
<li>fixed a rare mouse wheel event handling bug in the media viewer</li>
|
||||
<li>reduced db overhead of the 'loading x/y' results generation routine. this _may_ help some users who had very slow media result loading</li>
|
||||
<li>cleaned up how the server reports a bootup-action error such as 'cannot shut down server since it is not running'--this is now a simple statement to console, not a full error with trace</li>
|
||||
<li>improved client shutdown when a system session shutdown call arrives at the same time as a user shutdown request--the core shutdown routine should now only occur once</li>
|
||||
<li>fixed an issue with thumbnail presentation on collections that have their contents deleted during the thumbnail generation call</li>
|
||||
<li>misc wx->Qt layout conversion improvements</li>
|
||||
<li>updated the github readme to reflect some new links and so on</li>
|
||||
<li>misc code cleanup</li>
|
||||
</ul>
|
||||
<li><h3>version 379</h3></li>
|
||||
<ul>
|
||||
<li>downloaders:</li>
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
<p>Shut the client down while you run the backup, obviously.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="warning">Do not put your live database in a folder than continuously syncs to a cloud backup. Many of these services will interfere with a running client and can cause database corruption. If you still want to use a system like this, either turn the sync off while the client is running, or use the above backup workflows to safely backup your client to a separate folder that syncs to the cloud.</p>
|
||||
<p class="warning">Do not put your live database in a folder that continuously syncs to a cloud backup. Many of these services will interfere with a running client and can cause database corruption. If you still want to use a system like this, either turn the sync off while the client is running, or use the above backup workflows to safely backup your client to a separate folder that syncs to the cloud.</p>
|
||||
<p>I recommend you always backup before you update, just in case there is a problem with my code that breaks your database. If that happens, please <a href="contact.html">contact me</a>, describing the problem, and revert to the functioning older version. I'll get on any problems like that immediately.</p>
|
||||
<p class="right"><a href="getting_started_files.html">Let's import some files! ----></a></p>
|
||||
</div>
|
||||
|
|
|
@ -967,8 +967,16 @@ class ThumbnailCache( object ):
|
|||
|
||||
display_media = media.GetDisplayMedia()
|
||||
|
||||
magic_score = self._magic_mime_thumbnail_ease_score_lookup[ display_media.GetMime() ]
|
||||
hash = display_media.GetHash()
|
||||
if display_media is None:
|
||||
|
||||
magic_score = self._magic_mime_thumbnail_ease_score_lookup[ None ]
|
||||
hash = ''
|
||||
|
||||
else:
|
||||
|
||||
magic_score = self._magic_mime_thumbnail_ease_score_lookup[ display_media.GetMime() ]
|
||||
hash = display_media.GetHash()
|
||||
|
||||
|
||||
return ( magic_score, hash )
|
||||
|
||||
|
@ -1230,9 +1238,12 @@ class ThumbnailCache( object ):
|
|||
|
||||
( page_key, media ) = result
|
||||
|
||||
self.GetThumbnail( media )
|
||||
|
||||
page_keys_to_rendered_medias[ page_key ].append( media )
|
||||
if media.GetDisplayMedia() is not None:
|
||||
|
||||
self.GetThumbnail( media )
|
||||
|
||||
page_keys_to_rendered_medias[ page_key ].append( media )
|
||||
|
||||
|
||||
|
||||
if len( page_keys_to_rendered_medias ) > 0:
|
||||
|
|
|
@ -157,58 +157,63 @@ import_folder_string_lookup[ IMPORT_FOLDER_DELETE ] = 'delete the file'
|
|||
import_folder_string_lookup[ IMPORT_FOLDER_IGNORE ] = 'leave the file alone, do not reattempt it'
|
||||
import_folder_string_lookup[ IMPORT_FOLDER_MOVE ] = 'move the file'
|
||||
|
||||
MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL = 0
|
||||
MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL_PAUSED = 1
|
||||
MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE = 0
|
||||
MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE_PAUSED = 1
|
||||
MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED = 2
|
||||
MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED = 3
|
||||
MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON = 4
|
||||
MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY = 5
|
||||
MEDIA_VIEWER_ACTION_DO_NOT_SHOW = 6
|
||||
MEDIA_VIEWER_ACTION_SHOW_WITH_MPV = 7
|
||||
|
||||
media_viewer_action_string_lookup = {}
|
||||
|
||||
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL ] = 'show as normal'
|
||||
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL_PAUSED ] = 'show as normal, but start paused'
|
||||
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED ] = 'show, but initially behind an embed button'
|
||||
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED ] = 'show, but initially behind an embed button, and start paused'
|
||||
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE ] = 'show with native hydrus viewer'
|
||||
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE_PAUSED ] = 'show as normal, but start paused -- obselete'
|
||||
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED ] = 'show, but initially behind an embed button -- obselete'
|
||||
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED ] = 'show, but initially behind an embed button, and start paused -- obselete'
|
||||
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON ] = 'show an \'open externally\' button'
|
||||
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY ] = 'do not show in the media viewer. on thumbnail activation, open externally'
|
||||
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_DO_NOT_SHOW ] = 'do not show at all'
|
||||
media_viewer_action_string_lookup[ MEDIA_VIEWER_ACTION_SHOW_WITH_MPV ] = 'show using mpv'
|
||||
|
||||
static_full_support = [ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY ]
|
||||
animated_full_support = [ MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL_PAUSED, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED, MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY ]
|
||||
no_support = [ MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, MEDIA_VIEWER_ACTION_DO_NOT_SHOW ]
|
||||
unsupported_media_actions = [ MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, MEDIA_VIEWER_ACTION_DO_NOT_SHOW ]
|
||||
static_media_actions = [ MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE ] + unsupported_media_actions
|
||||
animated_media_actions = [ MEDIA_VIEWER_ACTION_SHOW_WITH_MPV ] + static_media_actions
|
||||
audio_media_actions = [ MEDIA_VIEWER_ACTION_SHOW_WITH_MPV ] + unsupported_media_actions
|
||||
|
||||
# actions, can_start_paused, can_start_with_embed
|
||||
static_full_support = ( static_media_actions, False, True )
|
||||
animated_full_support = ( animated_media_actions, True, True )
|
||||
audio_full_support = ( audio_media_actions, True, True )
|
||||
no_support = ( unsupported_media_actions, False, False )
|
||||
|
||||
media_viewer_capabilities = {}
|
||||
|
||||
media_viewer_capabilities[ HC.IMAGE_JPEG ] = static_full_support
|
||||
media_viewer_capabilities[ HC.IMAGE_PNG ] = static_full_support
|
||||
media_viewer_capabilities[ HC.IMAGE_WEBP ] = static_full_support
|
||||
media_viewer_capabilities[ HC.IMAGE_TIFF ] = static_full_support
|
||||
media_viewer_capabilities[ HC.IMAGE_ICON ] = static_full_support
|
||||
media_viewer_capabilities[ HC.IMAGE_APNG ] = animated_full_support
|
||||
media_viewer_capabilities[ HC.IMAGE_GIF ] = animated_full_support
|
||||
media_viewer_capabilities[ HC.APPLICATION_FLASH ] = no_support
|
||||
for mime in HC.SEARCHABLE_MIMES:
|
||||
|
||||
if mime in HC.IMAGES_THAT_CAN_HAVE_ANIMATION:
|
||||
|
||||
media_viewer_capabilities[ mime ] = animated_full_support
|
||||
|
||||
elif mime in HC.IMAGES:
|
||||
|
||||
media_viewer_capabilities[ mime ] = static_full_support
|
||||
|
||||
elif mime in HC.VIDEO:
|
||||
|
||||
media_viewer_capabilities[ mime ] = animated_full_support
|
||||
|
||||
elif mime in HC.AUDIO:
|
||||
|
||||
media_viewer_capabilities[ mime ] = audio_full_support
|
||||
|
||||
else:
|
||||
|
||||
media_viewer_capabilities[ mime ] = no_support
|
||||
|
||||
|
||||
|
||||
media_viewer_capabilities[ HC.APPLICATION_PDF ] = no_support
|
||||
media_viewer_capabilities[ HC.APPLICATION_PSD ] = no_support
|
||||
media_viewer_capabilities[ HC.APPLICATION_ZIP ] = no_support
|
||||
media_viewer_capabilities[ HC.APPLICATION_7Z ] = no_support
|
||||
media_viewer_capabilities[ HC.APPLICATION_RAR ] = no_support
|
||||
media_viewer_capabilities[ HC.VIDEO_AVI ] = animated_full_support
|
||||
media_viewer_capabilities[ HC.VIDEO_FLV ] = animated_full_support
|
||||
media_viewer_capabilities[ HC.VIDEO_MOV ] = animated_full_support
|
||||
media_viewer_capabilities[ HC.VIDEO_MP4 ] = animated_full_support
|
||||
media_viewer_capabilities[ HC.VIDEO_MKV ] = animated_full_support
|
||||
media_viewer_capabilities[ HC.VIDEO_WEBM ] = animated_full_support
|
||||
media_viewer_capabilities[ HC.VIDEO_MPEG ] = animated_full_support
|
||||
media_viewer_capabilities[ HC.VIDEO_WMV ] = animated_full_support
|
||||
media_viewer_capabilities[ HC.AUDIO_M4A ] = no_support
|
||||
media_viewer_capabilities[ HC.AUDIO_MP3 ] = no_support
|
||||
media_viewer_capabilities[ HC.AUDIO_OGG ] = no_support
|
||||
media_viewer_capabilities[ HC.AUDIO_FLAC ] = no_support
|
||||
media_viewer_capabilities[ HC.AUDIO_WMA ] = no_support
|
||||
media_viewer_capabilities[ HC.APPLICATION_HYDRUS_UPDATE_CONTENT ] = no_support
|
||||
media_viewer_capabilities[ HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = no_support
|
||||
|
||||
|
@ -406,11 +411,20 @@ shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_SCROLL_RIGHT ] = 'scroll right'
|
|||
|
||||
SHORTCUTS_RESERVED_NAMES = [ 'archive_delete_filter', 'duplicate_filter', 'media', 'main_gui', 'media_viewer_browser', 'media_viewer' ]
|
||||
|
||||
shortcut_names_to_descriptions = {}
|
||||
|
||||
shortcut_names_to_descriptions[ 'archive_delete_filter' ] = 'Navigation actions for the media viewer during an archive/delete filter. Mouse shortcuts should work.'
|
||||
shortcut_names_to_descriptions[ 'duplicate_filter' ] = 'Navigation actions for the media viewer during a duplicate filter. Mouse shortcuts should work.'
|
||||
shortcut_names_to_descriptions[ 'media' ] = 'Actions to alter metadata for media in the media viewer or the thumbnail grid.'
|
||||
shortcut_names_to_descriptions[ 'main_gui' ] = 'Actions to control pages in the main window of the program.'
|
||||
shortcut_names_to_descriptions[ 'media_viewer_browser' ] = 'Navigation actions for the regular browsable media viewer.'
|
||||
shortcut_names_to_descriptions[ 'media_viewer' ] = 'Zoom and pan actions for any media viewer.'
|
||||
|
||||
# shortcut commands
|
||||
|
||||
SHORTCUTS_MEDIA_ACTIONS = [ 'manage_file_tags', 'manage_file_ratings', 'manage_file_urls', 'manage_file_notes', 'archive_file', 'inbox_file', 'delete_file', 'export_files', 'export_files_quick_auto_export', 'remove_file_from_view', 'open_file_in_external_program', 'open_selection_in_new_page', 'launch_the_archive_delete_filter', 'copy_bmp', 'copy_file', 'copy_path', 'copy_sha256_hash', 'get_similar_to_exact', 'get_similar_to_very_similar', 'get_similar_to_similar', 'get_similar_to_speculative', 'duplicate_media_set_alternate', 'duplicate_media_set_alternate_collections', 'duplicate_media_set_custom', 'duplicate_media_set_focused_better', 'duplicate_media_set_focused_king', 'duplicate_media_set_same_quality', 'open_known_url' ]
|
||||
SHORTCUTS_MEDIA_VIEWER_ACTIONS = [ 'move_animation_to_previous_frame', 'move_animation_to_next_frame', 'switch_between_fullscreen_borderless_and_regular_framed_window', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'pan_top_edge', 'pan_bottom_edge', 'pan_left_edge', 'pan_right_edge', 'pan_vertical_center', 'pan_horizontal_center', 'zoom_in', 'zoom_out', 'switch_between_100_percent_and_canvas_zoom', 'flip_darkmode' ]
|
||||
SHORTCUTS_MEDIA_VIEWER_BROWSER_ACTIONS = [ 'view_next', 'view_first', 'view_last', 'view_previous' ]
|
||||
SHORTCUTS_MEDIA_VIEWER_ACTIONS = [ 'pause_media', 'pause_play_media', 'move_animation_to_previous_frame', 'move_animation_to_next_frame', 'switch_between_fullscreen_borderless_and_regular_framed_window', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'pan_top_edge', 'pan_bottom_edge', 'pan_left_edge', 'pan_right_edge', 'pan_vertical_center', 'pan_horizontal_center', 'zoom_in', 'zoom_out', 'switch_between_100_percent_and_canvas_zoom', 'flip_darkmode' ]
|
||||
SHORTCUTS_MEDIA_VIEWER_BROWSER_ACTIONS = [ 'view_next', 'view_first', 'view_last', 'view_previous', 'pause_play_slideshow' ]
|
||||
SHORTCUTS_MAIN_GUI_ACTIONS = [ 'refresh', 'refresh_all_pages', 'refresh_page_of_pages_pages', 'new_page', 'new_page_of_pages', 'new_duplicate_filter_page', 'new_gallery_downloader_page', 'new_url_downloader_page', 'new_simple_downloader_page', 'new_watcher_downloader_page', 'synchronised_wait_switch', 'set_media_focus', 'show_hide_splitters', 'set_search_focus', 'unclose_page', 'close_page', 'redo', 'undo', 'flip_darkmode', 'check_all_import_folders', 'flip_debug_force_idle_mode_do_not_set_this', 'show_and_focus_manage_tags_favourite_tags', 'show_and_focus_manage_tags_related_tags', 'show_and_focus_manage_tags_file_lookup_script_tags', 'show_and_focus_manage_tags_recent_tags', 'focus_media_viewer' ]
|
||||
SHORTCUTS_DUPLICATE_FILTER_ACTIONS = [ 'duplicate_filter_this_is_better_and_delete_other', 'duplicate_filter_this_is_better_but_keep_both', 'duplicate_filter_exactly_the_same', 'duplicate_filter_alternates', 'duplicate_filter_false_positive', 'duplicate_filter_custom_action', 'duplicate_filter_skip', 'duplicate_filter_back' ]
|
||||
SHORTCUTS_ARCHIVE_DELETE_FILTER_ACTIONS = [ 'archive_delete_filter_keep', 'archive_delete_filter_delete', 'archive_delete_filter_skip', 'archive_delete_filter_back' ]
|
||||
|
|
|
@ -85,7 +85,7 @@ class App( QW.QApplication ):
|
|||
|
||||
HG.emergency_exit = True
|
||||
|
||||
if hasattr( HG.client_controller, 'gui' ) and HG.client_controller.gui is not None:
|
||||
if hasattr( HG.client_controller, 'gui' ) and HG.client_controller.gui is not None and QP.isValid( HG.client_controller.gui ):
|
||||
|
||||
HG.client_controller.gui.SaveAndClose()
|
||||
|
||||
|
|
|
@ -5527,17 +5527,17 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
done_files_info_predicates = False
|
||||
|
||||
if query_hash_ids is None:
|
||||
if query_hash_ids is None or ( is_inbox and len( query_hash_ids ) == len( self._inbox_hash_ids ) ):
|
||||
|
||||
if file_service_key == CC.COMBINED_FILE_SERVICE_KEY:
|
||||
|
||||
query_hash_ids = self._GetHashIdsThatHaveTags( tag_service_key, include_current_tags, include_pending_tags )
|
||||
query_hash_ids = intersection_update_qhi( query_hash_ids, self._GetHashIdsThatHaveTags( tag_service_key, include_current_tags, include_pending_tags ) )
|
||||
|
||||
else:
|
||||
|
||||
files_info_predicates.insert( 0, 'service_id = ' + str( file_service_id ) )
|
||||
|
||||
query_hash_ids = self._STS( self._c.execute( 'SELECT hash_id FROM current_files NATURAL JOIN files_info WHERE ' + ' AND '.join( files_info_predicates ) + ';' ) )
|
||||
query_hash_ids = intersection_update_qhi( query_hash_ids, self._STS( self._c.execute( 'SELECT hash_id FROM current_files NATURAL JOIN files_info WHERE ' + ' AND '.join( files_info_predicates ) + ';' ) ) )
|
||||
|
||||
done_files_info_predicates = True
|
||||
|
||||
|
@ -6869,27 +6869,30 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
self._PopulateHashIdsToHashesCache( hash_ids )
|
||||
|
||||
hash_ids_to_info = { hash_id : ClientMedia.FileInfoManager( hash_id, self._hash_ids_to_hashes_cache[ hash_id ], size, mime, width, height, duration, num_frames, has_audio, num_words ) for ( hash_id, size, mime, width, height, duration, num_frames, has_audio, num_words ) in self._ExecuteManySelectSingleParam( 'SELECT * FROM files_info WHERE hash_id = ?;', hash_ids ) }
|
||||
|
||||
hash_ids_to_current_file_service_ids_and_timestamps = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, timestamp ) ) for ( hash_id, service_id, timestamp ) in self._ExecuteManySelectSingleParam( 'SELECT hash_id, service_id, timestamp FROM current_files WHERE hash_id = ?;', hash_ids ) ) )
|
||||
|
||||
hash_ids_to_deleted_file_service_ids = HydrusData.BuildKeyToListDict( self._ExecuteManySelectSingleParam( 'SELECT hash_id, service_id FROM deleted_files WHERE hash_id = ?;', hash_ids ) )
|
||||
|
||||
hash_ids_to_pending_file_service_ids = HydrusData.BuildKeyToListDict( self._ExecuteManySelectSingleParam( 'SELECT hash_id, service_id FROM file_transfers WHERE hash_id = ?;', hash_ids ) )
|
||||
|
||||
hash_ids_to_petitioned_file_service_ids = HydrusData.BuildKeyToListDict( self._ExecuteManySelectSingleParam( 'SELECT hash_id, service_id FROM file_petitions WHERE hash_id = ?;', hash_ids ) )
|
||||
|
||||
hash_ids_to_urls = HydrusData.BuildKeyToSetDict( self._ExecuteManySelectSingleParam( 'SELECT hash_id, url FROM url_map NATURAL JOIN urls WHERE hash_id = ?;', hash_ids ) )
|
||||
|
||||
hash_ids_to_service_ids_and_filenames = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, filename ) ) for ( hash_id, service_id, filename ) in self._ExecuteManySelectSingleParam( 'SELECT hash_id, service_id, filename FROM service_filenames WHERE hash_id = ?;', hash_ids ) ) )
|
||||
|
||||
hash_ids_to_local_ratings = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, rating ) ) for ( service_id, hash_id, rating ) in self._ExecuteManySelectSingleParam( 'SELECT service_id, hash_id, rating FROM local_ratings WHERE hash_id = ?;', hash_ids ) ) )
|
||||
|
||||
hash_ids_to_file_viewing_stats_managers = { hash_id : ClientMedia.FileViewingStatsManager( preview_views, preview_viewtime, media_views, media_viewtime ) for ( hash_id, preview_views, preview_viewtime, media_views, media_viewtime ) in self._ExecuteManySelectSingleParam( 'SELECT hash_id, preview_views, preview_viewtime, media_views, media_viewtime FROM file_viewing_stats WHERE hash_id = ?;', hash_ids ) }
|
||||
|
||||
hash_ids_to_file_modified_timestamps = dict( self._ExecuteManySelectSingleParam( 'SELECT hash_id, file_modified_timestamp FROM file_modified_timestamps WHERE hash_id = ?;', hash_ids ) )
|
||||
|
||||
#
|
||||
with HydrusDB.TemporaryIntegerTable( self._c, hash_ids, 'hash_id' ) as temp_table_name:
|
||||
|
||||
self._AnalyzeTempTable( temp_table_name )
|
||||
|
||||
hash_ids_to_info = { hash_id : ClientMedia.FileInfoManager( hash_id, self._hash_ids_to_hashes_cache[ hash_id ], size, mime, width, height, duration, num_frames, has_audio, num_words ) for ( hash_id, size, mime, width, height, duration, num_frames, has_audio, num_words ) in self._c.execute( 'SELECT * FROM files_info NATURAL JOIN {};'.format( temp_table_name ) ) }
|
||||
|
||||
hash_ids_to_current_file_service_ids_and_timestamps = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, timestamp ) ) for ( hash_id, service_id, timestamp ) in self._c.execute( 'SELECT hash_id, service_id, timestamp FROM current_files NATURAL JOIN {};'.format( temp_table_name ) ) ) )
|
||||
|
||||
hash_ids_to_deleted_file_service_ids = HydrusData.BuildKeyToListDict( self._c.execute( 'SELECT hash_id, service_id FROM deleted_files NATURAL JOIN {};'.format( temp_table_name ) ) )
|
||||
|
||||
hash_ids_to_pending_file_service_ids = HydrusData.BuildKeyToListDict( self._c.execute( 'SELECT hash_id, service_id FROM file_transfers NATURAL JOIN {};'.format( temp_table_name ) ) )
|
||||
|
||||
hash_ids_to_petitioned_file_service_ids = HydrusData.BuildKeyToListDict( self._c.execute( 'SELECT hash_id, service_id FROM file_petitions NATURAL JOIN {};'.format( temp_table_name ) ) )
|
||||
|
||||
hash_ids_to_urls = HydrusData.BuildKeyToSetDict( self._c.execute( 'SELECT hash_id, url FROM url_map NATURAL JOIN urls NATURAL JOIN {};'.format( temp_table_name ) ) )
|
||||
|
||||
hash_ids_to_service_ids_and_filenames = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, filename ) ) for ( hash_id, service_id, filename ) in self._c.execute( 'SELECT hash_id, service_id, filename FROM service_filenames NATURAL JOIN {};'.format( temp_table_name ) ) ) )
|
||||
|
||||
hash_ids_to_local_ratings = HydrusData.BuildKeyToListDict( ( ( hash_id, ( service_id, rating ) ) for ( service_id, hash_id, rating ) in self._c.execute( 'SELECT service_id, hash_id, rating FROM local_ratings NATURAL JOIN {};'.format( temp_table_name ) ) ) )
|
||||
|
||||
hash_ids_to_file_viewing_stats_managers = { hash_id : ClientMedia.FileViewingStatsManager( preview_views, preview_viewtime, media_views, media_viewtime ) for ( hash_id, preview_views, preview_viewtime, media_views, media_viewtime ) in self._c.execute( 'SELECT hash_id, preview_views, preview_viewtime, media_views, media_viewtime FROM file_viewing_stats NATURAL JOIN {};'.format( temp_table_name ) ) }
|
||||
|
||||
hash_ids_to_file_modified_timestamps = dict( self._c.execute( 'SELECT hash_id, file_modified_timestamp FROM file_modified_timestamps NATURAL JOIN {};'.format( temp_table_name ) ) )
|
||||
|
||||
|
||||
hash_ids_to_current_file_service_ids = { hash_id : [ file_service_id for ( file_service_id, timestamp ) in file_service_ids_and_timestamps ] for ( hash_id, file_service_ids_and_timestamps ) in list(hash_ids_to_current_file_service_ids_and_timestamps.items()) }
|
||||
|
||||
|
@ -13389,6 +13392,42 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
if version == 379:
|
||||
|
||||
try:
|
||||
|
||||
domain_manager = self._GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER )
|
||||
|
||||
domain_manager.Initialise()
|
||||
|
||||
#
|
||||
|
||||
domain_manager.OverwriteDefaultURLClasses( [ 'pixiv artist page', '8kun thread', '8kun thread json api', 'vch.moe thread', 'vch.moe thread json api' ] )
|
||||
|
||||
domain_manager.DeleteURLClasses( [ 'pixiv artist gallery page' ] )
|
||||
|
||||
#
|
||||
|
||||
domain_manager.OverwriteDefaultParsers( [ '4chan-style thread api parser', '8kun thread api parser' ] )
|
||||
|
||||
#
|
||||
|
||||
domain_manager.TryToLinkURLClassesAndParsers()
|
||||
|
||||
#
|
||||
|
||||
self._SetJSONDump( domain_manager )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
HydrusData.PrintException( e )
|
||||
|
||||
message = 'Trying to update some downloader objects 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, ) )
|
||||
|
|
|
@ -394,6 +394,8 @@ def GetDefaultShortcuts():
|
|||
|
||||
media_viewer = ClientGUIShortcuts.ShortcutSet( 'media_viewer' )
|
||||
|
||||
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL, CC.SHORTCUT_KEY_SPECIAL_SPACE, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'pause_play_media' ) )
|
||||
|
||||
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'B' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'move_animation_to_previous_frame' ) )
|
||||
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'N' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'move_animation_to_next_frame' ) )
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ from . import ClientGUIImport
|
|||
from . import ClientGUILogin
|
||||
from . import ClientGUIManagement
|
||||
from . import ClientGUIMenus
|
||||
from . import ClientGUIMPV
|
||||
from . import ClientGUIPages
|
||||
from . import ClientGUIParsing
|
||||
from . import ClientGUIPopupMessages
|
||||
|
@ -250,6 +251,37 @@ def THREADUploadPending( service_key ):
|
|||
HG.client_controller.pub( 'notify_new_pending' )
|
||||
|
||||
|
||||
class BonedUpdater( ClientGUIAsync.AsyncQtUpdater ):
|
||||
|
||||
def _getResult( self ):
|
||||
|
||||
boned_stats = HG.client_controller.Read( 'boned_stats' )
|
||||
|
||||
return boned_stats
|
||||
|
||||
|
||||
def _publishLoading( self ):
|
||||
|
||||
self._job_key = ClientThreading.JobKey()
|
||||
|
||||
self._job_key.SetVariable( 'popup_text_1', 'Loading Statistics\u2026' )
|
||||
|
||||
HG.client_controller.pub( 'message', self._job_key )
|
||||
|
||||
|
||||
def _publishResult( self, result ):
|
||||
|
||||
self._job_key.Delete()
|
||||
|
||||
boned_stats = result
|
||||
|
||||
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self._win, 'review your fate' )
|
||||
|
||||
panel = ClientGUIScrolledPanelsReview.ReviewHowBonedAmI( frame, boned_stats )
|
||||
|
||||
frame.SetPanel( panel )
|
||||
|
||||
|
||||
class MenuUpdaterFile( ClientGUIAsync.AsyncQtUpdater ):
|
||||
|
||||
def _getResult( self ):
|
||||
|
@ -368,6 +400,8 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
|
|||
|
||||
self._first_session_loaded = False
|
||||
|
||||
self._done_save_and_close = False
|
||||
|
||||
self._notebook = ClientGUIPages.PagesNotebook( self, self._controller, 'top page notebook' )
|
||||
|
||||
self._garbage_snapshot = collections.Counter()
|
||||
|
@ -461,9 +495,15 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
|
|||
library_versions.append( ( 'OpenCV', cv2.__version__ ) )
|
||||
library_versions.append( ( 'openssl', ssl.OPENSSL_VERSION ) )
|
||||
library_versions.append( ( 'Pillow', PIL.__version__ ) )
|
||||
library_versions.append( ( 'html5lib present: ', str( ClientParsing.HTML5LIB_IS_OK ) ) )
|
||||
library_versions.append( ( 'lxml present: ', str( ClientParsing.LXML_IS_OK ) ) )
|
||||
library_versions.append( ( 'lz4 present: ', str( ClientRendering.LZ4_OK ) ) )
|
||||
|
||||
if ClientGUIMPV.MPV_IS_AVAILABLE:
|
||||
|
||||
library_versions.append( ( 'mpv api version: ', ClientGUIMPV.GetClientAPIVersionString() ) )
|
||||
|
||||
else:
|
||||
|
||||
library_versions.append( ( 'mpv', 'not available' ) )
|
||||
|
||||
|
||||
# 2.7.12 (v2.7.12:d33e0cf91556, Jun 27 2016, 15:24:40) [MSC v.1500 64 bit (AMD64)]
|
||||
v = sys.version
|
||||
|
@ -494,6 +534,10 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
|
|||
library_versions.append( ( 'sip', SIP_VERSION_STR ) )
|
||||
|
||||
library_versions.append( ( 'Qt', QC.__version__ ) )
|
||||
|
||||
library_versions.append( ( 'html5lib present: ', str( ClientParsing.HTML5LIB_IS_OK ) ) )
|
||||
library_versions.append( ( 'lxml present: ', str( ClientParsing.LXML_IS_OK ) ) )
|
||||
library_versions.append( ( 'lz4 present: ', str( ClientRendering.LZ4_OK ) ) )
|
||||
library_versions.append( ( 'temp dir', HydrusPaths.GetCurrentTempDir() ) )
|
||||
|
||||
import locale
|
||||
|
@ -1584,13 +1628,7 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
|
|||
|
||||
self._controller.file_viewing_stats_manager.Flush()
|
||||
|
||||
boned_stats = self._controller.Read( 'boned_stats' )
|
||||
|
||||
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, 'review your fate' )
|
||||
|
||||
panel = ClientGUIScrolledPanelsReview.ReviewHowBonedAmI( frame, boned_stats )
|
||||
|
||||
frame.SetPanel( panel )
|
||||
self._boned_updater.update()
|
||||
|
||||
|
||||
def _ImportDownloaders( self ):
|
||||
|
@ -1831,6 +1869,8 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
|
|||
self._menu_updater_pages = MenuUpdaterPages( self )
|
||||
self._menu_updater_pending = MenuUpdaterPending( self )
|
||||
|
||||
self._boned_updater = BonedUpdater( self )
|
||||
|
||||
self.setMenuBar( self._menubar )
|
||||
|
||||
for name in MENU_ORDER:
|
||||
|
@ -3869,123 +3909,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
|
||||
|
||||
def TryToSaveAndClose( self, restart = False, force_shutdown_maintenance = False ):
|
||||
|
||||
# the return value here is 'exit allowed'
|
||||
|
||||
if not HG.emergency_exit:
|
||||
|
||||
able_to_close_statement = self._notebook.GetTestAbleToCloseStatement()
|
||||
|
||||
if HC.options[ 'confirm_client_exit' ] or able_to_close_statement is not None:
|
||||
|
||||
if restart:
|
||||
|
||||
text = 'Are you sure you want to restart the client? (Will auto-yes in 15 seconds)'
|
||||
|
||||
else:
|
||||
|
||||
text = 'Are you sure you want to exit the client? (Will auto-yes in 15 seconds)'
|
||||
|
||||
|
||||
if able_to_close_statement is not None:
|
||||
|
||||
text += os.linesep * 2
|
||||
text += able_to_close_statement
|
||||
|
||||
|
||||
result = ClientGUIDialogsQuick.GetYesNo( self, text, auto_yes_time = 15 )
|
||||
|
||||
if result == QW.QDialog.Rejected:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
if restart:
|
||||
|
||||
HG.restart = True
|
||||
|
||||
|
||||
if force_shutdown_maintenance:
|
||||
|
||||
HG.do_idle_shutdown_work = True
|
||||
|
||||
|
||||
self.SaveAndClose()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def SaveAndClose( self ):
|
||||
|
||||
try:
|
||||
|
||||
if self._message_manager:
|
||||
|
||||
self._message_manager.CleanBeforeDestroy()
|
||||
|
||||
self._message_manager.hide()
|
||||
|
||||
|
||||
#
|
||||
|
||||
if self._new_options.GetBoolean( 'saving_sash_positions_on_exit' ):
|
||||
|
||||
self._SaveSplitterPositions()
|
||||
|
||||
|
||||
ClientGUITopLevelWindows.SaveTLWSizeAndPosition( self, self._frame_key )
|
||||
|
||||
for tlw in QW.QApplication.topLevelWidgets():
|
||||
|
||||
tlw.hide()
|
||||
|
||||
|
||||
#
|
||||
|
||||
session = self._notebook.GetCurrentGUISession( 'last session' )
|
||||
|
||||
self._controller.SaveGUISession( session )
|
||||
|
||||
session.SetName( 'exit session' )
|
||||
|
||||
self._controller.SaveGUISession( session )
|
||||
|
||||
#
|
||||
|
||||
self._DestroyTimers()
|
||||
|
||||
self.DeleteAllClosedPages() # Obsolote comment, preserved just in case: wx crashes if any are left in here, wew
|
||||
|
||||
self._notebook.CleanBeforeDestroy()
|
||||
|
||||
self._controller.WriteSynchronous( 'save_options', HC.options )
|
||||
|
||||
self._controller.WriteSynchronous( 'serialisable', self._new_options )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
HydrusData.PrintException( e )
|
||||
|
||||
|
||||
if HG.emergency_exit:
|
||||
|
||||
self.deleteLater()
|
||||
|
||||
self._controller.Exit()
|
||||
|
||||
else:
|
||||
|
||||
self._controller.CreateSplash()
|
||||
|
||||
QP.CallAfter( self._controller.Exit )
|
||||
|
||||
self.deleteLater()
|
||||
|
||||
|
||||
|
||||
def FlipDarkmode( self ):
|
||||
|
||||
current_colourset = self._new_options.GetString( 'current_colourset' )
|
||||
|
@ -5106,7 +5029,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
self._notebook.NewPage( management_controller, on_deepest_notebook = True )
|
||||
|
||||
|
||||
def NewPageQuery( self, service_key, initial_hashes = None, initial_predicates = None, page_name = None, do_sort = False ):
|
||||
def NewPageQuery( self, service_key, initial_hashes = None, initial_predicates = None, page_name = None, do_sort = False, select_page = True, activate_window = False ):
|
||||
|
||||
if initial_hashes is None:
|
||||
|
||||
|
@ -5118,7 +5041,12 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
initial_predicates = []
|
||||
|
||||
|
||||
self._notebook.NewPageQuery( service_key, initial_hashes = initial_hashes, initial_predicates = initial_predicates, page_name = page_name, on_deepest_notebook = True, do_sort = do_sort )
|
||||
self._notebook.NewPageQuery( service_key, initial_hashes = initial_hashes, initial_predicates = initial_predicates, page_name = page_name, on_deepest_notebook = True, do_sort = do_sort, select_page = select_page )
|
||||
|
||||
if activate_window and not self.isActiveWindow():
|
||||
|
||||
self.activateWindow()
|
||||
|
||||
|
||||
|
||||
def NotifyClosedPage( self, page ):
|
||||
|
@ -5738,6 +5666,81 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
|
||||
|
||||
|
||||
def SaveAndClose( self ):
|
||||
|
||||
if self._done_save_and_close:
|
||||
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
|
||||
if QP.isValid( self._message_manager ):
|
||||
|
||||
self._message_manager.CleanBeforeDestroy()
|
||||
|
||||
self._message_manager.hide()
|
||||
|
||||
|
||||
#
|
||||
|
||||
if self._new_options.GetBoolean( 'saving_sash_positions_on_exit' ):
|
||||
|
||||
self._SaveSplitterPositions()
|
||||
|
||||
|
||||
ClientGUITopLevelWindows.SaveTLWSizeAndPosition( self, self._frame_key )
|
||||
|
||||
for tlw in QW.QApplication.topLevelWidgets():
|
||||
|
||||
tlw.hide()
|
||||
|
||||
|
||||
#
|
||||
|
||||
session = self._notebook.GetCurrentGUISession( 'last session' )
|
||||
|
||||
self._controller.SaveGUISession( session )
|
||||
|
||||
session.SetName( 'exit session' )
|
||||
|
||||
self._controller.SaveGUISession( session )
|
||||
|
||||
#
|
||||
|
||||
self._DestroyTimers()
|
||||
|
||||
self.DeleteAllClosedPages()
|
||||
|
||||
self._notebook.CleanBeforeDestroy()
|
||||
|
||||
self._controller.WriteSynchronous( 'save_options', HC.options )
|
||||
|
||||
self._controller.WriteSynchronous( 'serialisable', self._new_options )
|
||||
|
||||
self._done_save_and_close = True
|
||||
|
||||
except Exception as e:
|
||||
|
||||
HydrusData.PrintException( e )
|
||||
|
||||
|
||||
if HG.emergency_exit:
|
||||
|
||||
self.deleteLater()
|
||||
|
||||
self._controller.Exit()
|
||||
|
||||
else:
|
||||
|
||||
self._controller.CreateSplash()
|
||||
|
||||
QP.CallAfter( self._controller.Exit )
|
||||
|
||||
self.deleteLater()
|
||||
|
||||
|
||||
|
||||
def SetMediaFocus( self ):
|
||||
|
||||
self._SetMediaFocus()
|
||||
|
@ -5760,6 +5763,55 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
self._notebook.ShowPage( page )
|
||||
|
||||
|
||||
def TryToSaveAndClose( self, restart = False, force_shutdown_maintenance = False ):
|
||||
|
||||
# the return value here is 'exit allowed'
|
||||
|
||||
if not HG.emergency_exit:
|
||||
|
||||
able_to_close_statement = self._notebook.GetTestAbleToCloseStatement()
|
||||
|
||||
if HC.options[ 'confirm_client_exit' ] or able_to_close_statement is not None:
|
||||
|
||||
if restart:
|
||||
|
||||
text = 'Are you sure you want to restart the client? (Will auto-yes in 15 seconds)'
|
||||
|
||||
else:
|
||||
|
||||
text = 'Are you sure you want to exit the client? (Will auto-yes in 15 seconds)'
|
||||
|
||||
|
||||
if able_to_close_statement is not None:
|
||||
|
||||
text += os.linesep * 2
|
||||
text += able_to_close_statement
|
||||
|
||||
|
||||
result = ClientGUIDialogsQuick.GetYesNo( self, text, auto_yes_time = 15 )
|
||||
|
||||
if result == QW.QDialog.Rejected:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
if restart:
|
||||
|
||||
HG.restart = True
|
||||
|
||||
|
||||
if force_shutdown_maintenance:
|
||||
|
||||
HG.do_idle_shutdown_work = True
|
||||
|
||||
|
||||
self.SaveAndClose()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def UnregisterAnimationUpdateWindow( self, window ):
|
||||
|
||||
self._animation_update_windows.discard( window )
|
||||
|
|
|
@ -14,6 +14,7 @@ from . import ClientGUIFunctions
|
|||
from . import ClientGUIHoverFrames
|
||||
from . import ClientGUIMedia
|
||||
from . import ClientGUIMenus
|
||||
from . import ClientGUIMPV
|
||||
from . import ClientGUIScrolledPanels
|
||||
from . import ClientGUIScrolledPanelsButtonQuestions
|
||||
from . import ClientGUIScrolledPanelsEdit
|
||||
|
@ -34,6 +35,7 @@ from . import HydrusPaths
|
|||
from . import HydrusSerialisable
|
||||
from . import HydrusTags
|
||||
import os
|
||||
import time
|
||||
from . import QtPorting as QP
|
||||
from qtpy import QtCore as QC
|
||||
from qtpy import QtWidgets as QW
|
||||
|
@ -44,11 +46,11 @@ from . import QtPorting as QP
|
|||
|
||||
OPEN_EXTERNALLY_BUTTON_SIZE = ( 200, 45 )
|
||||
|
||||
def CalculateCanvasMediaSize( media, canvas_size ):
|
||||
def CalculateCanvasMediaSize( media, canvas_size, show_action ):
|
||||
|
||||
( canvas_width, canvas_height ) = canvas_size.toTuple()
|
||||
|
||||
if ShouldHaveAnimationBar( media ):
|
||||
if ShouldHaveAnimationBar( media, show_action ):
|
||||
|
||||
animated_scanbar_height = HG.client_controller.new_options.GetInteger( 'animated_scanbar_height' )
|
||||
|
||||
|
@ -81,7 +83,7 @@ def CalculateCanvasZooms( canvas, media, show_action ):
|
|||
|
||||
new_options = HG.client_controller.new_options
|
||||
|
||||
( canvas_width, canvas_height ) = CalculateCanvasMediaSize( media, canvas.size() )
|
||||
( canvas_width, canvas_height ) = CalculateCanvasMediaSize( media, canvas.size(), show_action )
|
||||
|
||||
width_zoom = canvas_width / media_width
|
||||
|
||||
|
@ -174,13 +176,13 @@ def CalculateCanvasZooms( canvas, media, show_action ):
|
|||
|
||||
return ( default_zoom, canvas_zoom )
|
||||
|
||||
def CalculateMediaContainerSize( media, zoom, action ):
|
||||
def CalculateMediaContainerSize( media, zoom, show_action ):
|
||||
|
||||
if action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
|
||||
if show_action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
|
||||
|
||||
raise Exception( 'This media should not be shown in the media viewer!' )
|
||||
|
||||
elif action == CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON:
|
||||
elif show_action == CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON:
|
||||
|
||||
( width, height ) = OPEN_EXTERNALLY_BUTTON_SIZE
|
||||
|
||||
|
@ -197,7 +199,7 @@ def CalculateMediaContainerSize( media, zoom, action ):
|
|||
|
||||
( media_width, media_height ) = CalculateMediaSize( media, zoom )
|
||||
|
||||
if ShouldHaveAnimationBar( media ):
|
||||
if ShouldHaveAnimationBar( media, show_action ):
|
||||
|
||||
animated_scanbar_height = HG.client_controller.new_options.GetInteger( 'animated_scanbar_height' )
|
||||
|
||||
|
@ -209,28 +211,44 @@ def CalculateMediaContainerSize( media, zoom, action ):
|
|||
|
||||
def CalculateMediaSize( media, zoom ):
|
||||
|
||||
( original_width, original_height ) = media.GetResolution()
|
||||
|
||||
media_width = int( round( zoom * original_width ) )
|
||||
media_height = int( round( zoom * original_height ) )
|
||||
if media.GetMime() in HC.AUDIO:
|
||||
|
||||
media_width = 360
|
||||
media_height = 240
|
||||
|
||||
else:
|
||||
|
||||
( original_width, original_height ) = media.GetResolution()
|
||||
|
||||
media_width = int( round( zoom * original_width ) )
|
||||
media_height = int( round( zoom * original_height ) )
|
||||
|
||||
|
||||
return ( media_width, media_height )
|
||||
|
||||
def IsStaticImage( media ):
|
||||
def ShouldHaveAnimationBar( media, show_action ):
|
||||
|
||||
return media.GetMime() in HC.IMAGES and not ShouldHaveAnimationBar( media )
|
||||
if show_action not in ( CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV ):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def ShouldHaveAnimationBar( media ):
|
||||
is_audio = media.GetMime() in HC.AUDIO
|
||||
|
||||
is_animated_gif = media.GetMime() == HC.IMAGE_GIF and media.HasDuration()
|
||||
if is_audio:
|
||||
|
||||
return True
|
||||
|
||||
|
||||
is_native_video = media.GetMime() in HC.NATIVE_VIDEO
|
||||
is_animated_image = media.GetMime() in HC.IMAGES_THAT_CAN_HAVE_ANIMATION and media.HasDuration()
|
||||
|
||||
is_video = media.GetMime() in HC.VIDEO
|
||||
|
||||
num_frames = media.GetNumFrames()
|
||||
|
||||
has_more_than_one_frame = num_frames is not None and num_frames > 1
|
||||
|
||||
return is_animated_gif or is_native_video
|
||||
return ( is_animated_image or is_video ) and has_more_than_one_frame
|
||||
|
||||
class Animation( QW.QWidget ):
|
||||
|
||||
|
@ -242,7 +260,6 @@ class Animation( QW.QWidget ):
|
|||
|
||||
self._media = None
|
||||
|
||||
self._drag_happened = False
|
||||
self._left_down_event = None
|
||||
|
||||
self._something_valid_has_been_drawn = False
|
||||
|
@ -528,7 +545,6 @@ class Animation( QW.QWidget ):
|
|||
|
||||
self._media = media
|
||||
|
||||
self._drag_happened = False
|
||||
self._left_down_event = None
|
||||
|
||||
self._ClearCanvasBitmap()
|
||||
|
@ -672,7 +688,7 @@ class AnimationBar( QW.QWidget ):
|
|||
|
||||
def _GetXFromFrameIndex( self, index, width_offset = 0 ):
|
||||
|
||||
if self._num_frames < 2:
|
||||
if self._num_frames is None or self._num_frames < 2:
|
||||
|
||||
return 0
|
||||
|
||||
|
@ -682,6 +698,13 @@ class AnimationBar( QW.QWidget ):
|
|||
return int( ( my_width - width_offset ) * index / ( self._num_frames - 1 ) )
|
||||
|
||||
|
||||
def _GetXFromTimestamp( self, timestamp_ms, width_offset = 0 ):
|
||||
|
||||
( my_width, my_height ) = self.size().toTuple()
|
||||
|
||||
return int( ( my_width - width_offset ) * timestamp_ms / self._duration_ms )
|
||||
|
||||
|
||||
def _Redraw( self, painter ):
|
||||
|
||||
self._last_drawn_info = self._GetAnimationBarStatus()
|
||||
|
@ -761,24 +784,46 @@ class AnimationBar( QW.QWidget ):
|
|||
|
||||
animated_scanbar_nub_width = HG.client_controller.new_options.GetInteger( 'animated_scanbar_nub_width' )
|
||||
|
||||
nub_x = self._GetXFromFrameIndex( current_frame_index, width_offset = animated_scanbar_nub_width )
|
||||
nub_x = None
|
||||
|
||||
painter.drawRect( nub_x, 0, animated_scanbar_nub_width, animated_scanbar_height )
|
||||
if self._num_frames is not None and current_frame_index is not None:
|
||||
|
||||
nub_x = self._GetXFromFrameIndex( current_frame_index, width_offset = animated_scanbar_nub_width )
|
||||
|
||||
elif self._duration_ms is not None and current_timestamp_ms is not None:
|
||||
|
||||
nub_x = self._GetXFromTimestamp( current_timestamp_ms, width_offset = animated_scanbar_nub_width )
|
||||
|
||||
|
||||
if nub_x is not None:
|
||||
|
||||
painter.drawRect( nub_x, 0, animated_scanbar_nub_width, animated_scanbar_height )
|
||||
|
||||
|
||||
#
|
||||
|
||||
painter.setPen( QG.QPen() )
|
||||
|
||||
s = HydrusData.ConvertValueRangeToPrettyString( current_frame_index + 1, self._num_frames )
|
||||
progress_strings = []
|
||||
|
||||
if self._num_frames is not None:
|
||||
|
||||
progress_strings.append( HydrusData.ConvertValueRangeToPrettyString( current_frame_index + 1, self._num_frames ) )
|
||||
|
||||
|
||||
if current_timestamp_ms is not None:
|
||||
|
||||
s += ' - {}'.format( HydrusData.ConvertValueRangeToScanbarTimestampsMS( current_timestamp_ms, self._duration_ms ) )
|
||||
progress_strings.append( HydrusData.ConvertValueRangeToScanbarTimestampsMS( current_timestamp_ms, self._duration_ms ) )
|
||||
|
||||
|
||||
( x, y ) = painter.fontMetrics().size( QC.Qt.TextSingleLine, s ).toTuple()
|
||||
s = ' - '.join( progress_strings )
|
||||
|
||||
QP.DrawText( painter, my_width-x-3, 3, s )
|
||||
if len( s ) > 0:
|
||||
|
||||
( x, y ) = painter.fontMetrics().size( QC.Qt.TextSingleLine, s ).toTuple()
|
||||
|
||||
QP.DrawText( painter, my_width-x-3, 3, s )
|
||||
|
||||
|
||||
|
||||
def EventMouse( self, event ):
|
||||
|
@ -808,7 +853,7 @@ class AnimationBar( QW.QWidget ):
|
|||
|
||||
( my_width, my_height ) = self.size().toTuple()
|
||||
|
||||
if event.buttons() != QC.Qt.NoButton and event.type() == QC.QEvent.MouseMove:
|
||||
if event.type() == QC.QEvent.MouseMove and event.buttons() != QC.Qt.NoButton:
|
||||
|
||||
self._currently_in_a_drag = True
|
||||
|
||||
|
@ -833,11 +878,20 @@ class AnimationBar( QW.QWidget ):
|
|||
if proportion < 0: proportion = 0
|
||||
if proportion > 1: proportion = 1
|
||||
|
||||
current_frame_index = int( proportion * ( self._num_frames - 1 ) + 0.5 )
|
||||
|
||||
self.update()
|
||||
|
||||
self._media_window.GotoFrame( current_frame_index )
|
||||
if isinstance( self._media_window, Animation ):
|
||||
|
||||
current_frame_index = int( proportion * ( self._num_frames - 1 ) + 0.5 )
|
||||
|
||||
self._media_window.GotoFrame( current_frame_index )
|
||||
|
||||
elif isinstance( self._media_window, ClientGUIMPV.mpvWidget ):
|
||||
|
||||
time_index_ms = int( proportion * self._duration_ms )
|
||||
|
||||
self._media_window.Seek( time_index_ms )
|
||||
|
||||
|
||||
elif event.type() == QC.QEvent.MouseButtonRelease:
|
||||
|
||||
|
@ -869,7 +923,18 @@ class AnimationBar( QW.QWidget ):
|
|||
|
||||
self._media_window = media_window
|
||||
self._duration_ms = max( media.GetDuration(), 1 )
|
||||
self._num_frames = max( media.GetNumFrames(), 1 )
|
||||
|
||||
num_frames = media.GetNumFrames()
|
||||
|
||||
if num_frames is None:
|
||||
|
||||
self._num_frames = num_frames
|
||||
|
||||
else:
|
||||
|
||||
self._num_frames = max( num_frames, 1 )
|
||||
|
||||
|
||||
self._last_drawn_info = None
|
||||
|
||||
self._has_experienced_mouse_down = False
|
||||
|
@ -901,8 +966,6 @@ class AnimationBar( QW.QWidget ):
|
|||
return
|
||||
|
||||
|
||||
frame_index = self._media_window.CurrentFrame()
|
||||
|
||||
if self._last_drawn_info != self._GetAnimationBarStatus():
|
||||
|
||||
self.update()
|
||||
|
@ -1027,6 +1090,8 @@ class CanvasFrame( ClientGUITopLevelWindows.FrameThatResizes ):
|
|||
|
||||
def TakeFocusForUser( self ):
|
||||
|
||||
self.activateWindow()
|
||||
|
||||
self._canvas_window.setFocus( QC.Qt.OtherFocusReason )
|
||||
|
||||
|
||||
|
@ -1108,7 +1173,9 @@ class Canvas( QW.QWidget ):
|
|||
return False
|
||||
|
||||
|
||||
if self._GetShowAction( media ) in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
|
||||
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( media )
|
||||
|
||||
if media_show_action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
|
||||
|
||||
return False
|
||||
|
||||
|
@ -1338,16 +1405,21 @@ class Canvas( QW.QWidget ):
|
|||
|
||||
def _GetShowAction( self, media ):
|
||||
|
||||
start_paused = False
|
||||
start_with_embed = False
|
||||
|
||||
bad_result = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, start_paused, start_with_embed )
|
||||
|
||||
if media is None:
|
||||
|
||||
return CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW
|
||||
return bad_result
|
||||
|
||||
|
||||
mime = media.GetMime()
|
||||
|
||||
if mime not in HC.ALLOWED_MIMES: # stopgap to catch a collection or application_unknown due to unusual import order/media moving
|
||||
|
||||
return CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW
|
||||
return bad_result
|
||||
|
||||
|
||||
if self.PREVIEW_WINDOW:
|
||||
|
@ -1369,9 +1441,9 @@ class Canvas( QW.QWidget ):
|
|||
|
||||
( my_width, my_height ) = self.size().toTuple()
|
||||
|
||||
action = self._GetShowAction( self._current_media )
|
||||
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( self._current_media )
|
||||
|
||||
( media_width, media_height ) = CalculateMediaContainerSize( self._current_media, self._current_zoom, action )
|
||||
( media_width, media_height ) = CalculateMediaContainerSize( self._current_media, self._current_zoom, media_show_action )
|
||||
|
||||
new_size = ( media_width, media_height )
|
||||
|
||||
|
@ -1395,7 +1467,9 @@ class Canvas( QW.QWidget ):
|
|||
return False
|
||||
|
||||
|
||||
return self._GetShowAction( self._current_media ) not in ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW )
|
||||
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( self._current_media )
|
||||
|
||||
return media_show_action not in ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW )
|
||||
|
||||
|
||||
def _IShouldCatchShortcutEvent( self, event = None ):
|
||||
|
@ -1418,9 +1492,9 @@ class Canvas( QW.QWidget ):
|
|||
|
||||
# set up canvas zoom
|
||||
|
||||
show_action = self._GetShowAction( self._current_media )
|
||||
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( self._current_media )
|
||||
|
||||
( gumpf_current_zoom, self._canvas_zoom ) = CalculateCanvasZooms( self, self._current_media, show_action )
|
||||
( gumpf_current_zoom, self._canvas_zoom ) = CalculateCanvasZooms( self, self._current_media, media_show_action )
|
||||
|
||||
# for init zoom, we want the _width_ to stay the same as previous
|
||||
|
||||
|
@ -1661,6 +1735,16 @@ class Canvas( QW.QWidget ):
|
|||
self._media_container.Pause()
|
||||
|
||||
|
||||
def _PausePlayCurrentMedia( self ):
|
||||
|
||||
if self._current_media is None:
|
||||
|
||||
return
|
||||
|
||||
|
||||
self._media_container.PausePlay()
|
||||
|
||||
|
||||
def _PrefetchNeighbours( self ):
|
||||
|
||||
pass
|
||||
|
@ -1691,9 +1775,9 @@ class Canvas( QW.QWidget ):
|
|||
return
|
||||
|
||||
|
||||
show_action = self._GetShowAction( self._current_media )
|
||||
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( self._current_media )
|
||||
|
||||
( self._current_zoom, self._canvas_zoom ) = CalculateCanvasZooms( self, self._current_media, show_action )
|
||||
( self._current_zoom, self._canvas_zoom ) = CalculateCanvasZooms( self, self._current_media, media_show_action )
|
||||
|
||||
HG.client_controller.pub( 'canvas_new_zoom', self._canvas_key, self._current_zoom )
|
||||
|
||||
|
@ -1707,9 +1791,9 @@ class Canvas( QW.QWidget ):
|
|||
|
||||
my_size = self.size()
|
||||
|
||||
action = self._GetShowAction( self._current_media )
|
||||
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( self._current_media )
|
||||
|
||||
( media_width, media_height ) = CalculateMediaContainerSize( self._current_media, self._current_zoom, action )
|
||||
( media_width, media_height ) = CalculateMediaContainerSize( self._current_media, self._current_zoom, media_show_action )
|
||||
|
||||
x = ( my_size.width() - media_width ) // 2
|
||||
y = ( my_size.height() - media_height ) // 2
|
||||
|
@ -2220,6 +2304,10 @@ class Canvas( QW.QWidget ):
|
|||
|
||||
self._PauseCurrentMedia()
|
||||
|
||||
elif action == 'pause_play_media':
|
||||
|
||||
self._PausePlayCurrentMedia()
|
||||
|
||||
elif action == 'move_animation_to_previous_frame':
|
||||
|
||||
self._media_container.GotoPreviousOrNextFrame( -1 )
|
||||
|
@ -2315,11 +2403,11 @@ class Canvas( QW.QWidget ):
|
|||
|
||||
if self._current_media.GetLocationsManager().IsLocal() and initial_width > 0 and initial_height > 0:
|
||||
|
||||
show_action = self._GetShowAction( self._current_media )
|
||||
( media_show_action, media_start_paused, media_start_with_embed ) = self._GetShowAction( self._current_media )
|
||||
|
||||
pos = ( self._media_window_pos.x(), self._media_window_pos.y() )
|
||||
|
||||
self._media_container.SetMedia( self._current_media, initial_size, pos, show_action )
|
||||
self._media_container.SetMedia( self._current_media, initial_size, pos, media_show_action, media_start_paused, media_start_with_embed )
|
||||
|
||||
self._PrefetchNeighbours()
|
||||
|
||||
|
@ -2441,11 +2529,11 @@ class CanvasPanel( Canvas ):
|
|||
|
||||
if CC.LOCAL_FILE_SERVICE_KEY in locations_manager.GetCurrent():
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'delete', 'Delete this file.', self._Delete, file_service_key=CC.LOCAL_FILE_SERVICE_KEY )
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'delete', 'Delete this file.', self._Delete, file_service_key = CC.LOCAL_FILE_SERVICE_KEY )
|
||||
|
||||
elif CC.TRASH_SERVICE_KEY in locations_manager.GetCurrent():
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'delete completely', 'Physically delete this file from disk.', self._Delete, file_service_key=CC.TRASH_SERVICE_KEY )
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'delete completely', 'Physically delete this file from disk.', self._Delete, file_service_key = CC.TRASH_SERVICE_KEY )
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'undelete', 'Take this file out of the trash.', self._Undelete )
|
||||
|
||||
|
||||
|
@ -3683,15 +3771,15 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
|
|||
|
||||
caught = True
|
||||
|
||||
if event.button() == QC.Qt.LeftButton and event.type() == QC.QEvent.MouseButtonPress:
|
||||
if event.type() == QC.QEvent.MouseButtonPress and event.button() == QC.Qt.LeftButton:
|
||||
|
||||
self.EventDragBegin( event )
|
||||
|
||||
elif event.button() == QC.Qt.LeftButton and event.type() == QC.QEvent.MouseButtonRelease:
|
||||
elif event.type() == QC.QEvent.MouseButtonRelease and event.button() == QC.Qt.LeftButton:
|
||||
|
||||
self.EventDragEnd( event )
|
||||
|
||||
elif event.buttons() != QC.Qt.NoButton and event.type() == QC.QEvent.MouseMove:
|
||||
elif event.type() == QC.QEvent.MouseMove and event.buttons() != QC.Qt.NoButton:
|
||||
|
||||
self.EventMouseMove( event )
|
||||
|
||||
|
@ -4031,7 +4119,7 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithHovers ):
|
|||
hash = media.GetHash()
|
||||
mime = media.GetMime()
|
||||
|
||||
if IsStaticImage( media ):
|
||||
if media.IsStaticImage():
|
||||
|
||||
if not image_cache.HasImageRenderer( hash ):
|
||||
|
||||
|
@ -4359,21 +4447,21 @@ class CanvasMediaListFilterArchiveDelete( CanvasMediaList ):
|
|||
if event.modifiers() & QC.Qt.ShiftModifier:
|
||||
|
||||
caught = True
|
||||
|
||||
if event.button() == QC.Qt.LeftButton and event.type() == QC.QEvent.MouseButtonPress:
|
||||
|
||||
|
||||
if event.type() == QC.QEvent.MouseButtonPress and event.button() == QC.Qt.LeftButton:
|
||||
|
||||
self.EventDragBegin( event )
|
||||
|
||||
elif event.button() == QC.Qt.LeftButton and event.type() == QC.QEvent.MouseButtonRelease:
|
||||
|
||||
|
||||
elif event.type() == QC.QEvent.MouseButtonRelease and event.button() == QC.Qt.LeftButton:
|
||||
|
||||
self.EventDragEnd( event )
|
||||
|
||||
elif event.buttons() != QC.Qt.NoButton and event.type() == QC.QEvent.MouseMove:
|
||||
|
||||
|
||||
elif event.type() == QC.QEvent.MouseMove and event.buttons() != QC.Qt.NoButton:
|
||||
|
||||
self.EventMouseMove( event )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
|
||||
caught = False
|
||||
|
||||
|
||||
|
@ -4944,7 +5032,6 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
|
|||
|
||||
if modifier == QC.Qt.NoModifier and key in CC.DELETE_KEYS: self._Delete()
|
||||
elif modifier == QC.Qt.ShiftModifier and key in CC.DELETE_KEYS: self._Undelete()
|
||||
elif modifier == QC.Qt.NoModifier and key in ( QC.Qt.Key_Space, ): self._PausePlaySlideshow()
|
||||
elif key in ( QC.Qt.Key_Enter, QC.Qt.Key_Return, QC.Qt.Key_Escape ):
|
||||
|
||||
self._TryToCloseWindow()
|
||||
|
@ -4960,6 +5047,44 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
|
|||
|
||||
|
||||
|
||||
def ProcessApplicationCommand( self, command, canvas_key = None ):
|
||||
|
||||
if canvas_key is not None and canvas_key != self._canvas_key:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
command_processed = True
|
||||
|
||||
command_type = command.GetCommandType()
|
||||
data = command.GetData()
|
||||
|
||||
if command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
|
||||
|
||||
action = data
|
||||
|
||||
if action == 'pause_play_slideshow':
|
||||
|
||||
self._PausePlaySlideshow()
|
||||
|
||||
else:
|
||||
|
||||
command_processed = False
|
||||
|
||||
|
||||
else:
|
||||
|
||||
command_processed = False
|
||||
|
||||
|
||||
if not command_processed:
|
||||
|
||||
command_processed = CanvasMediaListNavigable.ProcessApplicationCommand( self, command )
|
||||
|
||||
|
||||
return command_processed
|
||||
|
||||
|
||||
def wheelEvent( self, event ):
|
||||
|
||||
if self._IShouldCatchShortcutEvent( event = event ):
|
||||
|
@ -5004,7 +5129,9 @@ class MediaContainer( QW.QWidget ):
|
|||
self.setAttribute( QC.Qt.WA_OpaquePaintEvent, True )
|
||||
|
||||
self._media = None
|
||||
self._show_action = None
|
||||
self._show_action = CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE
|
||||
self._start_paused = False
|
||||
self._start_with_embed = False
|
||||
|
||||
self._media_window = None
|
||||
|
||||
|
@ -5016,6 +5143,7 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
self._animation_window = Animation( self )
|
||||
self._animation_bar = AnimationBar( self )
|
||||
self._mpv_window = None
|
||||
self._static_image_window = StaticImage( self )
|
||||
|
||||
self._animation_window.hide()
|
||||
|
@ -5035,6 +5163,18 @@ class MediaContainer( QW.QWidget ):
|
|||
media_window.SetNoneMedia()
|
||||
media_window.hide()
|
||||
|
||||
elif isinstance( media_window, ClientGUIMPV.mpvWidget ):
|
||||
|
||||
self._mpv_window = None
|
||||
|
||||
media_window.SetNoneMedia()
|
||||
|
||||
time.sleep( 0.1 ) # anti crash wew
|
||||
|
||||
media_window.deleteLater()
|
||||
|
||||
time.sleep( 0.1 ) # anti crash wew
|
||||
|
||||
else:
|
||||
|
||||
media_window.deleteLater()
|
||||
|
@ -5054,6 +5194,13 @@ class MediaContainer( QW.QWidget ):
|
|||
old_media_window = self._media_window
|
||||
destroy_old_media_window = True
|
||||
|
||||
if self._show_action == CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV and not ClientGUIMPV.MPV_IS_AVAILABLE:
|
||||
|
||||
self._show_action = CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON
|
||||
|
||||
HydrusData.ShowText( 'MPV is not available!' )
|
||||
|
||||
|
||||
if self._show_action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
|
||||
|
||||
raise Exception( 'This media should not be shown in the media viewer!' )
|
||||
|
@ -5062,40 +5209,9 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
self._media_window = OpenExternallyPanel( self, self._media )
|
||||
|
||||
self._HideAnimationBar()
|
||||
elif self._show_action == CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE:
|
||||
|
||||
else:
|
||||
|
||||
start_paused = self._show_action in ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL_PAUSED, CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED )
|
||||
|
||||
if ShouldHaveAnimationBar( self._media ):
|
||||
|
||||
if isinstance( self._media_window, Animation ):
|
||||
|
||||
destroy_old_media_window = False
|
||||
|
||||
else:
|
||||
|
||||
self._animation_window.show()
|
||||
|
||||
self._media_window = self._animation_window
|
||||
|
||||
|
||||
self._media_window.SetMedia( self._media, start_paused = start_paused )
|
||||
|
||||
|
||||
if ShouldHaveAnimationBar( self._media ):
|
||||
|
||||
self._animation_bar.show()
|
||||
|
||||
self._animation_bar.SetMediaAndWindow( self._media, self._media_window )
|
||||
|
||||
else:
|
||||
|
||||
self._HideAnimationBar()
|
||||
|
||||
|
||||
else:
|
||||
if self._media.IsStaticImage():
|
||||
|
||||
if isinstance( self._media_window, StaticImage ):
|
||||
|
||||
|
@ -5110,8 +5226,52 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
self._media_window.SetMedia( self._media )
|
||||
|
||||
self._HideAnimationBar()
|
||||
else:
|
||||
|
||||
if isinstance( self._media_window, Animation ):
|
||||
|
||||
destroy_old_media_window = False
|
||||
|
||||
else:
|
||||
|
||||
self._animation_window.show()
|
||||
|
||||
self._media_window = self._animation_window
|
||||
|
||||
|
||||
self._media_window.SetMedia( self._media, start_paused = self._start_paused )
|
||||
|
||||
|
||||
elif self._show_action == CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV:
|
||||
|
||||
if isinstance( self._media_window, ClientGUIMPV.mpvWidget ):
|
||||
|
||||
destroy_old_media_window = False
|
||||
|
||||
else:
|
||||
|
||||
if self._mpv_window is None:
|
||||
|
||||
self._mpv_window = ClientGUIMPV.mpvWidget( self )
|
||||
|
||||
|
||||
self._mpv_window.show()
|
||||
|
||||
self._media_window = self._mpv_window
|
||||
|
||||
|
||||
self._mpv_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 )
|
||||
|
||||
else:
|
||||
|
||||
self._HideAnimationBar()
|
||||
|
||||
|
||||
if old_media_window is not None and destroy_old_media_window:
|
||||
|
@ -5137,7 +5297,7 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
( media_width, media_height ) = ( my_width, my_height )
|
||||
|
||||
if ShouldHaveAnimationBar( self._media ) and not is_open_externally:
|
||||
if ShouldHaveAnimationBar( self._media, self._show_action ) and not is_open_externally:
|
||||
|
||||
animated_scanbar_height = HG.client_controller.new_options.GetInteger( 'animated_scanbar_height' )
|
||||
|
||||
|
@ -5145,6 +5305,7 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
self._animation_bar.setFixedSize( QP.TupleToQSize( ( my_width, animated_scanbar_height ) ) )
|
||||
self._animation_bar.move( QP.TupleToQPoint( ( 0, my_height - animated_scanbar_height ) ) )
|
||||
|
||||
|
||||
self._media_window.setFixedSize( QP.TupleToQSize( ( media_width, media_height ) ) )
|
||||
self._media_window.move( QP.TupleToQPoint( ( 0, 0 ) ) )
|
||||
|
@ -5177,36 +5338,43 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
if self._media is not None:
|
||||
|
||||
if ShouldHaveAnimationBar( self._media ):
|
||||
if ShouldHaveAnimationBar( self._media, self._show_action ):
|
||||
|
||||
current_frame_index = self._media_window.CurrentFrame()
|
||||
|
||||
num_frames = self._media.GetNumFrames()
|
||||
|
||||
if direction == 1:
|
||||
if isinstance( self._media_window, Animation ):
|
||||
|
||||
if current_frame_index == num_frames - 1:
|
||||
current_frame_index = self._media_window.CurrentFrame()
|
||||
|
||||
num_frames = self._media.GetNumFrames()
|
||||
|
||||
if direction == 1:
|
||||
|
||||
current_frame_index = 0
|
||||
if current_frame_index == num_frames - 1:
|
||||
|
||||
current_frame_index = 0
|
||||
|
||||
else:
|
||||
|
||||
current_frame_index += 1
|
||||
|
||||
|
||||
else:
|
||||
|
||||
current_frame_index += 1
|
||||
if current_frame_index == 0:
|
||||
|
||||
current_frame_index = num_frames - 1
|
||||
|
||||
else:
|
||||
|
||||
current_frame_index -= 1
|
||||
|
||||
|
||||
|
||||
else:
|
||||
self._media_window.GotoFrame( current_frame_index )
|
||||
|
||||
if current_frame_index == 0:
|
||||
|
||||
current_frame_index = num_frames - 1
|
||||
|
||||
else:
|
||||
|
||||
current_frame_index -= 1
|
||||
|
||||
elif isinstance( self._media_window, ClientGUIMPV.mpvWidget ):
|
||||
|
||||
self._media_window.GotoPreviousOrNextFrame( direction )
|
||||
|
||||
|
||||
self._media_window.GotoFrame( current_frame_index )
|
||||
|
||||
|
||||
|
||||
|
@ -5219,7 +5387,7 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
else:
|
||||
|
||||
if ShouldHaveAnimationBar( self._media ):
|
||||
if ShouldHaveAnimationBar( self._media, self._show_action ):
|
||||
|
||||
animation_bar_mouse_pos = self._animation_bar.mapFromGlobal( QG.QCursor.pos() )
|
||||
|
||||
|
@ -5240,13 +5408,24 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
if self._media is not None:
|
||||
|
||||
if isinstance( self._media_window, Animation ):
|
||||
if isinstance( self._media_window, ( Animation, ClientGUIMPV.mpvWidget ) ):
|
||||
|
||||
self._media_window.Pause()
|
||||
|
||||
|
||||
|
||||
|
||||
def PausePlay( self ):
|
||||
|
||||
if self._media is not None:
|
||||
|
||||
if isinstance( self._media_window, ( Animation, ClientGUIMPV.mpvWidget ) ):
|
||||
|
||||
self._media_window.PausePlay()
|
||||
|
||||
|
||||
|
||||
|
||||
def ReadyToSlideshow( self ):
|
||||
|
||||
if self._media is None:
|
||||
|
@ -5255,7 +5434,7 @@ class MediaContainer( QW.QWidget ):
|
|||
|
||||
else:
|
||||
|
||||
if isinstance( self._media_window, Animation ):
|
||||
if isinstance( self._media_window, ( Animation, ClientGUIMPV.mpvWidget ) ):
|
||||
|
||||
if self._media_window.IsPlaying() and not self._media_window.HasPlayedOnceThrough():
|
||||
|
||||
|
@ -5288,15 +5467,17 @@ class MediaContainer( QW.QWidget ):
|
|||
self._embed_button.show()
|
||||
|
||||
|
||||
def SetMedia( self, media, initial_size, initial_position, show_action ):
|
||||
def SetMedia( self, media, initial_size, initial_position, show_action, start_paused, start_with_embed ):
|
||||
|
||||
self._media = media
|
||||
|
||||
self.hide()
|
||||
|
||||
self._show_action = show_action
|
||||
self._start_paused = start_paused
|
||||
self._start_with_embed = start_with_embed
|
||||
|
||||
if self._show_action in ( CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED ):
|
||||
if self._start_with_embed:
|
||||
|
||||
self.SetEmbedButton()
|
||||
|
||||
|
@ -5313,6 +5494,7 @@ class MediaContainer( QW.QWidget ):
|
|||
self._SizeAndPositionChildren()
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def SetNoneMedia( self ):
|
||||
|
||||
|
@ -5358,16 +5540,6 @@ class EmbedButton( QW.QWidget ):
|
|||
|
||||
if self._thumbnail_qt_pixmap is not None:
|
||||
|
||||
if ShouldHaveAnimationBar( self._media ):
|
||||
|
||||
# animations will have the animation bar space underneath in this case, so colour it in
|
||||
painter.setBackground( QG.QBrush( QP.GetSystemColour( QG.QPalette.Button ) ) )
|
||||
|
||||
animated_scanbar_height = HG.client_controller.new_options.GetInteger( 'animated_scanbar_height' )
|
||||
|
||||
painter.drawRect( 0, y - animated_scanbar_height, x, animated_scanbar_height )
|
||||
|
||||
|
||||
( thumb_width, thumb_height ) = self._thumbnail_qt_pixmap.size().toTuple()
|
||||
|
||||
scale = x / thumb_width
|
||||
|
@ -5458,8 +5630,6 @@ class OpenExternallyPanel( QW.QWidget ):
|
|||
|
||||
self._new_options = HG.client_controller.new_options
|
||||
|
||||
QP.SetBackgroundColour( self, self._new_options.GetColour(CC.COLOUR_MEDIA_BACKGROUND) )
|
||||
|
||||
self._media = media
|
||||
|
||||
vbox = QP.VBoxLayout()
|
||||
|
@ -5480,10 +5650,10 @@ class OpenExternallyPanel( QW.QWidget ):
|
|||
m_text = HC.mime_string_lookup[ media.GetMime() ]
|
||||
|
||||
button = QW.QPushButton( 'open ' + m_text + ' externally', self )
|
||||
button.setFixedSize( QP.TupleToQSize( OPEN_EXTERNALLY_BUTTON_SIZE ) )
|
||||
|
||||
button.setFocusPolicy( QC.Qt.NoFocus )
|
||||
|
||||
QP.AddToLayout( vbox, button, CC.FLAGS_CENTER )
|
||||
QP.AddToLayout( vbox, button, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
@ -5504,6 +5674,19 @@ class OpenExternallyPanel( QW.QWidget ):
|
|||
|
||||
|
||||
|
||||
def paintEvent( self, event ):
|
||||
|
||||
# have to manually repaint background because of parent WA_OpaquePaintEvent
|
||||
|
||||
painter = QG.QPainter( self )
|
||||
|
||||
background_colour = self._new_options.GetColour( CC.COLOUR_MEDIA_BACKGROUND )
|
||||
|
||||
painter.setBackground( QG.QBrush( background_colour ) )
|
||||
|
||||
painter.eraseRect( painter.viewport() )
|
||||
|
||||
|
||||
def LaunchFile( self ):
|
||||
|
||||
hash = self._media.GetHash()
|
||||
|
|
|
@ -971,18 +971,13 @@ class ListBox( QW.QScrollArea ):
|
|||
|
||||
if isinstance( term, ClientSearch.Predicate ):
|
||||
|
||||
predicate_type = term.GetType()
|
||||
include_predicates.append( term )
|
||||
|
||||
if predicate_type in ( HC.PREDICATE_TYPE_TAG, HC.PREDICATE_TYPE_NAMESPACE, HC.PREDICATE_TYPE_WILDCARD ):
|
||||
possible_inverse = term.GetInverseCopy()
|
||||
|
||||
if possible_inverse is not None:
|
||||
|
||||
value = term.GetValue()
|
||||
|
||||
include_predicates.append( ClientSearch.Predicate( predicate_type, value ) )
|
||||
exclude_predicates.append( ClientSearch.Predicate( predicate_type, value, False ) )
|
||||
|
||||
else:
|
||||
|
||||
include_predicates.append( term )
|
||||
exclude_predicates.append( possible_inverse )
|
||||
|
||||
|
||||
else:
|
||||
|
@ -1600,9 +1595,7 @@ class ListBoxTags( ListBox ):
|
|||
|
||||
def _GetAllTagsForClipboard( self, with_counts = False ):
|
||||
|
||||
texts = list( self._terms_to_texts.values() )
|
||||
|
||||
texts.sort()
|
||||
texts = [ self._terms_to_texts[ term ] for term in self._ordered_terms ]
|
||||
|
||||
return texts
|
||||
|
||||
|
@ -1693,7 +1686,9 @@ class ListBoxTags( ListBox ):
|
|||
|
||||
page_name = ', '.join( s )
|
||||
|
||||
HG.client_controller.pub( 'new_page_query', CC.LOCAL_FILE_SERVICE_KEY, initial_predicates = predicates, page_name = page_name )
|
||||
activate_window = HG.client_controller.new_options.GetBoolean( 'activate_window_on_tag_search_page_activation' )
|
||||
|
||||
HG.client_controller.pub( 'new_page_query', CC.LOCAL_FILE_SERVICE_KEY, initial_predicates = predicates, page_name = page_name, activate_window = activate_window )
|
||||
|
||||
|
||||
|
||||
|
@ -1752,13 +1747,20 @@ class ListBoxTags( ListBox ):
|
|||
|
||||
text = os.linesep.join( texts )
|
||||
|
||||
elif command == 'copy_all_tags':
|
||||
elif command in ( 'copy_all_tags', 'copy_all_tags_with_counts' ):
|
||||
|
||||
text = os.linesep.join( self._GetAllTagsForClipboard( with_counts = False ) )
|
||||
if command == 'copy_all_tags':
|
||||
|
||||
with_counts = False
|
||||
|
||||
else:
|
||||
|
||||
with_counts = True
|
||||
|
||||
|
||||
elif command == 'copy_all_tags_with_counts':
|
||||
texts = self._GetAllTagsForClipboard( with_counts = with_counts )
|
||||
|
||||
text = os.linesep.join( self._GetAllTagsForClipboard( with_counts = True ) )
|
||||
text = os.linesep.join( texts )
|
||||
|
||||
|
||||
HG.client_controller.pub( 'clipboard', 'text', text )
|
||||
|
@ -1884,24 +1886,35 @@ class ListBoxTags( ListBox ):
|
|||
|
||||
|
||||
|
||||
siblings = []
|
||||
|
||||
if len( selected_tags ) == 1:
|
||||
|
||||
( selected_tag, ) = selected_tags
|
||||
|
||||
( selected_namespace, selected_subtag ) = HydrusTags.SplitTag( selected_tag )
|
||||
|
||||
siblings = set( HG.client_controller.tag_siblings_manager.GetAllSiblings( CC.COMBINED_TAG_SERVICE_KEY, selected_tag ) )
|
||||
|
||||
siblings.discard( selected_tag )
|
||||
siblings.discard( selected_subtag )
|
||||
|
||||
siblings = list( siblings )
|
||||
|
||||
HydrusTags.SortNumericTags( siblings )
|
||||
|
||||
|
||||
if len( self._selected_terms ) == 1:
|
||||
|
||||
( term, ) = self._selected_terms
|
||||
|
||||
if isinstance( term, ClientSearch.Predicate ):
|
||||
|
||||
if term.GetType() == HC.PREDICATE_TYPE_TAG:
|
||||
|
||||
selection_string = '"' + term.GetValue() + '"'
|
||||
|
||||
else:
|
||||
|
||||
selection_string = '"' + term.ToString( with_count = False ) + '"'
|
||||
|
||||
selection_string = term.ToString( with_count = False )
|
||||
|
||||
else:
|
||||
|
||||
selection_string = '"' + str( term ) + '"'
|
||||
selection_string = str( term )
|
||||
|
||||
|
||||
else:
|
||||
|
@ -1909,47 +1922,68 @@ class ListBoxTags( ListBox ):
|
|||
selection_string = 'selected'
|
||||
|
||||
|
||||
if self._get_current_predicates_callable is not None:
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, selection_string, 'Copy the selected predicates to your clipboard.', self._ProcessMenuCopyEvent, 'copy_terms' )
|
||||
|
||||
if len( self._selected_terms ) == 1:
|
||||
|
||||
current_predicates = self._get_current_predicates_callable()
|
||||
( namespace, subtag ) = HydrusTags.SplitTag( selection_string )
|
||||
|
||||
if current_predicates is not None:
|
||||
if namespace != '':
|
||||
|
||||
( include_predicates, exclude_predicates ) = self._GetSelectedIncludeExcludePredicates()
|
||||
sub_selection_string = subtag
|
||||
|
||||
if True in ( include_predicate in current_predicates for include_predicate in include_predicates ):
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'discard ' + selection_string + ' from current search', 'Remove the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'remove_include_predicates' )
|
||||
|
||||
|
||||
if True in ( include_predicate not in current_predicates for include_predicate in include_predicates ):
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'require ' + selection_string + ' for current search', 'Add the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'add_include_predicates' )
|
||||
|
||||
|
||||
if True in ( exclude_predicate in current_predicates for exclude_predicate in exclude_predicates ):
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'permit ' + selection_string + ' for current search', 'Stop disallowing the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'remove_exclude_predicates' )
|
||||
|
||||
|
||||
if True in ( exclude_predicate not in current_predicates for exclude_predicate in exclude_predicates ):
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'exclude ' + selection_string + ' from current search', 'Disallow the selected predicates for the current search.', self._ProcessMenuPredicateEvent, 'add_exclude_predicates' )
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, sub_selection_string, 'Copy the selected sub-predicate to your clipboard.', self._ProcessMenuCopyEvent, 'copy_sub_terms' )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, 'selected subtags', 'Copy the selected sub-predicates to your clipboard.', self._ProcessMenuCopyEvent, 'copy_sub_terms' )
|
||||
|
||||
|
||||
ClientGUIMenus.AppendSeparator( menu )
|
||||
|
||||
if self.can_spawn_new_windows:
|
||||
if len( siblings ) > 0:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'open a new search page for ' + selection_string, 'Open a new search page starting with the selected predicates.', self._NewSearchPage )
|
||||
siblings_menu = QW.QMenu( copy_menu )
|
||||
|
||||
if len( self._selected_terms ) > 1:
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'open new search pages for each in selection', 'Open one new search page for each selected predicate.', self._NewSearchPageForEach )
|
||||
for sibling in siblings:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( siblings_menu, sibling, 'Copy the selected tag sibling to your clipboard.', HG.client_controller.pub, 'clipboard', 'text', sibling )
|
||||
|
||||
if len( self._selected_terms ) == 1:
|
||||
|
||||
( sibling_namespace, sibling_subtag ) = HydrusTags.SplitTag( sibling )
|
||||
|
||||
if sibling_namespace != '':
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( siblings_menu, sibling_subtag, 'Copy the selected sibling subtag to your clipboard.', HG.client_controller.pub, 'clipboard', 'text', sibling_subtag )
|
||||
|
||||
|
||||
|
||||
|
||||
ClientGUIMenus.AppendMenu( copy_menu, siblings_menu, 'siblings' )
|
||||
|
||||
|
||||
|
||||
if len( self._ordered_terms ) > len( self._selected_terms ):
|
||||
|
||||
ClientGUIMenus.AppendSeparator( copy_menu )
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, 'all tags', 'Copy all the predicates in this list to your clipboard.', self._ProcessMenuCopyEvent, 'copy_all_tags' )
|
||||
|
||||
if self.has_counts:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, 'all tags with counts', 'Copy all the predicates in this list, with their counts, to your clipboard.', self._ProcessMenuCopyEvent, 'copy_all_tags_with_counts' )
|
||||
|
||||
|
||||
|
||||
if len( self._ordered_terms ) > 0:
|
||||
|
||||
ClientGUIMenus.AppendMenu( menu, copy_menu, 'copy' )
|
||||
|
||||
|
||||
if len( self._selected_terms ) > 0:
|
||||
|
||||
if len( selected_tags ) > 0:
|
||||
|
||||
ClientGUIMenus.AppendSeparator( menu )
|
||||
|
||||
if self._page_key is not None:
|
||||
|
||||
select_menu = QW.QMenu( menu )
|
||||
|
@ -1993,49 +2027,60 @@ class ListBoxTags( ListBox ):
|
|||
ClientGUIMenus.AppendMenu( menu, select_menu, 'select' )
|
||||
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, selection_string, 'Copy the selected predicates to your clipboard.', self._ProcessMenuCopyEvent, 'copy_terms' )
|
||||
|
||||
if self.can_spawn_new_windows:
|
||||
|
||||
if len( self._selected_terms ) == 1:
|
||||
ClientGUIMenus.AppendSeparator( menu )
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'open a new search page for ' + selection_string, 'Open a new search page starting with the selected predicates.', self._NewSearchPage )
|
||||
|
||||
if len( self._selected_terms ) > 1:
|
||||
|
||||
( namespace, subtag ) = HydrusTags.SplitTag( selection_string )
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'open new search pages for each in selection', 'Open one new search page for each selected predicate.', self._NewSearchPageForEach )
|
||||
|
||||
if namespace != '':
|
||||
|
||||
|
||||
if self._get_current_predicates_callable is not None:
|
||||
|
||||
ClientGUIMenus.AppendSeparator( menu )
|
||||
|
||||
current_predicates = self._get_current_predicates_callable()
|
||||
|
||||
if current_predicates is not None:
|
||||
|
||||
( include_predicates, exclude_predicates ) = self._GetSelectedIncludeExcludePredicates()
|
||||
|
||||
if True in ( include_predicate in current_predicates for include_predicate in include_predicates ):
|
||||
|
||||
sub_selection_string = '"' + subtag
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, sub_selection_string, 'Copy the selected sub-predicates to your clipboard.', self._ProcessMenuCopyEvent, 'copy_sub_terms' )
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'discard ' + selection_string + ' from current search', 'Remove the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'remove_include_predicates' )
|
||||
|
||||
|
||||
else:
|
||||
if True in ( include_predicate not in current_predicates for include_predicate in include_predicates ):
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'require ' + selection_string + ' for current search', 'Add the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'add_include_predicates' )
|
||||
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, 'selected subtags', 'Copy the selected sub-predicates to your clipboard.', self._ProcessMenuCopyEvent, 'copy_sub_terms' )
|
||||
if True in ( exclude_predicate in current_predicates for exclude_predicate in exclude_predicates ):
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'permit ' + selection_string + ' for current search', 'Stop disallowing the selected predicates from the current search.', self._ProcessMenuPredicateEvent, 'remove_exclude_predicates' )
|
||||
|
||||
|
||||
if True in ( exclude_predicate not in current_predicates for exclude_predicate in exclude_predicates ):
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( menu, 'exclude ' + selection_string + ' from current search', 'Disallow the selected predicates for the current search.', self._ProcessMenuPredicateEvent, 'add_exclude_predicates' )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if len( self._ordered_terms ) > len( self._selected_terms ):
|
||||
|
||||
ClientGUIMenus.AppendSeparator( copy_menu )
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, 'all tags', 'Copy all the predicates in this list to your clipboard.', self._ProcessMenuCopyEvent, 'copy_all_tags' )
|
||||
|
||||
if self.has_counts:
|
||||
|
||||
ClientGUIMenus.AppendMenuItem( copy_menu, 'all tags with counts', 'Copy all the predicates in this list, with their counts, to your clipboard.', self._ProcessMenuCopyEvent, 'copy_all_tags_with_counts' )
|
||||
|
||||
|
||||
|
||||
if len( self._ordered_terms ) > 0:
|
||||
|
||||
ClientGUIMenus.AppendMenu( menu, copy_menu, 'copy' )
|
||||
|
||||
|
||||
if len( selected_tags ) == 1:
|
||||
|
||||
( tag, ) = selected_tags
|
||||
|
||||
if self._tag_display_type in ( ClientTags.TAG_DISPLAY_SINGLE_MEDIA, ClientTags.TAG_DISPLAY_SELECTION_LIST ):
|
||||
|
||||
ClientGUIMenus.AppendSeparator( menu )
|
||||
|
||||
( namespace, subtag ) = HydrusTags.SplitTag( tag )
|
||||
|
||||
hide_menu = QW.QMenu( menu )
|
||||
|
@ -2164,7 +2209,7 @@ class ListBoxTagsPredicates( ListBoxTags ):
|
|||
|
||||
def _GetAllTagsForClipboard( self, with_counts = False ):
|
||||
|
||||
return [ term.ToString( with_counts ) for term in self._terms ]
|
||||
return [ term.ToString( with_counts ) for term in self._ordered_terms ]
|
||||
|
||||
|
||||
def _GetMutuallyExclusivePredicates( self, predicate ):
|
||||
|
@ -2969,7 +3014,7 @@ class ListBoxTagsSelection( ListBoxTags ):
|
|||
|
||||
else:
|
||||
|
||||
return self._ordered_terms
|
||||
return list( self._ordered_terms )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
from . import HydrusConstants as HC
|
||||
from . import HydrusData
|
||||
from . import HydrusGlobals as HG
|
||||
from . import HydrusPaths
|
||||
from qtpy import QtCore as QC
|
||||
from qtpy import QtWidgets as QW
|
||||
from qtpy import QtGui as QG
|
||||
import locale
|
||||
|
||||
try:
|
||||
|
||||
import mpv
|
||||
|
||||
MPV_IS_AVAILABLE = True
|
||||
|
||||
except:
|
||||
|
||||
MPV_IS_AVAILABLE = False
|
||||
|
||||
def GetClientAPIVersionString():
|
||||
|
||||
try:
|
||||
|
||||
( major, minor ) = mpv._mpv_client_api_version()
|
||||
|
||||
return '{}.{}'.format( major, minor )
|
||||
|
||||
except:
|
||||
|
||||
return 'unknown'
|
||||
|
||||
|
||||
#Not sure how well this works with hardware acceleration. This just renders to a QWidget. In my tests it seems fine, even with vdpau video out, but I'm not 100% sure it actually uses hardware acceleration.
|
||||
#Here is an example on how to render into a QOpenGLWidget instead: https://gist.github.com/cosven/b313de2acce1b7e15afda263779c0afc
|
||||
class mpvWidget( QW.QWidget ):
|
||||
|
||||
def __init__( self, parent ):
|
||||
|
||||
QW.QWidget.__init__( self, parent )
|
||||
|
||||
# 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' )
|
||||
|
||||
self.setAttribute( QC.Qt.WA_DontCreateNativeAncestors )
|
||||
self.setAttribute( QC.Qt.WA_NativeWindow )
|
||||
|
||||
self._player = mpv.MPV( wid = str( int( self.winId() ) ), log_handler = print, loglevel = 'fatal' )
|
||||
|
||||
self._player.profile = 'gpu-hq'
|
||||
|
||||
# hydev notes on OSC:
|
||||
# OSC is by default off, default input bindings are by default off
|
||||
# difficult to get this to intercept mouse/key events naturally, so you have to pipe them to the window with 'command', but this is not excellent
|
||||
# general recommendation when using libmpv is to just implement your own stuff anyway, so let's do that for prototype
|
||||
|
||||
#self._player[ 'input-default-bindings' ] = True
|
||||
|
||||
#To load an existing config file (by default it doesn't load the user/global config like standalone mpv does):
|
||||
#mpv._mpv_load_config_file(self._player.handle,'blah/mpv.conf'.encode('utf-8') )
|
||||
|
||||
#self._player.osc = True #Set to enable the mpv UI. Requires that mpv captures mouse/key events, otherwise it won't work.
|
||||
|
||||
self._player.loop = True
|
||||
|
||||
self._player.force_window = True
|
||||
|
||||
#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
|
||||
|
||||
self._media = None
|
||||
|
||||
self._has_played_once_through = False
|
||||
|
||||
self.destroyed.connect( self._player.terminate )
|
||||
|
||||
|
||||
def GetAnimationBarStatus( self ):
|
||||
|
||||
buffer_indices = None
|
||||
|
||||
if self._media is None:
|
||||
|
||||
current_frame_index = 0
|
||||
current_timestamp_ms = 0
|
||||
paused = True
|
||||
|
||||
else:
|
||||
|
||||
current_timestamp_s = self._player.time_pos
|
||||
|
||||
if current_timestamp_s is None:
|
||||
|
||||
current_frame_index = 0
|
||||
current_timestamp_ms = None
|
||||
|
||||
else:
|
||||
|
||||
current_timestamp_ms = current_timestamp_s * 1000
|
||||
|
||||
num_frames = self._media.GetNumFrames()
|
||||
|
||||
if num_frames is None:
|
||||
|
||||
current_frame_index = 0
|
||||
|
||||
else:
|
||||
|
||||
current_frame_index = int( round( ( current_timestamp_ms / self._media.GetDuration() ) * num_frames ) )
|
||||
|
||||
|
||||
|
||||
paused = self._player.pause
|
||||
|
||||
|
||||
return ( current_frame_index, current_timestamp_ms, paused, buffer_indices )
|
||||
|
||||
|
||||
def GotoPreviousOrNextFrame( self, direction ):
|
||||
|
||||
command = 'frame-step'
|
||||
|
||||
if direction == 1:
|
||||
|
||||
command = 'frame-step'
|
||||
|
||||
elif direction == -1:
|
||||
|
||||
command = 'frame-back-step'
|
||||
|
||||
|
||||
self._player.command( command )
|
||||
|
||||
|
||||
def Seek( self, time_index_ms ):
|
||||
|
||||
time_index_s = time_index_ms / 1000
|
||||
|
||||
self._player.seek( time_index_s, reference = 'absolute' )
|
||||
|
||||
|
||||
def HasPlayedOnceThrough( self ):
|
||||
|
||||
return self._has_played_once_through
|
||||
|
||||
|
||||
def IsPlaying( self ):
|
||||
|
||||
return not self._player.pause
|
||||
|
||||
|
||||
def mouseDoubleClickEvent( self, event ):
|
||||
|
||||
if not ( event.modifiers() & ( QC.Qt.ShiftModifier | QC.Qt.ControlModifier | QC.Qt.AltModifier) ):
|
||||
|
||||
if event.button() == QC.Qt.LeftButton:
|
||||
|
||||
self.Pause()
|
||||
|
||||
hash = self._media.GetHash()
|
||||
mime = self._media.GetMime()
|
||||
|
||||
client_files_manager = HG.client_controller.client_files_manager
|
||||
|
||||
path = client_files_manager.GetFilePath( hash, mime )
|
||||
|
||||
new_options = HG.client_controller.new_options
|
||||
|
||||
launch_path = new_options.GetMimeLaunch( mime )
|
||||
|
||||
HydrusPaths.LaunchFile( path, launch_path )
|
||||
|
||||
event.accept()
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
event.ignore()
|
||||
|
||||
|
||||
#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()
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
def Pause( self ):
|
||||
|
||||
self._player.pause = True
|
||||
|
||||
|
||||
def PausePlay( self ):
|
||||
|
||||
self._player.pause = not self._player.pause
|
||||
|
||||
|
||||
def Play( self ):
|
||||
|
||||
self._player.pause = False
|
||||
|
||||
|
||||
def SetMedia( self, media, start_paused = False ):
|
||||
|
||||
self._media = media
|
||||
|
||||
if self._media is None:
|
||||
|
||||
self._player.pause = True
|
||||
|
||||
self._player.command( 'playlist-remove', 'current' )
|
||||
|
||||
else:
|
||||
|
||||
hash = self._media.GetHash()
|
||||
mime = self._media.GetMime()
|
||||
|
||||
client_files_manager = HG.client_controller.client_files_manager
|
||||
|
||||
path = client_files_manager.GetFilePath( hash, mime )
|
||||
|
||||
self._has_played_once_through = False
|
||||
|
||||
self._player.visibility = 'always'
|
||||
|
||||
volume = 70
|
||||
mute = False
|
||||
|
||||
self._player.pause = True
|
||||
|
||||
self._player.loadfile( path )
|
||||
|
||||
self._player.pause = start_paused
|
||||
self._player.volume = volume
|
||||
self._player.mute = mute
|
||||
|
||||
|
||||
|
||||
def SetNoneMedia( self ):
|
||||
|
||||
self.SetMedia( None )
|
||||
|
||||
|
|
@ -1568,12 +1568,12 @@ class ManagementPanelImporterMultipleGallery( ManagementPanelImporter ):
|
|||
self._gallery_importers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.clear_highlight, self._ClearExistingHighlightAndPanel, tooltip = 'clear highlight', enabled_check_func = self._CanClearHighlight )
|
||||
self._gallery_importers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.file_pause, self._PausePlayFiles, tooltip = 'pause/play files', enabled_only_on_selection = True )
|
||||
self._gallery_importers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.gallery_pause, self._PausePlayGallery, tooltip = 'pause/play search', enabled_only_on_selection = True )
|
||||
self._gallery_importers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.trash, self._RemoveGalleryImports, tooltip = 'remove selected', enabled_only_on_selection = True )
|
||||
|
||||
self._gallery_importers_listctrl_panel.NewButtonRow()
|
||||
|
||||
self._gallery_importers_listctrl_panel.AddButton( 'retry failed', self._RetryFailed, enabled_check_func = self._CanRetryFailed )
|
||||
self._gallery_importers_listctrl_panel.AddButton( 'retry ignored', self._RetryIgnored, enabled_check_func = self._CanRetryIgnored )
|
||||
self._gallery_importers_listctrl_panel.AddButton( 'remove selected', self._RemoveGalleryImports, enabled_only_on_selection = True )
|
||||
|
||||
self._gallery_importers_listctrl_panel.NewButtonRow()
|
||||
|
||||
|
@ -2269,13 +2269,13 @@ class ManagementPanelImporterMultipleWatcher( ManagementPanelImporter ):
|
|||
self._watchers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.clear_highlight, self._ClearExistingHighlightAndPanel, tooltip = 'clear highlight', enabled_check_func = self._CanClearHighlight )
|
||||
self._watchers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.file_pause, self._PausePlayFiles, tooltip = 'pause/play files', enabled_only_on_selection = True )
|
||||
self._watchers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.gallery_pause, self._PausePlayChecking, tooltip = 'pause/play checking', enabled_only_on_selection = True )
|
||||
self._watchers_listctrl_panel.AddBitmapButton( CC.GlobalPixmaps.trash, self._RemoveWatchers, tooltip = 'remove selected', enabled_only_on_selection = True )
|
||||
self._watchers_listctrl_panel.AddButton( 'check now', self._CheckNow, enabled_only_on_selection = True )
|
||||
|
||||
self._watchers_listctrl_panel.NewButtonRow()
|
||||
|
||||
self._watchers_listctrl_panel.AddButton( 'retry failed', self._RetryFailed, enabled_check_func = self._CanRetryFailed )
|
||||
self._watchers_listctrl_panel.AddButton( 'retry ignored', self._RetryIgnored, enabled_check_func = self._CanRetryIgnored )
|
||||
self._watchers_listctrl_panel.AddButton( 'remove', self._RemoveWatchers, enabled_only_on_selection = True )
|
||||
|
||||
self._watchers_listctrl_panel.NewButtonRow()
|
||||
|
||||
|
|
|
@ -933,7 +933,7 @@ class MediaPanel( ClientMedia.ListeningMediaList, QW.QScrollArea ):
|
|||
|
||||
new_options = HG.client_controller.new_options
|
||||
|
||||
media_show_action = new_options.GetMediaShowAction( display_media.GetMime() )
|
||||
( media_show_action, media_start_paused, media_start_with_embed ) = new_options.GetMediaShowAction( display_media.GetMime() )
|
||||
|
||||
if media_show_action == CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY:
|
||||
|
||||
|
@ -3433,7 +3433,14 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
self._parent._drag_init_coordinates = QG.QCursor.pos()
|
||||
|
||||
self._parent._HitMedia( self._parent._GetThumbnailUnderMouse( event ), event.modifiers() & QC.Qt.ControlModifier, event.modifiers() & QC.Qt.ShiftModifier )
|
||||
thumb = self._parent._GetThumbnailUnderMouse( event )
|
||||
|
||||
right_on_whitespace = event.button() == QC.Qt.RightButton and thumb is None
|
||||
|
||||
if not right_on_whitespace:
|
||||
|
||||
self._parent._HitMedia( thumb, event.modifiers() & QC.Qt.ControlModifier, event.modifiers() & QC.Qt.ShiftModifier )
|
||||
|
||||
|
||||
# this specifically does not scroll to media, as for clicking (esp. double-clicking attempts), the scroll can be jarring
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ from qtpy import QtCore as QC
|
|||
from qtpy import QtWidgets as QW
|
||||
from qtpy import QtGui as QG
|
||||
from . import QtPorting as QP
|
||||
from . import QtPorting as QP
|
||||
|
||||
def AppendMenu( menu, submenu, label ):
|
||||
|
||||
|
@ -112,6 +111,8 @@ def AppendMenuLabel( menu, label, description = '' ):
|
|||
|
||||
menu.addAction( menu_item )
|
||||
|
||||
BindMenuItem( menu_item, HG.client_controller.pub, 'clipboard', 'text', label )
|
||||
|
||||
return menu_item
|
||||
|
||||
def AppendSeparator( menu ):
|
||||
|
|
|
@ -30,17 +30,24 @@ class OptionsPanelMimes( OptionsPanel ):
|
|||
|
||||
OptionsPanel.__init__( self, parent )
|
||||
|
||||
self._selectable_mimes = set( selectable_mimes )
|
||||
|
||||
self._mimes_to_checkboxes = {}
|
||||
self._mime_groups_to_checkboxes = {}
|
||||
self._mime_groups_to_values = {}
|
||||
|
||||
mime_groups = [ HC.APPLICATIONS, HC.AUDIO, HC.IMAGES, HC.VIDEO ]
|
||||
mime_groups = []
|
||||
|
||||
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 ) )
|
||||
|
||||
mime_groups_to_mimes = collections.defaultdict( list )
|
||||
|
||||
for mime in selectable_mimes:
|
||||
for mime in self._selectable_mimes:
|
||||
|
||||
for mime_group in mime_groups:
|
||||
for ( mime_group, mime_group_type ) in mime_groups:
|
||||
|
||||
if mime in mime_group:
|
||||
|
||||
|
@ -55,11 +62,11 @@ class OptionsPanelMimes( OptionsPanel ):
|
|||
|
||||
gridbox.setColumnStretch( 1, 1 )
|
||||
|
||||
for mime_group in mime_groups:
|
||||
for ( mime_group, mime_group_type ) in mime_groups:
|
||||
|
||||
mimes = mime_groups_to_mimes[ mime_group ]
|
||||
|
||||
mg_checkbox = QW.QCheckBox( HC.mime_string_lookup[mime_group], self )
|
||||
mg_checkbox = QW.QCheckBox( HC.mime_string_lookup[ mime_group_type ], self )
|
||||
mg_checkbox.clicked.connect( self.EventMimeGroupCheckbox )
|
||||
|
||||
self._mime_groups_to_checkboxes[ mime_group ] = mg_checkbox
|
||||
|
@ -71,9 +78,11 @@ class OptionsPanelMimes( OptionsPanel ):
|
|||
|
||||
for mime in mimes:
|
||||
|
||||
m_checkbox = QW.QCheckBox( HC.mime_string_lookup[mime], self )
|
||||
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 )
|
||||
|
@ -84,12 +93,38 @@ class OptionsPanelMimes( OptionsPanel ):
|
|||
|
||||
self.setLayout( gridbox )
|
||||
|
||||
self._HideShowSubCheckboxes()
|
||||
|
||||
|
||||
def _HideShowSubCheckboxes( self ):
|
||||
|
||||
pass
|
||||
|
||||
# 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.
|
||||
|
||||
'''
|
||||
for ( mime_group, all_true ) in self._mime_groups_to_values.items():
|
||||
|
||||
should_show = not all_true
|
||||
|
||||
for mime in mime_group:
|
||||
|
||||
if mime not in self._selectable_mimes:
|
||||
|
||||
continue
|
||||
|
||||
|
||||
self._mimes_to_checkboxes[ mime ].setVisible( should_show )
|
||||
|
||||
|
||||
'''
|
||||
|
||||
def _UpdateMimeGroupCheckboxes( self ):
|
||||
|
||||
for ( mime_group, mg_checkbox ) in list(self._mime_groups_to_checkboxes.items()):
|
||||
for ( mime_group, mg_checkbox ) in self._mime_groups_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]
|
||||
respective_checkbox_values = [ m_checkbox.isChecked() for ( mime, m_checkbox ) in list( self._mimes_to_checkboxes.items() ) if mime in mime_group ]
|
||||
|
||||
all_true = False not in respective_checkbox_values
|
||||
|
||||
|
@ -97,6 +132,8 @@ class OptionsPanelMimes( OptionsPanel ):
|
|||
self._mime_groups_to_values[ mime_group ] = all_true
|
||||
|
||||
|
||||
self._HideShowSubCheckboxes()
|
||||
|
||||
|
||||
def EventMimeCheckbox( self ):
|
||||
|
||||
|
@ -127,10 +164,12 @@ class OptionsPanelMimes( OptionsPanel ):
|
|||
|
||||
|
||||
|
||||
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() == True ] )
|
||||
|
||||
return mimes
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ class DialogPageChooser( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self._controller = controller
|
||||
|
||||
self._action_picked = False
|
||||
|
||||
self._result = None
|
||||
|
||||
# spawn and add to layout in this order, so focus precipitates from the graphical top
|
||||
|
@ -127,10 +129,6 @@ class DialogPageChooser( ClientGUIDialogs.Dialog ):
|
|||
self._button_2.clicked.connect( lambda: self._HitButton( 2 ) )
|
||||
self._button_3.clicked.connect( lambda: self._HitButton( 3 ) )
|
||||
|
||||
#
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _AddEntry( self, button, entry ):
|
||||
|
||||
|
@ -242,6 +240,8 @@ class DialogPageChooser( ClientGUIDialogs.Dialog ):
|
|||
self._result = ( 'page', ClientGUIManagement.CreateManagementControllerPetitions( petition_service_key ) )
|
||||
|
||||
|
||||
self._action_picked = True
|
||||
|
||||
self.done( QW.QDialog.Accepted )
|
||||
|
||||
|
||||
|
@ -335,6 +335,20 @@ class DialogPageChooser( ClientGUIDialogs.Dialog ):
|
|||
|
||||
|
||||
|
||||
def event( self, event ):
|
||||
|
||||
if event.type() == QC.QEvent.WindowDeactivate and not self._action_picked:
|
||||
|
||||
self.done( QW.QDialog.Rejected )
|
||||
|
||||
return True
|
||||
|
||||
else:
|
||||
|
||||
return ClientGUIDialogs.Dialog.event( self, event )
|
||||
|
||||
|
||||
|
||||
def keyPressEvent( self, event ):
|
||||
|
||||
id = None
|
||||
|
@ -354,6 +368,20 @@ class DialogPageChooser( ClientGUIDialogs.Dialog ):
|
|||
elif key == QC.Qt.Key_7 and modifier == QC.Qt.KeypadModifier: id = 7
|
||||
elif key == QC.Qt.Key_8 and modifier == QC.Qt.KeypadModifier: id = 8
|
||||
elif key == QC.Qt.Key_9 and modifier == QC.Qt.KeypadModifier: id = 9
|
||||
elif key in ( QC.Qt.Key_Enter, QC.Qt.Key_Return ):
|
||||
|
||||
# get the 'first', scanning from top-left
|
||||
|
||||
for possible_id in ( 7, 8, 9, 4, 5, 6, 1, 2, 3 ):
|
||||
|
||||
if possible_id in self._command_dict:
|
||||
|
||||
id = possible_id
|
||||
|
||||
break
|
||||
|
||||
|
||||
|
||||
elif key == QC.Qt.Key_Escape:
|
||||
|
||||
self.done( QW.QDialog.Rejected )
|
||||
|
@ -885,6 +913,7 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
|
|||
|
||||
QP.TabWidgetWithDnD.__init__( self, parent )
|
||||
|
||||
# this is disabled for now because it seems borked in Qt
|
||||
if controller.new_options.GetBoolean( 'notebook_tabs_on_left' ):
|
||||
|
||||
self.setTabPosition( QW.QTabWidget.West )
|
||||
|
|
|
@ -4067,6 +4067,8 @@ class ScriptManagementControl( QW.QWidget ):
|
|||
self._status = ClientGUICommon.BetterStaticText( main_panel )
|
||||
self._gauge = ClientGUICommon.Gauge( main_panel )
|
||||
|
||||
self._status.setWordWrap( True )
|
||||
|
||||
self._link_button = ClientGUICommon.BetterBitmapButton( main_panel, CC.GlobalPixmaps.link, self.LinkButton )
|
||||
self._link_button.setToolTip( 'urls found by the script' )
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ from . import ClientGUIMenus
|
|||
from . import ClientGUIScrolledPanels
|
||||
from . import ClientGUIFileSeedCache
|
||||
from . import ClientGUIGallerySeedLog
|
||||
from . import ClientGUIMPV
|
||||
from . import ClientGUITags
|
||||
from . import ClientGUITime
|
||||
from . import ClientGUITopLevelWindows
|
||||
|
@ -844,10 +845,20 @@ class EditDeleteFilesPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
|
||||
if suggested_file_service_key is None:
|
||||
|
||||
suggested_file_service_key = CC.LOCAL_FILE_SERVICE_KEY
|
||||
|
||||
|
||||
if suggested_file_service_key == CC.LOCAL_FILE_SERVICE_KEY:
|
||||
|
||||
possible_file_service_keys.append( CC.LOCAL_FILE_SERVICE_KEY )
|
||||
possible_file_service_keys.append( CC.TRASH_SERVICE_KEY )
|
||||
possible_file_service_keys.append( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
|
||||
|
||||
elif suggested_file_service_key == CC.TRASH_SERVICE_KEY:
|
||||
|
||||
possible_file_service_keys.append( CC.TRASH_SERVICE_KEY )
|
||||
possible_file_service_keys.append( CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
|
||||
|
||||
else:
|
||||
|
||||
possible_file_service_keys.append( suggested_file_service_key )
|
||||
|
@ -914,8 +925,14 @@ class EditDeleteFilesPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
|
||||
if len( hashes ) > 0:
|
||||
|
||||
if num_to_delete == 1: text = 'Permanently delete this file and do not save a deletion record?'
|
||||
else: text = 'Permanently delete these ' + HydrusData.ToHumanInt( num_to_delete ) + ' files and do not save a deletion record?'
|
||||
if num_to_delete == 1:
|
||||
|
||||
text = 'Permanently delete this file and do not save a deletion record?'
|
||||
|
||||
else:
|
||||
|
||||
text = 'Permanently delete these ' + HydrusData.ToHumanInt( num_to_delete ) + ' files and do not save a deletion record?'
|
||||
|
||||
|
||||
self._permitted_action_choices.append( ( text, ( 'clear_delete', hashes, text ) ) )
|
||||
|
||||
|
@ -2652,20 +2669,31 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
|
||||
self._original_info = info
|
||||
|
||||
( self._mime, media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) ) = self._original_info
|
||||
( self._mime, 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 ) ) = self._original_info
|
||||
|
||||
possible_actions = CC.media_viewer_capabilities[ self._mime ]
|
||||
( possible_show_actions, can_start_paused, can_start_with_embed ) = CC.media_viewer_capabilities[ self._mime ]
|
||||
|
||||
self._media_show_action = ClientGUICommon.BetterChoice( self )
|
||||
self._media_start_paused = QW.QCheckBox( self )
|
||||
self._media_start_with_embed = QW.QCheckBox( self )
|
||||
self._preview_show_action = ClientGUICommon.BetterChoice( self )
|
||||
self._preview_start_paused = QW.QCheckBox( self )
|
||||
self._preview_start_with_embed = QW.QCheckBox( self )
|
||||
|
||||
for action in possible_actions:
|
||||
advanced_mode = HG.client_controller.new_options.GetBoolean( 'advanced_mode' )
|
||||
|
||||
for action in possible_show_actions:
|
||||
|
||||
self._media_show_action.addItem( CC.media_viewer_action_string_lookup[ action], action )
|
||||
if action == CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV and ( not advanced_mode or not ClientGUIMPV.MPV_IS_AVAILABLE ):
|
||||
|
||||
continue
|
||||
|
||||
|
||||
self._media_show_action.addItem( CC.media_viewer_action_string_lookup[ action ], action )
|
||||
|
||||
if action != CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY:
|
||||
|
||||
self._preview_show_action.addItem( CC.media_viewer_action_string_lookup[ action], action )
|
||||
self._preview_show_action.addItem( CC.media_viewer_action_string_lookup[ action ], action )
|
||||
|
||||
|
||||
|
||||
|
@ -2708,7 +2736,12 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
#
|
||||
|
||||
self._media_show_action.SetValue( media_show_action )
|
||||
self._media_start_paused.setChecked( media_start_paused )
|
||||
self._media_start_with_embed.setChecked( media_start_with_embed )
|
||||
|
||||
self._preview_show_action.SetValue( preview_show_action )
|
||||
self._preview_start_paused.setChecked( preview_start_paused )
|
||||
self._preview_start_with_embed.setChecked( preview_start_with_embed )
|
||||
|
||||
self._media_scale_up.SetValue( media_scale_up )
|
||||
self._media_scale_down.SetValue( media_scale_down )
|
||||
|
@ -2726,11 +2759,32 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
|
||||
text = 'Setting media view options for ' + HC.mime_string_lookup[ self._mime ] + '.'
|
||||
|
||||
QP.AddToLayout( vbox, ClientGUICommon.BetterStaticText(self,text), CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, ClientGUICommon.WrapInText(self._media_show_action,self,'media viewer show action:'), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, ClientGUICommon.WrapInText(self._preview_show_action,self,'preview show action:'), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
if not ClientGUIMPV.MPV_IS_AVAILABLE:
|
||||
|
||||
text += ' MPV is not available for this client.'
|
||||
|
||||
|
||||
if possible_actions == CC.no_support:
|
||||
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 = []
|
||||
|
||||
rows.append( ( 'media viewer show action: ', self._media_show_action ) )
|
||||
rows.append( ( 'media starts paused: ', self._media_start_paused ) )
|
||||
rows.append( ( 'media starts covered with an embed button: ', self._media_start_with_embed ) )
|
||||
rows.append( ( 'preview viewer show action: ', self._preview_show_action ) )
|
||||
rows.append( ( 'preview starts paused: ', self._preview_start_paused ) )
|
||||
rows.append( ( 'preview starts covered with an embed button: ', self._preview_start_with_embed ) )
|
||||
|
||||
gridbox = ClientGUICommon.WrapInGrid( self, rows )
|
||||
|
||||
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
|
||||
if len( set( possible_show_actions ).intersection( { CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV } ) ) == 0:
|
||||
|
||||
self._media_scale_up.hide()
|
||||
self._media_scale_down.hide()
|
||||
|
@ -2771,45 +2825,78 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
|
||||
self.widget().setLayout( vbox )
|
||||
|
||||
|
||||
def EventActionChange( self, index ):
|
||||
self._UpdateControls()
|
||||
|
||||
if self._media_show_action.GetValue() in CC.no_support and self._preview_show_action.GetValue() in CC.no_support:
|
||||
|
||||
self._media_scale_up.setEnabled( False )
|
||||
self._media_scale_down.setEnabled( False )
|
||||
self._preview_scale_up.setEnabled( False )
|
||||
self._preview_scale_down.setEnabled( False )
|
||||
|
||||
self._exact_zooms_only.setEnabled( False )
|
||||
|
||||
self._scale_up_quality.setEnabled( False )
|
||||
self._scale_down_quality.setEnabled( False )
|
||||
|
||||
else:
|
||||
|
||||
self._media_scale_up.setEnabled( True )
|
||||
self._media_scale_down.setEnabled( True )
|
||||
self._preview_scale_up.setEnabled( True )
|
||||
self._preview_scale_down.setEnabled( True )
|
||||
|
||||
def _UpdateControls( self ):
|
||||
|
||||
media_ok = self._media_show_action.GetValue() not in CC.unsupported_media_actions
|
||||
preview_ok = self._preview_show_action.GetValue() not in CC.unsupported_media_actions
|
||||
|
||||
if media_ok or preview_ok:
|
||||
|
||||
self._exact_zooms_only.setEnabled( True )
|
||||
|
||||
self._scale_up_quality.setEnabled( True )
|
||||
self._scale_down_quality.setEnabled( True )
|
||||
|
||||
|
||||
if self._mime == HC.APPLICATION_FLASH:
|
||||
else:
|
||||
|
||||
self._exact_zooms_only.setEnabled( False )
|
||||
|
||||
self._scale_up_quality.setEnabled( False )
|
||||
self._scale_down_quality.setEnabled( False )
|
||||
|
||||
|
||||
if media_ok:
|
||||
|
||||
self._media_scale_up.setEnabled( True )
|
||||
self._media_scale_down.setEnabled( True )
|
||||
|
||||
self._media_start_paused.setEnabled( True )
|
||||
self._media_start_with_embed.setEnabled( True )
|
||||
|
||||
else:
|
||||
|
||||
self._media_scale_up.setEnabled( False )
|
||||
self._media_scale_down.setEnabled( False )
|
||||
|
||||
self._media_start_paused.setEnabled( False )
|
||||
self._media_start_with_embed.setEnabled( False )
|
||||
|
||||
|
||||
if preview_ok:
|
||||
|
||||
self._preview_scale_up.setEnabled( True )
|
||||
self._preview_scale_down.setEnabled( True )
|
||||
|
||||
self._preview_start_paused.setEnabled( True )
|
||||
self._preview_start_with_embed.setEnabled( True )
|
||||
|
||||
else:
|
||||
|
||||
self._preview_scale_up.setEnabled( False )
|
||||
self._preview_scale_down.setEnabled( False )
|
||||
|
||||
self._preview_start_paused.setEnabled( False )
|
||||
self._preview_start_with_embed.setEnabled( False )
|
||||
|
||||
|
||||
|
||||
def EventActionChange( self, index ):
|
||||
|
||||
self._UpdateControls()
|
||||
|
||||
|
||||
def GetValue( self ):
|
||||
|
||||
media_show_action = self._media_show_action.GetValue()
|
||||
media_start_paused = self._media_start_paused.isChecked()
|
||||
media_start_with_embed = self._media_start_with_embed.isChecked()
|
||||
|
||||
preview_show_action = self._preview_show_action.GetValue()
|
||||
preview_start_paused = self._preview_start_paused.isChecked()
|
||||
preview_start_with_embed = self._preview_start_with_embed.isChecked()
|
||||
|
||||
media_scale_up = self._media_scale_up.GetValue()
|
||||
media_scale_down = self._media_scale_down.GetValue()
|
||||
|
@ -2821,7 +2908,9 @@ class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
scale_up_quality = self._scale_up_quality.GetValue()
|
||||
scale_down_quality = self._scale_down_quality.GetValue()
|
||||
|
||||
return ( self._mime, media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) )
|
||||
zoom_info = ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality )
|
||||
|
||||
return ( self._mime, media_show_action, media_start_paused, media_start_with_embed, preview_show_action, preview_start_paused, preview_start_with_embed, zoom_info )
|
||||
|
||||
|
||||
class EditNetworkContextPanel( ClientGUIScrolledPanels.EditPanel ):
|
||||
|
@ -6351,9 +6440,9 @@ class EditURLClassPanel( ClientGUIScrolledPanels.EditPanel ):
|
|||
self._normalised_url = QW.QLineEdit( self )
|
||||
self._normalised_url.setReadOnly( True )
|
||||
|
||||
tt = 'The same url can be expressed in different ways. The parameters can be reordered, and descriptive \'sugar\' like "/123456/some-changing-tags-here" can be appended and then altered at a later date. In order to collapse all the different expressions of a url down to a single comparable form, the client will \'normalise\' them based on the essential definitions in their url class. Parameters will be alphebatised and non-defined elements will be removed.'
|
||||
tt = 'The same url can be expressed in different ways. The parameters can be reordered, and descriptive \'sugar\' like "/123456/bodysuit-samus_aran" can be altered at a later date, say to "/123456/bodysuit-green_eyes-samus_aran". In order to collapse all the different expressions of a url down to a single comparable form, the client will \'normalise\' them based on the essential definitions in their url class. Parameters will be alphebatised and non-defined elements will be removed.'
|
||||
tt += os.linesep * 2
|
||||
tt += 'All normalisation will switch to the preferred scheme, but the alphabetisation of parameters and stripping out of non-defined elements will only occur if this url is associated with files or uses an API Lookup. (In general, you can define gallery and watchable urls a little more loosely since they generally do not need to be compared, but if you will be saving it with a file or need to perform some regex transformation into an API URL, you\'ll want a rigorously defined url class that will normalise to something reliable and pretty.)'
|
||||
tt += 'All normalisation will switch to the preferred scheme (http/https). The alphabetisation of parameters and stripping out of non-defined elements will occur for all URLs except Gallery URLs or Watchable URLs that do not use an API Lookup. (In general, you can define gallery and watchable urls a little more loosely since they generally do not need to be compared, but if you will be saving it with a file or need to perform some regex transformation into an API URL, you\'ll want a rigorously defined url class that will normalise to something reliable and pretty.)'
|
||||
|
||||
self._normalised_url.setToolTip( tt )
|
||||
|
||||
|
|
|
@ -1570,6 +1570,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
gridbox = ClientGUICommon.WrapInGrid( self, rows )
|
||||
|
||||
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
@ -1680,7 +1681,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
vbox = QP.VBoxLayout()
|
||||
|
||||
QP.AddToLayout( vbox, coloursets_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
QP.AddToLayout( vbox, coloursets_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
@ -1834,6 +1836,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
QP.AddToLayout( vbox, general, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, proxy_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
@ -1976,7 +1979,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
gridbox = ClientGUICommon.WrapInGrid( gallery_downloader, rows )
|
||||
|
||||
gallery_downloader.Add( gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
||||
gallery_downloader.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
|
||||
#
|
||||
|
||||
|
@ -1989,7 +1992,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
gridbox = ClientGUICommon.WrapInGrid( subscriptions, rows )
|
||||
|
||||
subscriptions.Add( gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
||||
subscriptions.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
subscriptions.Add( self._subscription_checker_options, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
#
|
||||
|
@ -2001,7 +2004,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
gridbox = ClientGUICommon.WrapInGrid( watchers, rows )
|
||||
|
||||
watchers.Add( gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
|
||||
watchers.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
watchers.Add( self._watcher_checker_options, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
#
|
||||
|
@ -2029,6 +2032,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
QP.AddToLayout( vbox, watchers, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, misc, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
||||
|
@ -2134,6 +2139,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
gridbox = ClientGUICommon.WrapInGrid( self, rows )
|
||||
|
||||
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
@ -2186,9 +2192,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
vbox = QP.VBoxLayout()
|
||||
|
||||
QP.AddToLayout( vbox, self._always_show_system_everything, CC.FLAGS_VCENTER )
|
||||
QP.AddToLayout( vbox, self._filter_inbox_and_archive_predicates, CC.FLAGS_VCENTER )
|
||||
QP.AddToLayout( vbox, (20,20), CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._always_show_system_everything, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._filter_inbox_and_archive_predicates, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._file_system_predicate_age, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._file_system_predicate_duration, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._file_system_predicate_height, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
@ -2201,6 +2206,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
QP.AddToLayout( vbox, self._file_system_predicate_similar_to, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._file_system_predicate_size, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._file_system_predicate_width, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
@ -2810,6 +2816,12 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._default_new_page_goes.addItem( CC.new_page_goes_string_lookup[ value], value )
|
||||
|
||||
|
||||
self._activate_window_on_tag_search_page_activation = QW.QCheckBox( self )
|
||||
|
||||
tt = 'Middle-clicking one or more tags in a taglist will cause the creation of a new search page for those tags. If you do this from the media viewer or a child manage tags dialog, do you want to switch immediately to the main gui?'
|
||||
|
||||
self._activate_window_on_tag_search_page_activation.setToolTip( tt )
|
||||
|
||||
self._notebook_tabs_on_left = QW.QCheckBox( self )
|
||||
|
||||
self._max_page_name_chars = QP.MakeQSpinBox( self, min=1, max=256 )
|
||||
|
@ -2863,6 +2875,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
self._default_new_page_goes.SetValue( self._new_options.GetInteger( 'default_new_page_goes' ) )
|
||||
|
||||
self._activate_window_on_tag_search_page_activation.setChecked( self._new_options.GetBoolean( 'activate_window_on_tag_search_page_activation' ) )
|
||||
|
||||
self._notebook_tabs_on_left.setChecked( self._new_options.GetBoolean( 'notebook_tabs_on_left' ) )
|
||||
|
||||
self._max_page_name_chars.setValue( self._new_options.GetInteger( 'max_page_name_chars' ) )
|
||||
|
@ -2887,6 +2901,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
rows.append( ( 'Number of session backups to keep: ', self._number_of_gui_session_backups ) )
|
||||
rows.append( ( 'By default, put new page tabs on (requires restart): ', self._default_new_page_goes ) )
|
||||
rows.append( ( 'When switching to a page, focus its input field (if any): ', self._set_search_focus_on_page_change ) )
|
||||
rows.append( ( 'Switch to main window when opening tag search page from media viewer: ', self._activate_window_on_tag_search_page_activation ) )
|
||||
rows.append( ( 'Line notebook tabs down the left: ', self._notebook_tabs_on_left ) )
|
||||
rows.append( ( 'Max characters to display in a page name: ', self._max_page_name_chars ) )
|
||||
rows.append( ( 'Show page file count after its name: ', self._page_file_count_display ) )
|
||||
|
@ -2907,6 +2922,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
HC.options[ 'default_gui_session' ] = self._default_gui_session.currentText()
|
||||
|
||||
self._new_options.SetBoolean( 'activate_window_on_tag_search_page_activation', self._activate_window_on_tag_search_page_activation.isChecked() )
|
||||
|
||||
self._new_options.SetBoolean( 'notebook_tabs_on_left', self._notebook_tabs_on_left.isChecked() )
|
||||
|
||||
self._new_options.SetInteger( 'last_session_save_period_minutes', self._last_session_save_period_minutes.value() )
|
||||
|
@ -2970,6 +2987,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
vbox = QP.VBoxLayout()
|
||||
|
||||
QP.AddToLayout( vbox, default_fios, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
@ -3161,6 +3179,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
QP.AddToLayout( vbox, self._jobs_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._file_maintenance_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, self._vacuum_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
|
@ -3196,6 +3215,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._shutdown_work_period.setEnabled( True )
|
||||
self._idle_shutdown_max_minutes.setEnabled( True )
|
||||
|
||||
|
||||
|
||||
def UpdateOptions( self ):
|
||||
|
||||
HC.options[ 'idle_normal' ] = self._idle_normal.isChecked()
|
||||
|
@ -3281,7 +3302,7 @@ 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_MPEG, HC.VIDEO_WEBM, HC.VIDEO_WMV, HC.AUDIO_MP3, HC.AUDIO_M4A, HC.AUDIO_OGG, HC.AUDIO_FLAC, HC.AUDIO_WMA )
|
||||
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 )
|
||||
|
||||
for mime in mimes_in_correct_order:
|
||||
|
||||
|
@ -3292,7 +3313,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._media_viewer_options.AddDatas( ( data, ) )
|
||||
|
||||
|
||||
#self._media_viewer_options.SortListItems( col = 0 )
|
||||
self._media_viewer_options.Sort()
|
||||
|
||||
#
|
||||
|
||||
|
@ -3324,13 +3345,47 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
def _GetListCtrlData( self, data ):
|
||||
|
||||
( mime, media_show_action, preview_show_action, zoom_info ) = 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_media_show_action = CC.media_viewer_action_string_lookup[ media_show_action ]
|
||||
|
||||
if media_start_paused:
|
||||
|
||||
pretty_media_show_action += ', start paused'
|
||||
|
||||
|
||||
if media_start_with_embed:
|
||||
|
||||
pretty_media_show_action += ', start with embed button'
|
||||
|
||||
|
||||
pretty_preview_show_action = CC.media_viewer_action_string_lookup[ preview_show_action ]
|
||||
|
||||
no_show = media_show_action in CC.no_support and preview_show_action in CC.no_support
|
||||
if preview_start_paused:
|
||||
|
||||
pretty_preview_show_action += ', start paused'
|
||||
|
||||
|
||||
if preview_start_with_embed:
|
||||
|
||||
pretty_preview_show_action += ', start with embed button'
|
||||
|
||||
|
||||
no_show = len( set( ( media_show_action, preview_show_action ) ).intersection( { CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_MPV } ) ) == 0
|
||||
|
||||
if no_show:
|
||||
|
||||
|
@ -3778,6 +3833,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
#
|
||||
|
||||
QP.AddToLayout( vbox, QW.QWidget( self ), CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.setLayout( vbox )
|
||||
|
||||
#
|
||||
|
@ -5112,7 +5169,19 @@ class ManageShortcutsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
vbox = QP.VBoxLayout()
|
||||
|
||||
QP.AddToLayout( vbox, ClientGUICommon.WrapInText(self._name,self,'name: '), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
QP.AddToLayout( vbox, ClientGUICommon.WrapInText( self._name, self, 'name: ' ), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
|
||||
if name in CC.shortcut_names_to_descriptions:
|
||||
|
||||
description_text = CC.shortcut_names_to_descriptions[ name ]
|
||||
|
||||
description = ClientGUICommon.BetterStaticText( self, description_text, description_text )
|
||||
|
||||
description.setWordWrap( True )
|
||||
|
||||
QP.AddToLayout( vbox, description, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
|
||||
QP.AddToLayout( vbox, self._shortcuts, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
QP.AddToLayout( vbox, action_buttons, CC.FLAGS_BUTTON_SIZER )
|
||||
|
||||
|
|
|
@ -2823,6 +2823,14 @@ class ReviewHowBonedAmI( ClientGUIScrolledPanels.ReviewPanel ):
|
|||
|
||||
else:
|
||||
|
||||
average_filesize = size_total // num_total
|
||||
|
||||
summary_label = 'Total: {} files, totalling {}, averaging {}'.format( HydrusData.ToHumanInt( num_total ), HydrusData.ToHumanBytes( size_total ), HydrusData.ToHumanBytes( average_filesize ) )
|
||||
|
||||
summary_st = ClientGUICommon.BetterStaticText( self, label = summary_label )
|
||||
|
||||
QP.AddToLayout( vbox, summary_st, CC.FLAGS_CENTER )
|
||||
|
||||
num_archive_percent = num_archive / num_total
|
||||
size_archive_percent = size_archive / size_total
|
||||
|
||||
|
|
|
@ -117,15 +117,15 @@ def ConvertMouseEventToShortcut( event ):
|
|||
|
||||
key = None
|
||||
|
||||
if (event.type() == QC.QEvent.MouseButtonPress and event.buttons() & QC.Qt.LeftButton) or (event.type() == QC.QEvent.MouseButtonDblClick and event.button() == QC.Qt.LeftButton):
|
||||
if ( event.type() == QC.QEvent.MouseButtonPress and event.buttons() & QC.Qt.LeftButton ) or ( event.type() == QC.QEvent.MouseButtonDblClick and event.button() == QC.Qt.LeftButton ):
|
||||
|
||||
key = CC.SHORTCUT_MOUSE_LEFT
|
||||
|
||||
elif (event.type() == QC.QEvent.MouseButtonPress and event.buttons() & QC.Qt.MiddleButton) or (event.type() == QC.QEvent.MouseButtonDblClick and event.button() == QC.Qt.MiddleButton):
|
||||
elif ( event.type() == QC.QEvent.MouseButtonPress and event.buttons() & QC.Qt.MiddleButton ) or ( event.type() == QC.QEvent.MouseButtonDblClick and event.button() == QC.Qt.MiddleButton ):
|
||||
|
||||
key = CC.SHORTCUT_MOUSE_MIDDLE
|
||||
|
||||
elif (event.type() == QC.QEvent.MouseButtonPress and event.buttons() & QC.Qt.RightButton) or (event.type() == QC.QEvent.MouseButtonDblClick and event.button() == QC.Qt.RightButton):
|
||||
elif ( event.type() == QC.QEvent.MouseButtonPress and event.buttons() & QC.Qt.RightButton ) or ( event.type() == QC.QEvent.MouseButtonDblClick and event.button() == QC.Qt.RightButton ):
|
||||
|
||||
key = CC.SHORTCUT_MOUSE_RIGHT
|
||||
|
||||
|
|
|
@ -1263,25 +1263,33 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
only_these_queries = set( only_these_queries )
|
||||
|
||||
|
||||
my_queries = self._queries
|
||||
|
||||
self._queries = []
|
||||
|
||||
base_sub = self.Duplicate()
|
||||
|
||||
self._queries = my_queries
|
||||
|
||||
subscriptions = []
|
||||
|
||||
for query in self._queries:
|
||||
for query in my_queries:
|
||||
|
||||
if query not in only_these_queries:
|
||||
|
||||
continue
|
||||
|
||||
|
||||
subscription = self.Duplicate()
|
||||
subscription = base_sub.Duplicate()
|
||||
|
||||
subscription._queries = [ query.Duplicate() ]
|
||||
subscription._queries = [ query ]
|
||||
|
||||
subscription.SetName( base_name + ': ' + query.GetHumanName() )
|
||||
|
||||
subscriptions.append( subscription )
|
||||
|
||||
|
||||
self._queries = [ query for query in self._queries if query not in only_these_queries ]
|
||||
self._queries = [ query for query in my_queries if query not in only_these_queries ]
|
||||
|
||||
return subscriptions
|
||||
|
||||
|
|
|
@ -1449,7 +1449,7 @@ class HydrusResourceClientAPIRestrictedGetFilesFileMetadata( HydrusResourceClien
|
|||
metadata_row[ 'file_id' ] = file_info_manager.hash_id
|
||||
metadata_row[ 'hash' ] = file_info_manager.hash.hex()
|
||||
metadata_row[ 'size' ] = file_info_manager.size
|
||||
metadata_row[ 'mime' ] = HC.mime_string_lookup[ file_info_manager.mime ]
|
||||
metadata_row[ 'mime' ] = HC.mime_mimetype_string_lookup[ file_info_manager.mime ]
|
||||
metadata_row[ 'width' ] = file_info_manager.width
|
||||
metadata_row[ 'height' ] = file_info_manager.height
|
||||
metadata_row[ 'duration' ] = file_info_manager.duration
|
||||
|
|
|
@ -1564,11 +1564,11 @@ class MediaList( object ):
|
|||
|
||||
elif file_filter.filter_type == FILE_FILTER_LOCAL:
|
||||
|
||||
filtered_media = { m for m in self._sorted_media if file_service_key in m.GetLocationsManager().IsLocal() }
|
||||
filtered_media = { m for m in self._sorted_media if m.GetLocationsManager().IsLocal() }
|
||||
|
||||
elif file_filter.filter_type == FILE_FILTER_REMOTE:
|
||||
|
||||
filtered_media = { m for m in self._sorted_media if file_service_key in m.GetLocationsManager().IsRemote() }
|
||||
filtered_media = { m for m in self._sorted_media if m.GetLocationsManager().IsRemote() }
|
||||
|
||||
elif file_filter.filter_type == FILE_FILTER_TAGS:
|
||||
|
||||
|
@ -1666,7 +1666,7 @@ class MediaList( object ):
|
|||
|
||||
new_options = HG.client_controller.new_options
|
||||
|
||||
media_show_action = new_options.GetMediaShowAction( media.GetMime() )
|
||||
( media_show_action, media_start_paused, media_start_with_embed ) = new_options.GetMediaShowAction( media.GetMime() )
|
||||
|
||||
if media_show_action in ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW_ON_ACTIVATION_OPEN_EXTERNALLY, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW ):
|
||||
|
||||
|
@ -1953,6 +1953,7 @@ FILE_FILTER_LOCAL = 6
|
|||
FILE_FILTER_REMOTE = 7
|
||||
FILE_FILTER_TAGS = 8
|
||||
FILE_FILTER_SELECTED = 9
|
||||
FILE_FILTER_MIME = 10
|
||||
|
||||
file_filter_str_lookup = {}
|
||||
|
||||
|
@ -1966,6 +1967,7 @@ file_filter_str_lookup[ FILE_FILTER_FILE_SERVICE ] = 'file service'
|
|||
file_filter_str_lookup[ FILE_FILTER_LOCAL ] = 'local'
|
||||
file_filter_str_lookup[ FILE_FILTER_REMOTE ] = 'remote'
|
||||
file_filter_str_lookup[ FILE_FILTER_TAGS ] = 'tags'
|
||||
file_filter_str_lookup[ FILE_FILTER_MIME ] = 'filetype'
|
||||
|
||||
class FileFilter( object ):
|
||||
|
||||
|
@ -1998,18 +2000,18 @@ class FileFilter( object ):
|
|||
return
|
||||
|
||||
|
||||
quick_calculations = {}
|
||||
quick_inverse_lookups= {}
|
||||
|
||||
quick_calculations[ FileFilter( FILE_FILTER_INBOX ) ] = FileFilter( FILE_FILTER_ARCHIVE )
|
||||
quick_calculations[ FileFilter( FILE_FILTER_ARCHIVE ) ] = FileFilter( FILE_FILTER_INBOX )
|
||||
quick_calculations[ FileFilter( FILE_FILTER_SELECTED ) ] = FileFilter( FILE_FILTER_NOT_SELECTED )
|
||||
quick_calculations[ FileFilter( FILE_FILTER_NOT_SELECTED ) ] = FileFilter( FILE_FILTER_SELECTED )
|
||||
quick_calculations[ FileFilter( FILE_FILTER_LOCAL ) ] = FileFilter( FILE_FILTER_REMOTE )
|
||||
quick_calculations[ FileFilter( FILE_FILTER_REMOTE ) ] = FileFilter( FILE_FILTER_LOCAL )
|
||||
quick_inverse_lookups[ FileFilter( FILE_FILTER_INBOX ) ] = FileFilter( FILE_FILTER_ARCHIVE )
|
||||
quick_inverse_lookups[ FileFilter( FILE_FILTER_ARCHIVE ) ] = FileFilter( FILE_FILTER_INBOX )
|
||||
quick_inverse_lookups[ FileFilter( FILE_FILTER_SELECTED ) ] = FileFilter( FILE_FILTER_NOT_SELECTED )
|
||||
quick_inverse_lookups[ FileFilter( FILE_FILTER_NOT_SELECTED ) ] = FileFilter( FILE_FILTER_SELECTED )
|
||||
quick_inverse_lookups[ FileFilter( FILE_FILTER_LOCAL ) ] = FileFilter( FILE_FILTER_REMOTE )
|
||||
quick_inverse_lookups[ FileFilter( FILE_FILTER_REMOTE ) ] = FileFilter( FILE_FILTER_LOCAL )
|
||||
|
||||
if self in quick_calculations:
|
||||
if self in quick_inverse_lookups:
|
||||
|
||||
inverse = quick_calculations[ self ]
|
||||
inverse = quick_inverse_lookups[ self ]
|
||||
|
||||
all_filter = FileFilter( FILE_FILTER_ALL )
|
||||
|
||||
|
@ -2050,6 +2052,12 @@ class FileFilter( object ):
|
|||
|
||||
s = HydrusText.ElideText( s, 64 )
|
||||
|
||||
elif self.filter_type == FILE_FILTER_MIME:
|
||||
|
||||
mime = self.filter_data
|
||||
|
||||
s = HC.mime_string_lookup[ mime ]
|
||||
|
||||
else:
|
||||
|
||||
s = file_filter_str_lookup[ self.filter_type ]
|
||||
|
@ -2231,7 +2239,16 @@ class MediaCollection( MediaList, Media ):
|
|||
|
||||
def GetDisplayMedia( self ):
|
||||
|
||||
return self._GetFirst().GetDisplayMedia()
|
||||
first = self._GetFirst()
|
||||
|
||||
if first is None:
|
||||
|
||||
return None
|
||||
|
||||
else:
|
||||
|
||||
return first.GetDisplayMedia()
|
||||
|
||||
|
||||
|
||||
def GetDuration( self ):
|
||||
|
|
|
@ -390,7 +390,7 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
|
|||
self._url_class_keys_to_display = set()
|
||||
self._url_class_keys_to_parser_keys = HydrusSerialisable.SerialisableBytesDictionary()
|
||||
|
||||
self._domains_to_url_classes = collections.defaultdict( list )
|
||||
self._second_level_domains_to_url_classes = collections.defaultdict( list )
|
||||
|
||||
from . import ClientImportOptions
|
||||
|
||||
|
@ -552,9 +552,9 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
domain = ConvertDomainIntoSecondLevelDomain( ConvertURLIntoDomain( url ) )
|
||||
|
||||
if domain in self._domains_to_url_classes:
|
||||
if domain in self._second_level_domains_to_url_classes:
|
||||
|
||||
url_classes = self._domains_to_url_classes[ domain ]
|
||||
url_classes = self._second_level_domains_to_url_classes[ domain ]
|
||||
|
||||
for url_class in url_classes:
|
||||
|
||||
|
@ -635,16 +635,16 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
def _RecalcCache( self ):
|
||||
|
||||
self._domains_to_url_classes = collections.defaultdict( list )
|
||||
self._second_level_domains_to_url_classes = collections.defaultdict( list )
|
||||
|
||||
for url_class in self._url_classes:
|
||||
|
||||
domain = url_class.GetDomain()
|
||||
domain = ConvertDomainIntoSecondLevelDomain( url_class.GetDomain() )
|
||||
|
||||
self._domains_to_url_classes[ domain ].append( url_class )
|
||||
self._second_level_domains_to_url_classes[ domain ].append( url_class )
|
||||
|
||||
|
||||
for url_classes in list(self._domains_to_url_classes.values()):
|
||||
for url_classes in self._second_level_domains_to_url_classes.values():
|
||||
|
||||
NetworkDomainManager.STATICSortURLClassesDescendingComplexity( url_classes )
|
||||
|
||||
|
@ -1150,6 +1150,16 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
|
|||
self.SetGUGs( gugs )
|
||||
|
||||
|
||||
def DeleteURLClasses( self, deletee_names ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
url_classes = [ url_class for url_class in self._url_classes if url_class.GetName() not in deletee_names ]
|
||||
|
||||
|
||||
self.SetURLClasses( url_classes )
|
||||
|
||||
|
||||
def GenerateValidationPopupProcess( self, network_contexts ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -2936,7 +2946,7 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
def GetDomain( self ):
|
||||
|
||||
return ConvertDomainIntoSecondLevelDomain( self._netloc )
|
||||
return self._netloc
|
||||
|
||||
|
||||
def GetExampleURL( self ):
|
||||
|
|
|
@ -18,7 +18,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_CLIENT_OPTIONS
|
||||
SERIALISABLE_NAME = 'Client Options'
|
||||
SERIALISABLE_VERSION = 3
|
||||
SERIALISABLE_VERSION = 4
|
||||
|
||||
def __init__( self ):
|
||||
|
||||
|
@ -60,6 +60,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
self._dictionary[ 'booleans' ][ 'replace_siblings_on_manage_tags' ] = True
|
||||
self._dictionary[ 'booleans' ][ 'yes_no_on_remove_on_manage_tags' ] = True
|
||||
|
||||
self._dictionary[ 'booleans' ][ 'activate_window_on_tag_search_page_activation' ] = False
|
||||
|
||||
self._dictionary[ 'booleans' ][ 'show_related_tags' ] = False
|
||||
self._dictionary[ 'booleans' ][ 'show_file_lookup_script_tags' ] = False
|
||||
self._dictionary[ 'booleans' ][ 'hide_message_manager_on_gui_iconise' ] = HC.PLATFORM_MACOS
|
||||
|
@ -468,7 +470,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
self._dictionary[ 'media_view' ] = HydrusSerialisable.SerialisableDictionary() # integer keys, so got to be cleverer dict
|
||||
|
||||
# media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) )
|
||||
# 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 )
|
||||
|
@ -476,37 +478,37 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
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 )
|
||||
|
||||
self._dictionary[ 'media_view' ][ HC.IMAGE_JPEG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, image_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.IMAGE_PNG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, image_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.IMAGE_APNG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, gif_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.IMAGE_GIF ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, gif_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.IMAGE_WEBP ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, image_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.IMAGE_TIFF ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, image_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.IMAGE_ICON ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, image_zoom_info )
|
||||
media_start_paused = False
|
||||
media_start_with_embed = False
|
||||
preview_start_paused = False
|
||||
preview_start_with_embed = False
|
||||
|
||||
self._dictionary[ 'media_view' ][ HC.APPLICATION_FLASH ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
|
||||
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_PDF ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.APPLICATION_PSD ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.APPLICATION_ZIP ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.APPLICATION_7Z ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.APPLICATION_RAR ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.APPLICATION_HYDRUS_UPDATE_CONTENT ] = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, null_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = ( CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW, null_zoom_info )
|
||||
|
||||
self._dictionary[ 'media_view' ][ HC.VIDEO_AVI ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.VIDEO_FLV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.VIDEO_MOV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.VIDEO_MP4 ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.VIDEO_MPEG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.VIDEO_MKV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.VIDEO_WEBM ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.VIDEO_WMV ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, CC.MEDIA_VIEWER_ACTION_SHOW_AS_NORMAL, video_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.AUDIO_M4A ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.AUDIO_MP3 ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.AUDIO_OGG ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.AUDIO_FLAC ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, null_zoom_info )
|
||||
self._dictionary[ 'media_view' ][ HC.AUDIO_WMA ] = ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, 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_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 ]
|
||||
|
||||
|
@ -666,6 +668,39 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
return ( 3, new_serialisable_info )
|
||||
|
||||
|
||||
if version == 3:
|
||||
|
||||
loaded_dictionary = HydrusSerialisable.CreateFromSerialisableTuple( old_serialisable_info )
|
||||
|
||||
if 'media_view' in loaded_dictionary:
|
||||
|
||||
def convert_show_action( show_action ):
|
||||
|
||||
start_paused = show_action in ( CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED, CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE_PAUSED )
|
||||
start_with_embed = show_action in ( CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED, CC.MEDIA_VIEWER_ACTION_SHOW_BEHIND_EMBED_PAUSED )
|
||||
|
||||
if start_paused or start_with_embed:
|
||||
|
||||
show_action = CC.MEDIA_VIEWER_ACTION_SHOW_WITH_NATIVE
|
||||
|
||||
|
||||
return ( show_action, start_paused, start_with_embed )
|
||||
|
||||
|
||||
for ( mime, ( media_show_action, preview_show_action, zoom_info ) ) in list( loaded_dictionary[ 'media_view' ].items() ):
|
||||
|
||||
( media_show_action, media_start_paused, media_start_with_embed ) = convert_show_action( media_show_action )
|
||||
( preview_show_action, preview_start_paused, preview_start_with_embed ) = convert_show_action( preview_show_action )
|
||||
|
||||
loaded_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 )
|
||||
|
||||
|
||||
|
||||
new_serialisable_info = loaded_dictionary.GetSerialisableTuple()
|
||||
|
||||
return ( 4, new_serialisable_info )
|
||||
|
||||
|
||||
|
||||
def FlipBoolean( self, name ):
|
||||
|
||||
|
@ -813,14 +848,16 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
with self._lock:
|
||||
|
||||
( media_show_action, preview_show_action, 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._dictionary[ 'media_view' ][ mime ]
|
||||
|
||||
if media_show_action not in CC.media_viewer_capabilities[ mime ]:
|
||||
( possible_show_actions, can_start_paused, can_start_with_embed ) = CC.media_viewer_capabilities[ mime ]
|
||||
|
||||
if media_show_action not in possible_show_actions:
|
||||
|
||||
return CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON
|
||||
return ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, False, False )
|
||||
|
||||
|
||||
return media_show_action
|
||||
return ( media_show_action, media_start_paused, media_start_with_embed )
|
||||
|
||||
|
||||
|
||||
|
@ -836,7 +873,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
with self._lock:
|
||||
|
||||
( media_show_action, preview_show_action, 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._dictionary[ 'media_view' ][ mime ]
|
||||
|
||||
return zoom_info
|
||||
|
||||
|
@ -854,7 +891,9 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
with self._lock:
|
||||
|
||||
( media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) ) = 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._dictionary[ 'media_view' ][ mime ]
|
||||
|
||||
( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) = zoom_info
|
||||
|
||||
return ( scale_up_quality, scale_down_quality )
|
||||
|
||||
|
@ -893,14 +932,16 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
with self._lock:
|
||||
|
||||
( media_show_action, preview_show_action, 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._dictionary[ 'media_view' ][ mime ]
|
||||
|
||||
if preview_show_action not in CC.media_viewer_capabilities[ mime ]:
|
||||
( possible_show_actions, can_start_paused, can_start_with_embed ) = CC.media_viewer_capabilities[ mime ]
|
||||
|
||||
if preview_show_action not in possible_show_actions:
|
||||
|
||||
return CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON
|
||||
return ( CC.MEDIA_VIEWER_ACTION_SHOW_OPEN_EXTERNALLY_BUTTON, False, False )
|
||||
|
||||
|
||||
return preview_show_action
|
||||
return ( preview_show_action, preview_start_paused, preview_start_with_embed )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2356,7 +2356,7 @@ class ParseNodeContentLink( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
for search_url in search_urls:
|
||||
|
||||
job_key.SetVariable( 'script_status', 'fetching ' + search_url )
|
||||
job_key.SetVariable( 'script_status', 'fetching ' + HydrusText.ElideText( search_url, 32 ) )
|
||||
|
||||
network_job = ClientNetworkingJobs.NetworkJob( 'GET', search_url, referral_url = referral_url )
|
||||
|
||||
|
@ -2386,7 +2386,7 @@ class ParseNodeContentLink( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
job_key.SetVariable( 'script_status', 'Network error! Details written to log.' )
|
||||
|
||||
HydrusData.Print( 'Problem fetching ' + search_url + ':' )
|
||||
HydrusData.Print( 'Problem fetching ' + HydrusText.ElideText( search_url, 32 ) + ':' )
|
||||
HydrusData.PrintException( e )
|
||||
|
||||
time.sleep( 2 )
|
||||
|
@ -2605,7 +2605,7 @@ class ParseRootFileLookup( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
full_request_url = self._url + '?' + ClientNetworkingDomain.ConvertQueryDictToText( request_args )
|
||||
|
||||
job_key.SetVariable( 'script_status', 'fetching ' + full_request_url )
|
||||
job_key.SetVariable( 'script_status', 'fetching ' + HydrusText.ElideText( full_request_url, 32 ) )
|
||||
|
||||
job_key.AddURL( full_request_url )
|
||||
|
||||
|
|
|
@ -976,7 +976,7 @@ class ServiceRestricted( ServiceRemote ):
|
|||
|
||||
if content_type is not None:
|
||||
|
||||
network_job.AddAdditionalHeader( 'Content-Type', HC.mime_string_lookup[ content_type ] )
|
||||
network_job.AddAdditionalHeader( 'Content-Type', HC.mime_mimetype_string_lookup[ content_type ] )
|
||||
|
||||
|
||||
HG.client_controller.network_engine.AddJob( network_job )
|
||||
|
@ -2645,7 +2645,7 @@ class ServiceIPFS( ServiceRemote ):
|
|||
|
||||
url = api_base_url + 'add'
|
||||
|
||||
mime_string = HC.mime_string_lookup[ mime ]
|
||||
mime_string = HC.mime_mimetype_string_lookup[ mime ]
|
||||
|
||||
files = { 'path' : ( hash.hex(), f, mime_string ) }
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 18
|
||||
SOFTWARE_VERSION = 379
|
||||
SOFTWARE_VERSION = 380
|
||||
CLIENT_API_VERSION = 11
|
||||
|
||||
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
@ -481,30 +481,47 @@ IMAGE_WEBP = 33
|
|||
IMAGE_TIFF = 34
|
||||
APPLICATION_PSD = 35
|
||||
AUDIO_M4A = 36
|
||||
VIDEO_REALMEDIA = 37
|
||||
AUDIO_REALMEDIA = 38
|
||||
AUDIO_TRUEAUDIO = 39
|
||||
GENERAL_AUDIO = 40
|
||||
GENERAL_IMAGE = 41
|
||||
GENERAL_VIDEO = 42
|
||||
GENERAL_APPLICATION = 43
|
||||
APPLICATION_OCTET_STREAM = 100
|
||||
APPLICATION_UNKNOWN = 101
|
||||
|
||||
ALLOWED_MIMES = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, APPLICATION_FLASH, VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG, APPLICATION_PSD, APPLICATION_PDF, APPLICATION_ZIP, APPLICATION_RAR, APPLICATION_7Z, AUDIO_M4A, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA, VIDEO_WMV, APPLICATION_HYDRUS_UPDATE_CONTENT, APPLICATION_HYDRUS_UPDATE_DEFINITIONS )
|
||||
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_WEBM, VIDEO_MPEG, APPLICATION_PSD, APPLICATION_PDF, APPLICATION_ZIP, APPLICATION_RAR, APPLICATION_7Z, AUDIO_M4A, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA, VIDEO_WMV )
|
||||
GENERAL_FILETYPES = ( GENERAL_APPLICATION, GENERAL_AUDIO, GENERAL_IMAGE, GENERAL_VIDEO )
|
||||
|
||||
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 } )
|
||||
|
||||
DECOMPRESSION_BOMB_IMAGES = ( IMAGE_JPEG, IMAGE_PNG )
|
||||
|
||||
IMAGES = ( IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON )
|
||||
|
||||
AUDIO = ( AUDIO_M4A, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA )
|
||||
IMAGES_THAT_CAN_HAVE_ANIMATION = ( IMAGE_GIF, IMAGE_APNG )
|
||||
|
||||
VIDEO = ( VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG )
|
||||
AUDIO = ( AUDIO_M4A, AUDIO_MP3, AUDIO_OGG, AUDIO_FLAC, AUDIO_WMA, AUDIO_REALMEDIA, AUDIO_TRUEAUDIO )
|
||||
|
||||
NATIVE_VIDEO = ( IMAGE_APNG, VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG )
|
||||
VIDEO = ( VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_REALMEDIA, VIDEO_WEBM, VIDEO_MPEG )
|
||||
|
||||
APPLICATIONS = ( APPLICATION_FLASH, APPLICATION_PSD, APPLICATION_PDF, APPLICATION_ZIP, APPLICATION_RAR, APPLICATION_7Z )
|
||||
|
||||
general_mimetypes_to_mime_groups = {}
|
||||
|
||||
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
|
||||
|
||||
MIMES_THAT_DEFINITELY_HAVE_AUDIO = tuple( [ APPLICATION_FLASH ] + list( AUDIO ) )
|
||||
MIMES_THAT_MAY_HAVE_AUDIO = tuple( list( MIMES_THAT_DEFINITELY_HAVE_AUDIO ) + list( VIDEO ) )
|
||||
|
||||
ARCHIVES = ( APPLICATION_ZIP, APPLICATION_HYDRUS_ENCRYPTED_ZIP, APPLICATION_RAR, APPLICATION_7Z )
|
||||
|
||||
MIMES_WITH_THUMBNAILS = ( APPLICATION_FLASH, IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_WEBM, VIDEO_MPEG )
|
||||
MIMES_WITH_THUMBNAILS = ( APPLICATION_FLASH, IMAGE_JPEG, IMAGE_PNG, IMAGE_APNG, IMAGE_GIF, IMAGE_BMP, IMAGE_WEBP, IMAGE_TIFF, IMAGE_ICON, VIDEO_AVI, VIDEO_FLV, VIDEO_MOV, VIDEO_MP4, VIDEO_WMV, VIDEO_MKV, VIDEO_REALMEDIA, VIDEO_WEBM, VIDEO_MPEG )
|
||||
|
||||
HYDRUS_UPDATE_FILES = ( APPLICATION_HYDRUS_UPDATE_DEFINITIONS, APPLICATION_HYDRUS_UPDATE_CONTENT )
|
||||
|
||||
|
@ -544,6 +561,8 @@ mime_enum_lookup[ 'application' ] = APPLICATIONS
|
|||
mime_enum_lookup[ 'audio/mp4' ] = AUDIO_M4A
|
||||
mime_enum_lookup[ 'audio/mp3' ] = AUDIO_MP3
|
||||
mime_enum_lookup[ 'audio/ogg' ] = AUDIO_OGG
|
||||
mime_enum_lookup[ 'audio/vnd.rn-realaudio' ] = AUDIO_REALMEDIA
|
||||
mime_enum_lookup[ 'audio/x-tta' ] = AUDIO_TRUEAUDIO
|
||||
mime_enum_lookup[ 'audio/flac' ] = AUDIO_FLAC
|
||||
mime_enum_lookup[ 'audio/x-ms-wma' ] = AUDIO_WMA
|
||||
mime_enum_lookup[ 'text/html' ] = TEXT_HTML
|
||||
|
@ -555,6 +574,8 @@ mime_enum_lookup[ 'video/mp4' ] = VIDEO_MP4
|
|||
mime_enum_lookup[ 'video/mpeg' ] = VIDEO_MPEG
|
||||
mime_enum_lookup[ 'video/x-ms-wmv' ] = VIDEO_WMV
|
||||
mime_enum_lookup[ 'video/x-matroska' ] = VIDEO_MKV
|
||||
mime_enum_lookup[ 'video/vnd.rn-realvideo' ] = VIDEO_REALMEDIA
|
||||
mime_enum_lookup[ 'application/vnd.rn-realmedia' ] = VIDEO_REALMEDIA
|
||||
mime_enum_lookup[ 'video/webm' ] = VIDEO_WEBM
|
||||
mime_enum_lookup[ 'video' ] = VIDEO
|
||||
mime_enum_lookup[ 'unknown mime' ] = APPLICATION_UNKNOWN
|
||||
|
@ -562,47 +583,98 @@ mime_enum_lookup[ 'unknown mime' ] = APPLICATION_UNKNOWN
|
|||
mime_string_lookup = {}
|
||||
|
||||
mime_string_lookup[ APPLICATION_HYDRUS_CLIENT_COLLECTION ] = 'collection'
|
||||
mime_string_lookup[ IMAGE_JPEG ] = 'image/jpg'
|
||||
mime_string_lookup[ IMAGE_PNG ] = 'image/png'
|
||||
mime_string_lookup[ IMAGE_APNG ] = 'image/apng'
|
||||
mime_string_lookup[ IMAGE_GIF ] = 'image/gif'
|
||||
mime_string_lookup[ IMAGE_BMP ] = 'image/bmp'
|
||||
mime_string_lookup[ IMAGE_WEBP ] = 'image/webp'
|
||||
mime_string_lookup[ IMAGE_TIFF ] = 'image/tiff'
|
||||
mime_string_lookup[ IMAGE_ICON ] = 'image/x-icon'
|
||||
mime_string_lookup[ IMAGES ] = 'image'
|
||||
mime_string_lookup[ APPLICATION_FLASH ] = 'application/x-shockwave-flash'
|
||||
mime_string_lookup[ IMAGE_JPEG ] = 'jpeg'
|
||||
mime_string_lookup[ IMAGE_PNG ] = 'png'
|
||||
mime_string_lookup[ IMAGE_APNG ] = 'apng'
|
||||
mime_string_lookup[ IMAGE_GIF ] = 'gif'
|
||||
mime_string_lookup[ IMAGE_BMP ] = 'bmp'
|
||||
mime_string_lookup[ IMAGE_WEBP ] = 'webp'
|
||||
mime_string_lookup[ IMAGE_TIFF ] = 'tiff'
|
||||
mime_string_lookup[ IMAGE_ICON ] = 'icon'
|
||||
mime_string_lookup[ APPLICATION_FLASH ] = 'flash'
|
||||
mime_string_lookup[ APPLICATION_OCTET_STREAM ] = 'application/octet-stream'
|
||||
mime_string_lookup[ APPLICATION_YAML ] = 'application/x-yaml'
|
||||
mime_string_lookup[ APPLICATION_JSON ] = 'application/json'
|
||||
mime_string_lookup[ APPLICATION_PDF ] = 'application/pdf'
|
||||
mime_string_lookup[ APPLICATION_PSD ] = 'application/x-photoshop'
|
||||
mime_string_lookup[ APPLICATION_ZIP ] = 'application/zip'
|
||||
mime_string_lookup[ APPLICATION_RAR ] = 'application/vnd.rar'
|
||||
mime_string_lookup[ APPLICATION_7Z ] = 'application/x-7z-compressed'
|
||||
mime_string_lookup[ APPLICATION_YAML ] = 'yaml'
|
||||
mime_string_lookup[ APPLICATION_JSON ] = 'json'
|
||||
mime_string_lookup[ APPLICATION_PDF ] = 'pdf'
|
||||
mime_string_lookup[ APPLICATION_PSD ] = 'photoshop psd'
|
||||
mime_string_lookup[ APPLICATION_ZIP ] = 'zip'
|
||||
mime_string_lookup[ APPLICATION_RAR ] = 'rar'
|
||||
mime_string_lookup[ APPLICATION_7Z ] = '7z'
|
||||
mime_string_lookup[ APPLICATION_HYDRUS_ENCRYPTED_ZIP ] = 'application/hydrus-encrypted-zip'
|
||||
mime_string_lookup[ APPLICATION_HYDRUS_UPDATE_CONTENT ] = 'application/hydrus-update-content'
|
||||
mime_string_lookup[ APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = 'application/hydrus-update-definitions'
|
||||
mime_string_lookup[ APPLICATIONS ] = 'application'
|
||||
mime_string_lookup[ AUDIO_M4A ] = 'audio/mp4'
|
||||
mime_string_lookup[ AUDIO_MP3 ] = 'audio/mp3'
|
||||
mime_string_lookup[ AUDIO_OGG ] = 'audio/ogg'
|
||||
mime_string_lookup[ AUDIO_FLAC ] = 'audio/flac'
|
||||
mime_string_lookup[ AUDIO_WMA ] = 'audio/x-ms-wma'
|
||||
mime_string_lookup[ AUDIO ] = 'audio'
|
||||
mime_string_lookup[ TEXT_HTML ] = 'text/html'
|
||||
mime_string_lookup[ TEXT_PLAIN ] = 'text/plain'
|
||||
mime_string_lookup[ VIDEO_AVI ] = 'video/x-msvideo'
|
||||
mime_string_lookup[ VIDEO_FLV ] = 'video/x-flv'
|
||||
mime_string_lookup[ VIDEO_MOV ] = 'video/quicktime'
|
||||
mime_string_lookup[ VIDEO_MP4 ] = 'video/mp4'
|
||||
mime_string_lookup[ VIDEO_MPEG ] = 'video/mpeg'
|
||||
mime_string_lookup[ VIDEO_WMV ] = 'video/x-ms-wmv'
|
||||
mime_string_lookup[ VIDEO_MKV ] = 'video/x-matroska'
|
||||
mime_string_lookup[ VIDEO_WEBM ] = 'video/webm'
|
||||
mime_string_lookup[ VIDEO ] = 'video'
|
||||
mime_string_lookup[ UNDETERMINED_WM ] = 'audio/x-ms-wma or video/x-ms-wmv'
|
||||
mime_string_lookup[ APPLICATION_UNKNOWN ] = 'unknown mime'
|
||||
mime_string_lookup[ AUDIO_M4A ] = 'm4a'
|
||||
mime_string_lookup[ AUDIO_MP3 ] = 'mp3'
|
||||
mime_string_lookup[ AUDIO_OGG ] = 'ogg'
|
||||
mime_string_lookup[ AUDIO_FLAC ] = 'flac'
|
||||
mime_string_lookup[ AUDIO_REALMEDIA ] = 'realaudio'
|
||||
mime_string_lookup[ AUDIO_TRUEAUDIO ] = 'tta'
|
||||
mime_string_lookup[ AUDIO_WMA ] = 'wma'
|
||||
mime_string_lookup[ TEXT_HTML ] = 'html'
|
||||
mime_string_lookup[ TEXT_PLAIN ] = 'plaintext'
|
||||
mime_string_lookup[ VIDEO_AVI ] = 'avi'
|
||||
mime_string_lookup[ VIDEO_FLV ] = 'flv'
|
||||
mime_string_lookup[ VIDEO_MOV ] = 'quicktime'
|
||||
mime_string_lookup[ VIDEO_MP4 ] = 'mp4'
|
||||
mime_string_lookup[ VIDEO_MPEG ] = 'mpeg'
|
||||
mime_string_lookup[ VIDEO_WMV ] = 'wmv'
|
||||
mime_string_lookup[ VIDEO_MKV ] = 'matroska'
|
||||
mime_string_lookup[ VIDEO_REALMEDIA ] = 'realvideo'
|
||||
mime_string_lookup[ VIDEO_WEBM ] = 'webm'
|
||||
mime_string_lookup[ UNDETERMINED_WM ] = 'wma or wmv'
|
||||
mime_string_lookup[ APPLICATION_UNKNOWN ] = 'unknown filetype'
|
||||
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_mimetype_string_lookup = {}
|
||||
|
||||
mime_mimetype_string_lookup[ APPLICATION_HYDRUS_CLIENT_COLLECTION ] = 'collection'
|
||||
mime_mimetype_string_lookup[ IMAGE_JPEG ] = 'image/jpg'
|
||||
mime_mimetype_string_lookup[ IMAGE_PNG ] = 'image/png'
|
||||
mime_mimetype_string_lookup[ IMAGE_APNG ] = 'image/apng'
|
||||
mime_mimetype_string_lookup[ IMAGE_GIF ] = 'image/gif'
|
||||
mime_mimetype_string_lookup[ IMAGE_BMP ] = 'image/bmp'
|
||||
mime_mimetype_string_lookup[ IMAGE_WEBP ] = 'image/webp'
|
||||
mime_mimetype_string_lookup[ IMAGE_TIFF ] = 'image/tiff'
|
||||
mime_mimetype_string_lookup[ IMAGE_ICON ] = 'image/x-icon'
|
||||
mime_mimetype_string_lookup[ APPLICATION_FLASH ] = 'application/x-shockwave-flash'
|
||||
mime_mimetype_string_lookup[ APPLICATION_OCTET_STREAM ] = 'application/octet-stream'
|
||||
mime_mimetype_string_lookup[ APPLICATION_YAML ] = 'application/x-yaml'
|
||||
mime_mimetype_string_lookup[ APPLICATION_JSON ] = 'application/json'
|
||||
mime_mimetype_string_lookup[ APPLICATION_PDF ] = 'application/pdf'
|
||||
mime_mimetype_string_lookup[ APPLICATION_PSD ] = 'application/x-photoshop'
|
||||
mime_mimetype_string_lookup[ APPLICATION_ZIP ] = 'application/zip'
|
||||
mime_mimetype_string_lookup[ APPLICATION_RAR ] = 'application/vnd.rar'
|
||||
mime_mimetype_string_lookup[ APPLICATION_7Z ] = 'application/x-7z-compressed'
|
||||
mime_mimetype_string_lookup[ APPLICATION_HYDRUS_ENCRYPTED_ZIP ] = 'application/hydrus-encrypted-zip'
|
||||
mime_mimetype_string_lookup[ APPLICATION_HYDRUS_UPDATE_CONTENT ] = 'application/hydrus-update-content'
|
||||
mime_mimetype_string_lookup[ APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = 'application/hydrus-update-definitions'
|
||||
mime_mimetype_string_lookup[ AUDIO_M4A ] = 'audio/mp4'
|
||||
mime_mimetype_string_lookup[ AUDIO_MP3 ] = 'audio/mp3'
|
||||
mime_mimetype_string_lookup[ AUDIO_OGG ] = 'audio/ogg'
|
||||
mime_mimetype_string_lookup[ AUDIO_FLAC ] = 'audio/flac'
|
||||
mime_mimetype_string_lookup[ AUDIO_REALMEDIA ] = 'audio/vnd.rn-realaudio'
|
||||
mime_mimetype_string_lookup[ AUDIO_TRUEAUDIO ] = 'audio/x-tta'
|
||||
mime_mimetype_string_lookup[ AUDIO_WMA ] = 'audio/x-ms-wma'
|
||||
mime_mimetype_string_lookup[ TEXT_HTML ] = 'text/html'
|
||||
mime_mimetype_string_lookup[ TEXT_PLAIN ] = 'text/plain'
|
||||
mime_mimetype_string_lookup[ VIDEO_AVI ] = 'video/x-msvideo'
|
||||
mime_mimetype_string_lookup[ VIDEO_FLV ] = 'video/x-flv'
|
||||
mime_mimetype_string_lookup[ VIDEO_MOV ] = 'video/quicktime'
|
||||
mime_mimetype_string_lookup[ VIDEO_MP4 ] = 'video/mp4'
|
||||
mime_mimetype_string_lookup[ VIDEO_MPEG ] = 'video/mpeg'
|
||||
mime_mimetype_string_lookup[ VIDEO_WMV ] = 'video/x-ms-wmv'
|
||||
mime_mimetype_string_lookup[ VIDEO_MKV ] = 'video/x-matroska'
|
||||
mime_mimetype_string_lookup[ VIDEO_REALMEDIA ] = 'video/vnd.rn-realvideo'
|
||||
mime_mimetype_string_lookup[ VIDEO_WEBM ] = 'video/webm'
|
||||
mime_mimetype_string_lookup[ UNDETERMINED_WM ] = 'audio/x-ms-wma or video/x-ms-wmv'
|
||||
mime_mimetype_string_lookup[ APPLICATION_UNKNOWN ] = 'unknown mime'
|
||||
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_ext_lookup = {}
|
||||
|
||||
|
@ -630,7 +702,9 @@ mime_ext_lookup[ APPLICATION_HYDRUS_UPDATE_DEFINITIONS ] = ''
|
|||
mime_ext_lookup[ AUDIO_M4A ] = '.m4a'
|
||||
mime_ext_lookup[ AUDIO_MP3 ] = '.mp3'
|
||||
mime_ext_lookup[ AUDIO_OGG ] = '.ogg'
|
||||
mime_ext_lookup[ AUDIO_REALMEDIA ] = '.ra'
|
||||
mime_ext_lookup[ AUDIO_FLAC ] = '.flac'
|
||||
mime_ext_lookup[ AUDIO_TRUEAUDIO ] = '.tta'
|
||||
mime_ext_lookup[ AUDIO_WMA ] = '.wma'
|
||||
mime_ext_lookup[ TEXT_HTML ] = '.html'
|
||||
mime_ext_lookup[ TEXT_PLAIN ] = '.txt'
|
||||
|
@ -641,6 +715,7 @@ mime_ext_lookup[ VIDEO_MP4 ] = '.mp4'
|
|||
mime_ext_lookup[ VIDEO_MPEG ] = '.mpeg'
|
||||
mime_ext_lookup[ VIDEO_WMV ] = '.wmv'
|
||||
mime_ext_lookup[ VIDEO_MKV ] = '.mkv'
|
||||
mime_ext_lookup[ VIDEO_REALMEDIA ] = '.rm'
|
||||
mime_ext_lookup[ VIDEO_WEBM ] = '.webm'
|
||||
mime_ext_lookup[ APPLICATION_UNKNOWN ] = ''
|
||||
#mime_ext_lookup[ 'application/x-rar-compressed' ] = '.rar'
|
||||
|
|
|
@ -180,7 +180,7 @@ def GetFileInfo( path, mime = None, ok_to_look_for_hydrus_updates = False ):
|
|||
|
||||
( ( width, height ), duration, num_frames ) = HydrusFlashHandling.GetFlashProperties( path )
|
||||
|
||||
elif mime in ( HC.IMAGE_APNG, HC.VIDEO_AVI, HC.VIDEO_FLV, HC.VIDEO_WMV, HC.VIDEO_MOV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_WEBM, HC.VIDEO_MPEG ):
|
||||
elif mime in ( HC.IMAGE_APNG, HC.VIDEO_AVI, HC.VIDEO_FLV, HC.VIDEO_WMV, HC.VIDEO_MOV, HC.VIDEO_MP4, HC.VIDEO_MKV, HC.VIDEO_REALMEDIA, HC.VIDEO_WEBM, HC.VIDEO_MPEG ):
|
||||
|
||||
( ( width, height ), duration, num_frames ) = HydrusVideoHandling.GetFFMPEGVideoProperties( path )
|
||||
|
||||
|
|
|
@ -436,7 +436,7 @@ class HydrusResource( Resource ):
|
|||
|
||||
mime = response_context.GetMime()
|
||||
|
||||
content_type = HC.mime_string_lookup[ mime ]
|
||||
content_type = HC.mime_mimetype_string_lookup[ mime ]
|
||||
|
||||
content_length = size
|
||||
|
||||
|
@ -465,7 +465,7 @@ class HydrusResource( Resource ):
|
|||
|
||||
body_bytes = response_context.GetBodyBytes()
|
||||
|
||||
content_type = HC.mime_string_lookup[ mime ]
|
||||
content_type = HC.mime_mimetype_string_lookup[ mime ]
|
||||
|
||||
content_length = len( body_bytes )
|
||||
|
||||
|
|
|
@ -355,6 +355,10 @@ def GetMime( path ):
|
|||
|
||||
return HC.AUDIO_MP3
|
||||
|
||||
elif mime_text == 'tta':
|
||||
|
||||
return HC.AUDIO_TRUEAUDIO
|
||||
|
||||
elif 'mp4' in mime_text:
|
||||
|
||||
if has_audio and ( not has_video or 'mjpeg' in video_format ):
|
||||
|
@ -370,6 +374,18 @@ def GetMime( path ):
|
|||
|
||||
return HC.AUDIO_OGG
|
||||
|
||||
|
||||
elif 'rm' in mime_text:
|
||||
|
||||
if ParseFFMPEGHasVideo( lines ):
|
||||
|
||||
return HC.VIDEO_REALMEDIA
|
||||
|
||||
else:
|
||||
|
||||
return HC.AUDIO_REALMEDIA
|
||||
|
||||
|
||||
elif mime_text == 'asf':
|
||||
|
||||
if ParseFFMPEGHasVideo( lines ):
|
||||
|
|
|
@ -938,6 +938,7 @@ def AddToLayout( layout, item, flag = None, alignment = None, sizePolicy = None
|
|||
|
||||
item.setSizePolicy( sizePolicy[0], sizePolicy[1] )
|
||||
|
||||
expand_both_ways = flag in ( CC.FLAGS_EXPAND_BOTH_WAYS, CC.FLAGS_EXPAND_BOTH_WAYS_POLITE, CC.FLAGS_EXPAND_BOTH_WAYS_SHY )
|
||||
zero_border = False
|
||||
|
||||
# This is kind of a mess right now, adjustments might be needed
|
||||
|
@ -957,30 +958,30 @@ def AddToLayout( layout, item, flag = None, alignment = None, sizePolicy = None
|
|||
#item.setContentsMargins( 2, 2, 2, 2 )
|
||||
#wx.SizerFlags( 0 ).Border( wx.ALL, 2 )
|
||||
elif flag == CC.FLAGS_EXPAND_PERPENDICULAR:
|
||||
pass
|
||||
#if isinstance( item, QW.QWidget ): item.setSizePolicy( QW.QSizePolicy.Expanding, QW.QSizePolicy.Expanding )
|
||||
|
||||
if isinstance( item, QW.QWidget ):
|
||||
|
||||
if isinstance( layout, QW.QHBoxLayout ):
|
||||
|
||||
h_policy = QW.QSizePolicy.Fixed
|
||||
v_policy = QW.QSizePolicy.Expanding
|
||||
|
||||
else:
|
||||
|
||||
h_policy = QW.QSizePolicy.Expanding
|
||||
v_policy = QW.QSizePolicy.Fixed
|
||||
|
||||
|
||||
item.setSizePolicy( h_policy, v_policy )
|
||||
|
||||
|
||||
#wx.SizerFlags( 0 ).Border( wx.ALL, 2 ).Expand()
|
||||
#item.setContentsMargins( 2, 2, 2, 2 )
|
||||
elif flag == CC.FLAGS_EXPAND_BOTH_WAYS:
|
||||
#if isinstance( item, QW.QWidget ): item.setSizePolicy( QW.QSizePolicy.Expanding, QW.QSizePolicy.Expanding )
|
||||
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ): layout.setStretchFactor( item, 5 )
|
||||
#item.setContentsMargins( 2, 2, 2, 2 )
|
||||
#wx.SizerFlags( 5 ).Border( wx.ALL, 2 ).Expand()
|
||||
elif flag == CC.FLAGS_EXPAND_DEPTH_ONLY:
|
||||
#if isinstance( item, QW.QWidget ): item.setSizePolicy( item.sizePolicy().verticalPolicy(), QW.QSizePolicy.Expanding )
|
||||
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ): layout.setStretchFactor( item, 5 )
|
||||
#item.setContentsMargins( 2, 2, 2, 2 )
|
||||
#wx.SizerFlags( 5 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_CENTER_VERTICAL )
|
||||
elif flag == CC.FLAGS_EXPAND_BOTH_WAYS_POLITE:
|
||||
if isinstance( item, QW.QWidget ): item.setSizePolicy( item.sizePolicy().verticalPolicy(), QW.QSizePolicy.Expanding )
|
||||
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ): layout.setStretchFactor( item, 3 )
|
||||
#item.setContentsMargins( 2, 2, 2, 2 )
|
||||
#wx.SizerFlags( 3 ).Border( wx.ALL, 2 ).Expand()
|
||||
elif flag == CC.FLAGS_EXPAND_BOTH_WAYS_SHY:
|
||||
if isinstance( item, QW.QWidget ): item.setSizePolicy( QW.QSizePolicy.Expanding, QW.QSizePolicy.Expanding )
|
||||
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ): layout.setStretchFactor( item, 1 )
|
||||
#item.setContentsMargins( 2, 2, 2, 2 )
|
||||
#wx.SizerFlags( 1 ).Border( wx.ALL, 2 ).Expand()
|
||||
elif flag == CC.FLAGS_SIZER_CENTER:
|
||||
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ): layout.setStretchFactor( item, 5 )
|
||||
layout.setAlignment( item, QC.Qt.AlignHCenter | QC.Qt.AlignVCenter )
|
||||
|
@ -992,7 +993,7 @@ def AddToLayout( layout, item, flag = None, alignment = None, sizePolicy = None
|
|||
#wx.SizerFlags( 0 ).Expand()
|
||||
#item.setContentsMargins( 0, 0, 0, 0 )
|
||||
elif flag == CC.FLAGS_EXPAND_SIZER_BOTH_WAYS:
|
||||
|
||||
zero_border = True
|
||||
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ):
|
||||
|
||||
layout.setStretchFactor( item, 5 )
|
||||
|
@ -1034,9 +1035,33 @@ def AddToLayout( layout, item, flag = None, alignment = None, sizePolicy = None
|
|||
zero_border = True
|
||||
#item.setContentsMargins( 2, 2, 2, 2 )
|
||||
#wx.SizerFlags( 5 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_CENTER_VERTICAL )
|
||||
else:
|
||||
|
||||
if expand_both_ways:
|
||||
|
||||
if isinstance( item, QW.QWidget ):
|
||||
|
||||
item.setSizePolicy( QW.QSizePolicy.Expanding, QW.QSizePolicy.Expanding )
|
||||
|
||||
|
||||
if isinstance( layout, QW.QVBoxLayout ) or isinstance( layout, QW.QHBoxLayout ):
|
||||
|
||||
if flag == CC.FLAGS_EXPAND_BOTH_WAYS:
|
||||
|
||||
stretch_factor = 5
|
||||
|
||||
elif flag == CC.FLAGS_EXPAND_BOTH_WAYS_POLITE:
|
||||
|
||||
stretch_factor = 3
|
||||
|
||||
elif flag == CC.FLAGS_EXPAND_BOTH_WAYS_SHY:
|
||||
|
||||
stretch_factor = 1
|
||||
|
||||
|
||||
layout.setStretchFactor( item, stretch_factor )
|
||||
|
||||
|
||||
|
||||
raise ValueError( 'Unknown legacy sizer flag' )
|
||||
|
||||
|
||||
if zero_border:
|
||||
|
|
|
@ -68,6 +68,8 @@ def ProcessStartingAction( db_dir, action ):
|
|||
|
||||
else:
|
||||
|
||||
HydrusData.Print( 'Did not find an already running instance of the server--changing boot command from \'restart\' to \'start\'.' )
|
||||
|
||||
return 'start'
|
||||
|
||||
|
||||
|
|
|
@ -306,7 +306,7 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
# fail
|
||||
|
||||
headers = { 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
headers = { 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
|
||||
hash = os.urandom( 32 )
|
||||
hash_hex = hash.hex()
|
||||
|
@ -359,7 +359,7 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
session_key_hex = body_dict[ 'session_key' ]
|
||||
|
||||
headers = { 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
headers = { 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
|
||||
hash = os.urandom( 32 )
|
||||
hash_hex = hash.hex()
|
||||
|
@ -455,7 +455,7 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
HG.test_controller.SetRead( 'hash_status', ( CC.STATUS_UNKNOWN, None, '' ) )
|
||||
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_OCTET_STREAM ] }
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_OCTET_STREAM ] }
|
||||
|
||||
path = '/add_files/add_file'
|
||||
|
||||
|
@ -486,7 +486,7 @@ class TestClientAPI( unittest.TestCase ):
|
|||
HYDRUS_PNG_BYTES = f.read()
|
||||
|
||||
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_OCTET_STREAM ] }
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_OCTET_STREAM ] }
|
||||
|
||||
path = '/add_files/add_file'
|
||||
|
||||
|
@ -510,7 +510,7 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
# do hydrus png as path
|
||||
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
|
||||
path = '/add_files/add_file'
|
||||
|
||||
|
@ -600,7 +600,7 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
# add tags
|
||||
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
|
||||
hash = os.urandom( 32 )
|
||||
hash_hex = hash.hex()
|
||||
|
@ -900,9 +900,6 @@ class TestClientAPI( unittest.TestCase ):
|
|||
expected_answer[ 'normalised_url' ] = normalised_url
|
||||
expected_answer[ 'url_file_statuses' ] = json_url_file_statuses
|
||||
|
||||
HydrusData.Print( d )
|
||||
HydrusData.Print( expected_answer )
|
||||
|
||||
self.assertEqual( d, expected_answer )
|
||||
|
||||
# get url info
|
||||
|
@ -1006,7 +1003,7 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
HG.test_controller.ClearWrites( 'import_url_test' )
|
||||
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
|
||||
url = 'http://8ch.net/tv/res/1846574.html'
|
||||
|
||||
|
@ -1258,7 +1255,7 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
#
|
||||
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
|
||||
path = '/manage_cookies/set_cookies'
|
||||
|
||||
|
@ -1304,7 +1301,7 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
#
|
||||
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
|
||||
path = '/manage_cookies/set_cookies'
|
||||
|
||||
|
@ -1382,7 +1379,7 @@ class TestClientAPI( unittest.TestCase ):
|
|||
|
||||
#
|
||||
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
headers = { 'Hydrus-Client-API-Access-Key' : access_key_hex, 'Content-Type' : HC.mime_mimetype_string_lookup[ HC.APPLICATION_JSON ] }
|
||||
|
||||
path = '/manage_pages/focus_page'
|
||||
|
||||
|
@ -1626,7 +1623,7 @@ class TestClientAPI( unittest.TestCase ):
|
|||
metadata_row[ 'file_id' ] = file_info_manager.hash_id
|
||||
metadata_row[ 'hash' ] = file_info_manager.hash.hex()
|
||||
metadata_row[ 'size' ] = file_info_manager.size
|
||||
metadata_row[ 'mime' ] = HC.mime_string_lookup[ file_info_manager.mime ]
|
||||
metadata_row[ 'mime' ] = HC.mime_mimetype_string_lookup[ file_info_manager.mime ]
|
||||
metadata_row[ 'width' ] = file_info_manager.width
|
||||
metadata_row[ 'height' ] = file_info_manager.height
|
||||
metadata_row[ 'duration' ] = file_info_manager.duration
|
||||
|
|
|
@ -290,7 +290,7 @@ class TestClientDB( unittest.TestCase ):
|
|||
self.assertEqual( written_note, '' )
|
||||
self.assertEqual( file_import_job.GetHash(), hash )
|
||||
|
||||
time.sleep( 1 )
|
||||
time.sleep( 1.1 ) # to get timestamps right
|
||||
|
||||
#
|
||||
|
||||
|
@ -414,6 +414,8 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
self._write( 'content_updates', service_keys_to_content_updates )
|
||||
|
||||
time.sleep( 0.5 )
|
||||
|
||||
#
|
||||
|
||||
tests = []
|
||||
|
|
|
@ -528,13 +528,13 @@ class TestTagObjects( unittest.TestCase ):
|
|||
|
||||
p = ClientSearch.Predicate( HC.PREDICATE_TYPE_SYSTEM_MIME, ( HC.VIDEO_WEBM, ) )
|
||||
|
||||
self.assertEqual( p.ToString(), 'system:filetype is video/webm' )
|
||||
self.assertEqual( p.ToString(), 'system:filetype is webm' )
|
||||
self.assertEqual( p.GetNamespace(), 'system' )
|
||||
self.assertEqual( p.GetTextsAndNamespaces(), [ ( p.ToString(), p.GetNamespace() ) ] )
|
||||
|
||||
p = ClientSearch.Predicate( HC.PREDICATE_TYPE_SYSTEM_MIME, ( HC.VIDEO_WEBM, HC.IMAGE_GIF ) )
|
||||
|
||||
self.assertEqual( p.ToString(), 'system:filetype is video/webm, image/gif' )
|
||||
self.assertEqual( p.ToString(), 'system:filetype is webm, gif' )
|
||||
self.assertEqual( p.GetNamespace(), 'system' )
|
||||
self.assertEqual( p.GetTextsAndNamespaces(), [ ( p.ToString(), p.GetNamespace() ) ] )
|
||||
|
||||
|
|
11
server.py
|
@ -115,7 +115,16 @@ try:
|
|||
|
||||
#
|
||||
|
||||
action = ServerController.ProcessStartingAction( db_dir, action )
|
||||
try:
|
||||
|
||||
action = ServerController.ProcessStartingAction( db_dir, action )
|
||||
|
||||
except HydrusExceptions.ShutdownException as e:
|
||||
|
||||
HydrusData.Print( e )
|
||||
|
||||
action = 'exit'
|
||||
|
||||
|
||||
if action == 'exit':
|
||||
|
||||
|
|
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.6 KiB |