Version 407

closes #325, closes #388, closes #384, closes #369
This commit is contained in:
Hydrus Network Developer 2020-08-05 15:10:36 -05:00
parent 3a296e9bd0
commit 123e076ada
68 changed files with 2037 additions and 1530 deletions

View File

@ -8,6 +8,35 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 407</h3></li>
<ul>
<li>sibling prep:</li>
<li>I am preparing for a new siblings database cache for v408. this will ultimately make siblings (and parents) faster, more accurate, more powerful, and simple to undo. I have decided, as part of it, to make siblings and parents completely virtual (i.e. the tags won't exist for real, they'll be implied). better tools to manage hard-replace siblings and parents will come later, as trying to support both situations at once has not been excellent</li>
<li>.</li>
<li>created options to hold per-service sibling and parent preferences, so you'll be able to set up '"my tags" siblings and then "ptr" siblings apply to "my tags"' or 'no parents apply to this service'</li>
<li>wrote UI for the sibling options under 'services->manage where tag siblings apply'. you can play with it if you like, and it saves values, but it is not plugged in yet and makes no changes</li>
<li>siblings logic is a little tighter. the db and gui side of siblings structure calculation is more unified, petitioned siblings are discounted properly on all generation, and the db side now resolves conflict decisions the same on every regen. the gui-side still runs on an older structure, but will be updated to exactly mirror the db</li>
<li>updated and unified how large numbers of raw tag siblings are fetched in the database. it also supports fast tag slicing, speeding up sibling cache maintenance. the siblings lookup cache now uses this method for regeneration and update calls</li>
<li>.</li>
<li>the rest:</li>
<li>tag right-click menu copying now supports all combinations of selected/all, tags/subtags, and no_count/with_counts where appropriate (issue #325)</li>
<li>if the media viewer is too thin for the top hover window to fit into its space, the top-right hover now drops down below it. I don't really like how this looks, and will probably instead figure out a flow layout so the toolbar buttons always fit, but at least they are now accessible (issue #388)</li>
<li>altered the above fix--if the top-right hover window can be shrunk to fit in the available space, it will now squeeze in, only bumping down if it can't</li>
<li>moving the mouse off an activated (e.g. clicked) hover window now instantly activates the main canvas. this should fix up some fast swallowed clicks and annoying click-to-activate issues with the center-right duplicates hover window, which does not hide (issue #384)</li>
<li>the duplicates hover window now positions correctly if its min size is too wide to fit in a thin media window</li>
<li>if you make changes to a parser or content parser, there is now a yes/no confirmation when trying to cancel the dialog</li>
<li>fixed an issue where 'queue' listboxes with no edit button would throw an error on double-click. now double-click in this case deletes</li>
<li>fixed a couple of timestamp convertions that were doing YYYY/MM/DD instead of the more ISO-nice YYYY-MM-DD. also, when in UTC, they'll correctly say UTC now instead of GMT (issue #369)</li>
<li>fixed some borked centered text layout on ratings dialog and import folder dialog</li>
<li>fixed the manage services dialog's wrong headers for type/name columns</li>
<li>added links in the official help to the new user-written simple help guide at https://github.com/Zweibach/text/blob/master/Hydrus/Hydrus%20Help%20Docs/00_tableOfContents.md</li>
<li>moved object tag and ratings code to a new client module, 'metadata', and pulled various ratings gui code into a new separate file</li>
<li>refactored some more manager code around to generally more sensible locations</li>
<li>did a bit more work chasing down the highlight-downloader ui deadlock, which unfortunately still exists</li>
<li>reduced the number of db hits some paged downloaders need, particularly on highlight and init</li>
<li>updated some test code to support cleverer db testing</li>
<li>updated mpv for windows build. api version is now 1.109. this fixes at least one weird linux vm audio driver issue</li>
</ul>
<li><h3>version 406</h3></li>
<ul>
<li>subscription management:</li>

View File

@ -7,7 +7,7 @@
<body>
<div class="content">
<p><a href="getting_started_installing.html"><---- Back to the installing and updating</a></p>
<p>If any of this is confusing, some users are building video guides for hydrus to help new users <a href="https://github.com/CuddleBear92/Hydrus-guides">here</a>!</p>
<p>If any of this is confusing, a simpler guide is <a href="https://github.com/Zweibach/text/blob/master/Hydrus/Hydrus%20Help%20Docs/00_tableOfContents.md">here</a>, and some video guides are <a href="https://github.com/CuddleBear92/Hydrus-guides">here</a>!</p>
<h3 class="warning">a warning</h3>
<p class="warning">Hydrus can be powerful, and you control everything. By default, you are not connected to any servers and absolutely nothing is shared with other users--and you can't accidentally one-click your way to exposing your whole collection--but if you tag private files with real names and click to upload that data to a tag repository that other people have access to, the program won't try to stop you. If you want to do private sexy slideshows of your shy wife, that's great, but think twice before you upload files or tags anywhere, particularly as you learn. It is <b>impossible</b> to contain leaks of private information.</p>
<p class="warning">There are no limits and few brakes on your behaviour. It is possible to import millions of files. For many new users, their first mistake is downloading too much too fast in overexcitement and becoming overwhelmed. Take things slow and figure out good processing workflows that work for your schedule before you start adding 500 subscriptions.</p>

View File

@ -7,7 +7,7 @@
<body>
<div class="content">
<p><a href="introduction.html"><---- Back to the introduction</a></p>
<p>If any of this is confusing, some users are building video guides for hydrus to help new users <a href="https://github.com/CuddleBear92/Hydrus-guides">here</a>!</p>
<p>If any of this is confusing, a simpler guide is <a href="https://github.com/Zweibach/text/blob/master/Hydrus/Hydrus%20Help%20Docs/00_tableOfContents.md">here</a>, and some video guides are <a href="https://github.com/CuddleBear92/Hydrus-guides">here</a>!</p>
<h3 id="downloading">downloading</h3>
<p>You can get the latest release at <a href="https://github.com/hydrusnetwork/hydrus/releases">my github releases page</a>.</p>
<p>I try to release a new version every Wednesday by 8pm EST and write an accompanying post on <a href="http://hydrus.tumblr.com/">my tumblr</a> and a sticky on <a href="https://8kun.top/hydrus/index.html">my 8kun board</a>.</p>

View File

@ -7,7 +7,7 @@
<body>
<div class="content">
<p><a href="getting_started_files.html"><--- Back to files</a></p>
<p>If any of this is confusing, some users are building video guides for hydrus to help new users <a href="https://github.com/CuddleBear92/Hydrus-guides">here</a>!</p>
<p>If any of this is confusing, a simpler guide is <a href="https://github.com/Zweibach/text/blob/master/Hydrus/Hydrus%20Help%20Docs/00_tableOfContents.md">here</a>, and some video guides are <a href="https://github.com/CuddleBear92/Hydrus-guides">here</a>!</p>
<h3 id="intro">how do we find files?</h3>
<p>So, you have stored some media in your database. Everything is hashed and cached. You can search by inbox and resolution and size and so on, but if you really want to find what we are looking for, you will have to use <i>tags</i>.</p>
<p><a href="faq.html#tags">FAQ: what is a tag?</a></p>

View File

@ -5,7 +5,7 @@ from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusSerialisable
from hydrus.client import ClientTags
from hydrus.client.metadata import ClientTags
CLIENT_API_PERMISSION_ADD_URLS = 0
CLIENT_API_PERMISSION_ADD_FILES = 1

View File

@ -34,7 +34,7 @@ from hydrus.client import ClientFiles
from hydrus.client import ClientManagers
from hydrus.client import ClientOptions
from hydrus.client import ClientSearch
from hydrus.client import ClientTags
from hydrus.client import ClientServices
from hydrus.client import ClientThreading
from hydrus.client.gui import ClientGUI
from hydrus.client.gui import ClientGUIDialogs
@ -45,6 +45,8 @@ from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListManager
from hydrus.client.importing import ClientImportSubscriptions
from hydrus.client.metadata import ClientTags
from hydrus.client.metadata import ClientTagsHandling
from hydrus.client.networking import ClientNetworking
from hydrus.client.networking import ClientNetworkingBandwidth
from hydrus.client.networking import ClientNetworkingDomain
@ -799,7 +801,7 @@ class Controller( HydrusController.HydrusController ):
self.pub( 'splash_set_status_subtext', 'services' )
self.services_manager = ClientManagers.ServicesManager( self )
self.services_manager = ClientServices.ServicesManager( self )
self.pub( 'splash_set_status_subtext', 'options' )
@ -928,11 +930,13 @@ class Controller( HydrusController.HydrusController ):
self.pub( 'splash_set_status_subtext', 'tag display' )
# note that this has to be made before siblings/parents managers, as they rely on it
tag_display_manager = self.Read( 'serialisable', HydrusSerialisable.SERIALISABLE_TYPE_TAG_DISPLAY_MANAGER )
if tag_display_manager is None:
tag_display_manager = ClientTags.TagDisplayManager()
tag_display_manager = ClientTagsHandling.TagDisplayManager()
tag_display_manager._dirty = True
@ -1679,6 +1683,11 @@ class Controller( HydrusController.HydrusController ):
with HG.dirty_object_lock:
previous_services = self.services_manager.GetServices()
previous_service_keys = { service.GetServiceKey() for service in previous_services }
new_tag_service_keys = [ service.GetServiceKey() for service in services if service.GetServiceType() in HC.REAL_TAG_SERVICES and service.GetServiceKey() not in previous_service_keys ]
upnp_services = [ service for service in services if service.GetServiceType() in ( HC.LOCAL_BOORU, HC.CLIENT_API_SERVICE ) ]
self.CallToThread( self.services_upnp_manager.SetServices, upnp_services )
@ -1687,6 +1696,11 @@ class Controller( HydrusController.HydrusController ):
self.services_manager.RefreshServices()
if len( new_tag_service_keys ) > 0:
self.tag_display_manager.AddNewTagServiceKeys( new_tag_service_keys )
self.RestartClientServerServices()

View File

@ -35,12 +35,13 @@ from hydrus.client import ClientFiles
from hydrus.client import ClientOptions
from hydrus.client import ClientSearch
from hydrus.client import ClientServices
from hydrus.client import ClientTags
from hydrus.client import ClientThreading
from hydrus.client.media import ClientMedia
from hydrus.client.media import ClientMediaManagers
from hydrus.client.media import ClientMediaResult
from hydrus.client.media import ClientMediaResultCache
from hydrus.client.metadata import ClientTags
from hydrus.client.metadata import ClientTagsHandling
from hydrus.client.networking import ClientNetworkingBandwidth
from hydrus.client.networking import ClientNetworkingContexts
from hydrus.client.networking import ClientNetworkingDomain
@ -1311,10 +1312,9 @@ class DB( HydrusDB.HydrusDB ):
#
# update this to look up options
if tag_service_id == self._combined_tag_service_id:
# until we have the nice system here, we'll do the old-fashioned local, then remote precedence
search_tag_service_ids = self._GetServiceIds( HC.REAL_TAG_SERVICES )
else:
@ -1322,19 +1322,25 @@ class DB( HydrusDB.HydrusDB ):
search_tag_service_ids = [ tag_service_id ]
tss = ClientTags.TagSiblingsStructure()
tss = ClientTagsHandling.TagSiblingsStructure()
for search_tag_service_id in search_tag_service_ids:
for ( bad_tag_id, good_tag_id ) in self._c.execute( 'SELECT bad_tag_id, good_tag_id FROM tag_siblings WHERE service_id = ? AND status = ?;', ( search_tag_service_id, HC.CONTENT_STATUS_CURRENT ) ):
statuses_to_pair_ids = self._GetTagSiblingsIds( service_id = search_tag_service_id )
petitioned_fast_lookup = set( statuses_to_pair_ids[ HC.CONTENT_STATUS_PETITIONED ] )
for ( bad_tag_id, good_tag_id ) in statuses_to_pair_ids[ HC.CONTENT_STATUS_CURRENT ]:
if ( bad_tag_id, good_tag_id ) in petitioned_fast_lookup:
continue
tss.AddPair( bad_tag_id, good_tag_id )
for search_tag_service_id in search_tag_service_ids:
for ( bad_tag_id, good_tag_id ) in self._c.execute( 'SELECT bad_tag_id, good_tag_id FROM tag_sibling_petitions WHERE service_id = ? AND status = ?;', ( search_tag_service_id, HC.CONTENT_STATUS_PENDING ) ):
for ( bad_tag_id, good_tag_id ) in statuses_to_pair_ids[ HC.CONTENT_STATUS_PENDING ]:
tss.AddPair( bad_tag_id, good_tag_id )
@ -1368,84 +1374,94 @@ class DB( HydrusDB.HydrusDB ):
def _CacheTagSiblingsUpdateChains( self, tag_service_id, tag_ids, regenerate_existing_entry = True ):
cache_tag_siblings_lookup_table_name = GenerateTagSiblingsLookupCacheTableName( tag_service_id )
# the siblings for tag_ids have changed for tag_service_id
# therefore any service that is interested in tag_service_ids's siblings needs to regen the respective chains for these tags
tag_ids = set( tag_ids )
interested_service_ids = [ tag_service_id ] # update this to look up options
tag_ids.update( self._CacheTagSiblingsLookupGetAdditionalSiblings( tag_service_id, tag_ids ) )
if tag_service_id != self._combined_tag_service_id: # and remove this
interested_service_ids.append( self._combined_tag_service_id )
if regenerate_existing_entry:
for interested_service_id in interested_service_ids:
tag_ids_to_do = tag_ids
cache_tag_siblings_lookup_table_name = GenerateTagSiblingsLookupCacheTableName( interested_service_id )
else:
tag_ids = set( tag_ids )
tag_ids_to_do = set()
tag_ids.update( self._CacheTagSiblingsLookupGetAdditionalSiblings( interested_service_id, tag_ids ) )
for tag_id in tag_ids:
if regenerate_existing_entry:
result = self._c.execute( 'SELECT 1 FROM {} WHERE bad_tag_id = ? OR ideal_tag_id = ?;'.format( cache_tag_siblings_lookup_table_name ), ( tag_id, tag_id ) ).fetchone()
tag_ids_to_do = tag_ids
no_entry_yet = result is None
else:
if no_entry_yet:
tag_ids_to_do = set()
for tag_id in tag_ids:
tag_ids_to_do.add( tag_id )
result = self._c.execute( 'SELECT 1 FROM {} WHERE bad_tag_id = ? OR ideal_tag_id = ?;'.format( cache_tag_siblings_lookup_table_name ), ( tag_id, tag_id ) ).fetchone()
no_entry_yet = result is None
if no_entry_yet:
tag_ids_to_do.add( tag_id )
if len( tag_ids_to_do ) == 0:
return
if len( tag_ids_to_do ) == 0:
self._c.executemany( 'DELETE FROM {} WHERE bad_tag_id = ? OR ideal_tag_id = ?;'.format( cache_tag_siblings_lookup_table_name ), ( ( tag_id, tag_id ) for tag_id in tag_ids_to_do ) )
# update this to look up options
if interested_service_id == self._combined_tag_service_id:
return
tag_service_ids_in_precedence_order = self._GetServiceIds( HC.REAL_TAG_SERVICES )
else:
tag_service_ids_in_precedence_order = [ interested_service_id ]
self._c.executemany( 'DELETE FROM {} WHERE bad_tag_id = ? OR ideal_tag_id = ?;'.format( cache_tag_siblings_lookup_table_name ), ( ( tag_id, tag_id ) for tag_id in tag_ids_to_do ) )
if tag_service_id == self._combined_tag_service_id:
tss = ClientTagsHandling.TagSiblingsStructure()
# until we have the nice system here, we'll do the old-fashioned local, then remote precedence
search_tag_service_ids = self._GetServiceIds( HC.REAL_TAG_SERVICES )
else:
search_tag_service_ids = [ tag_service_id ]
tss = ClientTags.TagSiblingsStructure()
for search_tag_service_id in search_tag_service_ids:
for tag_id in tag_ids_to_do:
for search_tag_service_id in tag_service_ids_in_precedence_order:
some_pairs = self._c.execute( 'SELECT bad_tag_id, good_tag_id FROM tag_siblings WHERE service_id = ? AND ( bad_tag_id = ? OR good_tag_id = ? ) AND status = ?;', ( search_tag_service_id, tag_id, tag_id, HC.CONTENT_STATUS_CURRENT ) ).fetchall()
service_key = self._GetService( search_tag_service_id ).GetServiceKey()
for ( bad_tag_id, good_tag_id ) in some_pairs:
with HydrusDB.TemporaryIntegerTable( self._c, tag_ids_to_do, 'tag_id' ) as tag_ids_temp_table_name:
self._AnalyzeTempTable( tag_ids_temp_table_name )
statuses_to_pair_ids = self._GetTagSiblingsIds( service_id = search_tag_service_id, tag_ids_table_name = tag_ids_temp_table_name )
petitioned_fast_lookup = set( statuses_to_pair_ids[ HC.CONTENT_STATUS_PETITIONED ] )
for ( bad_tag_id, good_tag_id ) in statuses_to_pair_ids[ HC.CONTENT_STATUS_CURRENT ]:
if ( bad_tag_id, good_tag_id ) in petitioned_fast_lookup:
continue
tss.AddPair( bad_tag_id, good_tag_id )
for ( bad_tag_id, good_tag_id ) in statuses_to_pair_ids[ HC.CONTENT_STATUS_PENDING ]:
tss.AddPair( bad_tag_id, good_tag_id )
for search_tag_service_id in search_tag_service_ids:
for tag_id in tag_ids_to_do:
some_pairs = self._c.execute( 'SELECT bad_tag_id, good_tag_id FROM tag_sibling_petitions WHERE service_id = ? AND ( bad_tag_id = ? OR good_tag_id = ? ) AND status = ?;', ( search_tag_service_id, tag_id, tag_id, HC.CONTENT_STATUS_PENDING ) ).fetchall()
for ( bad_tag_id, good_tag_id ) in some_pairs:
tss.AddPair( bad_tag_id, good_tag_id )
self._c.executemany( 'INSERT OR IGNORE INTO {} ( bad_tag_id, ideal_tag_id ) VALUES ( ?, ? );'.format( cache_tag_siblings_lookup_table_name ), tss.GetBadTagsToIdealTags().items() )
if tag_service_id != self._combined_tag_service_id:
self._CacheTagSiblingsUpdateChains( self._combined_tag_service_id, tag_ids, regenerate_existing_entry = regenerate_existing_entry )
self._c.executemany( 'INSERT OR IGNORE INTO {} ( bad_tag_id, ideal_tag_id ) VALUES ( ?, ? );'.format( cache_tag_siblings_lookup_table_name ), tss.GetBadTagsToIdealTags().items() )
@ -1898,7 +1914,7 @@ class DB( HydrusDB.HydrusDB ):
self._SetJSONDump( favourite_search_manager )
tag_display_manager = ClientTags.TagDisplayManager()
tag_display_manager = ClientTagsHandling.TagDisplayManager()
self._SetJSONDump( tag_display_manager )
@ -8534,64 +8550,53 @@ class DB( HydrusDB.HydrusDB ):
def _GetTagSiblings( self, service_key = None ):
def _GetTagSiblings( self, service_key ):
def convert_statuses_and_pair_ids_to_statuses_to_pairs( statuses_and_pair_ids ):
service_id = self._GetServiceId( service_key )
statuses_to_pair_ids = self._GetTagSiblingsIds( service_id )
all_tag_ids = set()
for pair_ids in statuses_to_pair_ids.values():
all_tag_ids = set()
for ( status, bad_tag_id, good_tag_id ) in statuses_and_pair_ids:
for ( bad_tag_id, good_tag_id ) in pair_ids:
all_tag_ids.add( bad_tag_id )
all_tag_ids.add( good_tag_id )
self._PopulateTagIdsToTagsCache( all_tag_ids )
statuses_to_pairs = HydrusData.BuildKeyToSetDict( ( ( status, ( self._tag_ids_to_tags_cache[ bad_tag_id ], self._tag_ids_to_tags_cache[ good_tag_id ] ) ) for ( status, bad_tag_id, good_tag_id ) in statuses_and_pair_ids ) )
return statuses_to_pairs
if service_key is None:
service_ids_to_statuses_and_pair_ids = HydrusData.BuildKeyToListDict( ( ( service_id, ( status, bad_tag_id, good_tag_id ) ) for ( service_id, status, bad_tag_id, good_tag_id ) in self._c.execute( 'SELECT service_id, status, bad_tag_id, good_tag_id FROM tag_siblings UNION SELECT service_id, status, bad_tag_id, good_tag_id FROM tag_sibling_petitions;' ) ) )
service_keys_to_statuses_to_pairs = collections.defaultdict( HydrusData.default_dict_set )
for ( service_id, statuses_and_pair_ids ) in list( service_ids_to_statuses_and_pair_ids.items() ):
try:
service = self._GetService( service_id )
except HydrusExceptions.DataMissing:
self._c.execute( 'DELETE FROM tag_siblings WHERE service_id = ?;', ( service_id, ) )
self._c.execute( 'DELETE FROM tag_sibling_petitions WHERE service_id = ?;', ( service_id, ) )
continue
statuses_to_pairs = convert_statuses_and_pair_ids_to_statuses_to_pairs( statuses_and_pair_ids )
service_key = service.GetServiceKey()
service_keys_to_statuses_to_pairs[ service_key ] = statuses_to_pairs
return service_keys_to_statuses_to_pairs
else:
service_id = self._GetServiceId( service_key )
self._PopulateTagIdsToTagsCache( all_tag_ids )
statuses_to_pairs = collections.defaultdict( list )
statuses_to_pairs.update( { status : [ ( self._tag_ids_to_tags_cache[ bad_tag_id ], self._tag_ids_to_tags_cache[ good_tag_id ] ) for ( bad_tag_id, good_tag_id ) in pair_ids ] for ( status, pair_ids ) in statuses_to_pair_ids.items() } )
return statuses_to_pairs
def _GetTagSiblingsIds( self, service_id, tag_ids_table_name = None ):
if tag_ids_table_name is None:
statuses_and_pair_ids = self._c.execute( 'SELECT status, bad_tag_id, good_tag_id FROM tag_siblings WHERE service_id = ? UNION SELECT status, bad_tag_id, good_tag_id FROM tag_sibling_petitions WHERE service_id = ?;', ( service_id, service_id ) ).fetchall()
statuses_to_pairs = convert_statuses_and_pair_ids_to_statuses_to_pairs( statuses_and_pair_ids )
else:
return statuses_to_pairs
first_part = 'SELECT status, bad_tag_id, good_tag_id FROM tag_siblings, {} ON ( bad_tag_id = tag_id OR good_tag_id = tag_id ) WHERE service_id = ?'.format( tag_ids_table_name )
second_part = 'SELECT status, bad_tag_id, good_tag_id FROM tag_sibling_petitions, {} ON ( bad_tag_id = tag_id OR good_tag_id = tag_id ) WHERE service_id = ?'.format( tag_ids_table_name )
statuses_and_pair_ids = self._c.execute( '{} UNION {};'.format( first_part, second_part ), ( service_id, service_id ) ).fetchall()
unsorted_statuses_to_pair_ids = HydrusData.BuildKeyToListDict( ( status, ( bad_tag_id, good_tag_id ) ) for ( status, bad_tag_id, good_tag_id ) in statuses_and_pair_ids )
statuses_to_pair_ids = collections.defaultdict( list )
statuses_to_pair_ids.update( { status : sorted( pair_ids ) for ( status, pair_ids ) in unsorted_statuses_to_pair_ids.items() } )
return statuses_to_pair_ids
def _GetText( self, text_id ):
@ -13592,7 +13597,7 @@ class DB( HydrusDB.HydrusDB ):
if result is not None:
tag_display_manager = ClientTags.TagDisplayManager()
tag_display_manager = ClientTagsHandling.TagDisplayManager()
old_tag_censorship = self._c.execute( 'SELECT service_id, blacklist, tags FROM tag_censorship;' ).fetchall()

View File

@ -7,7 +7,7 @@ from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientTags
from hydrus.client.metadata import ClientTags
class DuplicateActionOptions( HydrusSerialisable.SerialisableBase ):

View File

@ -12,7 +12,7 @@ from hydrus.core import HydrusThreading
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientPaths
from hydrus.client import ClientSearch
from hydrus.client import ClientTags
from hydrus.client.metadata import ClientTags
MAX_PATH_LENGTH = 240 # bit of padding from 255 for .txt neigbouring and other surprises

View File

@ -18,9 +18,9 @@ from hydrus.core import HydrusTags
from hydrus.client import ClientAPI
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientSearch
from hydrus.client import ClientTags
from hydrus.client.importing import ClientImportFileSeeds
from hydrus.client.media import ClientMedia
from hydrus.client.metadata import ClientTags
from hydrus.client.networking import ClientNetworkingContexts
from hydrus.client.networking import ClientNetworkingDomain

View File

@ -1,20 +1,16 @@
import collections
import random
import threading
import traceback
import typing
from qtpy import QtGui as QG
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientSearch
from hydrus.client import ClientServices
# now let's fill out grandparents
def BuildServiceKeysToChildrenToParents( service_keys_to_simple_children_to_parents ):
@ -480,154 +476,6 @@ class FileViewingStatsManager( object ):
self._PubSubRow( hash, row )
class ServicesManager( object ):
def __init__( self, controller ):
self._controller = controller
self._lock = threading.Lock()
self._keys_to_services = {}
self._services_sorted = []
self.RefreshServices()
self._controller.sub( self, 'RefreshServices', 'notify_new_services_data' )
def _GetService( self, service_key: bytes ):
try:
return self._keys_to_services[ service_key ]
except KeyError:
raise HydrusExceptions.DataMissing( 'That service was not found!' )
def _SetServices( self, services: typing.Collection[ ClientServices.Service ] ):
self._keys_to_services = { service.GetServiceKey() : service for service in services }
self._keys_to_services[ CC.TEST_SERVICE_KEY ] = ClientServices.GenerateService( CC.TEST_SERVICE_KEY, HC.TEST_SERVICE, 'test service' )
key = lambda s: s.GetName()
self._services_sorted = sorted( services, key = key )
def Filter( self, service_keys: typing.Iterable[ bytes ], desired_types: typing.Iterable[ int ] ):
with self._lock:
filtered_service_keys = [ service_key for service_key in service_keys if service_key in self._keys_to_services and self._keys_to_services[ service_key ].GetServiceType() in desired_types ]
return filtered_service_keys
def FilterValidServiceKeys( self, service_keys: typing.Iterable[ bytes ] ):
with self._lock:
filtered_service_keys = [ service_key for service_key in service_keys if service_key in self._keys_to_services ]
return filtered_service_keys
def GetName( self, service_key: bytes ):
with self._lock:
service = self._GetService( service_key )
return service.GetName()
def GetService( self, service_key: bytes ):
with self._lock:
return self._GetService( service_key )
def GetServiceType( self, service_key: bytes ):
with self._lock:
return self._GetService( service_key ).GetServiceType()
def GetServiceKeyFromName( self, allowed_types: typing.Collection[ int ], service_name: str ):
with self._lock:
for service in self._services_sorted:
if service.GetServiceType() in allowed_types and service.GetName() == service_name:
return service.GetServiceKey()
raise HydrusExceptions.DataMissing()
def GetServiceKeys( self, desired_types: typing.Collection[ int ] = HC.ALL_SERVICES ):
with self._lock:
filtered_service_keys = [ service_key for ( service_key, service ) in self._keys_to_services.items() if service.GetServiceType() in desired_types ]
return filtered_service_keys
def GetServices( self, desired_types: typing.Collection[ int ] = HC.ALL_SERVICES, randomised: bool = False ):
with self._lock:
services = []
for desired_type in desired_types:
services.extend( [ service for service in self._services_sorted if service.GetServiceType() == desired_type ] )
if randomised:
random.shuffle( services )
return services
def RefreshServices( self ):
with self._lock:
services = self._controller.Read( 'services' )
self._SetServices( services )
def ServiceExists( self, service_key: bytes ):
with self._lock:
return service_key in self._keys_to_services
class TagParentsManager( object ):
def __init__( self, controller ):
@ -830,13 +678,15 @@ class TagSiblingsManager( object ):
tag_repo_pairs = set()
service_keys_to_statuses_to_pairs = self._controller.Read( 'tag_siblings' )
for ( service_key, statuses_to_pairs ) in service_keys_to_statuses_to_pairs.items():
for service in self._controller.services_manager.GetServices( HC.REAL_TAG_SERVICES ):
all_pairs = statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ].union( statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ] )
service_key = service.GetServiceKey()
service = self._controller.services_manager.GetService( service_key )
statuses_to_pairs = self._controller.Read( 'tag_siblings', service_key )
# don't do set here, do the same ordered lookup on lists, use set for petitioned to discount from current
# also, obviously, we'll be moving to TSS, rather than this ad-hoc mess, so we are all on the same page
all_pairs = set( statuses_to_pairs[ HC.CONTENT_STATUS_CURRENT ] ).union( statuses_to_pairs[ HC.CONTENT_STATUS_PENDING ] ).difference( statuses_to_pairs[ HC.CONTENT_UPDATE_PETITION ] )
if service.GetServiceType() == HC.LOCAL_TAG:

