Version 389

This commit is contained in:
Hydrus Network Developer 2020-03-18 16:35:57 -05:00
parent 3edc910a96
commit 5014c369b1
35 changed files with 793 additions and 478 deletions

1
.gitignore vendored
View File

@ -45,6 +45,7 @@ db/*.db-wal
db/*.crt
db/*.key
db/*.log
db/*.conf
db/client_files/
db/server_files/
db/missing_and_invalid_files/

View File

@ -8,6 +8,41 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 389</h3></li>
<ul>
<li>downloaders:</li>
<li>the e621 file page parser is updated again, thanks to a user's contribution. this one gets md5 and file url more reliably, and also gets rating tag</li>
<li>added a 'e621 file page (old format)' url class to help match and search for files downloaded with the old format. please be aware there is no good solution to auto-convert old urls to a new format yet, so this connection does not (yet) solve the old/new comparison test</li>
<li>updated deviant art file post parser to use their json api. this should be more resilient to their current layout changes</li>
<li>the nijie.info login script appears no longer to function. as with exhentai last week, it has been removed to make it easier to log in with hydrus companion. please use hydrus companion if you would like to log into nijie.info</li>
<li>updated file lookup scripts for 'iqdb danbooru' and 'danbooru md5' thanks to a user's contribution</li>
<li>.</li>
<li>the rest:</li>
<li>the way the mpv.conf works changes this week. it is now correctly fully portable, stored in the db directory beside the .db files. if this file does not exist, the 'default' as stored under the install_dir/static/mpv-conf folder will auto-populate it. if you have been using a non-default mpv conf, please re-set it one time after update, and you should be good</li>
<li>the code that loads mpv.conf is now more graceful on 'missing file' errors, which now means when both the db conf and the default conf are missing</li>
<li>hitting escape on a tag autocomplete input that has text will now clear that text! note that hitting escape on an _empty_ a/c input will still do 'lose focus' and then 'close dialog'</li>
<li>updated the slideshow logic so that if a media with duration has a shorter duration than the slideshow duration (e.g. a gif that lasts 0.5s on a 10s slideshow), the media will keep looping until the duration is up. media that has duration longer than the slideshow time will continue to play through once completely, delaying slideshow progression and then stopping promptly when it has finished</li>
<li>the string transformation system now allows 'url percent encoding' under the encode/decode type!</li>
<li>fixed the 'only add existing tags' filter in the tag import options, which was denying all the tested tags. it seems to have been hit by a typo in the last three months</li>
<li>the 'favourite searches' defaults now include an 'empty page' entry, which is a convenient way to simply clear a page. all users will also get this on update, feel free to delete if you don't like/need it</li>
<li>opening a new search page from a tag or an active search predicate ('open a new search page for...' or middle-click) now copies the file service (e.g. looking at trash) from the original page</li>
<li>opening a new search page in the 'all known files' file domain when the tag domain should be 'all known tags' (a currently unsupported combination) now coerces the tag domain to 'all local tags'</li>
<li>checkboxes should now appear again on the collect-by dropdown in Fusion (and hopefully any other) style</li>
<li>fixed an issue where entering 'namespace:*' explicitly would show the much less efficient wildcard search rather than the efficient 'anything' namespace search</li>
<li>fixed an issue where wildcard search could include multiple asterisks in a row</li>
<li>fixed an issue with page duplication where the main management object was not being duplicated properly until a session reload, meaning the two pages would sometimes share signals and changes</li>
<li>an old wx delayed hide/show performance hack is removed, making the floating autocomplete dropdown now update more smoothly to resize or move requests, such as when the main gui window is dragged</li>
<li>the program base installation directory is now calculated more accurately, both when running from source and the frozen build, and when launched using a symlink</li>
<li>install dir and db dir are now specified in the help->about window</li>
<li>the petition page content checkbox list now has a taller minimum height</li>
<li>improved error text reporting in hydrus service login failure, hydrus service delay reason-setting, and all 'cancelled' errors across the program</li>
<li>the review services panel now has elided... text. when unusually long errors propagate up to its status texts, it now won't suddenly jump to 2,000 pixels wide. full text appears in tooltips</li>
<li>code refactoring: the tag autocomplete input now now takes responsibility for the active predicate list above it</li>
<li>refactored some tag lists and added typing hints to improve how current page predicates are determined</li>
<li>did some prep work for tag filters supporting wildcards, but it isn't ready yet</li>
<li>cleaned up some wx->Qt data fetching code</li>
<li>misc code cleanup</li>
</ul>
<li><h3>version 388</h3></li>
<ul>
<li>favourite searches:</li>

View File

@ -718,6 +718,11 @@ class Controller( HydrusController.HydrusController ):
return clipboard_text
def GetDefaultMPVConfPath( self ):
return os.path.join( HC.STATIC_DIR, 'mpv-conf', 'default_mpv.conf' )
def GetIdleShutdownWorkDue( self, time_to_stop ):
work_to_do = []
@ -737,6 +742,11 @@ class Controller( HydrusController.HydrusController ):
return work_to_do
def GetMPVConfPath( self ):
return os.path.join( self.db_dir, 'mpv.conf' )
def GetNewOptions( self ):
return self.new_options

View File

@ -636,6 +636,19 @@ class DB( HydrusDB.HydrusDB ):
HydrusPaths.MirrorFile( source, dest )
conf_files = [ 'mpv.conf' ]
for conf_file in conf_files:
source = os.path.join( self._db_dir, conf_file )
dest = os.path.join( path, conf_file )
if os.path.exists( source ):
HydrusPaths.MirrorFile( source, dest )
def is_cancelled_hook():
return job_key.IsCancelled()
@ -3971,7 +3984,7 @@ class DB( HydrusDB.HydrusDB ):
tag_ids_to_tags = { self._GetTagId( tag ) : tag for tag in tags }
counts = self._CacheCombinedFilesMappingsGetAutocompleteCounts( service_id, list(tag_ids_to_tags.keys()) )
counts = self._CacheCombinedFilesMappingsGetAutocompleteCounts( service_id, list( tag_ids_to_tags.keys() ) )
existing_tag_ids = [ tag_id for ( tag_id, current_count, pending_count ) in counts if current_count > 0 ]
@ -13856,6 +13869,137 @@ class DB( HydrusDB.HydrusDB ):
if version == 388:
try:
favourite_search_manager = self._GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_FAVOURITE_SEARCH_MANAGER )
folders_to_names = favourite_search_manager.GetFoldersToNames()
do_it = True
if None in folders_to_names:
if 'empty page' in folders_to_names[ None ]:
do_it = False
if do_it:
foldername = None
name = 'empty page'
tag_search_context = ClientSearch.TagSearchContext()
predicates = []
file_search_context = ClientSearch.FileSearchContext( file_service_key = CC.LOCAL_FILE_SERVICE_KEY, tag_search_context = tag_search_context, predicates = predicates )
synchronised = True
media_sort = None
media_collect = None
new_rows = [ ( foldername, name, file_search_context, synchronised, media_sort, media_collect ) ]
#
rows = list( favourite_search_manager.GetFavouriteSearchRows() )
rows.extend( new_rows )
favourite_search_manager.SetFavouriteSearchRows( rows )
self._SetJSONDump( favourite_search_manager )
except Exception as e:
HydrusData.PrintException( e )
message = 'Trying to add an empty favourite search failed! Please let hydrus dev know!'
self.pub_initial_message( message )
try:
script_rows = ClientDefaults.GetDefaultScriptRows()
for script_row in script_rows:
dump_type = script_row[0]
dump_name = script_row[1]
self._c.execute( 'DELETE FROM json_dumps_named WHERE dump_type = ? AND dump_name = ?;', ( dump_type, dump_name ) )
self._c.executemany( 'REPLACE INTO json_dumps_named VALUES ( ?, ?, ?, ?, ? );', script_rows )
except Exception as e:
HydrusData.PrintException( e )
message = 'Trying to add new file lookup scripts search failed! Please let hydrus dev know!'
self.pub_initial_message( message )
try:
domain_manager = self._GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_DOMAIN_MANAGER )
domain_manager.Initialise()
#
domain_manager.OverwriteDefaultURLClasses( [ 'deviant art file page', 'deviant art file page api', 'e621 file page (old format)' ] )
#
domain_manager.OverwriteDefaultParsers( [ 'e621 file page parser', 'deviant art file page api parser' ] )
#
domain_manager.TryToLinkURLClassesAndParsers()
#
self._SetJSONDump( domain_manager )
#
login_manager = self._GetJSONDump( HydrusSerialisable.SERIALISABLE_TYPE_NETWORK_LOGIN_MANAGER )
login_manager.Initialise()
#
login_manager.DeleteLoginScripts( [ 'nijie.info login script' ] )
#
if not login_manager.DomainHasALoginScript( 'nijie.info' ):
login_manager.DeleteLoginDomain( 'nijie.info' )
#
self._SetJSONDump( login_manager )
except Exception as e:
HydrusData.PrintException( e )
message = 'Trying to update some parsers failed! Please let hydrus dev know!'
self.pub_initial_message( message )
self._controller.pub( 'splash_set_title_text', 'updated db to v{}'.format( HydrusData.ToHumanInt( version + 1 ) ) )
@ -14491,6 +14635,19 @@ class DB( HydrusDB.HydrusDB ):
HydrusPaths.MergeFile( dest, dest + '.old' )
conf_files = [ 'mpv.conf' ]
for conf_file in conf_files:
source = os.path.join( path, conf_file )
dest = os.path.join( self._db_dir, conf_file )
if os.path.exists( source ):
HydrusPaths.MirrorFile( source, dest )
client_files_source = os.path.join( path, 'client_files' )
client_files_default = os.path.join( self._db_dir, 'client_files' )

View File

@ -296,8 +296,9 @@ def GetDefaultScriptRows():
script_info = []
script_info.append( ( 32, 'iqdb danbooru', 2, HydrusData.GetNow(), '''["http://danbooru.iqdb.org/", 1, 0, [55, 1, [[], "some hash bytes"]], "file", {}, [[29, 1, ["link to danbooru", [27, 6, [[26, 1, [[62, 2, [0, "td", {"class": "image"}, 1, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "a", {}, 0, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 0, "href", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], [[30, 4, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "section", {"id": "tag-list"}, 0, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "li", {"class": "tag-type-1"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "a", {"class": "search-tag"}, 0, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], 0, false, "creator"]], [30, 4, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "section", {"id": "tag-list"}, 0, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "li", {"class": "tag-type-3"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "a", {"class": "search-tag"}, 0, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], 0, false, "series"]], [30, 4, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "section", {"id": "tag-list"}, 0, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "li", {"class": "tag-type-4"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "a", {"class": "search-tag"}, 0, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], 0, false, "character"]], [30, 4, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "section", {"id": "tag-list"}, 0, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "li", {"class": "tag-type-0"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "a", {"class": "search-tag"}, 0, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], 0, false, ""]], [30, 4, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "section", {"id": "post-information"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "li", {}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [2, "Rating:*", null, null, "Rating: Safe"]], [55, 1, [[[0, 8]], "Rating: Safe"]]]], 0, false, "rating"]], [30, 4, ["", 7, [27, 6, [[26, 1, [[62, 2, [0, "section", {"id": "post-information"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "li", {}, null, null, true, [51, 1, [2, "Source:*", null, null, "Source:"]]]], [62, 2, [0, "a", {}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 0, "href", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], 0, false, [8, 0]]]]]], [30, 4, ["no iqdb match found", 8, [27, 6, [[26, 1, [[62, 2, [0, "th", {}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], 0, false, [false, [51, 1, [2, "Best match", null, null, "Best match"]]]]]]]''' ) )
script_info.append( ( 32, 'danbooru md5', 2, HydrusData.GetNow(), '''["https://danbooru.donmai.us/", 0, 1, [55, 1, [[[4, "hex"]], "some hash bytes"]], "md5", {"page": "post", "s": "list"}, [[30, 4, ["we got sent back to main gallery page -- title test", 8, [27, 6, [[26, 1, [[62, 2, [0, "head", {}, 0, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "title", {}, 0, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], 0, false, [true, [51, 1, [2, "Image List", null, null, "Image List"]]]]], [30, 4, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "li", {"class": "tag-type-0"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "a", {}, 1, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], 0, false, ""]], [30, 4, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "li", {"class": "tag-type-3"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "a", {}, 1, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], 0, false, "series"]], [30, 4, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "li", {"class": "tag-type-1"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "a", {}, 1, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], 0, false, "creator"]], [30, 4, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "li", {"class": "tag-type-4"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "a", {}, 1, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], 0, false, "character"]], [30, 4, ["we got sent back to main gallery page -- page links exist", 8, [27, 6, [[26, 1, [[62, 2, [0, "div", {}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 0, "class", [51, 1, [3, "", null, null, "example string"]], [55, 1, [[], "parsed information"]]]], 0, false, [true, [51, 1, [2, "pagination", null, null, "pagination"]]]]], [30, 4, ["", 0, [27, 6, [[26, 1, [[62, 2, [0, "section", {"id": "post-information"}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]], [62, 2, [0, "li", {}, null, null, false, [51, 1, [3, "", null, null, "example string"]]]]]], 1, "href", [51, 1, [2, "Rating:*", null, null, "Rating: Safe"]], [55, 1, [[[0, 8]], "Rating: Safe"]]]], 0, false, "rating"]]]]''' ) )
script_info.append( ( 32, 'gelbooru md5', 1, HydrusData.GetNow(), '''["http://gelbooru.com/index.php", 0, 1, 1, "md5", {"s": "list", "page": "post"}, [[30, 1, ["we got sent back to main gallery page -- title test", 8, [27, 1, [[["head", {}, 0], ["title", {}, 0]], null]], [true, true, "Image List"]]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-general"}, null], ["a", {}, 1]], null]], ""]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-copyright"}, null], ["a", {}, 1]], null]], "series"]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-artist"}, null], ["a", {}, 1]], null]], "creator"]], [30, 1, ["", 0, [27, 1, [[["li", {"class": "tag-type-character"}, null], ["a", {}, 1]], null]], "character"]], [30, 1, ["we got sent back to main gallery page -- page links exist", 8, [27, 1, [[["div", {}, null]], "class"]], [true, true, "pagination"]]]]]''' ) )
script_info.append( ( 32, 'iqdb danbooru', 1, HydrusData.GetNow(), '''["http://danbooru.iqdb.org/", 1, 0, 0, "file", {}, [[29, 1, ["link to danbooru", [27, 1, [[["td", {"class": "image"}, 1], ["a", {}, 0]], "href"]], [[30, 1, ["", 0, [27, 1, [[["section", {"id": "tag-list"}, 0], ["li", {"class": "category-1"}, null], ["a", {"class": "search-tag"}, 0]], null]], "creator"]], [30, 1, ["", 0, [27, 1, [[["section", {"id": "tag-list"}, 0], ["li", {"class": "category-3"}, null], ["a", {"class": "search-tag"}, 0]], null]], "series"]], [30, 1, ["", 0, [27, 1, [[["section", {"id": "tag-list"}, 0], ["li", {"class": "category-4"}, null], ["a", {"class": "search-tag"}, 0]], null]], "character"]], [30, 1, ["", 0, [27, 1, [[["section", {"id": "tag-list"}, 0], ["li", {"class": "category-0"}, null], ["a", {"class": "search-tag"}, 0]], null]], ""]]]]], [30, 1, ["no iqdb match found", 8, [27, 1, [[["th", {}, null]], null]], [false, true, "Best match"]]]]]''' ) )
return script_info
@ -646,6 +647,10 @@ def SetDefaultFavouriteSearchManagerData( favourite_search_manager ):
from . import ClientMedia
from . import ClientSearch
rows = []
#
foldername = 'example search'
name = 'inbox filter'
@ -669,12 +674,29 @@ def SetDefaultFavouriteSearchManagerData( favourite_search_manager ):
media_sort = ClientMedia.MediaSort( sort_type = ( 'system', CC.SORT_FILES_BY_FILESIZE ), sort_asc = CC.SORT_DESC )
media_collect = None
rows = [ ( foldername, name, file_search_context, synchronised, media_sort, media_collect ) ]
rows.append( ( foldername, name, file_search_context, synchronised, media_sort, media_collect ) )
#
foldername = None
name = 'empty page'
tag_search_context = ClientSearch.TagSearchContext()
predicates = []
file_search_context = ClientSearch.FileSearchContext( file_service_key = CC.LOCAL_FILE_SERVICE_KEY, tag_search_context = tag_search_context, predicates = predicates )
synchronised = True
media_sort = None
media_collect = None
rows.append( ( foldername, name, file_search_context, synchronised, media_sort, media_collect ) )
#
favourite_search_manager.SetFavouriteSearchRows( rows )
#inbox, system:limit=256, image, animation, video, sort by largest
def SetDefaultLoginManagerScripts( login_manager ):
default_login_scripts = GetDefaultLoginScripts()

View File

@ -539,6 +539,8 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
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( ( 'install dir', HC.BASE_DIR ) )
library_versions.append( ( 'db dir', HG.client_controller.db_dir ) )
library_versions.append( ( 'temp dir', HydrusPaths.GetCurrentTempDir() ) )
import locale

View File

@ -68,7 +68,13 @@ def InsertStaticPredicatesForRead( predicates, parsed_search_text, include_unusu
if include_unusual_predicate_types:
if explicit_wildcard:
( namespace, subtag ) = HydrusTags.SplitTag( search_text )
if namespace != '' and subtag in ( '', '*' ):
predicates.insert( 0, ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_NAMESPACE, namespace, inclusive ) )
elif explicit_wildcard:
if wildcard_text != search_text:
@ -77,15 +83,6 @@ def InsertStaticPredicatesForRead( predicates, parsed_search_text, include_unusu
predicates.insert( 0, ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_WILDCARD, wildcard_text, inclusive ) )
else:
( namespace, subtag ) = HydrusTags.SplitTag( entry_text )
if namespace != '' and subtag in ( '', '*' ):
predicates.insert( 0, ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_NAMESPACE, namespace, inclusive ) )
try:
@ -498,9 +495,6 @@ class AutoCompleteDropdown( QW.QWidget ):
self._last_attempted_dropdown_width = 0
self._last_attempted_dropdown_position = ( None, None )
self._last_move_event_started = 0.0
self._last_move_event_occurred = 0.0
self._text_ctrl_widget_event_filter = QP.WidgetEventFilter( self._text_ctrl )
self._text_ctrl.textChanged.connect( self.EventText )
@ -509,7 +503,9 @@ class AutoCompleteDropdown( QW.QWidget ):
self._text_ctrl.installEventFilter( self )
vbox = QP.VBoxLayout( margin = 0 )
self._main_vbox = QP.VBoxLayout( margin = 0 )
self._SetupTopListBox()
self._text_input_hbox = QP.HBoxLayout()
@ -517,7 +513,7 @@ class AutoCompleteDropdown( QW.QWidget ):
self._text_input_panel.setLayout( self._text_input_hbox )
QP.AddToLayout( vbox, self._text_input_panel, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
QP.AddToLayout( self._main_vbox, self._text_input_panel, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
if self._float_mode:
@ -561,10 +557,10 @@ class AutoCompleteDropdown( QW.QWidget ):
if not self._float_mode:
QP.AddToLayout( vbox, self._dropdown_window, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( self._main_vbox, self._dropdown_window, CC.FLAGS_EXPAND_BOTH_WAYS )
self.setLayout( vbox )
self.setLayout( self._main_vbox )
self._current_list_raw_entry = ''
self._next_search_is_probably_fast = False
@ -585,7 +581,7 @@ class AutoCompleteDropdown( QW.QWidget ):
self._widget_event_filter.EVT_MOVE( self.EventMove )
self._widget_event_filter.EVT_SIZE( self.EventMove )
HG.client_controller.sub( self, '_ParentMovedOrResized', 'top_level_window_move_event' )
HG.client_controller.sub( self, '_DropdownHideShow', 'top_level_window_move_event' )
parent = self
@ -698,7 +694,13 @@ class AutoCompleteDropdown( QW.QWidget ):
def _HandleEscape( self ):
if self._float_mode:
if self._text_ctrl.text() != '':
self._ClearInput()
return True
elif self._float_mode:
self.parentWidget().setFocus( QC.Qt.OtherFocusReason )
@ -725,44 +727,6 @@ class AutoCompleteDropdown( QW.QWidget ):
raise NotImplementedError()
def _ParentMovedOrResized( self ):
if self._float_mode:
if HydrusData.TimeHasPassedFloat( self._last_move_event_occurred + 1.0 ):
self._last_move_event_started = HydrusData.GetNowFloat()
self._last_move_event_occurred = HydrusData.GetNowFloat()
# we'll do smoother move updates for a little bit to stop flickeryness, but after that we'll just hide
NICE_ANIMATION_GRACE_PERIOD = 0.25
time_to_delay_these_calls = HydrusData.TimeHasPassedFloat( self._last_move_event_started + NICE_ANIMATION_GRACE_PERIOD )
if time_to_delay_these_calls:
self._HideDropdown()
if self._ShouldShow():
if self._move_hide_job is None:
self._move_hide_job = HG.client_controller.CallRepeatingQtSafe( self._dropdown_window, 0.0, 0.25, self._DropdownHideShow )
self._move_hide_job.Delay( 0.25 )
else:
self._DropdownHideShow()
def _ScheduleListRefresh( self, delay ):
if self._refresh_list_job is not None and delay == 0.0:
@ -777,6 +741,11 @@ class AutoCompleteDropdown( QW.QWidget ):
def _SetupTopListBox( self ):
pass
def _SetListDirty( self ):
self._search_text_for_current_cache = None
@ -938,6 +907,15 @@ class AutoCompleteDropdown( QW.QWidget ):
self._TakeResponsibilityForEnter( shift_down )
elif key == QC.Qt.Key_Escape:
escape_caught = self._HandleEscape()
if not escape_caught:
send_input_to_current_list = True
elif input_is_empty: # maybe we should be sending a 'move' event to a different place
if key in ( QC.Qt.Key_Up, QC.Qt.Key_Down ) and current_list_is_empty:
@ -975,15 +953,6 @@ class AutoCompleteDropdown( QW.QWidget ):
self.MoveNotebookPageFocus( direction = direction )
elif key == QC.Qt.Key_Escape:
escape_caught = self._HandleEscape()
if not escape_caught:
send_input_to_current_list = True
else:
send_input_to_current_list = True
@ -1082,7 +1051,7 @@ class AutoCompleteDropdown( QW.QWidget ):
def EventMove( self, event ):
self._ParentMovedOrResized()
self._DropdownHideShow()
return True # was: event.ignore()
@ -1349,13 +1318,17 @@ class AutoCompleteDropdownTags( AutoCompleteDropdown ):
class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
def __init__( self, parent: QW.QWidget, predicates_listbox: ClientGUIListBoxes.ListBoxTagsActiveSearchPredicates, page_key, file_search_context: ClientSearch.FileSearchContext, media_sort_widget: typing.Optional[ ClientGUISearch.MediaSortControl ] = None, media_collect_widget: typing.Optional[ ClientGUISearch.MediaCollectControl ] = None, media_callable = None, synchronised = True, include_unusual_predicate_types = True, allow_all_known_files = True, force_system_everything = False, hide_favourites_edit_actions = False ):
def __init__( self, parent: QW.QWidget, page_key, file_search_context: ClientSearch.FileSearchContext, media_sort_widget: typing.Optional[ ClientGUISearch.MediaSortControl ] = None, media_collect_widget: typing.Optional[ ClientGUISearch.MediaCollectControl ] = None, media_callable = None, synchronised = True, include_unusual_predicate_types = True, allow_all_known_files = True, force_system_everything = False, hide_favourites_edit_actions = False ):
self._predicates_listbox = predicates_listbox
self._page_key = page_key
file_service_key = file_search_context.GetFileServiceKey()
tag_search_context = file_search_context.GetTagSearchContext()
self._include_unusual_predicate_types = include_unusual_predicate_types
self._force_system_everything = force_system_everything
self._hide_favourites_edit_actions = hide_favourites_edit_actions
AutoCompleteDropdownTags.__init__( self, parent, file_service_key, tag_search_context.service_key )
self._media_sort_widget = media_sort_widget
@ -1364,9 +1337,6 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
self._allow_all_known_files = allow_all_known_files
self._media_callable = media_callable
self._page_key = page_key
self._hide_favourites_edit_actions = hide_favourites_edit_actions
self._under_construction_or_predicate = None
@ -1405,9 +1375,6 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
self._or_rewind.setToolTip( 'Rewind OR Predicate construction.' )
self._or_rewind.hide()
self._include_unusual_predicate_types = include_unusual_predicate_types
self._force_system_everything = force_system_everything
button_hbox_1 = QP.HBoxLayout()
QP.AddToLayout( button_hbox_1, self._include_current_tags, CC.FLAGS_EXPAND_BOTH_WAYS )
@ -1823,6 +1790,13 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
self._ManageFavouriteSearches( favourite_search_row_to_save = search_row )
def _SetupTopListBox( self ):
self._predicates_listbox = ListBoxTagsActiveSearchPredicates( self, self._page_key )
QP.AddToLayout( self._main_vbox, self._predicates_listbox, CC.FLAGS_EXPAND_PERPENDICULAR )
def _StartResultsFetchJob( self, job_key ):
parsed_search_text = self._ParseSearchText()
@ -1908,6 +1882,11 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
return fsc
def GetPredicates( self ) -> typing.Set[ ClientSearch.Predicate ]:
return self._predicates_listbox.GetPredicates()
def IncludeCurrent( self, page_key, value ):
if page_key == self._page_key:
@ -1978,6 +1957,157 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
class ListBoxTagsActiveSearchPredicates( ClientGUIListBoxes.ListBoxTagsPredicates ):
has_counts = False
def __init__( self, parent: AutoCompleteDropdownTagsRead, page_key, initial_predicates = None ):
if initial_predicates is None:
initial_predicates = []
ClientGUIListBoxes.ListBoxTagsPredicates.__init__( self, parent, height_num_chars = 6 )
self._my_ac_parent = parent
self._page_key = page_key
if len( initial_predicates ) > 0:
for predicate in initial_predicates:
self._AppendTerm( predicate )
self._DataHasChanged()
HG.client_controller.sub( self, 'EnterPredicates', 'enter_predicates' )
def _Activate( self ):
if len( self._selected_terms ) > 0:
self._EnterPredicates( set( self._selected_terms ) )
def _DeleteActivate( self ):
self._Activate()
def _EnterPredicates( self, predicates, permit_add = True, permit_remove = True ):
if len( predicates ) == 0:
return
predicates_to_be_added = set()
predicates_to_be_removed = set()
for predicate in predicates:
predicate = predicate.GetCountlessCopy()
if self._HasPredicate( predicate ):
if permit_remove:
predicates_to_be_removed.add( predicate )
else:
if permit_add:
predicates_to_be_added.add( predicate )
predicates_to_be_removed.update( self._GetMutuallyExclusivePredicates( predicate ) )
for predicate in predicates_to_be_added:
self._AppendTerm( predicate )
for predicate in predicates_to_be_removed:
self._RemoveTerm( predicate )
self._SortByText()
self._DataHasChanged()
HG.client_controller.pub( 'refresh_query', self._page_key )
def _GetCurrentFileServiceKey( self ):
return self._my_ac_parent.GetFileSearchContext().GetFileServiceKey()
def _GetCurrentPagePredicates( self ) -> typing.Set[ ClientSearch.Predicate ]:
return self.GetPredicates()
def _GetTextFromTerm( self, term ):
predicate = term
return predicate.ToString( render_for_user = True )
def _ProcessMenuPredicateEvent( self, command ):
( include_predicates, exclude_predicates ) = self._GetSelectedIncludeExcludePredicates()
if command == 'add_include_predicates':
self._EnterPredicates( include_predicates, permit_remove = False )
elif command == 'remove_include_predicates':
self._EnterPredicates( include_predicates, permit_add = False )
elif command == 'add_exclude_predicates':
self._EnterPredicates( exclude_predicates, permit_remove = False )
elif command == 'remove_exclude_predicates':
self._EnterPredicates( exclude_predicates, permit_add = False )
def EnterPredicates( self, page_key, predicates, permit_add = True, permit_remove = True ):
if page_key == self._page_key:
self._EnterPredicates( predicates, permit_add = permit_add, permit_remove = permit_remove )
def SetPredicates( self, predicates ):
self._Clear()
for predicate in predicates:
self._AppendTerm( predicate )
self._DataHasChanged()
class AutoCompleteDropdownTagsWrite( AutoCompleteDropdownTags ):
def __init__( self, parent, chosen_tag_callable, expand_parents, file_service_key, tag_service_key, null_entry_callable = None, tag_service_key_changed_callable = None, show_paste_button = False ):

View File

@ -5051,8 +5051,6 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
self._timer_slideshow_job = HG.client_controller.CallLaterQtSafe( self, self._timer_slideshow_interval, self.DoSlideshow )
self._media_container.SetSlideshowMode( True )
def _StopSlideshow( self ):
@ -5062,7 +5060,7 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
self._timer_slideshow_job = None
self._media_container.SetSlideshowMode( False )
self._media_container.StopForSlideshow( False )
@ -5070,10 +5068,17 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
try:
# we are due for a slideshow change, so tell movie to stop for it once it has played once through
# if short movie, it has prob played through a lot and will shift immediately
# if longer movie, it has prob not played through but will now stop when it has and wait for us to change it then
self._media_container.StopForSlideshow( True )
if self._current_media is not None and self._RunningSlideshow():
if self._media_container.ReadyToSlideshow() and not CGC.core().MenuIsOpen():
self._media_container.StopForSlideshow( False )
self._ShowNext()
self._timer_slideshow_job = HG.client_controller.CallLaterQtSafe( self, self._timer_slideshow_interval, self.DoSlideshow )
@ -5183,13 +5188,6 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
return command_processed
def SetMedia( self, media ):
CanvasMediaListNavigable.SetMedia( self, media )
self._media_container.SetSlideshowMode( self._RunningSlideshow() )
def ShowMenu( self ):
if self._current_media is not None:
@ -5438,8 +5436,6 @@ class MediaContainer( QW.QWidget ):
self._start_paused = False
self._start_with_embed = False
self._slideshow_mode = False
self._media_window = None
self._embed_button = EmbedButton( self )
@ -5912,13 +5908,11 @@ class MediaContainer( QW.QWidget ):
self.hide()
def SetSlideshowMode( self, value ):
self._slideshow_mode = value
def StopForSlideshow( self, value ):
if isinstance( self._media_window, ( Animation, ClientGUIMPV.mpvWidget ) ):
self._media_window.StopForSlideshow( self._slideshow_mode )
self._media_window.StopForSlideshow( value )

View File

@ -241,11 +241,11 @@ class BetterChoice( QW.QComboBox ):
if selection != -1:
return QP.GetClientData( self, selection )
return self.itemData( selection, QC.Qt.UserRole )
elif self.count() > 0:
return QP.GetClientData( self, 0 )
return self.itemData( 0, QC.Qt.UserRole )
else:
@ -257,7 +257,7 @@ class BetterChoice( QW.QComboBox ):
for i in range( self.count() ):
if data == QP.GetClientData( self, i ):
if data == self.itemData( i, QC.Qt.UserRole ):
self.setCurrentIndex( i )
@ -859,7 +859,7 @@ class ListBook( QW.QWidget ):
for i in range( self._list_box.count() ):
i_key = QP.GetClientData( self._list_box, i )
i_key = self._list_box.item( i ).data( QC.Qt.UserRole )
if i_key == key:
@ -878,7 +878,7 @@ class ListBook( QW.QWidget ):
else:
self._current_key = QP.GetClientData( self._list_box, selection )
self._current_key = self._list_box.item( selection ).data( QC.Qt.UserRole )
self._current_panel.setVisible( False )
@ -2287,7 +2287,7 @@ class StaticBoxSorterForListBoxTags( StaticBox ):
if selection != -1:
sort = QP.GetClientData( self._sorter, selection )
sort = self._sorter.GetValue()
self._tags_box.SetSort( sort )

View File

@ -697,7 +697,7 @@ class EditStringConverterPanel( ClientGUIScrolledPanels.EditPanel ):
self._data_timezone_encode = ClientGUICommon.BetterChoice( self )
self._data_timezone_offset = QP.MakeQSpinBox( self, min=-86400, max=86400 )
for e in ( 'hex', 'base64' ):
for e in ( 'hex', 'base64', 'url percent encoding' ):
self._data_encoding.addItem( e, e )

View File

@ -31,7 +31,7 @@ def GetDeleteFilesJobs( win, media, default_reason, suggested_file_service_key =
else:
raise HydrusExceptions.CancelledException()
raise HydrusExceptions.CancelledException( 'Dialog cancelled.' )
@ -111,7 +111,7 @@ def SelectFromList( win, title, choice_tuples, value_to_select = None, sort_tupl
else:
raise HydrusExceptions.CancelledException()
raise HydrusExceptions.CancelledException( 'Dialog cancelled.' )
@ -131,7 +131,7 @@ def SelectFromListButtons( win, title, choice_tuples ):
else:
raise HydrusExceptions.CancelledException()
raise HydrusExceptions.CancelledException( 'Dialog cancelled.' )

View File

@ -215,9 +215,7 @@ class EditExportFolderPanel( ClientGUIScrolledPanels.EditPanel ):
self._page_key = 'export folders placeholder'
self._predicates_box = ClientGUIListBoxes.ListBoxTagsActiveSearchPredicates( self._query_box, self._page_key )
self._tag_autocomplete = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self._query_box, self._predicates_box, self._page_key, file_search_context, allow_all_known_files = False, force_system_everything = True )
self._tag_autocomplete = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self._query_box, self._page_key, file_search_context, allow_all_known_files = False, force_system_everything = True )
#
@ -292,7 +290,6 @@ If you select synchronise, be careful!'''
self._type_box.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._query_box.Add( self._predicates_box, CC.FLAGS_EXPAND_BOTH_WAYS )
self._query_box.Add( self._tag_autocomplete )
self._period_box.Add( self._period, CC.FLAGS_EXPAND_PERPENDICULAR )

View File

@ -19,6 +19,7 @@ from . import HydrusGlobals as HG
from . import HydrusSerialisable
from . import HydrusTags
import os
import typing
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
@ -1488,18 +1489,6 @@ class ListBox( QW.QScrollArea ):
self.widget().update()
def GetClientData( self, index = None ):
if index is None:
return set( self._terms )
else:
return self._GetTerm( index )
def GetIdealHeight( self ):
text_height = self.fontMetrics().height()
@ -1578,8 +1567,6 @@ class ListBoxTags( ListBox ):
ListBox.__init__( self, *args, **kwargs )
self._get_current_predicates_callable = None
self._tag_display_type = ClientTags.TAG_DISPLAY_STORAGE
self._page_key = None # placeholder. if a subclass sets this, it changes menu behaviour to allow 'select this tag' menu pubsubs
@ -1604,6 +1591,16 @@ class ListBoxTags( ListBox ):
return texts
def _GetCurrentFileServiceKey( self ):
return CC.LOCAL_FILE_SERVICE_KEY
def _GetCurrentPagePredicates( self ) -> typing.Optional[ typing.Set[ ClientSearch.Predicate ] ]:
return None
def _GetNamespaceFromTerm( self, term ):
raise NotImplementedError()
@ -1692,7 +1689,9 @@ class ListBoxTags( ListBox ):
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 )
file_service_key = self._GetCurrentFileServiceKey()
HG.client_controller.pub( 'new_page_query', file_service_key, initial_predicates = predicates, page_name = page_name, activate_window = activate_window )
@ -2060,35 +2059,32 @@ class ListBoxTags( ListBox ):
if self._get_current_predicates_callable is not None:
current_predicates = self._GetCurrentPagePredicates()
if current_predicates is not None:
ClientGUIMenus.AppendSeparator( menu )
current_predicates = self._get_current_predicates_callable()
( include_predicates, exclude_predicates ) = self._GetSelectedIncludeExcludePredicates()
if current_predicates is not None:
if True in ( include_predicate in current_predicates for include_predicate in include_predicates ):
( include_predicates, exclude_predicates ) = self._GetSelectedIncludeExcludePredicates()
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 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 ):
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( 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 in current_predicates for exclude_predicate in 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( 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' )
@ -2309,151 +2305,11 @@ class ListBoxTagsPredicates( ListBoxTags ):
def GetPredicates( self ):
def GetPredicates( self ) -> typing.Set[ ClientSearch.Predicate ]:
return set( self._terms )
class ListBoxTagsActiveSearchPredicates( ListBoxTagsPredicates ):
has_counts = False
def __init__( self, parent, page_key, initial_predicates = None ):
if initial_predicates is None:
initial_predicates = []
ListBoxTagsPredicates.__init__( self, parent, height_num_chars = 6 )
self._page_key = page_key
self._get_current_predicates_callable = self.GetPredicates
if len( initial_predicates ) > 0:
for predicate in initial_predicates:
self._AppendTerm( predicate )
self._DataHasChanged()
HG.client_controller.sub( self, 'EnterPredicates', 'enter_predicates' )
def _Activate( self ):
if len( self._selected_terms ) > 0:
self._EnterPredicates( set( self._selected_terms ) )
def _DeleteActivate( self ):
self._Activate()
def _EnterPredicates( self, predicates, permit_add = True, permit_remove = True ):
if len( predicates ) == 0:
return
predicates_to_be_added = set()
predicates_to_be_removed = set()
for predicate in predicates:
predicate = predicate.GetCountlessCopy()
if self._HasPredicate( predicate ):
if permit_remove:
predicates_to_be_removed.add( predicate )
else:
if permit_add:
predicates_to_be_added.add( predicate )
predicates_to_be_removed.update( self._GetMutuallyExclusivePredicates( predicate ) )
for predicate in predicates_to_be_added:
self._AppendTerm( predicate )
for predicate in predicates_to_be_removed:
self._RemoveTerm( predicate )
self._SortByText()
self._DataHasChanged()
HG.client_controller.pub( 'refresh_query', self._page_key )
def _GetTextFromTerm( self, term ):
predicate = term
return predicate.ToString( render_for_user = True )
def _ProcessMenuPredicateEvent( self, command ):
( include_predicates, exclude_predicates ) = self._GetSelectedIncludeExcludePredicates()
if command == 'add_include_predicates':
self._EnterPredicates( include_predicates, permit_remove = False )
elif command == 'remove_include_predicates':
self._EnterPredicates( include_predicates, permit_add = False )
elif command == 'add_exclude_predicates':
self._EnterPredicates( exclude_predicates, permit_remove = False )
elif command == 'remove_exclude_predicates':
self._EnterPredicates( exclude_predicates, permit_add = False )
def EnterPredicates( self, page_key, predicates, permit_add = True, permit_remove = True ):
if page_key == self._page_key:
self._EnterPredicates( predicates, permit_add = permit_add, permit_remove = permit_remove )
def SetPredicates( self, predicates ):
self._Clear()
for predicate in predicates:
self._AppendTerm( predicate )
self._DataHasChanged()
class ListBoxTagsAC( ListBoxTagsPredicates ):
def __init__( self, parent, callable, service_key, float_mode, **kwargs ):
@ -3331,78 +3187,6 @@ class ListBoxTagsSelectionHoverFrame( ListBoxTagsSelection ):
HG.client_controller.pub( 'canvas_manage_tags', self._canvas_key )
class ListBoxTagsSelectionManagementPanel( ListBoxTagsSelection ):
def __init__( self, parent, page_key, tag_display_type, predicates_callable = None ):
ListBoxTagsSelection.__init__( self, parent, tag_display_type, include_counts = True )
self._minimum_height_num_chars = 15
self._page_key = page_key
self._get_current_predicates_callable = predicates_callable
HG.client_controller.sub( self, 'IncrementTagsByMediaPubsub', 'increment_tags_selection' )
HG.client_controller.sub( self, 'SetTagsByMediaPubsub', 'new_tags_selection' )
HG.client_controller.sub( self, 'ChangeTagServicePubsub', 'change_tag_service' )
def _Activate( self ):
predicates = [ ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_TAG, term ) for term in self._selected_terms ]
if len( predicates ) > 0:
HG.client_controller.pub( 'enter_predicates', self._page_key, predicates )
def _ProcessMenuPredicateEvent( self, command ):
( include_predicates, exclude_predicates ) = self._GetSelectedIncludeExcludePredicates()
if command == 'add_include_predicates':
HG.client_controller.pub( 'enter_predicates', self._page_key, include_predicates, permit_remove = False )
elif command == 'remove_include_predicates':
HG.client_controller.pub( 'enter_predicates', self._page_key, include_predicates, permit_add = False )
elif command == 'add_exclude_predicates':
HG.client_controller.pub( 'enter_predicates', self._page_key, exclude_predicates, permit_remove = False )
elif command == 'remove_exclude_predicates':
HG.client_controller.pub( 'enter_predicates', self._page_key, exclude_predicates, permit_add = False )
def ChangeTagServicePubsub( self, page_key, service_key ):
if page_key == self._page_key:
self.ChangeTagService( service_key )
def IncrementTagsByMediaPubsub( self, page_key, media ):
if page_key == self._page_key:
self.IncrementTagsByMedia( media )
def SetTagsByMediaPubsub( self, page_key, media, force_reload = False ):
if page_key == self._page_key:
self.SetTagsByMedia( media, force_reload = force_reload )
class ListBoxTagsSelectionTagsDialog( ListBoxTagsSelection ):
render_for_user = False

View File

@ -12,6 +12,7 @@ from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from . import QtPorting as QP
import locale
import os
import traceback
mpv_failed_reason = 'MPV seems ok!'
@ -462,7 +463,23 @@ class mpvWidget( QW.QWidget ):
def UpdateConf( self ):
mpv_config_path = HydrusPaths.ConvertPortablePathToAbsPath( HG.client_controller.new_options.GetString( 'mpv_conf_path_portable' ) )
mpv_config_path = HG.client_controller.GetMPVConfPath()
if not os.path.exists( mpv_config_path ):
default_mpv_config_path = HG.client_controller.GetDefaultMPVConfPath()
if not os.path.exists( default_mpv_config_path ):
HydrusData.ShowText( 'There is no default mpv configuration file to load! Perhaps there is a problem with your install?' )
return
else:
HydrusPaths.MirrorFile( default_mpv_config_path, mpv_config_path )
#To load an existing config file (by default it doesn't load the user/global config like standalone mpv does):
if hasattr( mpv, '_mpv_load_config_file' ):
@ -480,7 +497,7 @@ class mpvWidget( QW.QWidget ):
else:
HydrusData.Print( 'Was unable to load mpv.conf--has the API changed?' )
HydrusData.Print( 'Was unable to load mpv.conf--has the MPV API changed?' )

View File

@ -43,6 +43,7 @@ import os
import random
import time
import traceback
import typing
from . import QtPorting as QP
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
@ -665,6 +666,96 @@ class ManagementController( HydrusSerialisable.SerialisableBase ):
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_MANAGEMENT_CONTROLLER ] = ManagementController
class ListBoxTagsSelectionManagementPanel( ClientGUIListBoxes.ListBoxTagsSelection ):
def __init__( self, parent, management_controller: ManagementController, page_key, tag_display_type, tag_autocomplete: typing.Optional[ ClientGUIACDropdown.AutoCompleteDropdownTagsRead ] = None ):
ClientGUIListBoxes.ListBoxTagsSelection.__init__( self, parent, tag_display_type, include_counts = True )
self._management_controller = management_controller
self._minimum_height_num_chars = 15
self._page_key = page_key
self._tag_autocomplete = tag_autocomplete
HG.client_controller.sub( self, 'IncrementTagsByMediaPubsub', 'increment_tags_selection' )
HG.client_controller.sub( self, 'SetTagsByMediaPubsub', 'new_tags_selection' )
HG.client_controller.sub( self, 'ChangeTagServicePubsub', 'change_tag_service' )
def _Activate( self ):
predicates = [ ClientSearch.Predicate( ClientSearch.PREDICATE_TYPE_TAG, term ) for term in self._selected_terms ]
if len( predicates ) > 0:
HG.client_controller.pub( 'enter_predicates', self._page_key, predicates )
def _GetCurrentFileServiceKey( self ):
return self._management_controller.GetKey( 'file_service' )
def _GetCurrentPagePredicates( self ) -> typing.Optional[ typing.Set[ ClientSearch.Predicate ] ]:
if self._tag_autocomplete is None:
return None
else:
return self._tag_autocomplete.GetPredicates()
def _ProcessMenuPredicateEvent( self, command ):
( include_predicates, exclude_predicates ) = self._GetSelectedIncludeExcludePredicates()
if command == 'add_include_predicates':
HG.client_controller.pub( 'enter_predicates', self._page_key, include_predicates, permit_remove = False )
elif command == 'remove_include_predicates':
HG.client_controller.pub( 'enter_predicates', self._page_key, include_predicates, permit_add = False )
elif command == 'add_exclude_predicates':
HG.client_controller.pub( 'enter_predicates', self._page_key, exclude_predicates, permit_remove = False )
elif command == 'remove_exclude_predicates':
HG.client_controller.pub( 'enter_predicates', self._page_key, exclude_predicates, permit_add = False )
def ChangeTagServicePubsub( self, page_key, service_key ):
if page_key == self._page_key:
self.ChangeTagService( service_key )
def IncrementTagsByMediaPubsub( self, page_key, media ):
if page_key == self._page_key:
self.IncrementTagsByMedia( media )
def SetTagsByMediaPubsub( self, page_key, media, force_reload = False ):
if page_key == self._page_key:
self.SetTagsByMedia( media, force_reload = force_reload )
def managementScrollbarValueChanged( value ):
HG.client_controller.pub( 'top_level_window_move_event' )
@ -727,7 +818,7 @@ class ManagementPanel( QW.QScrollArea ):
tags_box = ClientGUICommon.StaticBoxSorterForListBoxTags( self, 'selection tags' )
t = ClientGUIListBoxes.ListBoxTagsSelectionManagementPanel( tags_box, self._page_key, self.TAG_DISPLAY_TYPE )
t = ListBoxTagsSelectionManagementPanel( tags_box, self._management_controller, self._page_key, self.TAG_DISPLAY_TYPE )
tags_box.SetTagsBox( t )
@ -896,9 +987,7 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
file_search_context = management_controller.GetVariable( 'file_search_context' )
self._active_predicates_box = ClientGUIListBoxes.ListBoxTagsActiveSearchPredicates( self._filtering_panel, self._page_key )
self._ac_read = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self._filtering_panel, self._active_predicates_box, self._page_key, file_search_context, media_sort_widget = self._media_sort, media_collect_widget = self._media_collect, allow_all_known_files = False, force_system_everything = True )
self._tag_autocomplete = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self._filtering_panel, self._page_key, file_search_context, media_sort_widget = self._media_sort, media_collect_widget = self._media_collect, allow_all_known_files = False, force_system_everything = True )
self._both_files_match = QW.QCheckBox( self._filtering_panel )
@ -984,8 +1073,7 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
gridbox = ClientGUICommon.WrapInGrid( self._filtering_panel, rows )
self._filtering_panel.Add( self._active_predicates_box, CC.FLAGS_EXPAND_PERPENDICULAR )
self._filtering_panel.Add( self._ac_read, CC.FLAGS_EXPAND_PERPENDICULAR )
self._filtering_panel.Add( self._tag_autocomplete, CC.FLAGS_EXPAND_PERPENDICULAR )
self._filtering_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self._filtering_panel.Add( text_and_button_hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
self._filtering_panel.Add( self._launch_filter, CC.FLAGS_EXPAND_PERPENDICULAR )
@ -1040,7 +1128,7 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
def _GetFileSearchContextAndBothFilesMatch( self ):
file_search_context = self._ac_read.GetFileSearchContext()
file_search_context = self._tag_autocomplete.GetFileSearchContext()
both_files_match = self._both_files_match.isChecked()
@ -1163,7 +1251,7 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
self._UpdateBothFilesMatchButton()
if self._ac_read.IsSynchronised():
if self._tag_autocomplete.IsSynchronised():
self._dupe_count_numbers_dirty = True
@ -3636,6 +3724,10 @@ class ManagementPanelPetitions( ManagementPanel ):
self._contents.setSelectionMode( QW.QAbstractItemView.ExtendedSelection )
self._contents.itemDoubleClicked.connect( self.EventContentDoubleClick )
( min_width, min_height ) = ClientGUIFunctions.ConvertTextToPixels( self._contents, ( 16, 20 ) )
self._contents.setMinimumHeight( min_height )
self._process = QW.QPushButton( 'process', self._petition_panel )
self._process.clicked.connect( self.EventProcess )
self._process.setObjectName( 'HydrusAccept' )
@ -4232,6 +4324,8 @@ class ManagementPanelPetitions( ManagementPanel ):
QW.QMessageBox.warning( self, 'Warning', 'modify users does not work yet!' )
return
with ClientGUIDialogs.DialogModifyAccounts( self, self._petition_service_key, ( self._current_petition.GetPetitionerAccount(), ) ) as dlg:
dlg.exec()
@ -4336,11 +4430,9 @@ class ManagementPanelQuery( ManagementPanel ):
self._search_panel = ClientGUICommon.StaticBox( self, 'search' )
self._current_predicates_box = ClientGUIListBoxes.ListBoxTagsActiveSearchPredicates( self._search_panel, self._page_key )
synchronised = self._management_controller.GetVariable( 'synchronised' )
self._searchbox = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self._search_panel, self._current_predicates_box, self._page_key, file_search_context, media_sort_widget = self._media_sort, media_collect_widget = self._media_collect, media_callable = self._page.GetMedia, synchronised = synchronised )
self._tag_autocomplete = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self._search_panel, self._page_key, file_search_context, media_sort_widget = self._media_sort, media_collect_widget = self._media_collect, media_callable = self._page.GetMedia, synchronised = synchronised )
self._cancel_search_button = ClientGUICommon.BetterBitmapButton( self._search_panel, CC.global_pixmaps().stop, self._CancelSearch )
@ -4348,10 +4440,9 @@ class ManagementPanelQuery( ManagementPanel ):
hbox = QP.HBoxLayout()
QP.AddToLayout( hbox, self._searchbox, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, self._tag_autocomplete, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, self._cancel_search_button, CC.FLAGS_VCENTER )
self._search_panel.Add( self._current_predicates_box, CC.FLAGS_EXPAND_BOTH_WAYS )
self._search_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
@ -4387,7 +4478,7 @@ class ManagementPanelQuery( ManagementPanel ):
if self._search_enabled:
t = ClientGUIListBoxes.ListBoxTagsSelectionManagementPanel( tags_box, self._page_key, self.TAG_DISPLAY_TYPE, predicates_callable = self._current_predicates_box.GetPredicates )
t = ListBoxTagsSelectionManagementPanel( tags_box, self._management_controller, self._page_key, self.TAG_DISPLAY_TYPE, tag_autocomplete = self._tag_autocomplete )
file_search_context = self._management_controller.GetVariable( 'file_search_context' )
@ -4397,7 +4488,7 @@ class ManagementPanelQuery( ManagementPanel ):
else:
t = ClientGUIListBoxes.ListBoxTagsSelectionManagementPanel( tags_box, self._page_key, self.TAG_DISPLAY_TYPE )
t = ListBoxTagsSelectionManagementPanel( tags_box, self._management_controller, self._page_key, self.TAG_DISPLAY_TYPE )
tags_box.SetTagsBox( t )
@ -4415,7 +4506,7 @@ class ManagementPanelQuery( ManagementPanel ):
if self._management_controller.GetVariable( 'synchronised' ):
file_search_context = self._searchbox.GetFileSearchContext()
file_search_context = self._tag_autocomplete.GetFileSearchContext()
self._management_controller.SetVariable( 'file_search_context', file_search_context )
@ -4478,7 +4569,7 @@ class ManagementPanelQuery( ManagementPanel ):
if do_layout:
self._searchbox.ForceSizeCalcNow()
self._tag_autocomplete.ForceSizeCalcNow()
@ -4497,7 +4588,7 @@ class ManagementPanelQuery( ManagementPanel ):
if self._search_enabled:
self._searchbox.CancelCurrentResultsFetchJob()
self._tag_autocomplete.CancelCurrentResultsFetchJob()
self._query_job_key.Cancel()
@ -4509,7 +4600,7 @@ class ManagementPanelQuery( ManagementPanel ):
if self._search_enabled:
self._searchbox.CancelCurrentResultsFetchJob()
self._tag_autocomplete.CancelCurrentResultsFetchJob()
self._query_job_key.Cancel()
@ -4519,7 +4610,7 @@ class ManagementPanelQuery( ManagementPanel ):
if self._search_enabled:
return self._current_predicates_box.GetPredicates()
return self._tag_autocomplete.GetPredicates()
else:
@ -4549,7 +4640,7 @@ class ManagementPanelQuery( ManagementPanel ):
if self._search_enabled:
HG.client_controller.CallAfterQtSafe( self._searchbox, self._searchbox.setFocus, QC.Qt.OtherFocusReason)
HG.client_controller.CallAfterQtSafe( self._tag_autocomplete, self._tag_autocomplete.setFocus, QC.Qt.OtherFocusReason)

View File

@ -1111,6 +1111,8 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
session.AddPageTuple( page )
session = session.Duplicate() # this ensures we are using fresh new objects
self.InsertSessionPageTuples( index + 1, session.GetPageTuples() )
@ -2621,6 +2623,11 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
tag_service_key = CC.COMBINED_TAG_SERVICE_KEY
if file_service_key == CC.COMBINED_FILE_SERVICE_KEY and tag_service_key == CC.COMBINED_TAG_SERVICE_KEY:
tag_service_key = CC.COMBINED_LOCAL_FILE_SERVICE_KEY
tag_search_context = ClientSearch.TagSearchContext( service_key = tag_service_key )
file_search_context = ClientSearch.FileSearchContext( file_service_key = file_service_key, tag_search_context = tag_search_context, predicates = initial_predicates )

View File

@ -827,9 +827,9 @@ class ReviewServicePanel( QW.QWidget ):
self._my_updater = ClientGUIAsync.FastThreadToGUIUpdater( self, self._Refresh )
self._address = ClientGUICommon.BetterStaticText( self )
self._functional = ClientGUICommon.BetterStaticText( self )
self._bandwidth_summary = ClientGUICommon.BetterStaticText( self )
self._address = ClientGUICommon.BetterStaticText( self, ellipsize_end = True )
self._functional = ClientGUICommon.BetterStaticText( self, ellipsize_end = True )
self._bandwidth_summary = ClientGUICommon.BetterStaticText( self, ellipsize_end = True )
self._bandwidth_panel = QW.QWidget( self )
@ -920,10 +920,10 @@ class ReviewServicePanel( QW.QWidget ):
self._my_updater = ClientGUIAsync.FastThreadToGUIUpdater( self, self._Refresh )
self._title_and_expires_st = ClientGUICommon.BetterStaticText( self )
self._status_st = ClientGUICommon.BetterStaticText( self )
self._next_sync_st = ClientGUICommon.BetterStaticText( self )
self._bandwidth_summary = ClientGUICommon.BetterStaticText( self )
self._title_and_expires_st = ClientGUICommon.BetterStaticText( self, ellipsize_end = True )
self._status_st = ClientGUICommon.BetterStaticText( self, ellipsize_end = True )
self._next_sync_st = ClientGUICommon.BetterStaticText( self, ellipsize_end = True )
self._bandwidth_summary = ClientGUICommon.BetterStaticText( self, ellipsize_end = True )
self._bandwidth_panel = QW.QWidget( self )

View File

@ -3284,7 +3284,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._media_zooms = QW.QLineEdit( self )
self._media_zooms.textChanged.connect( self.EventZoomsChanged )
self._mpv_conf_path = QP.FilePickerCtrl( self )
self._mpv_conf_path = QP.FilePickerCtrl( self, starting_directory = os.path.join( HC.STATIC_DIR, 'mpv-conf' ) )
self._animated_scanbar_height = QP.MakeQSpinBox( self, min=1, max=255 )
self._animated_scanbar_nub_width = QP.MakeQSpinBox( self, min=1, max=63 )
@ -3311,7 +3311,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._media_viewer_cursor_autohide_time_ms.SetValue( self._new_options.GetNoneableInteger( 'media_viewer_cursor_autohide_time_ms' ) )
self._anchor_and_hide_canvas_drags.setChecked( self._new_options.GetBoolean( 'anchor_and_hide_canvas_drags' ) )
self._touchscreen_canvas_drags_unanchor.setChecked( self._new_options.GetBoolean( 'touchscreen_canvas_drags_unanchor' ) )
self._mpv_conf_path.SetPath( HydrusPaths.ConvertPortablePathToAbsPath( self._new_options.GetString( 'mpv_conf_path_portable' ) ) )
self._animated_scanbar_height.setValue( self._new_options.GetInteger( 'animated_scanbar_height' ) )
self._animated_scanbar_nub_width.setValue( self._new_options.GetInteger( 'animated_scanbar_nub_width' ) )
@ -3340,7 +3339,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
rows.append( ( 'Prefer system FFMPEG:', self._use_system_ffmpeg ) )
rows.append( ( 'Always Loop GIFs:', self._always_loop_gifs ) )
rows.append( ( 'Media zooms:', self._media_zooms ) )
rows.append( ( 'MPV conf path:', self._mpv_conf_path ) )
rows.append( ( 'Set a new mpv.conf on dialog ok?:', self._mpv_conf_path ) )
rows.append( ( 'Animation scanbar height:', self._animated_scanbar_height ) )
rows.append( ( 'Animation scanbar nub width:', self._animated_scanbar_nub_width ) )
rows.append( ( 'Time until mouse cursor autohides on media viewer:', self._media_viewer_cursor_autohide_time_ms ) )
@ -3570,7 +3569,22 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._new_options.SetNoneableInteger( 'media_viewer_cursor_autohide_time_ms', self._media_viewer_cursor_autohide_time_ms.GetValue() )
self._new_options.SetString( 'mpv_conf_path_portable', HydrusPaths.ConvertAbsPathToPortablePath( self._mpv_conf_path.GetPath() ) )
mpv_conf_path = self._mpv_conf_path.GetPath()
if mpv_conf_path is not None and mpv_conf_path != '' and os.path.exists( mpv_conf_path ) and os.path.isfile( mpv_conf_path ):
dest_mpv_conf_path = HG.client_controller.GetMPVConfPath()
try:
HydrusPaths.MirrorFile( mpv_conf_path, dest_mpv_conf_path )
except Exception as e:
HydrusData.ShowText( 'Could not set the mpv conf path "{}" to "{}"! Error follows!'.format( mpv_conf_path, dest_mpv_conf_path ) )
HydrusData.ShowException( e )
self._new_options.SetInteger( 'animated_scanbar_height', self._animated_scanbar_height.value() )
self._new_options.SetInteger( 'animated_scanbar_nub_width', self._animated_scanbar_nub_width.value() )

View File

@ -2505,11 +2505,9 @@ class ReviewFileMaintenance( ClientGUIScrolledPanels.ReviewPanel ):
page_key = HydrusData.GenerateKey()
self._current_predicates_box = ClientGUIListBoxes.ListBoxTagsActiveSearchPredicates( self._search_panel, page_key )
file_search_context = ClientSearch.FileSearchContext( file_service_key = CC.LOCAL_FILE_SERVICE_KEY )
self._tag_ac_input = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self._search_panel, self._current_predicates_box, page_key, file_search_context, allow_all_known_files = False, force_system_everything = True )
self._tag_autocomplete = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self._search_panel, page_key, file_search_context, allow_all_known_files = False, force_system_everything = True )
self._run_search_st = ClientGUICommon.BetterStaticText( self._search_panel, label = 'no results yet' )
@ -2520,8 +2518,7 @@ class ReviewFileMaintenance( ClientGUIScrolledPanels.ReviewPanel ):
QP.AddToLayout( hbox, self._run_search_st, CC.FLAGS_EXPAND_BOTH_WAYS )
QP.AddToLayout( hbox, self._run_search, CC.FLAGS_VCENTER )
self._search_panel.Add( self._current_predicates_box, CC.FLAGS_EXPAND_BOTH_WAYS )
self._search_panel.Add( self._tag_ac_input, CC.FLAGS_EXPAND_PERPENDICULAR )
self._search_panel.Add( self._tag_autocomplete, CC.FLAGS_EXPAND_PERPENDICULAR )
self._search_panel.Add( hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
@ -2762,7 +2759,7 @@ class ReviewFileMaintenance( ClientGUIScrolledPanels.ReviewPanel ):
self._run_search.setEnabled( False )
file_search_context = self._tag_ac_input.GetFileSearchContext()
file_search_context = self._tag_autocomplete.GetFileSearchContext()
HG.client_controller.CallToThread( do_it, file_search_context )

View File

@ -37,9 +37,7 @@ class EditFavouriteSearchPanel( ClientGUIScrolledPanels.EditPanel ):
from . import ClientGUIACDropdown
from . import ClientGUIListBoxes
self._predicates_box = ClientGUIListBoxes.ListBoxTagsActiveSearchPredicates( self, page_key )
self._searchbox = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self, self._predicates_box, page_key, file_search_context, media_sort_widget = self._media_sort, media_collect_widget = self._media_collect, synchronised = synchronised, hide_favourites_edit_actions = True )
self._tag_autocomplete = ClientGUIACDropdown.AutoCompleteDropdownTagsRead( self, page_key, file_search_context, media_sort_widget = self._media_sort, media_collect_widget = self._media_collect, synchronised = synchronised, hide_favourites_edit_actions = True )
self._include_media_sort = QW.QCheckBox( self )
self._include_media_collect = QW.QCheckBox( self )
@ -95,8 +93,7 @@ class EditFavouriteSearchPanel( ClientGUIScrolledPanels.EditPanel ):
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, top_gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
QP.AddToLayout( vbox, self._predicates_box, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._searchbox, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._tag_autocomplete, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, bottom_gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
self.widget().setLayout( vbox )
@ -150,8 +147,8 @@ class EditFavouriteSearchPanel( ClientGUIScrolledPanels.EditPanel ):
name = self._name.text()
file_search_context = self._searchbox.GetFileSearchContext()
synchronised = self._searchbox.IsSynchronised()
file_search_context = self._tag_autocomplete.GetFileSearchContext()
synchronised = self._tag_autocomplete.IsSynchronised()
if self._include_media_sort.isChecked():

View File

@ -161,6 +161,12 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
self._prefer_blacklist = prefer_blacklist
self._namespaces = namespaces
self._wildcard_replacements = {}
self._wildcard_replacements[ '*' ] = ''
self._wildcard_replacements[ '*:' ] = ':'
self._wildcard_replacements[ '*:*' ] = ':'
#
help_button = ClientGUICommon.BetterBitmapButton( self, CC.global_pixmaps().help, self._ShowHelp )
@ -261,7 +267,9 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
def _AdvancedAddBlacklist( self, tag_slice ):
if tag_slice in self._advanced_blacklist.GetClientData():
tag_slice = self._CleanTagSliceInput( tag_slice )
if tag_slice in self._advanced_blacklist.GetTags():
self._advanced_blacklist.RemoveTags( ( tag_slice, ) )
@ -299,7 +307,9 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
def _AdvancedAddWhitelist( self, tag_slice ):
if tag_slice in self._advanced_whitelist.GetClientData():
tag_slice = self._CleanTagSliceInput( tag_slice )
if tag_slice in self._advanced_whitelist.GetTags():
self._advanced_whitelist.RemoveTags( ( tag_slice, ) )
@ -382,6 +392,31 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
self._UpdateStatus()
def _CleanTagSliceInput( self, tag_slice ):
while '**' in tag_slice:
tag_slice = tag_slice.replace( '**', '*' )
if tag_slice in self._wildcard_replacements:
tag_slice = self._wildcard_replacements[ tag_slice ]
if ':' in tag_slice:
( namespace, subtag ) = HydrusTags.SplitTag( tag_slice )
if subtag == '*':
tag_slice = '{}:'.format( namespace )
return tag_slice
def _CurrentlyBlocked( self, tag_slice ):
if tag_slice in ( '', ':' ):
@ -403,7 +438,7 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
test_slices = { '', tag_slice }
blacklist = set( self._advanced_blacklist.GetClientData() )
blacklist = set( self._advanced_blacklist.GetTags() )
return not blacklist.isdisjoint( test_slices )
@ -473,8 +508,8 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
def _GetWhiteBlacklistsPossible( self ):
blacklist_tag_slices = self._advanced_blacklist.GetClientData()
whitelist_tag_slices = self._advanced_whitelist.GetClientData()
blacklist_tag_slices = self._advanced_blacklist.GetTags()
whitelist_tag_slices = self._advanced_whitelist.GetTags()
blacklist_is_only_simples = set( blacklist_tag_slices ).issubset( { '', ':' } )
@ -813,7 +848,7 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
for tag_slice in tag_slices:
if tag_slice in ( '', ':' ) and tag_slice in self._simple_whitelist.GetClientData():
if tag_slice in ( '', ':' ) and tag_slice in self._simple_whitelist.GetTags():
self._AdvancedAddBlacklist( tag_slice )
@ -866,8 +901,8 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
( whitelist_possible, blacklist_possible ) = self._GetWhiteBlacklistsPossible()
whitelist_tag_slices = self._advanced_whitelist.GetClientData()
blacklist_tag_slices = self._advanced_blacklist.GetClientData()
whitelist_tag_slices = self._advanced_whitelist.GetTags()
blacklist_tag_slices = self._advanced_blacklist.GetTags()
if whitelist_possible:
@ -935,8 +970,8 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
#
whitelist_tag_slices = self._advanced_whitelist.GetClientData()
blacklist_tag_slices = self._advanced_blacklist.GetClientData()
whitelist_tag_slices = self._advanced_whitelist.GetTags()
blacklist_tag_slices = self._advanced_blacklist.GetTags()
if blacklist_possible:
@ -995,8 +1030,8 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
#
whitelist_tag_slices = self._advanced_whitelist.GetClientData()
blacklist_tag_slices = self._advanced_blacklist.GetClientData()
whitelist_tag_slices = self._advanced_whitelist.GetTags()
blacklist_tag_slices = self._advanced_blacklist.GetTags()
if len( blacklist_tag_slices ) == 0:
@ -1096,7 +1131,7 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
tag_slice = QP.GetClientData( self._simple_whitelist_global_checkboxes, index )
if tag_slice in ( '', ':' ) and tag_slice in self._simple_whitelist.GetClientData():
if tag_slice in ( '', ':' ) and tag_slice in self._simple_whitelist.GetTags():
self._AdvancedAddBlacklist( tag_slice )
@ -1111,12 +1146,12 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
tag_filter = ClientTags.TagFilter()
for tag_slice in self._advanced_blacklist.GetClientData():
for tag_slice in self._advanced_blacklist.GetTags():
tag_filter.SetRule( tag_slice, CC.FILTER_BLACKLIST )
for tag_slice in self._advanced_whitelist.GetClientData():
for tag_slice in self._advanced_whitelist.GetTags():
tag_filter.SetRule( tag_slice, CC.FILTER_WHITELIST )
@ -1124,7 +1159,7 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
return tag_filter
def SetValue( self, tag_filter ):
def SetValue( self, tag_filter: ClientTags.TagFilter ):
blacklist_tag_slices = [ tag_slice for ( tag_slice, rule ) in tag_filter.GetTagSlicesToRules().items() if rule == CC.FILTER_BLACKLIST ]
whitelist_tag_slices = [ tag_slice for ( tag_slice, rule ) in tag_filter.GetTagSlicesToRules().items() if rule == CC.FILTER_WHITELIST ]

View File

@ -1390,7 +1390,7 @@ class ServiceTagImportOptions( HydrusSerialisable.SerialisableBase ):
tags.difference_update( applicable_tags )
existing_applicable_tags = HG.client_controller.Read( 'filter_existing_tags', service_key, tags )
existing_applicable_tags = HG.client_controller.Read( 'filter_existing_tags', service_key, applicable_tags )
tags.update( existing_applicable_tags )

View File

@ -429,6 +429,12 @@ class NetworkDomainManager( HydrusSerialisable.SerialisableBase ):
return self._file_post_default_tag_import_options
# some lad decided to api convert one url type to another
if url_class.GetURLType() not in ( HC.URL_TYPE_POST, HC.URL_TYPE_WATCHABLE ):
return self._file_post_default_tag_import_options
url_class_key = url_class.GetMatchKey()
if url_class_key in self._url_class_keys_to_default_tag_import_options:

View File

@ -352,11 +352,7 @@ class NetworkLoginManager( HydrusSerialisable.SerialisableBase ):
except Exception as e:
message = 'Service has had a recent error or is otherwise not functional! Specific error was:'
message += os.linesep * 2
message += str( e )
message += os.linesep * 2
message += 'You might like to try refreshing its account in \'review services\'.'
message = 'Service has had a recent error or is otherwise not functional! You might like to try refreshing its account in \'review services\'. Specific error was: {}'.format( e )
raise HydrusExceptions.ValidationException( message )
@ -1014,9 +1010,9 @@ class LoginScriptHydrus( object ):
HydrusData.Print( 'Successfully logged into ' + service.GetName() + '.' )
else:
elif service.IsFunctional():
service.DelayFutureRequests( 'Could not log in for unknown reason.' )
service.DelayFutureRequests( 'Could not log in for unknown reason. Current service status: {}'.format( service.GetStatusString() ) )
except Exception as e:

View File

@ -417,7 +417,6 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'strings' ][ 'default_gug_name' ] = 'safebooru tag search'
self._dictionary[ 'strings' ][ 'has_audio_label' ] = '\U0001F50A'
self._dictionary[ 'strings' ][ 'has_duration_label' ] = ' \u23F5 '
self._dictionary[ 'strings' ][ 'mpv_conf_path_portable' ] = HydrusPaths.ConvertAbsPathToPortablePath( os.path.join( HC.STATIC_DIR, 'mpv-conf', 'default_mpv.conf' ) )
self._dictionary[ 'string_list' ] = {}

View File

@ -2407,7 +2407,7 @@ class ParseNodeContentLink( HydrusSerialisable.SerialisableBase ):
if job_key.IsCancelled():
raise HydrusExceptions.CancelledException()
raise HydrusExceptions.CancelledException( 'Job was cancelled.' )
@ -2696,7 +2696,7 @@ class ParseRootFileLookup( HydrusSerialisable.SerialisableBaseNamed ):
if job_key.IsCancelled():
raise HydrusExceptions.CancelledException()
raise HydrusExceptions.CancelledException( 'Job was cancelled.' )
parsing_text = network_job.GetContentText()
@ -2918,40 +2918,54 @@ class StringConverter( HydrusSerialisable.SerialisableBase ):
elif transformation_type == STRING_TRANSFORMATION_ENCODE:
# due to py3, this is now a bit of a pain
# _for now_, let's convert to bytes and then spit out a str
encode_type = data
if isinstance( s, str ):
if encode_type == 'url percent encoding':
s = bytes( s, 'utf-8' )
s = urllib.parse.quote( s, safe = '' )
if encode_type == 'hex':
else:
s = s.hex()
# due to py3, this is now a bit of a pain
# _for now_, let's convert to bytes and then spit out a str
elif encode_type == 'base64':
if isinstance( s, str ):
s = bytes( s, 'utf-8' )
s = str( base64.b64encode( s ), 'utf-8' )
if encode_type == 'hex':
s = s.hex()
elif encode_type == 'base64':
s = str( base64.b64encode( s ), 'utf-8' )
elif transformation_type == STRING_TRANSFORMATION_DECODE:
# due to py3, this is now a bit of a pain
# as this is mostly used for hash stuff, _for now_, let's spit out a **bytes**
# the higher up object will have responsibility for coercing to str if needed
encode_type = data
if encode_type == 'hex':
if encode_type == 'url percent encoding':
s = bytes.fromhex( s )
s = urllib.parse.unquote( s )
elif encode_type == 'base64':
else:
s = base64.b64decode( s )
# due to py3, this is now a bit of a pain
# as this is mostly used for hash stuff, _for now_, let's spit out a **bytes**
# the higher up object will have responsibility for coercing to str if needed
if encode_type == 'hex':
s = bytes.fromhex( s )
elif encode_type == 'base64':
s = base64.b64decode( s )
elif transformation_type == STRING_TRANSFORMATION_REVERSE:

View File

@ -86,6 +86,11 @@ def ConvertEntryTextToSearchText( entry_text ):
wildcard_text = entry_text
while '**' in wildcard_text:
wildcard_text = wildcard_text.replace( '**', '*' )
entry_text = ConvertTagToSearchable( entry_text )
( namespace, subtag ) = HydrusTags.SplitTag( entry_text )

View File

@ -1,29 +1,29 @@
import os
import sys
# dirs
# old method of getting frozen dir, doesn't work for symlinks looks like:
# BASE_DIR = getattr( sys, '_MEIPASS', None )
if getattr( sys, 'frozen', False ):
RUNNING_FROM_FROZEN_BUILD = getattr( sys, 'frozen', False )
if RUNNING_FROM_FROZEN_BUILD:
RUNNING_FROM_FROZEN_BUILD = True
real_exe_path = os.path.realpath( sys.executable )
# we are in a pyinstaller frozen app
BASE_DIR = getattr( sys, '_MEIPASS', None )
if BASE_DIR is None:
raise Exception( 'It seems this hydrus is running from a frozen bundle, but there was no _MEIPASS variable under sys to define the bundle directory.' )
BASE_DIR = os.path.dirname( real_exe_path )
else:
# maybe this is better?
# os.path.dirname( __file__ )
RUNNING_FROM_FROZEN_BUILD = False
BASE_DIR = sys.path[0]
try:
hc_realpath_dir = os.path.dirname( os.path.realpath( __file__ ) )
BASE_DIR = os.path.split( hc_realpath_dir )[0]
except NameError: # if __file__ is not defined due to some weird OS
BASE_DIR = os.path.realpath( sys.path[0] )
if BASE_DIR == '':
@ -70,7 +70,7 @@ options = {}
# Misc
NETWORK_VERSION = 18
SOFTWARE_VERSION = 388
SOFTWARE_VERSION = 389
CLIENT_API_VERSION = 11
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -242,7 +242,7 @@ class FilePickerCtrl( QW.QWidget ):
filePickerChanged = QC.Signal()
def __init__( self, parent = None, wildcard = None ):
def __init__( self, parent = None, wildcard = None, starting_directory = None ):
QW.QWidget.__init__( self, parent )
@ -265,6 +265,8 @@ class FilePickerCtrl( QW.QWidget ):
self._wildcard = wildcard
self._starting_directory = starting_directory
def SetPath( self, path ):
@ -285,6 +287,11 @@ class FilePickerCtrl( QW.QWidget ):
existing_path = self._path_edit.text()
if existing_path == '' and self._starting_directory is not None:
existing_path = self._starting_directory
if self._save_mode:
if self._wildcard:
@ -1714,7 +1721,7 @@ class CheckListBox( QW.QListWidget ):
def GetChecked( self ):
result = [ GetClientData( self, index ) for index in self.GetCheckedItems() ]
result = [ self.item( index ).data( QC.Qt.UserRole ) for index in self.GetCheckedItems() ]
return result
@ -1723,7 +1730,7 @@ class CheckListBox( QW.QListWidget ):
for index in range( self.count() ):
data = GetClientData( self, index )
data = self.item( index ).data( QC.Qt.UserRole )
check_it = data in datas
@ -2487,10 +2494,8 @@ class CollectComboCtrl( QW.QComboBox ):
self.view().pressed.connect( self._HandleItemPressed )
if QW.QApplication.style().metaObject().className() == "QFusionStyle":
self.setItemDelegate( CheckBoxDelegate() )
# this was previously 'if Fusion style only', but as it works for normal styles too, it is more helpful to have it always on
self.setItemDelegate( CheckBoxDelegate() )
self.setModel( QG.QStandardItemModel( self ) )
@ -2550,7 +2555,7 @@ class CollectComboCtrl( QW.QComboBox ):
for index in self.GetCheckedItems():
(collect_type, collect_data) = GetClientData( self, index )
(collect_type, collect_data) = self.itemData( index, QC.Qt.UserRole )
if collect_type == 'namespace':
@ -2596,7 +2601,7 @@ class CollectComboCtrl( QW.QComboBox ):
for index in range( self.count() ):
( collect_type, collect_data ) = GetClientData( self, index )
( collect_type, collect_data ) = self.itemData( index, QC.Qt.UserRole )
p1 = collect_type == 'namespace' and collect_data in media_collect.namespaces
p2 = collect_type == 'rating' and collect_data in media_collect.rating_service_keys

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB