Version 377

This commit is contained in:
Hydrus Network Developer 2019-12-11 17:18:37 -06:00
parent 129676d9ba
commit 50771138ca
44 changed files with 1149 additions and 566 deletions

View File

@ -8,6 +8,48 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 377</h3></li>
<ul>
<li>qt:</li>
<li>all non-menubar menus across the program now launch on click release. some previously launched on click press. a variety of related click event behaviour is cleaned up, particularly with thumbnail/tag selection on the click down. this also fixes some users' menus immediately activating the first entry on slow clicks in some ui styles</li>
<li>I think I fixed the annoying single-frame delayed size-down resize on media viewer hover frames when changing media!</li>
<li>the vast majority of old wx panel background colour hacks are removed, so custom stylesheets should now cover much more of the UI</li>
<li>improved the new custom style and stylesheet setting, resetting, and error handling code, particularly for not re-applying the same style or stylesheet twice, and for handling un-re-settable styles (seems to be defaults initialised by third-party OS-wide Qt style) gracefully</li>
<li>fixed hyperlinks not using the custom web browser launch path as set in the options</li>
<li>fixed the 'migrate entire db' and 'set thumb location' buttons in the migrate database dialog</li>
<li>fixed a typo bug when launching the url selection tree after adding an ipfs directory to download</li>
<li>fixed two typo bugs when editing regex favourites and simple downloader formulae</li>
<li>fixed an issue where custom shortcut sets could not be deleted</li>
<li>fixed a typo in the edit account type panel</li>
<li>fixed sorting the login listctrl when there are session logins mixed with non-session logins</li>
<li>removed some old media viewer hover window display/raise hacks</li>
<li>retired the 'always show hover windows' debug mode</li>
<li>the media viewer will no longer perform any drag calculations on anything but left-click drag</li>
<li>misc Qt code refactoring/cleanup</li>
<li>.</li>
<li>url searching:</li>
<li>the database now stores 'known url' domain information more efficiently. it will take a few moments/minutes to reshape the db when updating</li>
<li>system:known url's exact url search now runs extremely fast. this will only affect new predicates of this type, not those in existing sessions</li>
<li>system:known url's domain search now runs much faster and matches subdomains of the given domain. this will only affect new predicates of this type, not those in existing sessions</li>
<li>system:known url's url class search now runs much faster. this will only affect new predicates of this type, not those in existing sessions</li>
<li>when entering a regex system:known url predicate, the dialog will now not OK (throwing up an error dialog) if the regex is invalid</li>
<li>.</li>
<li>the rest:</li>
<li>the shortcut system now allows all text characters. if it has text, it should work, but it is the wild west in terms of modifier labelling. anything unusual on your keyboard like ctrl+alt+e to make æ will _display_ as ctrl+alt+æ, but the same key combination will match up in the program all correct</li>
<li>added shortcut actions 'pan_top_edge', 'pan_bottom_edge', 'pan_left_edge', 'pan_right_edge' to the media viewer shortcut set that will move the current image so the respective edge is aligned with the larger canvas's</li>
<li>added shortcut actions 'pan_horizontal_center' and 'pan_vertical_center' to do as above but center on that axis</li>
<li>session save now hangs the UI significantly less, whether triggered by user command or auto-saving 'last session'</li>
<li>saving of last/exit sessions on client close is a little faster</li>
<li>the call to refresh thumbnail file info (and redraw if needed) when a file is imported or has metadata-regenererating file maintenance done will now only call for files that are actually loaded, run faster per file, run faster when the client has large collections in its session, and not hang the ui thread when waiting for the new media info to arrive</li>
<li>like regular popups, modal popups (like those created when big vacuum/analyze jobs jump in) will now only appear if the main gui or an on-parent child has OS focus</li>
<li>the main gui/on-parent child OS focus test now includes misc child windows like the autocomplete results hover window</li>
<li>network jobs that fail for one reason or another will now be more reliably cleaned up, and their connections returned to the connection pool. this may fix the 'too many open file handles' errors some users were seeing after long term unreliable network traffic</li>
<li>fixed an issue where some thumbnails that were trashed or physically deleted were being removed from 'all known files' and file repository views when it was not appropriate</li>
<li>connection and downloader retry time options now have a wider min/max range when in advanced mode, with an accompanying warning label for the connection panel</li>
<li>checker options times now have a wider min/max range when in advanced mode, with an accompanying warning label</li>
<li>cleaned up some shutdown reporting text</li>
<li>misc debug improvements</li>
</ul>
<li><h3>version 376</h3></li>
<ul>
<li>subscriptions:</li>

View File

@ -400,6 +400,14 @@ class MediaResultCache( object ):
def HasFile( self, hash_id ):
with self._lock:
return hash_id in self._hash_ids_to_media_results
def NewForceRefreshTags( self ):
# repo sync or tag migration occurred, so we need complete refresh

View File