View File

@ -273,7 +273,7 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'duplicate_action_options' ] = HydrusSerialisable.SerialisableDictionary()
from hydrus.client import ClientTags
from hydrus.client.metadata import ClientTags
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_BETTER ] = ClientDuplicates.DuplicateActionOptions( [ ( CC.DEFAULT_LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_MOVE, ClientTags.TagFilter() ) ], [], sync_archive = True, sync_urls_action = HC.CONTENT_MERGE_ACTION_COPY )
self._dictionary[ 'duplicate_action_options' ][ HC.DUPLICATE_SAME_QUALITY ] = ClientDuplicates.DuplicateActionOptions( [ ( CC.DEFAULT_LOCAL_TAG_SERVICE_KEY, HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE, ClientTags.TagFilter() ) ], [], sync_archive = True, sync_urls_action = HC.CONTENT_MERGE_ACTION_TWO_WAY_MERGE )

View File

@ -15,7 +15,8 @@ from hydrus.core import HydrusText
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientTags
from hydrus.client.metadata import ClientTags
from hydrus.client.metadata import ClientTagsHandling
PREDICATE_TYPE_TAG = 0
PREDICATE_TYPE_NAMESPACE = 1
@ -2439,7 +2440,7 @@ def SubtagIsEmpty( search_text: str ):
class ParsedAutocompleteText( object ):
def __init__( self, raw_input: str, tag_autocomplete_options: ClientTags.TagAutocompleteOptions, collapse_search_characters: bool ):
def __init__( self, raw_input: str, tag_autocomplete_options: ClientTagsHandling.TagAutocompleteOptions, collapse_search_characters: bool ):
self.raw_input = raw_input
self._tag_autocomplete_options = tag_autocomplete_options

View File

@ -1,6 +1,7 @@
import hashlib
import json
import os
import random
import threading
import time
import traceback
@ -20,12 +21,11 @@ from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientDownloading
from hydrus.client import ClientFiles
from hydrus.client import ClientRatings
from hydrus.client import ClientThreading
from hydrus.client.gui import QtPorting as QP
from hydrus.client.importing import ClientImporting
from hydrus.client.metadata import ClientRatings
from hydrus.client.networking import ClientNetworkingContexts
from hydrus.client.networking import ClientNetworkingJobs
@ -92,13 +92,15 @@ def GenerateDefaultServiceDictionary( service_type ):
dictionary[ 'shape' ] = ClientRatings.CIRCLE
dictionary[ 'colours' ] = []
from hydrus.client.gui import ClientGUIRatings
if service_type == HC.LOCAL_RATING_LIKE:
dictionary[ 'colours' ] = list( ClientRatings.default_like_colours.items() )
dictionary[ 'colours' ] = list( ClientGUIRatings.default_like_colours.items() )
elif service_type == HC.LOCAL_RATING_NUMERICAL:
dictionary[ 'colours' ] = list( ClientRatings.default_numerical_colours.items() )
dictionary[ 'colours' ] = list( ClientGUIRatings.default_numerical_colours.items() )
dictionary[ 'num_stars' ] = 5
dictionary[ 'allow_zero' ]= True
@ -2880,3 +2882,151 @@ class ServiceIPFS( ServiceRemote ):
HG.client_controller.WriteSynchronous( 'content_updates', { self._service_key : content_updates } )
class ServicesManager( object ):
def __init__( self, controller ):
self._controller = controller
self._lock = threading.Lock()
self._keys_to_services = {}
self._services_sorted = []
self.RefreshServices()
self._controller.sub( self, 'RefreshServices', 'notify_new_services_data' )
def _GetService( self, service_key: bytes ):
try:
return self._keys_to_services[ service_key ]
except KeyError:
raise HydrusExceptions.DataMissing( 'That service was not found!' )
def _SetServices( self, services: typing.Collection[ Service ] ):
self._keys_to_services = { service.GetServiceKey() : service for service in services }
self._keys_to_services[ CC.TEST_SERVICE_KEY ] = GenerateService( CC.TEST_SERVICE_KEY, HC.TEST_SERVICE, 'test service' )
key = lambda s: s.GetName()
self._services_sorted = sorted( services, key = key )
def Filter( self, service_keys: typing.Iterable[ bytes ], desired_types: typing.Iterable[ int ] ):
with self._lock:
filtered_service_keys = [ service_key for service_key in service_keys if service_key in self._keys_to_services and self._keys_to_services[ service_key ].GetServiceType() in desired_types ]
return filtered_service_keys
def FilterValidServiceKeys( self, service_keys: typing.Iterable[ bytes ] ):
with self._lock:
filtered_service_keys = [ service_key for service_key in service_keys if service_key in self._keys_to_services ]
return filtered_service_keys
def GetName( self, service_key: bytes ):
with self._lock:
service = self._GetService( service_key )
return service.GetName()
def GetService( self, service_key: bytes ):
with self._lock:
return self._GetService( service_key )
def GetServiceType( self, service_key: bytes ):
with self._lock:
return self._GetService( service_key ).GetServiceType()
def GetServiceKeyFromName( self, allowed_types: typing.Collection[ int ], service_name: str ):
with self._lock:
for service in self._services_sorted:
if service.GetServiceType() in allowed_types and service.GetName() == service_name:
return service.GetServiceKey()
raise HydrusExceptions.DataMissing()
def GetServiceKeys( self, desired_types: typing.Collection[ int ] = HC.ALL_SERVICES ):
with self._lock:
filtered_service_keys = [ service_key for ( service_key, service ) in self._keys_to_services.items() if service.GetServiceType() in desired_types ]
return filtered_service_keys
def GetServices( self, desired_types: typing.Collection[ int ] = HC.ALL_SERVICES, randomised: bool = False ):
with self._lock:
services = []
for desired_type in desired_types:
services.extend( [ service for service in self._services_sorted if service.GetServiceType() == desired_type ] )
if randomised:
random.shuffle( services )
return services
def RefreshServices( self ):
with self._lock:
services = self._controller.Read( 'services' )
self._SetServices( services )
def ServiceExists( self, service_key: bytes ):
with self._lock:
return service_key in self._keys_to_services

View File

@ -7,8 +7,6 @@ from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusImageHandling
from hydrus.client import ClientImageHandling
if cv2.__version__.startswith( '2' ):
CAP_PROP_FRAME_COUNT = cv2.cv.CV_CAP_PROP_FRAME_COUNT

View File

@ -39,7 +39,6 @@ from hydrus.client import ClientParsing
from hydrus.client import ClientPaths
from hydrus.client import ClientRendering
from hydrus.client import ClientServices
from hydrus.client import ClientTags
from hydrus.client import ClientThreading
from hydrus.client.gui import ClientGUIAsync
from hydrus.client.gui import ClientGUICommon
@ -77,6 +76,7 @@ from hydrus.client.gui import ClientGUITopLevelWindows
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QtPorting as QP
from hydrus.client.media import ClientMediaResult
from hydrus.client.metadata import ClientTags
from hydrus.client.networking import ClientNetworkingContexts
MENU_ORDER = [ 'file', 'undo', 'pages', 'database', 'pending', 'network', 'services', 'help' ]
@ -1669,7 +1669,7 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
ip = response[ 'ip' ]
timestamp = response[ 'timestamp' ]
gmt_time = HydrusData.ConvertTimestampToPrettyTime( timestamp, in_gmt = True )
gmt_time = HydrusData.ConvertTimestampToPrettyTime( timestamp, in_utc = True )
local_time = HydrusData.ConvertTimestampToPrettyTime( timestamp )
text = 'File Hash: ' + hash.hex()
@ -2824,7 +2824,7 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
def _ManageTagDisplay( self ):
title = 'manage tag display'
title = 'manage tag display and search'
with ClientGUITopLevelWindowsPanels.DialogEdit( self, title ) as dlg:
@ -2869,6 +2869,33 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
def _ManageTagSiblingsApplication( self ):
title = 'manage where tag siblings apply'
with ClientGUITopLevelWindowsPanels.DialogEdit( self, title ) as dlg:
panel = ClientGUITags.EditTagSiblingsApplication( dlg, self._controller.tag_display_manager )
dlg.SetPanel( panel )
if dlg.exec() == QW.QDialog.Accepted:
tag_display_manager = panel.GetValue()
tag_display_manager.SetDirty()
self._controller.tag_display_manager = tag_display_manager
# have to actually recompute siblings here
# based on actual changes
# this needs db work as well as gui regen
self._controller.pub( 'notify_new_tag_display_rules' )
def _ManageURLClasses( self ):
title = 'manage url classes'
@ -4722,7 +4749,9 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
ClientGUIMenus.AppendSeparator( menu )
ClientGUIMenus.AppendMenuItem( menu, 'manage tag display and search', 'Set which tags you want to see from which services.', self._ManageTagDisplay )
ClientGUIMenus.AppendMenuItem( menu, 'manage where tag siblings apply (does not do anything yet!)', 'Set certain tags to be automatically replaced with other tags.', self._ManageTagSiblingsApplication )
ClientGUIMenus.AppendMenuItem( menu, 'manage tag siblings', 'Set certain tags to be automatically replaced with other tags.', self._ManageTagSiblings )
#ClientGUIMenus.AppendMenuItem( menu, 'manage where tag parents apply', 'Set certain tags to be automatically replaced with other tags.', self._ManageTagParentsApplication )
ClientGUIMenus.AppendMenuItem( menu, 'manage tag parents', 'Set certain tags to be automatically added with other tags.', self._ManageTagParents )
return ( menu, '&services' )

View File

@ -16,7 +16,6 @@ from hydrus.core import HydrusText
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientSearch
from hydrus.client import ClientTags
from hydrus.client import ClientThreading
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUICore as CGC
@ -28,6 +27,7 @@ from hydrus.client.gui import ClientGUISearch
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListBoxes
from hydrus.client.metadata import ClientTags
from hydrus.external import LogicExpressionQueryParser

View File

