Version 251
This commit is contained in:
parent
1034350dd8
commit
b4049ccb7f
|
@ -8,8 +8,35 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 251</h3></li>
|
||||
<ul>
|
||||
<li>started shortcut overhaul by updating shortcut storage and underlying objects</li>
|
||||
<li>shortcuts will now support multiple modifiers (ctrl, alt, shift) in some places</li>
|
||||
<li>the regular shortcut entry control now supports mouse events through a radiobox (although only the duplicate filter will capture and deal with mouse events on the new system!)</li>
|
||||
<li>on db creation or update, a new 'duplicate_filter' shortcut set will be generated that includes mouse shortcuts and new duplicate_filter commands</li>
|
||||
<li>the duplicate filter now obeys this shortcut set</li>
|
||||
<li>duplicate filter shortcut edit cog menu now works, and the active shortcut set will update if edited</li>
|
||||
<li>wrote simple duplicate pair status update db code</li>
|
||||
<li>added dupe filtering optimisation--given A > B, anything else better than A will be set to be better than B, and anything worse than B will be set to be worse than A</li>
|
||||
<li>added dupe filtering optimisation--after any duplicate status change affecting A and B, any 'same file' siblings of A and B will receive the same relationship</li>
|
||||
<li>the 'process now' button on review services is now gated by a yes/no dialog that better explains what's about to happen</li>
|
||||
<li>the repository buttons on review services will disable when they can't do anything</li>
|
||||
<li>repository update processing will now cancel mid-job much faster</li>
|
||||
<li>reintroduced the 'service-wide update' button to tag services in review services</li>
|
||||
<li>reintroduced the 'clear trash' button to the trash file service in review services</li>
|
||||
<li>reintroduced num_files rating service reporting in review services</li>
|
||||
<li>fixed mouse scroll wheel events from the ratings hover window not being correctly processed by the main media canvas</li>
|
||||
<li>added some 'touch' event detection to try to better deal with media dragging through a touchscreen</li>
|
||||
<li>import folders will now 'action' their import paths as they go, rather than only at the end of their import run</li>
|
||||
<li>the new taglists are now better at remembering their selection through a content change</li>
|
||||
<li>opening a file or path from a non-windows client should now create a non-child process for open/xdg-open calls that block (so closing the client should not then close the child process movie player application or whatever)</li>
|
||||
<li>added unit tests for the new shortcut object</li>
|
||||
<li>added unit tests for the new application command object</li>
|
||||
<li>fixed unit tests for the updated shortcuts object</li>
|
||||
<li>misc cleanup</li>
|
||||
</ul>
|
||||
<li><h3>version 250</h3></li>
|
||||
<ul>
|
||||
<li>improved 'file storage locations' help page descriptive text, including adding statements for actual current storage percentages</li>
|
||||
<li>improved boot missing file storage error handling</li>
|
||||
<li>wrote a 'repair file storage' dialog panel to manually fix missing folders during the boot phase</li>
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
<p>If you are sure you want to keep a file long-term, you should <b>archive</b> it, which will remove it from the inbox. You can archive from your selected thumbnails' right-click menu, or by pressing F7. If you make a mistake, you can spam Ctrl-Z for undo or hit Shift-F7 on any set of files to explicitly return them to the inbox.</p>
|
||||
<p>Anything you do not want to keep should be deleted by selecting from the right-click menu or by hitting the delete key. Deleted files are sent to the trash. They will get a little trash icon:</p>
|
||||
<p><img src="processed_imports.png" /></p>
|
||||
<p>A trashed file will not appear in subsequent normal searches, although you can search the trash specifically by clicking the 'local files' button on the autocomplete dropdown. Undeleting a file (shift+delete) will return it to 'local files' as if nothing had happened. Files that remain in the trash will be permanently deleted, usually after a few days. You can change this behaviour in the client's options.</p>
|
||||
<p>A trashed file will not appear in subsequent normal searches, although you can search the trash specifically by clicking the 'my files' button on the autocomplete dropdown and changing the file domain to 'trash'. Undeleting a file (shift+delete) will return it to 'my files' as if nothing had happened. Files that remain in the trash will be permanently deleted, usually after a few days. You can change the permanent deletion behaviour in the client's options.</p>
|
||||
<p>A quick way of processing new files is—</p>
|
||||
<h3>filtering</h3>
|
||||
<p>Lets say you just downloaded a good thread, or perhaps you just imported an old folder of miscellany. You now have a whole bunch of files in your inbox—some good, some awful. You probably want to quickly go through them, saying <i>yes, yes, yes, no, yes, no, no, yes</i>, where <i>yes</i> means 'keep and archive' and <i>no</i> means 'delete this trash'. <b>Filtering</b> is the solution.</p>
|
||||
|
|
|
@ -11,6 +11,9 @@ ID_TIMER_UPDATES = wx.NewId()
|
|||
|
||||
#
|
||||
|
||||
APPLICATION_COMMAND_TYPE_SIMPLE = 0
|
||||
APPLICATION_COMMAND_TYPE_CONTENT = 1
|
||||
|
||||
BLANK_PHASH = '\x80\x00\x00\x00\x00\x00\x00\x00' # first bit 1 but everything else 0 means only significant part of dct was [0,0], which represents flat colour
|
||||
|
||||
CAN_HIDE_MOUSE = True
|
||||
|
@ -226,6 +229,40 @@ media_viewer_scale_string_lookup[ MEDIA_VIEWER_SCALE_100 ] = 'show at 100%'
|
|||
media_viewer_scale_string_lookup[ MEDIA_VIEWER_SCALE_MAX_REGULAR ] = 'scale to the largest regular zoom that fits'
|
||||
media_viewer_scale_string_lookup[ MEDIA_VIEWER_SCALE_TO_CANVAS ] = 'scale to the canvas size'
|
||||
|
||||
SHORTCUT_MODIFIER_CTRL = 0
|
||||
SHORTCUT_MODIFIER_ALT = 1
|
||||
SHORTCUT_MODIFIER_SHIFT = 2
|
||||
|
||||
shortcut_wx_to_hydrus_lookup = {}
|
||||
|
||||
shortcut_wx_to_hydrus_lookup[ wx.ACCEL_ALT ] = SHORTCUT_MODIFIER_ALT
|
||||
shortcut_wx_to_hydrus_lookup[ wx.ACCEL_CTRL ] = SHORTCUT_MODIFIER_CTRL
|
||||
shortcut_wx_to_hydrus_lookup[ wx.ACCEL_CMD ] = SHORTCUT_MODIFIER_CTRL
|
||||
shortcut_wx_to_hydrus_lookup[ wx.ACCEL_SHIFT ] = SHORTCUT_MODIFIER_SHIFT
|
||||
|
||||
SHORTCUT_MOUSE_LEFT = 0
|
||||
SHORTCUT_MOUSE_RIGHT = 1
|
||||
SHORTCUT_MOUSE_MIDDLE = 2
|
||||
SHORTCUT_MOUSE_SCROLL_UP = 3
|
||||
SHORTCUT_MOUSE_SCROLL_DOWN = 4
|
||||
|
||||
shortcut_mouse_string_lookup = {}
|
||||
|
||||
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_LEFT ] = 'left-click'
|
||||
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_RIGHT ] = 'right-click'
|
||||
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_MIDDLE ] = 'middle-click'
|
||||
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_SCROLL_UP ] = 'scroll up'
|
||||
shortcut_mouse_string_lookup[ SHORTCUT_MOUSE_SCROLL_DOWN ] = 'scroll down'
|
||||
|
||||
SHORTCUT_TYPE_KEYBOARD = 0
|
||||
SHORTCUT_TYPE_MOUSE = 1
|
||||
|
||||
SHORTCUTS_RESERVED_NAMES = [ 'duplicate_filter' ]
|
||||
|
||||
# shortcut commands
|
||||
|
||||
DUPLICATE_FILTER_ACTIONS = [ 'duplicate_filter_this_is_better', 'duplicate_filter_exactly_the_same', 'duplicate_filter_alternates', 'duplicate_filter_not_dupes', 'duplicate_filter_custom_action', 'duplicate_filter_skip' ]
|
||||
|
||||
SHUTDOWN_TIMESTAMP_VACUUM = 0
|
||||
SHUTDOWN_TIMESTAMP_FATTEN_AC_CACHE = 1
|
||||
SHUTDOWN_TIMESTAMP_DELETE_ORPHANS = 2
|
||||
|
|
|
@ -823,7 +823,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
self._CacheSimilarFilesDisassociatePHashes( hash_id, phash_ids )
|
||||
|
||||
self._c.execute( 'DELETE FROM shape_search_cache WHERE hash_id = ?;', ( hash_id, ) )
|
||||
self._c.execute( 'DELETE FROM duplicate_pairs WHERE smaller_hash_id = ? or larger_hash_id = ?;', ( hash_id, hash_id ) )
|
||||
self._c.execute( 'DELETE FROM shape_maintenance_phash_regen WHERE hash_id = ?;', ( hash_id, ) )
|
||||
|
||||
|
||||
|
@ -1001,6 +1000,8 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
duplicate_types_to_count = collections.Counter( dict( self._c.execute( 'SELECT duplicate_type, COUNT( * ) FROM ' + table_join + ' WHERE ' + predicate_string + ' GROUP BY duplicate_type;' ) ) )
|
||||
|
||||
duplicate_types_to_count[ HC.DUPLICATE_BETTER ] = duplicate_types_to_count[ HC.DUPLICATE_SMALLER_BETTER ] + duplicate_types_to_count[ HC.DUPLICATE_LARGER_BETTER ]
|
||||
|
||||
return ( num_phashes_to_regen, num_branches_to_regen, searched_distances_to_count, duplicate_types_to_count )
|
||||
|
||||
|
||||
|
@ -1637,6 +1638,182 @@ class DB( HydrusDB.HydrusDB ):
|
|||
return similar_hash_ids
|
||||
|
||||
|
||||
def _CacheSimilarFilesSetDuplicatePairStatus( self, duplicate_status, hash_a, hash_b, merge_options = None ):
|
||||
|
||||
if duplicate_status == HC.DUPLICATE_WORSE:
|
||||
|
||||
t = hash_a
|
||||
hash_a = hash_b
|
||||
hash_b = t
|
||||
|
||||
duplicate_status = HC.DUPLICATE_BETTER
|
||||
|
||||
|
||||
hash_id_a = self._GetHashId( hash_a )
|
||||
hash_id_b = self._GetHashId( hash_b )
|
||||
|
||||
self._CacheSimilarFilesSetDuplicatePairStatusSingleRow( duplicate_status, hash_id_a, hash_id_b, merge_options )
|
||||
|
||||
if duplicate_status == HC.DUPLICATE_BETTER:
|
||||
|
||||
# anything better than A is now better than B
|
||||
# i.e. for all X for which X > A, set X > B
|
||||
# anything worse than B is now worse than A
|
||||
# i.e. for all X for which B > X, set A > X
|
||||
|
||||
better_than_a = set()
|
||||
|
||||
better_than_a.update( self._STI( self._c.execute( 'SELECT smaller_hash_id FROM duplicate_pairs WHERE duplicate_type = ? AND larger_hash_id = ?;', ( HC.DUPLICATE_SMALLER_BETTER, hash_id_a ) ) ) )
|
||||
better_than_a.update( self._STI( self._c.execute( 'SELECT larger_hash_id FROM duplicate_pairs WHERE duplicate_type = ? AND smaller_hash_id = ?;', ( HC.DUPLICATE_LARGER_BETTER, hash_id_a ) ) ) )
|
||||
|
||||
for better_than_a_hash_id in better_than_a:
|
||||
|
||||
self._CacheSimilarFilesSetDuplicatePairStatusSingleRow( HC.DUPLICATE_BETTER, better_than_a_hash_id, hash_id_b, merge_options )
|
||||
|
||||
|
||||
worse_than_b = set()
|
||||
|
||||
worse_than_b.update( self._STI( self._c.execute( 'SELECT smaller_hash_id FROM duplicate_pairs WHERE duplicate_type = ? AND larger_hash_id = ?;', ( HC.DUPLICATE_LARGER_BETTER, hash_id_b ) ) ) )
|
||||
worse_than_b.update( self._STI( self._c.execute( 'SELECT larger_hash_id FROM duplicate_pairs WHERE duplicate_type = ? AND smaller_hash_id = ?;', ( HC.DUPLICATE_SMALLER_BETTER, hash_id_b ) ) ) )
|
||||
|
||||
for worse_than_b_hash_id in worse_than_b:
|
||||
|
||||
self._CacheSimilarFilesSetDuplicatePairStatusSingleRow( HC.DUPLICATE_BETTER, hash_id_a, worse_than_b_hash_id, merge_options )
|
||||
|
||||
|
||||
|
||||
# do a sync for better dupes that applies not_dupe and alternate relationships across better-than groups
|
||||
|
||||
self._CacheSimilarFilesSyncSameFileDuplicates( hash_id_a, merge_options )
|
||||
self._CacheSimilarFilesSyncSameFileDuplicates( hash_id_b, merge_options )
|
||||
|
||||
|
||||
def _CacheSimilarFilesSetDuplicatePairStatusSingleRow( self, duplicate_status, hash_id_a, hash_id_b, merge_options, only_update_given_previous_status = None ):
|
||||
|
||||
smaller_hash_id = min( hash_id_a, hash_id_b )
|
||||
larger_hash_id = max( hash_id_a, hash_id_b )
|
||||
|
||||
if duplicate_status == HC.DUPLICATE_BETTER:
|
||||
|
||||
if smaller_hash_id == hash_id_a:
|
||||
|
||||
duplicate_status = HC.DUPLICATE_SMALLER_BETTER
|
||||
|
||||
else:
|
||||
|
||||
duplicate_status = HC.DUPLICATE_LARGER_BETTER
|
||||
|
||||
|
||||
elif duplicate_status == HC.DUPLICATE_WORSE:
|
||||
|
||||
if smaller_hash_id == hash_id_a:
|
||||
|
||||
duplicate_status = HC.DUPLICATE_LARGER_BETTER
|
||||
|
||||
else:
|
||||
|
||||
duplicate_status = HC.DUPLICATE_SMALLER_BETTER
|
||||
|
||||
|
||||
|
||||
change_occured = False
|
||||
|
||||
if only_update_given_previous_status is None:
|
||||
|
||||
result = self._c.execute( 'SELECT 1 FROM duplicate_pairs WHERE smaller_hash_id = ? AND larger_hash_id = ? AND duplicate_type = ?;', ( smaller_hash_id, larger_hash_id, duplicate_status ) ).fetchone()
|
||||
|
||||
if result is None: # i.e. we are not needlessly overwriting
|
||||
|
||||
self._c.execute( 'REPLACE INTO duplicate_pairs ( smaller_hash_id, larger_hash_id, duplicate_type ) VALUES ( ?, ?, ? );', ( smaller_hash_id, larger_hash_id, duplicate_status ) )
|
||||
|
||||
change_occured = True
|
||||
|
||||
|
||||
else:
|
||||
|
||||
self._c.execute( 'UPDATE duplicate_pairs SET duplicate_type = ? WHERE smaller_hash_id = ? AND larger_hash_id = ? AND duplicate_type = ?;', ( duplicate_status, smaller_hash_id, larger_hash_id, only_update_given_previous_status ) )
|
||||
|
||||
if self._GetRowCount() > 0:
|
||||
|
||||
change_occured = True
|
||||
|
||||
|
||||
|
||||
if change_occured and merge_options is not None:
|
||||
|
||||
# follow merge_options
|
||||
|
||||
# if better:
|
||||
|
||||
# do tags
|
||||
|
||||
# do ratings
|
||||
|
||||
# delete file
|
||||
|
||||
# if same:
|
||||
|
||||
# do tags
|
||||
|
||||
# do ratings
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def _CacheSimilarFilesSyncSameFileDuplicates( self, hash_id, merge_options ):
|
||||
|
||||
# for every known relationship our file has, that should be replicated to all of its 'same file' siblings
|
||||
|
||||
all_relationships = set()
|
||||
|
||||
for ( smaller_hash_id, duplicate_status ) in self._c.execute( 'SELECT smaller_hash_id, duplicate_type FROM duplicate_pairs WHERE duplicate_type != ? AND larger_hash_id = ?;', ( HC.DUPLICATE_UNKNOWN, hash_id ) ):
|
||||
|
||||
if duplicate_status == HC.DUPLICATE_SMALLER_BETTER:
|
||||
|
||||
duplicate_status = HC.DUPLICATE_WORSE
|
||||
|
||||
elif duplicate_status == HC.DUPLICATE_LARGER_BETTER:
|
||||
|
||||
duplicate_status = HC.DUPLICATE_BETTER
|
||||
|
||||
|
||||
all_relationships.add( ( smaller_hash_id, duplicate_status ) )
|
||||
|
||||
|
||||
for ( larger_hash_id, duplicate_status ) in self._c.execute( 'SELECT larger_hash_id, duplicate_type FROM duplicate_pairs WHERE duplicate_type != ? AND smaller_hash_id = ?;', ( HC.DUPLICATE_UNKNOWN, hash_id ) ):
|
||||
|
||||
if duplicate_status == HC.DUPLICATE_SMALLER_BETTER:
|
||||
|
||||
duplicate_status = HC.DUPLICATE_BETTER
|
||||
|
||||
elif duplicate_status == HC.DUPLICATE_LARGER_BETTER:
|
||||
|
||||
duplicate_status = HC.DUPLICATE_WORSE
|
||||
|
||||
|
||||
all_relationships.add( ( larger_hash_id, duplicate_status ) )
|
||||
|
||||
|
||||
all_siblings = set()
|
||||
|
||||
all_siblings.update( self._STI( self._c.execute( 'SELECT smaller_hash_id FROM duplicate_pairs WHERE duplicate_type = ? AND larger_hash_id = ?;', ( HC.DUPLICATE_SAME_FILE, hash_id ) ) ) )
|
||||
all_siblings.update( self._STI( self._c.execute( 'SELECT larger_hash_id FROM duplicate_pairs WHERE duplicate_type = ? AND smaller_hash_id = ?;', ( HC.DUPLICATE_SAME_FILE, hash_id ) ) ) )
|
||||
|
||||
for sibling_hash_id in all_siblings:
|
||||
|
||||
for ( other_hash_id, duplicate_status ) in all_relationships:
|
||||
|
||||
if other_hash_id == sibling_hash_id:
|
||||
|
||||
continue
|
||||
|
||||
|
||||
self._CacheSimilarFilesSetDuplicatePairStatusSingleRow( duplicate_status, sibling_hash_id, other_hash_id, merge_options )
|
||||
|
||||
|
||||
|
||||
|
||||
def _CacheSpecificMappingsAddFiles( self, file_service_id, tag_service_id, hash_ids ):
|
||||
|
||||
( cache_files_table_name, cache_current_mappings_table_name, cache_pending_mappings_table_name, ac_cache_table_name ) = GenerateSpecificMappingsCacheTableNames( file_service_id, tag_service_id )
|
||||
|
@ -2252,6 +2429,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
self._SetJSONDump( new_options )
|
||||
|
||||
list_of_shortcuts = ClientDefaults.GetDefaultShortcuts()
|
||||
|
||||
for shortcuts in list_of_shortcuts:
|
||||
|
||||
self._SetJSONDump( shortcuts )
|
||||
|
||||
|
||||
self._c.execute( 'INSERT INTO namespaces ( namespace_id, namespace ) VALUES ( ?, ? );', ( 1, '' ) )
|
||||
|
||||
self._c.execute( 'INSERT INTO version ( version ) VALUES ( ? );', ( HC.SOFTWARE_VERSION, ) )
|
||||
|
@ -5894,6 +6078,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
for chunk in HydrusData.SplitListIntoChunks( content_update.GetNewFiles(), FILES_CHUNK_SIZE ):
|
||||
|
||||
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
||||
|
||||
if should_quit:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
precise_timestamp = HydrusData.GetNowPrecise()
|
||||
|
||||
files_info_rows = []
|
||||
|
@ -5927,6 +6118,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
for chunk in HydrusData.SplitListIntoChunks( content_update.GetDeletedFiles(), FILES_CHUNK_SIZE ):
|
||||
|
||||
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
||||
|
||||
if should_quit:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
precise_timestamp = HydrusData.GetNowPrecise()
|
||||
|
||||
service_hash_ids = chunk
|
||||
|
@ -5947,6 +6145,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
for chunk in HydrusData.SplitMappingListIntoChunks( content_update.GetNewMappings(), MAPPINGS_CHUNK_SIZE ):
|
||||
|
||||
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
||||
|
||||
if should_quit:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
precise_timestamp = HydrusData.GetNowPrecise()
|
||||
|
||||
mappings_ids = []
|
||||
|
@ -5975,6 +6180,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
for chunk in HydrusData.SplitMappingListIntoChunks( content_update.GetDeletedMappings(), MAPPINGS_CHUNK_SIZE ):
|
||||
|
||||
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
||||
|
||||
if should_quit:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
precise_timestamp = HydrusData.GetNowPrecise()
|
||||
|
||||
deleted_mappings_ids = []
|
||||
|
@ -6003,6 +6215,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
for chunk in HydrusData.SplitListIntoChunks( content_update.GetNewTagParents(), NEW_TAG_PARENTS_CHUNK_SIZE ):
|
||||
|
||||
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
||||
|
||||
if should_quit:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
precise_timestamp = HydrusData.GetNowPrecise()
|
||||
|
||||
parent_ids = []
|
||||
|
@ -6031,6 +6250,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
if len( deleted_parents ) > 0:
|
||||
|
||||
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
||||
|
||||
if should_quit:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
precise_timestamp = HydrusData.GetNowPrecise()
|
||||
|
||||
parent_ids = []
|
||||
|
@ -6059,6 +6285,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
if len( new_siblings ) > 0:
|
||||
|
||||
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
||||
|
||||
if should_quit:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
precise_timestamp = HydrusData.GetNowPrecise()
|
||||
|
||||
sibling_ids = []
|
||||
|
@ -6087,6 +6320,13 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
if len( deleted_siblings ) > 0:
|
||||
|
||||
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
||||
|
||||
if should_quit:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
precise_timestamp = HydrusData.GetNowPrecise()
|
||||
|
||||
sibling_ids = []
|
||||
|
@ -6109,6 +6349,8 @@ class DB( HydrusDB.HydrusDB ):
|
|||
job_key.SetVariable( 'popup_gauge_2', ( rows_processed, total_rows ) )
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _ProcessRepositoryDefinitionUpdate( self, service_id, definition_update ):
|
||||
|
||||
|
@ -6131,115 +6373,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
def _ProcessContentUpdatePackage( self, service_key, content_update_package, job_key ):
|
||||
|
||||
( previous_journal_mode, ) = self._c.execute( 'PRAGMA journal_mode;' ).fetchone()
|
||||
|
||||
if previous_journal_mode == 'wal' and not self._fast_big_transaction_wal:
|
||||
|
||||
self._Commit()
|
||||
|
||||
self._c.execute( 'PRAGMA journal_mode = TRUNCATE;' )
|
||||
|
||||
self._BeginImmediate()
|
||||
|
||||
|
||||
c_u_p_num_rows = content_update_package.GetNumRows()
|
||||
c_u_p_total_weight_processed = 0
|
||||
|
||||
update_speed_string = u'writing\u2026'
|
||||
|
||||
content_update_index_string = 'content row ' + HydrusData.ConvertValueRangeToPrettyString( c_u_p_total_weight_processed, c_u_p_num_rows ) + ': '
|
||||
|
||||
quit_early = False
|
||||
|
||||
package_precise_timestamp = HydrusData.GetNowPrecise()
|
||||
|
||||
for ( content_updates, weight ) in content_update_package.IterateContentUpdateChunks():
|
||||
|
||||
options = self._controller.GetOptions()
|
||||
|
||||
if options[ 'pause_repo_sync' ]:
|
||||
|
||||
quit_early = True
|
||||
|
||||
|
||||
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
||||
|
||||
if should_quit:
|
||||
|
||||
quit_early = True
|
||||
|
||||
|
||||
if quit_early:
|
||||
|
||||
package_took = HydrusData.GetNowPrecise() - package_precise_timestamp
|
||||
|
||||
rows_s = c_u_p_total_weight_processed / package_took
|
||||
|
||||
committing_string = 'wrote ' + HydrusData.ConvertIntToPrettyString( c_u_p_num_rows ) + ' rows at ' + HydrusData.ConvertIntToPrettyString( rows_s ) + ' rows/s - now committing to disk'
|
||||
|
||||
job_key.SetVariable( 'popup_text_2', committing_string )
|
||||
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
if previous_journal_mode == 'wal' and not self._fast_big_transaction_wal:
|
||||
|
||||
self._Commit()
|
||||
|
||||
self._c.execute( 'PRAGMA journal_mode = WAL;' )
|
||||
|
||||
self._BeginImmediate()
|
||||
|
||||
|
||||
return ( False, c_u_p_total_weight_processed )
|
||||
|
||||
|
||||
content_update_index_string = 'content row ' + HydrusData.ConvertValueRangeToPrettyString( c_u_p_total_weight_processed, c_u_p_num_rows ) + ': '
|
||||
|
||||
self._controller.pub( 'splash_set_status_text', content_update_index_string + update_speed_string, print_to_log = False )
|
||||
job_key.SetVariable( 'popup_text_2', content_update_index_string + update_speed_string )
|
||||
|
||||
job_key.SetVariable( 'popup_gauge_2', ( c_u_p_total_weight_processed, c_u_p_num_rows ) )
|
||||
|
||||
chunk_precise_timestamp = HydrusData.GetNowPrecise()
|
||||
|
||||
self._ProcessContentUpdates( { service_key : content_updates }, do_pubsubs = False )
|
||||
|
||||
chunk_took = HydrusData.GetNowPrecise() - chunk_precise_timestamp
|
||||
|
||||
rows_s = weight / chunk_took
|
||||
|
||||
update_speed_string = 'writing at ' + HydrusData.ConvertIntToPrettyString( rows_s ) + ' rows/s'
|
||||
|
||||
c_u_p_total_weight_processed += weight
|
||||
|
||||
|
||||
package_took = HydrusData.GetNowPrecise() - package_precise_timestamp
|
||||
|
||||
rows_s = c_u_p_total_weight_processed / package_took
|
||||
|
||||
committing_string = 'wrote ' + HydrusData.ConvertIntToPrettyString( c_u_p_num_rows ) + ' rows at ' + HydrusData.ConvertIntToPrettyString( rows_s ) + ' rows/s - now committing to disk'
|
||||
|
||||
self._controller.pub( 'splash_set_status_text', committing_string )
|
||||
job_key.SetVariable( 'popup_text_2', committing_string )
|
||||
|
||||
job_key.SetVariable( 'popup_gauge_2', ( c_u_p_num_rows, c_u_p_num_rows ) )
|
||||
|
||||
HydrusData.Print( job_key.ToString() )
|
||||
|
||||
if previous_journal_mode == 'wal' and not self._fast_big_transaction_wal:
|
||||
|
||||
self._Commit()
|
||||
|
||||
self._c.execute( 'PRAGMA journal_mode = WAL;' )
|
||||
|
||||
self._BeginImmediate()
|
||||
|
||||
|
||||
return ( True, c_u_p_total_weight_processed )
|
||||
|
||||
|
||||
def _ProcessContentUpdates( self, service_keys_to_content_updates, do_pubsubs = True ):
|
||||
|
||||
notify_new_downloads = False
|
||||
|
@ -7009,7 +7142,14 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
content_update = HydrusSerialisable.CreateFromNetworkString( update_network_string )
|
||||
|
||||
self._ProcessRepositoryContentUpdate( job_key, service_id, content_update )
|
||||
did_whole_update = self._ProcessRepositoryContentUpdate( job_key, service_id, content_update )
|
||||
|
||||
( i_paused, should_quit ) = job_key.WaitIfNeeded()
|
||||
|
||||
if should_quit or not did_whole_update:
|
||||
|
||||
return ( True, False )
|
||||
|
||||
|
||||
self._c.execute( 'UPDATE ' + repository_updates_table_name + ' SET processed = ? WHERE hash_id = ?;', ( True, hash_id ) )
|
||||
|
||||
|
@ -8437,6 +8577,23 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
|
||||
|
||||
if version == 250:
|
||||
|
||||
duplicate_filter = ClientData.Shortcuts( 'duplicate_filter' )
|
||||
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_LEFT, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_this_is_better' ) )
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_LEFT, [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_not_dupes' ) )
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_RIGHT, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_alternates' ) )
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_MIDDLE, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_exactly_the_same' ) )
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_RIGHT, [ CC.SHORTCUT_MODIFIER_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_skip' ) )
|
||||
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_SPACE, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_this_is_better' ) )
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_UP, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_skip' ) )
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_NUMPAD_UP, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_skip' ) )
|
||||
|
||||
self._SetJSONDump( duplicate_filter )
|
||||
|
||||
|
||||
self._controller.pub( 'splash_set_title_text', 'updated db to v' + str( version + 1 ) )
|
||||
|
||||
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
|
||||
|
@ -8455,10 +8612,6 @@ class DB( HydrusDB.HydrusDB ):
|
|||
|
||||
file_service_ids = self._GetServiceIds( HC.AUTOCOMPLETE_CACHE_SPECIFIC_FILE_SERVICES )
|
||||
|
||||
# this method grew into a monster that merged deleted, pending and current according to a hierarchy of services
|
||||
# this cost a lot of CPU time and was extremely difficult to maintain
|
||||
# now it attempts a simpler union, not letting delete overwrite a current or pending
|
||||
|
||||
change_in_num_mappings = 0
|
||||
change_in_num_deleted_mappings = 0
|
||||
change_in_num_pending_mappings = 0
|
||||
|
@ -9016,8 +9169,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
if action == 'analyze': result = self._AnalyzeStaleBigTables( *args, **kwargs )
|
||||
elif action == 'associate_repository_update_hashes': result = self._AssociateRepositoryUpdateHashes( *args, **kwargs )
|
||||
elif action == 'backup': result = self._Backup( *args, **kwargs )
|
||||
elif action == 'content_update_package':result = self._ProcessContentUpdatePackage( *args, **kwargs )
|
||||
elif action == 'content_updates':result = self._ProcessContentUpdates( *args, **kwargs )
|
||||
elif action == 'content_updates': result = self._ProcessContentUpdates( *args, **kwargs )
|
||||
elif action == 'db_integrity': result = self._CheckDBIntegrity( *args, **kwargs )
|
||||
elif action == 'delete_hydrus_session_key': result = self._DeleteHydrusSessionKey( *args, **kwargs )
|
||||
elif action == 'delete_imageboard': result = self._DeleteYAMLDump( YAML_DUMP_ID_IMAGEBOARD, *args, **kwargs )
|
||||
|
@ -9028,6 +9180,7 @@ class DB( HydrusDB.HydrusDB ):
|
|||
elif action == 'delete_service_info': result = self._DeleteServiceInfo( *args, **kwargs )
|
||||
elif action == 'delete_unknown_duplicate_pairs': result = self._CacheSimilarFilesDeleteUnknownDuplicatePairs( *args, **kwargs )
|
||||
elif action == 'dirty_services': result = self._SaveDirtyServices( *args, **kwargs )
|
||||
elif action == 'duplicate_pair_status': result = self._CacheSimilarFilesSetDuplicatePairStatus( *args, **kwargs )
|
||||
elif action == 'export_mappings': result = self._ExportToTagArchive( *args, **kwargs )
|
||||
elif action == 'file_integrity': result = self._CheckFileIntegrity( *args, **kwargs )
|
||||
elif action == 'hydrus_session': result = self._AddHydrusSession( *args, **kwargs )
|
||||
|
|
|
@ -485,6 +485,112 @@ def WaitPolitely( page_key = None ):
|
|||
HydrusGlobals.client_controller.pub( 'waiting_politely', page_key, False )
|
||||
|
||||
|
||||
class ApplicationCommand( HydrusSerialisable.SerialisableBase ):
|
||||
|
||||
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_APPLICATION_COMMAND
|
||||
SERIALISABLE_VERSION = 1
|
||||
|
||||
def __init__( self, command_type = None, data = None ):
|
||||
|
||||
if command_type is None:
|
||||
|
||||
command_type = CC.APPLICATION_COMMAND_TYPE_SIMPLE
|
||||
|
||||
|
||||
if data is None:
|
||||
|
||||
data = 'archive'
|
||||
|
||||
|
||||
HydrusSerialisable.SerialisableBase.__init__( self )
|
||||
|
||||
self._command_type = command_type
|
||||
self._data = data
|
||||
|
||||
|
||||
def __cmp__( self, other ):
|
||||
|
||||
return cmp( self.ToString(), other.ToString() )
|
||||
|
||||
|
||||
def _GetSerialisableInfo( self ):
|
||||
|
||||
if self._command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
|
||||
|
||||
serialisable_data = self._data
|
||||
|
||||
elif self._command_type == CC.APPLICATION_COMMAND_TYPE_CONTENT:
|
||||
|
||||
( service_key, content_type, action, value ) = self._data
|
||||
|
||||
serialisable_data = ( service_key.encode( 'hex' ), content_type, action, value )
|
||||
|
||||
|
||||
return ( self._command_type, serialisable_data )
|
||||
|
||||
|
||||
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
|
||||
|
||||
( self._command_type, serialisable_data ) = serialisable_info
|
||||
|
||||
if self._command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
|
||||
|
||||
self._data = serialisable_data
|
||||
|
||||
elif self._command_type == CC.APPLICATION_COMMAND_TYPE_CONTENT:
|
||||
|
||||
( serialisable_service_key, content_type, action, value ) = serialisable_data
|
||||
|
||||
self._data = ( serialisable_service_key.decode( 'hex' ), content_type, action, value )
|
||||
|
||||
|
||||
|
||||
def GetCommandType( self ):
|
||||
|
||||
return self._command_type
|
||||
|
||||
|
||||
def GetData( self ):
|
||||
|
||||
return self._data
|
||||
|
||||
|
||||
def ToString( self ):
|
||||
|
||||
if self._command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
|
||||
|
||||
return self._data
|
||||
|
||||
elif self._command_type == CC.APPLICATION_COMMAND_TYPE_CONTENT:
|
||||
|
||||
( service_key, content_type, action, value ) = self._data
|
||||
|
||||
components = []
|
||||
|
||||
components.append( HC.content_update_string_lookup[ action ] )
|
||||
components.append( HC.content_type_string_lookup[ content_type ] )
|
||||
components.append( '"' + HydrusData.ToUnicode( value ) + '"' )
|
||||
components.append( 'for' )
|
||||
|
||||
services_manager = HydrusGlobals.client_controller.GetServicesManager()
|
||||
|
||||
if services_manager.ServiceExists( service_key ):
|
||||
|
||||
service = services_manager.GetService( service_key )
|
||||
|
||||
components.append( service.GetName() )
|
||||
|
||||
else:
|
||||
|
||||
components.append( 'unknown service!' )
|
||||
|
||||
|
||||
return ' '.join( components )
|
||||
|
||||
|
||||
|
||||
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_APPLICATION_COMMAND ] = ApplicationCommand
|
||||
|
||||
class Booru( HydrusData.HydrusYAMLBase ):
|
||||
|
||||
yaml_tag = u'!Booru'
|
||||
|
@ -1483,126 +1589,223 @@ class ImportTagOptions( HydrusSerialisable.SerialisableBase ):
|
|||
|
||||
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_TAG_OPTIONS ] = ImportTagOptions
|
||||
|
||||
class Shortcut( HydrusSerialisable.SerialisableBase ):
|
||||
|
||||
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT
|
||||
SERIALISABLE_VERSION = 1
|
||||
|
||||
def __init__( self, shortcut_type = None, shortcut_key = None, modifiers = None ):
|
||||
|
||||
if shortcut_type is None:
|
||||
|
||||
shortcut_type = CC.SHORTCUT_TYPE_KEYBOARD
|
||||
|
||||
|
||||
if shortcut_key is None:
|
||||
|
||||
shortcut_key = wx.WXK_F7
|
||||
|
||||
|
||||
if modifiers is None:
|
||||
|
||||
modifiers = []
|
||||
|
||||
|
||||
modifiers.sort()
|
||||
|
||||
HydrusSerialisable.SerialisableBase.__init__( self )
|
||||
|
||||
self._shortcut_type = shortcut_type
|
||||
self._shortcut_key = shortcut_key
|
||||
self._modifiers = modifiers
|
||||
|
||||
|
||||
def __cmp__( self, other ):
|
||||
|
||||
return cmp( self.ToString(), other.ToString() )
|
||||
|
||||
|
||||
def __eq__( self, other ):
|
||||
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
|
||||
def __hash__( self ):
|
||||
|
||||
return ( self._shortcut_type, self._shortcut_key, tuple( self._modifiers ) ).__hash__()
|
||||
|
||||
|
||||
def _GetSerialisableInfo( self ):
|
||||
|
||||
return ( self._shortcut_type, self._shortcut_key, self._modifiers )
|
||||
|
||||
|
||||
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
|
||||
|
||||
( self._shortcut_type, self._shortcut_key, self._modifiers ) = serialisable_info
|
||||
|
||||
|
||||
def GetShortcutType( self ):
|
||||
|
||||
return self._shortcut_type
|
||||
|
||||
|
||||
def ToString( self ):
|
||||
|
||||
components = []
|
||||
|
||||
if CC.SHORTCUT_MODIFIER_CTRL in self._modifiers:
|
||||
|
||||
components.append( 'ctrl' )
|
||||
|
||||
|
||||
if CC.SHORTCUT_MODIFIER_ALT in self._modifiers:
|
||||
|
||||
components.append( 'alt' )
|
||||
|
||||
|
||||
if CC.SHORTCUT_MODIFIER_SHIFT in self._modifiers:
|
||||
|
||||
components.append( 'shift' )
|
||||
|
||||
|
||||
if self._shortcut_type == CC.SHORTCUT_TYPE_KEYBOARD:
|
||||
|
||||
if self._shortcut_key in range( 65, 91 ):
|
||||
|
||||
components.append( chr( self._shortcut_key + 32 ) ) # + 32 for converting ascii A -> a
|
||||
|
||||
elif self._shortcut_key in range( 97, 123 ):
|
||||
|
||||
components.append( chr( self._shortcut_key ) )
|
||||
|
||||
else:
|
||||
|
||||
components.append( CC.wxk_code_string_lookup[ self._shortcut_key ] )
|
||||
|
||||
|
||||
elif self._shortcut_type == CC.SHORTCUT_TYPE_MOUSE:
|
||||
|
||||
components.append( CC.shortcut_mouse_string_lookup[ self._shortcut_key ] )
|
||||
|
||||
|
||||
return '+'.join( components )
|
||||
|
||||
|
||||
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUT ] = Shortcut
|
||||
|
||||
class Shortcuts( HydrusSerialisable.SerialisableBaseNamed ):
|
||||
|
||||
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS
|
||||
SERIALISABLE_VERSION = 1
|
||||
SERIALISABLE_VERSION = 2
|
||||
|
||||
def __init__( self, name ):
|
||||
|
||||
HydrusSerialisable.SerialisableBaseNamed.__init__( self, name )
|
||||
|
||||
self._mouse_actions = {}
|
||||
self._keyboard_actions = {}
|
||||
|
||||
# update this to have actions as a separate serialisable class
|
||||
self._shortcuts_to_commands = {}
|
||||
|
||||
|
||||
def _ConvertActionToSerialisableAction( self, action ):
|
||||
def __iter__( self ):
|
||||
|
||||
( service_key, data ) = action
|
||||
|
||||
if service_key is None:
|
||||
for ( shortcut, command ) in self._shortcuts_to_commands.items():
|
||||
|
||||
return [ service_key, data ]
|
||||
|
||||
else:
|
||||
|
||||
serialisable_service_key = service_key.encode( 'hex' )
|
||||
|
||||
return [ serialisable_service_key, data ]
|
||||
|
||||
|
||||
|
||||
def _ConvertSerialisableActionToAction( self, serialisable_action ):
|
||||
|
||||
( serialisable_service_key, data ) = serialisable_action
|
||||
|
||||
if serialisable_service_key is None:
|
||||
|
||||
return ( serialisable_service_key, data ) # important to return tuple, as serialisable_action is likely a list
|
||||
|
||||
else:
|
||||
|
||||
service_key = serialisable_service_key.decode( 'hex' )
|
||||
|
||||
return ( service_key, data )
|
||||
yield ( shortcut, command )
|
||||
|
||||
|
||||
|
||||
def _GetSerialisableInfo( self ):
|
||||
|
||||
serialisable_mouse_actions = []
|
||||
|
||||
for ( ( modifier, mouse_button ), action ) in self._mouse_actions.items():
|
||||
|
||||
serialisable_action = self._ConvertActionToSerialisableAction( action )
|
||||
|
||||
serialisable_mouse_actions.append( ( modifier, mouse_button, serialisable_action ) )
|
||||
|
||||
|
||||
serialisable_keyboard_actions = []
|
||||
|
||||
for ( ( modifier, key ), action ) in self._keyboard_actions.items():
|
||||
|
||||
serialisable_action = self._ConvertActionToSerialisableAction( action )
|
||||
|
||||
serialisable_keyboard_actions.append( ( modifier, key, serialisable_action ) )
|
||||
|
||||
|
||||
return ( serialisable_mouse_actions, serialisable_keyboard_actions )
|
||||
return [ ( shortcut.GetSerialisableTuple(), command.GetSerialisableTuple() ) for ( shortcut, command ) in self._shortcuts_to_commands.items() ]
|
||||
|
||||
|
||||
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
|
||||
|
||||
( serialisable_mouse_actions, serialisable_keyboard_actions ) = serialisable_info
|
||||
|
||||
self._mouse_actions = {}
|
||||
|
||||
for ( modifier, mouse_button, serialisable_action ) in serialisable_mouse_actions:
|
||||
for ( serialisable_shortcut, serialisable_command ) in serialisable_info:
|
||||
|
||||
action = self._ConvertSerialisableActionToAction( serialisable_action )
|
||||
shortcut = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_shortcut )
|
||||
command = HydrusSerialisable.CreateFromSerialisableTuple( serialisable_command )
|
||||
|
||||
self._mouse_actions[ ( modifier, mouse_button ) ] = action
|
||||
|
||||
|
||||
self._keyboard_actions = {}
|
||||
|
||||
for ( modifier, key, serialisable_action ) in serialisable_keyboard_actions:
|
||||
|
||||
action = self._ConvertSerialisableActionToAction( serialisable_action )
|
||||
|
||||
self._keyboard_actions[ ( modifier, key ) ] = action
|
||||
self._shortcuts_to_commands[ shortcut ] = command
|
||||
|
||||
|
||||
|
||||
def ClearActions( self ):
|
||||
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
|
||||
|
||||
self._mouse_actions = {}
|
||||
self._keyboard_actions = {}
|
||||
|
||||
|
||||
def DeleteKeyboardAction( self, modifier, key ):
|
||||
|
||||
if ( modifier, key ) in self._keyboard_actions:
|
||||
if version == 1:
|
||||
|
||||
del self._keyboard_actions[ ( modifier, key ) ]
|
||||
( serialisable_mouse_actions, serialisable_keyboard_actions ) = old_serialisable_info
|
||||
|
||||
shortcuts_to_commands = {}
|
||||
|
||||
# this never stored mouse actions, so skip
|
||||
|
||||
services_manager = HydrusGlobals.client_controller.GetServicesManager()
|
||||
|
||||
for ( modifier, key, ( serialisable_service_key, data ) ) in serialisable_keyboard_actions:
|
||||
|
||||
if modifier not in CC.shortcut_wx_to_hydrus_lookup:
|
||||
|
||||
modifiers = []
|
||||
|
||||
else:
|
||||
|
||||
modifiers = [ CC.shortcut_wx_to_hydrus_lookup[ modifier ] ]
|
||||
|
||||
|
||||
shortcut = Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, key, modifiers )
|
||||
|
||||
if serialisable_service_key is None:
|
||||
|
||||
command = ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, data )
|
||||
|
||||
else:
|
||||
|
||||
service_key = serialisable_service_key.decode( 'hex' )
|
||||
|
||||
if not services_manager.ServiceExists( service_key ):
|
||||
|
||||
continue
|
||||
|
||||
|
||||
action = HC.CONTENT_UPDATE_FLIP
|
||||
|
||||
value = data
|
||||
|
||||
service = services_manager.GetService( service_key )
|
||||
|
||||
service_type = service.GetServiceType()
|
||||
|
||||
if service_type in HC.TAG_SERVICES:
|
||||
|
||||
content_type = HC.CONTENT_TYPE_MAPPINGS
|
||||
|
||||
elif service_type in HC.RATINGS_SERVICES:
|
||||
|
||||
content_type = HC.CONTENT_TYPE_RATINGS
|
||||
|
||||
else:
|
||||
|
||||
continue
|
||||
|
||||
|
||||
command = ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( service_key, content_type, action, value ) )
|
||||
|
||||
|
||||
shortcuts_to_commands[ shortcut ] = command
|
||||
|
||||
|
||||
new_serialisable_info = ( ( shortcut.GetSerialisableTuple(), command.GetSerialisableTuple() ) for ( shortcut, command ) in shortcuts_to_commands.items() )
|
||||
|
||||
return ( 2, new_serialisable_info )
|
||||
|
||||
|
||||
|
||||
def DeleteMouseAction( self, modifier, mouse_button ):
|
||||
def GetCommand( self, shortcut ):
|
||||
|
||||
if ( modifier, mouse_button ) in self._mouse_actions:
|
||||
if shortcut in self._shortcuts_to_commands:
|
||||
|
||||
del self._mouse_actions[ ( modifier, mouse_button ) ]
|
||||
|
||||
|
||||
|
||||
def GetKeyboardAction( self, modifier, key ):
|
||||
|
||||
if ( modifier, key ) in self._keyboard_actions:
|
||||
|
||||
return self._keyboard_actions[ ( modifier, key ) ]
|
||||
return self._shortcuts_to_commands[ shortcut ]
|
||||
|
||||
else:
|
||||
|
||||
|
@ -1610,41 +1813,44 @@ class Shortcuts( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
|
||||
|
||||
def GetMouseAction( self, modifier, mouse_button ):
|
||||
def SetCommand( self, shortcut, command ):
|
||||
|
||||
if ( modifier, mouse_button ) in self._mouse_actions:
|
||||
|
||||
return self._mouse_actions[ ( modifier, mouse_button ) ]
|
||||
|
||||
else:
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
||||
def IterateKeyboardShortcuts( self ):
|
||||
|
||||
for ( ( modifier, key ), action ) in self._keyboard_actions.items(): yield ( ( modifier, key ), action )
|
||||
|
||||
|
||||
def IterateMouseShortcuts( self ):
|
||||
|
||||
for ( ( modifier, mouse_button ), action ) in self._mouse_actions.items(): yield ( ( modifier, mouse_button ), action )
|
||||
|
||||
|
||||
def SetKeyboardAction( self, modifier, key, action ):
|
||||
|
||||
self._keyboard_actions[ ( modifier, key ) ] = action
|
||||
|
||||
|
||||
def SetMouseAction( self, modifier, mouse_button, action ):
|
||||
|
||||
self._mouse_actions[ ( modifier, mouse_button ) ] = action
|
||||
self._shortcuts_to_commands[ shortcut ] = command
|
||||
|
||||
|
||||
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS ] = Shortcuts
|
||||
|
||||
def GetShortcutFromEvent( event ):
|
||||
def ConvertKeyEventToShortcut( event ):
|
||||
|
||||
key = event.KeyCode
|
||||
|
||||
if key in range( 65, 91 ) or key in CC.wxk_code_string_lookup.keys():
|
||||
|
||||
modifiers = []
|
||||
|
||||
if event.AltDown():
|
||||
|
||||
modifiers.append( CC.SHORTCUT_MODIFIER_ALT )
|
||||
|
||||
|
||||
if event.CmdDown():
|
||||
|
||||
modifiers.append( CC.SHORTCUT_MODIFIER_CTRL )
|
||||
|
||||
|
||||
if event.ShiftDown():
|
||||
|
||||
modifiers.append( CC.SHORTCUT_MODIFIER_SHIFT )
|
||||
|
||||
|
||||
shortcut = Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, key, modifiers )
|
||||
|
||||
return shortcut
|
||||
|
||||
|
||||
return None
|
||||
|
||||
def ConvertKeyEventToSimpleTuple( event ):
|
||||
|
||||
modifier = wx.ACCEL_NORMAL
|
||||
|
||||
|
@ -1656,3 +1862,54 @@ def GetShortcutFromEvent( event ):
|
|||
|
||||
return ( modifier, key )
|
||||
|
||||
def ConvertMouseEventToShortcut( event ):
|
||||
|
||||
key = None
|
||||
|
||||
if event.LeftDown():
|
||||
|
||||
key = CC.SHORTCUT_MOUSE_LEFT
|
||||
|
||||
elif event.MiddleDown():
|
||||
|
||||
key = CC.SHORTCUT_MOUSE_MIDDLE
|
||||
|
||||
elif event.RightDown():
|
||||
|
||||
key = CC.SHORTCUT_MOUSE_RIGHT
|
||||
|
||||
elif event.GetWheelRotation() > 0:
|
||||
|
||||
key = CC.SHORTCUT_MOUSE_SCROLL_UP
|
||||
|
||||
elif event.GetWheelRotation() < 0:
|
||||
|
||||
key = CC.SHORTCUT_MOUSE_SCROLL_DOWN
|
||||
|
||||
|
||||
if key is not None:
|
||||
|
||||
modifiers = []
|
||||
|
||||
if event.AltDown():
|
||||
|
||||
modifiers.append( CC.SHORTCUT_MODIFIER_ALT )
|
||||
|
||||
|
||||
if event.CmdDown():
|
||||
|
||||
modifiers.append( CC.SHORTCUT_MODIFIER_CTRL )
|
||||
|
||||
|
||||
if event.ShiftDown():
|
||||
|
||||
modifiers.append( CC.SHORTCUT_MODIFIER_SHIFT )
|
||||
|
||||
|
||||
shortcut = Shortcut( CC.SHORTCUT_TYPE_MOUSE, key, modifiers )
|
||||
|
||||
return shortcut
|
||||
|
||||
|
||||
return None
|
||||
|
||||
|
|
|
@ -572,3 +572,22 @@ def GetDefaultScriptRows():
|
|||
|
||||
return script_info
|
||||
|
||||
def GetDefaultShortcuts():
|
||||
|
||||
shortcuts = []
|
||||
|
||||
duplicate_filter = ClientData.Shortcuts( 'duplicate_filter' )
|
||||
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_LEFT, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_this_is_better' ) )
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_LEFT, [ wx.ACCEL_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_not_dupes' ) )
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_RIGHT, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_alternates' ) )
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_MIDDLE, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_exactly_the_same' ) )
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_RIGHT, [ wx.ACCEL_CTRL ] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_skip' ) )
|
||||
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_SPACE, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_this_is_better' ) )
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_UP, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_skip' ) )
|
||||
duplicate_filter.SetCommand( ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_NUMPAD_UP, [] ), ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'duplicate_filter_skip' ) )
|
||||
|
||||
shortcuts.append( duplicate_filter )
|
||||
|
||||
return shortcuts
|
||||
|
|
|
@ -285,7 +285,7 @@ class AutoCompleteDropdown( wx.Panel ):
|
|||
|
||||
HydrusGlobals.client_controller.ResetIdleTimer()
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( wx.WXK_INSERT, wx.WXK_NUMPAD_INSERT ):
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import ClientTags
|
|||
import gc
|
||||
import HydrusImageHandling
|
||||
import HydrusPaths
|
||||
import HydrusSerialisable
|
||||
import HydrusTags
|
||||
import os
|
||||
import wx
|
||||
|
@ -1033,7 +1034,9 @@ class Canvas( wx.Window ):
|
|||
self._current_zoom = 1.0
|
||||
self._canvas_zoom = 1.0
|
||||
|
||||
self._drag_begin_coordinates = None
|
||||
self._last_drag_coordinates = None
|
||||
self._current_drag_is_touch = False
|
||||
self._last_motion_coordinates = ( 0, 0 )
|
||||
self._total_drag_delta = ( 0, 0 )
|
||||
|
||||
|
@ -1248,7 +1251,14 @@ class Canvas( wx.Window ):
|
|||
|
||||
if not ( ClientGUICommon.WindowHasFocus( self ) or ClientGUICommon.ChildHasFocus( self ) ):
|
||||
|
||||
return True
|
||||
focus = wx.Window.FindFocus()
|
||||
|
||||
focus_is_my_hover_window = focus.GetParent() == self and isinstance( focus, ClientGUIHoverFrames.FullscreenHoverFrame )
|
||||
|
||||
if not focus_is_my_hover_window:
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
if self._current_media is not None and self._current_media.GetMime() == HC.APPLICATION_FLASH:
|
||||
|
@ -1543,8 +1553,9 @@ class Canvas( wx.Window ):
|
|||
( x, y ) = pos
|
||||
|
||||
|
||||
self._drag_begin_coordinates = ( x, y )
|
||||
self._last_drag_coordinates = ( x, y )
|
||||
|
||||
self._current_drag_is_touch = False
|
||||
|
||||
|
||||
def EventEraseBackground( self, event ): pass
|
||||
|
@ -2192,12 +2203,15 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
|
|||
|
||||
self._media_list = ClientMedia.ListeningMediaList( self._file_service_key, [] )
|
||||
|
||||
self._duplicate_filter_shortcuts = HydrusGlobals.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS, 'duplicate_filter' )
|
||||
|
||||
self._hover_commands.AddCommand( 'this is better', self._CurrentMediaIsBetter )
|
||||
self._hover_commands.AddCommand( 'exact duplicates', self._MediaAreTheSame )
|
||||
self._hover_commands.AddCommand( 'alternates', self._MediaAreAlternates )
|
||||
self._hover_commands.AddCommand( 'not duplicates', self._MediaAreNotDupes )
|
||||
self._hover_commands.AddCommand( 'custom action', self._DoCustomAction )
|
||||
|
||||
self.Bind( wx.EVT_MOUSEWHEEL, self.EventMouseWheel )
|
||||
self.Bind( wx.EVT_MOUSE_EVENTS, self.EventMouse )
|
||||
self.Bind( wx.EVT_CHAR_HOOK, self.EventCharHook )
|
||||
|
||||
# add support for 'f' to borderless
|
||||
|
@ -2210,6 +2224,8 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
|
|||
HydrusGlobals.client_controller.sub( self, 'SwitchMedia', 'canvas_show_previous' )
|
||||
HydrusGlobals.client_controller.sub( self, 'ShowNewPair', 'canvas_show_new_pair' )
|
||||
|
||||
HydrusGlobals.client_controller.sub( self, 'RefreshShortcuts', 'refresh_shortcuts' )
|
||||
|
||||
|
||||
def _Close( self ):
|
||||
|
||||
|
@ -2220,14 +2236,26 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
|
|||
|
||||
def _CurrentMediaIsBetter( self ):
|
||||
|
||||
pass
|
||||
other_media = self._media_list.GetNext( self._current_media )
|
||||
|
||||
better_hash = self._current_media.GetHash()
|
||||
worse_hash = other_media.GetHash()
|
||||
|
||||
merge_options = self._GetMergeOptions( HC.DUPLICATE_BETTER )
|
||||
|
||||
HydrusGlobals.client_controller.WriteSynchronous( 'duplicate_pair_status', HC.DUPLICATE_BETTER, better_hash, worse_hash, merge_options )
|
||||
|
||||
self._ShowNewPair()
|
||||
|
||||
|
||||
def _DoCustomAction( self ):
|
||||
|
||||
pass
|
||||
wx.MessageBox( 'This doesn\'t do anything yet!' )
|
||||
|
||||
return
|
||||
|
||||
# ( duplicate_status, hash_a, hash_b, merge_options ) = panel.getvalue()
|
||||
# HydrusGlobals.client_controller.WriteSynchronous( 'duplicate_pair_status', duplicate_status, hash_a, hash_b, merge_options )
|
||||
|
||||
# launch the dialog to choose exactly what happens
|
||||
# if OK on that:
|
||||
|
@ -2258,20 +2286,122 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
|
|||
|
||||
|
||||
|
||||
def _GetMergeOptions( self, duplicate_status ):
|
||||
|
||||
# fetch it from client_options, given a status
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _MediaAreAlternates( self ):
|
||||
|
||||
pass
|
||||
other_media = self._media_list.GetNext( self._current_media )
|
||||
|
||||
hash_a = self._current_media.GetHash()
|
||||
hash_b = other_media.GetHash()
|
||||
|
||||
merge_options = self._GetMergeOptions( HC.DUPLICATE_ALTERNATE )
|
||||
|
||||
HydrusGlobals.client_controller.WriteSynchronous( 'duplicate_pair_status', HC.DUPLICATE_ALTERNATE, hash_a, hash_b )
|
||||
|
||||
self._ShowNewPair()
|
||||
|
||||
|
||||
def _MediaAreNotDupes( self ):
|
||||
|
||||
other_media = self._media_list.GetNext( self._current_media )
|
||||
|
||||
hash_a = self._current_media.GetHash()
|
||||
hash_b = other_media.GetHash()
|
||||
|
||||
merge_options = self._GetMergeOptions( HC.DUPLICATE_NOT_DUPLICATE )
|
||||
|
||||
HydrusGlobals.client_controller.WriteSynchronous( 'duplicate_pair_status', HC.DUPLICATE_NOT_DUPLICATE, hash_a, hash_b )
|
||||
|
||||
self._ShowNewPair()
|
||||
|
||||
|
||||
def _MediaAreTheSame( self ):
|
||||
|
||||
pass
|
||||
other_media = self._media_list.GetNext( self._current_media )
|
||||
|
||||
hash_a = self._current_media.GetHash()
|
||||
hash_b = other_media.GetHash()
|
||||
|
||||
merge_options = self._GetMergeOptions( HC.DUPLICATE_SAME_FILE )
|
||||
|
||||
HydrusGlobals.client_controller.WriteSynchronous( 'duplicate_pair_status', HC.DUPLICATE_SAME_FILE, hash_a, hash_b, merge_options )
|
||||
|
||||
self._ShowNewPair()
|
||||
|
||||
|
||||
def _ProcessApplicationCommand( self, command ):
|
||||
|
||||
command_processed = True
|
||||
|
||||
command_type = command.GetCommandType()
|
||||
data = command.GetData()
|
||||
|
||||
if command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
|
||||
|
||||
action = data
|
||||
|
||||
if action == 'duplicate_filter_this_is_better':
|
||||
|
||||
self._CurrentMediaIsBetter()
|
||||
|
||||
elif action == 'duplicate_filter_exactly_the_same':
|
||||
|
||||
self._MediaAreTheSame()
|
||||
|
||||
elif action == 'duplicate_filter_alternates':
|
||||
|
||||
self._MediaAreAlternates()
|
||||
|
||||
elif action == 'duplicate_filter_not_dupes':
|
||||
|
||||
self._MediaAreNotDupes()
|
||||
|
||||
elif action == 'duplicate_filter_custom_action':
|
||||
|
||||
self._DoCustomAction()
|
||||
|
||||
elif action == 'duplicate_filter_skip':
|
||||
|
||||
self._ShowNewPair()
|
||||
|
||||
else:
|
||||
|
||||
command_processed = False
|
||||
|
||||
|
||||
else:
|
||||
|
||||
command_processed = False
|
||||
|
||||
|
||||
return command_processed
|
||||
|
||||
|
||||
def _ProcessShortcut( self, shortcut ):
|
||||
|
||||
shortcut_processed = False
|
||||
|
||||
command = self._duplicate_filter_shortcuts.GetCommand( shortcut )
|
||||
|
||||
if command is not None:
|
||||
|
||||
command_processed = self._ProcessApplicationCommand( command )
|
||||
|
||||
if command_processed:
|
||||
|
||||
shortcut_processed = True
|
||||
|
||||
|
||||
|
||||
return shortcut_processed
|
||||
|
||||
|
||||
def _ShowNewPair( self ):
|
||||
|
||||
result = HydrusGlobals.client_controller.Read( 'duplicate_pair', self._file_service_key, HC.DUPLICATE_UNKNOWN )
|
||||
|
@ -2300,12 +2430,28 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
|
|||
|
||||
def EventCharHook( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
shortcut = ClientData.ConvertKeyEventToShortcut( event )
|
||||
|
||||
if shortcut is not None:
|
||||
|
||||
shortcut_processed = self._ProcessShortcut( shortcut )
|
||||
|
||||
if shortcut_processed:
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_ESCAPE ):
|
||||
|
||||
self._Close()
|
||||
|
||||
else:
|
||||
|
||||
event.Skip()
|
||||
|
||||
|
||||
|
||||
def EventClose( self, event ):
|
||||
|
@ -2313,7 +2459,7 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
|
|||
self._Close()
|
||||
|
||||
|
||||
def EventMouseWheel( self, event ):
|
||||
def EventMouse( self, event ):
|
||||
|
||||
if self._HydrusShouldNotProcessInput():
|
||||
|
||||
|
@ -2321,7 +2467,26 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
|
|||
|
||||
else:
|
||||
|
||||
self._SwitchMedia()
|
||||
shortcut = ClientData.ConvertMouseEventToShortcut( event )
|
||||
|
||||
if shortcut is not None:
|
||||
|
||||
shortcut_processed = self._ProcessShortcut( shortcut )
|
||||
|
||||
if shortcut_processed:
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
if event.GetWheelRotation() != 0:
|
||||
|
||||
self._SwitchMedia()
|
||||
|
||||
else:
|
||||
|
||||
event.Skip()
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2345,6 +2510,11 @@ class CanvasFilterDuplicates( CanvasWithHovers ):
|
|||
wx.CallLater( 100, catch_up )
|
||||
|
||||
|
||||
def RefreshShortcuts( self ):
|
||||
|
||||
self._duplicate_filter_shortcuts = HydrusGlobals.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS, 'duplicate_filter' )
|
||||
|
||||
|
||||
def ShowNewPair( self, canvas_key ):
|
||||
|
||||
if canvas_key == self._canvas_key:
|
||||
|
@ -2592,6 +2762,15 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithHovers ):
|
|||
|
||||
if event.Dragging() and self._last_drag_coordinates is not None:
|
||||
|
||||
off_starting_point = self._drag_begin_coordinates != self._last_drag_coordinates
|
||||
hit_same_point_twice = ( x, y ) == self._last_drag_coordinates
|
||||
|
||||
# touch drags generate motion events continuously, even when not moving
|
||||
if off_starting_point and hit_same_point_twice:
|
||||
|
||||
self._current_drag_is_touch = True
|
||||
|
||||
|
||||
( old_x, old_y ) = self._last_drag_coordinates
|
||||
|
||||
( delta_x, delta_y ) = ( x - old_x, y - old_y )
|
||||
|
@ -2600,7 +2779,16 @@ class CanvasMediaList( ClientMedia.ListeningMediaList, CanvasWithHovers ):
|
|||
|
||||
show_mouse = False
|
||||
|
||||
self.WarpPointer( old_x, old_y )
|
||||
if not self._current_drag_is_touch:
|
||||
|
||||
# touch events obviously don't mix with warping well. the touch just warps it back and again and we get a massive delta!
|
||||
|
||||
self.WarpPointer( old_x, old_y )
|
||||
|
||||
else:
|
||||
|
||||
self._last_drag_coordinates = ( x, y )
|
||||
|
||||
|
||||
else:
|
||||
|
||||
|
@ -2901,7 +3089,7 @@ class CanvasMediaListFilterInbox( CanvasMediaList ):
|
|||
if self._HydrusShouldNotProcessInput(): event.Skip()
|
||||
else:
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if modifier == wx.ACCEL_NORMAL and key == wx.WXK_SPACE: self._Keep()
|
||||
elif modifier == wx.ACCEL_NORMAL and key in ( ord( '+' ), wx.WXK_ADD, wx.WXK_NUMPAD_ADD ): self._ZoomIn()
|
||||
|
@ -3216,7 +3404,7 @@ class CanvasMediaListBrowser( CanvasMediaListNavigable ):
|
|||
|
||||
else:
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if modifier == wx.ACCEL_NORMAL and key in CC.DELETE_KEYS: self._Delete()
|
||||
elif modifier == wx.ACCEL_SHIFT and key in CC.DELETE_KEYS: self._Undelete()
|
||||
|
@ -3540,15 +3728,17 @@ class CanvasMediaListCustomFilter( CanvasMediaListNavigable ):
|
|||
if self._HydrusShouldNotProcessInput(): event.Skip()
|
||||
else:
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
shortcut = ClientData.ConvertKeyEventToShortcut( event )
|
||||
|
||||
action = self._shortcuts.GetKeyboardAction( modifier, key )
|
||||
command = self._shortcuts.GetCommand( shortcut )
|
||||
|
||||
if action is not None:
|
||||
if command is not None:
|
||||
|
||||
( service_key, data ) = action
|
||||
data = command.GetData()
|
||||
|
||||
if service_key is None:
|
||||
command_type = command.GetCommandType()
|
||||
|
||||
if command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
|
||||
|
||||
if data == 'archive': self._Archive()
|
||||
elif data == 'delete': self._Delete()
|
||||
|
@ -3571,7 +3761,9 @@ class CanvasMediaListCustomFilter( CanvasMediaListNavigable ):
|
|||
elif data == 'previous': self._ShowPrevious()
|
||||
elif data == 'next': self._ShowNext()
|
||||
|
||||
else:
|
||||
elif command_type == CC.APPLICATION_COMMAND_TYPE_CONTENT:
|
||||
|
||||
( service_key, content_type, action, value ) = data
|
||||
|
||||
service = HydrusGlobals.client_controller.GetServicesManager().GetService( service_key )
|
||||
|
||||
|
@ -3581,7 +3773,7 @@ class CanvasMediaListCustomFilter( CanvasMediaListNavigable ):
|
|||
|
||||
if service_type in HC.TAG_SERVICES:
|
||||
|
||||
tag = data
|
||||
tag = value
|
||||
|
||||
tags_manager = self._current_media.GetTagsManager()
|
||||
|
||||
|
@ -3663,7 +3855,7 @@ class CanvasMediaListCustomFilter( CanvasMediaListNavigable ):
|
|||
# maybe this needs to be more complicated, if action is, say, remove the rating?
|
||||
# ratings needs a good look at anyway
|
||||
|
||||
rating = data
|
||||
rating = value
|
||||
|
||||
row = ( rating, hashes )
|
||||
|
||||
|
@ -3675,6 +3867,8 @@ class CanvasMediaListCustomFilter( CanvasMediaListNavigable ):
|
|||
|
||||
else:
|
||||
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if modifier == wx.ACCEL_NORMAL and key in ( ord( '+' ), wx.WXK_ADD, wx.WXK_NUMPAD_ADD ): self._ZoomIn()
|
||||
elif modifier == wx.ACCEL_NORMAL and key in ( ord( '-' ), wx.WXK_SUBTRACT, wx.WXK_NUMPAD_SUBTRACT ): self._ZoomOut()
|
||||
elif modifier == wx.ACCEL_NORMAL and key == ord( 'Z' ): self._ZoomSwitch()
|
||||
|
|
|
@ -3127,7 +3127,7 @@ class SaneListCtrl( wx.ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin ):
|
|||
|
||||
def EventKeyDown( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in CC.DELETE_KEYS:
|
||||
|
||||
|
@ -3407,12 +3407,71 @@ class SaneListCtrlForSingleObject( SaneListCtrl ):
|
|||
self._objects_to_data_indices[ obj ] = data_index
|
||||
|
||||
|
||||
class Shortcut( wx.TextCtrl ):
|
||||
class Shortcut( wx.Panel ):
|
||||
|
||||
def __init__( self, parent, modifier = wx.ACCEL_NORMAL, key = wx.WXK_F7 ):
|
||||
def __init__( self, parent ):
|
||||
|
||||
self._modifier = modifier
|
||||
self._key = key
|
||||
wx.Panel.__init__( self, parent )
|
||||
|
||||
self._mouse_radio = wx.RadioButton( self, style = wx.RB_GROUP, label = 'mouse' )
|
||||
self._mouse_shortcut = ShortcutMouse( self, self._mouse_radio )
|
||||
|
||||
self._keyboard_radio = wx.RadioButton( self, label = 'keyboard' )
|
||||
self._keyboard_shortcut = ShortcutKeyboard( self, self._keyboard_radio )
|
||||
|
||||
#
|
||||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
vbox.AddF( wx.StaticText( self, label = 'Mouse events only work for the duplicate filter atm!' ), CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
gridbox = wx.FlexGridSizer( 0, 2 )
|
||||
|
||||
gridbox.AddGrowableCol( 1, 1 )
|
||||
|
||||
gridbox.AddF( self._mouse_radio, CC.FLAGS_VCENTER )
|
||||
gridbox.AddF( self._mouse_shortcut, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
gridbox.AddF( self._keyboard_radio, CC.FLAGS_VCENTER )
|
||||
gridbox.AddF( self._keyboard_shortcut, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
vbox.AddF( gridbox, CC.FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
self.SetSizer( vbox )
|
||||
|
||||
|
||||
def GetValue( self ):
|
||||
|
||||
if self._mouse_radio.GetValue() == True:
|
||||
|
||||
return self._mouse_shortcut.GetValue()
|
||||
|
||||
else:
|
||||
|
||||
return self._keyboard_shortcut.GetValue()
|
||||
|
||||
|
||||
|
||||
def SetValue( self, shortcut ):
|
||||
|
||||
if shortcut.GetShortcutType() == CC.SHORTCUT_TYPE_MOUSE:
|
||||
|
||||
self._mouse_radio.SetValue( True )
|
||||
self._mouse_shortcut.SetValue( shortcut )
|
||||
|
||||
else:
|
||||
|
||||
self._keyboard_radio.SetValue( True )
|
||||
self._keyboard_shortcut.SetValue( shortcut )
|
||||
|
||||
|
||||
|
||||
class ShortcutKeyboard( wx.TextCtrl ):
|
||||
|
||||
def __init__( self, parent, related_radio = None ):
|
||||
|
||||
self._shortcut = ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_F7, [] )
|
||||
|
||||
self._related_radio = related_radio
|
||||
|
||||
wx.TextCtrl.__init__( self, parent, style = wx.TE_PROCESS_ENTER )
|
||||
|
||||
|
@ -3423,35 +3482,87 @@ class Shortcut( wx.TextCtrl ):
|
|||
|
||||
def _SetShortcutString( self ):
|
||||
|
||||
display_string = ''
|
||||
|
||||
if self._modifier == wx.ACCEL_ALT: display_string += 'alt + '
|
||||
elif self._modifier == wx.ACCEL_CTRL: display_string += 'ctrl + '
|
||||
elif self._modifier == wx.ACCEL_SHIFT: display_string += 'shift + '
|
||||
|
||||
if self._key in range( 65, 91 ): display_string += chr( self._key + 32 ) # + 32 for converting ascii A -> a
|
||||
elif self._key in range( 97, 123 ): display_string += chr( self._key )
|
||||
else: display_string += CC.wxk_code_string_lookup[ self._key ]
|
||||
display_string = self._shortcut.ToString()
|
||||
|
||||
wx.TextCtrl.SetValue( self, display_string )
|
||||
|
||||
|
||||
def EventKeyDown( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
shortcut = ClientData.ConvertKeyEventToShortcut( event )
|
||||
|
||||
if key in range( 65, 91 ) or key in CC.wxk_code_string_lookup.keys():
|
||||
if shortcut is not None:
|
||||
|
||||
( self._modifier, self._key ) = ( modifier, key )
|
||||
self._shortcut = shortcut
|
||||
|
||||
if self._related_radio is not None:
|
||||
|
||||
self._related_radio.SetValue( True )
|
||||
|
||||
|
||||
self._SetShortcutString()
|
||||
|
||||
|
||||
|
||||
def GetValue( self ): return ( self._modifier, self._key )
|
||||
|
||||
def SetValue( self, modifier, key ):
|
||||
def GetValue( self ):
|
||||
|
||||
( self._modifier, self._key ) = ( modifier, key )
|
||||
return self._shortcut
|
||||
|
||||
|
||||
def SetValue( self, shortcut ):
|
||||
|
||||
self._shortcut = shortcut
|
||||
|
||||
self._SetShortcutString()
|
||||
|
||||
|
||||
class ShortcutMouse( wx.Button ):
|
||||
|
||||
def __init__( self, parent, related_radio = None ):
|
||||
|
||||
self._shortcut = ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_LEFT, [] )
|
||||
|
||||
self._related_radio = related_radio
|
||||
|
||||
wx.Button.__init__( self, parent )
|
||||
|
||||
self.Bind( wx.EVT_MOUSE_EVENTS, self.EventMouse )
|
||||
|
||||
self._SetShortcutString()
|
||||
|
||||
|
||||
def _SetShortcutString( self ):
|
||||
|
||||
display_string = self._shortcut.ToString()
|
||||
|
||||
self.SetLabel( display_string )
|
||||
|
||||
|
||||
def EventMouse( self, event ):
|
||||
|
||||
shortcut = ClientData.ConvertMouseEventToShortcut( event )
|
||||
|
||||
if shortcut is not None:
|
||||
|
||||
self._shortcut = shortcut
|
||||
|
||||
if self._related_radio is not None:
|
||||
|
||||
self._related_radio.SetValue( True )
|
||||
|
||||
|
||||
self._SetShortcutString()
|
||||
|
||||
|
||||
|
||||
def GetValue( self ):
|
||||
|
||||
return self._shortcut
|
||||
|
||||
|
||||
def SetValue( self, shortcut ):
|
||||
|
||||
self._shortcut = shortcut
|
||||
|
||||
self._SetShortcutString()
|
||||
|
||||
|
|
|
@ -498,13 +498,10 @@ class DialogInputImportTagOptions( Dialog ):
|
|||
|
||||
class DialogInputCustomFilterAction( Dialog ):
|
||||
|
||||
def __init__( self, parent, modifier = wx.ACCEL_NORMAL, key = wx.WXK_F7, service_key = None, action = 'archive' ):
|
||||
def __init__( self, parent, shortcut, command ):
|
||||
|
||||
Dialog.__init__( self, parent, 'input custom filter action' )
|
||||
|
||||
self._service_key = service_key
|
||||
self._action = action
|
||||
|
||||
self._current_ratings_like_service = None
|
||||
self._current_ratings_numerical_service = None
|
||||
|
||||
|
@ -512,11 +509,14 @@ class DialogInputCustomFilterAction( Dialog ):
|
|||
|
||||
self._shortcut_panel = ClientGUICommon.StaticBox( self, 'shortcut' )
|
||||
|
||||
self._shortcut = ClientGUICommon.Shortcut( self._shortcut_panel, modifier, key )
|
||||
self._shortcut = ClientGUICommon.Shortcut( self._shortcut_panel )
|
||||
|
||||
self._none_panel = ClientGUICommon.StaticBox( self, 'non-service actions' )
|
||||
|
||||
self._none_actions = wx.Choice( self._none_panel, choices = [ 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'delete', 'fullscreen_switch', 'frame_back', 'frame_next', 'next', 'first', 'last', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'previous', 'remove', 'zoom_in', 'zoom_out', 'zoom_switch' ] )
|
||||
choices = [ 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'delete', 'fullscreen_switch', 'frame_back', 'frame_next', 'next', 'first', 'last', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'previous', 'remove', 'zoom_in', 'zoom_out', 'zoom_switch' ]
|
||||
choices.extend( CC.DUPLICATE_FILTER_ACTIONS )
|
||||
|
||||
self._none_actions = wx.Choice( self._none_panel, choices = choices )
|
||||
|
||||
self._ok_none = wx.Button( self._none_panel, label = 'ok' )
|
||||
self._ok_none.Bind( wx.EVT_BUTTON, self.EventOKNone )
|
||||
|
@ -573,12 +573,23 @@ class DialogInputCustomFilterAction( Dialog ):
|
|||
|
||||
self._SetActions()
|
||||
|
||||
if self._service_key is None:
|
||||
#
|
||||
|
||||
self._shortcut.SetValue( shortcut )
|
||||
|
||||
command_type = command.GetCommandType()
|
||||
data = command.GetData()
|
||||
|
||||
if command_type == CC.APPLICATION_COMMAND_TYPE_SIMPLE:
|
||||
|
||||
self._none_actions.SetStringSelection( self._action )
|
||||
action = data
|
||||
|
||||
self._none_actions.SetStringSelection( action )
|
||||
|
||||
else:
|
||||
|
||||
( service_key, content_type, action, value ) = data
|
||||
|
||||
self._service = HydrusGlobals.client_controller.GetServicesManager().GetService( service_key )
|
||||
|
||||
service_name = self._service.GetName()
|
||||
|
@ -588,7 +599,7 @@ class DialogInputCustomFilterAction( Dialog ):
|
|||
|
||||
self._tag_service_keys.SetStringSelection( service_name )
|
||||
|
||||
self._tag_value.SetValue( self._action )
|
||||
self._tag_value.SetValue( value )
|
||||
|
||||
elif service_type == HC.LOCAL_RATING_LIKE:
|
||||
|
||||
|
@ -596,9 +607,18 @@ class DialogInputCustomFilterAction( Dialog ):
|
|||
|
||||
self._SetActions()
|
||||
|
||||
if self._action is None: self._ratings_like_remove.SetValue( True )
|
||||
elif self._action == True: self._ratings_like_like.SetValue( True )
|
||||
elif self._action == False: self._ratings_like_dislike.SetValue( True )
|
||||
if value is None:
|
||||
|
||||
self._ratings_like_remove.SetValue( True )
|
||||
|
||||
elif value == True:
|
||||
|
||||
self._ratings_like_like.SetValue( True )
|
||||
|
||||
elif value == False:
|
||||
|
||||
self._ratings_like_dislike.SetValue( True )
|
||||
|
||||
|
||||
elif service_type == HC.LOCAL_RATING_NUMERICAL:
|
||||
|
||||
|
@ -606,7 +626,7 @@ class DialogInputCustomFilterAction( Dialog ):
|
|||
|
||||
self._SetActions()
|
||||
|
||||
if self._action is None:
|
||||
if value is None:
|
||||
|
||||
self._ratings_numerical_remove.SetValue( True )
|
||||
|
||||
|
@ -614,7 +634,7 @@ class DialogInputCustomFilterAction( Dialog ):
|
|||
|
||||
num_stars = self._current_ratings_numerical_service.GetNumStars()
|
||||
|
||||
slider_value = int( round( self._action * num_stars ) )
|
||||
slider_value = int( round( value * num_stars ) )
|
||||
|
||||
self._ratings_numerical_slider.SetValue( slider_value )
|
||||
|
||||
|
@ -734,9 +754,9 @@ class DialogInputCustomFilterAction( Dialog ):
|
|||
|
||||
def EventOKNone( self, event ):
|
||||
|
||||
self._service_key = None
|
||||
self._action = self._none_actions.GetStringSelection()
|
||||
self._pretty_action = self._action
|
||||
action = self._none_actions.GetStringSelection()
|
||||
|
||||
self._final_command = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, action )
|
||||
|
||||
self.EndModal( wx.ID_OK )
|
||||
|
||||
|
@ -747,27 +767,29 @@ class DialogInputCustomFilterAction( Dialog ):
|
|||
|
||||
if selection != wx.NOT_FOUND:
|
||||
|
||||
self._service_key = self._ratings_like_service_keys.GetClientData( selection )
|
||||
service_key = self._ratings_like_service_keys.GetClientData( selection )
|
||||
|
||||
if self._ratings_like_like.GetValue():
|
||||
|
||||
self._action = 1.0
|
||||
self._pretty_action = 'like'
|
||||
value = 1.0
|
||||
|
||||
elif self._ratings_like_dislike.GetValue():
|
||||
|
||||
self._action = 0.0
|
||||
self._pretty_action = 'dislike'
|
||||
value = 0.0
|
||||
|
||||
else:
|
||||
|
||||
self._action = None
|
||||
self._pretty_action = 'remove'
|
||||
value = None
|
||||
|
||||
|
||||
self._final_command = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( service_key, HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_FLIP, value ) )
|
||||
|
||||
self.EndModal( wx.ID_OK )
|
||||
|
||||
else: self.EndModal( wx.ID_CANCEL )
|
||||
else:
|
||||
|
||||
self.EndModal( wx.ID_CANCEL )
|
||||
|
||||
|
||||
|
||||
def EventOKRatingsNumerical( self, event ):
|
||||
|
@ -776,53 +798,57 @@ class DialogInputCustomFilterAction( Dialog ):
|
|||
|
||||
if selection != wx.NOT_FOUND:
|
||||
|
||||
self._service_key = self._ratings_numerical_service_keys.GetClientData( selection )
|
||||
service_key = self._ratings_numerical_service_keys.GetClientData( selection )
|
||||
|
||||
if self._ratings_numerical_remove.GetValue():
|
||||
|
||||
self._action = None
|
||||
self._pretty_action = 'remove'
|
||||
value = None
|
||||
|
||||
else:
|
||||
|
||||
value = self._ratings_numerical_slider.GetValue()
|
||||
|
||||
self._pretty_action = HydrusData.ToUnicode( value )
|
||||
|
||||
num_stars = self._current_ratings_numerical_service.GetNumStars()
|
||||
allow_zero = self._current_ratings_numerical_service.AllowZero()
|
||||
|
||||
if allow_zero:
|
||||
|
||||
self._action = float( value ) / num_stars
|
||||
value = float( value ) / num_stars
|
||||
|
||||
else:
|
||||
|
||||
self._action = float( value - 1 ) / ( num_stars - 1 )
|
||||
value = float( value - 1 ) / ( num_stars - 1 )
|
||||
|
||||
|
||||
|
||||
self._final_command = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( service_key, HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_FLIP, value ) )
|
||||
|
||||
self.EndModal( wx.ID_OK )
|
||||
|
||||
else: self.EndModal( wx.ID_CANCEL )
|
||||
else:
|
||||
|
||||
self.EndModal( wx.ID_CANCEL )
|
||||
|
||||
|
||||
|
||||
def EventOKTag( self, event ):
|
||||
|
||||
# this could support multiple tags now
|
||||
|
||||
selection = self._tag_service_keys.GetSelection()
|
||||
|
||||
if selection != wx.NOT_FOUND:
|
||||
|
||||
self._service_key = self._tag_service_keys.GetClientData( selection )
|
||||
service_key = self._tag_service_keys.GetClientData( selection )
|
||||
|
||||
self._action = self._tag_value.GetValue()
|
||||
self._pretty_action = self._action
|
||||
value = self._tag_value.GetValue()
|
||||
|
||||
self._final_command = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( service_key, HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_FLIP, value ) )
|
||||
|
||||
self.EndModal( wx.ID_OK )
|
||||
|
||||
else: self.EndModal( wx.ID_CANCEL )
|
||||
else:
|
||||
|
||||
self.EndModal( wx.ID_CANCEL )
|
||||
|
||||
|
||||
|
||||
def EventRecalcActions( self, event ):
|
||||
|
@ -834,14 +860,9 @@ class DialogInputCustomFilterAction( Dialog ):
|
|||
|
||||
def GetInfo( self ):
|
||||
|
||||
( modifier, key ) = self._shortcut.GetValue()
|
||||
shortcut = self._shortcut.GetValue()
|
||||
|
||||
if self._service_key is None: pretty_service_key = ''
|
||||
else: pretty_service_key = HydrusGlobals.client_controller.GetServicesManager().GetService( self._service_key ).GetName()
|
||||
|
||||
( pretty_modifier, pretty_key ) = ClientData.ConvertShortcutToPrettyShortcut( modifier, key )
|
||||
|
||||
return ( ( pretty_modifier, pretty_key, pretty_service_key, self._pretty_action ), ( modifier, key, self._service_key, self._action ) )
|
||||
return ( shortcut, self._final_command )
|
||||
|
||||
|
||||
def SetTags( self, tags ):
|
||||
|
@ -949,7 +970,7 @@ class DialogInputFileSystemPredicates( Dialog ):
|
|||
|
||||
def EventCharHook( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
||||
|
||||
|
@ -1704,11 +1725,11 @@ class DialogInputShortcut( Dialog ):
|
|||
|
||||
def __init__( self, parent, modifier = wx.ACCEL_NORMAL, key = wx.WXK_F7, action = 'new_page' ):
|
||||
|
||||
Dialog.__init__( self, parent, 'configure shortcut' )
|
||||
Dialog.__init__( self, parent, 'edit shortcut' )
|
||||
|
||||
self._action = action
|
||||
|
||||
self._shortcut = ClientGUICommon.Shortcut( self, modifier, key )
|
||||
self._shortcut = ClientGUICommon.Shortcut( self )
|
||||
|
||||
self._actions = wx.Choice( self, choices = [ 'archive', 'inbox', 'close_page', 'filter', 'fullscreen_switch', 'frame_back', 'frame_next', 'manage_ratings', 'manage_tags', 'new_page', 'unclose_page', 'refresh', 'set_media_focus', 'set_search_focus', 'show_hide_splitters', 'synchronised_wait_switch', 'next', 'first', 'last', 'undo', 'redo', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'previous', 'remove', 'zoom_in', 'zoom_out', 'zoom_switch' ] )
|
||||
|
||||
|
@ -1720,6 +1741,18 @@ class DialogInputShortcut( Dialog ):
|
|||
|
||||
#
|
||||
|
||||
if modifier not in CC.shortcut_wx_to_hydrus_lookup:
|
||||
|
||||
modifiers = []
|
||||
|
||||
else:
|
||||
|
||||
modifiers = [ CC.shortcut_wx_to_hydrus_lookup[ modifier ] ]
|
||||
|
||||
|
||||
shortcut = ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, key, modifiers )
|
||||
|
||||
self._shortcut.SetValue( shortcut )
|
||||
self._actions.SetSelection( self._actions.FindString( action ) )
|
||||
|
||||
#
|
||||
|
@ -1727,7 +1760,7 @@ class DialogInputShortcut( Dialog ):
|
|||
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
||||
|
||||
hbox.AddF( self._shortcut, CC.FLAGS_VCENTER )
|
||||
hbox.AddF( self._actions, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
hbox.AddF( self._actions, CC.FLAGS_VCENTER )
|
||||
|
||||
b_box = wx.BoxSizer( wx.HORIZONTAL )
|
||||
b_box.AddF( self._ok, CC.FLAGS_VCENTER )
|
||||
|
@ -1749,7 +1782,37 @@ class DialogInputShortcut( Dialog ):
|
|||
|
||||
def GetInfo( self ):
|
||||
|
||||
( modifier, key ) = self._shortcut.GetValue()
|
||||
shortcut = self._shortcut.GetValue()
|
||||
|
||||
modifiers = shortcut._modifiers
|
||||
|
||||
if modifiers == []:
|
||||
|
||||
modifier = wx.ACCEL_NORMAL
|
||||
|
||||
else:
|
||||
|
||||
hydrus_modifier = modifiers[0]
|
||||
|
||||
if hydrus_modifier == CC.SHORTCUT_MODIFIER_ALT:
|
||||
|
||||
modifier = wx.ACCEL_ALT
|
||||
|
||||
elif hydrus_modifier == CC.SHORTCUT_MODIFIER_CTRL:
|
||||
|
||||
modifier = wx.ACCEL_CTRL
|
||||
|
||||
elif hydrus_modifier == CC.SHORTCUT_MODIFIER_SHIFT:
|
||||
|
||||
modifier = wx.ACCEL_SHIFT
|
||||
|
||||
else:
|
||||
|
||||
modifier = wx.ACCEL_NORMAL
|
||||
|
||||
|
||||
|
||||
key = shortcut._shortcut_key
|
||||
|
||||
return ( modifier, key, self._actions.GetStringSelection() )
|
||||
|
||||
|
@ -2392,7 +2455,7 @@ class DialogPageChooser( Dialog ):
|
|||
|
||||
id = None
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key == wx.WXK_UP: id = 8
|
||||
elif key == wx.WXK_LEFT: id = 4
|
||||
|
@ -3525,7 +3588,7 @@ class DialogSelectFromList( Dialog ):
|
|||
|
||||
def EventCharHook( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key == wx.WXK_ESCAPE:
|
||||
|
||||
|
@ -3539,7 +3602,7 @@ class DialogSelectFromList( Dialog ):
|
|||
|
||||
def EventListKeyDown( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( wx.WXK_SPACE, wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
||||
|
||||
|
@ -4040,7 +4103,6 @@ class DialogShortcuts( Dialog ):
|
|||
|
||||
self.SetInitialSize( ( x, y ) )
|
||||
|
||||
|
||||
wx.CallAfter( self.EventSelect, None )
|
||||
|
||||
wx.CallAfter( self._ok.SetFocus )
|
||||
|
@ -4054,16 +4116,30 @@ class DialogShortcuts( Dialog ):
|
|||
|
||||
for ( key, action ) in key_dict.items():
|
||||
|
||||
if modifier not in CC.shortcut_wx_to_hydrus_lookup:
|
||||
|
||||
modifiers = []
|
||||
|
||||
else:
|
||||
|
||||
modifiers = [ CC.shortcut_wx_to_hydrus_lookup[ modifier ] ]
|
||||
|
||||
|
||||
shortcut = ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, key, modifiers )
|
||||
|
||||
if action in ( 'manage_tags', 'manage_ratings', 'archive', 'inbox', 'fullscreen_switch', 'frame_back', 'frame_next', 'next', 'first', 'last', 'open_externally', 'pan_up', 'pan_down', 'pan_left', 'pan_right', 'previous', 'remove', 'zoom_in', 'zoom_out', 'zoom_switch' ):
|
||||
|
||||
service_key = None
|
||||
command = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, action )
|
||||
|
||||
default_shortcuts.SetKeyboardAction( modifier, key, ( service_key, action ) )
|
||||
default_shortcuts.SetCommand( shortcut, command )
|
||||
|
||||
|
||||
|
||||
|
||||
default_shortcuts.SetKeyboardAction( wx.ACCEL_NORMAL, wx.WXK_DELETE, ( None, 'delete' ) )
|
||||
shortcut = ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_DELETE, [] )
|
||||
command = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'delete' )
|
||||
|
||||
default_shortcuts.SetCommand( shortcut, command )
|
||||
|
||||
return default_shortcuts
|
||||
|
||||
|
@ -4072,7 +4148,7 @@ class DialogShortcuts( Dialog ):
|
|||
|
||||
name = self._shortcuts.GetCurrentKey()
|
||||
|
||||
if name is None: # 'default'
|
||||
if name is None or name in CC.SHORTCUTS_RESERVED_NAMES: # None = 'default'
|
||||
|
||||
return True
|
||||
|
||||
|
@ -4149,14 +4225,9 @@ class DialogShortcuts( Dialog ):
|
|||
|
||||
existing_shortcuts = page.GetShortcuts()
|
||||
|
||||
for ( ( modifier, key ), action ) in existing_shortcuts.IterateKeyboardShortcuts():
|
||||
for ( shortcut, command ) in existing_shortcuts:
|
||||
|
||||
shortcuts.SetKeyboardAction( modifier, key, action )
|
||||
|
||||
|
||||
for ( ( modifier, mouse_button ), action ) in existing_shortcuts.IterateMouseShortcuts():
|
||||
|
||||
shortcuts.SetKeyboardAction( modifier, mouse_button, action )
|
||||
shortcuts.SetCommand( shortcut, command )
|
||||
|
||||
|
||||
|
||||
|
@ -4194,9 +4265,8 @@ class DialogShortcuts( Dialog ):
|
|||
|
||||
wx.Panel.__init__( self, parent )
|
||||
|
||||
self._original_shortcuts = shortcuts
|
||||
|
||||
self._shortcuts = ClientGUICommon.SaneListCtrl( self, 120, [ ( 'modifier', 150 ), ( 'key', 150 ), ( 'service', 150 ), ( 'action', -1 ) ], delete_key_callback = self.RemoveShortcuts, activation_callback = self.EditShortcuts )
|
||||
self._name = shortcuts.GetName()
|
||||
self._shortcuts = ClientGUICommon.SaneListCtrl( self, 120, [ ( 'shortcut', 150 ), ( 'command', -1 ) ], delete_key_callback = self.RemoveShortcuts, activation_callback = self.EditShortcuts )
|
||||
|
||||
self._add = wx.Button( self, label = 'add' )
|
||||
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
|
||||
|
@ -4211,36 +4281,16 @@ class DialogShortcuts( Dialog ):
|
|||
|
||||
#
|
||||
|
||||
for ( ( modifier, key ), action ) in self._original_shortcuts.IterateKeyboardShortcuts():
|
||||
for ( shortcut, command ) in shortcuts:
|
||||
|
||||
( pretty_modifier, pretty_key ) = ClientData.ConvertShortcutToPrettyShortcut( modifier, key )
|
||||
sort_tuple = ( shortcut, command )
|
||||
|
||||
( service_key, data ) = action
|
||||
pretty_tuple = self._ConvertSortTupleToPrettyTuple( sort_tuple )
|
||||
|
||||
if service_key is None:
|
||||
|
||||
pretty_service_key = ''
|
||||
|
||||
else:
|
||||
|
||||
try:
|
||||
|
||||
service = HydrusGlobals.client_controller.GetServicesManager().GetService( service_key )
|
||||
|
||||
pretty_service_key = service.GetName()
|
||||
|
||||
except HydrusExceptions.DataMissing:
|
||||
|
||||
pretty_service_key = 'service not found'
|
||||
|
||||
|
||||
|
||||
pretty_data = data
|
||||
|
||||
self._shortcuts.Append( ( pretty_modifier, pretty_key, pretty_service_key, pretty_data ), ( modifier, key, service_key, data ) )
|
||||
self._shortcuts.Append( pretty_tuple, sort_tuple )
|
||||
|
||||
|
||||
self._SortListCtrl()
|
||||
self._shortcuts.SortListItems( 1 )
|
||||
|
||||
#
|
||||
|
||||
|
@ -4258,43 +4308,50 @@ class DialogShortcuts( Dialog ):
|
|||
self.SetSizer( vbox )
|
||||
|
||||
|
||||
def _SortListCtrl( self ):
|
||||
def _ConvertSortTupleToPrettyTuple( self, ( shortcut, command ) ):
|
||||
|
||||
self._shortcuts.SortListItems( 3 )
|
||||
return ( shortcut.ToString(), command.ToString() )
|
||||
|
||||
|
||||
def EditShortcuts( self ):
|
||||
|
||||
for index in self._shortcuts.GetAllSelected():
|
||||
|
||||
( modifier, key, service_key, action ) = self._shortcuts.GetClientData( index )
|
||||
( shortcut, command ) = self._shortcuts.GetClientData( index )
|
||||
|
||||
with DialogInputCustomFilterAction( self, modifier = modifier, key = key, service_key = service_key, action = action ) as dlg:
|
||||
with DialogInputCustomFilterAction( self, shortcut, command ) as dlg:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
( pretty_tuple, sort_tuple ) = dlg.GetInfo()
|
||||
( shortcut, command ) = dlg.GetInfo()
|
||||
|
||||
sort_tuple = ( shortcut, command )
|
||||
|
||||
pretty_tuple = self._ConvertSortTupleToPrettyTuple( sort_tuple )
|
||||
|
||||
self._shortcuts.UpdateRow( index, pretty_tuple, sort_tuple )
|
||||
|
||||
self._SortListCtrl()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def EventAdd( self, event ):
|
||||
|
||||
with DialogInputCustomFilterAction( self ) as dlg:
|
||||
shortcut = ClientData.Shortcut()
|
||||
command = ClientData.ApplicationCommand()
|
||||
|
||||
with DialogInputCustomFilterAction( self, shortcut, command ) as dlg:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
( pretty_tuple, sort_tuple ) = dlg.GetInfo()
|
||||
( shortcut, command ) = dlg.GetInfo()
|
||||
|
||||
sort_tuple = ( shortcut, command )
|
||||
|
||||
pretty_tuple = self._ConvertSortTupleToPrettyTuple( sort_tuple )
|
||||
|
||||
self._shortcuts.Append( pretty_tuple, sort_tuple )
|
||||
|
||||
self._SortListCtrl()
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -4310,17 +4367,11 @@ class DialogShortcuts( Dialog ):
|
|||
|
||||
def GetShortcuts( self ):
|
||||
|
||||
name = self._original_shortcuts.GetName()
|
||||
shortcuts = ClientData.Shortcuts( self._name )
|
||||
|
||||
shortcuts = ClientData.Shortcuts( name )
|
||||
|
||||
rows = self._shortcuts.GetClientData()
|
||||
|
||||
for ( modifier, key, service_key, data ) in rows:
|
||||
for ( shortcut, command ) in self._shortcuts.GetClientData():
|
||||
|
||||
action = ( service_key, data )
|
||||
|
||||
shortcuts.SetKeyboardAction( modifier, key, action )
|
||||
shortcuts.SetCommand( shortcut, command )
|
||||
|
||||
|
||||
return shortcuts
|
||||
|
|
|
@ -3568,7 +3568,7 @@ class DialogManageTagCensorship( ClientGUIDialogs.Dialog ):
|
|||
|
||||
def EventKeyDownTag( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import ClientConstants as CC
|
|||
import ClientData
|
||||
import ClientGUICanvas
|
||||
import ClientGUICommon
|
||||
import ClientGUIDialogs
|
||||
import ClientGUIListBoxes
|
||||
import HydrusConstants as HC
|
||||
import HydrusData
|
||||
|
@ -438,11 +439,12 @@ class FullscreenHoverFrameTopDuplicatesFilter( FullscreenHoverFrameTop ):
|
|||
|
||||
def _PopulateCenterButtons( self ):
|
||||
|
||||
# probably better to just launch a dialog with this stuff as proper panels, yeah
|
||||
# I'll be making those panels for the custom dialog anyway
|
||||
# make a merge_options serialisable object to track all this that I can plug into the dialog and obey at the db level
|
||||
# store two in the options, for better and same, and then the custom just displays/produces one depending on chosen status
|
||||
|
||||
# cog icon to control
|
||||
# cog icon launches a dialog
|
||||
# file delete on better
|
||||
# extend these to be per-service!
|
||||
# rating merging on better (d NO) (these are mutually exclusive, so add a radio menu type or whatever)
|
||||
# rating moving on better (d YES)
|
||||
# rating merging on same (d YES)
|
||||
|
@ -452,8 +454,8 @@ class FullscreenHoverFrameTopDuplicatesFilter( FullscreenHoverFrameTop ):
|
|||
|
||||
menu_items = []
|
||||
|
||||
menu_items.append( ( 'normal', 'edit tag/ratings merge options and whether to delete bad files', 'help compute.', self._Archive ) )
|
||||
menu_items.append( ( 'normal', 'edit shortcuts', 'help compute.', self._Archive ) )
|
||||
menu_items.append( ( 'normal', 'edit tag/ratings merge options and whether to delete bad files', 'edit what happens when you filter files', self._EditMergeOptions ) )
|
||||
menu_items.append( ( 'normal', 'edit shortcuts', 'edit how to quickly filter files', self._EditShortcuts ) )
|
||||
|
||||
cog_button = ClientGUICommon.MenuBitmapButton( self, CC.GlobalBMPs.cog, menu_items )
|
||||
|
||||
|
@ -462,6 +464,22 @@ class FullscreenHoverFrameTopDuplicatesFilter( FullscreenHoverFrameTop ):
|
|||
FullscreenHoverFrameTop._PopulateCenterButtons( self )
|
||||
|
||||
|
||||
def _EditMergeOptions( self ):
|
||||
|
||||
wx.MessageBox( 'This doesn\'t do anything yet!' )
|
||||
|
||||
|
||||
def _EditShortcuts( self ):
|
||||
|
||||
with ClientGUIDialogs.DialogShortcuts( self ) as dlg:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
HydrusGlobals.client_controller.pub( 'refresh_shortcuts' )
|
||||
|
||||
|
||||
|
||||
|
||||
def _PopulateLeftButtons( self ):
|
||||
|
||||
FullscreenHoverFrameTop._PopulateLeftButtons( self )
|
||||
|
|
|
@ -1304,7 +1304,6 @@ class ListBoxTagsAC( ListBoxTagsPredicates ):
|
|||
|
||||
if len( predicates ) > 0:
|
||||
|
||||
self._Hit( False, False, None )
|
||||
self._Hit( False, False, 0 )
|
||||
|
||||
|
||||
|
@ -1699,7 +1698,7 @@ class ListBoxTagsStringsAddRemove( ListBoxTagsStrings ):
|
|||
|
||||
def EventKeyDown( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in CC.DELETE_KEYS:
|
||||
|
||||
|
@ -1817,6 +1816,8 @@ class ListBoxTagsSelection( ListBoxTags ):
|
|||
|
||||
def _RecalcStrings( self, limit_to_these_tags = None ):
|
||||
|
||||
previous_selected_terms = set( self._selected_terms )
|
||||
|
||||
if limit_to_these_tags is None:
|
||||
|
||||
self._Clear()
|
||||
|
@ -1858,6 +1859,14 @@ class ListBoxTagsSelection( ListBoxTags ):
|
|||
|
||||
|
||||
|
||||
for term in previous_selected_terms:
|
||||
|
||||
if term in self._terms:
|
||||
|
||||
self._selected_terms.add( term )
|
||||
|
||||
|
||||
|
||||
self._SortTags()
|
||||
|
||||
|
||||
|
@ -2197,7 +2206,7 @@ class ListBoxTagsSelectionTagsDialog( ListBoxTagsSelection ):
|
|||
|
||||
if len( self._selected_terms ) > 0:
|
||||
|
||||
self._add_func( self._selected_terms )
|
||||
self._add_func( set( self._selected_terms ) )
|
||||
|
||||
|
||||
|
||||
|
@ -2205,6 +2214,6 @@ class ListBoxTagsSelectionTagsDialog( ListBoxTagsSelection ):
|
|||
|
||||
if len( self._selected_terms ) > 0:
|
||||
|
||||
self._delete_func( self._selected_terms )
|
||||
self._delete_func( set( self._selected_terms ) )
|
||||
|
||||
|
||||
|
|
|
@ -383,7 +383,7 @@ def GenerateDumpMultipartFormDataCTAndBody( fields ):
|
|||
|
||||
def EventKeyDown( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ): self.EventReady( None )
|
||||
else: event.Skip()
|
||||
|
@ -776,6 +776,7 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
|
|||
|
||||
self._file_domain_button = ClientGUICommon.BetterButton( self._filtering_panel, 'file domain', self._FileDomainButtonHit )
|
||||
self._num_unknown_duplicates = wx.StaticText( self._filtering_panel )
|
||||
self._num_better_duplicates = wx.StaticText( self._filtering_panel )
|
||||
self._num_same_file_duplicates = wx.StaticText( self._filtering_panel )
|
||||
self._num_alternate_duplicates = wx.StaticText( self._filtering_panel )
|
||||
self._show_some_dupes = ClientGUICommon.BetterButton( self._filtering_panel, 'show some pairs (prototype!)', self._ShowSomeDupes )
|
||||
|
@ -828,6 +829,7 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
|
|||
|
||||
self._filtering_panel.AddF( self._file_domain_button, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
self._filtering_panel.AddF( self._num_unknown_duplicates, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
self._filtering_panel.AddF( self._num_better_duplicates, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
self._filtering_panel.AddF( self._num_same_file_duplicates, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
self._filtering_panel.AddF( self._num_alternate_duplicates, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
self._filtering_panel.AddF( self._show_some_dupes, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
@ -1146,9 +1148,10 @@ class ManagementPanelDuplicateFilter( ManagementPanel ):
|
|||
|
||||
num_unknown = duplicate_types_to_count[ HC.DUPLICATE_UNKNOWN ]
|
||||
|
||||
self._num_unknown_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( num_unknown ) + ' potential duplicates found.' )
|
||||
self._num_same_file_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( duplicate_types_to_count[ HC.DUPLICATE_SAME_FILE ] ) + ' same file pairs filtered.' )
|
||||
self._num_alternate_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( duplicate_types_to_count[ HC.DUPLICATE_ALTERNATE ] ) + ' alternate file pairs filtered.' )
|
||||
self._num_unknown_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( num_unknown ) + ' potential matches found.' )
|
||||
self._num_better_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( duplicate_types_to_count[ HC.DUPLICATE_BETTER ] ) + ' better/worse pairs filtered.' )
|
||||
self._num_same_file_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( duplicate_types_to_count[ HC.DUPLICATE_SAME_FILE ] ) + ' exactly similar pairs filtered.' )
|
||||
self._num_alternate_duplicates.SetLabelText( HydrusData.ConvertIntToPrettyString( duplicate_types_to_count[ HC.DUPLICATE_ALTERNATE ] ) + ' alternate pairs filtered.' )
|
||||
|
||||
if num_unknown > 0:
|
||||
|
||||
|
@ -1533,7 +1536,7 @@ class ManagementPanelGalleryImport( ManagementPanel ):
|
|||
|
||||
def EventKeyDown( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
||||
|
||||
|
@ -2064,7 +2067,7 @@ class ManagementPanelPageOfImagesImport( ManagementPanel ):
|
|||
|
||||
def EventKeyDown( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
||||
|
||||
|
@ -3076,7 +3079,7 @@ class ManagementPanelThreadWatcherImport( ManagementPanel ):
|
|||
|
||||
def EventKeyDown( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
||||
|
||||
|
@ -3341,7 +3344,7 @@ class ManagementPanelURLsImport( ManagementPanel ):
|
|||
|
||||
def EventKeyDown( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
||||
|
||||
|
|
|
@ -2788,6 +2788,8 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
shortcut_names = HydrusGlobals.client_controller.Read( 'serialisable_names', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS )
|
||||
|
||||
shortcut_names = [ name for name in shortcut_names if name not in CC.SHORTCUTS_RESERVED_NAMES ]
|
||||
|
||||
if len( shortcut_names ) > 0:
|
||||
|
||||
custom_shortcuts_menu = wx.Menu()
|
||||
|
|
|
@ -306,8 +306,14 @@ class OptionsPanelTags( OptionsPanel ):
|
|||
|
||||
for namespace in namespaces:
|
||||
|
||||
if namespace == '': label = 'no namespace'
|
||||
else: label = namespace
|
||||
if namespace == '':
|
||||
|
||||
label = 'unnamespaced'
|
||||
|
||||
else:
|
||||
|
||||
label = namespace
|
||||
|
||||
|
||||
namespace_checkbox = wx.CheckBox( self, label = label )
|
||||
|
||||
|
|
|
@ -44,6 +44,11 @@ class ReviewServicePanel( wx.Panel ):
|
|||
subpanels.append( self._ServiceFilePanel( self, service ) )
|
||||
|
||||
|
||||
if self._service.GetServiceKey() == CC.TRASH_SERVICE_KEY:
|
||||
|
||||
subpanels.append( self._ServiceTrashPanel( self, service ) )
|
||||
|
||||
|
||||
if service_type in HC.TAG_SERVICES:
|
||||
|
||||
subpanels.append( self._ServiceTagPanel( self, service ) )
|
||||
|
@ -225,24 +230,6 @@ class ReviewServicePanel( wx.Panel ):
|
|||
|
||||
|
||||
|
||||
def EventClearTrash( self, event ):
|
||||
|
||||
def do_it():
|
||||
|
||||
hashes = self._controller.Read( 'trash_hashes' )
|
||||
|
||||
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, hashes )
|
||||
|
||||
service_keys_to_content_updates = { CC.TRASH_SERVICE_KEY : [ content_update ] }
|
||||
|
||||
self._controller.WriteSynchronous( 'content_updates', service_keys_to_content_updates )
|
||||
|
||||
wx.CallAfter( self._DisplayService )
|
||||
|
||||
|
||||
self._controller.CallToThread( do_it )
|
||||
|
||||
|
||||
def EventCopyExternalShareURL( self, event ):
|
||||
|
||||
shares = self._booru_shares.GetSelectedClientData()
|
||||
|
@ -414,18 +401,6 @@ class ReviewServicePanel( wx.Panel ):
|
|||
self.UnpinIPFSDirectories()
|
||||
|
||||
|
||||
def EventServiceWideUpdate( self, event ):
|
||||
|
||||
with ClientGUITopLevelWindows.DialogNullipotent( self, 'advanced content update' ) as dlg:
|
||||
|
||||
panel = ClientGUIScrolledPanelsReview.AdvancedContentUpdatePanel( dlg, self._service_key )
|
||||
|
||||
dlg.SetPanel( panel )
|
||||
|
||||
dlg.ShowModal()
|
||||
|
||||
|
||||
|
||||
def GetServiceKey( self ):
|
||||
|
||||
return self._service.GetServiceKey()
|
||||
|
@ -521,7 +496,7 @@ class ReviewServicePanel( wx.Panel ):
|
|||
|
||||
def _Refresh( self ):
|
||||
|
||||
HydrusGlobals.client_controller.CallToThread( self.THREADUpdateTagInfo )
|
||||
HydrusGlobals.client_controller.CallToThread( self.THREADUpdateFileInfo )
|
||||
|
||||
|
||||
def _UpdateFromThread( self, text ):
|
||||
|
@ -546,7 +521,7 @@ class ReviewServicePanel( wx.Panel ):
|
|||
|
||||
|
||||
|
||||
def THREADUpdateTagInfo( self ):
|
||||
def THREADUpdateFileInfo( self ):
|
||||
|
||||
service_info = HydrusGlobals.client_controller.Read( 'service_info', self._service.GetServiceKey() )
|
||||
|
||||
|
@ -1008,16 +983,28 @@ class ReviewServicePanel( wx.Panel ):
|
|||
|
||||
def _SyncNow( self ):
|
||||
|
||||
def do_it():
|
||||
|
||||
self._service.Sync( False )
|
||||
|
||||
self._my_updater.Update()
|
||||
|
||||
message = 'This will tell the database to process any outstanding update files.'
|
||||
message += os.linesep * 2
|
||||
message += 'This is a big task that usually runs during idle time. It locks the entire database. If you interact significantly with the program while it runs, your gui will hang, and then you will have to wait a long time for the processing to completely finish before you get it back.'
|
||||
message += os.linesep * 2
|
||||
message += 'If you are a new user, click \'no\'!'
|
||||
|
||||
self._sync_now_button.Disable()
|
||||
|
||||
HydrusGlobals.client_controller.CallToThread( do_it )
|
||||
with ClientGUIDialogs.DialogYesNo( self, message ) as dlg:
|
||||
|
||||
if dlg.ShowModal() == wx.ID_YES:
|
||||
|
||||
def do_it():
|
||||
|
||||
self._service.Sync( False )
|
||||
|
||||
self._my_updater.Update()
|
||||
|
||||
|
||||
self._sync_now_button.Disable()
|
||||
|
||||
HydrusGlobals.client_controller.CallToThread( do_it )
|
||||
|
||||
|
||||
|
||||
|
||||
def _UpdateFromThread( self, download_text, download_value, processing_text, processing_value, range ):
|
||||
|
@ -1027,6 +1014,29 @@ class ReviewServicePanel( wx.Panel ):
|
|||
self._download_progress.SetValue( download_text, download_value, range )
|
||||
self._processing_progress.SetValue( processing_text, processing_value, range )
|
||||
|
||||
if processing_value == download_value:
|
||||
|
||||
self._sync_now_button.Disable()
|
||||
|
||||
|
||||
if download_value == 0:
|
||||
|
||||
self._export_updates_button.Disable()
|
||||
|
||||
else:
|
||||
|
||||
self._export_updates_button.Enable()
|
||||
|
||||
|
||||
if processing_value == 0:
|
||||
|
||||
self._reset_button.Disable()
|
||||
|
||||
else:
|
||||
|
||||
self._reset_button.Enable()
|
||||
|
||||
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
pass
|
||||
|
@ -1143,7 +1153,7 @@ class ReviewServicePanel( wx.Panel ):
|
|||
|
||||
self._my_updater = ClientGUICommon.ThreadToGUIUpdater( self, self._Refresh )
|
||||
|
||||
self._name_and_type = wx.StaticText( self )
|
||||
self._rating_info_st = wx.StaticText( self )
|
||||
|
||||
#
|
||||
|
||||
|
@ -1151,16 +1161,26 @@ class ReviewServicePanel( wx.Panel ):
|
|||
|
||||
#
|
||||
|
||||
self.AddF( self._name_and_type, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
self.AddF( self._rating_info_st, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
HydrusGlobals.client_controller.sub( self, 'Update', 'service_updated' )
|
||||
|
||||
|
||||
def _Refresh( self ):
|
||||
|
||||
# put this fetch on a thread, since it'll have to go to the db
|
||||
HydrusGlobals.client_controller.CallToThread( self.THREADUpdateRatingInfo )
|
||||
|
||||
self._name_and_type.SetLabelText( 'This service has ratings. This box will regain its old information in a later version.' )
|
||||
|
||||
def _UpdateFromThread( self, text ):
|
||||
|
||||
try:
|
||||
|
||||
self._rating_info_st.SetLabelText( text )
|
||||
|
||||
except wx.PyDeadObjectError:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def Update( self, service ):
|
||||
|
@ -1173,6 +1193,17 @@ class ReviewServicePanel( wx.Panel ):
|
|||
|
||||
|
||||
|
||||
def THREADUpdateRatingInfo( self ):
|
||||
|
||||
service_info = HydrusGlobals.client_controller.Read( 'service_info', self._service.GetServiceKey() )
|
||||
|
||||
num_files = service_info[ HC.SERVICE_INFO_NUM_FILES ]
|
||||
|
||||
text = HydrusData.ConvertIntToPrettyString( num_files ) + ' files are rated'
|
||||
|
||||
wx.CallAfter( self._UpdateFromThread, text )
|
||||
|
||||
|
||||
|
||||
class _ServiceTagPanel( ClientGUICommon.StaticBox ):
|
||||
|
||||
|
@ -1186,6 +1217,8 @@ class ReviewServicePanel( wx.Panel ):
|
|||
|
||||
self._tag_info_st = wx.StaticText( self )
|
||||
|
||||
self._advanced_content_update = ClientGUICommon.BetterButton( self, 'advanced service-wide update', self._AdvancedContentUpdate )
|
||||
|
||||
#
|
||||
|
||||
self._Refresh()
|
||||
|
@ -1193,10 +1226,23 @@ class ReviewServicePanel( wx.Panel ):
|
|||
#
|
||||
|
||||
self.AddF( self._tag_info_st, CC.FLAGS_EXPAND_PERPENDICULAR )
|
||||
self.AddF( self._advanced_content_update, CC.FLAGS_LONE_BUTTON )
|
||||
|
||||
HydrusGlobals.client_controller.sub( self, 'Update', 'service_updated' )
|
||||
|
||||
|
||||
def _AdvancedContentUpdate( self ):
|
||||
|
||||
with ClientGUITopLevelWindows.DialogNullipotent( self, 'advanced content update' ) as dlg:
|
||||
|
||||
panel = ClientGUIScrolledPanelsReview.AdvancedContentUpdatePanel( dlg, self._service.GetServiceKey() )
|
||||
|
||||
dlg.SetPanel( panel )
|
||||
|
||||
dlg.ShowModal()
|
||||
|
||||
|
||||
|
||||
def _Refresh( self ):
|
||||
|
||||
HydrusGlobals.client_controller.CallToThread( self.THREADUpdateTagInfo )
|
||||
|
@ -1245,3 +1291,49 @@ class ReviewServicePanel( wx.Panel ):
|
|||
|
||||
|
||||
|
||||
class _ServiceTrashPanel( ClientGUICommon.StaticBox ):
|
||||
|
||||
def __init__( self, parent, service ):
|
||||
|
||||
ClientGUICommon.StaticBox.__init__( self, parent, 'trash' )
|
||||
|
||||
self._service = service
|
||||
|
||||
self._clear_trash = ClientGUICommon.BetterButton( self, 'clear trash', self._ClearTrash )
|
||||
|
||||
#
|
||||
|
||||
self.AddF( self._clear_trash, CC.FLAGS_LONE_BUTTON )
|
||||
|
||||
|
||||
def _ClearTrash( self ):
|
||||
|
||||
message = 'This will completely clear your trash of all its files, deleting them permanently from the client. This operation cannot be undone.'
|
||||
message += os.linesep * 2
|
||||
message += 'If you have many files in your trash, it will take some time to complete and for all the files to eventually be deleted.'
|
||||
|
||||
with ClientGUIDialogs.DialogYesNo( self, message, yes_label = 'do it', no_label = 'forget it' ) as dlg_add:
|
||||
|
||||
result = dlg_add.ShowModal()
|
||||
|
||||
if result == wx.ID_YES:
|
||||
|
||||
def do_it():
|
||||
|
||||
hashes = HydrusGlobals.client_controller.Read( 'trash_hashes' )
|
||||
|
||||
content_update = HydrusData.ContentUpdate( HC.CONTENT_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, hashes )
|
||||
|
||||
service_keys_to_content_updates = { CC.TRASH_SERVICE_KEY : [ content_update ] }
|
||||
|
||||
HydrusGlobals.client_controller.WriteSynchronous( 'content_updates', service_keys_to_content_updates )
|
||||
|
||||
HydrusGlobals.client_controller.pub( 'service_updated', self._service )
|
||||
|
||||
|
||||
HydrusGlobals.client_controller.CallToThread( do_it )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1622,7 +1622,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
def EventKeyDownNamespace( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
||||
|
||||
|
@ -2854,7 +2854,10 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
shortcuts[ wx.ACCEL_ALT ] = {}
|
||||
shortcuts[ wx.ACCEL_SHIFT ] = {}
|
||||
|
||||
for ( modifier, key, action ) in self._shortcuts.GetClientData(): shortcuts[ modifier ][ key ] = action
|
||||
for ( modifier, key, action ) in self._shortcuts.GetClientData():
|
||||
|
||||
shortcuts[ modifier ][ key ] = action
|
||||
|
||||
|
||||
HC.options[ 'shortcuts' ] = shortcuts
|
||||
|
||||
|
@ -2931,7 +2934,7 @@ class ManageOptionsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
|
||||
def EventKeyDownSortBy( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER ):
|
||||
|
||||
|
@ -4421,7 +4424,7 @@ class ManageTagsPanel( ClientGUIScrolledPanels.ManagePanel ):
|
|||
# the char hook event goes up. if it isn't skipped all the way, the subsequent text event will never occur
|
||||
# however we don't want the char hook going all the way up sometimes!
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if not HC.PLATFORM_LINUX:
|
||||
|
||||
|
|
|
@ -668,7 +668,7 @@ class FrameThatTakesScrollablePanel( FrameThatResizes ):
|
|||
|
||||
def EventCharHook( self, event ):
|
||||
|
||||
( modifier, key ) = ClientData.GetShortcutFromEvent( event )
|
||||
( modifier, key ) = ClientData.ConvertKeyEventToSimpleTuple( event )
|
||||
|
||||
if key == wx.WXK_ESCAPE:
|
||||
|
||||
|
|
|
@ -1111,6 +1111,8 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
|
||||
successful_hashes = set()
|
||||
|
||||
i = 0
|
||||
|
||||
while True:
|
||||
|
||||
path = self._path_cache.GetNextSeed( CC.STATUS_UNKNOWN )
|
||||
|
@ -1211,6 +1213,13 @@ class ImportFolder( HydrusSerialisable.SerialisableBaseNamed ):
|
|||
self._path_cache.UpdateSeedStatus( path, CC.STATUS_FAILED, exception = e )
|
||||
|
||||
|
||||
i += 1
|
||||
|
||||
if i % 100 == 0:
|
||||
|
||||
self._ActionPaths()
|
||||
|
||||
|
||||
|
||||
if len( successful_hashes ) > 0:
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 18
|
||||
SOFTWARE_VERSION = 250
|
||||
SOFTWARE_VERSION = 251
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
@ -135,6 +135,8 @@ CONTENT_UPDATE_DENY_PEND = 11
|
|||
CONTENT_UPDATE_DENY_PETITION = 12
|
||||
CONTENT_UPDATE_ADVANCED = 13
|
||||
CONTENT_UPDATE_UNDELETE = 14
|
||||
CONTENT_UPDATE_SET = 15
|
||||
CONTENT_UPDATE_FLIP = 16
|
||||
|
||||
content_update_string_lookup = {}
|
||||
|
||||
|
@ -151,6 +153,8 @@ content_update_string_lookup[ CONTENT_UPDATE_RATING ] = 'rating'
|
|||
content_update_string_lookup[ CONTENT_UPDATE_DENY_PEND ] = 'deny pend'
|
||||
content_update_string_lookup[ CONTENT_UPDATE_DENY_PETITION ] = 'deny petition'
|
||||
content_update_string_lookup[ CONTENT_UPDATE_UNDELETE ] = 'undelete'
|
||||
content_update_string_lookup[ CONTENT_UPDATE_SET ] = 'set'
|
||||
content_update_string_lookup[ CONTENT_UPDATE_FLIP ] = 'flip on/off'
|
||||
|
||||
DEFINITIONS_TYPE_HASHES = 0
|
||||
DEFINITIONS_TYPE_TAGS = 1
|
||||
|
@ -159,6 +163,10 @@ DUPLICATE_UNKNOWN = 0
|
|||
DUPLICATE_NOT_DUPLICATE = 1
|
||||
DUPLICATE_SAME_FILE = 2
|
||||
DUPLICATE_ALTERNATE = 3
|
||||
DUPLICATE_BETTER = 4
|
||||
DUPLICATE_SMALLER_BETTER = 5
|
||||
DUPLICATE_LARGER_BETTER = 6
|
||||
DUPLICATE_WORSE = 7
|
||||
|
||||
ENCODING_RAW = 0
|
||||
ENCODING_HEX = 1
|
||||
|
|
|
@ -320,7 +320,9 @@ def LaunchDirectory( path ):
|
|||
|
||||
cmd.append( path )
|
||||
|
||||
process = subprocess.Popen( cmd, startupinfo = HydrusData.GetSubprocessStartupInfo() )
|
||||
# setsid call un-childs this new process
|
||||
|
||||
process = subprocess.Popen( cmd, preexec_fn = os.setsid, startupinfo = HydrusData.GetSubprocessStartupInfo() )
|
||||
|
||||
process.wait()
|
||||
|
||||
|
@ -349,11 +351,13 @@ def LaunchFile( path ):
|
|||
|
||||
cmd.append( path )
|
||||
|
||||
process = subprocess.Popen( cmd, startupinfo = HydrusData.GetSubprocessStartupInfo() )
|
||||
# setsid call un-childs this new process
|
||||
|
||||
process = subprocess.Popen( cmd, preexec_fn = os.setsid, startupinfo = HydrusData.GetSubprocessStartupInfo() )
|
||||
|
||||
process.wait()
|
||||
|
||||
process.communicate()
|
||||
process.communicate()
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ SERIALISABLE_TYPE_METADATA = 37
|
|||
SERIALISABLE_TYPE_BANDWIDTH_RULES = 38
|
||||
SERIALISABLE_TYPE_BANDWIDTH_TRACKER = 39
|
||||
SERIALISABLE_TYPE_CLIENT_TO_SERVER_UPDATE = 40
|
||||
SERIALISABLE_TYPE_SHORTCUT = 41
|
||||
SERIALISABLE_TYPE_APPLICATION_COMMAND = 42
|
||||
|
||||
SERIALISABLE_TYPES_TO_OBJECT_TYPES = {}
|
||||
|
||||
|
|
|
@ -1171,35 +1171,41 @@ class TestClientDB( unittest.TestCase ):
|
|||
|
||||
def test_shortcuts( self ):
|
||||
|
||||
num_default = len( ClientDefaults.GetDefaultShortcuts() )
|
||||
|
||||
result = self._read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS )
|
||||
|
||||
self.assertEqual( result, [] )
|
||||
self.assertEqual( len( result ), num_default )
|
||||
|
||||
#
|
||||
|
||||
shortcuts = ClientData.Shortcuts( 'test' )
|
||||
|
||||
shortcuts.SetKeyboardAction( wx.ACCEL_NORMAL, wx.WXK_NUMPAD1, ( HydrusData.GenerateKey(), 'action_data' ) )
|
||||
shortcuts.SetKeyboardAction( wx.ACCEL_SHIFT, wx.WXK_END, ( None, 'other_action_data' ) )
|
||||
|
||||
self._write( 'serialisable', shortcuts )
|
||||
|
||||
result = self._read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS )
|
||||
|
||||
self.assertEqual( len( result ), 1 )
|
||||
|
||||
result = self._read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS, 'test' )
|
||||
|
||||
self.assertEqual( result.GetKeyboardAction( wx.ACCEL_NORMAL, wx.WXK_NUMPAD1 ), shortcuts.GetKeyboardAction( wx.ACCEL_NORMAL, wx.WXK_NUMPAD1 ) )
|
||||
self.assertEqual( result.GetKeyboardAction( wx.ACCEL_SHIFT, wx.WXK_END ), shortcuts.GetKeyboardAction( wx.ACCEL_SHIFT, wx.WXK_END ) )
|
||||
|
||||
#
|
||||
|
||||
self._write( 'delete_serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS, 'test' )
|
||||
|
||||
result = self._read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS )
|
||||
|
||||
self.assertEqual( result, [] )
|
||||
for ( i, shortcuts ) in enumerate( ClientDefaults.GetDefaultShortcuts() ):
|
||||
|
||||
name = 'shortcuts ' + str( i )
|
||||
|
||||
shortcuts.SetName( name )
|
||||
|
||||
self._write( 'serialisable', shortcuts )
|
||||
|
||||
result = self._read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS )
|
||||
|
||||
self.assertEqual( len( result ), num_default + 1 )
|
||||
|
||||
result = self._read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS, name )
|
||||
|
||||
for ( shortcut, command ) in shortcuts:
|
||||
|
||||
self.assertEqual( result.GetCommand( shortcut ).GetData(), command.GetData() )
|
||||
|
||||
|
||||
#
|
||||
|
||||
self._write( 'delete_serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS, name )
|
||||
|
||||
result = self._read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS )
|
||||
|
||||
self.assertEqual( len( result ), num_default )
|
||||
|
||||
|
||||
|
||||
class TestServerDB( unittest.TestCase ):
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import ClientConstants as CC
|
||||
import ClientData
|
||||
import ClientDefaults
|
||||
import ClientDownloading
|
||||
import ClientImporting
|
||||
import ClientSearch
|
||||
|
@ -117,53 +118,108 @@ class TestSerialisables( unittest.TestCase ):
|
|||
self._dump_and_load_and_test( db, test )
|
||||
|
||||
|
||||
def test_SERIALISABLE_TYPE_APPLICATION_COMMAND( self ):
|
||||
|
||||
def test( obj, dupe_obj ):
|
||||
|
||||
self.assertEqual( obj.GetCommandType(), dupe_obj.GetCommandType() )
|
||||
|
||||
self.assertEqual( obj.GetData(), dupe_obj.GetData() )
|
||||
|
||||
|
||||
acs = []
|
||||
|
||||
acs.append( ( ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'archive' ), 'archive' ) )
|
||||
acs.append( ( ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( HydrusData.GenerateKey(), HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_FLIP, 'test' ) ), 'flip on/off mappings "test" for unknown service!' ) )
|
||||
acs.append( ( ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_FLIP, 'test' ) ), 'flip on/off mappings "test" for local tags' ) )
|
||||
acs.append( ( ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( HydrusData.GenerateKey(), HC.CONTENT_TYPE_RATINGS, HC.CONTENT_UPDATE_SET, 0.4 ) ), 'set ratings "0.4" for unknown service!' ) )
|
||||
|
||||
for ( ac, s ) in acs:
|
||||
|
||||
self._dump_and_load_and_test( ac, test )
|
||||
|
||||
self.assertEqual( ac.ToString(), s )
|
||||
|
||||
|
||||
|
||||
def test_SERIALISABLE_TYPE_SHORTCUT( self ):
|
||||
|
||||
def test( obj, dupe_obj ):
|
||||
|
||||
self.assertEqual( dupe_obj.__hash__(), ( dupe_obj._shortcut_type, dupe_obj._shortcut_key, tuple( dupe_obj._modifiers ) ).__hash__() )
|
||||
|
||||
self.assertEqual( obj, dupe_obj )
|
||||
|
||||
|
||||
shortcuts = []
|
||||
|
||||
shortcuts.append( ( ClientData.Shortcut(), 'f7' ) )
|
||||
|
||||
shortcuts.append( ( ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_SPACE, [] ), 'space' ) )
|
||||
shortcuts.append( ( ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, ord( 'a' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), 'ctrl+a' ) )
|
||||
shortcuts.append( ( ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, ord( 'A' ), [ CC.SHORTCUT_MODIFIER_CTRL ] ), 'ctrl+a' ) )
|
||||
shortcuts.append( ( ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_HOME, [ CC.SHORTCUT_MODIFIER_ALT, CC.SHORTCUT_MODIFIER_CTRL ] ), 'ctrl+alt+home' ) )
|
||||
|
||||
shortcuts.append( ( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_LEFT, [] ), 'left-click' ) )
|
||||
shortcuts.append( ( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_MIDDLE, [ CC.SHORTCUT_MODIFIER_CTRL ] ), 'ctrl+middle-click' ) )
|
||||
shortcuts.append( ( ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_SCROLL_DOWN, [ CC.SHORTCUT_MODIFIER_ALT, CC.SHORTCUT_MODIFIER_SHIFT ] ), 'alt+shift+scroll down' ) )
|
||||
|
||||
for ( shortcut, s ) in shortcuts:
|
||||
|
||||
self._dump_and_load_and_test( shortcut, test )
|
||||
|
||||
self.assertEqual( shortcut.ToString(), s )
|
||||
|
||||
|
||||
|
||||
def test_SERIALISABLE_TYPE_SHORTCUTS( self ):
|
||||
|
||||
def test( obj, dupe_obj ):
|
||||
|
||||
for ( ( modifier, key ), action ) in obj.IterateKeyboardShortcuts():
|
||||
for ( shortcut, command ) in obj:
|
||||
|
||||
self.assertEqual( dupe_obj.GetKeyboardAction( modifier, key ), action )
|
||||
|
||||
|
||||
for ( ( modifier, mouse_button ), action ) in obj.IterateMouseShortcuts():
|
||||
|
||||
self.assertEqual( dupe_obj.GetMouseAction( modifier, mouse_button ), action )
|
||||
self.assertEqual( dupe_obj.GetCommand( shortcut ).GetData(), command.GetData() )
|
||||
|
||||
|
||||
|
||||
action_1 = ( HydrusData.GenerateKey(), u'test unicode tag\u2026' )
|
||||
action_2 = ( HydrusData.GenerateKey(), 0.5 )
|
||||
action_3 = ( None, 'archive' )
|
||||
default_shortcuts = ClientDefaults.GetDefaultShortcuts()
|
||||
|
||||
for shortcuts in default_shortcuts:
|
||||
|
||||
self._dump_and_load_and_test( shortcuts, test )
|
||||
|
||||
|
||||
command_1 = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_SIMPLE, 'archive' )
|
||||
command_2 = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( HydrusData.GenerateKey(), HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_FLIP, 'test' ) )
|
||||
command_3 = ClientData.ApplicationCommand( CC.APPLICATION_COMMAND_TYPE_CONTENT, ( CC.LOCAL_TAG_SERVICE_KEY, HC.CONTENT_TYPE_MAPPINGS, HC.CONTENT_UPDATE_FLIP, 'test' ) )
|
||||
|
||||
k_shortcut_1 = ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_SPACE, [] )
|
||||
k_shortcut_2 = ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, ord( 'a' ), [ CC.SHORTCUT_MODIFIER_CTRL ] )
|
||||
k_shortcut_3 = ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, ord( 'A' ), [ CC.SHORTCUT_MODIFIER_CTRL ] )
|
||||
k_shortcut_4 = ClientData.Shortcut( CC.SHORTCUT_TYPE_KEYBOARD, wx.WXK_HOME, [ CC.SHORTCUT_MODIFIER_ALT, CC.SHORTCUT_MODIFIER_CTRL ] )
|
||||
|
||||
m_shortcut_1 = ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_LEFT, [] )
|
||||
m_shortcut_2 = ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_MIDDLE, [ CC.SHORTCUT_MODIFIER_CTRL ] )
|
||||
m_shortcut_3 = ClientData.Shortcut( CC.SHORTCUT_TYPE_MOUSE, CC.SHORTCUT_MOUSE_SCROLL_DOWN, [ CC.SHORTCUT_MODIFIER_ALT, CC.SHORTCUT_MODIFIER_SHIFT ] )
|
||||
|
||||
shortcuts = ClientData.Shortcuts( 'test' )
|
||||
|
||||
shortcuts.SetKeyboardAction( wx.ACCEL_NORMAL, wx.WXK_NUMPAD1, action_1 )
|
||||
shortcuts.SetKeyboardAction( wx.ACCEL_SHIFT, wx.WXK_END, action_2 )
|
||||
shortcuts.SetKeyboardAction( wx.ACCEL_CTRL, ord( 'R' ), action_3 )
|
||||
shortcuts.SetCommand( k_shortcut_1, command_1 )
|
||||
shortcuts.SetCommand( k_shortcut_2, command_2 )
|
||||
shortcuts.SetCommand( k_shortcut_3, command_2 )
|
||||
shortcuts.SetCommand( k_shortcut_4, command_3 )
|
||||
|
||||
shortcuts.SetMouseAction( wx.ACCEL_NORMAL, wx.MOUSE_BTN_LEFT, action_1 )
|
||||
shortcuts.SetMouseAction( wx.ACCEL_SHIFT, wx.MOUSE_BTN_MIDDLE, action_2 )
|
||||
shortcuts.SetMouseAction( wx.ACCEL_CTRL, wx.MOUSE_BTN_RIGHT, action_3 )
|
||||
shortcuts.SetMouseAction( wx.ACCEL_ALT, wx.MOUSE_WHEEL_VERTICAL, action_3 )
|
||||
shortcuts.SetCommand( m_shortcut_1, command_1 )
|
||||
shortcuts.SetCommand( m_shortcut_2, command_2 )
|
||||
shortcuts.SetCommand( m_shortcut_3, command_3 )
|
||||
|
||||
k_a = [ item for item in shortcuts.IterateKeyboardShortcuts() ]
|
||||
self._dump_and_load_and_test( shortcuts, test )
|
||||
|
||||
self.assertEqual( len( k_a ), 3 )
|
||||
self.assertEqual( shortcuts.GetCommand( k_shortcut_1 ).GetData(), command_1.GetData() )
|
||||
|
||||
for ( ( modifier, key ), action ) in k_a:
|
||||
|
||||
self.assertEqual( shortcuts.GetKeyboardAction( modifier, key ), action )
|
||||
|
||||
shortcuts.SetCommand( k_shortcut_1, command_3 )
|
||||
|
||||
m_a = [ item for item in shortcuts.IterateMouseShortcuts() ]
|
||||
|
||||
self.assertEqual( len( m_a ), 4 )
|
||||
|
||||
for ( ( modifier, mouse_button ), action ) in m_a:
|
||||
|
||||
self.assertEqual( shortcuts.GetMouseAction( modifier, mouse_button ), action )
|
||||
|
||||
self.assertEqual( shortcuts.GetCommand( k_shortcut_1 ).GetData(), command_3.GetData() )
|
||||
|
||||
|
||||
def test_SERIALISABLE_TYPE_SUBSCRIPTION( self ):
|
||||
|
|
Loading…
Reference in New Issue