@ -271,9 +271,10 @@ page_file_count_display_string_lookup[ PAGE_FILE_COUNT_DISPLAY_ALL ] = 'for all
page_file_count_display_string_lookup[ PAGE_FILE_COUNT_DISPLAY_ONLY_IMPORTERS ] = 'for import pages'
page_file_count_display_string_lookup[ PAGE_FILE_COUNT_DISPLAY_NONE ] = 'for no pages'
SHORTCUT_TYPE_KEYBOARD_ASCII = 0
SHORTCUT_TYPE_KEYBOARD_CHARACTER = 0
SHORTCUT_TYPE_MOUSE = 1
SHORTCUT_TYPE_KEYBOARD_SPECIAL = 2
SHORTCUT_TYPE_NOT_ALLOWED = 3
SHORTCUT_MODIFIER_CTRL = 0
SHORTCUT_MODIFIER_ALT = 1
@ -407,7 +408,7 @@ SHORTCUTS_RESERVED_NAMES = [ 'archive_delete_filter', 'duplicate_filter', 'media
# shortcut commands
SHORTCUTS_MEDIA_ACTIONS = [ 'manage_file_tags', 'manage_file_ratings', 'manage_file_urls', 'manage_file_notes', 'archive_file', 'inbox_file', 'delete_file', 'export_files', 'export_files_quick_auto_export', 'remove_file_from_view', 'open_file_in_external_program', 'open_selection_in_new_page', 'launch_the_archive_delete_filter', 'copy_bmp', 'copy_file', 'copy_path', 'copy_sha256_hash', 'get_similar_to_exact', 'get_similar_to_very_similar', 'get_similar_to_similar', 'get_similar_to_speculative', 'duplicate_media_set_alternate', 'duplicate_media_set_alternate_collections', 'duplicate_media_set_custom', 'duplicate_media_set_focused_better', 'duplicate_media_set_focused_king', 'duplicate_media_set_same_quality', 'open_known_url' ]
SHORTCUTS_MEDIA_VIEWER_ACTIONS = [ 'move_animation_to_previous_frame', 'move_animation_to_next_frame', 'switch_between_fullscreen_borderless_and_regular_framed_window', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'zoom_in', 'zoom_out', 'switch_between_100_percent_and_canvas_zoom', 'flip_darkmode' ]
SHORTCUTS_MEDIA_VIEWER_ACTIONS = [ 'move_animation_to_previous_frame', 'move_animation_to_next_frame', 'switch_between_fullscreen_borderless_and_regular_framed_window', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'pan_top_edge', 'pan_bottom_edge', 'pan_left_edge', 'pan_right_edge', 'pan_vertical_center', 'pan_horizontal_center', 'zoom_in', 'zoom_out', 'switch_between_100_percent_and_canvas_zoom', 'flip_darkmode' ]
SHORTCUTS_MEDIA_VIEWER_BROWSER_ACTIONS = [ 'view_next', 'view_first', 'view_last', 'view_previous' ]
SHORTCUTS_MAIN_GUI_ACTIONS = [ 'refresh', 'refresh_all_pages', 'refresh_page_of_pages_pages', 'new_page', 'new_page_of_pages', 'new_duplicate_filter_page', 'new_gallery_downloader_page', 'new_url_downloader_page', 'new_simple_downloader_page', 'new_watcher_downloader_page', 'synchronised_wait_switch', 'set_media_focus', 'show_hide_splitters', 'set_search_focus', 'unclose_page', 'close_page', 'redo', 'undo', 'flip_darkmode', 'check_all_import_folders', 'flip_debug_force_idle_mode_do_not_set_this', 'show_and_focus_manage_tags_favourite_tags', 'show_and_focus_manage_tags_related_tags', 'show_and_focus_manage_tags_file_lookup_script_tags', 'show_and_focus_manage_tags_recent_tags', 'focus_media_viewer' ]
SHORTCUTS_DUPLICATE_FILTER_ACTIONS = [ 'duplicate_filter_this_is_better_and_delete_other', 'duplicate_filter_this_is_better_but_keep_both', 'duplicate_filter_exactly_the_same', 'duplicate_filter_alternates', 'duplicate_filter_false_positive', 'duplicate_filter_custom_action', 'duplicate_filter_skip', 'duplicate_filter_back' ]

View File

@ -124,6 +124,8 @@ class Controller( HydrusController.HydrusController ):
self._alive_page_keys = set()
self._closed_page_keys = set()
self._last_last_session_hash = None
self._last_mouse_position = None
self._menu_open = False
self._previously_idle = False
@ -919,7 +921,7 @@ class Controller( HydrusController.HydrusController ):
try:
ClientGUIStyle.SetStyle( qt_style_name )
ClientGUIStyle.SetStyleFromName( qt_style_name )
except Exception as e:
@ -933,7 +935,7 @@ class Controller( HydrusController.HydrusController ):
try:
ClientGUIStyle.SetStylesheet( qt_stylesheet_name )
ClientGUIStyle.SetStylesheetFromPath( qt_stylesheet_name )
except Exception as e:
@ -1364,6 +1366,27 @@ class Controller( HydrusController.HydrusController ):
def SaveGUISession( self, session ):
name = session.GetName()
if name == 'last session':
session_hash = hashlib.sha256( bytes( session.DumpToString(), 'utf-8' ) ).digest()
if session_hash == self._last_last_session_hash:
return
self._last_last_session_hash = session_hash
self.WriteSynchronous( 'serialisable', session )
self.pub( 'notify_new_sessions' )
def SetRunningTwistedServices( self, services ):
def TWISTEDDoIt():
@ -1510,6 +1533,8 @@ class Controller( HydrusController.HydrusController ):
self.DoIdleShutdownWork()
self.pub( 'splash_set_status_subtext', '' )
except:
self._ReportShutdownException()
@ -1520,6 +1545,8 @@ class Controller( HydrusController.HydrusController ):
try:
self.pub( 'splash_set_status_text', 'waiting for twisted to exit' )
self.SetRunningTwistedServices( [] )
except:

View File

@ -1473,8 +1473,10 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'CREATE TABLE IF NOT EXISTS external_master.texts ( text_id INTEGER PRIMARY KEY, text TEXT UNIQUE );' )
self._c.execute( 'CREATE TABLE IF NOT EXISTS external_master.urls ( url_id INTEGER PRIMARY KEY, domain TEXT, url TEXT UNIQUE );' )
self._CreateIndex( 'external_master.urls', [ 'domain' ] )
self._c.execute( 'CREATE TABLE IF NOT EXISTS external_master.url_domains ( domain_id INTEGER PRIMARY KEY, domain TEXT UNIQUE );' )
self._c.execute( 'CREATE TABLE IF NOT EXISTS external_master.urls ( url_id INTEGER PRIMARY KEY, domain_id INTEGER, url TEXT UNIQUE );' )
self._CreateIndex( 'external_master.urls', [ 'domain_id' ] )
# inserts
@ -3724,16 +3726,22 @@ class DB( HydrusDB.HydrusDB ):
if len( new_file_info ) > 0:
hashes = set()
hashes_that_need_refresh = set()
for ( hash_id, hash ) in new_file_info:
self._weakref_media_result_cache.DropMediaResult( hash_id, hash )
hashes.add( hash )
if self._weakref_media_result_cache.HasFile( hash_id ):
self._weakref_media_result_cache.DropMediaResult( hash_id, hash )
hashes_that_need_refresh.add( hash )
self._controller.pub( 'new_file_info', hashes )
if len( hashes_that_need_refresh ) > 0:
self._controller.pub( 'new_file_info', hashes_that_need_refresh )
@ -5087,7 +5095,7 @@ class DB( HydrusDB.HydrusDB ):
# start with some quick ways to populate query_hash_ids
def update_qhi( query_hash_ids, some_hash_ids, force_create_new_set = False ):
def intersection_update_qhi( query_hash_ids, some_hash_ids, force_create_new_set = False ):
if query_hash_ids is None:
@ -5148,7 +5156,7 @@ class DB( HydrusDB.HydrusDB ):
query_hash_ids = update_qhi( query_hash_ids, or_query_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, or_query_hash_ids )
return query_hash_ids
@ -5185,7 +5193,7 @@ class DB( HydrusDB.HydrusDB ):
specific_hash_ids = self._GetHashIds( matching_sha256_hashes )
query_hash_ids = update_qhi( query_hash_ids, specific_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, specific_hash_ids )
#
@ -5201,7 +5209,7 @@ class DB( HydrusDB.HydrusDB ):
modified_timestamp_hash_ids = self._STS( self._c.execute( 'SELECT hash_id FROM file_modified_timestamps WHERE {};'.format( pred_string ) ) )
query_hash_ids = update_qhi( query_hash_ids, modified_timestamp_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, modified_timestamp_hash_ids )
#
@ -5223,7 +5231,7 @@ class DB( HydrusDB.HydrusDB ):
all_similar_hash_ids.update( similar_hash_ids )
query_hash_ids = update_qhi( query_hash_ids, all_similar_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, all_similar_hash_ids )
for ( operator, value, rating_service_key ) in system_predicates.GetRatingsPredicates():
@ -5239,7 +5247,7 @@ class DB( HydrusDB.HydrusDB ):
rating_hash_ids = self._STI( self._c.execute( 'SELECT hash_id FROM local_ratings WHERE service_id = ?;', ( service_id, ) ) )
query_hash_ids = update_qhi( query_hash_ids, rating_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, rating_hash_ids )
else:
@ -5287,13 +5295,13 @@ class DB( HydrusDB.HydrusDB ):
rating_hash_ids = self._STI( self._c.execute( 'SELECT hash_id FROM local_ratings WHERE service_id = ? AND ' + predicate + ';', ( service_id, ) ) )
query_hash_ids = update_qhi( query_hash_ids, rating_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, rating_hash_ids )
if system_predicates.MustBeInbox():
query_hash_ids = update_qhi( query_hash_ids, self._inbox_hash_ids, force_create_new_set = True )
query_hash_ids = intersection_update_qhi( query_hash_ids, self._inbox_hash_ids, force_create_new_set = True )
for ( operator, num_relationships, dupe_type ) in system_predicates.GetDuplicateRelationshipCountPredicates():
@ -5313,7 +5321,7 @@ class DB( HydrusDB.HydrusDB ):
dupe_hash_ids = self._DuplicatesGetHashIdsFromDuplicateCountPredicate( file_service_key, operator, num_relationships, dupe_type )
query_hash_ids = update_qhi( query_hash_ids, dupe_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, dupe_hash_ids )
@ -5334,7 +5342,7 @@ class DB( HydrusDB.HydrusDB ):
viewing_hash_ids = self._GetHashIdsFromFileViewingStatistics( view_type, viewing_locations, operator, viewing_value )
query_hash_ids = update_qhi( query_hash_ids, viewing_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, viewing_hash_ids )
@ -5355,7 +5363,7 @@ class DB( HydrusDB.HydrusDB ):
tag_query_hash_ids = self._GetHashIdsFromTag( file_service_key, tag_service_key, tag, include_current_tags, include_pending_tags, allowed_hash_ids = query_hash_ids )
query_hash_ids = update_qhi( query_hash_ids, tag_query_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, tag_query_hash_ids )
if query_hash_ids == set():
@ -5367,14 +5375,14 @@ class DB( HydrusDB.HydrusDB ):
namespace_query_hash_ids = HydrusData.IntelligentMassIntersect( ( self._GetHashIdsFromNamespace( file_service_key, tag_service_key, namespace, include_current_tags, include_pending_tags, include_siblings = True ) for namespace in namespaces_to_include ) )
query_hash_ids = update_qhi( query_hash_ids, namespace_query_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, namespace_query_hash_ids )
if len( wildcards_to_include ) > 0:
wildcard_query_hash_ids = HydrusData.IntelligentMassIntersect( ( self._GetHashIdsFromWildcard( file_service_key, tag_service_key, wildcard, include_current_tags, include_pending_tags ) for wildcard in wildcards_to_include ) )
query_hash_ids = update_qhi( query_hash_ids, wildcard_query_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, wildcard_query_hash_ids )
@ -5419,7 +5427,7 @@ class DB( HydrusDB.HydrusDB ):
king_hash_ids = self._DuplicatesFilterKingHashIds( query_hash_ids )
query_hash_ids = update_qhi( query_hash_ids, king_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, king_hash_ids )
if not done_files_info_predicates and ( need_file_domain_cross_reference or there_are_simple_files_info_preds_to_search_for ):
@ -5428,13 +5436,13 @@ class DB( HydrusDB.HydrusDB ):
if file_service_key == CC.COMBINED_FILE_SERVICE_KEY:
query_hash_ids = update_qhi( query_hash_ids, self._STI( self._SelectFromList( 'SELECT hash_id FROM files_info WHERE ' + ' AND '.join( files_info_predicates ) + ';', query_hash_ids ) ) )
query_hash_ids = intersection_update_qhi( query_hash_ids, self._STI( self._SelectFromList( 'SELECT hash_id FROM files_info WHERE ' + ' AND '.join( files_info_predicates ) + ';', query_hash_ids ) ) )
else:
files_info_predicates.insert( 0, 'service_id = ' + str( file_service_id ) )
query_hash_ids = update_qhi( query_hash_ids, self._STI( self._SelectFromList( 'SELECT hash_id FROM current_files NATURAL JOIN files_info WHERE ' + ' AND '.join( files_info_predicates ) + ';', query_hash_ids ) ) )
query_hash_ids = intersection_update_qhi( query_hash_ids, self._STI( self._SelectFromList( 'SELECT hash_id FROM current_files NATURAL JOIN files_info WHERE ' + ' AND '.join( files_info_predicates ) + ';', query_hash_ids ) ) )
done_files_info_predicates = True
@ -5498,14 +5506,14 @@ class DB( HydrusDB.HydrusDB ):
service_id = self._GetServiceId( service_key )
query_hash_ids = update_qhi( query_hash_ids, self._STI( self._c.execute( 'SELECT hash_id FROM current_files WHERE service_id = ?;', ( service_id, ) ) ) )
query_hash_ids = intersection_update_qhi( query_hash_ids, self._STI( self._c.execute( 'SELECT hash_id FROM current_files WHERE service_id = ?;', ( service_id, ) ) ) )
for service_key in file_services_to_include_pending:
service_id = self._GetServiceId( service_key )
query_hash_ids = update_qhi( query_hash_ids, self._STI( self._c.execute( 'SELECT hash_id FROM file_transfers WHERE service_id = ?;', ( service_id, ) ) ) )
query_hash_ids = intersection_update_qhi( query_hash_ids, self._STI( self._c.execute( 'SELECT hash_id FROM file_transfers WHERE service_id = ?;', ( service_id, ) ) ) )
for service_key in file_services_to_exclude_current:
@ -5562,7 +5570,7 @@ class DB( HydrusDB.HydrusDB ):
hash_ids = zero_hash_ids.union( accurate_except_zero_hash_ids )
query_hash_ids = update_qhi( query_hash_ids, hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, hash_ids )
@ -5587,7 +5595,7 @@ class DB( HydrusDB.HydrusDB ):
hash_ids = zero_hash_ids.union( accurate_except_zero_hash_ids )
query_hash_ids = update_qhi( query_hash_ids, hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, hash_ids )
@ -5614,7 +5622,7 @@ class DB( HydrusDB.HydrusDB ):
if must_be_local:
query_hash_ids = update_qhi( query_hash_ids, local_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, local_hash_ids )
elif must_not_be_local:
@ -5632,7 +5640,7 @@ class DB( HydrusDB.HydrusDB ):
if operator: # inclusive
query_hash_ids = update_qhi( query_hash_ids, url_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, url_hash_ids )
else:
@ -5702,7 +5710,7 @@ class DB( HydrusDB.HydrusDB ):
elif num_tags_nonzero:
query_hash_ids = update_qhi( query_hash_ids, nonzero_tag_query_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, nonzero_tag_query_hash_ids )
@ -5719,7 +5727,7 @@ class DB( HydrusDB.HydrusDB ):
good_tag_count_hash_ids.update( zero_hash_ids )
query_hash_ids = update_qhi( query_hash_ids, good_tag_count_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, good_tag_count_hash_ids )
if job_key.IsCancelled():
@ -5735,7 +5743,7 @@ class DB( HydrusDB.HydrusDB ):
good_hash_ids = self._GetHashIdsThatHaveTagAsNum( file_service_key, tag_service_key, namespace, num, '>', include_current_tags, include_pending_tags )
query_hash_ids = update_qhi( query_hash_ids, good_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, good_hash_ids )
if 'max_tag_as_number' in simple_preds:
@ -5744,7 +5752,7 @@ class DB( HydrusDB.HydrusDB ):
good_hash_ids = self._GetHashIdsThatHaveTagAsNum( file_service_key, tag_service_key, namespace, num, '<', include_current_tags, include_pending_tags )
query_hash_ids = update_qhi( query_hash_ids, good_hash_ids )
query_hash_ids = intersection_update_qhi( query_hash_ids, good_hash_ids )
if job_key.IsCancelled():
@ -5984,34 +5992,155 @@ class DB( HydrusDB.HydrusDB ):
if hash_ids is None:
query = self._c.execute( 'SELECT hash_id, url FROM url_map NATURAL JOIN urls;' )
search_hash_ids = None
else:
query = self._SelectFromList( 'SELECT hash_id, url FROM url_map NATURAL JOIN urls WHERE hash_id in {};', hash_ids )
result_hash_ids = set()
for ( hash_id, url ) in query:
if rule_type in ( 'url_class', 'url_match' ):
if len( hash_ids ) <= 1000:
if rule.Matches( url ):
result_hash_ids.add( hash_id )
search_hash_ids = hash_ids
else:
if re.search( rule, url ) is not None:
result_hash_ids.add( hash_id )
search_hash_ids = None
return result_hash_ids
if rule_type == 'exact_match':
url = rule
result_hash_ids = self._STS( self._c.execute( 'SELECT hash_id FROM url_map NATURAL JOIN urls WHERE url = ?;', ( url, ) ) )
if hash_ids is not None:
result_hash_ids.intersection_update( hash_ids )
return result_hash_ids
elif rule_type in ( 'url_class', 'url_match' ):
url_class = rule
domain = url_class.GetDomain()
if url_class.MatchesSubdomains():
domain_ids = self._GetURLDomainAndSubdomainIds( domain )
else:
domain_ids = ( self._GetURLDomainId( domain ), )
result_hash_ids = set()
for domain_id in domain_ids:
domain_result_hash_ids = set()
if search_hash_ids is None:
query = self._c.execute( 'SELECT hash_id, url FROM url_map NATURAL JOIN urls WHERE domain_id = ?;', ( domain_id, ) )
else:
query = self._SelectFromList( 'SELECT hash_id, url FROM url_map NATURAL JOIN urls WHERE domain_id = ' + str( domain_id ) + ' AND hash_id in {};', search_hash_ids )
for ( hash_id, url ) in query:
if url_class.Matches( url ):
domain_result_hash_ids.add( hash_id )
result_hash_ids.update( domain_result_hash_ids )
if hash_ids is not None and search_hash_ids is None:
result_hash_ids.intersection_update( hash_ids )
return result_hash_ids
elif rule_type in 'domain':
domain = rule
# if we search for site.com, we also want artist.site.com or www.site.com or cdn2.site.com
domain_ids = self._GetURLDomainAndSubdomainIds( domain )
result_hash_ids = set()
for domain_id in domain_ids:
if search_hash_ids is None:
query = self._c.execute( 'SELECT DISTINCT hash_id FROM url_map NATURAL JOIN urls WHERE domain_id = ?;', ( domain_id, ) )
else:
query = self._SelectFromList( 'SELECT DISTINCT hash_id FROM url_map NATURAL JOIN urls WHERE domain_id = ' + str( domain_id ) + ' AND hash_id in {};', search_hash_ids )
domain_result_hash_ids = self._STS( query )
result_hash_ids.update( domain_result_hash_ids )
if hash_ids is not None and search_hash_ids is None:
result_hash_ids.intersection_update( hash_ids )
return result_hash_ids
else:
if search_hash_ids is None:
query = self._c.execute( 'SELECT hash_id, url FROM url_map NATURAL JOIN urls;' )
else:
query = self._SelectFromList( 'SELECT hash_id, url FROM url_map NATURAL JOIN urls WHERE hash_id in {};', search_hash_ids )
result_hash_ids = set()
for ( hash_id, url ) in query:
if rule_type in ( 'url_class', 'url_match' ):
url_class = rule
if url_class.Matches( url ):
result_hash_ids.add( hash_id )
else:
regex = rule
if re.search( regex, url ) is not None:
result_hash_ids.add( hash_id )
if hash_ids is not None and search_hash_ids is None:
result_hash_ids.intersection_update( hash_ids )
return result_hash_ids
def _GetHashIdsFromWildcard( self, file_service_key, tag_service_key, wildcard, include_current_tags, include_pending_tags ):
@ -7866,6 +7995,38 @@ class DB( HydrusDB.HydrusDB ):
return self._GetHashes( hash_ids )
def _GetURLDomainId( self, domain ):
result = self._c.execute( 'SELECT domain_id FROM url_domains WHERE domain = ?;', ( domain, ) ).fetchone()
if result is None:
self._c.execute( 'INSERT INTO url_domains ( domain ) VALUES ( ? );', ( domain, ) )
domain_id = self._c.lastrowid
else:
( domain_id, ) = result
return domain_id
def _GetURLDomainAndSubdomainIds( self, domain ):
domain_ids = set()
domain_ids.add( self._GetURLDomainId( domain ) )
for ( domain_id, ) in self._c.execute( 'SELECT domain_id FROM url_domains WHERE domain LIKE ?;', ( '%.{}'.format( domain ), ) ):
domain_ids.add( domain_id )
return domain_ids
def _GetURLId( self, url ):
result = self._c.execute( 'SELECT url_id FROM urls WHERE url = ?;', ( url, ) ).fetchone()
@ -7881,7 +8042,9 @@ class DB( HydrusDB.HydrusDB ):
domain = 'unknown.com'
self._c.execute( 'INSERT INTO urls ( domain, url ) VALUES ( ?, ? );', ( domain, url ) )
domain_id = self._GetURLDomainId( domain )
self._c.execute( 'INSERT INTO urls ( domain_id, url ) VALUES ( ?, ? );', ( domain_id, url ) )
url_id = self._c.lastrowid
@ -8071,9 +8234,12 @@ class DB( HydrusDB.HydrusDB ):
status = CC.STATUS_SUCCESSFUL_AND_NEW
self._weakref_media_result_cache.DropMediaResult( hash_id, hash )
self._controller.pub( 'new_file_info', set( ( hash, ) ) )
if self._weakref_media_result_cache.HasFile( hash_id ):
self._weakref_media_result_cache.DropMediaResult( hash_id, hash )
self._controller.pub( 'new_file_info', set( ( hash, ) ) )
if HG.file_import_report_mode:
@ -13221,6 +13387,50 @@ class DB( HydrusDB.HydrusDB ):
if version == 376:
result = self._c.execute( 'SELECT 1 FROM external_master.sqlite_master WHERE name = ?;', ( 'url_domains', ) ).fetchone()
try:
if result is None:
self._controller.pub( 'splash_set_status_subtext', 'compressing url storage--creating' )
self._c.execute( 'ALTER TABLE urls RENAME TO urls_old;' )
self._c.execute( 'CREATE TABLE IF NOT EXISTS external_master.url_domains ( domain_id INTEGER PRIMARY KEY, domain TEXT UNIQUE );' )
self._c.execute( 'CREATE TABLE IF NOT EXISTS external_master.urls ( url_id INTEGER PRIMARY KEY, domain_id INTEGER, url TEXT UNIQUE );' )
self._controller.pub( 'splash_set_status_subtext', 'compressing url storage--populating domains' )
self._c.execute( 'INSERT INTO url_domains ( domain ) SELECT DISTINCT domain FROM urls_old;' )
self._controller.pub( 'splash_set_status_subtext', 'compressing url storage--populating urls' )
self._c.execute( 'INSERT INTO urls ( url_id, domain_id, url ) SELECT url_id, domain_id, url FROM urls_old NATURAL JOIN url_domains;' )
self._controller.pub( 'splash_set_status_subtext', 'compressing url storage--indexing' )
self._CreateIndex( 'external_master.urls', [ 'domain_id' ] )
self._c.execute( 'DROP TABLE urls_old;' )
self._controller.pub( 'splash_set_status_subtext', 'compressing url storage--optimising' )
self._c.execute( 'ANALYZE external_master.urls;' )
except Exception as e:
HydrusData.Print( 'Could not update URL storage!' )
HydrusData.PrintException( e )
raise
self._controller.pub( 'splash_set_title_text', 'updated db to v' + str( version + 1 ) )
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )

View File

@ -162,7 +162,7 @@ def DAEMONSynchroniseRepositories( controller ):
return
time.sleep( 3 )
time.sleep( 1 )

View File

