changelog
-
+
version 145
+ - added custom gui colours for thumbnail backgrounds and borders, the autocomplete background, and media background and text +
- added <- and -> arrows to manage tags dialog launched from navigable media viewer +
- on empty input in the manage tags dialog, page up and page down work as shortcuts for the new <- and -> buttons +
- fixed the media height calculation for animations, so when they are vertically scaled, the total height including scanbar won't overflow off screen +
- allowed non-integer page, chapter, volume tags in display and sort calculations +
- semi-integer tags will sort along with integer tags and string tags like so: 0 < 0a < 0b < 1 < 2 < 22 +
- improved the old tag/media sorting code +
- removed loli and shota from hentai foundry filter options +
- patched old db-stored predicates to attempt to convert to the new format when queried for _inclusive +
- this _should_ have fixed the recent export folder problems +
- created an 8chan board, and updated my various links, including in the client, to migrate from my old forum to this +
- misc code improvements +
version 144
- files named 'Thumbs.db' will now be skipped in the import files dialog diff --git a/help/contact.html b/help/contact.html index 14912aec..b9ce4726 100755 --- a/help/contact.html +++ b/help/contact.html @@ -7,16 +7,20 @@
- forum -
- tumblr (rss) +
- homepage +
- new downloads +
- old downloads +
- github +
- 8chan board
- tumblr (rss) +
-
+
contact and links
-Please send bug reports straight to my email or forum. Your other ideas and comments are always welcome.
-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.
+Please send bug reports straight to my email or the board. Your other ideas and comments are always welcome.
+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.
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.
If you have a problem with something on someone else's server, please, do not come to me, 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.
Anyway:
-
-
If you would like to send me something physical, you can use my PO Box:
-
diff --git a/include/ClientConstants.py b/include/ClientConstants.py
index f11df15e..ae354f12 100755
--- a/include/ClientConstants.py
+++ b/include/ClientConstants.py
@@ -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' ] ) )
diff --git a/include/ClientDB.py b/include/ClientDB.py
index 5818d328..0c528018 100755
--- a/include/ClientDB.py
+++ b/include/ClientDB.py
@@ -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
diff --git a/include/ClientGUI.py b/include/ClientGUI.py
index cc43ad99..e6163761 100755
--- a/include/ClientGUI.py
+++ b/include/ClientGUI.py
@@ -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()
diff --git a/include/ClientGUICanvas.py b/include/ClientGUICanvas.py
index c1629dd2..fbabe650 100755
--- a/include/ClientGUICanvas.py
+++ b/include/ClientGUICanvas.py
@@ -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()
diff --git a/include/ClientGUICommon.py b/include/ClientGUICommon.py
index 33a93639..35c266f1 100755
--- a/include/ClientGUICommon.py
+++ b/include/ClientGUICommon.py
@@ -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' )
diff --git a/include/ClientGUIDialogsManage.py b/include/ClientGUIDialogsManage.py
index 821271b6..281148a4 100644
--- a/include/ClientGUIDialogsManage.py
+++ b/include/ClientGUIDialogsManage.py
@@ -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 )
diff --git a/include/ClientGUIMedia.py b/include/ClientGUIMedia.py
index ad6a901b..3a01ae67 100755
--- a/include/ClientGUIMedia.py
+++ b/include/ClientGUIMedia.py
@@ -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 )
diff --git a/include/HydrusConstants.py b/include/HydrusConstants.py
index a616e340..1ee64de9 100755
--- a/include/HydrusConstants.py
+++ b/include/HydrusConstants.py
@@ -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()
diff --git a/include/HydrusTags.py b/include/HydrusTags.py
index 785e6a30..e4ec146b 100644
--- a/include/HydrusTags.py
+++ b/include/HydrusTags.py
@@ -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 )
diff --git a/include/TestHydrusDownloading.py b/include/TestHydrusDownloading.py
index 7610c7ec..240924b1 100644
--- a/include/TestHydrusDownloading.py
+++ b/include/TestHydrusDownloading.py
@@ -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
diff --git a/include/TestHydrusTags.py b/include/TestHydrusTags.py
index 7f89d911..a362bf1c 100644
--- a/include/TestHydrusTags.py
+++ b/include/TestHydrusTags.py
@@ -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 ):
diff --git a/static/8chan.png b/static/8chan.png
new file mode 100644
index 00000000..28a10321
Binary files /dev/null and b/static/8chan.png differ