@ -17,8 +17,6 @@ from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientDuplicates
from hydrus.client import ClientPaths
from hydrus.client import ClientRatings
from hydrus.client import ClientTags
from hydrus.client.gui import ClientGUICanvasMedia
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUICore as CGC
@ -31,7 +29,7 @@ from hydrus.client.gui import ClientGUIMedia
from hydrus.client.gui import ClientGUIMediaActions
from hydrus.client.gui import ClientGUIMediaControls
from hydrus.client.gui import ClientGUIMenus
from hydrus.client.gui import ClientGUIScrolledPanels
from hydrus.client.gui import ClientGUIRatings
from hydrus.client.gui import ClientGUIScrolledPanelsEdit
from hydrus.client.gui import ClientGUIScrolledPanelsManagement
from hydrus.client.gui import ClientGUIShortcuts
@ -39,6 +37,8 @@ from hydrus.client.gui import ClientGUITags
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QtPorting as QP
from hydrus.client.media import ClientMedia
from hydrus.client.metadata import ClientRatings
from hydrus.client.metadata import ClientTags
ZOOM_CENTERPOINT_MEDIA_CENTER = 0
ZOOM_CENTERPOINT_VIEWER_CENTER = 1
@ -2059,7 +2059,7 @@ class CanvasWithDetails( Canvas ):
rating_state = ClientRatings.GetLikeStateFromMedia( ( self._current_media, ), service_key )
ClientRatings.DrawLike( painter, like_rating_current_x, current_y, service_key, rating_state )
ClientGUIRatings.DrawLike( painter, like_rating_current_x, current_y, service_key, rating_state )
like_rating_current_x -= 16
@ -2077,9 +2077,9 @@ class CanvasWithDetails( Canvas ):
( rating_state, rating ) = ClientRatings.GetNumericalStateFromMedia( ( self._current_media, ), service_key )
numerical_width = ClientRatings.GetNumericalWidth( service_key )
numerical_width = ClientGUIRatings.GetNumericalWidth( service_key )
ClientRatings.DrawNumerical( painter, my_width - numerical_width - 2, current_y, service_key, rating_state, rating ) # -2 to line up exactly with the floating panel
ClientGUIRatings.DrawNumerical( painter, my_width - numerical_width - 2, current_y, service_key, rating_state, rating ) # -2 to line up exactly with the floating panel
current_y += 18
@ -2219,13 +2219,15 @@ class CanvasWithHovers( CanvasWithDetails ):
CanvasWithDetails.__init__( self, parent )
self._GenerateHoverTopFrame()
top_hover = self._GenerateHoverTopFrame()
hover = ClientGUICanvasHoverFrames.CanvasHoverFrameTags( self, self, self._canvas_key )
self._my_shortcuts_handler.AddWindowToFilter( top_hover )
hover = ClientGUICanvasHoverFrames.CanvasHoverFrameTags( self, self, top_hover, self._canvas_key )
self._my_shortcuts_handler.AddWindowToFilter( hover )
hover = ClientGUICanvasHoverFrames.CanvasHoverFrameTopRight( self, self, self._canvas_key )
hover = ClientGUICanvasHoverFrames.CanvasHoverFrameTopRight( self, self, top_hover, self._canvas_key )
self._my_shortcuts_handler.AddWindowToFilter( hover )
@ -2340,6 +2342,17 @@ class CanvasWithHovers( CanvasWithDetails ):
def mouseMoveEvent( self, event ):
current_focus_tlw = QW.QApplication.activeWindow()
my_tlw = self.window()
if current_focus_tlw != my_tlw and ClientGUIFunctions.IsQtAncestor( current_focus_tlw, my_tlw, through_tlws = True ):
my_tlw.activateWindow()
#
CC.CAN_HIDE_MOUSE = True
# due to the mouse setPos below, the event pos can get funky I think due to out of order coordinate setting events, so we'll poll current value directly
@ -2710,9 +2723,7 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
def _GenerateHoverTopFrame( self ):
hover = ClientGUICanvasHoverFrames.CanvasHoverFrameTopDuplicatesFilter( self, self, self._canvas_key )
self._my_shortcuts_handler.AddWindowToFilter( hover )
return ClientGUICanvasHoverFrames.CanvasHoverFrameTopDuplicatesFilter( self, self, self._canvas_key )
def _GetBackgroundColour( self ):
@ -3228,7 +3239,7 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
HG.client_controller.CallLaterQtSafe(self, 0.1, catch_up)
HG.client_controller.CallLaterQtSafe( self, 0.1, catch_up )
def SetMedia( self, media ):
@ -3439,7 +3450,7 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithHovers ):
if not image_cache.HasImageRenderer( hash ):
HG.client_controller.CallLaterQtSafe(self, delay, image_cache.GetImageRenderer, media)
HG.client_controller.CallLaterQtSafe( self, delay, image_cache.GetImageRenderer, media )
@ -3693,9 +3704,7 @@ class CanvasMediaListFilterArchiveDelete( CanvasMediaList ):
def _GenerateHoverTopFrame( self ):
hover = ClientGUICanvasHoverFrames.CanvasHoverFrameTopArchiveDeleteFilter( self, self, self._canvas_key )
self._my_shortcuts_handler.AddWindowToFilter( hover )
return ClientGUICanvasHoverFrames.CanvasHoverFrameTopArchiveDeleteFilter( self, self, self._canvas_key )
def _Keep( self ):
@ -3833,9 +3842,7 @@ class CanvasMediaListNavigable( CanvasMediaList ):
def _GenerateHoverTopFrame( self ):
hover = ClientGUICanvasHoverFrames.CanvasHoverFrameTopNavigableList( self, self, self._canvas_key )
self._my_shortcuts_handler.AddWindowToFilter( hover )
return ClientGUICanvasHoverFrames.CanvasHoverFrameTopNavigableList( self, self, self._canvas_key )
def Archive( self, canvas_key ):

View File

@ -6,7 +6,6 @@ from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusGlobals as HG
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
from hydrus.client.gui import ClientGUICanvas
from hydrus.client.gui import ClientGUIMediaControls
from hydrus.client.gui import ClientGUIShortcuts

View File

@ -12,7 +12,6 @@ from hydrus.core import HydrusSerialisable
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientRatings
from hydrus.client.gui import ClientGUIDragDrop
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUICore as CGC
@ -20,6 +19,7 @@ from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import ClientGUIMediaControls
from hydrus.client.gui import ClientGUIMenus
from hydrus.client.gui import ClientGUIMPV
from hydrus.client.gui import ClientGUIRatings
from hydrus.client.gui import ClientGUIScrolledPanelsEdit
from hydrus.client.gui import ClientGUIShortcuts
from hydrus.client.gui import ClientGUIShortcutControls
@ -28,12 +28,13 @@ from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListBoxes
from hydrus.client.media import ClientMedia
from hydrus.client.metadata import ClientRatings
class RatingLikeCanvas( ClientGUICommon.RatingLike ):
class RatingLikeCanvas( ClientGUIRatings.RatingLike ):
def __init__( self, parent, service_key, canvas_key ):
ClientGUICommon.RatingLike.__init__( self, parent, service_key )
ClientGUIRatings.RatingLike.__init__( self, parent, service_key )
self._canvas_key = canvas_key
self._current_media = None
@ -59,7 +60,7 @@ class RatingLikeCanvas( ClientGUICommon.RatingLike ):
self._rating_state = ClientRatings.GetLikeStateFromMedia( ( self._current_media, ), self._service_key )
ClientRatings.DrawLike( painter, 0, 0, self._service_key, self._rating_state )
ClientGUIRatings.DrawLike( painter, 0, 0, self._service_key, self._rating_state )
self._dirty = False
@ -140,11 +141,11 @@ class RatingLikeCanvas( ClientGUICommon.RatingLike ):
class RatingNumericalCanvas( ClientGUICommon.RatingNumerical ):
class RatingNumericalCanvas( ClientGUIRatings.RatingNumerical ):
def __init__( self, parent, service_key, canvas_key ):
ClientGUICommon.RatingNumerical.__init__( self, parent, service_key )
ClientGUIRatings.RatingNumerical.__init__( self, parent, service_key )
self._canvas_key = canvas_key
self._current_media = None
@ -163,7 +164,7 @@ class RatingNumericalCanvas( ClientGUICommon.RatingNumerical ):
def _ClearRating( self ):
ClientGUICommon.RatingNumerical._ClearRating( self )
ClientGUIRatings.RatingNumerical._ClearRating( self )
if self._current_media is not None:
@ -185,7 +186,7 @@ class RatingNumericalCanvas( ClientGUICommon.RatingNumerical ):
( self._rating_state, self._rating ) = ClientRatings.GetNumericalStateFromMedia( ( self._current_media, ), self._service_key )
ClientRatings.DrawNumerical( painter, 0, 0, self._service_key, self._rating_state, self._rating )
ClientGUIRatings.DrawNumerical( painter, 0, 0, self._service_key, self._rating_state, self._rating )
self._dirty = False
@ -193,7 +194,7 @@ class RatingNumericalCanvas( ClientGUICommon.RatingNumerical ):
def _SetRating( self, rating ):
ClientGUICommon.RatingNumerical._SetRating( self, rating )
ClientGUIRatings.RatingNumerical._SetRating( self, rating )
if self._current_media is not None and rating is not None:
@ -688,7 +689,7 @@ class CanvasHoverFrameRightDuplicates( CanvasHoverFrame ):
my_width = my_size.width()
my_height = my_size.height()
my_ideal_width = int( parent_width * 0.2 )
my_ideal_width = max( int( parent_width * 0.2 ), self.sizeHint().width() )
my_ideal_height = self.sizeHint().height()
should_resize = my_ideal_width != my_width or my_ideal_height != my_height
@ -1233,10 +1234,12 @@ class CanvasHoverFrameTopNavigableList( CanvasHoverFrameTopNavigable ):
class CanvasHoverFrameTopRight( CanvasHoverFrame ):
def __init__( self, parent, my_canvas, canvas_key ):
def __init__( self, parent, my_canvas, top_hover: CanvasHoverFrameTop, canvas_key ):
CanvasHoverFrame.__init__( self, parent, my_canvas, canvas_key )
self._top_hover = top_hover
vbox = QP.VBoxLayout()
self._icon_panel = QW.QWidget( self )
@ -1343,15 +1346,25 @@ class CanvasHoverFrameTopRight( CanvasHoverFrame ):
my_width = my_size.width()
my_height = my_size.height()
my_ideal_width = int( parent_width * 0.2 )
width_beside_top_hover = ClientGUIFunctions.ClientToScreen( parent_window, parent_window.rect().topRight() ).x() - ClientGUIFunctions.ClientToScreen( self._top_hover, self._top_hover.rect().bottomRight() ).x()
my_ideal_width = max( self.sizeHint().width(), width_beside_top_hover )
my_ideal_height = self.sizeHint().height()
should_resize = my_ideal_width != my_width or my_ideal_height != my_height
ideal_size = QC.QSize( my_ideal_width, my_ideal_height )
ideal_position = ClientGUIFunctions.ClientToScreen( parent_window, QC.QPoint( int( parent_width - my_ideal_width ), 0 ) )
top_hover_bottom_right = ClientGUIFunctions.ClientToScreen( self._top_hover, self._top_hover.rect().bottomRight() )
if top_hover_bottom_right.x() > ideal_position.x():
ideal_position.setY( top_hover_bottom_right.y() )
return ( should_resize, ideal_size, ideal_position )
@ -1489,10 +1502,12 @@ class CanvasHoverFrameTopRight( CanvasHoverFrame ):
class CanvasHoverFrameTags( CanvasHoverFrame ):
def __init__( self, parent, my_canvas, canvas_key ):
def __init__( self, parent, my_canvas, top_hover: CanvasHoverFrameTop, canvas_key ):
CanvasHoverFrame.__init__( self, parent, my_canvas, canvas_key )
self._top_hover = top_hover
vbox = QP.VBoxLayout()
self._tags = ClientGUIListBoxes.ListBoxTagsMediaHoverFrame( self, self._canvas_key )

View File

@ -11,7 +11,6 @@ from hydrus.core import HydrusPaths
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientRendering
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUIFunctions

View File

@ -14,13 +14,11 @@ from hydrus.core import HydrusGlobals as HG
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientPaths
from hydrus.client import ClientRatings
from hydrus.client.gui import ClientGUICore as CGC
from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import ClientGUIMenus
from hydrus.client.gui import ClientGUIShortcuts
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui import QtPorting as QP
CANVAS_MEDIA_VIEWER = 0
CANVAS_PREVIEW = 1
@ -42,7 +40,7 @@ def WrapInGrid( parent, rows, expand_text = False, add_stretch_at_end = True ):
gridbox.setColumnStretch( 0, 1 )
text_flags = CC.FLAGS_CENTER_PERPENDICULAR_EXPAND_DEPTH # Trying to expand both ways nixes the center. This seems to work right.
text_flags = CC.FLAGS_EXPAND_BOTH_WAYS
control_flags = CC.FLAGS_CENTER_PERPENDICULAR
sizer_flags = CC.FLAGS_CENTER_PERPENDICULAR
@ -1610,309 +1608,6 @@ class OnOffButton( QW.QPushButton ):
self._SetValue( value )
class RatingLike( QW.QWidget ):
def __init__( self, parent, service_key ):
QW.QWidget.__init__( self, parent )
self._service_key = service_key
self._widget_event_filter = QP.WidgetEventFilter( self )
self._widget_event_filter.EVT_LEFT_DOWN( self.EventLeftDown )
self._widget_event_filter.EVT_LEFT_DCLICK( self.EventLeftDown )
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventRightDown )
self._widget_event_filter.EVT_RIGHT_DCLICK( self.EventRightDown )
self.setMinimumSize( QC.QSize( 16, 16 ) )
self._dirty = True
def _Draw( self, painter ):
raise NotImplementedError()
def EventLeftDown( self, event ):
raise NotImplementedError()
def paintEvent( self, event ):
painter = QG.QPainter( self )
self._Draw( painter )
def EventRightDown( self, event ):
raise NotImplementedError()
def GetServiceKey( self ):
return self._service_key
class RatingLikeDialog( RatingLike ):
def __init__( self, parent, service_key ):
RatingLike.__init__( self, parent, service_key )
self._rating_state = ClientRatings.NULL
def _Draw( self, painter ):
painter.setBackground( QG.QBrush( QP.GetBackgroundColour( self.parentWidget() ) ) )
painter.eraseRect( painter.viewport() )
( pen_colour, brush_colour ) = ClientRatings.GetPenAndBrushColours( self._service_key, self._rating_state )
ClientRatings.DrawLike( painter, 0, 0, self._service_key, self._rating_state )
self._dirty = False
def EventLeftDown( self, event ):
if self._rating_state == ClientRatings.LIKE: self._rating_state = ClientRatings.NULL
else: self._rating_state = ClientRatings.LIKE
self._dirty = True
self.update()
def EventRightDown( self, event ):
if self._rating_state == ClientRatings.DISLIKE: self._rating_state = ClientRatings.NULL
else: self._rating_state = ClientRatings.DISLIKE
self._dirty = True
self.update()
def GetRatingState( self ):
return self._rating_state
def SetRatingState( self, rating_state ):
self._rating_state = rating_state
self._dirty = True
self.update()
class RatingNumerical( QW.QWidget ):
def __init__( self, parent, service_key ):
QW.QWidget.__init__( self, parent )
self._service_key = service_key
self._service = HG.client_controller.services_manager.GetService( self._service_key )
self._num_stars = self._service.GetNumStars()
self._allow_zero = self._service.AllowZero()
my_width = ClientRatings.GetNumericalWidth( self._service_key )
self._widget_event_filter = QP.WidgetEventFilter( self )
self._widget_event_filter.EVT_LEFT_DOWN( self.EventLeftDown )
self._widget_event_filter.EVT_LEFT_DCLICK( self.EventLeftDown )
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventRightDown )
self._widget_event_filter.EVT_RIGHT_DCLICK( self.EventRightDown )
self.setMinimumSize( QC.QSize( my_width, 16 ) )
self._last_rating_set = None
self._dirty = True
def _ClearRating( self ):
self._last_rating_set = None
def _Draw( self, painter ):
raise NotImplementedError()
def _GetRatingFromClickEvent( self, event ):
click_pos = event.pos()
x = event.pos().x()
y = event.pos().y()
BORDER = 1
my_active_size = self.size() - QC.QSize( BORDER * 2, BORDER * 2 )
adjusted_click_pos = click_pos - QC.QPoint( BORDER, BORDER )
my_active_rect = QC.QRect( QC.QPoint( 0, 0 ), my_active_size )
if my_active_rect.contains( adjusted_click_pos ):
x_adjusted = x - BORDER
proportion_filled = x_adjusted / my_active_size.width()
if self._allow_zero:
stars = round( proportion_filled * self._num_stars )
else:
stars = int( proportion_filled * self._num_stars )
if proportion_filled <= 1.0:
stars += 1
rating = self._service.ConvertStarsToRating( stars )
return rating
return None
def _SetRating( self, rating ):
self._last_rating_set = rating
def EventLeftDown( self, event ):
rating = self._GetRatingFromClickEvent( event )
self._SetRating( rating )
def EventRightDown( self, event ):
self._ClearRating()
def GetServiceKey( self ):
return self._service_key
def mouseMoveEvent( self, event ):
if event.buttons() & QC.Qt.LeftButton:
rating = self._GetRatingFromClickEvent( event )
if rating != self._last_rating_set:
self._SetRating( rating )
def paintEvent( self, event ):
painter = QG.QPainter( self )
self._Draw( painter )
class RatingNumericalDialog( RatingNumerical ):
def __init__( self, parent, service_key ):
RatingNumerical.__init__( self, parent, service_key )
self._rating_state = ClientRatings.NULL
self._rating = None
def _ClearRating( self ):
RatingNumerical._ClearRating( self )
self._rating_state = ClientRatings.NULL
self._dirty = True
self.update()
def _Draw( self, painter ):
painter.setBackground( QG.QBrush( QP.GetBackgroundColour( self.parentWidget() ) ) )
painter.eraseRect( painter.viewport() )
ClientRatings.DrawNumerical( painter, 0, 0, self._service_key, self._rating_state, self._rating )
self._dirty = False
def _SetRating( self, rating ):
RatingNumerical._SetRating( self, rating )
if rating is None:
self._ClearRating()
else:
self._rating_state = ClientRatings.SET
self._rating = rating
self._dirty = True
self.update()
def GetRating( self ):
return self._rating
def GetRatingState( self ):
return self._rating_state
def SetRating( self, rating ):
self._SetRating( rating )
def SetRatingState( self, rating_state ):
self._rating_state = rating_state
self._dirty = True
self.update()
class RegexButton( BetterButton ):
def __init__( self, parent ):

View File

@ -11,13 +11,14 @@ from hydrus.core import HydrusNATPunch
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientRatings
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUIDialogs
from hydrus.client.gui import ClientGUIRatings
from hydrus.client.gui import ClientGUIShortcuts
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
from hydrus.client.gui.lists import ClientGUIListCtrl
from hydrus.client.metadata import ClientRatings
# Option Enums
@ -160,7 +161,7 @@ class DialogManageRatings( ClientGUIDialogs.Dialog ):
rating_state = ClientRatings.GetLikeStateFromMedia( self._media, service_key )
control = ClientGUICommon.RatingLikeDialog( self, service_key )
control = ClientGUIRatings.RatingLikeDialog( self, service_key )
control.SetRatingState( rating_state )
@ -226,7 +227,7 @@ class DialogManageRatings( ClientGUIDialogs.Dialog ):
( rating_state, rating ) = ClientRatings.GetNumericalStateFromMedia( self._media, service_key )
control = ClientGUICommon.RatingNumericalDialog( self, service_key )
control = ClientGUIRatings.RatingNumericalDialog( self, service_key )
if rating_state != ClientRatings.SET:

View File

@ -4,7 +4,6 @@ from qtpy import QtCore as QC
from qtpy import QtGui as QG
from qtpy import QtWidgets as QW
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusPaths

View File

@ -14,7 +14,6 @@ from hydrus.core import HydrusPaths
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientExporting
from hydrus.client import ClientSearch
from hydrus.client import ClientTags
from hydrus.client.gui import ClientGUIACDropdown
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUIDialogsQuick
@ -25,6 +24,7 @@ from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListBoxes
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
from hydrus.client.gui.lists import ClientGUIListCtrl
from hydrus.client.metadata import ClientTags
class EditExportFoldersPanel( ClientGUIScrolledPanels.EditPanel ):

View File

@ -4,19 +4,13 @@ from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusData
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.client import ClientTags
from hydrus.client.gui import QtPorting as QP
def ClientToScreen( win: QW.QWidget, pos: QC.QPoint ) -> QC.QPoint:
tlw = win.window()
if win.isVisible() and tlw.isVisible():
if ( win.isVisible() and tlw.isVisible() ) or True:
return win.mapToGlobal( pos )

View File

@ -16,7 +16,6 @@ from hydrus.core import HydrusText
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientTags
from hydrus.client.gui import ClientGUIACDropdown
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUIControls
@ -39,6 +38,7 @@ from hydrus.client.gui.lists import ClientGUIListCtrl
from hydrus.client.importing import ClientImporting
from hydrus.client.importing import ClientImportLocal
from hydrus.client.importing import ClientImportOptions
from hydrus.client.metadata import ClientTags
class CheckerOptionsButton( ClientGUICommon.BetterButton ):

View File

@ -12,7 +12,6 @@ from hydrus.core import HydrusImageHandling
from hydrus.core import HydrusPaths
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUIMedia
from hydrus.client.gui import ClientGUIMediaControls

View File