@ -345,13 +345,13 @@ def GetDefaultShortcuts():
media.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL, CC.SHORTCUT_KEY_SPECIAL_F7, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'archive_file' ) )
media.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL, CC.SHORTCUT_KEY_SPECIAL_F7, [ CC.SHORTCUT_MODIFIER_SHIFT ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'inbox_file' ) )
media.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'E' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'open_file_in_external_program' ) )
media.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'E' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'open_file_in_external_program' ) )
media.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'R' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'remove_file_from_view' ) )
media.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'R' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'remove_file_from_view' ) )
media.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL, CC.SHORTCUT_KEY_SPECIAL_F12, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'launch_the_archive_delete_filter' ) )
media.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'C' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'copy_file' ) )
media.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'C' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'copy_file' ) )
shortcuts.append( media )
@ -360,15 +360,15 @@ def GetDefaultShortcuts():
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL, CC.SHORTCUT_KEY_SPECIAL_F5, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'refresh' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL, CC.SHORTCUT_KEY_SPECIAL_F9, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'new_page' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'I' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'synchronised_wait_switch' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'M' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'set_media_focus' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'R' ), [ CC.SHORTCUT_MODIFIER_CTRL, CC.SHORTCUT_MODIFIER_SHIFT ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'show_hide_splitters' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'S' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'set_search_focus' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'T' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'new_page' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'U' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'unclose_page' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'W' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'close_page' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'Y' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'redo' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'Z' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'undo' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'I' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'synchronised_wait_switch' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'M' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'set_media_focus' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'R' ), [ CC.SHORTCUT_MODIFIER_CTRL, CC.SHORTCUT_MODIFIER_SHIFT ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'show_hide_splitters' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'S' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'set_search_focus' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'T' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'new_page' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'U' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'unclose_page' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'W' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'close_page' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'Y' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'redo' ) )
main_gui.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'Z' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'undo' ) )
shortcuts.append( main_gui )
@ -394,14 +394,14 @@ def GetDefaultShortcuts():
media_viewer = ClientGUIShortcuts.ShortcutSet( 'media_viewer' )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'B' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'move_animation_to_previous_frame' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'N' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'move_animation_to_next_frame' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'B' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'move_animation_to_previous_frame' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'N' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'move_animation_to_next_frame' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'F' ), [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'switch_between_fullscreen_borderless_and_regular_framed_window' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'F' ), [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'switch_between_fullscreen_borderless_and_regular_framed_window' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'Z' ), [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'switch_between_100_percent_and_canvas_zoom' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( '+' ), [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'zoom_in' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( '-' ), [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'zoom_out' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'Z' ), [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'switch_between_100_percent_and_canvas_zoom' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( '+' ), [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'zoom_in' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( '-' ), [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'zoom_out' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_SCROLL_UP, [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'zoom_in' ) )
media_viewer.SetCommand( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_SCROLL_DOWN, [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'zoom_out' ) )

View File

@ -384,7 +384,6 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
self._widget_event_filter.EVT_LEFT_DCLICK( self.EventFrameNewPage )
self._widget_event_filter.EVT_MIDDLE_DOWN( self.EventFrameNewPage )
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventFrameNotebookMenu )
self._widget_event_filter.EVT_SET_FOCUS( self.EventFocus )
self._widget_event_filter.EVT_ICONIZE( self.EventIconize )
@ -834,11 +833,15 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
if result == QW.QDialog.Accepted:
self._notebook.SaveGUISession( 'last session' )
self._notebook.SaveGUISession( 'exit session' )
session = self._notebook.GetCurrentGUISession( 'last session' )
# session save causes a db read in the menu refresh, so let's put this off just a bit
self._controller.CallLater( 1.5, self._controller.Write, 'backup', path )
self._controller.SaveGUISession( session )
session.SetName( 'exit session' )
self._controller.SaveGUISession( session )
self._controller.Write( 'backup', path )
@ -2301,22 +2304,36 @@ class FrameGUI( ClientGUITopLevelWindows.MainFrameThatResizes ):
qt_style_name = self._controller.new_options.GetNoneableString( 'qt_style_name' )
qt_stylesheet_name = self._controller.new_options.GetNoneableString( 'qt_stylesheet_name' )
if qt_style_name is None:
try:
ClientGUIStyle.SetStyle( ClientGUIStyle.ORIGINAL_STYLE )
if qt_style_name is None:
ClientGUIStyle.SetStyleFromName( ClientGUIStyle.ORIGINAL_STYLE_NAME )
else:
ClientGUIStyle.SetStyleFromName( qt_style_name )
else:
except Exception as e:
ClientGUIStyle.SetStyle( qt_style_name )
HydrusData.ShowException( e )
if qt_stylesheet_name is None:
try:
ClientGUIStyle.ClearStylesheet()
if qt_stylesheet_name is None:
ClientGUIStyle.ClearStylesheet()
else:
ClientGUIStyle.SetStylesheetFromPath( qt_stylesheet_name )
else:
except Exception as e:
ClientGUIStyle.SetStylesheet( qt_stylesheet_name )
HydrusData.ShowException( e )
self._controller.pub( 'wake_daemons' )
@ -3562,9 +3579,9 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
if self.isMinimized() or dialog_open:
if self.isMinimized() or dialog_open or not ClientGUIFunctions.TLWOrChildIsActive( self ):
self._controller.CallLaterQtSafe(self, 2, self.AddModalMessage, job_key)
self._controller.CallLaterQtSafe( self, 0.5, self.AddModalMessage, job_key )
else:
@ -3594,21 +3611,30 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
if only_save_last_session_during_idle and not self._controller.CurrentlyIdle():
next_call_delay = 60
self._controller.CallLaterQtSafe( self, 60, self.AutoSaveLastSession )
else:
if HC.options[ 'default_gui_session' ] == 'last session':
self._notebook.SaveGUISession( 'last session' )
session = self._notebook.GetCurrentGUISession( 'last session' )
callable = self.AutoSaveLastSession
last_session_save_period_minutes = self._controller.new_options.GetInteger( 'last_session_save_period_minutes' )
next_call_delay = last_session_save_period_minutes * 60
def do_it( controller, session, win, next_call_delay, callable ):
controller.SaveGUISession( session )
controller.CallLaterQtSafe( win, next_call_delay, callable )
self._controller.CallToThread( do_it, self._controller, session, self, next_call_delay, callable )
last_session_save_period_minutes = self._controller.new_options.GetInteger( 'last_session_save_period_minutes' )
next_call_delay = last_session_save_period_minutes * 60
self._controller.CallLaterQtSafe( self, next_call_delay, self.AutoSaveLastSession )
def DeleteAllClosedPages( self ):
@ -3706,11 +3732,18 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._notebook.EventNewPageFromScreenPosition( screen_position )
def EventFrameNotebookMenu( self, event ):
def mouseReleaseEvent( self, event ):
if event.button() != QC.Qt.RightButton:
ClientGUITopLevelWindows.MainFrameThatResizes.mouseReleaseEvent( self, event )
return
screen_position = QG.QCursor.pos()
self._notebook.EventMenuFromScreenPosition( screen_position )
self._notebook.ShowMenuFromScreenPosition( screen_position )
def EventIconize( self, event ):
@ -3849,13 +3882,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
try:
self._notebook.SaveGUISession( 'last session' )
self._notebook.SaveGUISession( 'exit session' )
self._DestroyTimers()
self.DeleteAllClosedPages() # Obsolote comment, preserved just in case: wx crashes if any are left in here, wew
if self._message_manager:
self._message_manager.CleanBeforeDestroy()
@ -3863,7 +3889,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
self._message_manager.hide()
self._notebook.CleanBeforeDestroy()
#
if self._new_options.GetBoolean( 'saving_sash_positions_on_exit' ):
@ -3872,6 +3898,29 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
ClientGUITopLevelWindows.SaveTLWSizeAndPosition( self, self._frame_key )
for tlw in QW.QApplication.topLevelWidgets():
tlw.hide()
#
session = self._notebook.GetCurrentGUISession( 'last session' )
self._controller.SaveGUISession( session )
session.SetName( 'exit session' )
self._controller.SaveGUISession( session )
#
self._DestroyTimers()
self.DeleteAllClosedPages() # Obsolote comment, preserved just in case: wx crashes if any are left in here, wew
self._notebook.CleanBeforeDestroy()
self._controller.WriteSynchronous( 'save_options', HC.options )
self._controller.WriteSynchronous( 'serialisable', self._new_options )
@ -3881,11 +3930,6 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
HydrusData.PrintException( e )
for tlw in QW.QApplication.topLevelWidgets():
tlw.hide()
if HG.emergency_exit:
self.deleteLater()
@ -4451,7 +4495,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
ClientGUIMenus.AppendMenuItem( gui_actions, 'make a new page in five seconds', 'Throw a delayed page at the main notebook, giving you time to minimise or otherwise alter the client before it arrives.', self._controller.CallLater, 5, self._controller.pub, 'new_page_query', CC.LOCAL_FILE_SERVICE_KEY )
ClientGUIMenus.AppendMenuItem( gui_actions, 'make a parentless text ctrl dialog', 'Make a parentless text control in a dialog to test some character event catching.', self._DebugMakeParentlessTextCtrl )
ClientGUIMenus.AppendMenuItem( gui_actions, 'force a main gui layout now', 'Tell the gui to relayout--useful to test some gui bootup layout issues.', self.adjustSize )
ClientGUIMenus.AppendMenuItem( gui_actions, 'save \'last session\' gui session', 'Make an immediate save of the \'last session\' gui session. Mostly for testing crashes, where last session is not saved correctly.', self._notebook.SaveGUISession, 'last session' )
ClientGUIMenus.AppendMenuItem( gui_actions, 'save \'last session\' gui session', 'Make an immediate save of the \'last session\' gui session. Mostly for testing crashes, where last session is not saved correctly.', self.ProposeSaveGUISession, 'last session' )
ClientGUIMenus.AppendMenuItem( gui_actions, 'run the ui test', 'Run hydrus_dev\'s weekly UI Test. Guaranteed to work and not mess up your session, ha ha.', self._RunUITest )
ClientGUIMenus.AppendMenu( debug, gui_actions, 'gui actions' )
@ -4697,10 +4741,10 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
continue
ClientGUIMenus.AppendMenuItem( save, name, 'Save the existing open pages as a session.', self._notebook.SaveGUISession, name )
ClientGUIMenus.AppendMenuItem( save, name, 'Save the existing open pages as a session.', self.ProposeSaveGUISession, name )
ClientGUIMenus.AppendMenuItem( save, 'as new session', 'Save the existing open pages as a session.', self._notebook.SaveGUISession )
ClientGUIMenus.AppendMenuItem( save, 'as new session', 'Save the existing open pages as a session.', self.ProposeSaveGUISession )
ClientGUIMenus.AppendMenu( sessions, save, 'save' )
@ -5264,6 +5308,76 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
return command_processed
def ProposeSaveGUISession( self, name = None, suggested_name = '', notebook = None ):
if notebook is None:
notebook = self._notebook
if name is None:
while True:
with ClientGUIDialogs.DialogTextEntry( self, 'Enter a name for the new session.', default = suggested_name ) as dlg:
if dlg.exec() == QW.QDialog.Accepted:
name = dlg.GetValue()
if name in ClientGUIPages.RESERVED_SESSION_NAMES:
QW.QMessageBox.critical( self, 'Error', 'Sorry, you cannot have that name! Try another.' )
else:
existing_session_names = self._controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION )
if name in existing_session_names:
message = 'Session "{}" already exists! Do you want to overwrite it?'.format( name )
result, closed_by_user = ClientGUIDialogsQuick.GetYesNo( self, message, title = 'Overwrite existing session?', yes_label = 'yes, overwrite', no_label = 'no, choose another name', check_for_cancelled = True )
if closed_by_user:
return
elif result == QW.QDialog.Rejected:
continue
break
else:
return
elif name not in ClientGUIPages.RESERVED_SESSION_NAMES: # i.e. a human asked to do this
message = 'Overwrite this session?'
result = ClientGUIDialogsQuick.GetYesNo( self, message, title = 'Overwrite existing session?', yes_label = 'yes, overwrite', no_label = 'no' )
if result != QW.QDialog.Accepted:
return
#
session = notebook.GetCurrentGUISession( name )
self._controller.CallToThread( self._controller.SaveGUISession, session )
def RefreshMenu( self ):
if not QP.isValid( self ) or not self or self.isMinimized():

View File

@ -783,13 +783,13 @@ class AutoCompleteDropdown( QW.QWidget ):
def _ShouldShow( self ):
tlp_active = self.window().isActiveWindow() or self._dropdown_window.isActiveWindow()
tlw_active = self.window().isActiveWindow() or self._dropdown_window.isActiveWindow()
visible = self._text_ctrl.isVisible()
focus_remains_on_self_or_children = ClientGUIFunctions.WindowOrAnyTLPChildHasFocus( self )
focus_remains_on_self_or_children = ClientGUIFunctions.WidgetOrAnyTLWChildHasFocus( self )
return tlp_active and visible and focus_remains_on_self_or_children
return tlw_active and visible and focus_remains_on_self_or_children
def _ShouldTakeResponsibilityForEnter( self ):

View File

@ -1221,6 +1221,51 @@ class Canvas( QW.QWidget ):
return True
def _DoEdgePan( self, pan_type ):
if self._current_media is None:
return
my_size = self.size()
media_size = self._media_container.size()
delta_x = 0
delta_y = 0
if pan_type == 'pan_top_edge':
delta_y = - self._media_window_pos.y()
elif pan_type == 'pan_left_edge':
delta_x = - self._media_window_pos.x()
elif pan_type == 'pan_bottom_edge':
delta_y = my_size.height() - ( self._media_window_pos.y() + media_size.height() )
elif pan_type == 'pan_right_edge':
delta_x = my_size.width() - ( self._media_window_pos.x() + media_size.width() )
elif pan_type == 'pan_vertical_center':
delta_y = ( my_size.height() / 2 ) - ( self._media_window_pos.y() + ( media_size.height() / 2 ) )
elif pan_type == 'pan_horizontal_center':
delta_x = ( my_size.width() / 2 ) - ( self._media_window_pos.x() + ( media_size.width() / 2 ) )
delta = QC.QPoint( delta_x, delta_y )
self._media_window_pos += delta
self._DrawCurrentMedia()
def _DoManualPan( self, delta_x_step, delta_y_step ):
if self._current_media is None:
@ -1355,7 +1400,7 @@ class Canvas( QW.QWidget ):
def _IShouldCatchShortcutEvent( self, event = None ):
return ClientGUIShortcuts.IShouldCatchShortcutEvent( self, event = event, child_tlp_classes_who_can_pass_up = ( ClientGUIHoverFrames.FullscreenHoverFrame, ) )
return ClientGUIShortcuts.IShouldCatchShortcutEvent( self, event = event, child_tlw_classes_who_can_pass_up = ( ClientGUIHoverFrames.FullscreenHoverFrame, ) )
def _MaintainZoom( self, previous_media ):
@ -2167,6 +2212,10 @@ class Canvas( QW.QWidget ):
self._DoManualPan( 1, 0 )
elif action in ( 'pan_top_edge', 'pan_bottom_edge', 'pan_left_edge', 'pan_right_edge', 'pan_vertical_center', 'pan_horizontal_center' ):
self._DoEdgePan( action )
elif action == 'pause_media':
self._PauseCurrentMedia()
@ -2332,11 +2381,11 @@ class CanvasPanel( Canvas ):
HG.client_controller.sub( self, 'ProcessContentUpdates', 'content_updates_gui' )
def mousePressEvent( self, event ):
def mouseReleaseEvent( self, event ):
if event.button() != QC.Qt.RightButton:
event.ignore()
Canvas.mouseReleaseEvent( self, event )
return
@ -2804,6 +2853,11 @@ class CanvasWithHovers( CanvasWithDetails ):
def EventDragBegin( self, event ):
if event.button() != QC.Qt.LeftButton:
return True
point = event.pos()
self.BeginDrag( point = point )
@ -2813,6 +2867,11 @@ class CanvasWithHovers( CanvasWithDetails ):
def EventDragEnd( self, event ):
if event.button() != QC.Qt.LeftButton:
return True
self._last_drag_pos = None
return True # was: event.ignore()
@ -2826,7 +2885,7 @@ class CanvasWithHovers( CanvasWithDetails ):
show_mouse = self.cursor() == QG.QCursor( QC.Qt.ArrowCursor )
is_dragging = ( event.type() == QC.QEvent.MouseMove and event.buttons() != QC.Qt.NoButton ) and self._last_drag_pos is not None
is_dragging = ( event.type() == QC.QEvent.MouseMove and event.buttons() == QC.Qt.LeftButton ) and self._last_drag_pos is not None
has_moved = event_pos != self._last_motion_pos
if is_dragging:
@ -2889,6 +2948,8 @@ class CanvasWithHovers( CanvasWithDetails ):
self.setCursor( QG.QCursor( QC.Qt.BlankCursor ) )
return True
def FullscreenSwitch( self, canvas_key ):
@ -4683,11 +4744,11 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
def mousePressEvent( self, event ):
def mouseReleaseEvent( self, event ):
if event.button() != QC.Qt.RightButton:
event.ignore()
CanvasMediaListNavigable.mouseReleaseEvent( self, event )
return

View File

@ -268,7 +268,6 @@ class BetterColourControl( QP.ColourPickerCtrl ):
QP.ColourPickerCtrl.__init__( self, *args, **kwargs )
self._widget_event_filter = QP.WidgetEventFilter( self )
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventMenu )
def _ImportHexFromClipboard( self ):
@ -314,7 +313,14 @@ class BetterColourControl( QP.ColourPickerCtrl ):
self.SetColour( colour )
def EventMenu( self, event ):
def mouseReleaseEvent( self, event ):
if event.button() != QC.Qt.RightButton:
QP.ColourPickerCtrl.mouseReleaseEvent( self, event )
return
menu = QW.QMenu()
@ -446,10 +452,16 @@ class BetterHyperLink( BetterStaticText ):
self.setTextFormat( QC.Qt.RichText )
self.setTextInteractionFlags( QC.Qt.TextBrowserInteraction )
self.setOpenExternalLinks( True )
self.setText( '<a href="{}">{}</a>'.format( url, label ) )
self.linkActivated.connect( self.Activated )
def Activated( self ):
ClientPaths.LaunchURLInWebBrowser( self._url )
class BufferedWindow( QW.QWidget ):
@ -1057,8 +1069,6 @@ class ListBook( QW.QWidget ):
QW.QWidget.__init__( self, *args, **kwargs )
QP.SetBackgroundColour( self, QP.GetSystemColour( QG.QPalette.Button ) )
self._keys_to_active_pages = {}
self._keys_to_proto_pages = {}
@ -1067,8 +1077,6 @@ class ListBook( QW.QWidget ):
self._empty_panel = QW.QWidget( self )
QP.SetBackgroundColour( self._empty_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._current_key = None
self._current_panel = self._empty_panel
@ -2383,6 +2391,7 @@ class RegexButton( BetterButton ):
ClientGUIMenus.AppendSeparator( submenu )
for ( regex_phrase, description ) in HC.options[ 'regex_favourites' ]:
ClientGUIMenus.AppendMenuItem( submenu, description, 'copy this phrase to the clipboard', HG.client_controller.pub, 'clipboard', 'text', regex_phrase )
@ -2424,8 +2433,6 @@ class StaticBox( QW.QFrame ):
self._spacer = QW.QSpacerItem( 0, 0, QW.QSizePolicy.Minimum, QW.QSizePolicy.MinimumExpanding )
QP.SetBackgroundColour( self, QP.GetSystemColour( QG.QPalette.Button ) )
self._sizer = QP.VBoxLayout()
normal_font = QW.QApplication.font()

View File

@ -108,9 +108,9 @@ class Dialog( QP.Dialog ):
if parent is not None and position == 'topleft':
parent_tlp = self.parentWidget().window()
parent_tlw = self.parentWidget().window()
( pos_x, pos_y ) = parent_tlp.pos().toTuple()
( pos_x, pos_y ) = parent_tlw.pos().toTuple()
pos = QC.QPoint( pos_x + 50, pos_y + 50 )
@ -125,8 +125,6 @@ class Dialog( QP.Dialog ):
self._new_options = HG.client_controller.new_options
QP.SetBackgroundColour( self, QP.GetSystemColour( QG.QPalette.Button ) )
self.setWindowIcon( QG.QIcon( HG.client_controller.frame_icon_pixmap ) )
self._widget_event_filter = QP.WidgetEventFilter( self )
@ -974,7 +972,7 @@ class DialogSelectFromURLTree( Dialog ):
def __init__( self, parent, url_tree ):
Dialog.__init__( self, parent, 'select items' )
self._tree = QP.TreeWidgetWithInheritedCheckState( self )
self._ok = ClientGUICommon.BetterButton( self, 'OK', self.done, QW.QDialog.Accepted )
@ -996,7 +994,7 @@ class DialogSelectFromURLTree( Dialog ):
self._tree.addTopLevelItem( root_item )
self._AddDirectory( root_item, children )
root_item.setExpanded( True )
#

View File

@ -178,8 +178,6 @@ class DialogManageRatings( ClientGUIDialogs.Dialog ):
QW.QWidget.__init__( self, parent )
QP.SetBackgroundColour( self, QP.GetSystemColour( QG.QPalette.Button ) )
self._services = services
self._media = media

View File

@ -374,7 +374,6 @@ class FileSeedCacheButton( ClientGUICommon.BetterBitmapButton ):
self.setToolTip( 'open detailed file import status--right-click for quick actions, if applicable' )
self._widget_event_filter = QP.WidgetEventFilter( self )
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventShowMenu )
def _ClearFileSeeds( self, statuses_to_remove ):
@ -533,9 +532,9 @@ class FileSeedCacheButton( ClientGUICommon.BetterBitmapButton ):
file_seed_cache = self._file_seed_cache_get_callable()
tlp = self.window()
tlw = self.window()
if isinstance( tlp, QP.Dialog ):
if isinstance( tlw, QP.Dialog ):
if self._file_seed_cache_set_callable is None: # throw up a dialog that edits the file_seed cache in place
@ -578,7 +577,14 @@ class FileSeedCacheButton( ClientGUICommon.BetterBitmapButton ):
def EventShowMenu( self, event ):
def mouseReleaseEvent( self, event ):
if event.button() != QC.Qt.RightButton:
ClientGUICommon.BetterBitmapButton.mouseReleaseEvent( self, event )
return
menu = QW.QMenu()

View File

@ -283,9 +283,9 @@ def ClientToScreen( win, pos ):
if isinstance( pos, tuple ): pos = QP.TupleToQPoint( pos )
tlp = win.window()
tlw = win.window()
if win.isVisible() and tlp.isVisible():
if win.isVisible() and tlw.isVisible():
return win.mapToGlobal( pos )
@ -307,22 +307,24 @@ def ConvertTextToPixelWidth( window, char_cols ):
return int( window.fontMetrics().boundingRect( char_cols * 'x' ).width() * MAGIC_TEXT_PADDING )
def GetTLPParents( window ):
def GetTLWParents( widget ):
window = window.window()
widget_tlw = widget.window()
parents = []
parent_tlws = []
parent = window.parentWidget()
parent = widget_tlw.parentWidget()
while parent is not None:
parents.append( parent )
parent_tlw = parent.window()
parent = parent.parentWidget()
parent_tlws.append( parent_tlw )
parent = parent_tlw.parentWidget()
return parents
return parent_tlws
def IsQtAncestor( child, ancestor, through_tlws = False ):
@ -379,11 +381,32 @@ def SetBitmapButtonBitmap( button, bitmap ):
button.last_bitmap = bitmap
def TLPIsActive( window ):
def TLWIsActive( window ):
return window.window() == QW.QApplication.activeWindow()
def WindowOrAnyTLPChildHasFocus( window ):
def TLWOrChildIsActive( win ):
current_focus_tlw = QW.QApplication.activeWindow()
if current_focus_tlw is None:
return False
if current_focus_tlw == win:
return True
if win in GetTLWParents( current_focus_tlw ):
return True
return False
def WidgetOrAnyTLWChildHasFocus( window ):
active_window = QW.QApplication.activeWindow()
@ -396,6 +419,7 @@ def WindowOrAnyTLPChildHasFocus( window ):
if widget is None:
# take active window in lieu of focus, if it is unavailable
widget = active_window

View File

@ -296,7 +296,6 @@ class GallerySeedLogButton( ClientGUICommon.BetterBitmapButton ):
self.setToolTip( 'open detailed gallery log--right-click for quick actions, if applicable' )
self._widget_event_filter = QP.WidgetEventFilter( self )
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventShowMenu )
def _ClearGallerySeeds( self, statuses_to_remove ):
@ -471,9 +470,9 @@ class GallerySeedLogButton( ClientGUICommon.BetterBitmapButton ):
gallery_seed_log = self._gallery_seed_log_get_callable()
tlp = self.window()
tlw = self.window()
if isinstance( tlp, QP.Dialog ):
if isinstance( tlw, QP.Dialog ):
if self._gallery_seed_log_set_callable is None: # throw up a dialog that edits the gallery_seed log in place
@ -516,7 +515,14 @@ class GallerySeedLogButton( ClientGUICommon.BetterBitmapButton ):
def EventShowMenu( self, event ):
def mouseReleaseEvent( self, event ):
if event.button() != QC.Qt.RightButton:
ClientGUICommon.BetterBitmapButton.mouseReleaseEvent( self, event )
return
menu = QW.QMenu()

