Version 142
This commit is contained in:
parent
b7edfb9719
commit
4927016a76
|
@ -6,6 +6,16 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
<h3>searching with wildcards</h3>
|
||||
<p>The autocomplete tag dropdown supports wildcard searching with '*'.</p>
|
||||
<p><img src="wildcard_gelion.png"/></p>
|
||||
<p>The '*' will match <i>any</i> number of characters. Every normal search has a secret '*' on the end that you don't see, which is how full words get matched from you only typing in a few letters.</p>
|
||||
<p>This is useful when you can only remember part of a word, or can't spell its first half. You can put the '*' anywhere, but you may want to experiment to get used to the exact way these searches work. Some results can be surprising!</p>
|
||||
<p><img src="wildcard_vage.png"/></p>
|
||||
<p>You can select the special predicate inserted at the top of your autocomplete results (the highlighted '*vagelion' and '*va*ge*' above), and it will work like any other predicate. <b>It will return all files that match that wildcard,</b> i.e. every file for every other tag in the current dropdown list.</p>
|
||||
<p>This is particularly useful if you have a number of files with commonly structured over-informationed tags, like this:</p>
|
||||
<p><img src="wildcard_cool_pic.png"/></p>
|
||||
<p>In this case, selecting the 'title:cool pic*' predicate will return all three images in the same search, where you can conveniently give them some more-easily searched tags like 'series:cool pic' and 'page:1', 'page:2', 'page:3'.</p>
|
||||
<h3>using flash in fullscreen view</h3>
|
||||
<p>Flash files are sometimes interested in inputs (like spacebar or mouse-scrollwheel) that mean something to hydrus's fullscreen view, and the libraries I have to use to show flash don't handle these events like normal windows. I now have it set so if your mouse is inside the flash window, the input will go to the flash, and if it is outside, it goes to the fullscreen window.</p>
|
||||
<p>So, if you want to play a flash game in fullscreen, keep your mouse inside the window.</p>
|
||||
|
|
|
@ -8,6 +8,26 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 142</h3></li>
|
||||
<ul>
|
||||
<li>added wildcards to autocomplete results and file queries</li>
|
||||
<li>autocomplete results will match your wildcard _exactly_</li>
|
||||
<li>a new predicate type will appear at the top of wildcard queries; selecting it will search files with that wildcard</li>
|
||||
<li>the new wildcard predicate can be prepended with a minus sign to exclude from results just like normal tag and namespace queries</li>
|
||||
<li>in wildcard predicates, namespace and/or tag can contain wildcard characters</li>
|
||||
<li>added some wildcard help</li>
|
||||
<li>putting in '***' as an autocomplete query is now a pretty bad idea!</li>
|
||||
<li>fixed some logic in how tags are matched against unusual search input in the db</li>
|
||||
<li>sped up and cleaned how tags are matched against search input</li>
|
||||
<li>fixed some namespace logic in how tags are matched against search input</li>
|
||||
<li>below-character-threshold autocomplete queries will now return all applicable namespace results (e.g. putting in '1' will return [ 1, page:1, chapter:1, volume:1 ], not just [ 1 ])</li>
|
||||
<li>added 'open externally' to launch the file in default external program to thumbnail and media viewer menu</li>
|
||||
<li>added a five second delay between gallery-page fetches in the downloader to reduce chance of 429 errors (this was affecting big sankaku searches)</li>
|
||||
<li>added danbooru webm downloading</li>
|
||||
<li>fixed a typo in the thread watcher</li>
|
||||
<li>fixed a bit-rot bug that was stopping the 'like' ratings filter from launching</li>
|
||||
<li>fixed right click menu in custom filter</li>
|
||||
</ul>
|
||||
<li><h3>version 141</h3></li>
|
||||
<ul>
|
||||
<li>combined mappings are no longer calculated and stored</li>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
|
@ -1571,6 +1571,19 @@ class FileSearchContext( object ):
|
|||
elif operator == '-': self._namespaces_to_exclude.append( namespace )
|
||||
|
||||
|
||||
wildcard_predicates = [ predicate for predicate in predicates if predicate.GetPredicateType() == HC.PREDICATE_TYPE_WILDCARD ]
|
||||
|
||||
self._wildcards_to_include = []
|
||||
self._wildcards_to_exclude = []
|
||||
|
||||
for predicate in wildcard_predicates:
|
||||
|
||||
( operator, wildcard ) = predicate.GetValue()
|
||||
|
||||
if operator == '+': self._wildcards_to_include.append( wildcard )
|
||||
elif operator == '-': self._wildcards_to_exclude.append( wildcard )
|
||||
|
||||
|
||||
|
||||
def GetFileServiceKey( self ): return self._file_service_key
|
||||
def GetNamespacesToExclude( self ): return self._namespaces_to_exclude
|
||||
|
@ -1580,6 +1593,8 @@ class FileSearchContext( object ):
|
|||
def GetTagServiceKey( self ): return self._tag_service_key
|
||||
def GetTagsToExclude( self ): return self._tags_to_exclude
|
||||
def GetTagsToInclude( self ): return self._tags_to_include
|
||||
def GetWildcardsToExclude( self ): return self._wildcards_to_exclude
|
||||
def GetWildcardsToInclude( self ): return self._wildcards_to_include
|
||||
def IncludeCurrentTags( self ): return self._include_current_tags
|
||||
def IncludePendingTags( self ): return self._include_pending_tags
|
||||
|
||||
|
|
|
@ -1835,7 +1835,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
if len( half_complete_tag ) > 0:
|
||||
|
||||
normal_characters = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
normal_characters = set( 'abcdefghijklmnopqrstuvwxyz0123456789' )
|
||||
|
||||
half_complete_tag_can_be_matched = True
|
||||
|
||||
|
@ -1855,8 +1855,21 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
# a search for '[s' actually only does 's'
|
||||
# so, let's do the old and slower LIKE instead of MATCH in weird cases
|
||||
|
||||
# note that queries with '*' are passed to LIKE, because MATCH only supports appended wildcards 'gun*', and not complex stuff like '*gun*'
|
||||
|
||||
if half_complete_tag_can_be_matched: return [ tag_id for ( tag_id, ) in self._c.execute( 'SELECT docid FROM tags_fts4 WHERE tag MATCH ?;', ( '"' + half_complete_tag + '*"', ) ) ]
|
||||
else: return [ tag_id for ( tag_id, ) in self._c.execute( 'SELECT tag_id FROM tags WHERE tag LIKE ?;', ( '%' + half_complete_tag + '%', ) ) ]
|
||||
else:
|
||||
|
||||
possible_tag_ids_half_complete_tag = half_complete_tag
|
||||
|
||||
if '*' in possible_tag_ids_half_complete_tag:
|
||||
|
||||
possible_tag_ids_half_complete_tag = possible_tag_ids_half_complete_tag.replace( '*', '%' )
|
||||
|
||||
else: possible_tag_ids_half_complete_tag += '%'
|
||||
|
||||
return [ tag_id for ( tag_id, ) in self._c.execute( 'SELECT tag_id FROM tags WHERE tag LIKE ? OR tag LIKE ?;', ( possible_tag_ids_half_complete_tag, ' ' + possible_tag_ids_half_complete_tag ) ) ]
|
||||
|
||||
|
||||
|
||||
if ':' in half_complete_tag:
|
||||
|
@ -1894,7 +1907,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
( namespace_id, tag_id ) = self._GetNamespaceIdTagId( tag )
|
||||
|
||||
predicates_phrase = 'namespace_id = ' + HC.u( namespace_id ) + ' AND tag_id = ' + HC.u( tag_id )
|
||||
if ':' in tag: predicates_phrase = 'namespace_id = ' + HC.u( namespace_id ) + ' AND tag_id = ' + HC.u( tag_id )
|
||||
else: predicates_phrase = 'tag_id = ' + HC.u( tag_id )
|
||||
|
||||
except: predicates_phrase = '1 = 1'
|
||||
|
||||
|
@ -2044,6 +2058,9 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
namespaces_to_include = search_context.GetNamespacesToInclude()
|
||||
namespaces_to_exclude = search_context.GetNamespacesToExclude()
|
||||
|
||||
wildcards_to_include = search_context.GetWildcardsToInclude()
|
||||
wildcards_to_exclude = search_context.GetWildcardsToExclude()
|
||||
|
||||
include_current_tags = search_context.IncludeCurrentTags()
|
||||
include_pending_tags = search_context.IncludePendingTags()
|
||||
|
||||
|
@ -2120,7 +2137,7 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
else: sql_predicates.append( '( duration < ' + HC.u( max_duration ) + ' OR duration IS NULL )' )
|
||||
|
||||
|
||||
if len( tags_to_include ) > 0 or len( namespaces_to_include ) > 0:
|
||||
if len( tags_to_include ) > 0 or len( namespaces_to_include ) > 0 or len( wildcards_to_include ) > 0:
|
||||
|
||||
query_hash_ids = None
|
||||
|
||||
|
@ -2134,6 +2151,14 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
else: query_hash_ids.intersection_update( namespace_query_hash_ids )
|
||||
|
||||
|
||||
if len( wildcards_to_include ) > 0:
|
||||
|
||||
wildcard_query_hash_ids = HC.IntelligentMassIntersect( ( self._GetHashIdsFromWildcard( file_service_key, tag_service_key, wildcard, include_current_tags, include_pending_tags ) for wildcard in wildcards_to_include ) )
|
||||
|
||||
if query_hash_ids is None: query_hash_ids = wildcard_query_hash_ids
|
||||
else: query_hash_ids.intersection_update( wildcard_query_hash_ids )
|
||||
|
||||
|
||||
if len( sql_predicates ) > 1: query_hash_ids.intersection_update( [ id for ( id, ) in self._c.execute( 'SELECT hash_id FROM files_info WHERE ' + ' AND '.join( sql_predicates ) + ';' ) ] )
|
||||
|
||||
else:
|
||||
|
@ -2229,6 +2254,8 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
|
||||
for namespace in namespaces_to_exclude: exclude_query_hash_ids.update( self._GetHashIdsFromNamespace( file_service_key, tag_service_key, namespace, include_current_tags, include_pending_tags ) )
|
||||
|
||||
for wildcard in wildcards_to_exclude: exclude_query_hash_ids.update( self._GetHashIdsFromWildcard( file_service_key, tag_service_key, wildcard, include_current_tags, include_pending_tags ) )
|
||||
|
||||
if file_service_type == HC.FILE_REPOSITORY and HC.options[ 'exclude_deleted_files' ]: exclude_query_hash_ids.update( [ hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM deleted_files WHERE service_id = ?;', ( self._local_file_service_id, ) ) ] )
|
||||
|
||||
query_hash_ids.difference_update( exclude_query_hash_ids )
|
||||
|
@ -2513,6 +2540,93 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ):
|
|||
return hash_ids
|
||||
|
||||
|
||||
def _GetHashIdsFromWildcard( self, file_service_key, tag_service_key, wildcard, include_current_tags, include_pending_tags ):
|
||||
|
||||
statuses = []
|
||||
|
||||
if include_current_tags: statuses.append( HC.CURRENT )
|
||||
if include_pending_tags: statuses.append( HC.PENDING )
|
||||
|
||||
if len( statuses ) == 0: return {}
|
||||
|
||||
predicates = []
|
||||
|
||||
if len( statuses ) > 0: predicates.append( 'mappings.status IN ' + HC.SplayListForDB( statuses ) )
|
||||
|
||||
if file_service_key == HC.COMBINED_FILE_SERVICE_KEY:
|
||||
|
||||
table_phrase = 'mappings'
|
||||
|
||||
else:
|
||||
|
||||
table_phrase = 'mappings, files_info USING ( hash_id )'
|
||||
|
||||
file_service_id = self._GetServiceId( file_service_key )
|
||||
|
||||
predicates.append( 'files_info.service_id = ' + HC.u( file_service_id ) )
|
||||
|
||||
|
||||
if tag_service_key != HC.COMBINED_TAG_SERVICE_KEY:
|
||||
|
||||
tag_service_id = self._GetServiceId( tag_service_key )
|
||||
|
||||
predicates.append( 'mappings.service_id = ' + HC.u( tag_service_id ) )
|
||||
|
||||
|
||||
if len( predicates ) > 0: predicates_phrase = ' AND '.join( predicates ) + ' AND '
|
||||
else: predicates_phrase = ''
|
||||
|
||||
def GetNamespaceIdsFromWildcard( w ):
|
||||
|
||||
if '*' in w:
|
||||
|
||||
w = w.replace( '*', '%' )
|
||||
|
||||
return { namespace_id for ( namespace_id, ) in self._c.execute( 'SELECT namespace_id FROM namespaces WHERE namespace LIKE ?;', ( w, ) ) }
|
||||
|
||||
else:
|
||||
|
||||
namespace_id = self._GetNamespaceId( w )
|
||||
|
||||
return [ namespace_id ]
|
||||
|
||||
|
||||
|
||||
def GetTagIdsFromWildcard( w ):
|
||||
|
||||
if '*' in w:
|
||||
|
||||
w = w.replace( '*', '%' )
|
||||
|
||||
return { tag_id for ( tag_id, ) in self._c.execute( 'SELECT tag_id FROM tags WHERE tag LIKE ?;', ( w, ) ) }
|
||||
|
||||
else:
|
||||
|
||||
( namespace_id, tag_id ) = self._GetNamespaceIdTagId( w )
|
||||
|
||||
return [ tag_id ]
|
||||
|
||||
|
||||
|
||||
if ':' in wildcard:
|
||||
|
||||
( namespace_wildcard, tag_wildcard ) = wildcard.split( ':', 1 )
|
||||
|
||||
possible_namespace_ids = GetNamespaceIdsFromWildcard( namespace_wildcard )
|
||||
possible_tag_ids = GetTagIdsFromWildcard( tag_wildcard )
|
||||
|
||||
hash_ids = { id for ( id, ) in self._c.execute( 'SELECT hash_id FROM ' + table_phrase + ' WHERE ' + predicates_phrase + 'namespace_id IN ' + HC.SplayListForDB( possible_namespace_ids ) + ' AND tag_id IN ' + HC.SplayListForDB( possible_tag_ids ) + ';' ) }
|
||||
|
||||
else:
|
||||
|
||||
possible_tag_ids = GetTagIdsFromWildcard( wildcard )
|
||||
|
||||
hash_ids = { id for ( id, ) in self._c.execute( 'SELECT hash_id FROM ' + table_phrase + ' WHERE ' + predicates_phrase + 'tag_id IN ' + HC.SplayListForDB( possible_tag_ids ) + ';' ) }
|
||||
|
||||
|
||||
return hash_ids
|
||||
|
||||
|
||||
def _GetHydrusSessions( self ):
|
||||
|
||||
now = HC.GetNow()
|
||||
|
@ -5178,15 +5292,6 @@ class DB( ServiceDB ):
|
|||
|
||||
def _UpdateDB( self, version ):
|
||||
|
||||
if version == 91:
|
||||
|
||||
( HC.options, ) = self._c.execute( 'SELECT options FROM options;' ).fetchone()
|
||||
|
||||
HC.options[ 'num_autocomplete_chars' ] = 2
|
||||
|
||||
self._c.execute( 'UPDATE options SET options = ?;', ( HC.options, ) )
|
||||
|
||||
|
||||
if version == 93:
|
||||
|
||||
self._c.execute( 'CREATE TABLE gui_sessions ( name TEXT, info TEXT_YAML );' )
|
||||
|
|
|
@ -703,6 +703,16 @@ class Canvas( object ):
|
|||
|
||||
|
||||
|
||||
def _OpenExternally( self ):
|
||||
|
||||
hash = self._current_display_media.GetHash()
|
||||
mime = self._current_display_media.GetMime()
|
||||
|
||||
path = CC.GetFilePath( hash, mime )
|
||||
|
||||
subprocess.call( 'start "" "' + path + '"', shell = True )
|
||||
|
||||
|
||||
def _PrefetchImages( self ): pass
|
||||
|
||||
def _RecalcZoom( self ):
|
||||
|
@ -1523,6 +1533,7 @@ class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaList ):
|
|||
elif command == 'inbox': self._Inbox()
|
||||
elif command == 'manage_ratings': self._ManageRatings()
|
||||
elif command == 'manage_tags': self._ManageTags()
|
||||
elif command == 'open_externally': self._OpenExternally()
|
||||
elif command in ( 'pan_up', 'pan_down', 'pan_left', 'pan_right' ):
|
||||
|
||||
distance = 20
|
||||
|
@ -1617,6 +1628,10 @@ class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaList ):
|
|||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'remove', HC.LOCAL_FILE_SERVICE_KEY ), '&remove' )
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete', HC.LOCAL_FILE_SERVICE_KEY ), '&delete' )
|
||||
|
||||
menu.AppendSeparator()
|
||||
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'open_externally', HC.LOCAL_FILE_SERVICE_KEY ), '&open externally' )
|
||||
|
||||
share_menu = wx.Menu()
|
||||
|
||||
copy_menu = wx.Menu()
|
||||
|
@ -1907,6 +1922,7 @@ class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaList ):
|
|||
elif command == 'inbox': self._Inbox()
|
||||
elif command == 'manage_ratings': self._ManageRatings()
|
||||
elif command == 'manage_tags': self._ManageTags()
|
||||
elif command == 'open_externally': self._OpenExternally()
|
||||
elif command == 'remove': self._Remove()
|
||||
elif command == 'slideshow': self._StartSlideshow( data )
|
||||
elif command == 'slideshow_pause_play': self._PausePlaySlideshow()
|
||||
|
@ -1938,6 +1954,14 @@ class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaList ):
|
|||
|
||||
def EventShowMenu( self, event ):
|
||||
|
||||
services = HC.app.GetManager( 'services' ).GetServices()
|
||||
|
||||
local_ratings_services = [ service for service in services if service.GetServiceType() in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ) ]
|
||||
|
||||
i_can_post_ratings = len( local_ratings_services ) > 0
|
||||
|
||||
#
|
||||
|
||||
self._last_drag_coordinates = None # to stop successive right-click drag warp bug
|
||||
|
||||
menu = wx.Menu()
|
||||
|
@ -1988,6 +2012,8 @@ class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaList ):
|
|||
|
||||
menu.AppendSeparator()
|
||||
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'open_externally', HC.LOCAL_FILE_SERVICE_KEY ), '&open externally' )
|
||||
|
||||
share_menu = wx.Menu()
|
||||
|
||||
copy_menu = wx.Menu()
|
||||
|
@ -2651,7 +2677,7 @@ class RatingsFilterFrameLike( CanvasFullscreenMediaListFilter ):
|
|||
|
||||
def __init__( self, my_parent, page_key, service_key, media_results ):
|
||||
|
||||
CanvasFullscreenMediaListFilter.__init__( self, my_parent, page_key, HC.LOCAL_FILE_SERVICE_KEY, [], media_results )
|
||||
CanvasFullscreenMediaListFilter.__init__( self, my_parent, page_key, HC.LOCAL_FILE_SERVICE_KEY, media_results )
|
||||
|
||||
self._rating_service_key = service_key
|
||||
self._service = HC.app.GetManager( 'services' ).GetService( service_key )
|
||||
|
|
|
@ -625,6 +625,8 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
|
|||
|
||||
must_do_a_search = False
|
||||
|
||||
if '*' in search_text: must_do_a_search = True
|
||||
|
||||
if ':' in search_text:
|
||||
|
||||
( namespace, half_complete_tag ) = search_text.split( ':' )
|
||||
|
@ -652,7 +654,6 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
|
|||
|
||||
media = self._media_callable()
|
||||
|
||||
# if synchro not on, then can't rely on current media as being accurate for current preds, so search db normally
|
||||
if media is not None and self._synchronised.IsOn(): fetch_from_db = False
|
||||
|
||||
|
||||
|
@ -662,18 +663,18 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
|
|||
|
||||
results = HC.app.Read( 'autocomplete_tags', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, tag = search_text, include_current = self._include_current, include_pending = self._include_pending )
|
||||
|
||||
matches = results.GetMatches( half_complete_tag )
|
||||
matches = results.GetMatches( search_text )
|
||||
|
||||
else:
|
||||
|
||||
if must_do_a_search or self._first_letters == '' or not half_complete_tag.startswith( self._first_letters ):
|
||||
if must_do_a_search or self._first_letters == '' or not search_text.startswith( self._first_letters ):
|
||||
|
||||
self._first_letters = half_complete_tag
|
||||
self._first_letters = search_text
|
||||
|
||||
self._cached_results = HC.app.Read( 'autocomplete_tags', file_service_key = self._file_service_key, tag_service_key = self._tag_service_key, half_complete_tag = search_text, include_current = self._include_current, include_pending = self._include_pending )
|
||||
|
||||
|
||||
matches = self._cached_results.GetMatches( half_complete_tag )
|
||||
matches = self._cached_results.GetMatches( search_text )
|
||||
|
||||
|
||||
else:
|
||||
|
@ -695,14 +696,8 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
|
|||
current_tags_flat_iterable = itertools.chain.from_iterable( lists_of_current_tags )
|
||||
pending_tags_flat_iterable = itertools.chain.from_iterable( lists_of_pending_tags )
|
||||
|
||||
current_tags_flat = [ tag for tag in current_tags_flat_iterable if HC.SearchEntryMatchesTag( half_complete_tag, tag ) ]
|
||||
pending_tags_flat = [ tag for tag in pending_tags_flat_iterable if HC.SearchEntryMatchesTag( half_complete_tag, tag ) ]
|
||||
|
||||
if self._current_namespace != '':
|
||||
|
||||
current_tags_flat = [ tag for tag in current_tags_flat if tag.startswith( self._current_namespace + ':' ) ]
|
||||
pending_tags_flat = [ tag for tag in pending_tags_flat if tag.startswith( self._current_namespace + ':' ) ]
|
||||
|
||||
current_tags_flat = [ tag for tag in current_tags_flat_iterable if HC.SearchEntryMatchesTag( search_text, tag ) ]
|
||||
pending_tags_flat = [ tag for tag in pending_tags_flat_iterable if HC.SearchEntryMatchesTag( search_text, tag ) ]
|
||||
|
||||
current_tags_to_count = collections.Counter( current_tags_flat )
|
||||
pending_tags_to_count = collections.Counter( pending_tags_flat )
|
||||
|
@ -714,11 +709,12 @@ class AutoCompleteDropdownTagsRead( AutoCompleteDropdownTags ):
|
|||
|
||||
results = CC.AutocompleteMatchesPredicates( self._tag_service_key, [ HC.Predicate( HC.PREDICATE_TYPE_TAG, ( operator, tag ), { HC.CURRENT : current_tags_to_count[ tag ], HC.PENDING : pending_tags_to_count[ tag ] } ) for tag in tags_to_do ] )
|
||||
|
||||
matches = results.GetMatches( half_complete_tag )
|
||||
matches = results.GetMatches( search_text )
|
||||
|
||||
|
||||
|
||||
if self._current_namespace != '': matches.insert( 0, HC.Predicate( HC.PREDICATE_TYPE_NAMESPACE, ( operator, namespace ) ) )
|
||||
if '*' in search_text: matches.insert( 0, HC.Predicate( HC.PREDICATE_TYPE_WILDCARD, ( operator, search_text ) ) )
|
||||
|
||||
entry_predicate = HC.Predicate( HC.PREDICATE_TYPE_TAG, ( operator, search_text ) )
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import HydrusThreading
|
|||
import itertools
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
@ -466,6 +467,16 @@ class MediaPanel( ClientGUIMixins.ListeningMediaList, wx.ScrolledWindow ):
|
|||
if len( hashes ) > 0: HC.pubsub.pub( 'new_thread_dumper', hashes )
|
||||
|
||||
|
||||
def _OpenExternally( self ):
|
||||
|
||||
hash = self._focussed_media.GetHash()
|
||||
mime = self._focussed_media.GetMime()
|
||||
|
||||
path = CC.GetFilePath( hash, mime )
|
||||
|
||||
subprocess.call( 'start "" "' + path + '"', shell = True )
|
||||
|
||||
|
||||
def _PetitionFiles( self, file_service_key ):
|
||||
|
||||
hashes = self._GetSelectedHashes()
|
||||
|
@ -1551,6 +1562,7 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
elif command == 'manage_tags': self._ManageTags()
|
||||
elif command == 'modify_account': self._ModifyUploaders( data )
|
||||
elif command == 'new_thread_dumper': self._NewThreadDumper()
|
||||
elif command == 'open_externally': self._OpenExternally()
|
||||
elif command == 'petition': self._PetitionFiles( data )
|
||||
elif command == 'ratings_filter': self._RatingsFilter( data )
|
||||
elif command == 'remove': self._Remove()
|
||||
|
@ -1931,6 +1943,10 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
# share
|
||||
|
||||
menu.AppendSeparator()
|
||||
|
||||
menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'open_externally', HC.LOCAL_FILE_SERVICE_KEY ), '&open externally' )
|
||||
|
||||
share_menu = wx.Menu()
|
||||
|
||||
#
|
||||
|
|
|
@ -65,7 +65,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 15
|
||||
SOFTWARE_VERSION = 141
|
||||
SOFTWARE_VERSION = 142
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
@ -403,6 +403,7 @@ PREDICATE_TYPE_SYSTEM = 0
|
|||
PREDICATE_TYPE_TAG = 1
|
||||
PREDICATE_TYPE_NAMESPACE = 2
|
||||
PREDICATE_TYPE_PARENT = 3
|
||||
PREDICATE_TYPE_WILDCARD = 4
|
||||
|
||||
SITE_TYPE_DEVIANT_ART = 0
|
||||
SITE_TYPE_GIPHY = 1
|
||||
|
@ -1273,7 +1274,30 @@ def SearchEntryMatchesPredicate( search_entry, predicate ):
|
|||
|
||||
def SearchEntryMatchesTag( search_entry, tag, search_siblings = True ):
|
||||
|
||||
if ':' in search_entry: ( search_entry_namespace, search_entry ) = search_entry.split( ':', 1 )
|
||||
def compile_re( s ):
|
||||
|
||||
regular_parts_of_s = s.split( '*' )
|
||||
|
||||
escaped_parts_of_s = [ re.escape( part ) for part in regular_parts_of_s ]
|
||||
|
||||
s = '.*'.join( escaped_parts_of_s )
|
||||
|
||||
return re.compile( '(\\A|\\s)' + s + '(\\s|\\Z)', flags = re.UNICODE )
|
||||
|
||||
|
||||
if ':' in search_entry:
|
||||
|
||||
search_namespace = True
|
||||
|
||||
( namespace_entry, search_entry ) = search_entry.split( ':', 1 )
|
||||
|
||||
namespace_re_predicate = compile_re( namespace_entry )
|
||||
|
||||
else: search_namespace = False
|
||||
|
||||
if '*' not in search_entry: search_entry += '*'
|
||||
|
||||
re_predicate = compile_re( search_entry )
|
||||
|
||||
if search_siblings:
|
||||
|
||||
|
@ -1289,11 +1313,18 @@ def SearchEntryMatchesTag( search_entry, tag, search_siblings = True ):
|
|||
|
||||
( n, t ) = tag.split( ':', 1 )
|
||||
|
||||
if search_namespace and re.search( namespace_re_predicate, n ) is None: continue
|
||||
|
||||
comparee = t
|
||||
|
||||
else: comparee = tag
|
||||
else:
|
||||
|
||||
if search_namespace: continue
|
||||
|
||||
comparee = tag
|
||||
|
||||
|
||||
if comparee.startswith( search_entry ) or ' ' + search_entry in comparee: return True
|
||||
if re.search( re_predicate, comparee ) is not None: return True
|
||||
|
||||
|
||||
return False
|
||||
|
@ -2333,6 +2364,15 @@ class Predicate( HydrusYAMLBase ):
|
|||
|
||||
base += namespace + u':*'
|
||||
|
||||
elif self._predicate_type == PREDICATE_TYPE_WILDCARD:
|
||||
|
||||
( operator, wildcard ) = self._value
|
||||
|
||||
if operator == '-': base = u'-'
|
||||
elif operator == '+': base = u''
|
||||
|
||||
base += wildcard
|
||||
|
||||
|
||||
return base
|
||||
|
||||
|
|
|
@ -256,13 +256,15 @@ class DownloaderBooru( Downloader ):
|
|||
|
||||
image_string = soup.find( text = re.compile( 'Save this file' ) )
|
||||
|
||||
if image_string is None: image_string = soup.find( text = re.compile( 'Save this video' ) )
|
||||
|
||||
image = image_string.parent
|
||||
|
||||
image_url = image[ 'href' ]
|
||||
|
||||
else:
|
||||
|
||||
if image.name == 'img':
|
||||
if image.name in ( 'img', 'video' ):
|
||||
|
||||
image_url = image[ 'src' ]
|
||||
|
||||
|
@ -1648,6 +1650,8 @@ class ImportQueueGeneratorGallery( ImportQueueGenerator ):
|
|||
self._job_key.SetVariable( 'queue', queue )
|
||||
|
||||
|
||||
time.sleep( 5 )
|
||||
|
||||
|
||||
for downloader in downloaders_to_remove: downloaders.remove( downloader )
|
||||
|
||||
|
@ -1751,7 +1755,7 @@ class ImportQueueGeneratorThread( ImportQueueGenerator ):
|
|||
if HC.shutdown or self._job_key.IsDone(): break
|
||||
|
||||
|
||||
if HC.shutdown or self._ob_key.IsDone(): break
|
||||
if HC.shutdown or self._job_key.IsDone(): break
|
||||
|
||||
thread_time = self._job_key.GetVariable( 'thread_time' )
|
||||
|
||||
|
|
Loading…
Reference in New Issue