@ -20,7 +20,6 @@ from hydrus.client import ClientDefaults
from hydrus.client import ClientParsing
from hydrus.client import ClientPaths
from hydrus.client import ClientSearch
from hydrus.client import ClientTags
from hydrus.client import ClientThreading
from hydrus.client.gui import ClientGUIACDropdown
from hydrus.client.gui import ClientGUICanvas
@ -51,6 +50,7 @@ from hydrus.client.importing import ClientImportOptions
from hydrus.client.importing import ClientImportSimpleURLs
from hydrus.client.importing import ClientImportWatchers
from hydrus.client.media import ClientMedia
from hydrus.client.metadata import ClientTags
MANAGEMENT_TYPE_DUMPER = 0
MANAGEMENT_TYPE_IMPORT_MULTIPLE_GALLERY = 1
@ -1990,11 +1990,21 @@ class ManagementPanelImporterMultipleGallery( ManagementPanelImporter ):
self._multiple_gallery_import.SetHighlightedGalleryImport( self._highlighted_gallery_import )
self._highlighted_gallery_import.PublishToPage( True )
hashes = self._highlighted_gallery_import.GetPresentedHashes()
hashes = HG.client_controller.Read( 'filter_hashes', hashes, CC.LOCAL_FILE_SERVICE_KEY )
media_results = HG.client_controller.Read( 'media_results', hashes )
if len( hashes ) > 0:
hashes = HG.client_controller.Read( 'filter_hashes', hashes, CC.LOCAL_FILE_SERVICE_KEY )
media_results = HG.client_controller.Read( 'media_results', hashes )
else:
hashes = []
media_results = []
hashes_to_media_results = { media_result.GetHash() : media_result for media_result in media_results }
@ -2273,7 +2283,7 @@ class ManagementPanelImporterMultipleGallery( ManagementPanelImporter ):
else:
QW.QMessageBox.warning( self, 'Warning', 'No presented hashes for that selection!' )
QW.QMessageBox.warning( self, 'Warning', 'No presented files for that selection!' )
@ -2776,9 +2786,17 @@ class ManagementPanelImporterMultipleWatcher( ManagementPanelImporter ):
hashes = self._highlighted_watcher.GetPresentedHashes()
hashes = HG.client_controller.Read( 'filter_hashes', hashes, CC.LOCAL_FILE_SERVICE_KEY )
media_results = HG.client_controller.Read( 'media_results', hashes )
if len( hashes ) > 0:
hashes = HG.client_controller.Read( 'filter_hashes', hashes, CC.LOCAL_FILE_SERVICE_KEY )
media_results = HG.client_controller.Read( 'media_results', hashes )
else:
hashes = []
media_results = []
hashes_to_media_results = { media_result.GetHash() : media_result for media_result in media_results }
@ -2792,6 +2810,8 @@ class ManagementPanelImporterMultipleWatcher( ManagementPanelImporter ):
self._multiple_watcher_import.SetHighlightedWatcher( self._highlighted_watcher )
self._highlighted_watcher.PublishToPage( True )
self._watchers_listctrl_panel.UpdateButtons()
self._watchers_listctrl.UpdateDatas()
@ -3055,7 +3075,7 @@ class ManagementPanelImporterMultipleWatcher( ManagementPanelImporter ):
else:
QW.QMessageBox.warning( self, 'Warning', 'No presented hashes for that selection!' )
QW.QMessageBox.warning( self, 'Warning', 'No presented files for that selection!' )

View File

@ -10,10 +10,10 @@ from hydrus.core import HydrusGlobals as HG
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientTags
from hydrus.client.media import ClientMedia
from hydrus.client.gui import ClientGUIScrolledPanelsEdit
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.media import ClientMedia
from hydrus.client.metadata import ClientTags
def ApplyContentApplicationCommandToMedia( parent: QW.QWidget, command: CAC.ApplicationCommand, media: typing.Collection[ ClientMedia.Media ] ):

View File

@ -1,4 +1,3 @@
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG

View File

@ -10,10 +10,6 @@ from hydrus.client.gui import QtPorting as QP
class OptionsPanel( QW.QWidget ):
def GetOptions( self ): raise NotImplementedError()
def SetOptions( self, options ): raise NotImplementedError()
def GetValue( self ): raise NotImplementedError()
def SetValue( self, info ): raise NotImplementedError()

View File

@ -1758,6 +1758,8 @@ class EditContentParserPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent: QW.QWidget, content_parser: ClientParsing.ContentParser, test_data: ClientParsing.ParsingTestData, permitted_content_types ):
self._original_content_parser = content_parser
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
#
@ -2173,6 +2175,22 @@ class EditContentParserPanel( ClientGUIScrolledPanels.EditPanel ):
return content_parser
def UserIsOKToCancel( self ):
if self._original_content_parser.GetSerialisableTuple() != self.GetValue().GetSerialisableTuple():
text = 'It looks like you have made changes to the content parser--are you sure you want to cancel?'
result = ClientGUIDialogsQuick.GetYesNo( self, text )
return result == QW.QDialog.Accepted
else:
return True
class EditContentParsersPanel( ClientGUICommon.StaticBox ):
def __init__( self, parent: QW.QWidget, test_data_callable: typing.Callable[ [], ClientParsing.ParsingTestData ], permitted_content_types ):
@ -3238,6 +3256,24 @@ class EditPageParserPanel( ClientGUIScrolledPanels.EditPanel ):
return parser
def UserIsOKToCancel( self ):
if self._original_parser.GetSerialisableTuple() != self.GetValue().GetSerialisableTuple():
text = 'It looks like you have made changes to the parser--are you sure you want to cancel?'
text += os.linesep * 2
text += 'If this is a subsidiary page parser and you think you have made no changes, it might just be example test data, auto-populated from the parent, that changed.'
result = ClientGUIDialogsQuick.GetYesNo( self, text )
return result == QW.QDialog.Accepted
else:
return True
class EditParsersPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, parsers ):

View File

@ -0,0 +1,481 @@
from qtpy import QtCore as QC
from qtpy import QtGui as QG
from qtpy import QtWidgets as QW
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.client.gui import QtPorting as QP
from hydrus.client.metadata import ClientRatings
default_like_colours = {}
default_like_colours[ ClientRatings.LIKE ] = ( ( 0, 0, 0 ), ( 80, 200, 120 ) )
default_like_colours[ ClientRatings.DISLIKE ] = ( ( 0, 0, 0 ), ( 200, 80, 120 ) )
default_like_colours[ ClientRatings.NULL ] = ( ( 0, 0, 0 ), ( 191, 191, 191 ) )
default_like_colours[ ClientRatings.MIXED ] = ( ( 0, 0, 0 ), ( 95, 95, 95 ) )
default_numerical_colours = {}
default_numerical_colours[ ClientRatings.LIKE ] = ( ( 0, 0, 0 ), ( 80, 200, 120 ) )
default_numerical_colours[ ClientRatings.DISLIKE ] = ( ( 0, 0, 0 ), ( 255, 255, 255 ) )
default_numerical_colours[ ClientRatings.NULL ] = ( ( 0, 0, 0 ), ( 191, 191, 191 ) )
default_numerical_colours[ ClientRatings.MIXED ] = ( ( 0, 0, 0 ), ( 95, 95, 95 ) )
STAR_COORDS = []
STAR_COORDS.append( QC.QPoint( 6, 0 ) ) # top
STAR_COORDS.append( QC.QPoint( 9, 4 ) )
STAR_COORDS.append( QC.QPoint( 12, 4 ) ) # right
STAR_COORDS.append( QC.QPoint( 9, 8 ) )
STAR_COORDS.append( QC.QPoint( 10, 12 ) ) # bottom right
STAR_COORDS.append( QC.QPoint( 6, 10 ) )
STAR_COORDS.append( QC.QPoint( 2, 12 ) ) # bottom left
STAR_COORDS.append( QC.QPoint( 3, 8 ) )
STAR_COORDS.append( QC.QPoint( 0, 4 ) ) # left
STAR_COORDS.append( QC.QPoint( 3, 4 ) )
def DrawLike( painter, x, y, service_key, rating_state ):
shape = ClientRatings.GetShape( service_key )
( pen_colour, brush_colour ) = GetPenAndBrushColours( service_key, rating_state )
painter.setPen( QG.QPen( pen_colour ) )
painter.setBrush( QG.QBrush( brush_colour ) )
if shape == ClientRatings.CIRCLE:
painter.drawEllipse( QC.QPointF( x+7, y+7 ), 6, 6 )
elif shape == ClientRatings.SQUARE:
painter.drawRect( x+2, y+2, 12, 12 )
elif shape == ClientRatings.STAR:
offset = QC.QPoint( x + 1, y + 1 )
painter.translate( offset )
painter.drawPolygon( QG.QPolygonF( STAR_COORDS ) )
painter.translate( -offset )
def DrawNumerical( painter, x, y, service_key, rating_state, rating ):
( shape, stars ) = GetStars( service_key, rating_state, rating )
x_delta = 0
x_step = 12
for ( num_stars, pen_colour, brush_colour ) in stars:
painter.setPen( QG.QPen( pen_colour ) )
painter.setBrush( QG.QBrush( brush_colour ) )
for i in range( num_stars ):
if shape == ClientRatings.CIRCLE:
painter.drawEllipse( QC.QPointF( x + 7 + x_delta, y + 7 ), 6, 6 )
elif shape == ClientRatings.SQUARE:
painter.drawRect( x + 2 + x_delta, y + 2, 12, 12 )
elif shape == ClientRatings.STAR:
offset = QC.QPoint( x + 1 + x_delta, y + 1 )
painter.translate( offset )
painter.drawPolygon( QG.QPolygonF( STAR_COORDS ) )
painter.translate( -offset )
x_delta += x_step
def GetNumericalWidth( service_key ):
try:
service = HG.client_controller.services_manager.GetService( service_key )
num_stars = service.GetNumStars()
except HydrusExceptions.DataMissing:
num_stars = 1
return 4 + 12 * num_stars
def GetPenAndBrushColours( service_key, rating_state ):
try:
service = HG.client_controller.services_manager.GetService( service_key )
colour = service.GetColour( rating_state )
except HydrusExceptions.DataMissing:
colour = ( ( 0, 0, 0 ), ( 0, 0, 0 ) )
( pen_rgb, brush_rgb ) = colour
pen_colour = QG.QColor( *pen_rgb )
brush_colour = QG.QColor( *brush_rgb )
return ( pen_colour, brush_colour )
def GetStars( service_key, rating_state, rating ):
try:
service = HG.client_controller.services_manager.GetService( service_key )
except HydrusExceptions.DataMissing:
return ( ClientRatings.STAR, 0 )
shape = service.GetShape()
num_stars = service.GetNumStars()
stars = []
if rating_state in ( ClientRatings.NULL, ClientRatings.MIXED ):
( pen_colour, brush_colour ) = GetPenAndBrushColours( service_key, rating_state )
stars.append( ( num_stars, pen_colour, brush_colour ) )
else:
num_stars_on = service.ConvertRatingToStars( rating )
num_stars_off = num_stars - num_stars_on
( pen_colour, brush_colour ) = GetPenAndBrushColours( service_key, ClientRatings.LIKE )
stars.append( ( num_stars_on, pen_colour, brush_colour ) )
( pen_colour, brush_colour ) = GetPenAndBrushColours( service_key, ClientRatings.DISLIKE )
stars.append( ( num_stars_off, pen_colour, brush_colour ) )
return ( shape, stars )
class RatingLike( QW.QWidget ):
def __init__( self, parent, service_key ):
QW.QWidget.__init__( self, parent )
self._service_key = service_key
self._widget_event_filter = QP.WidgetEventFilter( self )
self._widget_event_filter.EVT_LEFT_DOWN( self.EventLeftDown )
self._widget_event_filter.EVT_LEFT_DCLICK( self.EventLeftDown )
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventRightDown )
self._widget_event_filter.EVT_RIGHT_DCLICK( self.EventRightDown )
self.setMinimumSize( QC.QSize( 16, 16 ) )
self._dirty = True
def _Draw( self, painter ):
raise NotImplementedError()
def EventLeftDown( self, event ):
raise NotImplementedError()
def paintEvent( self, event ):
painter = QG.QPainter( self )
self._Draw( painter )
def EventRightDown( self, event ):
raise NotImplementedError()
def GetServiceKey( self ):
return self._service_key
class RatingLikeDialog( RatingLike ):
def __init__( self, parent, service_key ):
RatingLike.__init__( self, parent, service_key )
self._rating_state = ClientRatings.NULL
def _Draw( self, painter ):
painter.setBackground( QG.QBrush( QP.GetBackgroundColour( self.parentWidget() ) ) )
painter.eraseRect( painter.viewport() )
( pen_colour, brush_colour ) = GetPenAndBrushColours( self._service_key, self._rating_state )
DrawLike( painter, 0, 0, self._service_key, self._rating_state )
self._dirty = False
def EventLeftDown( self, event ):
if self._rating_state == ClientRatings.LIKE: self._rating_state = ClientRatings.NULL
else: self._rating_state = ClientRatings.LIKE
self._dirty = True
self.update()
def EventRightDown( self, event ):
if self._rating_state == ClientRatings.DISLIKE: self._rating_state = ClientRatings.NULL
else: self._rating_state = ClientRatings.DISLIKE
self._dirty = True
self.update()
def GetRatingState( self ):
return self._rating_state
def SetRatingState( self, rating_state ):
self._rating_state = rating_state
self._dirty = True
self.update()
class RatingNumerical( QW.QWidget ):
def __init__( self, parent, service_key ):
QW.QWidget.__init__( self, parent )
self._service_key = service_key
self._service = HG.client_controller.services_manager.GetService( self._service_key )
self._num_stars = self._service.GetNumStars()
self._allow_zero = self._service.AllowZero()
my_width = GetNumericalWidth( self._service_key )
self._widget_event_filter = QP.WidgetEventFilter( self )
self._widget_event_filter.EVT_LEFT_DOWN( self.EventLeftDown )
self._widget_event_filter.EVT_LEFT_DCLICK( self.EventLeftDown )
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventRightDown )
self._widget_event_filter.EVT_RIGHT_DCLICK( self.EventRightDown )
self.setMinimumSize( QC.QSize( my_width, 16 ) )
self._last_rating_set = None
self._dirty = True
def _ClearRating( self ):
self._last_rating_set = None
def _Draw( self, painter ):
raise NotImplementedError()
def _GetRatingFromClickEvent( self, event ):
click_pos = event.pos()
x = event.pos().x()
y = event.pos().y()
BORDER = 1
my_active_size = self.size() - QC.QSize( BORDER * 2, BORDER * 2 )
adjusted_click_pos = click_pos - QC.QPoint( BORDER, BORDER )
my_active_rect = QC.QRect( QC.QPoint( 0, 0 ), my_active_size )
if my_active_rect.contains( adjusted_click_pos ):
x_adjusted = x - BORDER
proportion_filled = x_adjusted / my_active_size.width()
if self._allow_zero:
stars = round( proportion_filled * self._num_stars )
else:
stars = int( proportion_filled * self._num_stars )
if proportion_filled <= 1.0:
stars += 1
rating = self._service.ConvertStarsToRating( stars )
return rating
return None
def _SetRating( self, rating ):
self._last_rating_set = rating
def EventLeftDown( self, event ):
rating = self._GetRatingFromClickEvent( event )
self._SetRating( rating )
def EventRightDown( self, event ):
self._ClearRating()
def GetServiceKey( self ):
return self._service_key
def mouseMoveEvent( self, event ):
if event.buttons() & QC.Qt.LeftButton:
rating = self._GetRatingFromClickEvent( event )
if rating != self._last_rating_set:
self._SetRating( rating )
def paintEvent( self, event ):
painter = QG.QPainter( self )
self._Draw( painter )
class RatingNumericalDialog( RatingNumerical ):
def __init__( self, parent, service_key ):
RatingNumerical.__init__( self, parent, service_key )
self._rating_state = ClientRatings.NULL
self._rating = None
def _ClearRating( self ):
RatingNumerical._ClearRating( self )
self._rating_state = ClientRatings.NULL
self._dirty = True
self.update()
def _Draw( self, painter ):
painter.setBackground( QG.QBrush( QP.GetBackgroundColour( self.parentWidget() ) ) )
painter.eraseRect( painter.viewport() )
DrawNumerical( painter, 0, 0, self._service_key, self._rating_state, self._rating )
self._dirty = False
def _SetRating( self, rating ):
RatingNumerical._SetRating( self, rating )
if rating is None:
self._ClearRating()
else:
self._rating_state = ClientRatings.SET
self._rating = rating
self._dirty = True
self.update()
def GetRating( self ):
return self._rating
def GetRatingState( self ):
return self._rating_state
def SetRating( self, rating ):
self._SetRating( rating )
def SetRatingState( self, rating_state ):
self._rating_state = rating_state
self._dirty = True
self.update()

View File

@ -23,7 +23,6 @@ from hydrus.client import ClientFiles
from hydrus.client.media import ClientMedia
from hydrus.client import ClientPaths
from hydrus.client import ClientSearch
from hydrus.client import ClientTags
from hydrus.client.gui import ClientGUIDragDrop
from hydrus.client.gui import ClientGUICanvas
from hydrus.client.gui import ClientGUICanvasFrame
@ -37,14 +36,13 @@ from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import ClientGUIMedia
from hydrus.client.gui import ClientGUIMediaActions
from hydrus.client.gui import ClientGUIMenus
from hydrus.client.gui import ClientGUIScrolledPanels
from hydrus.client.gui import ClientGUIScrolledPanelsEdit
from hydrus.client.gui import ClientGUIScrolledPanelsManagement
from hydrus.client.gui import ClientGUIShortcuts
from hydrus.client.gui import ClientGUITags
from hydrus.client.gui import ClientGUITopLevelWindows
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QtPorting as QP
from hydrus.client.metadata import ClientTags
class MediaPanel( ClientMedia.ListeningMediaList, QW.QScrollArea ):

View File

@ -17,7 +17,6 @@ from hydrus.core import HydrusText
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientDuplicates
from hydrus.client import ClientTags
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUIControls
from hydrus.client.gui import ClientGUIDialogs
@ -35,6 +34,7 @@ from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
from hydrus.client.gui.lists import ClientGUIListCtrl
from hydrus.client.importing import ClientImportOptions
from hydrus.client.media import ClientMedia
from hydrus.client.metadata import ClientTags
class EditAccountTypePanel( ClientGUIScrolledPanels.EditPanel ):

View File

@ -1637,8 +1637,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
default_fios = ClientGUICommon.StaticBox( self, 'default file import options' )
from hydrus.client.gui import ClientGUIImport
show_downloader_options = True
quiet_file_import_options = self._new_options.GetDefaultFileImportOptions( 'quiet' )

View File

@ -29,7 +29,6 @@ from hydrus.client import ClientPaths
from hydrus.client import ClientRendering
from hydrus.client import ClientSearch
from hydrus.client import ClientSerialisable
from hydrus.client import ClientTags
from hydrus.client import ClientThreading
from hydrus.client.gui import ClientGUIDragDrop
from hydrus.client.gui import ClientGUIACDropdown
@ -40,13 +39,13 @@ from hydrus.client.gui import ClientGUIDialogsQuick
from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import ClientGUIImport
from hydrus.client.gui import ClientGUIScrolledPanels
from hydrus.client.gui import ClientGUIScrolledPanelsEdit
from hydrus.client.gui import ClientGUIPopupMessages
from hydrus.client.gui import ClientGUITags
from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
from hydrus.client.gui.lists import ClientGUIListCtrl
from hydrus.client.metadata import ClientTags
from hydrus.client.networking import ClientNetworkingContexts
from hydrus.client.networking import ClientNetworkingDomain
from hydrus.client.networking import ClientNetworkingLogin

View File