View File

@ -43,7 +43,6 @@ class FullscreenHoverFrame( QW.QFrame ):
self._last_ideal_position = None
QP.SetBackgroundColour( self, QP.GetSystemColour( QG.QPalette.Button ) )
self.setCursor( QG.QCursor( QC.Qt.ArrowCursor ) )
self._hide_until = None
@ -60,9 +59,9 @@ class FullscreenHoverFrame( QW.QFrame ):
raise NotImplementedError()
def _SizeAndPosition( self ):
def _SizeAndPosition( self, force = False ):
if self.parentWidget().isVisible():
if self.parentWidget().isVisible() or force:
( should_resize, my_ideal_size, my_ideal_position ) = self._GetIdealSizeAndPosition()
@ -71,13 +70,6 @@ class FullscreenHoverFrame( QW.QFrame ):
self.resize( QP.TupleToQSize( my_ideal_size ) )
changes_occurred = should_resize or self.pos() != QP.TupleToQPoint( my_ideal_position )
if HC.PLATFORM_MACOS and changes_occurred and self._always_on_top:
self.raise_()
self.move( QP.TupleToQPoint( my_ideal_position ) )
@ -99,38 +91,21 @@ class FullscreenHoverFrame( QW.QFrame ):
def TIMERUIUpdate( self ):
current_focus_tlp = QW.QApplication.activeWindow()
current_focus_tlw = QW.QApplication.activeWindow()
focus_is_on_descendant = ClientGUIFunctions.IsQtAncestor( current_focus_tlp, self._my_canvas.window(), through_tlws = True )
focus_has_right_window_type = isinstance( current_focus_tlp, ( ClientGUICanvas.CanvasFrame, FullscreenHoverFrame ) )
focus_is_on_descendant = ClientGUIFunctions.IsQtAncestor( current_focus_tlw, self._my_canvas.window(), through_tlws = True )
focus_has_right_window_type = isinstance( current_focus_tlw, ( ClientGUICanvas.CanvasFrame, FullscreenHoverFrame ) )
focus_is_good = focus_is_on_descendant and focus_has_right_window_type
new_options = HG.client_controller.new_options
if self._always_on_top or new_options.GetBoolean( 'always_show_hover_windows' ):
if self._always_on_top:
self._SizeAndPosition()
self.show()
if HC.PLATFORM_MACOS:
( mouse_x, mouse_y ) = QG.QCursor.pos().toTuple()
( my_x, my_y ) = self.mapToGlobal( self.pos() ).toTuple()
( my_width, my_height ) = self.size().toTuple()
in_actual_x = my_x <= mouse_x and mouse_x <= my_x + my_width
in_actual_y = my_y <= mouse_y and mouse_y <= my_y + my_height
if in_actual_x and in_actual_y and focus_is_good:
self.raise_()
return
@ -195,11 +170,11 @@ class FullscreenHoverFrame( QW.QFrame ):
dialog_open = False
tlps = QW.QApplication.topLevelWidgets()
tlws = QW.QApplication.topLevelWidgets()
for tlp in tlps:
for tlw in tlws:
if isinstance( tlp, QW.QDialog ) and not isinstance( tlp, ClientGUICanvas.CanvasFrame ) and tlp.isModal():
if isinstance( tlw, QW.QDialog ) and not isinstance( tlw, ClientGUICanvas.CanvasFrame ) and tlw.isModal():
dialog_open = True
@ -211,7 +186,7 @@ class FullscreenHoverFrame( QW.QFrame ):
mouse_is_over_something_important = mouse_is_near_animation_bar # this used to have the flash media window test to ensure mouse over flash window hid hovers going over it
hide_focus_is_good = focus_is_good or current_focus_tlp is None # don't hide if focus is either gone to another problem or temporarily sperging-out due to a click-transition or similar
hide_focus_is_good = focus_is_good or current_focus_tlw is None # don't hide if focus is either gone to another problem or temporarily sperging-out due to a click-transition or similar
ready_to_show = in_position and not mouse_is_over_something_important and focus_is_good and not dialog_open and not menu_open
ready_to_hide = not menu_open and ( not in_position or dialog_open or not hide_focus_is_good )
@ -231,7 +206,7 @@ class FullscreenHoverFrame( QW.QFrame ):
tuples.append( ( 'mouse near animation bar: ', mouse_is_near_animation_bar ) )
tuples.append( ( 'focus is good: ', focus_is_good ) )
tuples.append( ( 'focus is on descendant: ', focus_is_on_descendant ) )
tuples.append( ( 'current focus tlp: ', current_focus_tlp ) )
tuples.append( ( 'current focus tlw: ', current_focus_tlw ) )
message = os.linesep * 2 + os.linesep.join( ( a + str( b ) for ( a, b ) in tuples ) )
@ -251,20 +226,6 @@ class FullscreenHoverFrame( QW.QFrame ):
self.show()
# in case focus jumps around from the show, let's test it and raise as needed
current_focus_tlp = QW.QApplication.activeWindow()
focus_is_on_descendant = ClientGUIFunctions.IsQtAncestor( current_focus_tlp, self._my_canvas.window(), through_tlws = True )
focus_has_right_window_type = isinstance( current_focus_tlp, ( ClientGUICanvas.CanvasFrame, FullscreenHoverFrame ) )
focus_is_good = focus_is_on_descendant and focus_has_right_window_type
if not focus_is_good:
self.raise_()
elif ready_to_hide:
@ -605,6 +566,8 @@ class FullscreenHoverFrameTop( FullscreenHoverFrame ):
def _GetIdealSizeAndPosition( self ):
# clip this and friends to availableScreenGeometry for size and position, not rely 100% on parent
parent_window = self.parentWidget().window()
( parent_width, parent_height ) = parent_window.size().toTuple()
@ -744,8 +707,6 @@ class FullscreenHoverFrameTop( FullscreenHoverFrame ):
self._undelete_button.show()
self._SizeAndPosition()
def _ResetText( self ):
@ -915,6 +876,11 @@ class FullscreenHoverFrameTop( FullscreenHoverFrame ):
self._ResetButtons()
# minimumsize is not immediately updated without this
self.layout().activate()
self._SizeAndPosition( force = True )
def SetIndexString( self, canvas_key, text ):
@ -1223,6 +1189,11 @@ class FullscreenHoverFrameTopRight( FullscreenHoverFrame ):
self._ResetData()
# minimumsize is not immediately updated without this
self.layout().activate()
self._SizeAndPosition( force = True )
class FullscreenHoverFrameTags( FullscreenHoverFrame ):

View File

