parent
07980588ae
commit
7bfe474a5d
|
@ -7,6 +7,38 @@ title: Changelog
|
|||
!!! note
|
||||
This is the new changelog, only the most recent builds. For all versions, see the [old changelog](old_changelog.html).
|
||||
|
||||
## [Version 526](https://github.com/hydrusnetwork/hydrus/releases/tag/v526)
|
||||
|
||||
### there will be an important update next week
|
||||
|
||||
* next week's release will have two important program changes--I will integrate an OpenCV update, which will require 'extract' users to perform a clean install, and the executables are finally changing from 'client' and 'server' to 'hydrus_client' and 'hydrus_server'! be prepared to update your shortcuts and launch scripts
|
||||
|
||||
### time
|
||||
|
||||
* fixed a stupid logical bug in my new date code, which was throwing errors on system:time predicates that had a month value equal to the current month (e.g. 'x years, 5 months' during May)--sorry! (issue #1362)
|
||||
* when a subscription dies, the popup note about it says the death velocity period in the neat '180 days', as you set in UI, rather than converting to a date and stating the number of months and days using the recent calendar calculation updates
|
||||
* I unified some more 'xxxified date' UI labels to be 'xxxified time'. we're generally moving to the latter format as the ideal while still accepting various combinations for system parsing input
|
||||
|
||||
### shortcuts
|
||||
|
||||
* added 'media play-pause/previous/next' and 'volume up/down/mute' key recognition to the shortcut system. if your keyboard/headphones have media keys, they _should_ be mappable now. note, however, that, at least on Windows, while these capture in the hydrus UI, they seem to have global OS-level hooks, and as far as I can tell Qt can't stop that event propagating, so these may have limited effectiveness if you also have an mp3 player open, since Windows will also send the 'next' call to that etc... it may be there is a nice way to properly register a Qt app as a media thing for Windows to global-hook these events to, but I'm not sure!
|
||||
* also added 'mouse task button' to the mappable buttons. this is apparently a common Mouse6 mapping, so if you have it, knock yourself out
|
||||
* the code in the shortcut system that tries to detect and merge many small scroll wheel events (such as the emulated scroll that a trackpad may generate) now applies to all mouse devices, not just synthesised events. with luck, this will mean that mice that generate like 15 smoothscroll events of one degree instead of one of fifteen degrees for every wheel tick will no longer spam-navigate the media viewer wew
|
||||
|
||||
### misc
|
||||
|
||||
* to save you typing/pasting time, the 'enter your reason' prompts in manage tags, tag siblings, and tag parents now remember the last five custom reasons you enter! you can change the number saved using the new option under _options->tags_, including setting it to 0 to disable the system
|
||||
* fixed pasting tags in the manage tags dialog when the number of tags you are pasting is larger than the number of allowed 'recent tags'. previously it was saying 'did not understand what was in the clipboard', so hooray for the new error reporting
|
||||
* every multi-column list in the program now has a 'reset column widths' item in its header right-click menu! when these reset events happen, the respective lists also resize themselves immediately, no restart required
|
||||
* when you set 'try again' on an import object, it now clears all saved hashes from the import object (including the SHA256 which may have been linked from the database in an 'already in db'/'previously deleted' result). this will ensure the next attempt is not poisoned by these hashes (which can happen for various reasons) in the subsequent attempt. basically 'try again' resets better now (issue #1353)
|
||||
|
||||
### some build stuff
|
||||
|
||||
* the main build script now only uses Node16 sub-Actions (Node12 support is deprecated and being dropped in June)
|
||||
* the main build script no longer uses set-output commands (these are deprecated and being dropped later in the year I think, in favour of some ENV stuff)
|
||||
* tidied some cruft from the main build script
|
||||
* I moved the 'new' python-mpv in the requirements.txts from 1.0.1 to 1.0.3. source users might like to rebuild their venvs again, particularly Windows users who updated to the new mpv dll recently
|
||||
|
||||
## [Version 525](https://github.com/hydrusnetwork/hydrus/releases/tag/v525)
|
||||
|
||||
### library updates
|
||||
|
@ -358,26 +390,3 @@ title: Changelog
|
|||
* cleaned up how popup file buttons are set and cleared
|
||||
* cleaned up how popup main and secondary texts are set and cleared
|
||||
* misc linting cleanup
|
||||
|
||||
## [Version 516](https://github.com/hydrusnetwork/hydrus/releases/tag/v516)
|
||||
|
||||
### misc
|
||||
|
||||
* the 'manage sidecar routers' control, which is on manage import folders, manage export folders, path-tagging-before-manual-import, and manual export files, now has import/export/duplicate buttons. you can save and transfer your work now! if you try to import 'export to sidecar' routers to an 'import from sidecar' context or _vice versa_, it should give you a nicely worded error
|
||||
* fixed the error that was raising when you turn related tags off with the suggestions set to side-by-side layout. very sorry for the trouble!
|
||||
* apngs that are set to 'loop x times' (usually once) now only loop that many times, on both mpv and my native renderer! like gifs, the 'always loop animations' setting under _options->media_ overrides it!
|
||||
* fixed an issue with my native renderer not updating on scanbar scrubs very well. should be back to nice smooth instant draw as you scrub
|
||||
* thanks to a user, folded in another deviant art parser update to the defaults
|
||||
* updated the setuptools version in the requirements.txt due to a security note--I don't think the problem (which was about some vulnerable regex when fetching malicious package info) applies to us, but running from source users might like to run setup_venv again this week anyway
|
||||
|
||||
### related tags
|
||||
|
||||
* a new 'concurrence threshold' setting under _options->tag suggestions_ allows you to set how 'strict' the related tags search is. a higher percentage causes fewer but more relevant results. I'm increasing the default this week from 4% to 6%
|
||||
* two new 'namespace to weight' settings under _options->tag suggestions_ now manage how much weight the 'search' and 'suggestion' sides of related tags have. you can say 'rank the suggestions from character tags highly' or 'rank unnamespaced suggestions lower', and 'do not search x tags' and 'do not suggest y tags'. I have prepped it with some 'creator/character/series namespaces are better than unnamespaced, and title/filename/page/chapter/volume are useless' defaults, but feel free to play around with it
|
||||
* the related tags algorithm takes a larger sample now, resulting in a _little_ less ranking-variability
|
||||
|
||||
### client api
|
||||
|
||||
* changed and fixed an issue in the client api's new `get_file_relationships` call. previously, I said 'king' would be null if it was not on the given file domain, but this was not working correctly--it was giving pseudorandom 'fallback' kings. now it always gives the king, no matter what! a new param, `king_is_on_file_domain` says whether the king is on the given domain. `king_is_local` says whether the king is available on disk
|
||||
* added some discussion and a list of the 8 possible 'better than' and 'same quality' logical combinations to the `set_file_relationships` help so you can see how group merge involving non-kings works
|
||||
* client api is now version 42
|
||||
|
|
|
@ -34,6 +34,31 @@
|
|||
<div class="content">
|
||||
<h1 id="changelog"><a href="#changelog">changelog</a></h1>
|
||||
<ul>
|
||||
<li>
|
||||
<h2 id="version_526"><a href="#version_526">version 526</a></h2>
|
||||
<ul>
|
||||
<li><h3>there will be an important update next week</h3></li>
|
||||
<li>next week's release will have two important program changes--I will integrate an OpenCV update, which will require 'extract' users to perform a clean install, and the executables are finally changing from 'client' and 'server' to 'hydrus_client' and 'hydrus_server'! be prepared to update your shortcuts and launch scripts</li>
|
||||
<li><h3>time</h3></li>
|
||||
<li>fixed a stupid logical bug in my new date code, which was throwing errors on system:time predicates that had a month value equal to the current month (e.g. 'x years, 5 months' during May)--sorry! (issue #1362)</li>
|
||||
<li>when a subscription dies, the popup note about it says the death velocity period in the neat '180 days', as you set in UI, rather than converting to a date and stating the number of months and days using the recent calendar calculation updates</li>
|
||||
<li>I unified some more 'xxxified date' UI labels to be 'xxxified time'. we're generally moving to the latter format as the ideal while still accepting various combinations for system parsing input</li>
|
||||
<li><h3>shortcuts</h3></li>
|
||||
<li>added 'media play-pause/previous/next' and 'volume up/down/mute' key recognition to the shortcut system. if your keyboard/headphones have media keys, they _should_ be mappable now. note, however, that, at least on Windows, while these capture in the hydrus UI, they seem to have global OS-level hooks, and as far as I can tell Qt can't stop that event propagating, so these may have limited effectiveness if you also have an mp3 player open, since Windows will also send the 'next' call to that etc... it may be there is a nice way to properly register a Qt app as a media thing for Windows to global-hook these events to, but I'm not sure!</li>
|
||||
<li>also added 'mouse task button' to the mappable buttons. this is apparently a common Mouse6 mapping, so if you have it, knock yourself out</li>
|
||||
<li>the code in the shortcut system that tries to detect and merge many small scroll wheel events (such as the emulated scroll that a trackpad may generate) now applies to all mouse devices, not just synthesised events. with luck, this will mean that mice that generate like 15 smoothscroll events of one degree instead of one of fifteen degrees for every wheel tick will no longer spam-navigate the media viewer wew</li>
|
||||
<li><h3>misc</h3></li>
|
||||
<li>to save you typing/pasting time, the 'enter your reason' prompts in manage tags, tag siblings, and tag parents now remember the last five custom reasons you enter! you can change the number saved using the new option under _options->tags_, including setting it to 0 to disable the system</li>
|
||||
<li>fixed pasting tags in the manage tags dialog when the number of tags you are pasting is larger than the number of allowed 'recent tags'. previously it was saying 'did not understand what was in the clipboard', so hooray for the new error reporting</li>
|
||||
<li>every multi-column list in the program now has a 'reset column widths' item in its header right-click menu! when these reset events happen, the respective lists also resize themselves immediately, no restart required</li>
|
||||
<li>when you set 'try again' on an import object, it now clears all saved hashes from the import object (including the SHA256 which may have been linked from the database in an 'already in db'/'previously deleted' result). this will ensure the next attempt is not poisoned by these hashes (which can happen for various reasons) in the subsequent attempt. basically 'try again' resets better now (issue #1353)</li>
|
||||
<li><h3>some build stuff</h3></li>
|
||||
<li>the main build script now only uses Node16 sub-Actions (Node12 support is deprecated and being dropped in June)</li>
|
||||
<li>the main build script no longer uses set-output commands (these are deprecated and being dropped later in the year I think, in favour of some ENV stuff)</li>
|
||||
<li>tidied some cruft from the main build script</li>
|
||||
<li>I moved the 'new' python-mpv in the requirements.txts from 1.0.1 to 1.0.3. source users might like to rebuild their venvs again, particularly Windows users who updated to the new mpv dll recently</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h2 id="version_525"><a href="#version_525">version 525</a></h2>
|
||||
<ul>
|
||||
|
|
|
@ -67,7 +67,7 @@ regen_file_enum_to_str_lookup = {
|
|||
REGENERATE_FILE_DATA_JOB_FIX_PERMISSIONS : 'fix file read/write permissions',
|
||||
REGENERATE_FILE_DATA_JOB_CHECK_SIMILAR_FILES_MEMBERSHIP : 'check for membership in the similar files search system',
|
||||
REGENERATE_FILE_DATA_JOB_SIMILAR_FILES_METADATA : 'regenerate similar files metadata',
|
||||
REGENERATE_FILE_DATA_JOB_FILE_MODIFIED_TIMESTAMP : 'regenerate file modified date',
|
||||
REGENERATE_FILE_DATA_JOB_FILE_MODIFIED_TIMESTAMP : 'regenerate file modified time',
|
||||
REGENERATE_FILE_DATA_JOB_FILE_HAS_EXIF : 'determine if the file has EXIF metadata',
|
||||
REGENERATE_FILE_DATA_JOB_FILE_HAS_HUMAN_READABLE_EMBEDDED_METADATA : 'determine if the file has non-EXIF human-readable embedded metadata',
|
||||
REGENERATE_FILE_DATA_JOB_FILE_HAS_ICC_PROFILE : 'determine if the file has an icc profile',
|
||||
|
|
|
@ -325,6 +325,10 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
#
|
||||
|
||||
self._dictionary[ 'recent_petition_reasons' ] = HydrusSerialisable.SerialisableDictionary()
|
||||
|
||||
#
|
||||
|
||||
self._dictionary[ 'duplicate_action_options' ] = HydrusSerialisable.SerialisableDictionary()
|
||||
|
||||
duplicate_content_merge_options = ClientDuplicates.DuplicateContentMergeOptions()
|
||||
|
@ -392,6 +396,8 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
self._dictionary[ 'integers' ][ 'default_new_page_goes' ] = CC.NEW_PAGE_GOES_FAR_RIGHT
|
||||
|
||||
self._dictionary[ 'integers' ][ 'num_recent_petition_reasons' ] = 5
|
||||
|
||||
self._dictionary[ 'integers' ][ 'max_page_name_chars' ] = 20
|
||||
self._dictionary[ 'integers' ][ 'page_file_count_display' ] = CC.PAGE_FILE_COUNT_DISPLAY_ALL
|
||||
|
||||
|
@ -1377,6 +1383,14 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
return self._dictionary[ name ]
|
||||
|
||||
|
||||
def GetRecentPetitionReasons( self, content_type, action ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
return list( self._dictionary[ 'recent_petition_reasons' ].get( ( content_type, action ), [] ) )[ : self._dictionary[ 'integers' ][ 'num_recent_petition_reasons' ] ]
|
||||
|
||||
|
||||
|
||||
def GetRecentPredicates( self, predicate_types ):
|
||||
|
||||
with self._lock:
|
||||
|
@ -1467,6 +1481,30 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
|
||||
|
||||
def PushRecentPetitionReason( self, content_type, action, reason ):
|
||||
|
||||
with self._lock:
|
||||
|
||||
key = ( content_type, action )
|
||||
|
||||
if key not in self._dictionary[ 'recent_petition_reasons' ]:
|
||||
|
||||
self._dictionary[ 'recent_petition_reasons' ][ key ] = []
|
||||
|
||||
|
||||
reasons = self._dictionary[ 'recent_petition_reasons' ][ key ]
|
||||
|
||||
if reason in reasons:
|
||||
|
||||
reasons.remove( reason )
|
||||
|
||||
|
||||
reasons.insert( 0, reason )
|
||||
|
||||
self._dictionary[ 'recent_petition_reasons' ][ key ] = reasons[ : self._dictionary[ 'integers' ][ 'num_recent_petition_reasons' ] ]
|
||||
|
||||
|
||||
|
||||
def PushRecentPredicates( self, predicates ):
|
||||
|
||||
with self._lock:
|
||||
|
|
|
@ -2476,7 +2476,7 @@ class Predicate( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_LAST_VIEWED_TIME:
|
||||
|
||||
base = 'last view time'
|
||||
base = 'last viewed time'
|
||||
|
||||
elif self._predicate_type == PREDICATE_TYPE_SYSTEM_MODIFIED_TIME:
|
||||
|
||||
|
|
|
@ -1329,7 +1329,7 @@ class EditDuplicateContentMergeOptionsPanel( ClientGUIScrolledPanels.EditPanel )
|
|||
rows = []
|
||||
|
||||
rows.append( ( 'sync archived status?: ', self._sync_archive_action ) )
|
||||
rows.append( ( 'sync file modified date?: ', self._sync_file_modified_date_action ) )
|
||||
rows.append( ( 'sync file modified time?: ', self._sync_file_modified_date_action ) )
|
||||
rows.append( ( 'sync known urls?: ', self._sync_urls_action ) )
|
||||
rows.append( ( 'sync notes?: ', self._sync_notes_action ) )
|
||||
rows.append( ( '', self._sync_note_import_options_button ) )
|
||||
|
@ -2713,11 +2713,11 @@ class EditFileTimestampsPanel( CAC.ApplicationCommandProcessorMixin, ClientGUISc
|
|||
|
||||
if HydrusPaths.FileModifiedTimeIsOk( timestamp_data.timestamp ):
|
||||
|
||||
self._file_modified_timestamp_warning_st.setText( 'This will also change the modified date of the file on disk!' )
|
||||
self._file_modified_timestamp_warning_st.setText( 'This will also change the modified time of the file on disk!' )
|
||||
|
||||
else:
|
||||
|
||||
self._file_modified_timestamp_warning_st.setText( 'File modified date on disk will not be changed--the timestamp is too early.' )
|
||||
self._file_modified_timestamp_warning_st.setText( 'File modified time on disk will not be changed--the timestamp is too early.' )
|
||||
|
||||
|
||||
return
|
||||
|
|
|
@ -3570,6 +3570,10 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._show_sibling_decorators_on_storage_taglists = QW.QCheckBox( general_panel )
|
||||
self._show_sibling_decorators_on_storage_autocomplete_taglists = QW.QCheckBox( general_panel )
|
||||
|
||||
self._num_recent_petition_reasons = ClientGUICommon.BetterSpinBox( general_panel, initial = 5, min = 0, max = 100 )
|
||||
tt = 'In manage tags, tag siblings, and tag parents, you may be asked to provide a reason with a petition you make to a hydrus repository. There are some fixed reasons, but the dialog can also remember what you recently typed. This controls how many recent reasons it will remember.'
|
||||
self._num_recent_petition_reasons.setToolTip( tt )
|
||||
|
||||
self._ac_select_first_with_count = QW.QCheckBox( general_panel )
|
||||
|
||||
#
|
||||
|
@ -3606,6 +3610,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._show_sibling_decorators_on_storage_autocomplete_taglists.setChecked( self._new_options.GetBoolean( 'show_sibling_decorators_on_storage_autocomplete_taglists' ) )
|
||||
self._show_sibling_decorators_on_storage_autocomplete_taglists.setToolTip( 'This affects the autocomplete results taglist.' )
|
||||
|
||||
self._num_recent_petition_reasons.setValue( self._new_options.GetInteger( 'num_recent_petition_reasons' ) )
|
||||
|
||||
self._ac_select_first_with_count.setChecked( self._new_options.GetBoolean( 'ac_select_first_with_count' ) )
|
||||
|
||||
#
|
||||
|
@ -3624,6 +3630,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
rows.append( ( 'Show parents expanded by default on edit/write autocomplete taglists: ', self._expand_parents_on_storage_autocomplete_taglists ) )
|
||||
rows.append( ( 'Show sibling info by default on edit/write taglists: ', self._show_sibling_decorators_on_storage_taglists ) )
|
||||
rows.append( ( 'Show sibling info by default on edit/write autocomplete taglists: ', self._show_sibling_decorators_on_storage_autocomplete_taglists ) )
|
||||
rows.append( ( 'Number of recent petition reasons to remember in dialogs: ', self._num_recent_petition_reasons ) )
|
||||
rows.append( ( 'By default, select the first tag result with actual count in write-autocomplete: ', self._ac_select_first_with_count ) )
|
||||
|
||||
gridbox = ClientGUICommon.WrapInGrid( general_panel, rows )
|
||||
|
@ -3670,6 +3677,8 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
self._new_options.SetBoolean( 'show_sibling_decorators_on_storage_taglists', self._show_sibling_decorators_on_storage_taglists.isChecked() )
|
||||
self._new_options.SetBoolean( 'show_sibling_decorators_on_storage_autocomplete_taglists', self._show_sibling_decorators_on_storage_autocomplete_taglists.isChecked() )
|
||||
|
||||
self._new_options.SetInteger( 'num_recent_petition_reasons', self._num_recent_petition_reasons.value() )
|
||||
|
||||
self._new_options.SetBoolean( 'ac_select_first_with_count', self._ac_select_first_with_count.isChecked() )
|
||||
|
||||
#
|
||||
|
|
|
@ -70,6 +70,12 @@ SHORTCUT_KEY_SPECIAL_F9 = 25
|
|||
SHORTCUT_KEY_SPECIAL_F10 = 26
|
||||
SHORTCUT_KEY_SPECIAL_F11 = 27
|
||||
SHORTCUT_KEY_SPECIAL_F12 = 28
|
||||
SHORTCUT_KEY_SPECIAL_MEDIA_PLAY_PAUSE = 29
|
||||
SHORTCUT_KEY_SPECIAL_MEDIA_PREVIOUS = 30
|
||||
SHORTCUT_KEY_SPECIAL_MEDIA_NEXT = 31
|
||||
SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_DOWN = 32
|
||||
SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_UP = 33
|
||||
SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_MUTE_UNMUTE = 32
|
||||
|
||||
if HC.PLATFORM_MACOS:
|
||||
|
||||
|
@ -111,7 +117,15 @@ special_key_shortcut_enum_lookup = {
|
|||
QC.Qt.Key_F9 : SHORTCUT_KEY_SPECIAL_F9,
|
||||
QC.Qt.Key_F10 : SHORTCUT_KEY_SPECIAL_F10,
|
||||
QC.Qt.Key_F11 : SHORTCUT_KEY_SPECIAL_F11,
|
||||
QC.Qt.Key_F12 : SHORTCUT_KEY_SPECIAL_F12
|
||||
QC.Qt.Key_F12 : SHORTCUT_KEY_SPECIAL_F12,
|
||||
QC.Qt.Key_MediaTogglePlayPause : SHORTCUT_KEY_SPECIAL_MEDIA_PLAY_PAUSE,
|
||||
QC.Qt.Key_MediaPlay : SHORTCUT_KEY_SPECIAL_MEDIA_PLAY_PAUSE,
|
||||
QC.Qt.Key_MediaPause : SHORTCUT_KEY_SPECIAL_MEDIA_PLAY_PAUSE,
|
||||
QC.Qt.Key_MediaPrevious : SHORTCUT_KEY_SPECIAL_MEDIA_PREVIOUS,
|
||||
QC.Qt.Key_MediaNext : SHORTCUT_KEY_SPECIAL_MEDIA_NEXT,
|
||||
QC.Qt.Key_VolumeDown : SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_DOWN,
|
||||
QC.Qt.Key_VolumeUp : SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_UP,
|
||||
QC.Qt.Key_VolumeMute : SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_MUTE_UNMUTE
|
||||
}
|
||||
|
||||
special_key_shortcut_str_lookup = {
|
||||
|
@ -143,7 +157,13 @@ special_key_shortcut_str_lookup = {
|
|||
SHORTCUT_KEY_SPECIAL_F9 : 'f9',
|
||||
SHORTCUT_KEY_SPECIAL_F10 : 'f10',
|
||||
SHORTCUT_KEY_SPECIAL_F11 : 'f11',
|
||||
SHORTCUT_KEY_SPECIAL_F12 : 'f12'
|
||||
SHORTCUT_KEY_SPECIAL_F12 : 'f12',
|
||||
SHORTCUT_KEY_SPECIAL_MEDIA_PLAY_PAUSE : 'media: play/pause',
|
||||
SHORTCUT_KEY_SPECIAL_MEDIA_PREVIOUS : 'media: previous',
|
||||
SHORTCUT_KEY_SPECIAL_MEDIA_NEXT : 'media: next',
|
||||
SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_DOWN : 'volume down',
|
||||
SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_UP : 'volume up',
|
||||
SHORTCUT_KEY_SPECIAL_MEDIA_VOLUME_MUTE_UNMUTE : 'volume mute/unmute'
|
||||
}
|
||||
|
||||
SHORTCUT_MOUSE_LEFT = 0
|
||||
|
@ -155,15 +175,17 @@ SHORTCUT_MOUSE_SCROLL_LEFT = 5
|
|||
SHORTCUT_MOUSE_SCROLL_RIGHT = 6
|
||||
SHORTCUT_MOUSE_BACK = 7
|
||||
SHORTCUT_MOUSE_FORWARD = 8
|
||||
SHORTCUT_MOUSE_TASK = 9
|
||||
|
||||
SHORTCUT_MOUSE_CLICKS = { SHORTCUT_MOUSE_LEFT, SHORTCUT_MOUSE_MIDDLE, SHORTCUT_MOUSE_RIGHT, SHORTCUT_MOUSE_BACK, SHORTCUT_MOUSE_FORWARD }
|
||||
SHORTCUT_MOUSE_CLICKS = { SHORTCUT_MOUSE_LEFT, SHORTCUT_MOUSE_MIDDLE, SHORTCUT_MOUSE_RIGHT, SHORTCUT_MOUSE_BACK, SHORTCUT_MOUSE_FORWARD, SHORTCUT_MOUSE_TASK }
|
||||
|
||||
qt_mouse_buttons_to_hydrus_mouse_buttons = {
|
||||
QC.Qt.LeftButton : SHORTCUT_MOUSE_LEFT,
|
||||
QC.Qt.MiddleButton : SHORTCUT_MOUSE_MIDDLE,
|
||||
QC.Qt.RightButton : SHORTCUT_MOUSE_RIGHT,
|
||||
QC.Qt.BackButton : SHORTCUT_MOUSE_BACK,
|
||||
QC.Qt.ForwardButton : SHORTCUT_MOUSE_FORWARD
|
||||
QC.Qt.ForwardButton : SHORTCUT_MOUSE_FORWARD,
|
||||
QC.Qt.TaskButton : SHORTCUT_MOUSE_TASK
|
||||
}
|
||||
|
||||
shortcut_mouse_string_lookup = {
|
||||
|
@ -172,6 +194,7 @@ shortcut_mouse_string_lookup = {
|
|||
SHORTCUT_MOUSE_MIDDLE : 'middle-click',
|
||||
SHORTCUT_MOUSE_BACK : 'back',
|
||||
SHORTCUT_MOUSE_FORWARD : 'forward',
|
||||
SHORTCUT_MOUSE_TASK : 'task button',
|
||||
SHORTCUT_MOUSE_SCROLL_UP : 'scroll up',
|
||||
SHORTCUT_MOUSE_SCROLL_DOWN : 'scroll down',
|
||||
SHORTCUT_MOUSE_SCROLL_LEFT : 'scroll left',
|
||||
|
@ -387,8 +410,9 @@ def ConvertKeyEventToSimpleTuple( event ):
|
|||
|
||||
return ( modifier, key )
|
||||
|
||||
|
||||
GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS = 0
|
||||
ONE_TICK_ON_A_NORMAL_MOUSE_IN_DEGREES = 15 * 8 # fifteen degrees, in eighths of a degree
|
||||
ONE_TICK_ON_A_NORMAL_MOUSE_IN_EIGHTS_OF_A_DEGREE = 15 * 8 # fifteen degrees, in eighths of a degree
|
||||
|
||||
def ConvertMouseEventToShortcut( event: QG.QMouseEvent ):
|
||||
|
||||
|
@ -440,26 +464,25 @@ def ConvertMouseEventToShortcut( event: QG.QMouseEvent ):
|
|||
|
||||
angle_delta = angle_delta_point.y()
|
||||
|
||||
if QP.WheelEventIsSynthesised( event ):
|
||||
# we used to do QP.WheelEventIsSynthesised here, but it seems some normal mice produce a billion small wheel events for smoothscroll gubbins or something, lfg
|
||||
# so let's just try this tech for everyone
|
||||
if abs( angle_delta ) < ONE_TICK_ON_A_NORMAL_MOUSE_IN_EIGHTS_OF_A_DEGREE:
|
||||
|
||||
if abs( angle_delta ) < ONE_TICK_ON_A_NORMAL_MOUSE_IN_DEGREES:
|
||||
# likely using a trackpad to generate artificial wheel events
|
||||
|
||||
global GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS
|
||||
|
||||
GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS += angle_delta
|
||||
|
||||
if abs( GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS ) > ONE_TICK_ON_A_NORMAL_MOUSE_IN_EIGHTS_OF_A_DEGREE:
|
||||
|
||||
# likely using a trackpad to generate artificial wheel events
|
||||
angle_delta = GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS
|
||||
|
||||
global GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS
|
||||
GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS = 0
|
||||
|
||||
GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS += angle_delta
|
||||
else:
|
||||
|
||||
if abs( GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS ) > ONE_TICK_ON_A_NORMAL_MOUSE_IN_DEGREES:
|
||||
|
||||
angle_delta = GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS
|
||||
|
||||
GLOBAL_MOUSE_SCROLL_DELTA_FOR_TRACKPADS = 0
|
||||
|
||||
else:
|
||||
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2518,12 +2518,16 @@ class ManageTagsPanel( CAC.ApplicationCommandProcessorMixin, ClientGUIScrolledPa
|
|||
|
||||
message = 'Enter a reason for ' + tag_text + ' to be removed. A janitor will review your petition.'
|
||||
|
||||
suggestions = []
|
||||
fixed_suggestions = [
|
||||
'mangled parse/typo',
|
||||
'not applicable/incorrect',
|
||||
'clearing mass-pasted junk',
|
||||
'splitting filename/title/etc... into individual tags'
|
||||
]
|
||||
|
||||
suggestions.append( 'mangled parse/typo' )
|
||||
suggestions.append( 'not applicable/incorrect' )
|
||||
suggestions.append( 'clearing mass-pasted junk' )
|
||||
suggestions.append( 'splitting filename/title/etc... into individual tags' )
|
||||
suggestions = HG.client_controller.new_options.GetRecentPetitionReasons( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_DELETE )
|
||||
|
||||
suggestions.extend( fixed_suggestions )
|
||||
|
||||
with ClientGUIDialogs.DialogTextEntry( self, message, suggestions = suggestions ) as dlg:
|
||||
|
||||
|
@ -2531,6 +2535,11 @@ class ManageTagsPanel( CAC.ApplicationCommandProcessorMixin, ClientGUIScrolledPa
|
|||
|
||||
reason = dlg.GetValue()
|
||||
|
||||
if reason not in fixed_suggestions:
|
||||
|
||||
HG.client_controller.new_options.PushRecentPetitionReason( HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_DELETE, reason )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
return
|
||||
|
@ -2602,6 +2611,8 @@ class ManageTagsPanel( CAC.ApplicationCommandProcessorMixin, ClientGUIScrolledPa
|
|||
|
||||
if len( recent_tags ) > 0 and num_recent_tags is not None:
|
||||
|
||||
recent_tags = list( recent_tags )
|
||||
|
||||
if len( recent_tags ) > num_recent_tags:
|
||||
|
||||
recent_tags = random.sample( recent_tags, num_recent_tags )
|
||||
|
@ -3232,10 +3243,14 @@ class ManageTagParents( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
message = 'Enter a reason for:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'To be added. A janitor will review your request.'
|
||||
|
||||
suggestions = []
|
||||
fixed_suggestions = [
|
||||
'obvious by definition (a sword is a weapon)',
|
||||
'character/series/studio/etc... belonging (character x belongs to series y)'
|
||||
]
|
||||
|
||||
suggestions.append( 'obvious by definition (a sword is a weapon)' )
|
||||
suggestions.append( 'character/series/studio/etc... belonging (character x belongs to series y)' )
|
||||
suggestions = HG.client_controller.new_options.GetRecentPetitionReasons( HC.CONTENT_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_ADD )
|
||||
|
||||
suggestions.extend( fixed_suggestions )
|
||||
|
||||
with ClientGUIDialogs.DialogTextEntry( self, message, suggestions = suggestions ) as dlg:
|
||||
|
||||
|
@ -3243,6 +3258,11 @@ class ManageTagParents( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
reason = dlg.GetValue()
|
||||
|
||||
if reason not in fixed_suggestions:
|
||||
|
||||
HG.client_controller.new_options.PushRecentPetitionReason( HC.CONTENT_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_ADD, reason )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
do_it = False
|
||||
|
@ -3309,9 +3329,13 @@ class ManageTagParents( ClientGUIScrolledPanels.ManagePanel ):
|
|||
message += os.linesep * 2
|
||||
message += 'to be removed. A janitor will review your petition.'
|
||||
|
||||
suggestions = []
|
||||
fixed_suggestions = [
|
||||
'obvious typo/mistake'
|
||||
]
|
||||
|
||||
suggestions.append( 'obvious typo/mistake' )
|
||||
suggestions = HG.client_controller.new_options.GetRecentPetitionReasons( HC.CONTENT_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_DELETE )
|
||||
|
||||
suggestions.extend( fixed_suggestions )
|
||||
|
||||
with ClientGUIDialogs.DialogTextEntry( self, message, suggestions = suggestions ) as dlg:
|
||||
|
||||
|
@ -3319,6 +3343,12 @@ class ManageTagParents( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
reason = dlg.GetValue()
|
||||
|
||||
|
||||
if reason not in fixed_suggestions:
|
||||
|
||||
HG.client_controller.new_options.PushRecentPetitionReason( HC.CONTENT_TYPE_TAG_PARENTS, HC.CONTENT_UPDATE_DELETE, reason )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
do_it = False
|
||||
|
@ -4376,10 +4406,14 @@ class ManageTagSiblings( ClientGUIScrolledPanels.ManagePanel ):
|
|||
pair_strings = os.linesep.join( ( old + '->' + new for ( old, new ) in new_pairs ) )
|
||||
|
||||
|
||||
suggestions = []
|
||||
fixed_suggestions = [
|
||||
'merging underscores/typos/phrasing/unnamespaced to a single uncontroversial good tag',
|
||||
'rewording/namespacing based on preference'
|
||||
]
|
||||
|
||||
suggestions.append( 'merging underscores/typos/phrasing/unnamespaced to a single uncontroversial good tag' )
|
||||
suggestions.append( 'rewording/namespacing based on preference' )
|
||||
suggestions = HG.client_controller.new_options.GetRecentPetitionReasons( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_ADD )
|
||||
|
||||
suggestions.extend( fixed_suggestions )
|
||||
|
||||
message = 'Enter a reason for:' + os.linesep * 2 + pair_strings + os.linesep * 2 + 'To be added. A janitor will review your petition.'
|
||||
|
||||
|
@ -4389,6 +4423,11 @@ class ManageTagSiblings( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
reason = dlg.GetValue()
|
||||
|
||||
if reason not in fixed_suggestions:
|
||||
|
||||
HG.client_controller.new_options.PushRecentPetitionReason( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_ADD, reason )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
do_it = False
|
||||
|
@ -4472,11 +4511,15 @@ class ManageTagSiblings( ClientGUIScrolledPanels.ManagePanel ):
|
|||
message += os.linesep * 2
|
||||
message += 'to be removed. You will see the delete as soon as you upload, but a janitor will review your petition to decide if all users should receive it as well.'
|
||||
|
||||
suggestions = []
|
||||
fixed_suggestions = [
|
||||
'obvious typo/mistake',
|
||||
'disambiguation',
|
||||
'correcting to repository standard'
|
||||
]
|
||||
|
||||
suggestions.append( 'obvious typo/mistake' )
|
||||
suggestions.append( 'disambiguation' )
|
||||
suggestions.append( 'correcting to repository standard' )
|
||||
suggestions = HG.client_controller.new_options.GetRecentPetitionReasons( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_DELETE )
|
||||
|
||||
suggestions.extend( fixed_suggestions )
|
||||
|
||||
with ClientGUIDialogs.DialogTextEntry( self, message, suggestions = suggestions ) as dlg:
|
||||
|
||||
|
@ -4484,6 +4527,11 @@ class ManageTagSiblings( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
reason = dlg.GetValue()
|
||||
|
||||
if reason not in fixed_suggestions:
|
||||
|
||||
HG.client_controller.new_options.PushRecentPetitionReason( HC.CONTENT_TYPE_TAG_SIBLINGS, HC.CONTENT_UPDATE_DELETE, reason )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
do_it = False
|
||||
|
|
|
@ -179,6 +179,9 @@ class BetterListCtrl( QW.QTreeWidget ):
|
|||
self.header().setContextMenuPolicy( QC.Qt.CustomContextMenu )
|
||||
self.header().customContextMenuRequested.connect( self._ShowHeaderMenu )
|
||||
|
||||
HG.client_controller.sub( self, 'NotifySettingsUpdated', 'reset_all_listctrl_status' )
|
||||
HG.client_controller.sub( self, 'NotifySettingsUpdated', 'reset_listctrl_status' )
|
||||
|
||||
|
||||
def _AddDataInfo( self, data_info ):
|
||||
|
||||
|
@ -387,7 +390,7 @@ class BetterListCtrl( QW.QTreeWidget ):
|
|||
|
||||
name = CGLC.column_list_type_name_lookup[ self._column_list_type ]
|
||||
|
||||
ClientGUIMenus.AppendMenuLabel( menu, f'multi-column list: {name}', 'This is the name of this multi-column list widget.' )
|
||||
ClientGUIMenus.AppendMenuItem( menu, f'reset default column widths for "{name}" lists', 'Reset the column widths and other display settings for all lists of this type', HG.client_controller.column_list_manager.ResetToDefaults, self._column_list_type )
|
||||
|
||||
CGC.core().PopupMenu( self, menu )
|
||||
|
||||
|
@ -685,6 +688,57 @@ class BetterListCtrl( QW.QTreeWidget ):
|
|||
return len( self.selectedItems() ) > 0
|
||||
|
||||
|
||||
def NotifySettingsUpdated( self, column_list_type = None ):
|
||||
|
||||
if column_list_type is not None and column_list_type != self._column_list_type:
|
||||
|
||||
return
|
||||
|
||||
|
||||
self.blockSignals( True )
|
||||
self.header().blockSignals( True )
|
||||
|
||||
self._column_list_status: ClientGUIListStatus.ColumnListStatus = HG.client_controller.column_list_manager.GetStatus( self._column_list_type )
|
||||
self._original_column_list_status = self._column_list_status
|
||||
|
||||
#
|
||||
|
||||
( self._sort_column_type, self._sort_asc ) = self._column_list_status.GetSort()
|
||||
|
||||
#
|
||||
|
||||
main_tlw = HG.client_controller.GetMainTLW()
|
||||
|
||||
MIN_SECTION_SIZE_CHARS = 3
|
||||
|
||||
last_column_index = self._column_list_status.GetColumnCount() - 1
|
||||
|
||||
for ( i, column_type ) in enumerate( self._column_list_status.GetColumnTypes() ):
|
||||
|
||||
if i == last_column_index:
|
||||
|
||||
width_chars = MIN_SECTION_SIZE_CHARS
|
||||
|
||||
else:
|
||||
|
||||
width_chars = self._column_list_status.GetColumnWidth( column_type )
|
||||
|
||||
|
||||
width_chars = max( width_chars, MIN_SECTION_SIZE_CHARS )
|
||||
|
||||
width_pixels = ClientGUIFunctions.ConvertTextToPixelWidth( main_tlw, width_chars )
|
||||
|
||||
self.setColumnWidth( i, width_pixels )
|
||||
|
||||
|
||||
self.header().blockSignals( False )
|
||||
self.blockSignals( False )
|
||||
|
||||
#
|
||||
|
||||
self.Sort() # note this saves the current status, so don't do it until we resize stuff
|
||||
|
||||
|
||||
def ProcessActivateAction( self ):
|
||||
|
||||
if self._activation_callback is not None:
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from hydrus.core import HydrusSerialisable
|
||||
|
||||
from hydrus.core import HydrusGlobals as HG
|
||||
|
||||
from hydrus.client.gui.lists import ClientGUIListStatus
|
||||
|
||||
class ColumnListManager( HydrusSerialisable.SerialisableBase ):
|
||||
|
@ -50,9 +52,23 @@ class ColumnListManager( HydrusSerialisable.SerialisableBase ):
|
|||
return column_list_status
|
||||
|
||||
|
||||
def ResetToDefaults( self ):
|
||||
def ResetToDefaults( self, column_list_type = None ):
|
||||
|
||||
self._column_list_types_to_statuses = HydrusSerialisable.SerialisableDictionary()
|
||||
if column_list_type is None:
|
||||
|
||||
self._column_list_types_to_statuses = HydrusSerialisable.SerialisableDictionary()
|
||||
|
||||
HG.client_controller.pub( 'reset_all_listctrl_status' )
|
||||
|
||||
else:
|
||||
|
||||
if column_list_type in self._column_list_types_to_statuses:
|
||||
|
||||
del self._column_list_types_to_statuses[ column_list_type ]
|
||||
|
||||
HG.client_controller.pub( 'reset_listctrl_status', column_list_type )
|
||||
|
||||
|
||||
|
||||
self._dirty = True
|
||||
|
||||
|
|
|
@ -472,7 +472,7 @@ class PanelPredicateSystemLastViewedDate( PanelPredicateSystemDate ):
|
|||
|
||||
def _GetSystemPredicateLabel( self ) -> str:
|
||||
|
||||
return 'system:last viewed date'
|
||||
return 'system:last viewed time'
|
||||
|
||||
|
||||
def _GetPredicateType( self ) -> int:
|
||||
|
@ -485,7 +485,7 @@ class PanelPredicateSystemArchivedDate( PanelPredicateSystemDate ):
|
|||
|
||||
def _GetSystemPredicateLabel( self ) -> str:
|
||||
|
||||
return 'system:archived date'
|
||||
return 'system:archived time'
|
||||
|
||||
|
||||
def _GetPredicateType( self ) -> int:
|
||||
|
@ -498,7 +498,7 @@ class PanelPredicateSystemModifiedDate( PanelPredicateSystemDate ):
|
|||
|
||||
def _GetSystemPredicateLabel( self ) -> str:
|
||||
|
||||
return 'system:modified date'
|
||||
return 'system:modified time'
|
||||
|
||||
|
||||
def _GetPredicateType( self ) -> int:
|
||||
|
@ -597,7 +597,7 @@ class PanelPredicateSystemLastViewedDelta( PanelPredicateSystemSingle ):
|
|||
|
||||
hbox = QP.HBoxLayout()
|
||||
|
||||
QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:last viewed'), CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:last viewed time'), CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, self._years, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'years'), CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
|
@ -656,7 +656,7 @@ class PanelPredicateSystemArchivedDelta( PanelPredicateSystemSingle ):
|
|||
|
||||
hbox = QP.HBoxLayout()
|
||||
|
||||
QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:archived date'), CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:archived time'), CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, self._years, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'years'), CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
|
@ -715,7 +715,7 @@ class PanelPredicateSystemModifiedDelta( PanelPredicateSystemSingle ):
|
|||
|
||||
hbox = QP.HBoxLayout()
|
||||
|
||||
QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:modified date'), CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'system:modified time'), CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, self._sign, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, self._years, CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
QP.AddToLayout( hbox, ClientGUICommon.BetterStaticText(self,'years'), CC.FLAGS_CENTER_PERPENDICULAR )
|
||||
|
|
|
@ -1237,7 +1237,7 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
|
|||
def SetReferralURL( self, referral_url: str ):
|
||||
|
||||
self._referral_url = referral_url
|
||||
|
||||
|
||||
|
||||
def SetRequestHeaders( self, request_headers: dict ):
|
||||
|
||||
|
@ -1261,6 +1261,14 @@ class FileSeed( HydrusSerialisable.SerialisableBase ):
|
|||
self.status = status
|
||||
self.note = note
|
||||
|
||||
if status == CC.STATUS_UNKNOWN:
|
||||
|
||||
# if user is 'try again'ing for a complicated 'the destination file changed' situation,
|
||||
# we want to scrub any db-assigned sha256 that was allocated by a previous 'already in db' file import result
|
||||
# also kill any md5 or whatever, just in case that was causing the original mess-up
|
||||
self._hashes = {}
|
||||
|
||||
|
||||
self._UpdateModified()
|
||||
|
||||
|
||||
|
|
|
@ -703,7 +703,7 @@ class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
( death_files_found, death_time_delta ) = death_file_velocity
|
||||
|
||||
HydrusData.ShowText( 'The query "{}" for subscription "{}" found fewer than {} files in the last {}, so it appears to be dead!'.format( query_name, self._name, HydrusData.ToHumanInt( death_files_found ), HydrusTime.TimeDeltaToPrettyTimeDelta( death_time_delta ) ) )
|
||||
HydrusData.ShowText( 'The query "{}" for subscription "{}" found fewer than {} files in the last {}, so it appears to be dead!'.format( query_name, self._name, HydrusData.ToHumanInt( death_files_found ), HydrusTime.TimeDeltaToPrettyTimeDelta( death_time_delta, no_bigger_than_days = True ) ) )
|
||||
|
||||
|
||||
else:
|
||||
|
|
|
@ -1886,7 +1886,7 @@ class MediaSingleton( Media ):
|
|||
|
||||
if len( modified_timestamp_lines ) > 1:
|
||||
|
||||
lines.append( ( False, ( 'all modified dates', modified_timestamp_lines ) ) )
|
||||
lines.append( ( False, ( 'all modified times', modified_timestamp_lines ) ) )
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 20
|
||||
SOFTWARE_VERSION = 525
|
||||
SOFTWARE_VERSION = 526
|
||||
CLIENT_API_VERSION = 44
|
||||
|
||||
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
|
|
@ -516,6 +516,11 @@ class SerialisableDictionary( SerialisableBase, dict ):
|
|||
|
||||
key = ConvertMetaSerialisableTupleToObject( meta_key )
|
||||
|
||||
if isinstance( key, list ):
|
||||
|
||||
key = tuple( key )
|
||||
|
||||
|
||||
value = ConvertMetaSerialisableTupleToObject( meta_value )
|
||||
|
||||
except HydrusExceptions.SerialisationException as e:
|
||||
|
|
|
@ -130,16 +130,29 @@ def CalendarDeltaToDateTime( years : int, months : int, days : int, hours : int
|
|||
|
||||
result = now - day_and_hour_delta
|
||||
|
||||
while months > result.month:
|
||||
|
||||
years += 1
|
||||
months -= 12
|
||||
|
||||
|
||||
new_year = result.year - years
|
||||
new_month = result.month - months
|
||||
|
||||
dayrange = calendar.monthrange( new_year, new_month )
|
||||
while new_month < 1:
|
||||
|
||||
new_year -= 1
|
||||
new_month += 12
|
||||
|
||||
|
||||
while new_month > 12:
|
||||
|
||||
new_year += 1
|
||||
new_month -= 12
|
||||
|
||||
|
||||
try:
|
||||
|
||||
dayrange = calendar.monthrange( new_year, new_month )
|
||||
|
||||
except:
|
||||
|
||||
dayrange = ( 0, 30 )
|
||||
|
||||
|
||||
new_day = min( dayrange[1], result.day )
|
||||
|
||||
|
@ -160,7 +173,7 @@ def CalendarDeltaToRoughDateTimeTimeDelta( years : int, months : int, days : int
|
|||
return datetime.timedelta( days = days + ( months * ( 365.25 / 12 ) ) + ( years * 365.25 ), hours = hours )
|
||||
|
||||
|
||||
def TimeDeltaToPrettyTimeDelta( seconds, show_seconds = True ):
|
||||
def TimeDeltaToPrettyTimeDelta( seconds, show_seconds = True, no_bigger_than_days = False ):
|
||||
|
||||
if seconds is None:
|
||||
|
||||
|
@ -189,8 +202,12 @@ def TimeDeltaToPrettyTimeDelta( seconds, show_seconds = True ):
|
|||
|
||||
lines = []
|
||||
|
||||
lines.append( ( 'year', YEAR ) )
|
||||
lines.append( ( 'month', MONTH ) )
|
||||
if not no_bigger_than_days:
|
||||
|
||||
lines.append( ( 'year', YEAR ) )
|
||||
lines.append( ( 'month', MONTH ) )
|
||||
|
||||
|
||||
lines.append( ( 'day', DAY ) )
|
||||
lines.append( ( 'hour', HOUR ) )
|
||||
lines.append( ( 'minute', MINUTE ) )
|
||||
|
|
|
@ -2051,12 +2051,12 @@ class TestTagObjects( unittest.TestCase ):
|
|||
( 'system:modified time: since 1 day ago', "system:modified date < 1 day" ),
|
||||
( 'system:modified time: since 1 day ago', "system:modified time < 1 day" ),
|
||||
( 'system:modified time: since 1 month 1 day ago', "system:date modified < 0 years 1 month 1 day 1 hour" ),
|
||||
( 'system:last view time: since 7 years 45 days ago', "system:last viewed time < 7 years 45 days 700h" ),
|
||||
( 'system:last view time: since 7 years 45 days ago', "system:last viewed date < 7 years 45 days 700h" ),
|
||||
( 'system:last view time: since 7 years 45 days ago', "system:last view time < 7 years 45 days 700h" ),
|
||||
( 'system:last view time: since 7 years 45 days ago', "system:last view date < 7 years 45 days 700h" ),
|
||||
( 'system:last view time: since 7 years 45 days ago', "system:time last viewed < 7 years 45 days 700h" ),
|
||||
( 'system:last view time: since 7 years 45 days ago', "system:date last viewed < 7 years 45 days 700h" ),
|
||||
( 'system:last viewed time: since 7 years 45 days ago', "system:last viewed time < 7 years 45 days 700h" ),
|
||||
( 'system:last viewed time: since 7 years 45 days ago', "system:last viewed date < 7 years 45 days 700h" ),
|
||||
( 'system:last viewed time: since 7 years 45 days ago', "system:last view time < 7 years 45 days 700h" ),
|
||||
( 'system:last viewed time: since 7 years 45 days ago', "system:last view date < 7 years 45 days 700h" ),
|
||||
( 'system:last viewed time: since 7 years 45 days ago', "system:time last viewed < 7 years 45 days 700h" ),
|
||||
( 'system:last viewed time: since 7 years 45 days ago', "system:date last viewed < 7 years 45 days 700h" ),
|
||||
( 'system:import time: since 7 years 45 days ago', "system:time_imported < 7 years 45 days 700h" ),
|
||||
( 'system:import time: since 2011-06-04', "system:time imported > 2011-06-04" ),
|
||||
( 'system:import time: before 7 years 2 months ago', "system:time imported > 7 years 2 months" ),
|
||||
|
|
|
@ -68,6 +68,7 @@ from hydrus.test import TestHydrusSerialisable
|
|||
from hydrus.test import TestHydrusServer
|
||||
from hydrus.test import TestHydrusSessions
|
||||
from hydrus.test import TestHydrusTags
|
||||
from hydrus.test import TestHydrusTime
|
||||
from hydrus.test import TestServerDB
|
||||
|
||||
DB_DIR = None
|
||||
|
@ -784,6 +785,7 @@ class Controller( object ):
|
|||
TestClientDBDuplicates,
|
||||
TestClientDBTags,
|
||||
TestHydrusData,
|
||||
TestHydrusTime,
|
||||
TestHydrusNATPunch,
|
||||
TestClientNetworking,
|
||||
TestHydrusNetworking,
|
||||
|
@ -818,6 +820,7 @@ class Controller( object ):
|
|||
TestFunctions,
|
||||
TestHydrusData,
|
||||
TestHydrusTags,
|
||||
TestHydrusTime,
|
||||
TestHydrusSerialisable,
|
||||
TestHydrusSessions
|
||||
]
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import unittest
|
||||
|
||||
from hydrus.core import HydrusConstants as HC
|
||||
from hydrus.core import HydrusGlobals as HG
|
||||
from hydrus.core import HydrusTime
|
||||
|
||||
from hydrus.client import ClientConstants as CC
|
||||
from hydrus.client import ClientData
|
||||
from hydrus.client.metadata import ClientTags
|
||||
|
||||
class TestHydrusTime( unittest.TestCase ):
|
||||
|
||||
def test_quick( self ):
|
||||
|
||||
self.assertEqual( HydrusTime.TimeDeltaToPrettyTimeDelta( 86400 * 5 ), '5 days' )
|
||||
self.assertIn( 'month', HydrusTime.TimeDeltaToPrettyTimeDelta( 86400 * 35 ) )
|
||||
self.assertEqual( HydrusTime.TimeDeltaToPrettyTimeDelta( 86400 * 35, no_bigger_than_days = True ), '35 days' )
|
||||
|
||||
|
Loading…
Reference in New Issue