@ -11,8 +11,6 @@ from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusText
from hydrus.client import ClientConstants as CC
from hydrus.client.media import ClientMedia
from hydrus.client import ClientRatings
from hydrus.client import ClientSearch
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUIControls
@ -20,10 +18,13 @@ from hydrus.client.gui import ClientGUICore as CGC
from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import ClientGUIMenus
from hydrus.client.gui import ClientGUIOptionsPanels
from hydrus.client.gui import ClientGUIRatings
from hydrus.client.gui import ClientGUIScrolledPanels
from hydrus.client.gui import ClientGUIShortcuts
from hydrus.client.gui import ClientGUITime
from hydrus.client.gui import QtPorting as QP
from hydrus.client.media import ClientMedia
from hydrus.client.metadata import ClientRatings
FLESH_OUT_SYSTEM_PRED_TYPES = {
ClientSearch.PREDICATE_TYPE_SYSTEM_NUM_TAGS,
@ -1999,7 +2000,7 @@ class PanelPredicateSystemRating( PanelPredicateSystem ):
rated_checkbox = QW.QCheckBox( 'rated', self )
not_rated_checkbox = QW.QCheckBox( 'not rated', self )
rating_ctrl = ClientGUICommon.RatingLikeDialog( self, service_key )
rating_ctrl = ClientGUIRatings.RatingLikeDialog( self, service_key )
self._like_checkboxes_to_info[ rated_checkbox ] = ( service_key, ClientRatings.SET )
self._like_checkboxes_to_info[ not_rated_checkbox ] = ( service_key, ClientRatings.NULL )
@ -2028,7 +2029,7 @@ class PanelPredicateSystemRating( PanelPredicateSystem ):
rated_checkbox = QW.QCheckBox( 'rated', self )
not_rated_checkbox = QW.QCheckBox( 'not rated', self )
choice = QP.RadioBox( self, choices=['>','<','=','\u2248'] )
rating_ctrl = ClientGUICommon.RatingNumericalDialog( self, service_key )
rating_ctrl = ClientGUIRatings.RatingNumericalDialog( self, service_key )
choice.Select( 2 )

View File

@ -14,7 +14,6 @@ from hydrus.core import HydrusSerialisable
from hydrus.core import HydrusTagArchive
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientRatings
from hydrus.client import ClientServices
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUIControls
@ -28,6 +27,7 @@ from hydrus.client.gui import ClientGUITopLevelWindowsPanels
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
from hydrus.client.gui.lists import ClientGUIListCtrl
from hydrus.client.metadata import ClientRatings
from hydrus.client.networking import ClientNetworkingContexts
from hydrus.client.networking import ClientNetworkingJobs
@ -538,7 +538,7 @@ class ManageClientServicesPanel( ClientGUIScrolledPanels.ManagePanel ):
pretty_deletable = ''
return ( ( pretty_service_type, name, pretty_deletable ), ( pretty_service_type, name, deletable ) )
return ( ( name, pretty_service_type, pretty_deletable ), ( name, pretty_service_type, deletable ) )
def _GetExistingNames( self ):

View File

@ -181,8 +181,6 @@ class EditStringConverterPanel( ClientGUIScrolledPanels.EditPanel ):
text = 'Delete all selected?'
from hydrus.client.gui import ClientGUIDialogsQuick
result = ClientGUIDialogsQuick.GetYesNo( self, text )
if result == QW.QDialog.Accepted:

View File

@ -13,13 +13,13 @@ from hydrus.client import ClientConstants as CC
from hydrus.client.media import ClientMedia
from hydrus.client import ClientParsing
from hydrus.client import ClientSearch
from hydrus.client import ClientTags
from hydrus.client import ClientThreading
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUIDialogs
from hydrus.client.gui import ClientGUIParsing
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListBoxes
from hydrus.client.metadata import ClientTags
def FilterSuggestedPredicatesForMedia( predicates: typing.List[ ClientSearch.Predicate ], medias: typing.Collection[ ClientMedia.Media ], service_key: bytes ) -> typing.List[ ClientSearch.Predicate ]:

View File

@ -18,8 +18,6 @@ from hydrus.core import HydrusText
from hydrus.client import ClientApplicationCommand as CAC
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientManagers
from hydrus.client.media import ClientMedia
from hydrus.client import ClientTags
from hydrus.client.gui import ClientGUIACDropdown
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUIControls
@ -37,10 +35,13 @@ from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListBoxes
from hydrus.client.gui.lists import ClientGUIListConstants as CGLC
from hydrus.client.gui.lists import ClientGUIListCtrl
from hydrus.client.media import ClientMedia
from hydrus.client.metadata import ClientTags
from hydrus.client.metadata import ClientTagsHandling
class EditTagAutocompleteOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent: QW.QWidget, tag_autocomplete_options: ClientTags.TagAutocompleteOptions ):
def __init__( self, parent: QW.QWidget, tag_autocomplete_options: ClientTagsHandling.TagAutocompleteOptions ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
@ -178,7 +179,7 @@ class EditTagAutocompleteOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
def GetValue( self ):
tag_autocomplete_options = ClientTags.TagAutocompleteOptions( self._original_tag_autocomplete_options.GetServiceKey() )
tag_autocomplete_options = ClientTagsHandling.TagAutocompleteOptions( self._original_tag_autocomplete_options.GetServiceKey() )
write_autocomplete_tag_domain = self._write_autocomplete_tag_domain.GetValue()
override_write_autocomplete_file_domain = self._override_write_autocomplete_file_domain.isChecked()
@ -203,10 +204,12 @@ class EditTagAutocompleteOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
class EditTagDisplayManagerPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, tag_display_manager ):
def __init__( self, parent, tag_display_manager: ClientTagsHandling.TagDisplayManager ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._original_tag_display_manager = tag_display_manager
self._tag_services = ClientGUICommon.BetterNotebook( self )
min_width = ClientGUIFunctions.ConvertTextToPixelWidth( self._tag_services, 100 )
@ -222,7 +225,7 @@ class EditTagDisplayManagerPanel( ClientGUIScrolledPanels.EditPanel ):
service_key = service.GetServiceKey()
name = service.GetName()
page = self._Panel( self._tag_services, tag_display_manager, service_key )
page = self._Panel( self._tag_services, self._original_tag_display_manager, service_key )
select = service_key == CC.COMBINED_TAG_SERVICE_KEY
@ -234,12 +237,6 @@ class EditTagDisplayManagerPanel( ClientGUIScrolledPanels.EditPanel ):
vbox = QP.VBoxLayout()
intro = 'Please note this new system is under construction. It is neither completely functional nor as efficient as intended.'
st = ClientGUICommon.BetterStaticText( self, intro )
st.setWordWrap( True )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._tag_services, CC.FLAGS_EXPAND_BOTH_WAYS )
self.widget().setLayout( vbox )
@ -247,7 +244,9 @@ class EditTagDisplayManagerPanel( ClientGUIScrolledPanels.EditPanel ):
def GetValue( self ):
tag_display_manager = ClientTags.TagDisplayManager()
tag_display_manager = self._original_tag_display_manager.Duplicate()
tag_display_manager.ClearTagDisplayOptions()
for page in self._tag_services.GetPages():
@ -266,7 +265,7 @@ class EditTagDisplayManagerPanel( ClientGUIScrolledPanels.EditPanel ):
class _Panel( QW.QWidget ):
def __init__( self, parent: QW.QWidget, tag_display_manager: ClientTags.TagDisplayManager, service_key: bytes ):
def __init__( self, parent: QW.QWidget, tag_display_manager: ClientTagsHandling.TagDisplayManager, service_key: bytes ):
QW.QWidget.__init__( self, parent )
@ -1482,6 +1481,169 @@ class EditTagFilterPanel( ClientGUIScrolledPanels.EditPanel ):
self._UpdateStatus()
class EditTagSiblingsApplication( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, tag_display_manager: ClientTagsHandling.TagDisplayManager ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._original_tag_display_manager = tag_display_manager
self._tag_services = ClientGUICommon.BetterNotebook( self )
min_width = ClientGUIFunctions.ConvertTextToPixelWidth( self._tag_services, 100 )
self._tag_services.setMinimumWidth( min_width )
#
services = list( HG.client_controller.services_manager.GetServices( ( HC.COMBINED_TAG, HC.LOCAL_TAG, HC.TAG_REPOSITORY ) ) )
local_service_keys = list( HG.client_controller.services_manager.GetServiceKeys( ( HC.LOCAL_TAG, ) ) )
select_service_key = local_service_keys[0]
# a panel for combined, panels for all else
# default select the first local tag service
for service in services:
service_key = service.GetServiceKey()
name = service.GetName()
page = self._Panel( self._tag_services, self._original_tag_display_manager, service_key )
select = service_key == select_service_key
self._tag_services.addTab( page, name )
if select:
self._tag_services.setCurrentWidget( page )
#
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, self._tag_services, CC.FLAGS_EXPAND_BOTH_WAYS )
self.widget().setLayout( vbox )
def GetValue( self ):
tag_display_manager = self._original_tag_display_manager.Duplicate()
service_keys_to_ordered_sibling_service_keys = {}
for page in self._tag_services.GetPages():
service_key = page.GetServiceKey()
ordered_service_keys = page.GetValue()
service_keys_to_ordered_sibling_service_keys[ service_key ] = ordered_service_keys
tag_display_manager.SetSiblingServiceKeys( service_keys_to_ordered_sibling_service_keys )
return tag_display_manager
class _Panel( QW.QWidget ):
def __init__( self, parent: QW.QWidget, tag_display_manager: ClientTagsHandling.TagDisplayManager, service_key: bytes ):
QW.QWidget.__init__( self, parent )
self._service_key = service_key
if self._service_key == CC.COMBINED_TAG_SERVICE_KEY:
message = 'In a couple of situations, typically tag autocomplete suggestion swapping, the client wants to do sibling lookups in \'all known tags\' domain. In this case, it generally tries to combine all known siblings.'
message += os.linesep * 2
message += 'You can leave this as it is, but if you do not want any siblings, or always want one priority, feel free to set something else.'
else:
message = 'Normally, a tag service applies its own siblings to itself. If, however, you want a different service\'s siblings (e.g. putting the PTR\'s siblings on your "my tags"), or multiple services\', then set it here. You can also apply no siblings.'
message += os.linesep * 2
message += 'If there are conflicts, the services at the top of the list have precedence.'
ordered_service_keys = tag_display_manager.GetSiblingServiceKeys( HG.client_controller.services_manager, self._service_key )
#
self._display_box = ClientGUICommon.StaticBox( self, 'sibling application' )
self._message = ClientGUICommon.BetterStaticText( self._display_box, label = message )
self._message.setWordWrap( True )
#
self._service_keys = ClientGUIListBoxes.QueueListBox( self._display_box, 6, HG.client_controller.services_manager.GetName, add_callable = self._Add )
#
self._service_keys.AddDatas( ordered_service_keys )
#
self._display_box.Add( self._message, CC.FLAGS_EXPAND_PERPENDICULAR )
self._display_box.Add( self._service_keys, CC.FLAGS_EXPAND_BOTH_WAYS )
#
vbox = QP.VBoxLayout()
QP.AddToLayout( vbox, self._display_box, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.addStretch( 1 )
self.setLayout( vbox )
def _Add( self ):
current_service_keys = set( self.GetValue() )
allowed_services = HG.client_controller.services_manager.GetServices( HC.REAL_TAG_SERVICES )
allowed_services = [ service for service in allowed_services if service.GetServiceKey() not in current_service_keys ]
if len( allowed_services ) == 0:
QW.QMessageBox.information( self, 'Information', 'You have all the current tag services applied to this service.' )
raise HydrusExceptions.VetoException()
choice_tuples = [ ( service.GetName(), service.GetServiceKey(), service.GetName() ) for service in allowed_services ]
try:
service_key = ClientGUIDialogsQuick.SelectFromListButtons( self, 'Which service?', choice_tuples )
return service_key
except HydrusExceptions.CancelledException:
raise HydrusExceptions.VetoException()
def GetServiceKey( self ):
return self._service_key
def GetValue( self ):
return self._service_keys.GetData()
class ManageTagsPanel( ClientGUIScrolledPanels.ManagePanel ):
def __init__( self, parent, file_service_key, media, immediate_commit = False, canvas_key = None ):

View File

@ -16,7 +16,6 @@ from hydrus.core import HydrusTags
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientSearch
from hydrus.client import ClientSerialisable
from hydrus.client import ClientTags
from hydrus.client.gui import ClientGUICommon
from hydrus.client.gui import ClientGUICore as CGC
from hydrus.client.gui import ClientGUIFunctions
@ -25,6 +24,7 @@ from hydrus.client.gui import ClientGUISearch
from hydrus.client.gui import ClientGUIShortcuts
from hydrus.client.gui import QtPorting as QP
from hydrus.client.media import ClientMedia
from hydrus.client.metadata import ClientTags
class BetterQListWidget( QW.QListWidget ):
@ -754,7 +754,15 @@ class QueueListBox( QW.QWidget ):
#
self._listbox.itemSelectionChanged.connect( self._UpdateButtons )
self._listbox.itemDoubleClicked.connect( self._Edit )
if self._edit_callable is None:
self._listbox.itemDoubleClicked.connect( self._Edit )
else:
self._listbox.itemDoubleClicked.connect( self._Delete )
self._UpdateButtons()
@ -1722,6 +1730,15 @@ class ListBox( QW.QScrollArea ):
return size_hint
COPY_ALL_TAGS = 0
COPY_ALL_TAGS_WITH_COUNTS = 1
COPY_SELECTED_TAGS = 2
COPY_SELECTED_TAGS_WITH_COUNTS = 3
COPY_SELECTED_SUBTAGS = 4
COPY_SELECTED_SUBTAGS_WITH_COUNTS = 5
COPY_ALL_SUBTAGS = 6
COPY_ALL_SUBTAGS_WITH_COUNTS = 7
class ListBoxTags( ListBox ):
ors_are_under_construction = False
@ -1744,14 +1761,71 @@ class ListBoxTags( ListBox ):
HG.client_controller.sub( self, '_UpdateBackgroundColour', 'notify_new_colourset' )
def _CanProvideCurrentPagePredicates( self ):
def _GetCopyableTagStrings( self, command ):
return False
only_selected = command in ( COPY_SELECTED_TAGS, COPY_SELECTED_TAGS_WITH_COUNTS, COPY_SELECTED_SUBTAGS, COPY_SELECTED_SUBTAGS_WITH_COUNTS )
with_counts = command in ( COPY_ALL_TAGS_WITH_COUNTS, COPY_ALL_SUBTAGS_WITH_COUNTS, COPY_SELECTED_TAGS_WITH_COUNTS, COPY_SELECTED_SUBTAGS_WITH_COUNTS )
only_subtags = command in ( COPY_ALL_SUBTAGS, COPY_ALL_SUBTAGS_WITH_COUNTS, COPY_SELECTED_SUBTAGS, COPY_SELECTED_SUBTAGS_WITH_COUNTS )
def _GetNamespaceColours( self ):
if only_selected:
if len( self._selected_terms ) > 1:
# keep order
terms = [ term for term in self._ordered_terms if term in self._selected_terms ]
else:
# nice and fast
terms = self._selected_terms
else:
terms = self._ordered_terms
return HC.options[ 'namespace_colours' ]
copyable_tag_strings = []
for term in terms:
if isinstance( term, ClientSearch.Predicate ):
if term.GetType() in ( ClientSearch.PREDICATE_TYPE_TAG, ClientSearch.PREDICATE_TYPE_NAMESPACE, ClientSearch.PREDICATE_TYPE_WILDCARD ):
tag = term.GetValue()
else:
tag = term.ToString( with_count = with_counts )
else:
if self._HasCounts() and with_counts:
tag = self._terms_to_texts[ term ]
else:
tag = str( term )
copyable_tag_strings.append( tag )
if only_subtags:
copyable_tag_strings = [ HydrusTags.SplitTag( tag_string )[1] for tag_string in copyable_tag_strings ]
if not with_counts:
copyable_tag_strings = HydrusData.DedupeList( copyable_tag_strings )
return copyable_tag_strings
def _GetCurrentFileServiceKey( self ):
@ -1764,11 +1838,21 @@ class ListBoxTags( ListBox ):
return set()
def _GetNamespaceColours( self ):
return HC.options[ 'namespace_colours' ]
def _GetNamespaceFromTerm( self, term ):
raise NotImplementedError()
def _CanProvideCurrentPagePredicates( self ):
return False
def _GetSelectedActualTags( self ):
selected_actual_tags = set()
@ -1795,60 +1879,6 @@ class ListBoxTags( ListBox ):
return selected_actual_tags
def _GetCopyableTagStrings( self, only_selected = False, with_counts = False ):
if only_selected:
if len( self._selected_terms ) > 1:
# keep order
terms = [ term for term in self._ordered_terms if term in self._selected_terms ]
else:
terms = self._selected_terms
else:
terms = self._ordered_terms
selected_copyable_tag_strings = []
for term in terms:
if isinstance( term, ClientSearch.Predicate ):
if term.GetType() in ( ClientSearch.PREDICATE_TYPE_TAG, ClientSearch.PREDICATE_TYPE_NAMESPACE, ClientSearch.PREDICATE_TYPE_WILDCARD ):
tag = term.GetValue()
else:
tag = term.ToString( with_count = with_counts )
selected_copyable_tag_strings.append( tag )
else:
if self._HasCounts() and with_counts:
tag = self._terms_to_texts[ term ]
else:
tag = str( term )
selected_copyable_tag_strings.append( tag )
return selected_copyable_tag_strings
def _GetTagFromTerm( self, term ):
raise NotImplementedError()
@ -1963,27 +1993,7 @@ class ListBoxTags( ListBox ):
def _ProcessMenuCopyEvent( self, command ):
only_selected = False
with_counts = False
texts = []
if command in ( 'copy_selected_terms', 'copy_selected_sub_terms' ):
only_selected = True
if command == 'copy_all_tags_with_counts':
with_counts = True
texts = self._GetCopyableTagStrings( only_selected = only_selected, with_counts = with_counts )
if command == 'copy_selected_sub_terms':
texts = [ subtag for ( namespace, subtag ) in [ HydrusTags.SplitTag( text ) for text in texts ] ]
texts = self._GetCopyableTagStrings( command )
if len( texts ) > 0:
@ -2134,13 +2144,16 @@ class ListBoxTags( ListBox ):
def ShowMenu( self ):
sub_selection_string = None
if len( self._ordered_terms ) > 0:
menu = QW.QMenu()
copy_menu = QW.QMenu( menu )
selected_copyable_tag_strings = self._GetCopyableTagStrings( only_selected = True, with_counts = False )
selected_copyable_tag_strings = self._GetCopyableTagStrings( COPY_SELECTED_TAGS )
selected_copyable_subtag_strings = self._GetCopyableTagStrings( COPY_SELECTED_SUBTAGS )
selected_actual_tags = self._GetSelectedActualTags()
if len( selected_copyable_tag_strings ) == 1:
@ -2149,16 +2162,14 @@ class ListBoxTags( ListBox ):
else:
selection_string = 'selected'
selection_string = '{} selected'.format( HydrusData.ToHumanInt( len( selected_copyable_tag_strings ) ) )
selected_stuff_to_copy = len( selected_copyable_tag_strings ) > 0
if selected_stuff_to_copy:
if len( selected_copyable_tag_strings ) > 0:
ClientGUIMenus.AppendMenuItem( copy_menu, selection_string, 'Copy the selected predicates to your clipboard.', self._ProcessMenuCopyEvent, 'copy_selected_terms' )
ClientGUIMenus.AppendMenuItem( copy_menu, selection_string, 'Copy the selected tags to your clipboard.', self._ProcessMenuCopyEvent, COPY_SELECTED_TAGS )
if len( selected_copyable_tag_strings ) == 1:
if len( selected_copyable_subtag_strings ) == 1:
( selection_string, ) = selected_copyable_tag_strings
@ -2168,12 +2179,14 @@ class ListBoxTags( ListBox ):
sub_selection_string = subtag
ClientGUIMenus.AppendMenuItem( copy_menu, sub_selection_string, 'Copy the selected sub-predicate to your clipboard.', self._ProcessMenuCopyEvent, 'copy_selected_sub_terms' )
ClientGUIMenus.AppendMenuItem( copy_menu, sub_selection_string, 'Copy the selected subtag to your clipboard.', self._ProcessMenuCopyEvent, COPY_SELECTED_SUBTAGS )
else:
ClientGUIMenus.AppendMenuItem( copy_menu, 'selected subtags', 'Copy the selected sub-predicates to your clipboard.', self._ProcessMenuCopyEvent, 'copy_selected_sub_terms' )
sub_selection_string = '{} selected subtags'.format( HydrusData.ToHumanInt( len( selected_copyable_subtag_strings ) ) )
ClientGUIMenus.AppendMenuItem( copy_menu, sub_selection_string, 'Copy the selected subtags to your clipboard.', self._ProcessMenuCopyEvent, COPY_SELECTED_SUBTAGS )
siblings = []
@ -2222,6 +2235,18 @@ class ListBoxTags( ListBox ):
if self._HasCounts():
ClientGUIMenus.AppendSeparator( copy_menu )
ClientGUIMenus.AppendMenuItem( copy_menu, '{} with counts'.format( selection_string ), 'Copy the selected tags, with their counts, to your clipboard.', self._ProcessMenuCopyEvent, COPY_SELECTED_TAGS_WITH_COUNTS )
if sub_selection_string is not None:
ClientGUIMenus.AppendMenuItem( copy_menu, '{} with counts'.format( sub_selection_string ), 'Copy the selected subtags, with their counts, to your clipboard.', self._ProcessMenuCopyEvent, COPY_SELECTED_SUBTAGS_WITH_COUNTS )
copy_all_is_appropriate = len( self._ordered_terms ) > len( self._selected_terms )
@ -2229,11 +2254,13 @@ class ListBoxTags( ListBox ):
ClientGUIMenus.AppendSeparator( copy_menu )
ClientGUIMenus.AppendMenuItem( copy_menu, 'all tags', 'Copy all the predicates in this list to your clipboard.', self._ProcessMenuCopyEvent, 'copy_all_tags' )
ClientGUIMenus.AppendMenuItem( copy_menu, 'all tags', 'Copy all the tags in this list to your clipboard.', self._ProcessMenuCopyEvent, COPY_ALL_TAGS )
ClientGUIMenus.AppendMenuItem( copy_menu, 'all subtags', 'Copy all the subtags in this list to your clipboard.', self._ProcessMenuCopyEvent, COPY_ALL_SUBTAGS )
if self._HasCounts():
ClientGUIMenus.AppendMenuItem( copy_menu, 'all tags with counts', 'Copy all the predicates in this list, with their counts, to your clipboard.', self._ProcessMenuCopyEvent, 'copy_all_tags_with_counts' )
ClientGUIMenus.AppendMenuItem( copy_menu, 'all tags with counts', 'Copy all the tags in this list, with their counts, to your clipboard.', self._ProcessMenuCopyEvent, COPY_ALL_TAGS_WITH_COUNTS )
ClientGUIMenus.AppendMenuItem( copy_menu, 'all subtags with counts', 'Copy all the subtags in this list, with their counts, to your clipboard.', self._ProcessMenuCopyEvent, COPY_ALL_SUBTAGS_WITH_COUNTS )

View File

@ -22,9 +22,9 @@ from hydrus.client import ClientData
from hydrus.client import ClientImageHandling
from hydrus.client import ClientParsing
from hydrus.client import ClientPaths
from hydrus.client import ClientTags
from hydrus.client.importing import ClientImporting
from hydrus.client.importing import ClientImportOptions
from hydrus.client.metadata import ClientTags
from hydrus.client.networking import ClientNetworkingDomain
class FileImportJob( object ):
@ -2333,7 +2333,14 @@ class FileSeedCache( HydrusSerialisable.SerialisableBase ):
file_seed_hashes = [ file_seed.GetHash() for file_seed in eligible_file_seeds ]
inbox_hashes = HG.client_controller.Read( 'in_inbox', file_seed_hashes )
if len( file_seed_hashes ) > 0:
inbox_hashes = HG.client_controller.Read( 'in_inbox', file_seed_hashes )
else:
inbox_hashes = set()
hashes = []
hashes_seen = set()

View File

@ -538,9 +538,11 @@ class GalleryImport( HydrusSerialisable.SerialisableBase ):
with self._lock:
return self._file_seed_cache.GetHashes()
fsc = self._file_seed_cache
return fsc.GetHashes()
def GetNetworkJobs( self ):
@ -554,13 +556,15 @@ class GalleryImport( HydrusSerialisable.SerialisableBase ):
with self._lock:
file_import_options = ClientImportOptions.FileImportOptions()
file_import_options.SetPresentationOptions( True, False, False )
return self._file_seed_cache.GetPresentedHashes( file_import_options )
fsc = self._file_seed_cache
file_import_options = ClientImportOptions.FileImportOptions()
file_import_options.SetPresentationOptions( True, False, False )
return fsc.GetPresentedHashes( file_import_options )
def GetNumSeeds( self ):
@ -582,9 +586,12 @@ class GalleryImport( HydrusSerialisable.SerialisableBase ):
with self._lock:
return self._file_seed_cache.GetPresentedHashes( self._file_import_options )
fsc = self._file_seed_cache
fio = self._file_import_options
return fsc.GetPresentedHashes( fio )
def GetQueryText( self ):
@ -1438,8 +1445,6 @@ class MultipleGalleryImport( HydrusSerialisable.SerialisableBase ):
self._highlighted_gallery_import_key = highlighted_gallery_import.GetGalleryImportKey()
highlighted_gallery_import.PublishToPage( True )

View File

@ -15,8 +15,8 @@ from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientParsing
from hydrus.client import ClientTags
from hydrus.client.importing import ClientImporting
from hydrus.client.metadata import ClientTags
def GenerateGallerySeedLogStatus( statuses_to_counts ):

View File

@ -15,11 +15,11 @@ from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientFiles
from hydrus.client import ClientPaths
from hydrus.client import ClientTags
from hydrus.client import ClientThreading
from hydrus.client.importing import ClientImporting
from hydrus.client.importing import ClientImportFileSeeds
from hydrus.client.importing import ClientImportOptions
from hydrus.client.metadata import ClientTags
class HDDImport( HydrusSerialisable.SerialisableBase ):

View File

@ -12,8 +12,8 @@ from hydrus.core import HydrusText
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientTags
from hydrus.client.media import ClientMediaResult
from hydrus.client.metadata import ClientTags
def FilterDeletedTags( service_key: bytes, media_result: ClientMediaResult.MediaResult, tags: typing.Iterable[ str ] ):

View File

@ -10,11 +10,11 @@ from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientTags
from hydrus.client.importing import ClientImporting
from hydrus.client.importing import ClientImportFileSeeds
from hydrus.client.importing import ClientImportGallerySeeds
from hydrus.client.importing import ClientImportOptions
from hydrus.client.metadata import ClientTags
from hydrus.client.networking import ClientNetworkingJobs
class SimpleDownloaderImport( HydrusSerialisable.SerialisableBase ):

View File

@ -8,11 +8,11 @@ from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientTags
from hydrus.client.importing import ClientImporting
from hydrus.client.importing import ClientImportOptions
from hydrus.client.importing import ClientImportFileSeeds
from hydrus.client.importing import ClientImportGallerySeeds
from hydrus.client.metadata import ClientTags
from hydrus.client.networking import ClientNetworkingJobs
class MultipleWatcherImport( HydrusSerialisable.SerialisableBase ):
@ -437,8 +437,6 @@ class MultipleWatcherImport( HydrusSerialisable.SerialisableBase ):
self._highlighted_watcher_url = highlighted_watcher.GetURL()
highlighted_watcher.PublishToPage( True )
@ -1102,9 +1100,11 @@ class WatcherImport( HydrusSerialisable.SerialisableBase ):
with self._lock:
return self._file_seed_cache.GetHashes()
fsc = self._file_seed_cache
return fsc.GetHashes()
def GetNetworkJobs( self ):
@ -1118,13 +1118,15 @@ class WatcherImport( HydrusSerialisable.SerialisableBase ):
with self._lock:
file_import_options = ClientImportOptions.FileImportOptions()
file_import_options.SetPresentationOptions( True, False, False )
return self._file_seed_cache.GetPresentedHashes( file_import_options )
fsc = self._file_seed_cache
file_import_options = ClientImportOptions.FileImportOptions()
file_import_options.SetPresentationOptions( True, False, False )
return fsc.GetPresentedHashes( file_import_options )
def GetNumSeeds( self ):
@ -1146,9 +1148,12 @@ class WatcherImport( HydrusSerialisable.SerialisableBase ):
with self._lock:
return self._file_seed_cache.GetPresentedHashes( self._file_import_options )
fsc = self._file_seed_cache
fio = self._file_import_options
return fsc.GetPresentedHashes( fio )
def GetSimpleStatus( self ):

View File

@ -12,9 +12,9 @@ from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientTags
from hydrus.client.media import ClientMediaManagers
from hydrus.client.media import ClientMediaResult
from hydrus.client.metadata import ClientTags
hashes_to_jpeg_quality = {}
hashes_to_pixel_hashes = {}

View File

@ -11,7 +11,7 @@ from hydrus.core import HydrusGlobals as HG
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientSearch
from hydrus.client import ClientTags
from hydrus.client.metadata import ClientTags
class DuplicatesManager( object ):

View File

@ -1,7 +1,3 @@
from qtpy import QtCore as QC
from qtpy import QtGui as QG
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
@ -15,98 +11,6 @@ CIRCLE = 0
SQUARE = 1
STAR = 2
default_like_colours = {}
default_like_colours[ LIKE ] = ( ( 0, 0, 0 ), ( 80, 200, 120 ) )
default_like_colours[ DISLIKE ] = ( ( 0, 0, 0 ), ( 200, 80, 120 ) )
default_like_colours[ NULL ] = ( ( 0, 0, 0 ), ( 191, 191, 191 ) )
default_like_colours[ MIXED ] = ( ( 0, 0, 0 ), ( 95, 95, 95 ) )
default_numerical_colours = {}
default_numerical_colours[ LIKE ] = ( ( 0, 0, 0 ), ( 80, 200, 120 ) )
default_numerical_colours[ DISLIKE ] = ( ( 0, 0, 0 ), ( 255, 255, 255 ) )
default_numerical_colours[ NULL ] = ( ( 0, 0, 0 ), ( 191, 191, 191 ) )
default_numerical_colours[ MIXED ] = ( ( 0, 0, 0 ), ( 95, 95, 95 ) )
STAR_COORDS = []
STAR_COORDS.append( QC.QPoint( 6, 0 ) ) # top
STAR_COORDS.append( QC.QPoint( 9, 4 ) )
STAR_COORDS.append( QC.QPoint( 12, 4 ) ) # right
STAR_COORDS.append( QC.QPoint( 9, 8 ) )
STAR_COORDS.append( QC.QPoint( 10, 12 ) ) # bottom right
STAR_COORDS.append( QC.QPoint( 6, 10 ) )
STAR_COORDS.append( QC.QPoint( 2, 12 ) ) # bottom left
STAR_COORDS.append( QC.QPoint( 3, 8 ) )
STAR_COORDS.append( QC.QPoint( 0, 4 ) ) # left
STAR_COORDS.append( QC.QPoint( 3, 4 ) )
def DrawLike( painter, x, y, service_key, rating_state ):
shape = GetShape( service_key )
( pen_colour, brush_colour ) = GetPenAndBrushColours( service_key, rating_state )
painter.setPen( QG.QPen( pen_colour ) )
painter.setBrush( QG.QBrush( brush_colour ) )
if shape == CIRCLE:
painter.drawEllipse( QC.QPointF( x+7, y+7 ), 6, 6 )
elif shape == SQUARE:
painter.drawRect( x+2, y+2, 12, 12 )
elif shape == STAR:
offset = QC.QPoint( x + 1, y + 1 )
painter.translate( offset )
painter.drawPolygon( QG.QPolygonF( STAR_COORDS ) )
painter.translate( -offset )
def DrawNumerical( painter, x, y, service_key, rating_state, rating ):
( shape, stars ) = GetStars( service_key, rating_state, rating )
x_delta = 0
x_step = 12
for ( num_stars, pen_colour, brush_colour ) in stars:
painter.setPen( QG.QPen( pen_colour ) )
painter.setBrush( QG.QBrush( brush_colour ) )
for i in range( num_stars ):
if shape == CIRCLE:
painter.drawEllipse( QC.QPointF( x + 7 + x_delta, y + 7 ), 6, 6 )
elif shape == SQUARE:
painter.drawRect( x + 2 + x_delta, y + 2, 12, 12 )
elif shape == STAR:
offset = QC.QPoint( x + 1 + x_delta, y + 1 )
painter.translate( offset )
painter.drawPolygon( QG.QPolygonF( STAR_COORDS ) )
painter.translate( -offset )
x_delta += x_step
def GetLikeStateFromMedia( media, service_key ):
on_exists = False
@ -204,41 +108,6 @@ def GetNumericalStateFromMedia( media, service_key ):
return ( SET, existing_rating )
def GetNumericalWidth( service_key ):
try:
service = HG.client_controller.services_manager.GetService( service_key )
num_stars = service.GetNumStars()
except HydrusExceptions.DataMissing:
num_stars = 1
return 4 + 12 * num_stars
def GetPenAndBrushColours( service_key, rating_state ):
try:
service = HG.client_controller.services_manager.GetService( service_key )
colour = service.GetColour( rating_state )
except HydrusExceptions.DataMissing:
colour = ( ( 0, 0, 0 ), ( 0, 0, 0 ) )
( pen_rgb, brush_rgb ) = colour
pen_colour = QG.QColor( *pen_rgb )
brush_colour = QG.QColor( *brush_rgb )
return ( pen_colour, brush_colour )
def GetShape( service_key ):
try:
@ -254,43 +123,3 @@ def GetShape( service_key ):
return shape
def GetStars( service_key, rating_state, rating ):
try:
service = HG.client_controller.services_manager.GetService( service_key )
except HydrusExceptions.DataMissing:
return ( STAR, 0 )
shape = service.GetShape()
num_stars = service.GetNumStars()
stars = []
if rating_state in ( NULL, MIXED ):
( pen_colour, brush_colour ) = GetPenAndBrushColours( service_key, rating_state )
stars.append( ( num_stars, pen_colour, brush_colour ) )
else:
num_stars_on = service.ConvertRatingToStars( rating )
num_stars_off = num_stars - num_stars_on
( pen_colour, brush_colour ) = GetPenAndBrushColours( service_key, LIKE )
stars.append( ( num_stars_on, pen_colour, brush_colour ) )
( pen_colour, brush_colour ) = GetPenAndBrushColours( service_key, DISLIKE )
stars.append( ( num_stars_off, pen_colour, brush_colour ) )
return ( shape, stars )

View File

@ -1,6 +1,5 @@
import collections
import threading
import typing
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusSerialisable
@ -8,6 +7,11 @@ from hydrus.core import HydrusTags
from hydrus.client import ClientConstants as CC
TAG_DISPLAY_STORAGE = 0
TAG_DISPLAY_SIBLINGS_AND_PARENTS = 1
TAG_DISPLAY_SINGLE_MEDIA = 2
TAG_DISPLAY_SELECTION_LIST = 3
def ConvertTagSliceToString( tag_slice ):
if tag_slice == '':
@ -227,420 +231,6 @@ class ServiceKeysToTags( HydrusSerialisable.SerialisableBase, collections.defaul
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SERVICE_KEYS_TO_TAGS ] = ServiceKeysToTags
TAG_DISPLAY_STORAGE = 0
TAG_DISPLAY_SIBLINGS_AND_PARENTS = 1
TAG_DISPLAY_SINGLE_MEDIA = 2
TAG_DISPLAY_SELECTION_LIST = 3
class TagAutocompleteOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_TAG_AUTOCOMPLETE_OPTIONS
SERIALISABLE_NAME = 'Tag Autocomplete Options'
SERIALISABLE_VERSION = 2
def __init__( self, service_key: typing.Optional[ bytes ] = None ):
if service_key is None:
service_key = CC.COMBINED_TAG_SERVICE_KEY
HydrusSerialisable.SerialisableBase.__init__( self )
self._service_key = service_key
self._write_autocomplete_tag_domain = self._service_key
self._override_write_autocomplete_file_domain = True
if service_key == CC.DEFAULT_LOCAL_TAG_SERVICE_KEY:
self._write_autocomplete_file_domain = CC.LOCAL_FILE_SERVICE_KEY
else:
self._write_autocomplete_file_domain = CC.COMBINED_FILE_SERVICE_KEY
self._search_namespaces_into_full_tags = False
self._namespace_bare_fetch_all_allowed = False
self._namespace_fetch_all_allowed = False
self._fetch_all_allowed = False
def _GetSerialisableInfo( self ):
serialisable_service_key = self._service_key.hex()
serialisable_write_autocomplete_tag_domain = self._write_autocomplete_tag_domain.hex()
serialisable_write_autocomplete_file_domain = self._write_autocomplete_file_domain.hex()
serialisable_info = [
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
self._override_write_autocomplete_file_domain,
serialisable_write_autocomplete_file_domain,
self._search_namespaces_into_full_tags,
self._namespace_bare_fetch_all_allowed,
self._namespace_fetch_all_allowed,
self._fetch_all_allowed
]
return serialisable_info
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
[
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
self._override_write_autocomplete_file_domain,
serialisable_write_autocomplete_file_domain,
self._search_namespaces_into_full_tags,
self._namespace_bare_fetch_all_allowed,
self._namespace_fetch_all_allowed,
self._fetch_all_allowed
] = serialisable_info
self._service_key = bytes.fromhex( serialisable_service_key )
self._write_autocomplete_tag_domain = bytes.fromhex( serialisable_write_autocomplete_tag_domain )
self._write_autocomplete_file_domain = bytes.fromhex( serialisable_write_autocomplete_file_domain )
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
[
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
override_write_autocomplete_file_domain,
serialisable_write_autocomplete_file_domain,
search_namespaces_into_full_tags,
namespace_fetch_all_allowed,
fetch_all_allowed
] = old_serialisable_info
namespace_bare_fetch_all_allowed = False
new_serialisable_info = [
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
override_write_autocomplete_file_domain,
serialisable_write_autocomplete_file_domain,
search_namespaces_into_full_tags,
namespace_bare_fetch_all_allowed,
namespace_fetch_all_allowed,
fetch_all_allowed
]
return ( 2, new_serialisable_info )
def FetchAllAllowed( self ):
return self._fetch_all_allowed
def GetServiceKey( self ):
return self._service_key
def GetWriteAutocompleteFileDomain( self ):
return self._write_autocomplete_file_domain
def GetWriteAutocompleteServiceKeys( self, file_service_key: bytes ):
tag_service_key = self._service_key
if self._service_key != CC.COMBINED_TAG_SERVICE_KEY:
if self._override_write_autocomplete_file_domain:
file_service_key = self._write_autocomplete_file_domain
tag_service_key = self._write_autocomplete_tag_domain
if file_service_key == CC.COMBINED_FILE_SERVICE_KEY and tag_service_key == CC.COMBINED_TAG_SERVICE_KEY: # ruh roh
file_service_key = CC.COMBINED_LOCAL_FILE_SERVICE_KEY
return ( file_service_key, tag_service_key )
def GetWriteAutocompleteTagDomain( self ):
return self._write_autocomplete_tag_domain
def NamespaceBareFetchAllAllowed( self ):
return self._namespace_bare_fetch_all_allowed
def NamespaceFetchAllAllowed( self ):
return self._namespace_fetch_all_allowed
def OverridesWriteAutocompleteFileDomain( self ):
return self._override_write_autocomplete_file_domain
def SearchNamespacesIntoFullTags( self ):
return self._search_namespaces_into_full_tags
def SetTuple( self,
write_autocomplete_tag_domain: bytes,
override_write_autocomplete_file_domain: bool,
write_autocomplete_file_domain: bytes,
search_namespaces_into_full_tags: bool,
namespace_bare_fetch_all_allowed: bool,
namespace_fetch_all_allowed: bool,
fetch_all_allowed: bool
):
self._write_autocomplete_tag_domain = write_autocomplete_tag_domain
self._override_write_autocomplete_file_domain = override_write_autocomplete_file_domain
self._write_autocomplete_file_domain = write_autocomplete_file_domain
self._search_namespaces_into_full_tags = search_namespaces_into_full_tags
self._namespace_bare_fetch_all_allowed = namespace_bare_fetch_all_allowed
self._namespace_fetch_all_allowed = namespace_fetch_all_allowed
self._fetch_all_allowed = fetch_all_allowed
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_TAG_AUTOCOMPLETE_OPTIONS ] = TagAutocompleteOptions
class TagDisplayManager( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_TAG_DISPLAY_MANAGER
SERIALISABLE_NAME = 'Tag Display Manager'
SERIALISABLE_VERSION = 2
def __init__( self ):
HydrusSerialisable.SerialisableBase.__init__( self )
service_keys_to_tag_filters_defaultdict = lambda: collections.defaultdict( TagFilter )
self._tag_display_types_to_service_keys_to_tag_filters = collections.defaultdict( service_keys_to_tag_filters_defaultdict )
self._tag_service_keys_to_tag_autocomplete_options = dict()
self._lock = threading.Lock()
self._dirty = False
def _GetSerialisableInfo( self ):
serialisable_tag_display_types_to_service_keys_to_tag_filters = []
for ( tag_display_type, service_keys_to_tag_filters ) in self._tag_display_types_to_service_keys_to_tag_filters.items():
serialisable_service_keys_to_tag_filters = [ ( service_key.hex(), tag_filter.GetSerialisableTuple() ) for ( service_key, tag_filter ) in service_keys_to_tag_filters.items() ]
serialisable_tag_display_types_to_service_keys_to_tag_filters.append( ( tag_display_type, serialisable_service_keys_to_tag_filters ) )
serialisable_tag_autocomplete_options = HydrusSerialisable.SerialisableList( self._tag_service_keys_to_tag_autocomplete_options.values() ).GetSerialisableTuple()
serialisable_info = [
serialisable_tag_display_types_to_service_keys_to_tag_filters,
serialisable_tag_autocomplete_options
]
return serialisable_info
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
[
serialisable_tag_display_types_to_service_keys_to_tag_filters,
serialisable_tag_autocomplete_options
] = serialisable_info
for ( tag_display_type, serialisable_service_keys_to_tag_filters ) in serialisable_tag_display_types_to_service_keys_to_tag_filters:
for ( serialisable_service_key, serialisable_tag_filter ) in serialisable_service_keys_to_tag_filters:
service_key = bytes.fromhex( serialisable_service_key )
tag_filter = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_tag_filter )
self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ] = tag_filter
self._tag_service_keys_to_tag_autocomplete_options = { tag_autocomplete_options.GetServiceKey() : tag_autocomplete_options for tag_autocomplete_options in HydrusSerialisable.CreateFromSerialisableTuple( serialisable_tag_autocomplete_options ) }
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
serialisable_tag_display_types_to_service_keys_to_tag_filters = old_serialisable_info
tag_autocomplete_options_list = HydrusSerialisable.SerialisableList()
new_serialisable_info = [
serialisable_tag_display_types_to_service_keys_to_tag_filters,
tag_autocomplete_options_list.GetSerialisableTuple()
]
return ( 2, new_serialisable_info )
def SetClean( self ):
with self._lock:
self._dirty = False
def SetDirty( self ):
with self._lock:
self._dirty = True
def FilterTags( self, tag_display_type, service_key, tags ):
with self._lock:
if service_key in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
tag_filter = self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ]
tags = tag_filter.Filter( tags )
if service_key != CC.COMBINED_TAG_SERVICE_KEY and CC.COMBINED_TAG_SERVICE_KEY in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
tag_filter = self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ CC.COMBINED_TAG_SERVICE_KEY ]
tags = tag_filter.Filter( tags )
return tags
def FiltersTags( self, tag_display_type, service_key ):
with self._lock:
if service_key in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
return True
if service_key != CC.COMBINED_TAG_SERVICE_KEY and CC.COMBINED_TAG_SERVICE_KEY in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
return True
return False
def GetTagAutocompleteOptions( self, service_key: bytes ):
with self._lock:
if service_key not in self._tag_service_keys_to_tag_autocomplete_options:
tag_autocomplete_options = TagAutocompleteOptions( service_key )
self._tag_service_keys_to_tag_autocomplete_options[ service_key ] = tag_autocomplete_options
return self._tag_service_keys_to_tag_autocomplete_options[ service_key ]
def GetTagFilter( self, tag_display_type, service_key ):
with self._lock:
return self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ].Duplicate()
def HideTag( self, tag_display_type, service_key, tag ):
with self._lock:
tag_filter = self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ]
tag_filter.SetRule( tag, CC.FILTER_BLACKLIST )
self._dirty = True
def IsDirty( self ):
with self._lock:
return self._dirty
def SetTagAutocompleteOptions( self, tag_autocomplete_options: TagAutocompleteOptions ):
with self._lock:
self._tag_service_keys_to_tag_autocomplete_options[ tag_autocomplete_options.GetServiceKey() ] = tag_autocomplete_options
def SetTagFilter( self, tag_display_type, service_key, tag_filter ):
with self._lock:
if tag_filter.AllowsEverything():
if service_key in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
del self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ]
self._dirty = True
else:
self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ] = tag_filter
self._dirty = True
def TagOK( self, tag_display_type, service_key, tag ):
return len( self.FilterTags( tag_display_type, service_key, ( tag, ) ) ) > 0
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_TAG_DISPLAY_MANAGER ] = TagDisplayManager
TAG_DISPLAY_STORAGE = 0
TAG_DISPLAY_SIBLINGS_AND_PARENTS = 1
TAG_DISPLAY_SINGLE_MEDIA = 2
TAG_DISPLAY_SELECTION_LIST = 3
class TagFilter( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_TAG_FILTER
@ -950,90 +540,3 @@ class TagFilter( HydrusSerialisable.SerialisableBase ):
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_TAG_FILTER ] = TagFilter
class TagSiblingsStructure( object ):
def __init__( self ):
self._bad_tags_to_good_tags = {}
self._bad_tags_to_ideal_tags = {}
self._ideal_tags_to_all_worse_tags = collections.defaultdict( set )
# some sort of structure for 'bad cycles' so we can later raise these to the user to fix
def AddPair( self, bad_tag: object, good_tag: object ):
# disallowed siblings are:
# A -> A
# larger loops
# A -> C when A -> B already exists
if bad_tag == good_tag:
return
if bad_tag in self._bad_tags_to_good_tags:
return
joining_existing_chain = good_tag in self._bad_tags_to_ideal_tags
extending_existing_chain = bad_tag in self._ideal_tags_to_all_worse_tags
if extending_existing_chain and joining_existing_chain:
joined_chain_ideal = self._bad_tags_to_ideal_tags
if joined_chain_ideal == bad_tag:
# we found a cycle, as the ideal of the chain we are joining is our bad tag
# basically the chain we are joining and the chain we are extending are the same one
return
# now compute our ideal
ideal_tags_that_need_updating = set()
if joining_existing_chain:
# our ideal will be the end of that chain
ideal_tag = self._bad_tags_to_ideal_tags[ good_tag ]
else:
ideal_tag = good_tag
self._bad_tags_to_good_tags[ bad_tag ] = good_tag
self._bad_tags_to_ideal_tags[ bad_tag ] = ideal_tag
self._ideal_tags_to_all_worse_tags[ ideal_tag ].add( bad_tag )
if extending_existing_chain:
# the existing chain needs its ideal updating
old_ideal_tag = bad_tag
bad_tags_that_need_updating = self._ideal_tags_to_all_worse_tags[ old_ideal_tag ]
for bad_tag_that_needs_updating in bad_tags_that_need_updating:
self._bad_tags_to_ideal_tags[ bad_tag_that_needs_updating ] = ideal_tag
self._ideal_tags_to_all_worse_tags[ ideal_tag ].update( bad_tags_that_need_updating )
del self._ideal_tags_to_all_worse_tags[ old_ideal_tag ]
def GetBadTagsToIdealTags( self ):
return self._bad_tags_to_ideal_tags