@ -2101,7 +2101,6 @@ class TagImportOptionsButton( ClientGUICommon.BetterButton ):
#
self._widget_event_filter = QP.WidgetEventFilter( self )
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventShowMenu )
def _Copy( self ):
@ -2184,7 +2183,14 @@ class TagImportOptionsButton( ClientGUICommon.BetterButton ):
def EventShowMenu( self, event ):
def mouseReleaseEvent( self, event ):
if event.button() != QC.Qt.RightButton:
ClientGUICommon.BetterButton.mouseReleaseEvent( self, event )
return
menu = QW.QMenu()

View File

@ -864,6 +864,7 @@ class ListBox( QW.QScrollArea ):
self._widget_event_filter = QP.WidgetEventFilter( self.widget() )
self._widget_event_filter.EVT_LEFT_DOWN( self.EventMouseSelect )
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventMouseSelect )
self._widget_event_filter.EVT_LEFT_DCLICK( self.EventDClick )
@ -1571,7 +1572,6 @@ class ListBoxTags( ListBox ):
self._UpdateBackgroundColour()
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventMouseRightClick )
self._widget_event_filter.EVT_MIDDLE_DOWN( self.EventMouseMiddleClick )
HG.client_controller.sub( self, 'ForceTagRecalc', 'refresh_all_tag_presentation_gui' )
@ -1835,9 +1835,14 @@ class ListBoxTags( ListBox ):
def EventMouseRightClick( self, event ):
def mouseReleaseEvent( self, event ):
self._HandleClick( event )
if event.button() != QC.Qt.RightButton:
ListBox.mouseReleaseEvent( self, event )
return
if len( self._ordered_terms ) > 0:
@ -2077,6 +2082,7 @@ class ListBoxTags( ListBox ):
HG.client_controller.PopupMenu( self, menu )
def GetSelectedTags( self ):

View File

@ -345,7 +345,7 @@ class BetterListCtrl( QW.QTreeWidget ):
def EventColumnClick( self, col ):
if col == self._sort_column:
self._sort_asc = not self._sort_asc

View File

@ -670,7 +670,16 @@ class EditLoginsPanel( ClientGUIScrolledPanels.EditPanel ):
sort_validity += ' - ' + validity_error_text
sort_logged_in = ( logged_in, login_expiry )
if login_expiry is None:
sort_login_expiry = HydrusData.GetNow() + 45 * 60
else:
sort_login_expiry = login_expiry
sort_logged_in = ( logged_in, sort_login_expiry )
if HydrusData.TimeHasPassed( no_work_until ):

View File

@ -685,8 +685,6 @@ class ManagementPanel( QW.QScrollArea ):
self.verticalScrollBar().valueChanged.connect( managementScrollbarValueChanged )
QP.SetBackgroundColour( self, QP.GetSystemColour( QG.QPalette.Button ) )
self._controller = controller
self._management_controller = management_controller
@ -3039,7 +3037,7 @@ class ManagementPanelImporterSimpleDownloader( ManagementPanelImporter ):
if dlg_2.exec() == QW.QDialog.Accepted:
name = dlg_2.value()
name = dlg_2.GetValue()
else:

View File

@ -2790,8 +2790,6 @@ class MediaPanelThumbnails( MediaPanel ):
self.verticalScrollBar().setSingleStep( int( round( thumbnail_span_height * thumbnail_scroll_rate ) ) )
self._widget_event_filter = QP.WidgetEventFilter( self.widget() )
self._widget_event_filter.EVT_LEFT_DOWN( self.EventLeftDown )
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventShowMenu )
self._widget_event_filter.EVT_LEFT_DCLICK( self.EventMouseFullScreen )
self._widget_event_filter.EVT_MIDDLE_DOWN( self.EventMouseFullScreen )
@ -2807,7 +2805,7 @@ class MediaPanelThumbnails( MediaPanel ):
self._UpdateScrollBars()
HG.client_controller.sub( self, 'MaintainPageCache', 'memory_maintenance_pulse' )
HG.client_controller.sub( self, 'NewFileInfo', 'new_file_info' )
HG.client_controller.sub( self, 'NotifyNewFileInfo', 'new_file_info' )
HG.client_controller.sub( self, 'NewThumbnails', 'new_thumbnails' )
HG.client_controller.sub( self, 'ThumbnailsReset', 'notify_complete_thumbnail_reset' )
HG.client_controller.sub( self, 'RedrawAllThumbnails', 'refresh_all_tag_presentation_gui' )
@ -3507,17 +3505,6 @@ class MediaPanelThumbnails( MediaPanel ):
def EventLeftDown( self, event ):
self._drag_init_coordinates = QG.QCursor.pos()
self._HitMedia( self._GetThumbnailUnderMouse( event ), event.modifiers() & QC.Qt.ControlModifier, event.modifiers() & QC.Qt.ShiftModifier )
# this specifically does not scroll to media, as for clicking (esp. double-clicking attempts), the scroll can be jarring
return True # was: event.ignore()
def EventMouseFullScreen( self, event ):
t = self._GetThumbnailUnderMouse( event )
@ -3551,6 +3538,15 @@ class MediaPanelThumbnails( MediaPanel ):
self._parent = parent
def mousePressEvent( self, event ):
self._parent._drag_init_coordinates = QG.QCursor.pos()
self._parent._HitMedia( self._parent._GetThumbnailUnderMouse( event ), event.modifiers() & QC.Qt.ControlModifier, event.modifiers() & QC.Qt.ShiftModifier )
# this specifically does not scroll to media, as for clicking (esp. double-clicking attempts), the scroll can be jarring
def paintEvent( self, event ):
painter = QG.QPainter( self )
@ -3647,7 +3643,14 @@ class MediaPanelThumbnails( MediaPanel ):
self._last_client_size = QP.ScrollAreaVisibleRect( self ).size().toTuple()
def EventShowMenu( self, event ):
def mouseReleaseEvent( self, event ):
if event.button() != QC.Qt.RightButton:
QW.QScrollArea.mouseReleaseEvent( self, event )
return
new_options = HG.client_controller.new_options
@ -3655,13 +3658,6 @@ class MediaPanelThumbnails( MediaPanel ):
services_manager = HG.client_controller.services_manager
thumbnail = self._GetThumbnailUnderMouse( event )
if thumbnail is not None:
self._HitMedia( thumbnail, event.modifiers() & QC.Qt.ControlModifier, event.modifiers() & QC.Qt.ShiftModifier )
all_locations_managers = [ media.GetLocationsManager() for media in self._sorted_media ]
selected_locations_managers = [ media.GetLocationsManager() for media in self._selected_media ]
@ -4581,18 +4577,6 @@ class MediaPanelThumbnails( MediaPanel ):
self._DeleteAllDirtyPages()
def NewFileInfo( self, hashes ):
affected_media = self._GetMedia( hashes )
for media in affected_media:
media.RefreshFileInfo()
self._RedrawMedia( affected_media )
def NewThumbnails( self, hashes ):
affected_thumbnails = self._GetMedia( hashes )
@ -4603,6 +4587,34 @@ class MediaPanelThumbnails( MediaPanel ):
def NotifyNewFileInfo( self, hashes ):
def qt_do_update( hashes_to_media_results ):
affected_media = self._GetMedia( set( hashes_to_media_results.keys() ) )
for media in affected_media:
media.UpdateFileInfo( hashes_to_media_results )
self._RedrawMedia( affected_media )
def do_it( win, callable, affected_hashes ):
media_results = HG.client_controller.Read( 'media_results', affected_hashes )
hashes_to_media_results = { media_result.GetHash() : media_result for media_result in media_results }
HG.client_controller.CallLaterQtSafe( win, 0, qt_do_update, hashes_to_media_results )
affected_hashes = self._hashes.intersection( hashes )
HG.client_controller.CallToThread( do_it, self, do_it, affected_hashes )
def RedrawAllThumbnails( self ):
self._DirtyAllPages()
@ -4628,6 +4640,8 @@ class MediaPanelThumbnails( MediaPanel ):
child.setParent( None )
child.deleteLater()
def ctrl_space_callback( self ):

View File

@ -897,15 +897,12 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
self._closed_pages = []
self._last_last_session_hash = None
self._controller.sub( self, 'RefreshPageName', 'refresh_page_name' )
self._controller.sub( self, 'NotifyPageUnclosed', 'notify_page_unclosed' )
self._widget_event_filter = QP.WidgetEventFilter( self )
self._widget_event_filter.EVT_LEFT_DCLICK( self.EventLeftDoubleClick )
self._widget_event_filter.EVT_MIDDLE_DOWN( self.EventMiddleClick )
self._widget_event_filter.EVT_RIGHT_DOWN( self.EventMenu )
self._widget_event_filter.EVT_LEFT_DOWN( lambda ev: ev.accept() )
self._widget_event_filter.EVT_LEFT_DOWN( lambda ev: ev.accept() )
@ -1468,6 +1465,7 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
ClientGUIMenus.AppendMenuItem( menu, 'rename page', 'Rename this page.', self._RenamePage, tab_index )
ClientGUIMenus.AppendMenuItem( menu, 'new page', 'Choose a new page.', self._ChooseNewPage )
if click_over_tab:
@ -1511,8 +1509,8 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
ClientGUIMenus.AppendMenuItem( menu, 'send this page down to a new page of pages', 'Make a new page of pages and put this page in it.', self._SendPageToNewNotebook, tab_index )
if can_go_right:
ClientGUIMenus.AppendMenuItem( menu, 'send pages to the right to a new page of pages', 'Make a new page of pages and put all the pages to the right into it.', self._SendRightPagesToNewNotebook, tab_index )
ClientGUIMenus.AppendMenuItem( menu, 'send pages to the right to a new page of pages', 'Make a new page of pages and put all the pages to the right into it.', self._SendRightPagesToNewNotebook, tab_index )
if click_over_page_of_pages and page.count() > 0:
@ -1534,6 +1532,7 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
submenu = QW.QMenu( menu )
for name in existing_session_names:
ClientGUIMenus.AppendMenuItem( submenu, name, 'Load this session here.', self.AppendGUISession, name )
@ -1550,9 +1549,11 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
continue
ClientGUIMenus.AppendMenuItem( submenu, name, 'Save this page of pages to the session.', page.SaveGUISession, name )
ClientGUIMenus.AppendMenuItem( submenu, 'create a new session', 'Save this page of pages to the session.', page.SaveGUISession, suggested_name=page.GetName() )
ClientGUIMenus.AppendMenuItem( submenu, name, 'Save this page of pages to the session.', self._controller.gui.ProposeSaveGUISession, notebook = page, name = name )
ClientGUIMenus.AppendMenuItem( submenu, 'create a new session', 'Save this page of pages to the session.', self._controller.gui.ProposeSaveGUISession, notebook = page, suggested_name = page.GetName() )
ClientGUIMenus.AppendMenu( menu, submenu, 'save this page of pages to a session' )
@ -1796,14 +1797,21 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
def EventMenu( self, event ):
def mouseReleaseEvent( self, event ):
if event.button() != QC.Qt.RightButton:
QP.TabWidgetWithDnD.mouseReleaseEvent( self, event )
return
screen_position = ClientGUIFunctions.ClientToScreen( self, event.pos() )
self._ShowMenu( screen_position )
def EventMenuFromScreenPosition( self, position ):
def ShowMenuFromScreenPosition( self, position ):
notebook = self._GetNotebookFromScreenPosition( position )
@ -1869,6 +1877,18 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
return {}
def GetCurrentGUISession( self, name ):
session = GUISession( name )
for page in self._GetPages():
session.AddPageTuple( page )
return session
def GetCurrentMediaPage( self ):
page = self.currentWidget()
@ -2728,92 +2748,6 @@ class PagesNotebook( QP.TabWidgetWithDnD ):
def SaveGUISession( self, name = None, suggested_name = '' ):
if name is None:
while True:
with ClientGUIDialogs.DialogTextEntry( self, 'Enter a name for the new session.', default = suggested_name ) as dlg:
if dlg.exec() == QW.QDialog.Accepted:
name = dlg.GetValue()
if name in RESERVED_SESSION_NAMES:
QW.QMessageBox.critical( self, 'Error', 'Sorry, you cannot have that name! Try another.' )
else:
existing_session_names = self._controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_GUI_SESSION )
if name in existing_session_names:
message = 'Session "{}" already exists! Do you want to overwrite it?'.format( name )
result, closed_by_user = ClientGUIDialogsQuick.GetYesNo( self, message, title = 'Overwrite existing session?', yes_label = 'yes, overwrite', no_label = 'no, choose another name', check_for_cancelled = True )
if closed_by_user:
return
elif result == QW.QDialog.Rejected:
continue
break
else:
return
elif name not in RESERVED_SESSION_NAMES: # i.e. a human asked to do this
message = 'Overwrite this session?'
result = ClientGUIDialogsQuick.GetYesNo( self, message, title = 'Overwrite existing session?', yes_label = 'yes, overwrite', no_label = 'no' )
if result != QW.QDialog.Accepted:
return
#
session = GUISession( name )
for page in self._GetPages():
session.AddPageTuple( page )
#
if name == 'last session':
session_hash = hashlib.sha256( bytes( session.DumpToString(), 'utf-8' ) ).digest()
if session_hash == self._last_last_session_hash:
return
self._last_last_session_hash = session_hash
self._controller.WriteSynchronous( 'serialisable', session )
self._controller.pub( 'notify_new_sessions' )
def SetName( self, name ):
self._name = name

View File

@ -236,8 +236,6 @@ class ReviewServicePanel( QW.QWidget ):
QW.QWidget.__init__( self, parent )
QP.SetBackgroundColour( self, QP.GetSystemColour( QG.QPalette.Button ) )
self._service = service
service_type = self._service.GetServiceType()

View File

