Version 145
This commit is contained in:
parent
d13afc0285
commit
2f75a7f777
|
@ -8,6 +8,21 @@
|
|||
<div class="content">
|
||||
<h3>changelog</h3>
|
||||
<ul>
|
||||
<li><h3>version 145</h3></li>
|
||||
<ul>
|
||||
<li>added custom gui colours for thumbnail backgrounds and borders, the autocomplete background, and media background and text</li>
|
||||
<li>added <- and -> arrows to manage tags dialog launched from navigable media viewer</li>
|
||||
<li>on empty input in the manage tags dialog, page up and page down work as shortcuts for the new <- and -> buttons</li>
|
||||
<li>fixed the media height calculation for animations, so when they are vertically scaled, the total height including scanbar won't overflow off screen</li>
|
||||
<li>allowed non-integer page, chapter, volume tags in display and sort calculations</li>
|
||||
<li>semi-integer tags will sort along with integer tags and string tags like so: 0 < 0a < 0b < 1 < 2 < 22</li>
|
||||
<li>improved the old tag/media sorting code</li>
|
||||
<li>removed loli and shota from hentai foundry filter options</li>
|
||||
<li>patched old db-stored predicates to attempt to convert to the new format when queried for _inclusive</li>
|
||||
<li>this _should_ have fixed the recent export folder problems</li>
|
||||
<li>created an 8chan board, and updated my various links, including in the client, to migrate from my old forum to this</li>
|
||||
<li>misc code improvements</li>
|
||||
</ul>
|
||||
<li><h3>version 144</h3></li>
|
||||
<ul>
|
||||
<li>files named 'Thumbs.db' will now be skipped in the import files dialog</li>
|
||||
|
|
|
@ -7,16 +7,20 @@
|
|||
<body>
|
||||
<div class="content">
|
||||
<h3>contact and links</h3>
|
||||
<p>Please send bug reports straight to my email or forum. Your other ideas and comments are always welcome.</p>
|
||||
<p>I don't really do chat, and I don't get caught up in the social stuff on twitter/tumblr. I like to spend a day or so to think before replying to non-urgent emails, but I do reply to everything.</p>
|
||||
<p>Please send bug reports straight to my email or the board. Your other ideas and comments are always welcome.</p>
|
||||
<p>I don't really do chat, and I don't get caught up in social networking stuff. I often like to spend a day or so to think before replying to non-urgent emails, but I do reply to everything.</p>
|
||||
<p>I delete all tweets and resolved email conversations after three months. So, if you think you are waiting for a reply, or I said I was going to work on something you care about and seem to have forgotten, please do nudge me.</p>
|
||||
<p>If you have a problem with something on someone else's server, please, <span class="warning">do not come to me</span>, as I cannot help. If your ex-gf's nudes have leaked onto the internet, or you find something terribly offensive, or you just plain hate the free flow of information, I cannot help you at all.</p>
|
||||
<p>Anyway:</p>
|
||||
<ul>
|
||||
<li><a href="mailto:hydrus.admin@gmail.com">email</a></li>
|
||||
<li><a href="http://hydrus.x10.mx/forum/">forum</a></li>
|
||||
<li><a href="http://hydrus.tumblr.com/">tumblr</a> (<a href="http://hydrus.tumblr.com/rss">rss</a>)</li>
|
||||
<li><a href="http://hydrusnetwork.github.io/hydrus/">homepage</a></li>
|
||||
<li><a href="https://github.com/hydrusnetwork/hydrus/releases">new downloads</a></li>
|
||||
<li><a href="http://www.mediafire.com/hydrus">old downloads</a></li>
|
||||
<li><a href="https://github.com/hydrusnetwork/hydrus">github</a></li>
|
||||
<li><a href="http://8ch.net/hydrus/index.html">8chan board</a></li>
|
||||
<li><a href="http://twitter.com/hydrusnetwork">twitter</a></li>
|
||||
<li><a href="http://hydrus.tumblr.com">tumblr</a> (<a href="http://hydrus.tumblr.com/rss">rss</a>)</li>
|
||||
<li><a href="mailto:hydrus.admin@gmail.com">email</a></li>
|
||||
</ul>
|
||||
<p>If you would like to send me something physical, you can use my PO Box:</p>
|
||||
<ul>
|
||||
|
|
|
@ -197,6 +197,7 @@ CLIENT_DEFAULT_OPTIONS[ 'thread_checker_timings' ] = ( 3, 1200 )
|
|||
CLIENT_DEFAULT_OPTIONS[ 'idle_period' ] = 60 * 30
|
||||
CLIENT_DEFAULT_OPTIONS[ 'maintenance_delete_orphans_period' ] = 86400 * 3
|
||||
CLIENT_DEFAULT_OPTIONS[ 'maintenance_vacuum_period' ] = 86400 * 5
|
||||
CLIENT_DEFAULT_OPTIONS[ 'fit_to_canvas' ] = False
|
||||
|
||||
system_predicates = {}
|
||||
|
||||
|
@ -226,6 +227,23 @@ default_namespace_colours[ '' ] = ( 0, 111, 250 )
|
|||
|
||||
CLIENT_DEFAULT_OPTIONS[ 'namespace_colours' ] = default_namespace_colours
|
||||
|
||||
default_gui_colours = {}
|
||||
|
||||
default_gui_colours[ 'thumb_background' ] = ( 255, 255, 255 )
|
||||
default_gui_colours[ 'thumb_background_selected' ] = ( 217, 242, 255 ) # light blue
|
||||
default_gui_colours[ 'thumb_background_remote' ] = ( 32, 32, 36 ) # 50% Payne's Gray
|
||||
default_gui_colours[ 'thumb_background_remote_selected' ] = ( 64, 64, 72 ) # Payne's Gray
|
||||
default_gui_colours[ 'thumb_border' ] = ( 223, 227, 230 ) # light grey
|
||||
default_gui_colours[ 'thumb_border_selected' ] = ( 1, 17, 26 ) # dark grey
|
||||
default_gui_colours[ 'thumb_border_remote' ] = ( 248, 208, 204 ) # 25% Vermillion, 75% White
|
||||
default_gui_colours[ 'thumb_border_remote_selected' ] = ( 227, 66, 52 ) # Vermillion, lol
|
||||
default_gui_colours[ 'thumbgrid_background' ] = ( 255, 255, 255 )
|
||||
default_gui_colours[ 'autocomplete_background' ] = ( 235, 248, 255 ) # very light blue
|
||||
default_gui_colours[ 'media_background' ] = ( 255, 255, 255 )
|
||||
default_gui_colours[ 'media_text' ] = ( 0, 0, 0 )
|
||||
|
||||
CLIENT_DEFAULT_OPTIONS[ 'gui_colours' ] = default_gui_colours
|
||||
|
||||
default_sort_by_choices = []
|
||||
|
||||
default_sort_by_choices.append( ( 'namespaces', [ 'series', 'creator', 'title', 'volume', 'chapter', 'page' ] ) )
|
||||
|
|
|
@ -5345,107 +5345,6 @@ class DB( ServiceDB ):
|
|||
|
||||
def _UpdateDB( self, version ):
|
||||
|
||||
if version == 95:
|
||||
|
||||
self._c.execute( 'COMMIT' )
|
||||
|
||||
self._c.execute( 'PRAGMA foreign_keys = OFF;' )
|
||||
|
||||
self._c.execute( 'BEGIN IMMEDIATE' )
|
||||
|
||||
service_basic_info = self._c.execute( 'SELECT service_id, service_key, type, name FROM services;' ).fetchall()
|
||||
service_address_info = self._c.execute( 'SELECT service_id, host, port, last_error FROM addresses;' ).fetchall()
|
||||
service_account_info = self._c.execute( 'SELECT service_id, access_key, account FROM accounts;' ).fetchall()
|
||||
service_repository_info = self._c.execute( 'SELECT service_id, first_begin, next_begin FROM repositories;' ).fetchall()
|
||||
service_ratings_like_info = self._c.execute( 'SELECT service_id, like, dislike FROM ratings_like;' ).fetchall()
|
||||
service_ratings_numerical_info = self._c.execute( 'SELECT service_id, lower, upper FROM ratings_numerical;' ).fetchall()
|
||||
|
||||
service_address_info = { service_id : ( host, port, last_error ) for ( service_id, host, port, last_error ) in service_address_info }
|
||||
service_account_info = { service_id : ( access_key, account ) for ( service_id, access_key, account ) in service_account_info }
|
||||
service_repository_info = { service_id : ( first_begin, next_begin ) for ( service_id, first_begin, next_begin ) in service_repository_info }
|
||||
service_ratings_like_info = { service_id : ( like, dislike ) for ( service_id, like, dislike ) in service_ratings_like_info }
|
||||
service_ratings_numerical_info = { service_id : ( lower, upper ) for ( service_id, lower, upper ) in service_ratings_numerical_info }
|
||||
|
||||
self._c.execute( 'DROP TABLE services;' )
|
||||
self._c.execute( 'DROP TABLE addresses;' )
|
||||
self._c.execute( 'DROP TABLE accounts;' )
|
||||
self._c.execute( 'DROP TABLE repositories;' )
|
||||
self._c.execute( 'DROP TABLE ratings_like;' )
|
||||
self._c.execute( 'DROP TABLE ratings_numerical;' )
|
||||
|
||||
self._c.execute( 'CREATE TABLE services ( service_id INTEGER PRIMARY KEY, service_key BLOB_BYTES, service_type INTEGER, name TEXT, info TEXT_YAML );' )
|
||||
self._c.execute( 'CREATE UNIQUE INDEX services_service_key_index ON services ( service_key );' )
|
||||
|
||||
services = []
|
||||
|
||||
for ( service_id, service_key, service_type, name ) in service_basic_info:
|
||||
|
||||
info = {}
|
||||
|
||||
if service_id in service_address_info:
|
||||
|
||||
( host, port, last_error ) = service_address_info[ service_id ]
|
||||
|
||||
info[ 'host' ] = host
|
||||
info[ 'port' ] = port
|
||||
info[ 'last_error' ] = last_error
|
||||
|
||||
|
||||
if service_id in service_account_info:
|
||||
|
||||
( access_key, account ) = service_account_info[ service_id ]
|
||||
|
||||
info[ 'access_key' ] = access_key
|
||||
info[ 'account' ] = account
|
||||
|
||||
|
||||
if service_id in service_repository_info:
|
||||
|
||||
( first_begin, next_begin ) = service_repository_info[ service_id ]
|
||||
|
||||
info[ 'first_begin' ] = first_begin
|
||||
info[ 'next_begin' ] = next_begin
|
||||
|
||||
|
||||
if service_id in service_ratings_like_info:
|
||||
|
||||
( like, dislike ) = service_ratings_like_info[ service_id ]
|
||||
|
||||
info[ 'like' ] = like
|
||||
info[ 'dislike' ] = dislike
|
||||
|
||||
|
||||
if service_id in service_ratings_numerical_info:
|
||||
|
||||
( lower, upper ) = service_ratings_numerical_info[ service_id ]
|
||||
|
||||
info[ 'lower' ] = lower
|
||||
info[ 'upper' ] = upper
|
||||
|
||||
|
||||
self._c.execute( 'INSERT INTO services ( service_id, service_key, service_type, name, info ) VALUES ( ?, ?, ?, ?, ? );', ( service_id, sqlite3.Binary( service_key ), service_type, name, info ) )
|
||||
|
||||
|
||||
self._c.execute( 'COMMIT' )
|
||||
|
||||
self._c.execute( 'PRAGMA foreign_keys = ON;' )
|
||||
|
||||
self._c.execute( 'BEGIN IMMEDIATE' )
|
||||
|
||||
|
||||
if version == 95:
|
||||
|
||||
for ( service_id, info ) in self._c.execute( 'SELECT service_id, info FROM services;' ).fetchall():
|
||||
|
||||
if 'account' in info:
|
||||
|
||||
info[ 'account' ].MakeStale()
|
||||
|
||||
self._c.execute( 'UPDATE services SET info = ? WHERE service_id = ?;', ( info, service_id ) )
|
||||
|
||||
|
||||
|
||||
|
||||
if version == 101:
|
||||
|
||||
self._c.execute( 'CREATE TABLE yaml_dumps ( dump_type INTEGER, dump_name TEXT, dump TEXT_YAML, PRIMARY KEY ( dump_type, dump_name ) );' )
|
||||
|
@ -7296,8 +7195,6 @@ def DAEMONSynchroniseSubscriptions():
|
|||
|
||||
info[ 'rating_yaoi' ] = 1
|
||||
info[ 'rating_yuri' ] = 1
|
||||
info[ 'rating_loli' ] = 1
|
||||
info[ 'rating_shota' ] = 1
|
||||
info[ 'rating_teen' ] = 1
|
||||
info[ 'rating_guro' ] = 1
|
||||
info[ 'rating_furry' ] = 1
|
||||
|
|
|
@ -998,18 +998,18 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
|
|||
dont_know.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'auto_server_setup' ), p( 'Just set up the server on this computer, please' ) )
|
||||
menu.AppendMenu( wx.ID_NONE, p( 'I don\'t know what I am doing' ), dont_know )
|
||||
links = wx.Menu()
|
||||
tumblr = wx.MenuItem( links, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'tumblr' ), p( 'Tumblr' ) )
|
||||
tumblr.SetBitmap( wx.Bitmap( HC.STATIC_DIR + os.path.sep + 'tumblr.png' ) )
|
||||
twitter = wx.MenuItem( links, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'twitter' ), p( 'Twitter' ) )
|
||||
twitter.SetBitmap( wx.Bitmap( HC.STATIC_DIR + os.path.sep + 'twitter.png' ) )
|
||||
site = wx.MenuItem( links, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'site' ), p( 'Site' ) )
|
||||
site.SetBitmap( wx.Bitmap( HC.STATIC_DIR + os.path.sep + 'file_repository_small.png' ) )
|
||||
forum = wx.MenuItem( links, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'forum' ), p( 'Forum' ) )
|
||||
forum.SetBitmap( wx.Bitmap( HC.STATIC_DIR + os.path.sep + 'file_repository_small.png' ) )
|
||||
links.AppendItem( tumblr )
|
||||
links.AppendItem( twitter )
|
||||
board = wx.MenuItem( links, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( '8chan_board' ), p( '8chan Board' ) )
|
||||
board.SetBitmap( wx.Bitmap( HC.STATIC_DIR + os.path.sep + '8chan.png' ) )
|
||||
twitter = wx.MenuItem( links, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'twitter' ), p( 'Twitter' ) )
|
||||
twitter.SetBitmap( wx.Bitmap( HC.STATIC_DIR + os.path.sep + 'twitter.png' ) )
|
||||
tumblr = wx.MenuItem( links, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'tumblr' ), p( 'Tumblr' ) )
|
||||
tumblr.SetBitmap( wx.Bitmap( HC.STATIC_DIR + os.path.sep + 'tumblr.png' ) )
|
||||
links.AppendItem( site )
|
||||
links.AppendItem( forum )
|
||||
links.AppendItem( board )
|
||||
links.AppendItem( twitter )
|
||||
links.AppendItem( tumblr )
|
||||
menu.AppendMenu( wx.ID_NONE, p( 'Links' ), links )
|
||||
debug = wx.Menu()
|
||||
debug.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'debug_garbage' ), p( 'Garbage' ) )
|
||||
|
@ -1828,7 +1828,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
|
|||
elif command == 'delete_service_info': self._DeleteServiceInfo()
|
||||
elif command == 'exit': self.EventExit( event )
|
||||
elif command == 'fetch_ip': self._FetchIP( data )
|
||||
elif command == 'forum': webbrowser.open( 'http://hydrus.x10.mx/forum' )
|
||||
elif command == '8chan_board': webbrowser.open( 'http://8ch.net/hydrus/index.html' )
|
||||
elif command == 'file_integrity': self._CheckFileIntegrity()
|
||||
elif command == 'help': webbrowser.open( 'file://' + HC.BASE_DIR + '/help/index.html' )
|
||||
elif command == 'help_about': self._AboutWindow()
|
||||
|
|
|
@ -7,6 +7,7 @@ import ClientGUIMixins
|
|||
import collections
|
||||
import gc
|
||||
import HydrusImageHandling
|
||||
import HydrusTags
|
||||
import HydrusVideoHandling
|
||||
import os
|
||||
import Queue
|
||||
|
@ -156,7 +157,7 @@ class Animation( wx.Window ):
|
|||
|
||||
dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.WHITE ) )
|
||||
dc.SetBackground( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) ) )
|
||||
|
||||
dc.Clear()
|
||||
|
||||
|
@ -441,6 +442,8 @@ class Canvas( object ):
|
|||
|
||||
self._file_service = HC.app.GetManager( 'services' ).GetService( self._file_service_key )
|
||||
|
||||
self._canvas_key = os.urandom( 32 )
|
||||
|
||||
self._closing = False
|
||||
|
||||
self._service_keys_to_services = {}
|
||||
|
@ -457,7 +460,7 @@ class Canvas( object ):
|
|||
self._last_drag_coordinates = None
|
||||
self._total_drag_delta = ( 0, 0 )
|
||||
|
||||
self.SetBackgroundColour( wx.WHITE )
|
||||
self.SetBackgroundColour( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) )
|
||||
|
||||
self._canvas_bmp = wx.EmptyBitmap( 0, 0, 24 )
|
||||
|
||||
|
@ -481,7 +484,7 @@ class Canvas( object ):
|
|||
|
||||
dc = wx.BufferedDC( cdc, self._canvas_bmp )
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.WHITE ) )
|
||||
dc.SetBackground( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) ) )
|
||||
|
||||
dc.Clear()
|
||||
|
||||
|
@ -529,7 +532,7 @@ class Canvas( object ):
|
|||
current_y += y
|
||||
|
||||
|
||||
dc.SetTextForeground( wx.BLACK )
|
||||
dc.SetTextForeground( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_text' ] ) )
|
||||
|
||||
# icons
|
||||
|
||||
|
@ -697,9 +700,7 @@ class Canvas( object ):
|
|||
|
||||
if self._current_media is not None:
|
||||
|
||||
try:
|
||||
with ClientGUIDialogsManage.DialogManageTags( self, self._file_service_key, ( self._current_media, ) ) as dlg: dlg.ShowModal()
|
||||
except: wx.MessageBox( 'Had a problem displaying the manage tags dialog from fullscreen.' )
|
||||
with ClientGUIDialogsManage.DialogManageTags( self, self._file_service_key, ( self._current_media, ) ) as dlg: dlg.ShowModal()
|
||||
|
||||
|
||||
|
||||
|
@ -727,14 +728,14 @@ class Canvas( object ):
|
|||
|
||||
( media_width, media_height ) = self._current_display_media.GetResolution()
|
||||
|
||||
if ShouldHaveAnimationBar( self._current_display_media ):
|
||||
|
||||
media_height += ANIMATED_SCANBAR_HEIGHT
|
||||
|
||||
if ShouldHaveAnimationBar( self._current_display_media ): my_height -= ANIMATED_SCANBAR_HEIGHT
|
||||
|
||||
if self._current_display_media.GetMime() in NON_LARGABLY_ZOOMABLE_MIMES: my_width -= 1
|
||||
|
||||
if media_width > my_width or media_height > my_height:
|
||||
media_needs_to_be_scaled_down = media_width > my_width or media_height > my_height
|
||||
media_needs_to_be_scaled_up = media_width < my_width and media_height < my_height and HC.options[ 'fit_to_canvas' ]
|
||||
|
||||
if media_needs_to_be_scaled_down or media_needs_to_be_scaled_up:
|
||||
|
||||
width_zoom = my_width / float( media_width )
|
||||
|
||||
|
@ -854,6 +855,7 @@ class Canvas( object ):
|
|||
|
||||
|
||||
|
||||
|
||||
class CanvasPanel( Canvas, wx.Window ):
|
||||
|
||||
def __init__( self, parent, page_key, file_service_key ):
|
||||
|
@ -1000,7 +1002,12 @@ class CanvasFullscreenMediaList( ClientGUIMixins.ListeningMediaList, Canvas, Cli
|
|||
|
||||
collections_string_append = 'volume ' + HC.u( volume )
|
||||
|
||||
else: collections_string_append = 'volumes ' + HC.u( min( volumes ) ) + '-' + HC.u( max( volumes ) )
|
||||
else:
|
||||
|
||||
volumes_sorted = HydrusTags.SortTags( volumes )
|
||||
|
||||
collections_string_append = 'volumes ' + HC.u( volumes_sorted[0] ) + '-' + HC.u( volumes_sorted[-1] )
|
||||
|
||||
|
||||
if len( collections_string ) > 0: collections_string += ' - ' + collections_string_append
|
||||
else: collections_string = collections_string_append
|
||||
|
@ -1014,7 +1021,12 @@ class CanvasFullscreenMediaList( ClientGUIMixins.ListeningMediaList, Canvas, Cli
|
|||
|
||||
collections_string_append = 'chapter ' + HC.u( chapter )
|
||||
|
||||
else: collections_string_append = 'chapters ' + HC.u( min( chapters ) ) + '-' + HC.u( max( chapters ) )
|
||||
else:
|
||||
|
||||
chapters_sorted = HydrusTags.SortTags( chapters )
|
||||
|
||||
collections_string_append = 'chapters ' + HC.u( chapters_sorted[0] ) + '-' + HC.u( chapters_sorted[-1] )
|
||||
|
||||
|
||||
if len( collections_string ) > 0: collections_string += ' - ' + collections_string_append
|
||||
else: collections_string = collections_string_append
|
||||
|
@ -1028,7 +1040,12 @@ class CanvasFullscreenMediaList( ClientGUIMixins.ListeningMediaList, Canvas, Cli
|
|||
|
||||
collections_string_append = 'page ' + HC.u( page )
|
||||
|
||||
else: collections_string_append = 'pages ' + HC.u( min( pages ) ) + '-' + HC.u( max( pages ) )
|
||||
else:
|
||||
|
||||
pages_sorted = HydrusTags.SortTags( pages )
|
||||
|
||||
collections_string_append = 'pages ' + HC.u( pages_sorted[0] ) + '-' + HC.u( pages_sorted[-1] )
|
||||
|
||||
|
||||
if len( collections_string ) > 0: collections_string += ' - ' + collections_string_append
|
||||
else: collections_string = collections_string_append
|
||||
|
@ -1400,11 +1417,281 @@ class CanvasFullscreenMediaList( ClientGUIMixins.ListeningMediaList, Canvas, Cli
|
|||
else: self.SetCursor( wx.StockCursor( wx.CURSOR_BLANK ) )
|
||||
|
||||
|
||||
class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaList ):
|
||||
class CanvasFullscreenMediaListFilter( CanvasFullscreenMediaList ):
|
||||
|
||||
def __init__( self, my_parent, page_key, file_service_key, media_results ):
|
||||
|
||||
CanvasFullscreenMediaList.__init__( self, my_parent, page_key, file_service_key, media_results )
|
||||
|
||||
self._kept = set()
|
||||
self._deleted = set()
|
||||
|
||||
self.Bind( wx.EVT_LEFT_DOWN, self.EventMouseKeep )
|
||||
self.Bind( wx.EVT_LEFT_DCLICK, self.EventMouseKeep )
|
||||
self.Bind( wx.EVT_MIDDLE_DOWN, self.EventBack )
|
||||
self.Bind( wx.EVT_MIDDLE_DCLICK, self.EventBack )
|
||||
self.Bind( wx.EVT_MOUSEWHEEL, self.EventMouseWheel )
|
||||
self.Bind( wx.EVT_RIGHT_DOWN, self.EventDelete )
|
||||
self.Bind( wx.EVT_RIGHT_DCLICK, self.EventDelete )
|
||||
|
||||
self.Bind( wx.EVT_MENU, self.EventMenu )
|
||||
|
||||
self.Bind( wx.EVT_CHAR_HOOK, self.EventCharHook )
|
||||
|
||||
self.SetMedia( self._GetFirst() )
|
||||
|
||||
|
||||
def _Delete( self ):
|
||||
|
||||
self._deleted.add( self._current_media )
|
||||
|
||||
if self._current_media == self._GetLast(): self.EventClose( None )
|
||||
else: self._ShowNext()
|
||||
|
||||
|
||||
def _Keep( self ):
|
||||
|
||||
self._kept.add( self._current_media )
|
||||
|
||||
if self._current_media == self._GetLast(): self.EventClose( None )
|
||||
else: self._ShowNext()
|
||||
|
||||
|
||||
def EventBack( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
if self._current_media == self._GetFirst(): return
|
||||
else:
|
||||
|
||||
self._ShowPrevious()
|
||||
|
||||
self._kept.discard( self._current_media )
|
||||
self._deleted.discard( self._current_media )
|
||||
|
||||
|
||||
|
||||
|
||||
def EventButtonBack( self, event ): self.EventBack( event )
|
||||
def EventButtonDelete( self, event ): self._Delete()
|
||||
def EventButtonDone( self, event ): self.EventClose( event )
|
||||
def EventButtonKeep( self, event ): self._Keep()
|
||||
def EventButtonSkip( self, event ):
|
||||
|
||||
if self._current_media == self._GetLast(): self.EventClose( event )
|
||||
else: self._ShowNext()
|
||||
|
||||
|
||||
def EventClose( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
if len( self._kept ) > 0 or len( self._deleted ) > 0:
|
||||
|
||||
with ClientGUIDialogs.DialogFinishFiltering( self, len( self._kept ), len( self._deleted ) ) as dlg:
|
||||
|
||||
modal = dlg.ShowModal()
|
||||
|
||||
if modal == wx.ID_CANCEL:
|
||||
|
||||
if self._current_media in self._kept: self._kept.remove( self._current_media )
|
||||
if self._current_media in self._deleted: self._deleted.remove( self._current_media )
|
||||
|
||||
else:
|
||||
|
||||
if modal == wx.ID_YES:
|
||||
|
||||
self._deleted_hashes = [ media.GetHash() for media in self._deleted ]
|
||||
self._kept_hashes = [ media.GetHash() for media in self._kept ]
|
||||
|
||||
content_updates = []
|
||||
|
||||
content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, self._deleted_hashes ) )
|
||||
content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, self._kept_hashes ) )
|
||||
|
||||
HC.app.Write( 'content_updates', { HC.LOCAL_FILE_SERVICE_KEY : content_updates } )
|
||||
|
||||
self._kept = set()
|
||||
self._deleted = set()
|
||||
|
||||
self._current_media = self._GetFirst() # so the pubsub on close is better
|
||||
|
||||
|
||||
CanvasFullscreenMediaList.EventClose( self, event )
|
||||
|
||||
|
||||
|
||||
else: CanvasFullscreenMediaList.EventClose( self, event )
|
||||
|
||||
|
||||
|
||||
def EventCharHook( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
( modifier, key ) = HC.GetShortcutFromEvent( 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()
|
||||
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()
|
||||
elif modifier == wx.ACCEL_NORMAL and key == wx.WXK_BACK: self.EventBack( event )
|
||||
elif modifier == wx.ACCEL_NORMAL and key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_ESCAPE ): self.EventClose( event )
|
||||
elif modifier == wx.ACCEL_NORMAL and key in ( wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE ): self.EventDelete( event )
|
||||
elif modifier == wx.ACCEL_CTRL and key == ord( 'C' ):
|
||||
with wx.BusyCursor(): HC.app.Write( 'copy_files', ( self._current_media.GetHash(), ) )
|
||||
elif not event.ShiftDown() and key in ( wx.WXK_UP, wx.WXK_NUMPAD_UP ): self.EventSkip( event )
|
||||
else:
|
||||
|
||||
key_dict = HC.options[ 'shortcuts' ][ modifier ]
|
||||
|
||||
if key in key_dict:
|
||||
|
||||
action = key_dict[ key ]
|
||||
|
||||
self.ProcessEvent( wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) )
|
||||
|
||||
else: event.Skip()
|
||||
|
||||
|
||||
|
||||
|
||||
def EventDelete( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else: self._Delete()
|
||||
|
||||
|
||||
def EventMouseKeep( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
if event.ShiftDown(): self.EventDragBegin( event )
|
||||
else: self._Keep()
|
||||
|
||||
|
||||
|
||||
def EventMenu( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
|
||||
|
||||
if action is not None:
|
||||
|
||||
( command, data ) = action
|
||||
|
||||
if command == 'archive': self._Keep()
|
||||
elif command == 'back': self.EventBack( event )
|
||||
elif command == 'close': self.EventClose( event )
|
||||
elif command == 'delete': self.EventDelete( event )
|
||||
elif command == 'fullscreen_switch': self._FullscreenSwitch()
|
||||
elif command == 'filter': self.EventClose( event )
|
||||
elif command == 'frame_back': self._media_container.GotoPreviousOrNextFrame( -1 )
|
||||
elif command == 'frame_next': self._media_container.GotoPreviousOrNextFrame( 1 )
|
||||
elif command == 'manage_ratings': self._ManageRatings()
|
||||
elif command == 'manage_tags': self._ManageTags()
|
||||
elif command in ( 'pan_up', 'pan_down', 'pan_left', 'pan_right' ):
|
||||
|
||||
distance = 20
|
||||
|
||||
if command == 'pan_up': self._DoManualPan( 0, -distance )
|
||||
elif command == 'pan_down': self._DoManualPan( 0, distance )
|
||||
elif command == 'pan_left': self._DoManualPan( -distance, 0 )
|
||||
elif command == 'pan_right': self._DoManualPan( distance, 0 )
|
||||
|
||||
elif command == 'zoom_in': self._ZoomIn()
|
||||
elif command == 'zoom_out': self._ZoomOut()
|
||||
else: event.Skip()
|
||||
|
||||
|
||||
|
||||
|
||||
def EventMouseWheel( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
if event.CmdDown():
|
||||
|
||||
if event.GetWheelRotation() > 0: self._ZoomIn()
|
||||
else: self._ZoomOut()
|
||||
|
||||
|
||||
|
||||
|
||||
def EventSkip( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
if self._current_media == self._GetLast(): self.EventClose( event )
|
||||
else: self._ShowNext()
|
||||
|
||||
|
||||
|
||||
class CanvasFullscreenMediaListFilterInbox( CanvasFullscreenMediaListFilter ):
|
||||
|
||||
def __init__( self, my_parent, page_key, file_service_key, media_results ):
|
||||
|
||||
CanvasFullscreenMediaListFilter.__init__( self, my_parent, page_key, file_service_key, media_results )
|
||||
|
||||
FullscreenPopoutFilterInbox( self )
|
||||
|
||||
|
||||
class CanvasFullscreenMediaListNavigable( CanvasFullscreenMediaList ):
|
||||
|
||||
def __init__( self, my_parent, page_key, file_service_key, media_results ):
|
||||
|
||||
CanvasFullscreenMediaList.__init__( self, my_parent, page_key, file_service_key, media_results )
|
||||
|
||||
HC.pubsub.sub( self, 'ShowNext', 'canvas_show_next' )
|
||||
HC.pubsub.sub( self, 'ShowPrevious', 'canvas_show_previous' )
|
||||
|
||||
|
||||
def _BroadcastCurrentDisplayMedia( self ):
|
||||
|
||||
HC.pubsub.pub( 'canvas_broadcast_current_display_media', self._canvas_key, self._current_display_media )
|
||||
|
||||
|
||||
def _ManageTags( self ):
|
||||
|
||||
if self._current_media is not None:
|
||||
|
||||
with ClientGUIDialogsManage.DialogManageTags( self, self._file_service_key, ( self._current_media, ), canvas_key = self._canvas_key ) as dlg: dlg.ShowModal()
|
||||
|
||||
|
||||
|
||||
def ShowNext( self, canvas_key ):
|
||||
|
||||
if canvas_key == self._canvas_key:
|
||||
|
||||
self._ShowNext()
|
||||
|
||||
self._BroadcastCurrentDisplayMedia()
|
||||
|
||||
|
||||
|
||||
def ShowPrevious( self, canvas_key ):
|
||||
|
||||
if canvas_key == self._canvas_key:
|
||||
|
||||
self._ShowPrevious()
|
||||
|
||||
self._BroadcastCurrentDisplayMedia()
|
||||
|
||||
|
||||
|
||||
class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaListNavigable ):
|
||||
|
||||
def __init__( self, my_parent, page_key, file_service_key, media_results, first_hash ):
|
||||
|
||||
CanvasFullscreenMediaList.__init__( self, my_parent, page_key, file_service_key, media_results )
|
||||
CanvasFullscreenMediaListNavigable.__init__( self, my_parent, page_key, file_service_key, media_results )
|
||||
|
||||
self._timer_slideshow = wx.Timer( self, id = ID_TIMER_SLIDESHOW )
|
||||
|
||||
|
@ -1681,11 +1968,11 @@ class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaList ):
|
|||
|
||||
def TIMEREventSlideshow( self, event ): self._ShowNext()
|
||||
|
||||
class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaList ):
|
||||
class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaListNavigable ):
|
||||
|
||||
def __init__( self, my_parent, page_key, file_service_key, media_results, actions ):
|
||||
|
||||
CanvasFullscreenMediaList.__init__( self, my_parent, page_key, file_service_key, media_results )
|
||||
CanvasFullscreenMediaListNavigable.__init__( self, my_parent, page_key, file_service_key, media_results )
|
||||
|
||||
self._actions = actions
|
||||
|
||||
|
@ -2046,233 +2333,6 @@ class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaList ):
|
|||
event.Skip()
|
||||
|
||||
|
||||
class CanvasFullscreenMediaListFilter( CanvasFullscreenMediaList ):
|
||||
|
||||
def __init__( self, my_parent, page_key, file_service_key, media_results ):
|
||||
|
||||
CanvasFullscreenMediaList.__init__( self, my_parent, page_key, file_service_key, media_results )
|
||||
|
||||
self._kept = set()
|
||||
self._deleted = set()
|
||||
|
||||
self.Bind( wx.EVT_LEFT_DOWN, self.EventMouseKeep )
|
||||
self.Bind( wx.EVT_LEFT_DCLICK, self.EventMouseKeep )
|
||||
self.Bind( wx.EVT_MIDDLE_DOWN, self.EventBack )
|
||||
self.Bind( wx.EVT_MIDDLE_DCLICK, self.EventBack )
|
||||
self.Bind( wx.EVT_MOUSEWHEEL, self.EventMouseWheel )
|
||||
self.Bind( wx.EVT_RIGHT_DOWN, self.EventDelete )
|
||||
self.Bind( wx.EVT_RIGHT_DCLICK, self.EventDelete )
|
||||
|
||||
self.Bind( wx.EVT_MENU, self.EventMenu )
|
||||
|
||||
self.Bind( wx.EVT_CHAR_HOOK, self.EventCharHook )
|
||||
|
||||
self.SetMedia( self._GetFirst() )
|
||||
|
||||
|
||||
def _Delete( self ):
|
||||
|
||||
self._deleted.add( self._current_media )
|
||||
|
||||
if self._current_media == self._GetLast(): self.EventClose( None )
|
||||
else: self._ShowNext()
|
||||
|
||||
|
||||
def _Keep( self ):
|
||||
|
||||
self._kept.add( self._current_media )
|
||||
|
||||
if self._current_media == self._GetLast(): self.EventClose( None )
|
||||
else: self._ShowNext()
|
||||
|
||||
|
||||
def EventBack( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
if self._current_media == self._GetFirst(): return
|
||||
else:
|
||||
|
||||
self._ShowPrevious()
|
||||
|
||||
self._kept.discard( self._current_media )
|
||||
self._deleted.discard( self._current_media )
|
||||
|
||||
|
||||
|
||||
|
||||
def EventButtonBack( self, event ): self.EventBack( event )
|
||||
def EventButtonDelete( self, event ): self._Delete()
|
||||
def EventButtonDone( self, event ): self.EventClose( event )
|
||||
def EventButtonKeep( self, event ): self._Keep()
|
||||
def EventButtonSkip( self, event ):
|
||||
|
||||
if self._current_media == self._GetLast(): self.EventClose( event )
|
||||
else: self._ShowNext()
|
||||
|
||||
|
||||
def EventClose( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
if len( self._kept ) > 0 or len( self._deleted ) > 0:
|
||||
|
||||
with ClientGUIDialogs.DialogFinishFiltering( self, len( self._kept ), len( self._deleted ) ) as dlg:
|
||||
|
||||
modal = dlg.ShowModal()
|
||||
|
||||
if modal == wx.ID_CANCEL:
|
||||
|
||||
if self._current_media in self._kept: self._kept.remove( self._current_media )
|
||||
if self._current_media in self._deleted: self._deleted.remove( self._current_media )
|
||||
|
||||
else:
|
||||
|
||||
if modal == wx.ID_YES:
|
||||
|
||||
self._deleted_hashes = [ media.GetHash() for media in self._deleted ]
|
||||
self._kept_hashes = [ media.GetHash() for media in self._kept ]
|
||||
|
||||
content_updates = []
|
||||
|
||||
content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_DELETE, self._deleted_hashes ) )
|
||||
content_updates.append( HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_ARCHIVE, self._kept_hashes ) )
|
||||
|
||||
HC.app.Write( 'content_updates', { HC.LOCAL_FILE_SERVICE_KEY : content_updates } )
|
||||
|
||||
self._kept = set()
|
||||
self._deleted = set()
|
||||
|
||||
self._current_media = self._GetFirst() # so the pubsub on close is better
|
||||
|
||||
|
||||
CanvasFullscreenMediaList.EventClose( self, event )
|
||||
|
||||
|
||||
|
||||
else: CanvasFullscreenMediaList.EventClose( self, event )
|
||||
|
||||
|
||||
|
||||
def EventCharHook( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
( modifier, key ) = HC.GetShortcutFromEvent( 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()
|
||||
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()
|
||||
elif modifier == wx.ACCEL_NORMAL and key == wx.WXK_BACK: self.EventBack( event )
|
||||
elif modifier == wx.ACCEL_NORMAL and key in ( wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_ESCAPE ): self.EventClose( event )
|
||||
elif modifier == wx.ACCEL_NORMAL and key in ( wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE ): self.EventDelete( event )
|
||||
elif modifier == wx.ACCEL_CTRL and key == ord( 'C' ):
|
||||
with wx.BusyCursor(): HC.app.Write( 'copy_files', ( self._current_media.GetHash(), ) )
|
||||
elif not event.ShiftDown() and key in ( wx.WXK_UP, wx.WXK_NUMPAD_UP ): self.EventSkip( event )
|
||||
else:
|
||||
|
||||
key_dict = HC.options[ 'shortcuts' ][ modifier ]
|
||||
|
||||
if key in key_dict:
|
||||
|
||||
action = key_dict[ key ]
|
||||
|
||||
self.ProcessEvent( wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( action ) ) )
|
||||
|
||||
else: event.Skip()
|
||||
|
||||
|
||||
|
||||
|
||||
def EventDelete( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else: self._Delete()
|
||||
|
||||
|
||||
def EventMouseKeep( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
if event.ShiftDown(): self.EventDragBegin( event )
|
||||
else: self._Keep()
|
||||
|
||||
|
||||
|
||||
def EventMenu( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
|
||||
|
||||
if action is not None:
|
||||
|
||||
( command, data ) = action
|
||||
|
||||
if command == 'archive': self._Keep()
|
||||
elif command == 'back': self.EventBack( event )
|
||||
elif command == 'close': self.EventClose( event )
|
||||
elif command == 'delete': self.EventDelete( event )
|
||||
elif command == 'fullscreen_switch': self._FullscreenSwitch()
|
||||
elif command == 'filter': self.EventClose( event )
|
||||
elif command == 'frame_back': self._media_container.GotoPreviousOrNextFrame( -1 )
|
||||
elif command == 'frame_next': self._media_container.GotoPreviousOrNextFrame( 1 )
|
||||
elif command == 'manage_ratings': self._ManageRatings()
|
||||
elif command == 'manage_tags': self._ManageTags()
|
||||
elif command in ( 'pan_up', 'pan_down', 'pan_left', 'pan_right' ):
|
||||
|
||||
distance = 20
|
||||
|
||||
if command == 'pan_up': self._DoManualPan( 0, -distance )
|
||||
elif command == 'pan_down': self._DoManualPan( 0, distance )
|
||||
elif command == 'pan_left': self._DoManualPan( -distance, 0 )
|
||||
elif command == 'pan_right': self._DoManualPan( distance, 0 )
|
||||
|
||||
elif command == 'zoom_in': self._ZoomIn()
|
||||
elif command == 'zoom_out': self._ZoomOut()
|
||||
else: event.Skip()
|
||||
|
||||
|
||||
|
||||
|
||||
def EventMouseWheel( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
if event.CmdDown():
|
||||
|
||||
if event.GetWheelRotation() > 0: self._ZoomIn()
|
||||
else: self._ZoomOut()
|
||||
|
||||
|
||||
|
||||
|
||||
def EventSkip( self, event ):
|
||||
|
||||
if self._ShouldSkipInputDueToFlash(): event.Skip()
|
||||
else:
|
||||
|
||||
if self._current_media == self._GetLast(): self.EventClose( event )
|
||||
else: self._ShowNext()
|
||||
|
||||
|
||||
|
||||
class CanvasFullscreenMediaListFilterInbox( CanvasFullscreenMediaListFilter ):
|
||||
|
||||
def __init__( self, my_parent, page_key, file_service_key, media_results ):
|
||||
|
||||
CanvasFullscreenMediaListFilter.__init__( self, my_parent, page_key, file_service_key, media_results )
|
||||
|
||||
FullscreenPopoutFilterInbox( self )
|
||||
|
||||
|
||||
class FullscreenPopout( wx.Frame ):
|
||||
|
||||
def __init__( self, parent ):
|
||||
|
@ -3884,7 +3944,7 @@ class EmbedButton( wx.Window ):
|
|||
|
||||
dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
|
||||
|
||||
dc.SetBackground( wx.WHITE_BRUSH )
|
||||
dc.SetBackground( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) ) )
|
||||
|
||||
dc.Clear() # gcdc doesn't support clear
|
||||
|
||||
|
@ -3900,7 +3960,7 @@ class EmbedButton( wx.Window ):
|
|||
|
||||
dc.DrawCircle( center_x, center_y, radius )
|
||||
|
||||
dc.SetBrush( wx.WHITE_BRUSH )
|
||||
dc.SetBrush( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) ) )
|
||||
|
||||
m = ( 2 ** 0.5 ) / 2 # 45 degree angle
|
||||
|
||||
|
@ -4077,7 +4137,7 @@ class StaticImage( wx.Window ):
|
|||
|
||||
dc = wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.WHITE ) )
|
||||
dc.SetBackground( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_background' ] ) ) )
|
||||
|
||||
dc.Clear()
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ class AutoCompleteDropdown( wx.TextCtrl ):
|
|||
|
||||
wx.TextCtrl.__init__( self, parent, style=wx.TE_PROCESS_ENTER )
|
||||
|
||||
self.SetBackgroundColour( CC.COLOUR_LIGHT_SELECTED )
|
||||
self.SetBackgroundColour( wx.Colour( *HC.options[ 'gui_colours' ][ 'autocomplete_background' ] ) )
|
||||
|
||||
#self._dropdown_window = wx.PopupWindow( self, flags = wx.BORDER_RAISED )
|
||||
#self._dropdown_window = wx.PopupTransientWindow( self, style = wx.BORDER_RAISED )
|
||||
|
@ -215,6 +215,15 @@ class AutoCompleteDropdown( wx.TextCtrl ):
|
|||
|
||||
self.ProcessEvent( new_event )
|
||||
|
||||
elif event.KeyCode in ( wx.WXK_PAGEDOWN, wx.WXK_NUMPAD_PAGEDOWN, wx.WXK_PAGEUP, wx.WXK_NUMPAD_PAGEUP ) and self.GetValue() == '' and len( self._dropdown_list ) == 0:
|
||||
|
||||
if event.KeyCode in ( wx.WXK_PAGEUP, wx.WXK_NUMPAD_PAGEUP ): id = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'canvas_show_previous' )
|
||||
elif event.KeyCode in ( wx.WXK_PAGEDOWN, wx.WXK_NUMPAD_PAGEDOWN ): id = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'canvas_show_next' )
|
||||
|
||||
new_event = wx.CommandEvent( commandType = wx.wxEVT_COMMAND_MENU_SELECTED, winid = id )
|
||||
|
||||
self.ProcessEvent( new_event )
|
||||
|
||||
else: self._dropdown_list.ProcessEvent( event )
|
||||
|
||||
|
||||
|
@ -3444,8 +3453,6 @@ class AdvancedHentaiFoundryOptions( AdvancedOptions ):
|
|||
|
||||
self._rating_yaoi = wx.CheckBox( panel )
|
||||
self._rating_yuri = wx.CheckBox( panel )
|
||||
self._rating_loli = wx.CheckBox( panel )
|
||||
self._rating_shota = wx.CheckBox( panel )
|
||||
self._rating_teen = wx.CheckBox( panel )
|
||||
self._rating_guro = wx.CheckBox( panel )
|
||||
self._rating_furry = wx.CheckBox( panel )
|
||||
|
@ -3457,8 +3464,6 @@ class AdvancedHentaiFoundryOptions( AdvancedOptions ):
|
|||
|
||||
self._rating_yaoi.SetValue( True )
|
||||
self._rating_yuri.SetValue( True )
|
||||
self._rating_loli.SetValue( True )
|
||||
self._rating_shota.SetValue( True )
|
||||
self._rating_teen.SetValue( True )
|
||||
self._rating_guro.SetValue( True )
|
||||
self._rating_furry.SetValue( True )
|
||||
|
@ -3507,12 +3512,6 @@ class AdvancedHentaiFoundryOptions( AdvancedOptions ):
|
|||
gridbox.AddF( wx.StaticText( panel, label = 'yuri' ), FLAGS_MIXED )
|
||||
gridbox.AddF( self._rating_yuri, FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
gridbox.AddF( wx.StaticText( panel, label = 'loli' ), FLAGS_MIXED )
|
||||
gridbox.AddF( self._rating_loli, FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
gridbox.AddF( wx.StaticText( panel, label = 'shota' ), FLAGS_MIXED )
|
||||
gridbox.AddF( self._rating_shota, FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
gridbox.AddF( wx.StaticText( panel, label = 'teen' ), FLAGS_MIXED )
|
||||
gridbox.AddF( self._rating_teen, FLAGS_EXPAND_BOTH_WAYS )
|
||||
|
||||
|
@ -3558,8 +3557,6 @@ class AdvancedHentaiFoundryOptions( AdvancedOptions ):
|
|||
|
||||
info[ 'rating_yaoi' ] = int( self._rating_yaoi.GetValue() )
|
||||
info[ 'rating_yuri' ] = int( self._rating_yuri.GetValue() )
|
||||
info[ 'rating_loli' ] = int( self._rating_loli.GetValue() )
|
||||
info[ 'rating_shota' ] = int( self._rating_shota.GetValue() )
|
||||
info[ 'rating_teen' ] = int( self._rating_teen.GetValue() )
|
||||
info[ 'rating_guro' ] = int( self._rating_guro.GetValue() )
|
||||
info[ 'rating_furry' ] = int( self._rating_furry.GetValue() )
|
||||
|
@ -3587,8 +3584,6 @@ class AdvancedHentaiFoundryOptions( AdvancedOptions ):
|
|||
|
||||
self._rating_yaoi.SetValue( bool( info[ 'rating_yaoi' ] ) )
|
||||
self._rating_yuri.SetValue( bool( info[ 'rating_yuri' ] ) )
|
||||
self._rating_loli.SetValue( bool( info[ 'rating_loli' ] ) )
|
||||
self._rating_shota.SetValue( bool( info[ 'rating_shota' ] ) )
|
||||
self._rating_teen.SetValue( bool( info[ 'rating_teen' ] ) )
|
||||
self._rating_guro.SetValue( bool( info[ 'rating_guro' ] ) )
|
||||
self._rating_furry.SetValue( bool( info[ 'rating_furry' ] ) )
|
||||
|
@ -4305,7 +4300,7 @@ class TagsBoxCounts( TagsBox ):
|
|||
|
||||
self._sort = HC.options[ 'default_tag_sort' ]
|
||||
|
||||
self._last_media = None
|
||||
self._last_media = set()
|
||||
|
||||
self._tag_service_key = HC.COMBINED_TAG_SERVICE_KEY
|
||||
|
||||
|
@ -4384,7 +4379,7 @@ class TagsBoxCounts( TagsBox ):
|
|||
|
||||
self._tag_service_key = service_key
|
||||
|
||||
if self._last_media is not None: self.SetTagsByMedia( self._last_media, force_reload = True )
|
||||
self.SetTagsByMedia( self._last_media, force_reload = True )
|
||||
|
||||
|
||||
def SetSort( self, sort ):
|
||||
|
@ -4430,12 +4425,8 @@ class TagsBoxCounts( TagsBox ):
|
|||
|
||||
else:
|
||||
|
||||
if self._last_media is None: ( removees, adds ) = ( set(), media )
|
||||
else:
|
||||
|
||||
removees = self._last_media.difference( media )
|
||||
adds = media.difference( self._last_media )
|
||||
|
||||
removees = self._last_media.difference( media )
|
||||
adds = media.difference( self._last_media )
|
||||
|
||||
siblings_manager = HC.app.GetManager( 'tag_siblings' )
|
||||
|
||||
|
|
|
@ -2844,6 +2844,15 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self._listbook.AddPage( self._maintenance_page, 'maintenance and memory' )
|
||||
|
||||
# media
|
||||
|
||||
self._media_page = wx.Panel( self._listbook )
|
||||
self._media_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
||||
|
||||
self._fit_to_canvas = wx.CheckBox( self._media_page, label = '' )
|
||||
|
||||
self._listbook.AddPage( self._media_page, 'media' )
|
||||
|
||||
# gui
|
||||
|
||||
self._gui_page = wx.Panel( self._listbook )
|
||||
|
@ -2963,6 +2972,17 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
self._colour_page = wx.Panel( self._listbook )
|
||||
self._colour_page.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
|
||||
|
||||
self._gui_colours = {}
|
||||
|
||||
for ( name, rgb ) in HC.options[ 'gui_colours' ].items():
|
||||
|
||||
ctrl = wx.ColourPickerCtrl( self._colour_page )
|
||||
|
||||
ctrl.SetMaxSize( ( 20, -1 ) )
|
||||
|
||||
self._gui_colours[ name ] = ctrl
|
||||
|
||||
|
||||
self._namespace_colours = ClientGUICommon.TagsBoxColourOptions( self._colour_page, HC.options[ 'namespace_colours' ] )
|
||||
|
||||
self._edit_namespace_colour = wx.Button( self._colour_page, label = 'edit selected' )
|
||||
|
@ -3081,6 +3101,10 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
#
|
||||
|
||||
self._fit_to_canvas.SetValue( HC.options[ 'fit_to_canvas' ] )
|
||||
|
||||
#
|
||||
|
||||
gui_sessions = HC.app.Read( 'gui_sessions' )
|
||||
|
||||
gui_session_names = gui_sessions.keys()
|
||||
|
@ -3189,6 +3213,10 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
#
|
||||
|
||||
for ( name, rgb ) in HC.options[ 'gui_colours' ].items(): self._gui_colours[ name ].SetColour( wx.Colour( *rgb ) )
|
||||
|
||||
#
|
||||
|
||||
for ( name, ato ) in HC.options[ 'default_advanced_tag_options' ].items():
|
||||
|
||||
if name == 'default': pretty_name = 'default'
|
||||
|
@ -3313,6 +3341,17 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
gridbox.AddGrowableCol( 1, 1 )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self._media_page, label = 'Zoom smaller images to fit media canvas: ' ), FLAGS_MIXED )
|
||||
gridbox.AddF( self._fit_to_canvas, FLAGS_MIXED )
|
||||
|
||||
self._media_page.SetSizer( gridbox )
|
||||
|
||||
#
|
||||
|
||||
gridbox = wx.FlexGridSizer( 0, 2 )
|
||||
|
||||
gridbox.AddGrowableCol( 1, 1 )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self._gui_page, label = 'Default session on startup:' ), FLAGS_MIXED )
|
||||
gridbox.AddF( self._default_gui_session, FLAGS_MIXED )
|
||||
|
||||
|
@ -3486,6 +3525,43 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
gridbox = wx.FlexGridSizer( 0, 2 )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self._colour_page, label = 'thumbnail background (local: normal/selected, remote: normal/selected): ' ), FLAGS_MIXED )
|
||||
|
||||
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
||||
|
||||
hbox.AddF( self._gui_colours[ 'thumb_background' ], FLAGS_MIXED )
|
||||
hbox.AddF( self._gui_colours[ 'thumb_background_selected' ], FLAGS_MIXED )
|
||||
hbox.AddF( self._gui_colours[ 'thumb_background_remote' ], FLAGS_MIXED )
|
||||
hbox.AddF( self._gui_colours[ 'thumb_background_remote_selected' ], FLAGS_MIXED )
|
||||
|
||||
gridbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self._colour_page, label = 'thumbnail border (local: normal/selected, remote: normal/selected): ' ), FLAGS_MIXED )
|
||||
|
||||
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
||||
|
||||
hbox.AddF( self._gui_colours[ 'thumb_border' ], FLAGS_MIXED )
|
||||
hbox.AddF( self._gui_colours[ 'thumb_border_selected' ], FLAGS_MIXED )
|
||||
hbox.AddF( self._gui_colours[ 'thumb_border_remote' ], FLAGS_MIXED )
|
||||
hbox.AddF( self._gui_colours[ 'thumb_border_remote_selected' ], FLAGS_MIXED )
|
||||
|
||||
gridbox.AddF( hbox, FLAGS_EXPAND_SIZER_PERPENDICULAR )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self._colour_page, label = 'thumbnail grid background: '), FLAGS_MIXED )
|
||||
gridbox.AddF( self._gui_colours[ 'thumbgrid_background' ], FLAGS_MIXED )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self._colour_page, label = 'autocomplete background: '), FLAGS_MIXED )
|
||||
gridbox.AddF( self._gui_colours[ 'autocomplete_background' ], FLAGS_MIXED )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self._colour_page, label = 'media viewer background: '), FLAGS_MIXED )
|
||||
gridbox.AddF( self._gui_colours[ 'media_background' ], FLAGS_MIXED )
|
||||
|
||||
gridbox.AddF( wx.StaticText( self._colour_page, label = 'media viewer text: '), FLAGS_MIXED )
|
||||
gridbox.AddF( self._gui_colours[ 'media_text' ], FLAGS_MIXED )
|
||||
|
||||
vbox.AddF( gridbox, FLAGS_EXPAND_PERPENDICULAR )
|
||||
vbox.AddF( self._namespace_colours, FLAGS_EXPAND_BOTH_WAYS )
|
||||
vbox.AddF( self._new_namespace_colour, FLAGS_EXPAND_PERPENDICULAR )
|
||||
vbox.AddF( self._edit_namespace_colour, FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
@ -3823,6 +3899,17 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
|
|||
|
||||
HC.options[ 'ac_timings' ] = ( char_limit, long_wait, short_wait )
|
||||
|
||||
HC.options[ 'fit_to_canvas' ] = self._fit_to_canvas.GetValue()
|
||||
|
||||
for ( name, ctrl ) in self._gui_colours.items():
|
||||
|
||||
colour = ctrl.GetColour()
|
||||
|
||||
rgb = ( colour.Red(), colour.Green(), colour.Blue() )
|
||||
|
||||
HC.options[ 'gui_colours' ][ name ] = rgb
|
||||
|
||||
|
||||
HC.options[ 'namespace_colours' ] = self._namespace_colours.GetNamespaceColours()
|
||||
|
||||
sort_by_choices = []
|
||||
|
@ -7527,10 +7614,19 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
|
|||
|
||||
class DialogManageTags( ClientGUIDialogs.Dialog ):
|
||||
|
||||
def __init__( self, parent, file_service_key, media ):
|
||||
def __init__( self, parent, file_service_key, media, canvas_key = None ):
|
||||
|
||||
def InitialiseControls():
|
||||
|
||||
if canvas_key is not None:
|
||||
|
||||
self._next = wx.Button( self, label = '->' )
|
||||
self._next.Bind( wx.EVT_BUTTON, self.EventNext )
|
||||
|
||||
self._previous = wx.Button( self, label = '<-' )
|
||||
self._previous.Bind( wx.EVT_BUTTON, self.EventPrevious )
|
||||
|
||||
|
||||
self._tag_repositories = ClientGUICommon.ListBook( self )
|
||||
self._tag_repositories.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.EventServiceChanged )
|
||||
|
||||
|
@ -7573,6 +7669,17 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
|
||||
vbox = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
if canvas_key is not None:
|
||||
|
||||
hbox = wx.BoxSizer( wx.HORIZONTAL )
|
||||
|
||||
hbox.AddF( self._previous, FLAGS_MIXED )
|
||||
hbox.AddF( ( 20, 20 ), FLAGS_EXPAND_BOTH_WAYS )
|
||||
hbox.AddF( self._next, FLAGS_MIXED )
|
||||
|
||||
vbox.AddF( hbox, FLAGS_EXPAND_PERPENDICULAR )
|
||||
|
||||
|
||||
vbox.AddF( self._tag_repositories, FLAGS_EXPAND_BOTH_WAYS )
|
||||
vbox.AddF( buttonbox, FLAGS_BUTTON_SIZER )
|
||||
|
||||
|
@ -7587,6 +7694,8 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self._hashes = set()
|
||||
|
||||
self._canvas_key = canvas_key
|
||||
|
||||
for m in media: self._hashes.update( m.GetHashes() )
|
||||
|
||||
ClientGUIDialogs.Dialog.__init__( self, parent, 'manage tags for ' + HC.ConvertIntToPrettyString( len( self._hashes ) ) + ' files' )
|
||||
|
@ -7601,6 +7710,33 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self.RefreshAcceleratorTable()
|
||||
|
||||
if self._canvas_key is not None: HC.pubsub.sub( self, 'CanvasHasNewMedia', 'canvas_broadcast_current_display_media' )
|
||||
|
||||
|
||||
def _ClearPanels( self ):
|
||||
|
||||
for page in self._tag_repositories.GetNameToPageDict().values(): page.SetMedia( set() )
|
||||
|
||||
|
||||
def _CommitCurrentChanges( self, sync = False ):
|
||||
|
||||
service_keys_to_content_updates = {}
|
||||
|
||||
for page in self._tag_repositories.GetNameToPageDict().values():
|
||||
|
||||
( service_key, content_updates ) = page.GetContentUpdates()
|
||||
|
||||
if len( content_updates ) > 0: service_keys_to_content_updates[ service_key ] = content_updates
|
||||
|
||||
|
||||
if len( service_keys_to_content_updates ) > 0:
|
||||
|
||||
if sync: m = HC.app.WriteSynchronous
|
||||
else: m = HC.app.Write
|
||||
|
||||
m( 'content_updates', service_keys_to_content_updates )
|
||||
|
||||
|
||||
|
||||
def _SetSearchFocus( self ):
|
||||
|
||||
|
@ -7609,6 +7745,14 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
page.SetTagBoxFocus()
|
||||
|
||||
|
||||
def CanvasHasNewMedia( self, canvas_key, new_media ):
|
||||
|
||||
if canvas_key == self._canvas_key:
|
||||
|
||||
for page in self._tag_repositories.GetNameToPageDict().values(): page.SetMedia( ( new_media, ) )
|
||||
|
||||
|
||||
|
||||
def EventMenu( self, event ):
|
||||
|
||||
action = CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetAction( event.GetId() )
|
||||
|
@ -7617,31 +7761,39 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
|
||||
( command, data ) = action
|
||||
|
||||
if command == 'manage_tags': self.EndModal( wx.ID_CANCEL )
|
||||
if command == 'canvas_show_next': self.EventNext( event )
|
||||
elif command == 'canvas_show_previous': self.EventPrevious( event )
|
||||
elif command == 'manage_tags': self.EndModal( wx.ID_CANCEL )
|
||||
elif command == 'set_search_focus': self._SetSearchFocus()
|
||||
elif command == 'ok': self.EventOK( event )
|
||||
else: event.Skip()
|
||||
|
||||
|
||||
|
||||
def EventNext( self, event ):
|
||||
|
||||
self._CommitCurrentChanges( sync = True )
|
||||
|
||||
self._ClearPanels()
|
||||
|
||||
HC.pubsub.pub( 'canvas_show_next', self._canvas_key )
|
||||
|
||||
|
||||
def EventOK( self, event ):
|
||||
|
||||
try:
|
||||
|
||||
service_keys_to_content_updates = {}
|
||||
|
||||
for page in self._tag_repositories.GetNameToPageDict().values():
|
||||
|
||||
( service_key, content_updates ) = page.GetContentUpdates()
|
||||
|
||||
service_keys_to_content_updates[ service_key ] = content_updates
|
||||
|
||||
|
||||
if len( service_keys_to_content_updates ) > 0: HC.app.Write( 'content_updates', service_keys_to_content_updates )
|
||||
|
||||
try: self._CommitCurrentChanges()
|
||||
finally: self.EndModal( wx.ID_OK )
|
||||
|
||||
|
||||
def EventPrevious( self, event ):
|
||||
|
||||
self._CommitCurrentChanges( sync = True )
|
||||
|
||||
self._ClearPanels()
|
||||
|
||||
HC.pubsub.pub( 'canvas_show_previous', self._canvas_key )
|
||||
|
||||
|
||||
def EventServiceChanged( self, event ):
|
||||
|
||||
page = self._tag_repositories.GetCurrentPage()
|
||||
|
@ -7693,7 +7845,7 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self._tags_box.ChangeTagRepository( self._tag_service_key )
|
||||
|
||||
self._tags_box.SetTagsByMedia( self._media )
|
||||
self.SetMedia( media )
|
||||
|
||||
|
||||
def ArrangeControls():
|
||||
|
@ -7728,10 +7880,6 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
|
||||
self._i_am_local_tag_service = self._tag_service_key == HC.LOCAL_TAG_SERVICE_KEY
|
||||
|
||||
self._hashes = { hash for hash in itertools.chain.from_iterable( ( m.GetHashes() for m in media ) ) }
|
||||
|
||||
self._content_updates = []
|
||||
|
||||
if not self._i_am_local_tag_service:
|
||||
|
||||
service = HC.app.GetManager( 'services' ).GetService( tag_service_key )
|
||||
|
@ -7740,15 +7888,6 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
except: self._account = HC.GetUnknownAccount()
|
||||
|
||||
|
||||
hashes = set( itertools.chain.from_iterable( ( m.GetHashes() for m in media ) ) )
|
||||
|
||||
media_results = HC.app.Read( 'media_results', self._file_service_key, hashes )
|
||||
|
||||
# this should now be a nice clean copy of the original media
|
||||
self._media = [ ClientGUIMixins.MediaSingleton( media_result ) for media_result in media_results ]
|
||||
|
||||
tags_managers = [ m.GetTagsManager() for m in self._media ]
|
||||
|
||||
InitialiseControls()
|
||||
|
||||
PopulateControls()
|
||||
|
@ -7840,6 +7979,22 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
|
|||
|
||||
|
||||
|
||||
def SetMedia( self, media ):
|
||||
|
||||
self._hashes = { hash for hash in itertools.chain.from_iterable( ( m.GetHashes() for m in media ) ) }
|
||||
|
||||
self._content_updates = []
|
||||
|
||||
hashes = set( itertools.chain.from_iterable( ( m.GetHashes() for m in media ) ) )
|
||||
|
||||
media_results = HC.app.Read( 'media_results', self._file_service_key, hashes )
|
||||
|
||||
# this should now be a nice clean copy of the original media
|
||||
self._media = [ ClientGUIMixins.MediaSingleton( media_result ) for media_result in media_results ]
|
||||
|
||||
self._tags_box.SetTagsByMedia( self._media )
|
||||
|
||||
|
||||
def EventCopyTags( self, event ):
|
||||
|
||||
( current_tags_to_count, deleted_tags_to_count, pending_tags_to_count, petitioned_tags_to_count ) = CC.GetMediasTagCount( self._media, self._tag_service_key )
|
||||
|
|
|
@ -6,6 +6,7 @@ import ClientGUIDialogsManage
|
|||
import ClientGUICanvas
|
||||
import ClientGUIMixins
|
||||
import collections
|
||||
import HydrusTags
|
||||
import HydrusThreading
|
||||
import itertools
|
||||
import os
|
||||
|
@ -1083,7 +1084,7 @@ class MediaPanelThumbnails( MediaPanel ):
|
|||
|
||||
dc = self._GetScrolledDC()
|
||||
|
||||
dc.SetBrush( wx.WHITE_BRUSH )
|
||||
dc.SetBrush( wx.Brush( wx.Colour( *HC.options[ 'gui_colours' ][ 'thumbgrid_background' ] ) ) )
|
||||
|
||||
dc.SetPen( wx.TRANSPARENT_PEN )
|
||||
|
||||
|
@ -2331,15 +2332,17 @@ class Thumbnail( Selectable ):
|
|||
|
||||
if not local:
|
||||
|
||||
if self._selected: dc.SetBackground( wx.Brush( wx.Colour( 64, 64, 72 ) ) ) # Payne's Gray
|
||||
else: dc.SetBackground( wx.Brush( wx.Colour( 32, 32, 36 ) ) ) # 50% Payne's Gray
|
||||
if self._selected: rgb = HC.options[ 'gui_colours' ][ 'thumb_background_remote_selected' ]
|
||||
else: rgb = HC.options[ 'gui_colours' ][ 'thumb_background_remote' ]
|
||||
|
||||
else:
|
||||
|
||||
if self._selected: dc.SetBackground( wx.Brush( CC.COLOUR_SELECTED ) )
|
||||
else: dc.SetBackground( wx.Brush( wx.WHITE ) )
|
||||
if self._selected: rgb = HC.options[ 'gui_colours' ][ 'thumb_background_selected' ]
|
||||
else: rgb = HC.options[ 'gui_colours' ][ 'thumb_background' ]
|
||||
|
||||
|
||||
dc.SetBackground( wx.Brush( wx.Colour( *rgb ) ) )
|
||||
|
||||
dc.Clear()
|
||||
|
||||
( thumb_width, thumb_height ) = self._hydrus_bmp.GetSize()
|
||||
|
@ -2364,7 +2367,12 @@ class Thumbnail( Selectable ):
|
|||
|
||||
collections_string = 'v' + HC.u( volume )
|
||||
|
||||
else: collections_string = 'v' + HC.u( min( volumes ) ) + '-' + HC.u( max( volumes ) )
|
||||
else:
|
||||
|
||||
volumes_sorted = HydrusTags.SortTags( volumes )
|
||||
|
||||
collections_string_append = 'v' + HC.u( volumes_sorted[0] ) + '-' + HC.u( volumes_sorted[-1] )
|
||||
|
||||
|
||||
|
||||
if len( chapters ) > 0:
|
||||
|
@ -2375,7 +2383,12 @@ class Thumbnail( Selectable ):
|
|||
|
||||
collections_string_append = 'c' + HC.u( chapter )
|
||||
|
||||
else: collections_string_append = 'c' + HC.u( min( chapters ) ) + '-' + HC.u( max( chapters ) )
|
||||
else:
|
||||
|
||||
chapters_sorted = HydrusTags.SortTags( chapters )
|
||||
|
||||
collections_string_append = 'c' + HC.u( chapters_sorted[0] ) + '-' + HC.u( chapters_sorted[-1] )
|
||||
|
||||
|
||||
if len( collections_string ) > 0: collections_string += '-' + collections_string_append
|
||||
else: collections_string = collections_string_append
|
||||
|
@ -2389,7 +2402,12 @@ class Thumbnail( Selectable ):
|
|||
|
||||
collections_string_append = 'p' + HC.u( page )
|
||||
|
||||
else: collections_string_append = 'p' + HC.u( min( pages ) ) + '-' + HC.u( max( pages ) )
|
||||
else:
|
||||
|
||||
pages_sorted = HydrusTags.SortTags( pages )
|
||||
|
||||
collections_string_append = 'p' + HC.u( pages_sorted[0] ) + '-' + HC.u( pages_sorted[-1] )
|
||||
|
||||
|
||||
if len( collections_string ) > 0: collections_string += '-' + collections_string_append
|
||||
else: collections_string = collections_string_append
|
||||
|
@ -2465,16 +2483,16 @@ class Thumbnail( Selectable ):
|
|||
|
||||
if not local:
|
||||
|
||||
if self._selected: colour = wx.Colour( 227, 66, 52 ) # Vermillion, lol
|
||||
else: colour = wx.Colour( 248, 208, 204 ) # 25% Vermillion, 75% White
|
||||
if self._selected: rgb = HC.options[ 'gui_colours' ][ 'thumb_border_remote_selected' ]
|
||||
else: rgb = HC.options[ 'gui_colours' ][ 'thumb_border_remote' ]
|
||||
|
||||
else:
|
||||
|
||||
if self._selected: colour = CC.COLOUR_SELECTED_DARK
|
||||
else: colour = CC.COLOUR_UNSELECTED
|
||||
if self._selected: rgb = HC.options[ 'gui_colours' ][ 'thumb_border_selected' ]
|
||||
else: rgb = HC.options[ 'gui_colours' ][ 'thumb_border' ]
|
||||
|
||||
|
||||
dc.SetPen( wx.Pen( colour, style=wx.SOLID ) )
|
||||
dc.SetPen( wx.Pen( wx.Colour( *rgb ), style=wx.SOLID ) )
|
||||
|
||||
dc.DrawRectangle( 0, 0, width, height )
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ options = {}
|
|||
# Misc
|
||||
|
||||
NETWORK_VERSION = 15
|
||||
SOFTWARE_VERSION = 144
|
||||
SOFTWARE_VERSION = 145
|
||||
|
||||
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
|
||||
|
||||
|
@ -2188,7 +2188,24 @@ class Predicate( HydrusYAMLBase ):
|
|||
else: return self._counts[ current_or_pending ]
|
||||
|
||||
|
||||
def GetInclusive( self ): return self._inclusive
|
||||
def GetInclusive( self ):
|
||||
|
||||
# patch from an upgrade mess-up ~v144
|
||||
if not hasattr( self, '_inclusive' ):
|
||||
|
||||
if self._predicate_type != PREDICATE_TYPE_SYSTEM:
|
||||
|
||||
( operator, value ) = self._value
|
||||
|
||||
self._value = value
|
||||
|
||||
self._inclusive = operator == '+'
|
||||
|
||||
else: self._inclusive = True
|
||||
|
||||
|
||||
return self._inclusive
|
||||
|
||||
|
||||
def GetInfo( self ): return ( self._predicate_type, self._value, self._inclusive )
|
||||
|
||||
|
@ -2701,37 +2718,33 @@ class SortedList( object ):
|
|||
|
||||
if sort_function is None: sort_function = lambda x: x
|
||||
|
||||
self._sorted_list = [ ( sort_function( item ), item ) for item in initial_items ]
|
||||
self._sort_function = sort_function
|
||||
|
||||
self._sorted_list = list( initial_items )
|
||||
|
||||
self._items_to_indices = None
|
||||
|
||||
self._sort_function = sort_function
|
||||
|
||||
if do_sort: self.sort()
|
||||
|
||||
|
||||
def __contains__( self, item ): return self._items_to_indices.__contains__( item )
|
||||
|
||||
def __getitem__( self, value ):
|
||||
|
||||
if type( value ) == int: return self._sorted_list.__getitem__( value )[1]
|
||||
elif type( value ) == slice: return [ item for ( sort_item, item ) in self._sorted_list.__getitem__( value ) ]
|
||||
|
||||
def __getitem__( self, value ): return self._sorted_list.__getitem__( value )
|
||||
|
||||
def __iter__( self ):
|
||||
|
||||
for ( sorting_value, item ) in self._sorted_list: yield item
|
||||
for item in self._sorted_list: yield item
|
||||
|
||||
|
||||
def __len__( self ): return self._sorted_list.__len__()
|
||||
|
||||
def _DirtyIndices( self ): self._items_to_indices = None
|
||||
|
||||
def _RecalcIndices( self ): self._items_to_indices = { item : index for ( index, ( sort_item, item ) ) in enumerate( self._sorted_list ) }
|
||||
def _RecalcIndices( self ): self._items_to_indices = { item : index for ( index, item ) in enumerate( self._sorted_list ) }
|
||||
|
||||
def append_items( self, items ):
|
||||
|
||||
self._sorted_list.extend( [ ( self._sort_function( item ), item ) for item in items ] )
|
||||
self._sorted_list.extend( items )
|
||||
|
||||
self._DirtyIndices()
|
||||
|
||||
|
@ -2745,9 +2758,9 @@ class SortedList( object ):
|
|||
|
||||
def insert_items( self, items ):
|
||||
|
||||
for item in items: bisect.insort( self._sorted_list, ( self._sort_function( item ), item ) )
|
||||
self.append_items( items )
|
||||
|
||||
self._DirtyIndices()
|
||||
self.sort()
|
||||
|
||||
|
||||
def remove_items( self, items ):
|
||||
|
@ -2758,7 +2771,7 @@ class SortedList( object ):
|
|||
|
||||
deletee_indices.reverse()
|
||||
|
||||
for index in deletee_indices: self._sorted_list.pop( index )
|
||||
for index in deletee_indices: del self._sorted_list[ index ]
|
||||
|
||||
self._DirtyIndices()
|
||||
|
||||
|
@ -2767,9 +2780,7 @@ class SortedList( object ):
|
|||
|
||||
if f is not None: self._sort_function = f
|
||||
|
||||
self._sorted_list = [ ( self._sort_function( item ), item ) for ( old_value, item ) in self._sorted_list ]
|
||||
|
||||
self._sorted_list.sort()
|
||||
self._sorted_list.sort( key = f )
|
||||
|
||||
self._DirtyIndices()
|
||||
|
||||
|
|
|
@ -241,6 +241,43 @@ def MergeTagsManagers( tags_managers ):
|
|||
|
||||
return TagsManagerSimple( merged_service_keys_to_statuses_to_tags )
|
||||
|
||||
def SortTags( tags ):
|
||||
|
||||
def tagkey( t ):
|
||||
|
||||
if t[0].isdigit():
|
||||
|
||||
# We want to maintain that:
|
||||
# 0 < 0a < 0b < 1 ( lexicographic comparison )
|
||||
# -and-
|
||||
# 2 < 22 ( value comparison )
|
||||
# So, if the first bit can be turned into an int, split it into ( int, extra )
|
||||
|
||||
int_component = ''
|
||||
|
||||
i = 0
|
||||
|
||||
for character in t:
|
||||
|
||||
if character.isdigit(): int_component += character
|
||||
else: break
|
||||
|
||||
i += 1
|
||||
|
||||
|
||||
str_component = t[i:]
|
||||
|
||||
return ( int( int_component ), str_component )
|
||||
|
||||
else: return t
|
||||
|
||||
|
||||
tags = list( tags )
|
||||
|
||||
tags.sort( key = tagkey )
|
||||
|
||||
return tags
|
||||
|
||||
class TagsManagerSimple( object ):
|
||||
|
||||
def __init__( self, service_keys_to_statuses_to_tags ):
|
||||
|
@ -265,25 +302,6 @@ class TagsManagerSimple( object ):
|
|||
|
||||
self._combined_namespaces_cache = HC.BuildKeyToSetDict( tag.split( ':', 1 ) for tag in combined_current.union( combined_pending ) if ':' in tag )
|
||||
|
||||
only_int_allowed = ( 'volume', 'chapter', 'page' )
|
||||
|
||||
for namespace in only_int_allowed:
|
||||
|
||||
tags = self._combined_namespaces_cache[ namespace ]
|
||||
|
||||
int_tags = set()
|
||||
|
||||
for tag in tags:
|
||||
|
||||
try: tag = int( tag )
|
||||
except: continue
|
||||
|
||||
int_tags.add( tag )
|
||||
|
||||
|
||||
self._combined_namespaces_cache[ namespace ] = int_tags
|
||||
|
||||
|
||||
|
||||
result = { namespace : self._combined_namespaces_cache[ namespace ] for namespace in namespaces }
|
||||
|
||||
|
@ -314,15 +332,7 @@ class TagsManagerSimple( object ):
|
|||
|
||||
tags = [ tag.split( ':', 1 )[1] for tag in tags ]
|
||||
|
||||
def process_tag( t ):
|
||||
|
||||
try: return int( t )
|
||||
except: return t
|
||||
|
||||
|
||||
tags = [ process_tag( tag ) for tag in tags ]
|
||||
|
||||
tags.sort()
|
||||
tags = SortTags( tags )
|
||||
|
||||
tags = tuple( tags )
|
||||
|
||||
|
|
|
@ -260,8 +260,6 @@ class TestDownloaders( unittest.TestCase ):
|
|||
|
||||
info[ 'rating_yaoi' ] = 1
|
||||
info[ 'rating_yuri' ] = 1
|
||||
info[ 'rating_loli' ] = 1
|
||||
info[ 'rating_shota' ] = 1
|
||||
info[ 'rating_teen' ] = 1
|
||||
info[ 'rating_guro' ] = 1
|
||||
info[ 'rating_furry' ] = 1
|
||||
|
|
|
@ -66,7 +66,7 @@ class TestMergeTagsManagers( unittest.TestCase ):
|
|||
|
||||
#
|
||||
|
||||
result = { 'creator' : { 'tsutomu nihei' }, 'series' : { 'blame!' }, 'title' : { 'double page spread' }, 'volume' : { 3 }, 'chapter' : { 1, 2 }, 'page' : { 4, 5 } }
|
||||
result = { 'creator' : { 'tsutomu nihei' }, 'series' : { 'blame!' }, 'title' : { 'double page spread' }, 'volume' : { '3' }, 'chapter' : { '1', '2' }, 'page' : { '4', '5' } }
|
||||
|
||||
self.assertEqual( tags_manager.GetCombinedNamespaces( ( 'creator', 'series', 'title', 'volume', 'chapter', 'page' ) ), result )
|
||||
|
||||
|
@ -92,7 +92,7 @@ class TestTagsManager( unittest.TestCase ):
|
|||
service_keys_to_statuses_to_tags[ self._second_key ][ HC.PENDING ] = { 'pending' }
|
||||
service_keys_to_statuses_to_tags[ self._second_key ][ HC.PETITIONED ] = { 'petitioned' }
|
||||
|
||||
service_keys_to_statuses_to_tags[ self._third_key ][ HC.CURRENT ] = { 'petitioned', 'volume:broken_volume', 'chapter:broken_chapter', 'page:broken_page' }
|
||||
service_keys_to_statuses_to_tags[ self._third_key ][ HC.CURRENT ] = { 'petitioned' }
|
||||
service_keys_to_statuses_to_tags[ self._third_key ][ HC.DELETED ] = { 'pending' }
|
||||
|
||||
self._tags_manager = HydrusTags.TagsManager( service_keys_to_statuses_to_tags )
|
||||
|
@ -126,7 +126,7 @@ class TestTagsManager( unittest.TestCase ):
|
|||
|
||||
def test_get_cstvcp( self ):
|
||||
|
||||
result = { 'creator' : { 'tsutomu nihei' }, 'series' : { 'blame!' }, 'title' : { 'test title' }, 'volume' : { 3 }, 'chapter' : { 2 }, 'page' : { 1 } }
|
||||
result = { 'creator' : { 'tsutomu nihei' }, 'series' : { 'blame!' }, 'title' : { 'test title' }, 'volume' : { '3' }, 'chapter' : { '2' }, 'page' : { '1' } }
|
||||
|
||||
self.assertEqual( self._tags_manager.GetCombinedNamespaces( ( 'creator', 'series', 'title', 'volume', 'chapter', 'page' ) ), result )
|
||||
|
||||
|
@ -146,9 +146,9 @@ class TestTagsManager( unittest.TestCase ):
|
|||
|
||||
self.assertEqual( self._tags_manager.GetCurrent( self._first_key ), { 'current', u'\u2835', 'creator:tsutomu nihei', 'series:blame!', 'title:test title', 'volume:3', 'chapter:2', 'page:1' } )
|
||||
self.assertEqual( self._tags_manager.GetCurrent( self._second_key ), { 'deleted', u'\u2835' } )
|
||||
self.assertEqual( self._tags_manager.GetCurrent( self._third_key ), { 'petitioned', 'volume:broken_volume', 'chapter:broken_chapter', 'page:broken_page' } )
|
||||
self.assertEqual( self._tags_manager.GetCurrent( self._third_key ), { 'petitioned' } )
|
||||
|
||||
self.assertEqual( self._tags_manager.GetCurrent(), { 'current', 'deleted', u'\u2835', 'creator:tsutomu nihei', 'series:blame!', 'title:test title', 'volume:3', 'chapter:2', 'page:1', 'petitioned', 'volume:broken_volume', 'chapter:broken_chapter', 'page:broken_page' } )
|
||||
self.assertEqual( self._tags_manager.GetCurrent(), { 'current', 'deleted', u'\u2835', 'creator:tsutomu nihei', 'series:blame!', 'title:test title', 'volume:3', 'chapter:2', 'page:1', 'petitioned' } )
|
||||
|
||||
|
||||
def test_get_deleted( self ):
|
||||
|
@ -182,14 +182,14 @@ class TestTagsManager( unittest.TestCase ):
|
|||
self.assertEqual( self._tags_manager.GetNumTags( self._second_key, include_current_tags = True, include_pending_tags = True ), 3 )
|
||||
|
||||
self.assertEqual( self._tags_manager.GetNumTags( self._third_key, include_current_tags = False, include_pending_tags = False ), 0 )
|
||||
self.assertEqual( self._tags_manager.GetNumTags( self._third_key, include_current_tags = True, include_pending_tags = False ), 4 )
|
||||
self.assertEqual( self._tags_manager.GetNumTags( self._third_key, include_current_tags = True, include_pending_tags = False ), 1 )
|
||||
self.assertEqual( self._tags_manager.GetNumTags( self._third_key, include_current_tags = False, include_pending_tags = True ), 0 )
|
||||
self.assertEqual( self._tags_manager.GetNumTags( self._third_key, include_current_tags = True, include_pending_tags = True ), 4 )
|
||||
self.assertEqual( self._tags_manager.GetNumTags( self._third_key, include_current_tags = True, include_pending_tags = True ), 1 )
|
||||
|
||||
self.assertEqual( self._tags_manager.GetNumTags( HC.COMBINED_TAG_SERVICE_KEY, include_current_tags = False, include_pending_tags = False ), 0 )
|
||||
self.assertEqual( self._tags_manager.GetNumTags( HC.COMBINED_TAG_SERVICE_KEY, include_current_tags = True, include_pending_tags = False ), 13 )
|
||||
self.assertEqual( self._tags_manager.GetNumTags( HC.COMBINED_TAG_SERVICE_KEY, include_current_tags = True, include_pending_tags = False ), 10 )
|
||||
self.assertEqual( self._tags_manager.GetNumTags( HC.COMBINED_TAG_SERVICE_KEY, include_current_tags = False, include_pending_tags = True ), 1 )
|
||||
self.assertEqual( self._tags_manager.GetNumTags( HC.COMBINED_TAG_SERVICE_KEY, include_current_tags = True, include_pending_tags = True ), 14 )
|
||||
self.assertEqual( self._tags_manager.GetNumTags( HC.COMBINED_TAG_SERVICE_KEY, include_current_tags = True, include_pending_tags = True ), 11 )
|
||||
|
||||
|
||||
def test_get_pending( self ):
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 264 B |
Loading…
Reference in New Issue