View File

@ -0,0 +1,646 @@
import collections
import threading
import typing
from hydrus.core import HydrusConstants as HC
from hydrus.core import HydrusSerialisable
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientServices
from hydrus.client.metadata import ClientTags
class TagAutocompleteOptions( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_TAG_AUTOCOMPLETE_OPTIONS
SERIALISABLE_NAME = 'Tag Autocomplete Options'
SERIALISABLE_VERSION = 2
def __init__( self, service_key: typing.Optional[ bytes ] = None ):
if service_key is None:
service_key = CC.COMBINED_TAG_SERVICE_KEY
HydrusSerialisable.SerialisableBase.__init__( self )
self._service_key = service_key
self._write_autocomplete_tag_domain = self._service_key
self._override_write_autocomplete_file_domain = True
if service_key == CC.DEFAULT_LOCAL_TAG_SERVICE_KEY:
self._write_autocomplete_file_domain = CC.LOCAL_FILE_SERVICE_KEY
else:
self._write_autocomplete_file_domain = CC.COMBINED_FILE_SERVICE_KEY
self._search_namespaces_into_full_tags = False
self._namespace_bare_fetch_all_allowed = False
self._namespace_fetch_all_allowed = False
self._fetch_all_allowed = False
def _GetSerialisableInfo( self ):
serialisable_service_key = self._service_key.hex()
serialisable_write_autocomplete_tag_domain = self._write_autocomplete_tag_domain.hex()
serialisable_write_autocomplete_file_domain = self._write_autocomplete_file_domain.hex()
serialisable_info = [
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
self._override_write_autocomplete_file_domain,
serialisable_write_autocomplete_file_domain,
self._search_namespaces_into_full_tags,
self._namespace_bare_fetch_all_allowed,
self._namespace_fetch_all_allowed,
self._fetch_all_allowed
]
return serialisable_info
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
[
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
self._override_write_autocomplete_file_domain,
serialisable_write_autocomplete_file_domain,
self._search_namespaces_into_full_tags,
self._namespace_bare_fetch_all_allowed,
self._namespace_fetch_all_allowed,
self._fetch_all_allowed
] = serialisable_info
self._service_key = bytes.fromhex( serialisable_service_key )
self._write_autocomplete_tag_domain = bytes.fromhex( serialisable_write_autocomplete_tag_domain )
self._write_autocomplete_file_domain = bytes.fromhex( serialisable_write_autocomplete_file_domain )
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
[
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
override_write_autocomplete_file_domain,
serialisable_write_autocomplete_file_domain,
search_namespaces_into_full_tags,
namespace_fetch_all_allowed,
fetch_all_allowed
] = old_serialisable_info
namespace_bare_fetch_all_allowed = False
new_serialisable_info = [
serialisable_service_key,
serialisable_write_autocomplete_tag_domain,
override_write_autocomplete_file_domain,
serialisable_write_autocomplete_file_domain,
search_namespaces_into_full_tags,
namespace_bare_fetch_all_allowed,
namespace_fetch_all_allowed,
fetch_all_allowed
]
return ( 2, new_serialisable_info )
def FetchAllAllowed( self ):
return self._fetch_all_allowed
def GetServiceKey( self ):
return self._service_key
def GetWriteAutocompleteFileDomain( self ):
return self._write_autocomplete_file_domain
def GetWriteAutocompleteServiceKeys( self, file_service_key: bytes ):
tag_service_key = self._service_key
if self._service_key != CC.COMBINED_TAG_SERVICE_KEY:
if self._override_write_autocomplete_file_domain:
file_service_key = self._write_autocomplete_file_domain
tag_service_key = self._write_autocomplete_tag_domain
if file_service_key == CC.COMBINED_FILE_SERVICE_KEY and tag_service_key == CC.COMBINED_TAG_SERVICE_KEY: # ruh roh
file_service_key = CC.COMBINED_LOCAL_FILE_SERVICE_KEY
return ( file_service_key, tag_service_key )
def GetWriteAutocompleteTagDomain( self ):
return self._write_autocomplete_tag_domain
def NamespaceBareFetchAllAllowed( self ):
return self._namespace_bare_fetch_all_allowed
def NamespaceFetchAllAllowed( self ):
return self._namespace_fetch_all_allowed
def OverridesWriteAutocompleteFileDomain( self ):
return self._override_write_autocomplete_file_domain
def SearchNamespacesIntoFullTags( self ):
return self._search_namespaces_into_full_tags
def SetTuple( self,
write_autocomplete_tag_domain: bytes,
override_write_autocomplete_file_domain: bool,
write_autocomplete_file_domain: bytes,
search_namespaces_into_full_tags: bool,
namespace_bare_fetch_all_allowed: bool,
namespace_fetch_all_allowed: bool,
fetch_all_allowed: bool
):
self._write_autocomplete_tag_domain = write_autocomplete_tag_domain
self._override_write_autocomplete_file_domain = override_write_autocomplete_file_domain
self._write_autocomplete_file_domain = write_autocomplete_file_domain
self._search_namespaces_into_full_tags = search_namespaces_into_full_tags
self._namespace_bare_fetch_all_allowed = namespace_bare_fetch_all_allowed
self._namespace_fetch_all_allowed = namespace_fetch_all_allowed
self._fetch_all_allowed = fetch_all_allowed
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_TAG_AUTOCOMPLETE_OPTIONS ] = TagAutocompleteOptions
class TagDisplayManager( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_TAG_DISPLAY_MANAGER
SERIALISABLE_NAME = 'Tag Display Manager'
SERIALISABLE_VERSION = 3
def __init__( self ):
HydrusSerialisable.SerialisableBase.__init__( self )
service_keys_to_tag_filters_defaultdict = lambda: collections.defaultdict( ClientTags.TagFilter )
self._tag_display_types_to_service_keys_to_tag_filters = collections.defaultdict( service_keys_to_tag_filters_defaultdict )
self._tag_service_keys_to_tag_autocomplete_options = dict()
self._service_keys_to_ordered_sibling_service_keys = collections.defaultdict( list )
self._service_keys_to_ordered_parent_service_keys = collections.defaultdict( list )
self._lock = threading.Lock()
self._dirty = False
def _GetSerialisableInfo( self ):
serialisable_tag_display_types_to_service_keys_to_tag_filters = []
for ( tag_display_type, service_keys_to_tag_filters ) in self._tag_display_types_to_service_keys_to_tag_filters.items():
serialisable_service_keys_to_tag_filters = [ ( service_key.hex(), tag_filter.GetSerialisableTuple() ) for ( service_key, tag_filter ) in service_keys_to_tag_filters.items() ]
serialisable_tag_display_types_to_service_keys_to_tag_filters.append( ( tag_display_type, serialisable_service_keys_to_tag_filters ) )
serialisable_tag_autocomplete_options = HydrusSerialisable.SerialisableList( self._tag_service_keys_to_tag_autocomplete_options.values() ).GetSerialisableTuple()
serialisable_service_keys_to_ordered_sibling_service_keys = HydrusSerialisable.SerialisableBytesDictionary( self._service_keys_to_ordered_sibling_service_keys ).GetSerialisableTuple()
serialisable_service_keys_to_ordered_parent_service_keys = HydrusSerialisable.SerialisableBytesDictionary( self._service_keys_to_ordered_parent_service_keys ).GetSerialisableTuple()
serialisable_info = [
serialisable_tag_display_types_to_service_keys_to_tag_filters,
serialisable_tag_autocomplete_options,
serialisable_service_keys_to_ordered_sibling_service_keys,
serialisable_service_keys_to_ordered_parent_service_keys
]
return serialisable_info
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
[
serialisable_tag_display_types_to_service_keys_to_tag_filters,
serialisable_tag_autocomplete_options,
serialisable_service_keys_to_ordered_sibling_service_keys,
serialisable_service_keys_to_ordered_parent_service_keys
] = serialisable_info
for ( tag_display_type, serialisable_service_keys_to_tag_filters ) in serialisable_tag_display_types_to_service_keys_to_tag_filters:
for ( serialisable_service_key, serialisable_tag_filter ) in serialisable_service_keys_to_tag_filters:
service_key = bytes.fromhex( serialisable_service_key )
tag_filter = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_tag_filter )
self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ] = tag_filter
self._tag_service_keys_to_tag_autocomplete_options = { tag_autocomplete_options.GetServiceKey() : tag_autocomplete_options for tag_autocomplete_options in HydrusSerialisable.CreateFromSerialisableTuple( serialisable_tag_autocomplete_options ) }
self._service_keys_to_ordered_sibling_service_keys = collections.defaultdict( list )
self._service_keys_to_ordered_sibling_service_keys.update( HydrusSerialisable.CreateFromSerialisableTuple( serialisable_service_keys_to_ordered_sibling_service_keys ) )
self._service_keys_to_ordered_parent_service_keys = collections.defaultdict( list )
self._service_keys_to_ordered_sibling_service_keys.update( HydrusSerialisable.CreateFromSerialisableTuple( serialisable_service_keys_to_ordered_parent_service_keys ) )
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
serialisable_tag_display_types_to_service_keys_to_tag_filters = old_serialisable_info
tag_autocomplete_options_list = HydrusSerialisable.SerialisableList()
new_serialisable_info = [
serialisable_tag_display_types_to_service_keys_to_tag_filters,
tag_autocomplete_options_list.GetSerialisableTuple()
]
return ( 2, new_serialisable_info )
if version == 2:
[
serialisable_tag_display_types_to_service_keys_to_tag_filters,
serialisable_tag_autocomplete_options
] = old_serialisable_info
service_keys_to_ordered_sibling_service_keys = collections.defaultdict( list )
service_keys_to_ordered_parent_service_keys = collections.defaultdict( list )
serialisable_service_keys_to_ordered_sibling_service_keys = HydrusSerialisable.SerialisableBytesDictionary( service_keys_to_ordered_sibling_service_keys ).GetSerialisableTuple()
serialisable_service_keys_to_ordered_parent_service_keys = HydrusSerialisable.SerialisableBytesDictionary( service_keys_to_ordered_parent_service_keys ).GetSerialisableTuple()
new_serialisable_info = [
serialisable_tag_display_types_to_service_keys_to_tag_filters,
serialisable_tag_autocomplete_options,
serialisable_service_keys_to_ordered_sibling_service_keys,
serialisable_service_keys_to_ordered_parent_service_keys
]
return ( 3, new_serialisable_info )
def AddNewTagServiceKeys( self, tag_service_keys ):
with self._lock:
self._service_keys_to_ordered_sibling_service_keys[ CC.COMBINED_TAG_SERVICE_KEY ].extend( tag_service_keys )
def ClearTagDisplayOptions( self ):
with self._lock:
service_keys_to_tag_filters_defaultdict = lambda: collections.defaultdict( ClientTags.TagFilter )
self._tag_display_types_to_service_keys_to_tag_filters = collections.defaultdict( service_keys_to_tag_filters_defaultdict )
self._tag_service_keys_to_tag_autocomplete_options = dict()
def SetClean( self ):
with self._lock:
self._dirty = False
def SetDirty( self ):
with self._lock:
self._dirty = True
def FilterTags( self, tag_display_type, service_key, tags ):
with self._lock:
if service_key in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
tag_filter = self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ]
tags = tag_filter.Filter( tags )
if service_key != CC.COMBINED_TAG_SERVICE_KEY and CC.COMBINED_TAG_SERVICE_KEY in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
tag_filter = self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ CC.COMBINED_TAG_SERVICE_KEY ]
tags = tag_filter.Filter( tags )
return tags
def FiltersTags( self, tag_display_type, service_key ):
with self._lock:
if service_key in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
return True
if service_key != CC.COMBINED_TAG_SERVICE_KEY and CC.COMBINED_TAG_SERVICE_KEY in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
return True
return False
def GetParentServiceKeys( self, services_manager: ClientServices.ServicesManager, requested_service_key: bytes ):
with self._lock:
if requested_service_key not in self._service_keys_to_ordered_parent_service_keys:
self._service_keys_to_ordered_parent_service_keys[ requested_service_key ] = [ requested_service_key ]
ordered_service_keys = self._service_keys_to_ordered_parent_service_keys[ requested_service_key ]
valid_ordered_service_keys = [ service_key for service_key in ordered_service_keys if services_manager.ServiceExists( service_key ) ]
if len( valid_ordered_service_keys ) != len( ordered_service_keys ):
self._service_keys_to_ordered_parent_service_keys[ requested_service_key ] = valid_ordered_service_keys
self._dirty = True
return valid_ordered_service_keys
def GetSiblingServiceKeys( self, services_manager: ClientServices.ServicesManager, requested_service_key: bytes ):
local_service_keys = services_manager.GetServiceKeys( ( HC.LOCAL_TAG, ) )
tag_repo_service_keys = services_manager.GetServiceKeys( ( HC.TAG_REPOSITORY, ) )
all_current_tag_service_keys = set( local_service_keys )
all_current_tag_service_keys.update( tag_repo_service_keys )
with self._lock:
if requested_service_key not in self._service_keys_to_ordered_sibling_service_keys:
if requested_service_key == CC.COMBINED_TAG_SERVICE_KEY:
service_keys = list( local_service_keys )
service_keys.extend( tag_repo_service_keys )
else:
service_keys = [ requested_service_key ]
self._service_keys_to_ordered_sibling_service_keys[ requested_service_key ] = service_keys
ordered_service_keys = self._service_keys_to_ordered_sibling_service_keys[ requested_service_key ]
valid_ordered_service_keys = list( filter( lambda sk: sk in all_current_tag_service_keys, ordered_service_keys ) )
if len( valid_ordered_service_keys ) != len( ordered_service_keys ):
self._service_keys_to_ordered_sibling_service_keys[ requested_service_key ] = valid_ordered_service_keys
self._dirty = True
return valid_ordered_service_keys
def GetTagAutocompleteOptions( self, service_key: bytes ):
with self._lock:
if service_key not in self._tag_service_keys_to_tag_autocomplete_options:
tag_autocomplete_options = TagAutocompleteOptions( service_key )
self._tag_service_keys_to_tag_autocomplete_options[ service_key ] = tag_autocomplete_options
return self._tag_service_keys_to_tag_autocomplete_options[ service_key ]
def GetTagFilter( self, tag_display_type, service_key ):
with self._lock:
return self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ].Duplicate()
def HideTag( self, tag_display_type, service_key, tag ):
with self._lock:
tag_filter = self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ]
tag_filter.SetRule( tag, CC.FILTER_BLACKLIST )
self._dirty = True
def IsDirty( self ):
with self._lock:
return self._dirty
def SetParentServiceKeys( self, service_keys_to_ordered_parent_service_keys ):
with self._lock:
self._service_keys_to_ordered_parent_service_keys = collections.defaultdict( list )
self._service_keys_to_ordered_parent_service_keys.update( service_keys_to_ordered_parent_service_keys )
self._dirty = True
def SetSiblingServiceKeys( self, service_keys_to_ordered_sibling_service_keys ):
with self._lock:
self._service_keys_to_ordered_sibling_service_keys = collections.defaultdict( list )
self._service_keys_to_ordered_sibling_service_keys.update( service_keys_to_ordered_sibling_service_keys )
self._dirty = True
def SetTagAutocompleteOptions( self, tag_autocomplete_options: TagAutocompleteOptions ):
with self._lock:
self._tag_service_keys_to_tag_autocomplete_options[ tag_autocomplete_options.GetServiceKey() ] = tag_autocomplete_options
def SetTagFilter( self, tag_display_type, service_key, tag_filter ):
with self._lock:
if tag_filter.AllowsEverything():
if service_key in self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ]:
del self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ]
self._dirty = True
else:
self._tag_display_types_to_service_keys_to_tag_filters[ tag_display_type ][ service_key ] = tag_filter
self._dirty = True
def TagOK( self, tag_display_type, service_key, tag ):
return len( self.FilterTags( tag_display_type, service_key, ( tag, ) ) ) > 0
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_TAG_DISPLAY_MANAGER ] = TagDisplayManager
class TagSiblingsStructure( object ):
def __init__( self ):
self._bad_tags_to_good_tags = {}
self._bad_tags_to_ideal_tags = {}
self._ideal_tags_to_all_worse_tags = collections.defaultdict( set )
# some sort of structure for 'bad cycles' so we can later raise these to the user to fix
def AddPair( self, bad_tag: object, good_tag: object ):
# disallowed siblings are:
# A -> A
# larger loops
# A -> C when A -> B already exists
if bad_tag == good_tag:
return
if bad_tag in self._bad_tags_to_good_tags:
return
joining_existing_chain = good_tag in self._bad_tags_to_ideal_tags
extending_existing_chain = bad_tag in self._ideal_tags_to_all_worse_tags
if extending_existing_chain and joining_existing_chain:
joined_chain_ideal = self._bad_tags_to_ideal_tags
if joined_chain_ideal == bad_tag:
# we found a cycle, as the ideal of the chain we are joining is our bad tag
# basically the chain we are joining and the chain we are extending are the same one
return
# now compute our ideal
ideal_tags_that_need_updating = set()
if joining_existing_chain:
# our ideal will be the end of that chain
ideal_tag = self._bad_tags_to_ideal_tags[ good_tag ]
else:
ideal_tag = good_tag
self._bad_tags_to_good_tags[ bad_tag ] = good_tag
self._bad_tags_to_ideal_tags[ bad_tag ] = ideal_tag
self._ideal_tags_to_all_worse_tags[ ideal_tag ].add( bad_tag )
if extending_existing_chain:
# the existing chain needs its ideal updating
old_ideal_tag = bad_tag
bad_tags_that_need_updating = self._ideal_tags_to_all_worse_tags[ old_ideal_tag ]
for bad_tag_that_needs_updating in bad_tags_that_need_updating:
self._bad_tags_to_ideal_tags[ bad_tag_that_needs_updating ] = ideal_tag
self._ideal_tags_to_all_worse_tags[ ideal_tag ].update( bad_tags_that_need_updating )
del self._ideal_tags_to_all_worse_tags[ old_ideal_tag ]
def GetBadTagsToIdealTags( self ):
return self._bad_tags_to_ideal_tags