@ -532,8 +532,6 @@ class EditCompoundFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
edit_panel = ClientGUICommon.StaticBox( self, 'edit' )
QP.SetBackgroundColour( edit_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._formulae = QW.QListWidget( edit_panel )
self._formulae.setSelectionMode( QW.QAbstractItemView.SingleSelection )
self._formulae.itemDoubleClicked.connect( self.Edit )
@ -560,8 +558,6 @@ class EditCompoundFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
test_panel = ClientGUICommon.StaticBox( self, 'test' )
QP.SetBackgroundColour( test_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._test_panel = TestPanel( test_panel, self.GetValue, test_context = test_context )
#
@ -771,8 +767,6 @@ class EditContextVariableFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
edit_panel = ClientGUICommon.StaticBox( self, 'edit' )
QP.SetBackgroundColour( edit_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._variable_name = QW.QLineEdit( edit_panel )
( variable_name, string_match, string_converter ) = formula.ToTuple()
@ -785,8 +779,6 @@ class EditContextVariableFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
test_panel = ClientGUICommon.StaticBox( self, 'test' )
QP.SetBackgroundColour( test_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._test_panel = TestPanel( test_panel, self.GetValue, test_context = test_context )
#
@ -1225,8 +1217,6 @@ class EditHTMLFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
edit_panel = ClientGUICommon.StaticBox( self, 'edit' )
QP.SetBackgroundColour( edit_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._tag_rules = QW.QListWidget( edit_panel )
self._tag_rules.setSelectionMode( QW.QAbstractItemView.SingleSelection )
@ -1262,8 +1252,6 @@ class EditHTMLFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
test_panel = ClientGUICommon.StaticBox( self, 'test' )
QP.SetBackgroundColour( test_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._test_panel = TestPanel( test_panel, self.GetValue, test_context = test_context )
#
@ -1585,8 +1573,6 @@ class EditJSONFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
edit_panel = ClientGUICommon.StaticBox( self, 'edit' )
QP.SetBackgroundColour( edit_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._parse_rules = QW.QListWidget( edit_panel )
self._parse_rules.setSelectionMode( QW.QAbstractItemView.SingleSelection )
self._parse_rules.itemDoubleClicked.connect( self.Edit )
@ -1617,8 +1603,6 @@ class EditJSONFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
test_panel = ClientGUICommon.StaticBox( self, 'test' )
QP.SetBackgroundColour( test_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._test_panel = TestPanel( test_panel, self.GetValue, test_context = test_context )
#
@ -1825,16 +1809,12 @@ class EditContentParserPanel( ClientGUIScrolledPanels.EditPanel ):
test_panel = ClientGUICommon.StaticBox( self, 'test' )
QP.SetBackgroundColour( test_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._test_panel = TestPanel( test_panel, self.GetValue, test_context = test_context )
#
self._edit_panel = ClientGUICommon.StaticBox( self, 'edit' )
QP.SetBackgroundColour( self._edit_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._name = QW.QLineEdit( self._edit_panel )
self._content_panel = ClientGUICommon.StaticBox( self._edit_panel, 'content type' )
@ -2644,8 +2624,6 @@ class EditParseNodeContentLinkPanel( ClientGUIScrolledPanels.EditPanel ):
edit_panel = QW.QWidget( notebook )
QP.SetBackgroundColour( edit_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._name = QW.QLineEdit( edit_panel )
get_example_parsing_context = lambda: {}
@ -2660,8 +2638,6 @@ class EditParseNodeContentLinkPanel( ClientGUIScrolledPanels.EditPanel ):
test_panel = QW.QWidget( notebook )
QP.SetBackgroundColour( test_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._example_data = QW.QPlainTextEdit( test_panel )
self._example_data.setMinimumHeight( 200 )
@ -2912,8 +2888,6 @@ class EditPageParserPanel( ClientGUIScrolledPanels.EditPanel ):
main_panel = QW.QWidget( edit_notebook )
QP.SetBackgroundColour( main_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._name = QW.QLineEdit( main_panel )
#
@ -2942,8 +2916,6 @@ class EditPageParserPanel( ClientGUIScrolledPanels.EditPanel ):
sub_page_parsers_notebook_panel = QW.QWidget( edit_notebook )
QP.SetBackgroundColour( sub_page_parsers_notebook_panel, QP.GetSystemColour( QG.QPalette.Button ) )
#
sub_page_parsers_panel = ClientGUIListCtrl.BetterListCtrlPanel( sub_page_parsers_notebook_panel )
@ -2962,8 +2934,6 @@ class EditPageParserPanel( ClientGUIScrolledPanels.EditPanel ):
test_panel = ClientGUICommon.StaticBox( self, 'test' )
QP.SetBackgroundColour( test_panel, QP.GetSystemColour( QG.QPalette.Button ) )
test_url_fetch_panel = ClientGUICommon.StaticBox( test_panel, 'fetch test data from url' )
self._test_url = QW.QLineEdit( test_url_fetch_panel )
@ -2984,8 +2954,6 @@ class EditPageParserPanel( ClientGUIScrolledPanels.EditPanel ):
content_parsers_panel = QW.QWidget( edit_notebook )
QP.SetBackgroundColour( content_parsers_panel, QP.GetSystemColour( QG.QPalette.Button ) )
#
permitted_content_types = [ HC.CONTENT_TYPE_URLS, HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_TYPE_HASH, HC.CONTENT_TYPE_TIMESTAMP, HC.CONTENT_TYPE_TITLE, HC.CONTENT_TYPE_VETO ]
@ -3461,8 +3429,6 @@ class EditParsingScriptFileLookupPanel( ClientGUIScrolledPanels.EditPanel ):
edit_panel = QW.QWidget( notebook )
QP.SetBackgroundColour( edit_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._name = QW.QLineEdit( edit_panel )
query_panel = ClientGUICommon.StaticBox( edit_panel, 'query' )
@ -3499,8 +3465,6 @@ class EditParsingScriptFileLookupPanel( ClientGUIScrolledPanels.EditPanel ):
test_panel = QW.QWidget( notebook )
QP.SetBackgroundColour( test_panel, QP.GetSystemColour( QG.QPalette.Button ) )
self._test_script_management = ScriptManagementControl( test_panel )
self._test_arg = QW.QLineEdit( test_panel )
@ -4246,6 +4210,7 @@ class ScriptManagementControl( QW.QWidget ):
menu = QW.QMenu()
for url in urls:
ClientGUIMenus.AppendMenuItem( menu, url, 'launch this url in your browser', ClientPaths.LaunchURLInWebBrowser, url )
@ -4274,8 +4239,6 @@ class TestPanel( QW.QWidget ):
test_context = ( {}, '' )
QP.SetBackgroundColour( self, QP.GetSystemColour( QG.QPalette.Button ) )
self._object_callable = object_callable
self._example_parsing_context = ClientGUIControls.StringToStringDictButton( self, 'edit example parsing context' )

View File

@ -541,8 +541,6 @@ class PopupMessageManager( QW.QWidget ):
self.setSizePolicy( QW.QSizePolicy.MinimumExpanding, QW.QSizePolicy.Preferred )
QP.SetBackgroundColour( self, QP.GetSystemColour( QG.QPalette.Button ) )
self._last_best_size_i_fit_on = ( 0, 0 )
self._max_messages_to_display = 10
@ -660,9 +658,9 @@ class PopupMessageManager( QW.QWidget ):
current_focus_tlp = QW.QApplication.activeWindow()
current_focus_tlw = QW.QApplication.activeWindow()
main_gui_is_active = current_focus_tlp in ( self, parent )
main_gui_is_active = current_focus_tlw in ( self, parent )
if new_options.GetBoolean( 'hide_message_manager_on_gui_deactive' ) and not self._DisplayingError():
@ -719,28 +717,11 @@ class PopupMessageManager( QW.QWidget ):
going_to_bug_out_at_hide_or_show = possibly_on_hidden_virtual_desktop
current_focus_tlp = QW.QApplication.activeWindow()
current_focus_tlw = QW.QApplication.activeWindow()
main_gui_is_active = current_focus_tlp in ( self, gui_frame )
self_is_active = current_focus_tlw == self
on_top_frame_is_active = False
if not main_gui_is_active and current_focus_tlp is not None:
c_f_tlp_is_resizing_frame = isinstance( current_focus_tlp, ClientGUITopLevelWindows.FrameThatResizes )
frame_parent = current_focus_tlp.parentWidget()
if c_f_tlp_is_resizing_frame and frame_parent is not None:
c_f_tlp_is_child_frame_of_main_gui = frame_parent.window() == gui_frame
if c_f_tlp_is_child_frame_of_main_gui:
on_top_frame_is_active = True
main_gui_or_child_window_is_active = ClientGUIFunctions.TLWOrChildIsActive( gui_frame )
num_messages_displayed = self._message_vbox.count()
@ -765,9 +746,8 @@ class PopupMessageManager( QW.QWidget ):
# Unhiding tends to raise the main gui tlp, which is annoying if a media viewer window has focus
# Qt port note: the on_top_frame_is_active part was uncommented originally, but it IS annoying since it leads to flickering (e.g. open the options window with this uncommented to see it in action)
show_is_not_annoying = main_gui_is_active or self._DisplayingError() or on_top_frame_is_active
# Unhiding tends to raise the main gui tlw in some window managers, which is annoying if a media viewer window has focus
show_is_not_annoying = main_gui_or_child_window_is_active or self._DisplayingError()
ok_to_show = show_is_not_annoying and not going_to_bug_out_at_hide_or_show
@ -1189,7 +1169,7 @@ class PopupMessageDialogPanel( QW.QWidget ):
# The problem is that when the window is created, the initial size is too small because all widgets are empty before the first update,
# but after it updates it doesn't want to resize the window, rather it just adds scrollbars.
# This is a manual fix. Need to find a better solution...
self.layout().update()
self._message_window.adjustSize()
self.adjustSize()

View File

@ -189,7 +189,7 @@ class InputFileSystemPredicate( ClientGUIScrolledPanels.EditPanel ):
self._predicate_panel = predicate_class( self )
self._parent = parent
self._ok = QW.QPushButton( 'OK', self )
self._ok = QW.QPushButton( 'ok', self )
self._ok.clicked.connect( self._DoOK )
QP.SetForegroundColour( self._ok, (0,128,0) )
@ -205,6 +205,19 @@ class InputFileSystemPredicate( ClientGUIScrolledPanels.EditPanel ):
def _DoOK( self ):
try:
self._predicate_panel.CheckCanOK()
except Exception as e:
message = 'Cannot OK: {}'.format( e )
QW.QMessageBox.warning( self, 'Warning', message )
return
predicates = self._predicate_panel.GetPredicates()
self._parent.SubPanelOK( predicates )
@ -250,6 +263,11 @@ class PanelPredicateSystem( QW.QWidget ):
PREDICATE_TYPE = None
def CheckCanOK( self ):
pass
def GetInfo( self ):
raise NotImplementedError()
@ -866,18 +884,18 @@ class PanelPredicateSystemKnownURLsExactURL( PanelPredicateSystem ):
if operator:
operator_description = 'has this url: '
operator_description = 'has url: '
else:
operator_description = 'does not have this url: '
operator_description = 'does not have url: '
rule_type = 'regex'
rule_type = 'exact_match'
exact_url = self._exact_url.text()
rule = re.escape( exact_url )
rule = exact_url
description = operator_description + exact_url
@ -927,11 +945,11 @@ class PanelPredicateSystemKnownURLsDomain( PanelPredicateSystem ):
operator_description = 'does not have a url with domain: '
rule_type = 'regex'
rule_type = 'domain'
domain = self._domain.text()
rule = r'^https?\:\/\/(www[^\.]*\.)?' + re.escape( domain ) + r'\/.*'
rule = domain
description = operator_description + domain
@ -966,6 +984,20 @@ class PanelPredicateSystemKnownURLsRegex( PanelPredicateSystem ):
self.setLayout( hbox )
def CheckCanOK( self ):
regex = self._regex.text()
try:
re.compile( regex )
except Exception as e:
raise Exception( 'Cannot compile that regex: {}'.format( e ) )
def GetInfo( self ):
operator = self._operator.GetValue()

View File

@ -102,7 +102,7 @@ class EditAccountTypePanel( ClientGUIScrolledPanels.EditPanel ):
#
self._title.SetValue( title )
self._title.setText( title )
#
@ -142,7 +142,7 @@ class EditAccountTypePanel( ClientGUIScrolledPanels.EditPanel ):
def GetValue( self ):
title = self._title.GetValue()
title = self._title.text()
permissions = {}
@ -3275,7 +3275,7 @@ class EditRegexFavourites( ClientGUIScrolledPanels.EditPanel ):
if dlg_2.exec() == QW.QDialog.Accepted:
description = dlg_2.value()
description = dlg_2.GetValue()
edited_row = ( regex_phrase, description )

View File

@ -1707,21 +1707,48 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
QW.QWidget.__init__( self, parent )
self._new_options = HG.client_controller.new_options
general = ClientGUICommon.StaticBox( self, 'general' )
self._verify_regular_https = QW.QCheckBox( general )
self._network_timeout = QP.MakeQSpinBox( general, min = 3, max = 600 )
if self._new_options.GetBoolean( 'advanced_mode' ):
network_timeout_min = 1
network_timeout_max = 86400 * 30
error_wait_time_min = 1
error_wait_time_max = 86400 * 30
max_network_jobs_max = 1000
max_network_jobs_per_domain_max = 100
else:
network_timeout_min = 3
network_timeout_max = 600
error_wait_time_min = 3
error_wait_time_max = 1800
max_network_jobs_max = 30
max_network_jobs_per_domain_max = 5
self._network_timeout = QP.MakeQSpinBox( general, min = network_timeout_min, max = network_timeout_max )
self._network_timeout.setToolTip( 'If a network connection cannot be made in this duration or, if once started, it experiences uninterrupted inactivity for six times this duration, it will be abandoned.' )
self._connection_error_wait_time = QP.MakeQSpinBox( general, min = 3, max = 1800 )
self._connection_error_wait_time = QP.MakeQSpinBox( general, min = error_wait_time_min, max = error_wait_time_max )
self._connection_error_wait_time.setToolTip( 'If a network connection times out as above, it will wait increasing multiples of this base time before retrying.' )
self._serverside_bandwidth_wait_time = QP.MakeQSpinBox( general, min = 3, max = 1800 )
self._serverside_bandwidth_wait_time = QP.MakeQSpinBox( general, min = error_wait_time_min, max = error_wait_time_max )
self._serverside_bandwidth_wait_time.setToolTip( 'If a server returns a failure status code indicating it is short on bandwidth, the network job will wait increasing multiples of this base time before retrying.' )
self._max_network_jobs = QP.MakeQSpinBox( general, min = 1, max = 30 )
self._max_network_jobs_per_domain = QP.MakeQSpinBox( general, min = 1, max = 5 )
self._max_network_jobs = QP.MakeQSpinBox( general, min = 1, max = max_network_jobs_max )
self._max_network_jobs_per_domain = QP.MakeQSpinBox( general, min = 1, max = max_network_jobs_per_domain_max )
#
@ -1732,8 +1759,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
#
self._new_options = HG.client_controller.new_options
self._verify_regular_https.setChecked( self._new_options.GetBoolean( 'verify_regular_https' ) )
self._http_proxy.SetValue( self._new_options.GetNoneableString( 'http_proxy' ) )
@ -1748,6 +1773,19 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
#
if self._new_options.GetBoolean( 'advanced_mode' ):
label = 'As you are in advanced mode, these options have very low and high limits. Be very careful about lowering delay time or raising max number of connections too far, as things will break.'
st = ClientGUICommon.BetterStaticText( general, label = label )
QP.SetForegroundColour( st, ( 127, 0, 0 ) )
st.setWordWrap( True )
general.Add( st, CC.FLAGS_EXPAND_PERPENDICULAR )
rows = []
rows.append( ( 'network timeout (seconds): ', self._network_timeout ) )
@ -1759,7 +1797,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( general, rows )
general.Add( gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
general.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
text = 'Enter strings such as "http://ip:port" or "http://user:pass@ip:port". It should take affect immediately on dialog ok.'
text += os.linesep * 2
@ -1775,7 +1813,11 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
text += 'It does not look like you have socks support! If you want it, try adding "pysocks" (or "requests[socks]")!'
proxy_panel.Add( QW.QLabel( text, proxy_panel ), CC.FLAGS_EXPAND_PERPENDICULAR )
st = ClientGUICommon.BetterStaticText( proxy_panel, text )
st.setWordWrap( True )
proxy_panel.Add( st, CC.FLAGS_EXPAND_PERPENDICULAR )
rows = []
@ -1784,7 +1826,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
gridbox = ClientGUICommon.WrapInGrid( proxy_panel, rows )
proxy_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
proxy_panel.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
#
@ -1869,9 +1911,18 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._show_new_on_file_seed_short_summary = QW.QCheckBox( misc )
self._show_deleted_on_file_seed_short_summary = QW.QCheckBox( misc )
self._subscription_network_error_delay = ClientGUITime.TimeDeltaButton( misc, min = 600, days = True, hours = True, minutes = True )
self._subscription_other_error_delay = ClientGUITime.TimeDeltaButton( misc, min = 600, days = True, hours = True, minutes = True )
self._downloader_network_error_delay = ClientGUITime.TimeDeltaButton( misc, min = 600, days = True, hours = True, minutes = True )
if self._new_options.GetBoolean( 'advanced_mode' ):
delay_min = 1
else:
delay_min = 600
self._subscription_network_error_delay = ClientGUITime.TimeDeltaButton( misc, min = delay_min, days = True, hours = True, minutes = True, seconds = True )
self._subscription_other_error_delay = ClientGUITime.TimeDeltaButton( misc, min = delay_min, days = True, hours = True, minutes = True, seconds = True )
self._downloader_network_error_delay = ClientGUITime.TimeDeltaButton( misc, min = delay_min, days = True, hours = True, minutes = True, seconds = True )
#
@ -2580,9 +2631,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._secret_discord_dnd_fix = QW.QCheckBox( self )
self._secret_discord_dnd_fix.setToolTip( 'This saves the lag but is potentially dangerous, as it (may) treat the from-db-files-drag as a move rather than a copy and hence only works when the drop destination will not consume the files. It requires an additional secret Alternate key to unlock.' )
self._always_show_hover_windows = QW.QCheckBox( self )
self._always_show_hover_windows.setToolTip( 'If your window manager doesn\'t like showing the hover windows on mouse-over (typically on some Linux flavours), please try this out and give the dev feedback on this forced size and position accuracy!' )
self._hide_message_manager_on_gui_iconise = QW.QCheckBox( self )
self._hide_message_manager_on_gui_iconise.setToolTip( 'If your message manager does not automatically minimise with your main gui, try this. It can lead to unusual show and positioning behaviour on window managers that do not support it, however.' )
@ -2619,8 +2667,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._secret_discord_dnd_fix.setChecked( self._new_options.GetBoolean( 'secret_discord_dnd_fix' ) )
self._always_show_hover_windows.setChecked( self._new_options.GetBoolean( 'always_show_hover_windows' ) )
self._hide_message_manager_on_gui_iconise.setChecked( self._new_options.GetBoolean( 'hide_message_manager_on_gui_iconise' ) )
self._hide_message_manager_on_gui_deactive.setChecked( self._new_options.GetBoolean( 'hide_message_manager_on_gui_deactive' ) )
@ -2647,7 +2693,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
rows.append( ( 'BUGFIX: Force this width as the minimum width for all popup messages: ', self._popup_message_force_min_width ) )
rows.append( ( 'BUGFIX: Discord file drag-and-drop fix (works for <=25, <200MB file DnDs): ', self._discord_dnd_fix ) )
rows.append( ( 'EXPERIMENTAL BUGFIX: Secret discord file drag-and-drop fix: ', self._secret_discord_dnd_fix ) )
rows.append( ( 'BUGFIX: Always show media viewer hover windows: ', self._always_show_hover_windows ) )
rows.append( ( 'BUGFIX: Hide the popup message manager when the main gui is minimised: ', self._hide_message_manager_on_gui_iconise ) )
rows.append( ( 'BUGFIX: Hide the popup message manager when the main gui loses focus: ', self._hide_message_manager_on_gui_deactive ) )
@ -2726,7 +2771,6 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._new_options.SetBoolean( 'discord_dnd_fix', self._discord_dnd_fix.isChecked() )
self._new_options.SetBoolean( 'secret_discord_dnd_fix', self._secret_discord_dnd_fix.isChecked() )
self._new_options.SetBoolean( 'always_show_hover_windows', self._always_show_hover_windows.isChecked() )
self._new_options.SetBoolean( 'hide_message_manager_on_gui_iconise', self._hide_message_manager_on_gui_iconise.isChecked() )
self._new_options.SetBoolean( 'hide_message_manager_on_gui_deactive', self._hide_message_manager_on_gui_deactive.isChecked() )
@ -3324,6 +3368,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
def EventZoomsChanged( self, text ):
try:
@ -3833,7 +3878,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._qt_style_name = ClientGUICommon.BetterChoice( self )
self._qt_stylesheet_name = ClientGUICommon.BetterChoice( self )
self._qt_style_name.addItem( 'use default ("{}")'.format( ClientGUIStyle.ORIGINAL_STYLE ), None )
self._qt_style_name.addItem( 'use default ("{}")'.format( ClientGUIStyle.ORIGINAL_STYLE_NAME ), None )
try:
@ -3900,22 +3945,36 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
qt_style_name = self._qt_style_name.GetValue()
qt_stylesheet_name = self._qt_stylesheet_name.GetValue()
if qt_style_name is None:
try:
ClientGUIStyle.SetStyle( ClientGUIStyle.ORIGINAL_STYLE )
if qt_style_name is None:
ClientGUIStyle.SetStyleFromName( ClientGUIStyle.ORIGINAL_STYLE_NAME )
else:
ClientGUIStyle.SetStyleFromName( qt_style_name )
else:
except Exception as e:
ClientGUIStyle.SetStyle( qt_style_name )
QW.QMessageBox.critical( self, 'Critical', 'Could not apply style: {}'.format( str( e ) ) )
if qt_stylesheet_name is None:
try:
ClientGUIStyle.ClearStylesheet()
if qt_stylesheet_name is None:
ClientGUIStyle.ClearStylesheet()
else:
ClientGUIStyle.SetStylesheetFromPath( qt_stylesheet_name )
else:
except Exception as e:
ClientGUIStyle.SetStylesheet( qt_stylesheet_name )
QW.QMessageBox.critical( self, 'Critical', 'Could not apply stylesheet: {}'.format( str( e ) ) )
@ -4891,7 +4950,7 @@ class ManageShortcutsPanel( ClientGUIScrolledPanels.ManagePanel ):
if result == QW.QDialog.Accepted:
self._custom_shortcuts.RemoveAllSelected()
self._custom_shortcuts.DeleteSelected()

View File

@ -548,7 +548,7 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ):
with QP.DirDialog( self, message = 'Choose new database location.' ) as dlg:
dlg.SetPath( source )
dlg.setDirectory( source )
if dlg.exec() == QW.QDialog.Accepted:
@ -728,7 +728,7 @@ class MigrateDatabasePanel( ClientGUIScrolledPanels.ReviewPanel ):
if self._ideal_thumbnails_location_override is not None:
dlg.SetPath( self._ideal_thumbnails_location_override )
dlg.setDirectory( self._ideal_thumbnails_location_override )
if dlg.exec() == QW.QDialog.Accepted:

View File

@ -2,8 +2,6 @@ from . import ClientConstants as CC
from . import ClientData
from . import ClientGUICommon
from . import ClientGUIFunctions
from . import ClientGUIScrolledPanels
from . import ClientGUITopLevelWindows
from . import HydrusConstants as HC
from . import HydrusData
from . import HydrusGlobals as HG
@ -12,23 +10,46 @@ from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from qtpy import QtGui as QG
from . import QtPorting as QP
# ok, the problem here is that I get key codes that are converted, so if someone does shift+1 on a US keyboard, this ends up with Shift+! same with ctrl+alt+ to get accented characters
# it isn't really a big deal since everything still lines up, but the QGuiApplicationPrivate::platformIntegration()->possibleKeys(e) to get some variant of 'yeah this is just !' seems unavailable for python
# it is basically a display bug, but it'd be nice to have it working right
def ConvertQtKeyToShortcutKey( key_qt ):
if key_qt in CC.special_key_shortcut_enum_lookup:
key_ord = CC.special_key_shortcut_enum_lookup[ key_qt ]
return ( CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL, key_ord )
else:
try:
key_ord = int( key_qt )
key_chr = chr( key_ord )
# this is turbo lower() that converts Scharfes S (beta) to 'ss'
key_chr = key_chr.casefold()[0]
casefold_key_ord = ord( key_chr )
return ( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, casefold_key_ord )
except:
return ( CC.SHORTCUT_TYPE_NOT_ALLOWED, key_ord )
def ConvertKeyEventToShortcut( event ):
key = event.key()
key_qt = event.key()
if key in CC.special_key_shortcut_enum_lookup or ClientData.OrdIsSensibleASCII( key ):
if key in CC.special_key_shortcut_enum_lookup:
shortcut_type = CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL
key = CC.special_key_shortcut_enum_lookup[ key ]
else:
shortcut_type = CC.SHORTCUT_TYPE_KEYBOARD_ASCII
key = int( key )
( shortcut_type, key_ord ) = ConvertQtKeyToShortcutKey( key_qt )
if shortcut_type != CC.SHORTCUT_TYPE_NOT_ALLOWED:
modifiers = []
@ -66,7 +87,7 @@ def ConvertKeyEventToShortcut( event ):
modifiers.append( CC.SHORTCUT_MODIFIER_KEYPAD )
shortcut = Shortcut( shortcut_type, key, modifiers )
shortcut = Shortcut( shortcut_type, key_ord, modifiers )
if HG.gui_report_mode:
@ -158,7 +179,7 @@ def ConvertMouseEventToShortcut( event ):
return None
def IShouldCatchShortcutEvent( evt_handler, event = None, child_tlp_classes_who_can_pass_up = None ):
def IShouldCatchShortcutEvent( evt_handler, event = None, child_tlw_classes_who_can_pass_up = None ):
do_focus_test = True
@ -170,13 +191,13 @@ def IShouldCatchShortcutEvent( evt_handler, event = None, child_tlp_classes_who_
if do_focus_test:
if not ClientGUIFunctions.TLPIsActive( evt_handler ):
if not ClientGUIFunctions.TLWIsActive( evt_handler ):
if child_tlp_classes_who_can_pass_up is not None:
if child_tlw_classes_who_can_pass_up is not None:
child_tlp_has_focus = ClientGUIFunctions.WindowOrAnyTLPChildHasFocus( evt_handler ) and isinstance( QW.QApplication.activeWindow(), child_tlp_classes_who_can_pass_up )
child_tlw_has_focus = ClientGUIFunctions.WidgetOrAnyTLWChildHasFocus( evt_handler ) and isinstance( QW.QApplication.activeWindow(), child_tlw_classes_who_can_pass_up )
if not child_tlp_has_focus:
if not child_tlw_has_focus:
return False
@ -213,7 +234,7 @@ class Shortcut( HydrusSerialisable.SerialisableBase ):
modifiers = []
if shortcut_type == CC.SHORTCUT_TYPE_KEYBOARD_ASCII and ClientData.OrdIsAlphaUpper( shortcut_key ):
if shortcut_type == CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER and ClientData.OrdIsAlphaUpper( shortcut_key ):
shortcut_key += 32 # convert A to a
@ -299,7 +320,7 @@ class Shortcut( HydrusSerialisable.SerialisableBase ):
330 : ord( '6' ),
331 : ord( '7' ),
332 : ord( '8' ),
332 : ord( '9' ),
333 : ord( '9' ),
388 : ord( '+' ),
392 : ord( '/' ),
390 : ord( '-' ),
@ -322,7 +343,7 @@ class Shortcut( HydrusSerialisable.SerialisableBase ):
( shortcut_type, shortcut_key, modifiers ) = old_serialisable_info
if shortcut_type == CC.SHORTCUT_TYPE_KEYBOARD_ASCII:
if shortcut_type == CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER:
if shortcut_key in wx_to_qt_flat_conversion:
@ -352,7 +373,7 @@ class Shortcut( HydrusSerialisable.SerialisableBase ):
if shortcut_type == CC.SHORTCUT_TYPE_KEYBOARD_ASCII:
if shortcut_type == CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER:
if ClientData.OrdIsAlphaUpper( shortcut_key ):
@ -403,7 +424,7 @@ class Shortcut( HydrusSerialisable.SerialisableBase ):
components.append( CC.special_key_shortcut_str_lookup[ self._shortcut_key ] )
elif self._shortcut_type == CC.SHORTCUT_TYPE_KEYBOARD_ASCII and ClientData.OrdIsSensibleASCII( self._shortcut_key ):
elif self._shortcut_type == CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER:
if ClientData.OrdIsAlphaUpper( self._shortcut_key ):
@ -658,7 +679,7 @@ class ShortcutSet( HydrusSerialisable.SerialisableBaseNamed ):
modifiers = []
shortcut = Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, key, modifiers )
shortcut = Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, key, modifiers )
if serialisable_service_key is None:

View File

@ -6,12 +6,14 @@ from qtpy import QtWidgets as QW
STYLESHEET_DIR = os.path.join( HC.BASE_DIR, 'static', 'qss' )
ORIGINAL_STYLE = None
ORIGINAL_STYLE_NAME = None
CURRENT_STYLE_NAME = None
ORIGINAL_STYLESHEET = None
CURRENT_STYLESHEET = None
def ClearStylesheet():
QW.QApplication.instance().setStyleSheet( ORIGINAL_STYLESHEET )
SetStyleSheet( ORIGINAL_STYLESHEET )
def GetAvailableStyles():
@ -19,10 +21,6 @@ def GetAvailableStyles():
return list( QW.QStyleFactory.keys() )
def GetCurrentStyleName():
return QW.QApplication.instance().style().objectName()
def GetAvailableStylesheets():
if not os.path.exists( STYLESHEET_DIR ) or not os.path.isdir( STYLESHEET_DIR ):
@ -46,33 +44,57 @@ def GetAvailableStylesheets():
def InitialiseDefaults():
global ORIGINAL_STYLE
global ORIGINAL_STYLE_NAME
global CURRENT_STYLE_NAME
ORIGINAL_STYLE = GetCurrentStyleName()
ORIGINAL_STYLE_NAME = QW.QApplication.instance().style().objectName()
CURRENT_STYLE_NAME = ORIGINAL_STYLE_NAME
global ORIGINAL_STYLESHEET
global CURRENT_STYLESHEET
ORIGINAL_STYLESHEET = QW.QApplication.instance().styleSheet()
CURRENT_STYLESHEET = ORIGINAL_STYLESHEET
def SetStyle( name ):
def SetStyleFromName( name ):
current_name = GetCurrentStyleName()
global CURRENT_STYLE_NAME
if name == current_name:
if name == CURRENT_STYLE_NAME:
return
if name in QW.QStyleFactory.keys():
if name in GetAvailableStyles():
QW.QApplication.instance().setStyle( QW.QStyleFactory.create( name ) )
try:
QW.QApplication.instance().setStyle( name )
CURRENT_STYLE_NAME = name
except Exception as e:
raise HydrusExceptions.DataMissing( 'Style "{}" could not be generated/applied. If this is the default, perhaps a third-party custom style, you may have to restart the client to re-set it. Extra error info: {}'.format( name, e ) )
else:
raise HydrusExceptions.DataMissing( 'Style "{}" does not exist!'.format( name ) )
raise HydrusExceptions.DataMissing( 'Style "{}" does not exist! If this is the default, perhaps a third-party custom style, you may have to restart the client to re-set it.'.format( name ) )
def SetStylesheet( filename ):
def SetStyleSheet( stylesheet ):
global CURRENT_STYLESHEET
if CURRENT_STYLESHEET != stylesheet:
QW.QApplication.instance().setStyleSheet( stylesheet )
CURRENT_STYLESHEET = stylesheet
def SetStylesheetFromPath( filename ):
path = os.path.join( STYLESHEET_DIR, filename )
@ -86,5 +108,5 @@ def SetStylesheet( filename ):
qss = f.read()
QW.QApplication.instance().setStyleSheet( qss )
SetStyleSheet( qss )

View File

@ -1270,17 +1270,17 @@ class ManageTagsPanel( ClientGUIScrolledPanels.ManagePanel ):
elif action == 'focus_media_viewer':
tlps = ClientGUIFunctions.GetTLPParents( self )
tlws = ClientGUIFunctions.GetTLWParents( self )
from . import ClientGUICanvas
command_processed = False
for tlp in tlps:
for tlw in tlws:
if isinstance( tlp, ClientGUICanvas.CanvasFrame ):
if isinstance( tlw, ClientGUICanvas.CanvasFrame ):
tlp.TakeFocusForUser()
tlw.TakeFocusForUser()
command_processed = True

View File

@ -5,6 +5,7 @@ from . import ClientGUITopLevelWindows
from . import ClientImporting
from . import ClientImportOptions
from . import HydrusData
from . import HydrusGlobals as HG
import os
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
@ -51,19 +52,34 @@ class EditCheckerOptions( ClientGUIScrolledPanels.EditPanel ):
#
if HG.client_controller.new_options.GetBoolean( 'advanced_mode' ):
never_faster_than_min = 1
never_slower_than_min = 1
flat_check_period_min = 1
else:
never_faster_than_min = 30
never_slower_than_min = 600
flat_check_period_min = 180
self._reactive_check_panel = ClientGUICommon.StaticBox( self, 'reactive checking' )
self._intended_files_per_check = QP.MakeQSpinBox( self._reactive_check_panel, min=1, max=1000 )
self._never_faster_than = TimeDeltaCtrl( self._reactive_check_panel, min = 30, days = True, hours = True, minutes = True, seconds = True )
self._never_faster_than = TimeDeltaCtrl( self._reactive_check_panel, min = never_faster_than_min, days = True, hours = True, minutes = True, seconds = True )
self._never_slower_than = TimeDeltaCtrl( self._reactive_check_panel, min = 600, days = True, hours = True, minutes = True )
self._never_slower_than = TimeDeltaCtrl( self._reactive_check_panel, min = never_slower_than_min, days = True, hours = True, minutes = True, seconds = True )
#
self._static_check_panel = ClientGUICommon.StaticBox( self, 'static checking' )
self._flat_check_period = TimeDeltaCtrl( self._static_check_panel, min = 180, days = True, hours = True, minutes = True )
self._flat_check_period = TimeDeltaCtrl( self._static_check_panel, min = flat_check_period_min, days = True, hours = True, minutes = True, seconds = True )
#
@ -79,6 +95,8 @@ class EditCheckerOptions( ClientGUIScrolledPanels.EditPanel ):
#
#
rows = []
rows.append( ( 'intended new files per check: ', self._intended_files_per_check ) )
@ -113,6 +131,20 @@ class EditCheckerOptions( ClientGUIScrolledPanels.EditPanel ):
QP.AddToLayout( vbox, help_hbox, CC.FLAGS_BUTTON_SIZER )
QP.AddToLayout( vbox, defaults_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
if HG.client_controller.new_options.GetBoolean( 'advanced_mode' ):
label = 'As you are in advanced mode, these options have extremely low limits. This is intended only for testing and small scale private network tasks. Do not use very fast check times for real world use on public websites, as it is wasteful and rude, hydrus will be overloaded with high-CPU parsing work, and you may get your IP banned.'
st = ClientGUICommon.BetterStaticText( self, label = label )
QP.SetForegroundColour( st, ( 127, 0, 0 ) )
st.setWordWrap( True )
QP.AddToLayout( vbox, st, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._reactive_check_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
QP.AddToLayout( vbox, self._static_check_panel, CC.FLAGS_EXPAND_PERPENDICULAR )

View File

@ -336,8 +336,6 @@ class NewDialog( QP.Dialog ):
self._new_options = HG.client_controller.new_options
QP.SetBackgroundColour( self, QP.GetSystemColour( QG.QPalette.Button ) )
self.setWindowIcon( QG.QIcon( HG.client_controller.frame_icon_pixmap ) )
HG.client_controller.ResetIdleTimer()
@ -483,28 +481,35 @@ class NewDialog( QP.Dialog ):
if event_object is not None:
tlp = event_object.window()
tlw = event_object.window()
if tlp != self:
if tlw != self:
return
self._TryEndModal( QW.QDialog.Accepted )
def EventDialogButtonCancel( self ):
if not self or not QP.isValid( self ):
return
event_object = self.sender()
if event_object is not None:
tlp = event_object.window()
tlw = event_object.window()
if tlp != self:
if tlw != self:
return
self._TryEndModal( QW.QDialog.Rejected )
@ -784,8 +789,6 @@ class Frame( QW.QWidget ):
self._last_move_pub = 0.0
QP.SetBackgroundColour( self, QP.GetSystemColour( QG.QPalette.Button ) )
self.setWindowIcon( QG.QIcon( HG.client_controller.frame_icon_pixmap ) )
self._widget_event_filter = QP.WidgetEventFilter( self )
@ -829,8 +832,6 @@ class MainFrame( QW.QMainWindow ):
self._new_options = HG.client_controller.new_options
QP.SetBackgroundColour( self, QP.GetSystemColour( QG.QPalette.Button ) )
self.setWindowIcon( QG.QIcon( HG.client_controller.frame_icon_pixmap ) )
self._widget_event_filter = QP.WidgetEventFilter( self )

View File

@ -1746,13 +1746,25 @@ class SubscriptionsManager( object ):
with self._lock:
message = '{} subs: {}'.format( HydrusData.ToHumanInt( len( self._current_subscription_names ) ), ', '.join( self._current_subscription_names ) )
subs = list( self._current_subscription_names )
subs.sort()
running = list( self._running_subscriptions.keys() )
running.sort()
cannot_run = list( self._names_that_cannot_run )
cannot_run.sort()
next_times = list( self._names_to_next_work_time.items() )
next_times.sort( key = lambda n, nwt: nwt )
message = '{} subs: {}'.format( HydrusData.ToHumanInt( len( self._current_subscription_names ) ), ', '.join( subs ) )
message += os.linesep * 2
message += '{} running: {}'.format( HydrusData.ToHumanInt( len( self._running_subscriptions ) ), ', '.join( self._running_subscriptions.keys() ) )
message += '{} running: {}'.format( HydrusData.ToHumanInt( len( self._running_subscriptions ) ), ', '.join( running ) )
message += os.linesep * 2
message += '{} not runnable: {}'.format( HydrusData.ToHumanInt( len( self._names_that_cannot_run ) ), ', '.join( self._names_that_cannot_run ) )
message += '{} not runnable: {}'.format( HydrusData.ToHumanInt( len( self._names_that_cannot_run ) ), ', '.join( cannot_run ) )
message += os.linesep * 2
message += '{} next times: {}'.format( HydrusData.ToHumanInt( len( self._names_to_next_work_time ) ), ', '.join( ( '{}: {}'.format( name, HydrusData.TimestampToPrettyTimeDelta( next_work_time ) ) for ( name, next_work_time ) in self._names_to_next_work_time.items() ) ) )
message += '{} next times: {}'.format( HydrusData.ToHumanInt( len( self._names_to_next_work_time ) ), ', '.join( ( '{}: {}'.format( name, HydrusData.TimestampToPrettyTimeDelta( next_work_time ) ) for ( name, next_work_time ) in next_times ) ) )
HydrusData.ShowText( message )

View File

@ -1703,7 +1703,7 @@ class MediaList( object ):
physically_deleted = service_key in ( CC.TRASH_SERVICE_KEY, CC.COMBINED_LOCAL_FILE_SERVICE_KEY )
trashed = service_key in local_file_domains
deleted_from_our_domain = service_key = self._file_service_key
deleted_from_our_domain = service_key == self._file_service_key
physically_deleted_and_local_view = physically_deleted and self._file_service_key in all_local_file_services
@ -2075,19 +2075,19 @@ class MediaCollection( MediaList, Media ):
self._RecalcInternals()
def RefreshFileInfo( self ):
def ResetService( self, service_key ):
for media in self._sorted_media:
media.RefreshFileInfo()
MediaList.ResetService( self, service_key )
self._RecalcInternals()
def ResetService( self, service_key ):
def UpdateFileInfo( self, hashes_to_media_results ):
MediaList.ResetService( self, service_key )
for media in self._sorted_media:
media.UpdateFileInfo( hashes_to_media_results )
self._RecalcInternals()
@ -2431,13 +2431,13 @@ class MediaSingleton( Media ):
return True
def RefreshFileInfo( self ):
def UpdateFileInfo( self, hashes_to_media_results ):
media_results = HG.client_controller.Read( 'media_results', ( self._media_result.GetHash(), ) )
hash = self.GetHash()
if len( media_results ) > 0:
if hash in hashes_to_media_results:
media_result = media_results[0]
media_result = hashes_to_media_results[ hash ]
self._media_result = media_result

View File

@ -3127,6 +3127,11 @@ class URLClass( HydrusSerialisable.SerialisableBaseNamed ):
def MatchesSubdomains( self ):
return self._match_subdomains
def Normalise( self, url ):
p = urllib.parse.urlparse( url )

View File

@ -1021,12 +1021,14 @@ class NetworkJob( object ):
while not request_completed:
try:
if self._IsCancelled():
if self._IsCancelled():
return
return
response = None
try:
response = self._SendRequestAndGetResponse()
@ -1163,6 +1165,15 @@ class NetworkJob( object ):
self._WaitOnConnectionError( 'read timed out' )
finally:
if response is not None:
# if full data was not read, the response will hang around in connection pool longer than we want
# so just an explicit close here
response.close()
except Exception as e:

View File

@ -44,8 +44,6 @@ class ClientOptions( HydrusSerialisable.SerialisableBase ):
self._dictionary[ 'booleans' ][ 'advanced_mode' ] = False
self._dictionary[ 'booleans' ][ 'always_show_hover_windows' ] = False
self._dictionary[ 'booleans' ][ 'apply_all_parents_to_all_services' ] = False
self._dictionary[ 'booleans' ][ 'apply_all_siblings_to_all_services' ] = False
self._dictionary[ 'booleans' ][ 'filter_inbox_and_archive_predicates' ] = False

View File

@ -67,7 +67,7 @@ options = {}
# Misc
NETWORK_VERSION = 18
SOFTWARE_VERSION = 376
SOFTWARE_VERSION = 377
CLIENT_API_VERSION = 11
SERVER_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -216,17 +216,22 @@ class DirPickerCtrl( QW.QWidget ):
self.setLayout( layout )
def SetPath( self, path ):
self._path_edit.setText( path )
def GetPath( self ):
return self._path_edit.text()
def _Browse( self ):
path = QW.QFileDialog.getExistingDirectory( None, '', self._path_edit.text() )
existing_path = self._path_edit.text()
path = QW.QFileDialog.getExistingDirectory( self, '', existing_path )
if path == '':
@ -295,27 +300,29 @@ class FilePickerCtrl( QW.QWidget ):
def _Browse( self ):
existing_path = self._path_edit.text()
if self._save_mode:
if self._wildcard:
path = QW.QFileDialog.getSaveFileName( None, '', self._path_edit.text(), filter = self._wildcard, selectedFilter = self._wildcard )[0]
path = QW.QFileDialog.getSaveFileName( self, '', existing_path, filter = self._wildcard, selectedFilter = self._wildcard )[0]
else:
path = QW.QFileDialog.getSaveFileName( None, '', self._path_edit.text() )[0]
path = QW.QFileDialog.getSaveFileName( self, '', existing_path )[0]
else:
if self._wildcard:
path = QW.QFileDialog.getOpenFileName( None, '', self._path_edit.text(), filter = self._wildcard, selectedFilter = self._wildcard )[0]
path = QW.QFileDialog.getOpenFileName( self, '', existing_path, filter = self._wildcard, selectedFilter = self._wildcard )[0]
else:
path = QW.QFileDialog.getOpenFileName( None, '', self._path_edit.text() )[0]
path = QW.QFileDialog.getOpenFileName( self, '', existing_path )[0]
@ -1249,7 +1256,6 @@ def GetSystemColour( colour ):
return QG.QPalette().color( colour )
def CenterOnWindow( parent, window ):
parent_window = parent.window()
@ -1402,7 +1408,7 @@ def SetBackgroundColour( widget, colour ):
if isinstance( colour, QG.QColor ):
widget.setStyleSheet( '#{} {{ background-color: {} }}'.format( object_name, colour.name()) )
elif isinstance( colour, tuple ):
widget.setStyleSheet( '#{} {{ background-color: {} }}'.format( object_name, TupleToQColor( colour ).name() ) )
@ -1410,8 +1416,8 @@ def SetBackgroundColour( widget, colour ):
else:
widget.setStyleSheet( '#{} {{ background-color: {} }}'.format( object_name, QG.QColor( colour ).name() ) )
def SetForegroundColour( widget, colour ):
widget.setAutoFillBackground( True )
@ -1421,8 +1427,9 @@ def SetForegroundColour( widget, colour ):
if not object_name:
object_name = str( id( widget ) )
widget.setObjectName( object_name )
if isinstance( colour, QG.QColor ):
@ -1735,7 +1742,7 @@ class CheckListBox( QW.QListWidget ):
if event.button() == QC.Qt.RightButton:
self.rightClicked.emit()
else:
QW.QListWidget.mousePressEvent( self, event )
@ -2139,14 +2146,16 @@ class TreeWidgetWithInheritedCheckState( QW.QTreeWidget ):
def __init__( self, *args, **kwargs ):
QW.QTreeWidget.__init__( *args, **kwargs )
QW.QTreeWidget.__init__( self, *args, **kwargs )
self.itemClicked.connect( self._UpdateCheckState )
def _HandleItemClickedForCheckStateUpdate( self, item, column ):
self._UpdateCheckState( item, item.checkState() )
def _UpdateCheckState( self, item, check_state ):
item.setCheckState( check_state )

View File

@ -399,8 +399,8 @@ class TestSerialisables( unittest.TestCase ):
shortcuts.append( ( ClientGUIShortcuts.Shortcut(), 'f7' ) )
shortcuts.append( ( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL, CC.SHORTCUT_KEY_SPECIAL_SPACE, [] ), 'space' ) )
shortcuts.append( ( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'a' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), 'ctrl+a' ) )
shortcuts.append( ( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'A' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), 'ctrl+a' ) )
shortcuts.append( ( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'a' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), 'ctrl+a' ) )
shortcuts.append( ( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'A' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), 'ctrl+a' ) )
shortcuts.append( ( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL, CC.SHORTCUT_KEY_SPECIAL_HOME, [ CC.SHORTCUT_MODIFIER_ALT, CC.SHORTCUT_MODIFIER_CTRL ] ), 'ctrl+alt+home' ) )
shortcuts.append( ( ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_LEFT, [] ), 'left-click' ) )
@ -437,8 +437,8 @@ class TestSerialisables( unittest.TestCase ):
command_3 = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( CC.DEFAULT_LOCAL_TAG_SERVICE_KEY, HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_FLIP, 'test' ) )
k_shortcut_1 = ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL, CC.SHORTCUT_KEY_SPECIAL_SPACE, [] )
k_shortcut_2 = ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'a' ), [ CC.SHORTCUT_MODIFIER_CTRL ] )
k_shortcut_3 = ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_ASCII, ord( 'A' ), [ CC.SHORTCUT_MODIFIER_CTRL ] )
k_shortcut_2 = ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'a' ), [ CC.SHORTCUT_MODIFIER_CTRL ] )
k_shortcut_3 = ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_CHARACTER, ord( 'A' ), [ CC.SHORTCUT_MODIFIER_CTRL ] )
k_shortcut_4 = ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD_SPECIAL, CC.SHORTCUT_KEY_SPECIAL_HOME, [ CC.SHORTCUT_MODIFIER_ALT, CC.SHORTCUT_MODIFIER_CTRL ] )
m_shortcut_1 = ClientGUIShortcuts.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_LEFT, [] )