View File

@ -70,7 +70,7 @@ options = {}
# Misc
NETWORK_VERSION = 18
SOFTWARE_VERSION = 406
SOFTWARE_VERSION = 407
CLIENT_API_VERSION = 13
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -366,7 +366,7 @@ def ConvertTimestampToPrettyExpires( timestamp ):
return 'unparseable time {}'.format( timestamp )
def ConvertTimestampToPrettyTime( timestamp, in_gmt = False, include_24h_time = True ):
def ConvertTimestampToPrettyTime( timestamp, in_utc = False, include_24h_time = True ):
if timestamp is None:
@ -375,20 +375,20 @@ def ConvertTimestampToPrettyTime( timestamp, in_gmt = False, include_24h_time =
if include_24h_time:
phrase = '%Y/%m/%d %H:%M:%S'
phrase = '%Y-%m-%d %H:%M:%S'
else:
phrase = '%Y/%m/%d'
phrase = '%Y-%m-%d'
try:
if in_gmt:
if in_utc:
struct_time = time.gmtime( timestamp )
phrase = phrase + ' GMT'
phrase = phrase + ' UTC'
else:

View File

@ -1500,7 +1500,7 @@ class DB( HydrusDB.HydrusDB ):
( name, ) = self._c.execute( 'SELECT name FROM services WHERE service_id = ?;', ( service_id, ) ).fetchone()
HydrusData.Print( 'Creating update for ' + repr( name ) + ' from ' + HydrusData.ConvertTimestampToPrettyTime( begin, in_gmt = True ) + ' to ' + HydrusData.ConvertTimestampToPrettyTime( end, in_gmt = True ) )
HydrusData.Print( 'Creating update for ' + repr( name ) + ' from ' + HydrusData.ConvertTimestampToPrettyTime( begin, in_utc = True ) + ' to ' + HydrusData.ConvertTimestampToPrettyTime( end, in_utc = True ) )
updates = self._RepositoryGenerateUpdates( service_id, begin, end )

View File

@ -25,9 +25,9 @@ from hydrus.client import ClientLocalServerResources
from hydrus.client import ClientManagers
from hydrus.client import ClientSearch
from hydrus.client import ClientServices
from hydrus.client import ClientTags
from hydrus.client.media import ClientMediaManagers
from hydrus.client.media import ClientMediaResult
from hydrus.client.metadata import ClientTags
class TestClientAPI( unittest.TestCase ):
@ -723,15 +723,11 @@ class TestClientAPI( unittest.TestCase ):
old_sib = HG.test_controller.tag_siblings_manager
old_par = HG.test_controller.tag_parents_manager
tag_siblings = collections.defaultdict( HydrusData.default_dict_set )
first_dict = HydrusData.default_dict_set()
first_dict[ HC.CONTENT_STATUS_CURRENT ] = { ( 'test', 'muh test' ) }
first_dict[ HC.CONTENT_STATUS_CURRENT ] = [ ( 'test', 'muh test' ) ]
tag_siblings[ CC.DEFAULT_LOCAL_TAG_SERVICE_KEY ] = first_dict
HG.test_controller.SetRead( 'tag_siblings', tag_siblings )
HG.test_controller.SetRead( 'tag_siblings', first_dict )
tag_parents = collections.defaultdict( HydrusData.default_dict_set )

View File

@ -36,7 +36,7 @@ class TestManagers( unittest.TestCase ):
HG.test_controller.SetRead( 'services', services )
services_manager = ClientManagers.ServicesManager( HG.client_controller )
services_manager = ClientServices.ServicesManager( HG.client_controller )
#

View File

@ -14,12 +14,12 @@ from hydrus.client import ClientDefaults
from hydrus.client import ClientExporting
from hydrus.client import ClientSearch
from hydrus.client import ClientServices
from hydrus.client import ClientTags
from hydrus.client.gui import ClientGUIManagement
from hydrus.client.gui import ClientGUIPages
from hydrus.client.importing import ClientImportLocal
from hydrus.client.importing import ClientImportOptions
from hydrus.client.importing import ClientImportFileSeeds
from hydrus.client.metadata import ClientTags
from hydrus.test import TestController

View File

@ -11,12 +11,12 @@ from hydrus.core import HydrusExceptions
from hydrus.core import HydrusGlobals as HG
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientTags
from hydrus.client.importing import ClientImportFileSeeds
from hydrus.client.importing import ClientImportOptions
from hydrus.client.media import ClientMedia
from hydrus.client.media import ClientMediaManagers
from hydrus.client.media import ClientMediaResult
from hydrus.client.metadata import ClientTags
class TestCheckerOptions( unittest.TestCase ):

View File

@ -15,10 +15,10 @@ from hydrus.client import ClientDB
from hydrus.client import ClientManagers
from hydrus.client import ClientMigration
from hydrus.client import ClientServices
from hydrus.client import ClientTags
from hydrus.client.importing import ClientImportFileSeeds
from hydrus.client.importing import ClientImportOptions
from hydrus.client.media import ClientMediaResultCache
from hydrus.client.metadata import ClientTags
current_tag_pool = [ 'blonde hair', 'blue eyes', 'bodysuit', 'character:samus aran', 'series:metroid', 'studio:nintendo' ]
pending_tag_pool = [ 'favourites', 'kino', 'brown shirt', 'huge knees' ]
@ -155,7 +155,7 @@ class TestMigration( unittest.TestCase ):
self.WriteSynchronous( 'update_services', services )
self.services_manager = ClientManagers.ServicesManager( self )
self.services_manager = ClientServices.ServicesManager( self )
def _do_fake_imports( self ):

View File

@ -8,8 +8,9 @@ from hydrus.core import HydrusGlobals as HG
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientManagers
from hydrus.client import ClientSearch
from hydrus.client import ClientTags
from hydrus.client.media import ClientMediaManagers
from hydrus.client.metadata import ClientTags
from hydrus.client.metadata import ClientTagsHandling
class TestMergeTagsManagers( unittest.TestCase ):
@ -378,7 +379,7 @@ class TestTagDisplayManager( unittest.TestCase ):
filter_pages.SetRule( 'page:', CC.FILTER_BLACKLIST )
tag_display_manager = ClientTags.TagDisplayManager()
tag_display_manager = ClientTagsHandling.TagDisplayManager()
tag_display_manager.SetTagFilter( ClientTags.TAG_DISPLAY_SELECTION_LIST, CC.COMBINED_TAG_SERVICE_KEY, filter_pages )
@ -435,7 +436,7 @@ class TestTagObjects( unittest.TestCase ):
self.assertEqual( pat.GetAddTagPredicate(), values[0] )
tag_autocomplete_options = ClientTags.TagAutocompleteOptions( CC.COMBINED_TAG_SERVICE_KEY )
tag_autocomplete_options = ClientTagsHandling.TagAutocompleteOptions( CC.COMBINED_TAG_SERVICE_KEY )
parsed_autocomplete_text = ClientSearch.ParsedAutocompleteText( '', tag_autocomplete_options, True )
@ -579,7 +580,7 @@ class TestTagObjects( unittest.TestCase ):
#
#
tag_autocomplete_options = ClientTags.TagAutocompleteOptions( CC.COMBINED_TAG_SERVICE_KEY )
tag_autocomplete_options = ClientTagsHandling.TagAutocompleteOptions( CC.COMBINED_TAG_SERVICE_KEY )
search_namespaces_into_full_tags = True
namespace_bare_fetch_all_allowed = False
@ -639,7 +640,7 @@ class TestTagObjects( unittest.TestCase ):
#
#
tag_autocomplete_options = ClientTags.TagAutocompleteOptions( CC.COMBINED_TAG_SERVICE_KEY )
tag_autocomplete_options = ClientTagsHandling.TagAutocompleteOptions( CC.COMBINED_TAG_SERVICE_KEY )
search_namespaces_into_full_tags = False
namespace_bare_fetch_all_allowed = True
@ -699,7 +700,7 @@ class TestTagObjects( unittest.TestCase ):
#
#
tag_autocomplete_options = ClientTags.TagAutocompleteOptions( CC.COMBINED_TAG_SERVICE_KEY )
tag_autocomplete_options = ClientTagsHandling.TagAutocompleteOptions( CC.COMBINED_TAG_SERVICE_KEY )
search_namespaces_into_full_tags = False
namespace_bare_fetch_all_allowed = False
@ -759,7 +760,7 @@ class TestTagObjects( unittest.TestCase ):
#
#
tag_autocomplete_options = ClientTags.TagAutocompleteOptions( CC.COMBINED_TAG_SERVICE_KEY )
tag_autocomplete_options = ClientTagsHandling.TagAutocompleteOptions( CC.COMBINED_TAG_SERVICE_KEY )
search_namespaces_into_full_tags = False
namespace_bare_fetch_all_allowed = False
@ -1335,24 +1336,20 @@ class TestTagSiblings( unittest.TestCase ):
cls._first_key = CC.DEFAULT_LOCAL_TAG_SERVICE_KEY
cls._second_key = HG.test_controller.example_tag_repo_service_key
tag_siblings = collections.defaultdict( HydrusData.default_dict_set )
first_dict = HydrusData.default_dict_list()
first_dict = HydrusData.default_dict_set()
first_dict[ HC.CONTENT_STATUS_CURRENT ] = [ ( 'ishygddt', 'i sure hope you guys don\'t do that' ), ( 'character:rei ayanami', 'character:ayanami rei' ), ( 'tree_1', 'tree_3' ), ( 'tree_2', 'tree_3' ), ( 'tree_3', 'tree_5' ), ( 'tree_4', 'tree_5' ), ( 'tree_5', 'tree_6' ), ( 'current_a', 'current_b' ), ( 'chain_a', 'chain_b' ), ( 'chain_b', 'chain_c' ), ( 'closed_loop', 'closed_loop' ), ( 'loop_a', 'loop_b' ), ( 'loop_b', 'loop_c' ), ( 'loop_c', 'loop_a' ) ]
first_dict[ HC.CONTENT_STATUS_DELETED ] = [ ( 'deleted_a', 'deleted_b' ) ]
first_dict[ HC.CONTENT_STATUS_CURRENT ] = { ( 'ishygddt', 'i sure hope you guys don\'t do that' ), ( 'character:rei ayanami', 'character:ayanami rei' ), ( 'tree_1', 'tree_3' ), ( 'tree_2', 'tree_3' ), ( 'tree_3', 'tree_5' ), ( 'tree_4', 'tree_5' ), ( 'tree_5', 'tree_6' ), ( 'current_a', 'current_b' ), ( 'chain_a', 'chain_b' ), ( 'chain_b', 'chain_c' ), ( 'closed_loop', 'closed_loop' ), ( 'loop_a', 'loop_b' ), ( 'loop_b', 'loop_c' ), ( 'loop_c', 'loop_a' ) }
first_dict[ HC.CONTENT_STATUS_DELETED ] = { ( 'deleted_a', 'deleted_b' ) }
second_dict = HydrusData.default_dict_list()
second_dict = HydrusData.default_dict_set()
second_dict[ HC.CONTENT_STATUS_CURRENT ] = [ ( 'loop_c', 'loop_a' ), ( 'current_a', 'current_b' ), ( 'petitioned_a', 'petitioned_b' ) ]
second_dict[ HC.CONTENT_STATUS_DELETED ] = [ ( 'pending_a', 'pending_b' ) ]
second_dict[ HC.CONTENT_STATUS_PENDING ] = [ ( 'pending_a', 'pending_b' ) ]
second_dict[ HC.CONTENT_STATUS_PETITIONED ] = [ ( 'petitioned_a', 'petitioned_b' ) ]
second_dict[ HC.CONTENT_STATUS_CURRENT ] = { ( 'loop_c', 'loop_a' ), ( 'current_a', 'current_b' ), ( 'petitioned_a', 'petitioned_b' ) }
second_dict[ HC.CONTENT_STATUS_DELETED ] = { ( 'pending_a', 'pending_b' ) }
second_dict[ HC.CONTENT_STATUS_PENDING ] = { ( 'pending_a', 'pending_b' ) }
second_dict[ HC.CONTENT_STATUS_PETITIONED ] = { ( 'petitioned_a', 'petitioned_b' ) }
tag_siblings[ cls._first_key ] = first_dict
tag_siblings[ cls._second_key ] = second_dict
HG.test_controller.SetRead( 'tag_siblings', tag_siblings )
HG.test_controller.SetParamRead( 'tag_siblings', ( cls._first_key, ), first_dict )
HG.test_controller.SetParamRead( 'tag_siblings', ( cls._second_key, ), second_dict )
cls._tag_siblings_manager = ClientManagers.TagSiblingsManager( HG.test_controller )

View File

@ -28,10 +28,11 @@ from hydrus.client import ClientFiles
from hydrus.client import ClientOptions
from hydrus.client import ClientManagers
from hydrus.client import ClientServices
from hydrus.client import ClientTags
from hydrus.client import ClientThreading
from hydrus.client.gui import QtPorting as QP
from hydrus.client.gui.lists import ClientGUIListManager
from hydrus.client.metadata import ClientTags
from hydrus.client.metadata import ClientTagsHandling
from hydrus.client.networking import ClientNetworking
from hydrus.client.networking import ClientNetworkingBandwidth
from hydrus.client.networking import ClientNetworkingDomain
@ -212,6 +213,8 @@ class Controller( object ):
self._reads[ 'file_system_predicates' ] = []
self._reads[ 'media_results' ] = []
self._param_reads = {}
self.example_tag_repo_service_key = HydrusData.GenerateKey()
services = []
@ -243,7 +246,7 @@ class Controller( object ):
self._reads[ 'sessions' ] = []
self._reads[ 'tag_parents' ] = {}
self._reads[ 'tag_siblings' ] = {}
self._reads[ 'tag_siblings' ] = collections.defaultdict( list )
self._reads[ 'in_inbox' ] = False
self._writes = collections.defaultdict( list )
@ -252,7 +255,7 @@ class Controller( object ):
self.column_list_manager = ClientGUIListManager.ColumnListManager()
self.services_manager = ClientManagers.ServicesManager( self )
self.services_manager = ClientServices.ServicesManager( self )
self.client_files_manager = ClientFiles.ClientFilesManager( self )
self.parsing_cache = ClientCaches.ParsingCache()
@ -269,7 +272,7 @@ class Controller( object ):
self.CallToThreadLongRunning( self.network_engine.MainLoop )
self.tag_display_manager = ClientTags.TagDisplayManager()
self.tag_display_manager = ClientTagsHandling.TagDisplayManager()
self.tag_siblings_manager = ClientManagers.TagSiblingsManager( self )
self.tag_parents_manager = ClientManagers.TagParentsManager( self )
self._managers[ 'undo' ] = ClientManagers.UndoManager( self )
@ -619,6 +622,18 @@ class Controller( object ):
def Read( self, name, *args, **kwargs ):
try:
if ( name, args ) in self._param_reads:
return self._param_reads[ ( name, args ) ]
except:
pass
return self._reads[ name ]
@ -782,6 +797,11 @@ class Controller( object ):
test_thread.start()
def SetParamRead( self, name, args, value ):
self._param_reads[ ( name, args ) ] = value
def SetRead( self, name, value ):
self._reads[ name ] = value

View File

@ -6,7 +6,7 @@ from hydrus.core import HydrusGlobals as HG
from hydrus.client import ClientConstants as CC
from hydrus.client import ClientData
from hydrus.client import ClientTags
from hydrus.client.metadata import ClientTags
class TestFunctions( unittest.TestCase ):

View File

@ -10,7 +10,6 @@ from hydrus.client import ClientData
from hydrus.client import ClientDefaults
from hydrus.client import ClientDuplicates
from hydrus.client import ClientSearch
from hydrus.client import ClientTags
from hydrus.client.gui import ClientGUIShortcuts
from hydrus.client.importing import ClientImportOptions
from hydrus.client.importing import ClientImportSubscriptions
@ -18,6 +17,7 @@ from hydrus.client.importing import ClientImportSubscriptionQuery
from hydrus.client.media import ClientMedia
from hydrus.client.media import ClientMediaManagers
from hydrus.client.media import ClientMediaResult
from hydrus.client.metadata import ClientTags
from hydrus.test import TestController as TC