diff --git a/help/changelog.html b/help/changelog.html
index df0e867f..78680fdf 100755
--- a/help/changelog.html
+++ b/help/changelog.html
@@ -8,6 +8,29 @@
changelog
+ version 157
+
+ - fixed a bug in listbook page initialisation and retrieval that was affecting many dialogs on OK
+ - some general dialog OK code cleanup
+ - fixed a media-navigation bug in managetags dialog
+ - fixed a serious OK bug in imageboards dialog
+ - created a new 'periodic' object to manage future subscriptions timing improvements
+ - started subscription YAML->JSON conversion
+ - stopped compressing json in the client db so it is human readable and thus easily editable
+ - subscriptions are no longer renamable, as this code was super buggy and could delete other subs
+ - tidied up the database menu
+ - a bit of misc cleanup
+ - in many cases where it was once pseudorandom, services are now reported in alphabetical order
+ - prototyped 'like' ratings control
+ - added new like ratings control to the background bmp of the media viewer
+ - added new like ratings control to the manage ratings dialog
+ - added new like ratings control to a new hover window in the top-right
+ - added basic additional icon support to new hover window
+ - fixed some misc new alignment bugs related to new ratings stuff
+ - like ratings controls on the hover window have tooltips
+ - fixed up some icon/rating display logic in the background bmp of the media viewer
+ - updated ratings dialog error handling
+
version 156
- improved my build workflow in several ways, which should result in fewer orphan files and other build weirdnesses
diff --git a/include/ClientCaches.py b/include/ClientCaches.py
index 955725b6..3274b7dd 100644
--- a/include/ClientCaches.py
+++ b/include/ClientCaches.py
@@ -49,7 +49,9 @@ class DataCache( object ):
if key not in self._keys_to_data:
- while self._total_estimated_memory_footprint > HC.options[ self._cache_size_key ]:
+ options = wx.GetApp().GetOptions()
+
+ while self._total_estimated_memory_footprint > options[ self._cache_size_key ]:
( deletee_key, last_access_time ) = self._keys_fifo.pop( 0 )
@@ -395,7 +397,9 @@ class ThumbnailCache( object ):
path = HC.STATIC_DIR + os.path.sep + name + '.png'
- thumbnail = HydrusFileHandling.GenerateThumbnail( path, HC.options[ 'thumbnail_dimensions' ] )
+ options = wx.GetApp().GetOptions()
+
+ thumbnail = HydrusFileHandling.GenerateThumbnail( path, options[ 'thumbnail_dimensions' ] )
with open( temp_path, 'wb' ) as f: f.write( thumbnail )
diff --git a/include/ClientConstants.py b/include/ClientConstants.py
index b0a4d7c7..e606fc8e 100755
--- a/include/ClientConstants.py
+++ b/include/ClientConstants.py
@@ -96,6 +96,12 @@ field_string_lookup[ FIELD_FILE ] = 'file'
field_string_lookup[ FIELD_THREAD_ID ] = 'thread id'
field_string_lookup[ FIELD_PASSWORD ] = 'password'
+FILE_UNKNOWN = 0
+FILE_SUCCESSFUL = 1
+FILE_REDUNDANT = 2
+FILE_DELETED = 3
+FILE_FAILED = 4
+
FLAGS_NONE = wx.SizerFlags( 0 )
FLAGS_SMALL_INDENT = wx.SizerFlags( 0 ).Border( wx.ALL, 2 )
@@ -119,6 +125,10 @@ FLAGS_LONE_BUTTON = wx.SizerFlags( 0 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_RIGHT
FLAGS_MIXED = wx.SizerFlags( 0 ).Border( wx.ALL, 2 ).Align( wx.ALIGN_CENTER_VERTICAL )
+DAY = 0
+WEEK = 1
+MONTH = 2
+
RESTRICTION_MIN_RESOLUTION = 0
RESTRICTION_MAX_RESOLUTION = 1
RESTRICTION_MAX_FILE_SIZE = 2
diff --git a/include/ClientController.py b/include/ClientController.py
index 510af231..9bf59160 100755
--- a/include/ClientController.py
+++ b/include/ClientController.py
@@ -149,9 +149,9 @@ class Controller( HydrusController.HydrusController ):
def CurrentlyIdle( self ):
- if HC.options[ 'idle_period' ] == 0: return False
+ if self._options[ 'idle_period' ] == 0: return False
- return HydrusData.GetNow() - self._timestamps[ 'last_user_action' ] > HC.options[ 'idle_period' ]
+ return HydrusData.GetNow() - self._timestamps[ 'last_user_action' ] > self._options[ 'idle_period' ]
def DoHTTP( self, *args, **kwargs ): return self._http.Request( *args, **kwargs )
@@ -160,6 +160,11 @@ class Controller( HydrusController.HydrusController ):
def GetManager( self, manager_type ): return self._managers[ manager_type ]
+ def GetOptions( self ):
+
+ return self._options
+
+
def InitCheckPassword( self ):
while True:
@@ -168,7 +173,7 @@ class Controller( HydrusController.HydrusController ):
if dlg.ShowModal() == wx.ID_OK:
- if hashlib.sha256( dlg.GetValue() ).digest() == HC.options[ 'password' ]: break
+ if hashlib.sha256( dlg.GetValue() ).digest() == self._options[ 'password' ]: break
else: raise HydrusExceptions.PermissionException()
@@ -254,14 +259,14 @@ class Controller( HydrusController.HydrusController ):
shutdown_timestamps = self.Read( 'shutdown_timestamps' )
- if HC.options[ 'maintenance_vacuum_period' ] != 0:
+ if self._options[ 'maintenance_vacuum_period' ] != 0:
- if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] > HC.options[ 'maintenance_vacuum_period' ]: self.Write( 'vacuum' )
+ if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_VACUUM ] > self._options[ 'maintenance_vacuum_period' ]: self.Write( 'vacuum' )
- if HC.options[ 'maintenance_delete_orphans_period' ] != 0:
+ if self._options[ 'maintenance_delete_orphans_period' ] != 0:
- if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_DELETE_ORPHANS ] > HC.options[ 'maintenance_delete_orphans_period' ]: self.Write( 'delete_orphans' )
+ if now - shutdown_timestamps[ CC.SHUTDOWN_TIMESTAMP_DELETE_ORPHANS ] > self._options[ 'maintenance_delete_orphans_period' ]: self.Write( 'delete_orphans' )
if self._timestamps[ 'last_service_info_cache_fatten' ] != 0 and now - self._timestamps[ 'last_service_info_cache_fatten' ] > 60 * 20:
@@ -316,7 +321,7 @@ class Controller( HydrusController.HydrusController ):
def PrepStringForDisplay( self, text ):
- if HC.options[ 'gui_capitalisation' ]: return text
+ if self._options[ 'gui_capitalisation' ]: return text
else: return text.lower()
@@ -387,7 +392,7 @@ class Controller( HydrusController.HydrusController ):
def RestartServer( self ):
- port = HC.options[ 'local_port' ]
+ port = self._options[ 'local_port' ]
def TWISTEDRestartServer():
@@ -525,9 +530,11 @@ class Controller( HydrusController.HydrusController ):
self.InitDB() # can't run on wx thread because we need event queue free to update splash text
- HC.options = wx.GetApp().Read( 'options' )
+ self._options = wx.GetApp().Read( 'options' )
- if HC.options[ 'password' ] is not None:
+ HC.options = self._options
+
+ if self._options[ 'password' ] is not None:
HydrusGlobals.pubsub.pub( 'splash_set_text', 'waiting for password' )
diff --git a/include/ClientDB.py b/include/ClientDB.py
index b648449f..7b87ff24 100755
--- a/include/ClientDB.py
+++ b/include/ClientDB.py
@@ -23,6 +23,7 @@ import HydrusTags
import HydrusThreading
import ClientConstants as CC
import ClientDaemons
+import lz4
import os
import Queue
import random
@@ -1116,13 +1117,15 @@ class DB( HydrusDB.HydrusDB ):
def _AddThumbnails( self, thumbnails ):
+ options = wx.GetApp().GetOptions()
+
for ( hash, thumbnail ) in thumbnails:
thumbnail_path = ClientFiles.GetExpectedThumbnailPath( hash, True )
with open( thumbnail_path, 'wb' ) as f: f.write( thumbnail )
- thumbnail_resized = HydrusFileHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
+ thumbnail_resized = HydrusFileHandling.GenerateThumbnail( thumbnail_path, options[ 'thumbnail_dimensions' ] )
thumbnail_resized_path = ClientFiles.GetExpectedThumbnailPath( hash, False )
@@ -2298,7 +2301,9 @@ class DB( HydrusDB.HydrusDB ):
for wildcard in wildcards_to_exclude: exclude_query_hash_ids.update( self._GetHashIdsFromWildcard( file_service_key, tag_service_key, wildcard, include_current_tags, include_pending_tags ) )
- if file_service_type == HC.FILE_REPOSITORY and HC.options[ 'exclude_deleted_files' ]: exclude_query_hash_ids.update( [ hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM deleted_files WHERE service_id = ?;', ( self._local_file_service_id, ) ) ] )
+ options = wx.GetApp().GetOptions()
+
+ if file_service_type == HC.FILE_REPOSITORY and options[ 'exclude_deleted_files' ]: exclude_query_hash_ids.update( [ hash_id for ( hash_id, ) in self._c.execute( 'SELECT hash_id FROM deleted_files WHERE service_id = ?;', ( self._local_file_service_id, ) ) ] )
query_hash_ids.difference_update( exclude_query_hash_ids )
@@ -2431,7 +2436,9 @@ class DB( HydrusDB.HydrusDB ):
if service_type == HC.FILE_REPOSITORY:
- if HC.options[ 'exclude_deleted_files' ]:
+ options = wx.GetApp().GetOptions()
+
+ if options[ 'exclude_deleted_files' ]:
( num_everything_deleted, ) = self._c.execute( 'SELECT COUNT( * ) FROM files_info, deleted_files USING ( hash_id ) WHERE files_info.service_id = ? AND deleted_files.service_id = ?;', ( service_id, self._local_file_service_id ) ).fetchone()
@@ -2804,7 +2811,9 @@ class DB( HydrusDB.HydrusDB ):
( hash_id, ) = result
- if HC.options[ 'exclude_deleted_files' ]:
+ options = wx.GetApp().GetOptions()
+
+ if options[ 'exclude_deleted_files' ]:
result = self._c.execute( 'SELECT 1 FROM deleted_files WHERE hash_id = ?;', ( hash_id, ) ).fetchone()
@@ -3645,7 +3654,9 @@ class DB( HydrusDB.HydrusDB ):
( hash_id, ) = result
- if HC.options[ 'exclude_deleted_files' ]:
+ options = wx.GetApp().GetOptions()
+
+ if options[ 'exclude_deleted_files' ]:
result = self._c.execute( 'SELECT 1 FROM deleted_files WHERE hash_id = ?;', ( hash_id, ) ).fetchone()
@@ -4653,15 +4664,15 @@ class DB( HydrusDB.HydrusDB ):
HydrusData.ShowText( 'Service ' + name + ' was reset successfully!' )
- def _SaveOptions( self ):
+ def _SaveOptions( self, options ):
( old_options, ) = self._c.execute( 'SELECT options FROM options;' ).fetchone()
( old_width, old_height ) = old_options[ 'thumbnail_dimensions' ]
- ( new_width, new_height ) = HC.options[ 'thumbnail_dimensions' ]
+ ( new_width, new_height ) = options[ 'thumbnail_dimensions' ]
- self._c.execute( 'UPDATE options SET options = ?;', ( HC.options, ) )
+ self._c.execute( 'UPDATE options SET options = ?;', ( options, ) )
resize_thumbs = new_width != old_width or new_height != old_height
@@ -4718,9 +4729,11 @@ class DB( HydrusDB.HydrusDB ):
if password is not None: password = hashlib.sha256( password ).digest()
- HC.options[ 'password' ] = password
+ options = wx.GetApp().GetOptions()
- self._SaveOptions()
+ options[ 'password' ] = password
+
+ self._SaveOptions( options )
def _SetTagCensorship( self, info ):
@@ -5086,11 +5099,11 @@ class DB( HydrusDB.HydrusDB ):
if version == 125:
- HC.options = self._GetOptions()
+ options = self._GetOptions()
- HC.options[ 'default_tag_repository' ] = HC.options[ 'default_tag_repository' ].GetServiceKey()
+ options[ 'default_tag_repository' ] = options[ 'default_tag_repository' ].GetServiceKey()
- self._c.execute( 'UPDATE options SET options = ?;', ( HC.options, ) )
+ self._c.execute( 'UPDATE options SET options = ?;', ( options, ) )
#
@@ -5154,17 +5167,17 @@ class DB( HydrusDB.HydrusDB ):
#
- HC.options = self._GetOptions()
+ options = self._GetOptions()
- client_size = HC.options[ 'client_size' ]
+ client_size = options[ 'client_size' ]
client_size[ 'fs_fullscreen' ] = True
client_size[ 'gui_fullscreen' ] = False
- del HC.options[ 'fullscreen_borderless' ]
+ del options[ 'fullscreen_borderless' ]
- self._c.execute( 'UPDATE options SET options = ?;', ( HC.options, ) )
+ self._c.execute( 'UPDATE options SET options = ?;', ( options, ) )
if version == 135:
@@ -5254,29 +5267,29 @@ class DB( HydrusDB.HydrusDB ):
if version == 143:
- HC.options = self._GetOptions()
+ options = self._GetOptions()
- HC.options[ 'shortcuts' ][ wx.ACCEL_CTRL ][ ord( 'E' ) ] = 'open_externally'
+ options[ 'shortcuts' ][ wx.ACCEL_CTRL ][ ord( 'E' ) ] = 'open_externally'
- self._c.execute( 'UPDATE options SET options = ?;', ( HC.options, ) )
+ self._c.execute( 'UPDATE options SET options = ?;', ( options, ) )
if version == 145:
- HC.options = self._GetOptions()
+ options = self._GetOptions()
- HC.options[ 'gui_colours' ][ 'tags_box' ] = ( 255, 255, 255 )
+ options[ 'gui_colours' ][ 'tags_box' ] = ( 255, 255, 255 )
- self._c.execute( 'UPDATE options SET options = ?;', ( HC.options, ) )
+ self._c.execute( 'UPDATE options SET options = ?;', ( options, ) )
if version == 150:
- HC.options = self._GetOptions()
+ options = self._GetOptions()
- HC.options[ 'file_system_predicates' ][ 'hamming_distance' ] = 5
+ options[ 'file_system_predicates' ][ 'hamming_distance' ] = 5
- self._c.execute( 'UPDATE options SET options = ?;', ( HC.options, ) )
+ self._c.execute( 'UPDATE options SET options = ?;', ( options, ) )
if version == 151:
@@ -5293,20 +5306,20 @@ class DB( HydrusDB.HydrusDB ):
if version == 152:
- HC.options = self._GetOptions()
+ options = self._GetOptions()
- HC.options[ 'file_system_predicates' ][ 'num_pixels' ] = ( 1, 2, 2 )
+ options[ 'file_system_predicates' ][ 'num_pixels' ] = ( 1, 2, 2 )
- self._c.execute( 'UPDATE options SET options = ?;', ( HC.options, ) )
+ self._c.execute( 'UPDATE options SET options = ?;', ( options, ) )
if version == 153:
- HC.options = self._GetOptions()
+ options = self._GetOptions()
- HC.options[ 'file_system_predicates' ] = ClientDefaults.GetClientDefaultOptions()[ 'file_system_predicates' ]
+ options[ 'file_system_predicates' ] = ClientDefaults.GetClientDefaultOptions()[ 'file_system_predicates' ]
- self._c.execute( 'UPDATE options SET options = ?;', ( HC.options, ) )
+ self._c.execute( 'UPDATE options SET options = ?;', ( options, ) )
#
@@ -5351,6 +5364,18 @@ class DB( HydrusDB.HydrusDB ):
self._c.execute( 'DELETE FROM yaml_dumps WHERE dump_type = ?;', ( YAML_DUMP_ID_FAVOURITE_CUSTOM_FILTER_ACTIONS, ) )
+ if version == 156:
+
+ results = self._c.execute( 'SELECT dump_type, dump_name, dump FROM json_dumps_named;' ).fetchall()
+
+ for ( dump_type, dump_name, dump ) in results:
+
+ dump = lz4.loads( dump )
+
+ self._c.execute( 'UPDATE json_dumps_named SET dump = ? WHERE dump_type = ? AND dump_name = ?;', ( sqlite3.Binary( dump ), dump_type, dump_name ) )
+
+
+
self._c.execute( 'UPDATE version SET version = ?;', ( version + 1, ) )
HydrusGlobals.is_db_updated = True
diff --git a/include/ClientDaemons.py b/include/ClientDaemons.py
index be3208b1..ab2f6ddd 100644
--- a/include/ClientDaemons.py
+++ b/include/ClientDaemons.py
@@ -35,7 +35,9 @@ import HydrusGlobals
def DAEMONCheckExportFolders():
- if not HC.options[ 'pause_export_folders_sync' ]:
+ options = wx.GetApp().GetOptions()
+
+ if not options[ 'pause_export_folders_sync' ]:
export_folders = wx.GetApp().Read( 'export_folders' )
@@ -73,7 +75,7 @@ def DAEMONCheckExportFolders():
while i < len( query_hash_ids ):
- if HC.options[ 'pause_export_folders_sync' ]: return
+ if options[ 'pause_export_folders_sync' ]: return
if i == 0: ( last_i, i ) = ( 0, base )
else: ( last_i, i ) = ( i, i + base )
@@ -120,7 +122,9 @@ def DAEMONCheckExportFolders():
def DAEMONCheckImportFolders():
- if not HC.options[ 'pause_import_folders_sync' ]:
+ options = wx.GetApp().GetOptions()
+
+ if not options[ 'pause_import_folders_sync' ]:
import_folders = wx.GetApp().Read( 'import_folders' )
@@ -149,7 +153,7 @@ def DAEMONCheckImportFolders():
for ( i, path ) in enumerate( all_paths ):
- if HC.options[ 'pause_import_folders_sync' ]: return
+ if options[ 'pause_import_folders_sync' ]: return
info = os.lstat( path )
@@ -328,11 +332,13 @@ def DAEMONResizeThumbnails():
limit = max( 100, len( thumbnail_paths_to_render ) / 10 )
+ options = wx.GetApp().GetOptions()
+
for thumbnail_path in thumbnail_paths_to_render:
try:
- thumbnail_resized = HydrusFileHandling.GenerateThumbnail( thumbnail_path, HC.options[ 'thumbnail_dimensions' ] )
+ thumbnail_resized = HydrusFileHandling.GenerateThumbnail( thumbnail_path, options[ 'thumbnail_dimensions' ] )
thumbnail_resized_path = thumbnail_path + '_resized'
@@ -360,6 +366,8 @@ def DAEMONSynchroniseAccounts():
services = wx.GetApp().GetManager( 'services' ).GetServices( HC.RESTRICTED_SERVICES )
+ options = wx.GetApp().GetOptions()
+
do_notify = False
for service in services:
@@ -372,7 +380,7 @@ def DAEMONSynchroniseAccounts():
if service_type in HC.REPOSITORIES:
- if HC.options[ 'pause_repo_sync' ]: continue
+ if options[ 'pause_repo_sync' ]: continue
info = service.GetInfo()
@@ -581,7 +589,9 @@ def DAEMONSynchroniseRepositories():
HydrusGlobals.repos_changed = False
- if not HC.options[ 'pause_repo_sync' ]:
+ options = wx.GetApp().GetOptions()
+
+ if not options[ 'pause_repo_sync' ]:
services = wx.GetApp().GetManager( 'services' ).GetServices( HC.REPOSITORIES )
@@ -612,13 +622,13 @@ def DAEMONSynchroniseRepositories():
while service.CanDownloadUpdate():
- while job_key.IsPaused() or job_key.IsCancelled() or HC.options[ 'pause_repo_sync' ] or HydrusGlobals.shutdown:
+ while job_key.IsPaused() or job_key.IsCancelled() or options[ 'pause_repo_sync' ] or HydrusGlobals.shutdown:
time.sleep( 0.1 )
if job_key.IsPaused(): job_key.SetVariable( 'popup_message_text_1', 'paused' )
- if HC.options[ 'pause_repo_sync' ]: job_key.SetVariable( 'popup_message_text_1', 'repository synchronisation paused' )
+ if options[ 'pause_repo_sync' ]: job_key.SetVariable( 'popup_message_text_1', 'repository synchronisation paused' )
if HydrusGlobals.shutdown: raise Exception( 'application shutting down!' )
@@ -705,13 +715,13 @@ def DAEMONSynchroniseRepositories():
while service.CanProcessUpdate():
- while job_key.IsPaused() or job_key.IsCancelled() or HC.options[ 'pause_repo_sync' ] or HydrusGlobals.shutdown:
+ while job_key.IsPaused() or job_key.IsCancelled() or options[ 'pause_repo_sync' ] or HydrusGlobals.shutdown:
time.sleep( 0.1 )
if job_key.IsPaused(): job_key.SetVariable( 'popup_message_text_1', 'paused' )
- if HC.options[ 'pause_repo_sync' ]: job_key.SetVariable( 'popup_message_text_1', 'repository synchronisation paused' )
+ if options[ 'pause_repo_sync' ]: job_key.SetVariable( 'popup_message_text_1', 'repository synchronisation paused' )
if HydrusGlobals.shutdown: raise Exception( 'application shutting down!' )
@@ -770,13 +780,13 @@ def DAEMONSynchroniseRepositories():
for ( i, content_update ) in enumerate( update.IterateContentUpdates() ):
- while job_key.IsPaused() or job_key.IsCancelled() or HC.options[ 'pause_repo_sync' ] or HydrusGlobals.shutdown:
+ while job_key.IsPaused() or job_key.IsCancelled() or options[ 'pause_repo_sync' ] or HydrusGlobals.shutdown:
time.sleep( 0.1 )
if job_key.IsPaused(): job_key.SetVariable( 'popup_message_text_2', 'paused' )
- if HC.options[ 'pause_repo_sync' ]: job_key.SetVariable( 'popup_message_text_2', 'repository synchronisation paused' )
+ if options[ 'pause_repo_sync' ]: job_key.SetVariable( 'popup_message_text_2', 'repository synchronisation paused' )
if HydrusGlobals.shutdown: raise Exception( 'application shutting down!' )
@@ -906,13 +916,13 @@ def DAEMONSynchroniseRepositories():
if len( thumbnail_hashes_i_need ) > 0:
- while job_key.IsPaused() or job_key.IsCancelled() or HC.options[ 'pause_repo_sync' ] or HydrusGlobals.shutdown:
+ while job_key.IsPaused() or job_key.IsCancelled() or options[ 'pause_repo_sync' ] or HydrusGlobals.shutdown:
time.sleep( 0.1 )
if job_key.IsPaused(): job_key.SetVariable( 'popup_message_text_1', 'paused' )
- if HC.options[ 'pause_repo_sync' ]: job_key.SetVariable( 'popup_message_text_1', 'repository synchronisation paused' )
+ if options[ 'pause_repo_sync' ]: job_key.SetVariable( 'popup_message_text_1', 'repository synchronisation paused' )
if HydrusGlobals.shutdown: raise Exception( 'application shutting down!' )
@@ -1014,7 +1024,9 @@ def DAEMONSynchroniseSubscriptions():
HydrusGlobals.subs_changed = False
- if not HC.options[ 'pause_subs_sync' ]:
+ options = wx.GetApp().GetOptions()
+
+ if not options[ 'pause_subs_sync' ]:
subscription_names = wx.GetApp().Read( 'subscription_names' )
@@ -1111,13 +1123,13 @@ def DAEMONSynchroniseSubscriptions():
while True:
- while job_key.IsPaused() or job_key.IsCancelled() or HC.options[ 'pause_subs_sync' ] or HydrusGlobals.shutdown:
+ while job_key.IsPaused() or job_key.IsCancelled() or options[ 'pause_subs_sync' ] or HydrusGlobals.shutdown:
time.sleep( 0.1 )
if job_key.IsPaused(): job_key.SetVariable( 'popup_message_text_1', 'paused' )
- if HC.options[ 'pause_subs_sync' ]: job_key.SetVariable( 'popup_message_text_1', 'subscriptions paused' )
+ if options[ 'pause_subs_sync' ]: job_key.SetVariable( 'popup_message_text_1', 'subscriptions paused' )
if HydrusGlobals.shutdown: raise Exception( 'application shutting down!' )
@@ -1185,13 +1197,13 @@ def DAEMONSynchroniseSubscriptions():
for ( i, url_args ) in enumerate( all_url_args ):
- while job_key.IsPaused() or job_key.IsCancelled() or HC.options[ 'pause_subs_sync' ] or HydrusGlobals.shutdown:
+ while job_key.IsPaused() or job_key.IsCancelled() or options[ 'pause_subs_sync' ] or HydrusGlobals.shutdown:
time.sleep( 0.1 )
if job_key.IsPaused(): job_key.SetVariable( 'popup_message_text_1', 'paused' )
- if HC.options[ 'pause_subs_sync' ]: job_key.SetVariable( 'popup_message_text_1', 'subscriptions paused' )
+ if options[ 'pause_subs_sync' ]: job_key.SetVariable( 'popup_message_text_1', 'subscriptions paused' )
if HydrusGlobals.shutdown: raise Exception( 'application shutting down!' )
diff --git a/include/ClientData.py b/include/ClientData.py
index 3db92469..49add314 100644
--- a/include/ClientData.py
+++ b/include/ClientData.py
@@ -1,5 +1,6 @@
import ClientConstants as CC
import collections
+import datetime
import HydrusConstants as HC
import HydrusExceptions
import HydrusNetworking
@@ -9,6 +10,7 @@ import traceback
import os
import sqlite3
import sys
+import time
import wx
import yaml
import HydrusData
@@ -122,7 +124,9 @@ def GenerateExportFilename( media, terms ):
def GetExportPath():
- path = HC.options[ 'export_path' ]
+ options = wx.GetApp().GetOptions()
+
+ path = options[ 'export_path' ]
if path is None:
@@ -788,6 +792,39 @@ class FileSystemPredicates( object ):
def MustNotBeLocal( self ): return self._not_local
+class GalleryQuery( HydrusSerialisable.SerialisableBase ):
+
+ SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_GALLERY_QUERY
+ VERSION = 1
+
+ def __init__( self, name ):
+
+ HydrusSerialisable.SerialisableBase.__init__( self )
+
+ self._site_type = None
+ self._query_type = None
+ self._query = None
+ # add 'check tags if redundant' here
+ self._import_file_options = None
+ self._import_tag_options = None
+
+
+ def _GetSerialisableInfo( self ):
+
+ return ( self._site_type, self._query_type, self._query, self._import_file_options.GetEasySerialisedInfo(), self._import_tag_options.GetEasySerialisedInfo() )
+
+
+ def _InitialiseFromSerialisableInfo( self, serialisable_info ):
+
+ ( self._site_type, self._query_type, self._query, easy_serialised_import_file_options, easy_serialised_import_tag_options ) = serialisable_info
+
+ self._import_file_options = HydrusSerialisable.CreateFromEasy( easy_serialised_import_file_options )
+
+ self._import_tag_options = HydrusSerialisable.CreateFromEasy( easy_serialised_import_tag_options )
+
+
+HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_GALLERY_QUERY ] = GalleryQuery
+
class Imageboard( HydrusData.HydrusYAMLBase ):
yaml_tag = u'!Imageboard'
@@ -832,6 +869,246 @@ class Imageboard( HydrusData.HydrusYAMLBase ):
sqlite3.register_adapter( Imageboard, yaml.safe_dump )
+class ImportFileOptions( HydrusSerialisable.SerialisableBase ):
+
+ SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FILE_OPTIONS
+ VERSION = 1
+
+ def __init__( self, name ):
+
+ HydrusSerialisable.SerialisableBase.__init__( self )
+
+ options = wx.GetApp().GetOptions()
+
+ self._automatic_archive = False
+ self._exclude_deleted = options[ 'exclude_deleted_files' ]
+ self._min_size = None
+ self._min_resolution = None
+ self._file_limit = None
+
+
+ def _GetSerialisableInfo( self ):
+
+ return ( self._automatic_archive, self._exclude_deleted, self._min_size, self._min_resolution, self._file_limit )
+
+
+ def _InitialiseFromSerialisableInfo( self, serialisable_info ):
+
+ ( self._automatic_archive, self._exclude_deleted, self._min_size, self._min_resolution, self._file_limit ) = serialisable_info
+
+
+ def ToTuple( self ):
+
+ return ( self._automatic_archive, self._exclude_deleted, self._min_size, self._min_resolution, self._file_limit )
+
+
+ def SetTuple( self, automatic_archive, exclude_deleted, min_size, min_resolution, file_limit ):
+
+ self._automatic_archive = automatic_archive
+ self._exclude_deleted = exclude_deleted
+ self._min_size = min_size
+ self._min_resolution = min_resolution
+ file_limit = file_limit
+
+
+HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_FILE_OPTIONS ] = ImportFileOptions
+
+class ImportTagOptions( HydrusSerialisable.SerialisableBase ):
+
+ SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_TAG_OPTIONS
+ VERSION = 1
+
+ def __init__( self, name ):
+
+ HydrusSerialisable.SerialisableBase.__init__( self )
+
+ self._get_tags_on_redundant = False
+ self._service_keys_to_namespaces = {}
+
+
+ def _GetSerialisableInfo( self ):
+
+ safe_service_keys_to_namespaces = { service_key.encode( 'hex' ) : list( namespaces ) for ( service_key, namespaces ) in self._service_keys_to_namespaces.items() }
+
+ return ( self._get_tags_on_redundant, safe_service_keys_to_namespaces )
+
+
+ def _InitialiseFromSerialisableInfo( self, serialisable_info ):
+
+ ( self._get_tags_on_redundant, safe_service_keys_to_namespaces ) = serialisable_info
+
+ self._service_keys_to_namespaces = { service_key.decode( 'hex' ) : set( namespaces ) for ( service_key, namespaces ) in self._service_keys_to_namespaces.items() }
+
+
+HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_IMPORT_TAG_OPTIONS ] = ImportTagOptions
+
+class Periodic( HydrusSerialisable.SerialisableBase ):
+
+ SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_PERIODIC
+ VERSION = 1
+
+ def __init__( self, name ):
+
+ HydrusSerialisable.SerialisableBase.__init__( self )
+
+ self._wavelength = CC.DAY
+ self._multiplier = 1
+ self._phase = 0
+ self._last_run = 0
+ self._failure_delay_timestamp = None
+ self._paused = False
+
+
+ def _GetSerialisableInfo( self ):
+
+ return ( self._wavelength, self._multiplier, self._phase, self._last_run, self._failure_delay_timestamp, self._paused )
+
+
+ def _InitialiseFromSerialisableInfo( self, serialisable_info ):
+
+ ( self._wavelength, self._multiplier, self._phase, self._last_run, self._failure_delay_timestamp, self._paused ) = serialisable_info
+
+
+ def GetDue( self ):
+
+ day_phase = self._phase / ( 24 * 3600 )
+ hour_phase = ( self._phase % ( 24 * 3600 ) ) / 3600
+ minute_phase = ( self._phase % 3600 ) / 60
+
+ last_run_datetime = datetime.datetime.fromtimestamp( self._last_run )
+
+ due_datetime = last_run_datetime.replace( hour = hour_phase, minute = minute_phase, second = 0, microsecond = 0 )
+
+ one_day = datetime.timedelta( days = 1 )
+
+ if self._wavelength == CC.DAY:
+
+ due_datetime += one_day * self._multiplier
+
+ elif self._wavelength == CC.WEEK:
+
+ times_passed = 0
+
+ while times_passed < self._multiplier:
+
+ due_datetime += one_day
+
+ if due_datetime.weekday() == day_phase:
+
+ times_passed += 1
+
+
+
+ elif self._wavelength == CC.MONTH:
+
+ times_passed = 0
+
+ while times_passed < self._multiplier:
+
+ due_datetime += one_day
+
+ if due_datetime.day == day_phase + 1:
+
+ times_passed += 1
+
+
+
+
+ due_timestamp = time.mktime( due_datetime.timetuple() )
+
+ return due_timestamp
+
+
+ def GetString( self ):
+
+ s = 'last run was '
+ s += HydrusData.ConvertTimestampToPrettyAgo( self._last_run )
+ s += ', will next run in '
+
+ if self.IsFailureDelaying():
+
+ s += HydrusData.ConvertTimestampToPrettyPending( max( self.GetDue(), self._failure_delay_timestamp ) )
+ s += ', which may be slightly delayed because of an error'
+
+ else:
+
+ s += HydrusData.ConvertTimestampToPrettyPending( self.GetDue() )
+
+
+ return s
+
+
+ def GetPeriodics( self ):
+
+ return ( self._wavelength, self._multiplier, self._phase )
+
+
+ def IsDue( self ):
+
+ return HydrusData.GetNow() > self.GetDue()
+
+
+ def IsFailureDelaying( self ):
+
+ if self._failure_delay_timestamp is None:
+
+ return False
+
+ else:
+
+ return HydrusData.GetNow() > self._failure_delay_timestamp
+
+
+
+ def IsPaused( self ): return self._paused
+
+ def IsReadyToRun( self ):
+
+ if self.IsPaused(): return False
+
+ if not self.IsDue(): return False
+
+ if self.IsFailureDelaying(): return False
+
+ return True
+
+
+ def Pause( self ):
+
+ self._paused = True
+
+
+ def ReportError( self, delay ):
+
+ self._failure_delay_timestamp = HydrusData.GetNow() + delay
+
+
+ def ReportRun( self ):
+
+ self._last_run = HydrusData.GetNow()
+
+
+ def Reset( self ):
+
+ self._last_run = 0
+ self._failure_delay_timestamp = None
+ self._paused = False
+
+
+ def Resume( self ):
+
+ self.paused = False
+
+
+ def SetPeriodics( self, wavelength, multiplier, phase ):
+
+ self._wavelength = wavelength
+ self._multiplier = multiplier
+ self._phase = phase
+
+
+HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_PERIODIC ] = Periodic
+
class Service( HydrusData.HydrusYAMLBase ):
yaml_tag = u'!Service'
@@ -1123,6 +1400,7 @@ class ServicesManager( object ):
self._lock = threading.Lock()
self._keys_to_services = {}
+ self._services_sorted = []
self.RefreshServices()
@@ -1140,7 +1418,7 @@ class ServicesManager( object ):
def GetServices( self, types = HC.ALL_SERVICES ):
- with self._lock: return [ service for service in self._keys_to_services.values() if service.GetServiceType() in types ]
+ with self._lock: return [ service for service in self._services_sorted if service.GetServiceType() in types ]
def RefreshServices( self ):
@@ -1151,6 +1429,11 @@ class ServicesManager( object ):
self._keys_to_services = { service.GetServiceKey() : service for service in services }
+ compare_function = lambda a, b: cmp( a.GetName(), b.GetName() )
+
+ self._services_sorted = list( services )
+ self._services_sorted.sort( cmp = compare_function )
+
class Shortcuts( HydrusSerialisable.SerialisableBaseNamed ):
@@ -1312,6 +1595,32 @@ class Shortcuts( HydrusSerialisable.SerialisableBaseNamed ):
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SHORTCUTS ] = Shortcuts
+class Subscription( HydrusSerialisable.SerialisableBaseNamed ):
+
+ SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION
+ VERSION = 1
+
+ def __init__( self, name ):
+
+ HydrusSerialisable.SerialisableBaseNamed.__init__( self, name )
+
+ self._gallery_query = None
+ self._periodic_info = None # include last checked and paused
+ self._url_cache = []
+
+
+ def _GetSerialisableInfo( self ):
+
+ return ( serialisable_mouse_actions, serialisable_keyboard_actions )
+
+
+ def _InitialiseFromSerialisableInfo( self, serialisable_info ):
+
+ ( serialisable_mouse_actions, serialisable_keyboard_actions ) = serialisable_info
+
+
+HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_SUBSCRIPTION ] = Subscription
+
class UndoManager( object ):
def __init__( self ):
@@ -1520,7 +1829,9 @@ def GetDefaultAdvancedTagOptions( lookup ):
if site_type == HC.SITE_TYPE_BOORU: backup_lookup = HC.SITE_TYPE_BOORU
- ato_options = HC.options[ 'default_advanced_tag_options' ]
+ options = wx.GetApp().GetOptions()
+
+ ato_options = options[ 'default_advanced_tag_options' ]
if lookup in ato_options: ato = ato_options[ lookup ]
elif backup_lookup is not None and backup_lookup in ato_options: ato = ato_options[ backup_lookup ]
diff --git a/include/ClientFiles.py b/include/ClientFiles.py
index 0759560a..042d4d10 100644
--- a/include/ClientFiles.py
+++ b/include/ClientFiles.py
@@ -7,6 +7,7 @@ import dircache
import itertools
import ClientData
import ClientConstants
+import wx
def GetAllFileHashes():
@@ -203,7 +204,9 @@ def GetThumbnailPath( hash, full_size = True ):
full_size_path = GetThumbnailPath( hash, True )
- thumbnail_dimensions = HC.options[ 'thumbnail_dimensions' ]
+ options = wx.GetApp().GetOptions()
+
+ thumbnail_dimensions = options[ 'thumbnail_dimensions' ]
thumbnail_resized = HydrusFileHandling.GenerateThumbnail( full_size_path, thumbnail_dimensions )
diff --git a/include/ClientGUI.py b/include/ClientGUI.py
index 32407023..c30cf7ee 100755
--- a/include/ClientGUI.py
+++ b/include/ClientGUI.py
@@ -696,13 +696,16 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'backup_database' ), p( 'Create Database Backup' ), p( 'Back the database up to an external location.' ) )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'restore_database' ), p( 'Restore Database Backup' ), p( 'Restore the database from an external location.' ) )
menu.AppendSeparator()
- menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'vacuum_db' ), p( '&Vacuum' ), p( 'Rebuild the Database.' ) )
- menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete_orphans' ), p( '&Delete Orphan Files' ), p( 'Go through the client\'s file store, deleting any files that are no longer needed.' ) )
- menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete_service_info' ), p( '&Clear Service Info Cache' ), p( 'Delete all cache service info, in case it has become desynchronised.' ) )
- menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'regenerate_thumbnails' ), p( '&Regenerate All Thumbnails' ), p( 'Delete all thumbnails and regenerate from original files.' ) )
- menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'file_integrity' ), p( '&Check File Integrity' ), p( 'Review and fix all local file records.' ) )
- menu.AppendSeparator()
- menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'clear_caches' ), p( '&Clear Caches' ), p( 'Fully clear the fullscreen, preview and thumbnail caches.' ) )
+
+ submenu = wx.Menu()
+
+ submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'vacuum_db' ), p( '&Vacuum' ), p( 'Rebuild the Database.' ) )
+ submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete_orphans' ), p( '&Delete Orphan Files' ), p( 'Go through the client\'s file store, deleting any files that are no longer needed.' ) )
+ submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'delete_service_info' ), p( '&Clear Service Info Cache' ), p( 'Delete all cache service info, in case it has become desynchronised.' ) )
+ submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'regenerate_thumbnails' ), p( '&Regenerate All Thumbnails' ), p( 'Delete all thumbnails and regenerate from original files.' ) )
+ submenu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'file_integrity' ), p( '&Check File Integrity' ), p( 'Review and fix all local file records.' ) )
+
+ menu.AppendMenu( CC.ID_NULL, p( '&Maintenance' ), submenu )
return ( menu, p( '&Database' ), True )
@@ -898,6 +901,8 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
debug.AppendCheckItem( db_profile_mode_id, p( '&DB Profile Mode' ) )
debug.Check( db_profile_mode_id, HydrusGlobals.db_profile_mode )
debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'debug_garbage' ), p( 'Garbage' ) )
+ debug.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'clear_caches' ), p( '&Clear Caches' ) )
+
menu.AppendMenu( wx.ID_NONE, p( 'Debug' ), debug )
menu.Append( ClientCaches.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'help_shortcuts' ), p( '&Shortcuts' ) )
@@ -1285,7 +1290,7 @@ class FrameGUI( ClientGUICommon.FrameThatResizes ):
HydrusGlobals.pubsub.pub( 'notify_restart_import_folders_daemon' )
- try: wx.GetApp().Write( 'save_options' )
+ try: wx.GetApp().Write( 'save_options', HC.options )
except: wx.MessageBox( traceback.format_exc() )
@@ -2200,7 +2205,7 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p
( HC.options[ 'hpos' ], HC.options[ 'vpos' ] ) = page.GetSashPositions()
- wx.GetApp().Write( 'save_options' )
+ wx.GetApp().Write( 'save_options', HC.options )
self._SaveGUISession( 'last session' )
diff --git a/include/ClientGUICanvas.py b/include/ClientGUICanvas.py
index 1731c599..cc18d33a 100755
--- a/include/ClientGUICanvas.py
+++ b/include/ClientGUICanvas.py
@@ -7,6 +7,7 @@ import ClientGUICommon
import ClientGUIDialogs
import ClientGUIDialogsManage
import ClientMedia
+import ClientRatings
import collections
import gc
import HydrusImageHandling
@@ -586,9 +587,7 @@ class Canvas( object ):
if self._current_media is not None:
- try:
- with ClientGUIDialogsManage.DialogManageRatings( self, ( self._current_media, ) ) as dlg: dlg.ShowModal()
- except: wx.MessageBox( 'Had a problem displaying the manage ratings dialog from fullscreen.' )
+ with ClientGUIDialogsManage.DialogManageRatings( self, ( self._current_media, ) ) as dlg: dlg.ShowModal()
@@ -900,6 +899,10 @@ class CanvasWithDetails( Canvas ):
self._hover_commands = FullscreenHoverFrameCommands( self, self._canvas_key )
self._hover_tags = FullscreenHoverFrameTags( self, self._canvas_key )
+ ratings_services = wx.GetApp().GetManager( 'services' ).GetServices( ( HC.RATINGS_SERVICES ) )
+
+ if len( ratings_services ) > 0: self._hover_ratings = FullscreenHoverFrameRatings( self, self._canvas_key )
+
def _DrawBackgroundDetails( self, dc ):
@@ -951,6 +954,10 @@ class CanvasWithDetails( Canvas ):
dc.SetTextForeground( wx.Colour( *HC.options[ 'gui_colours' ][ 'media_text' ] ) )
+ # top right
+
+ current_y = 2
+
# icons
icons_to_show = []
@@ -978,12 +985,37 @@ class CanvasWithDetails( Canvas ):
current_x -= 20
- # top right
+ if len( icons_to_show ) > 0: current_y += 18
+
+ # ratings
top_right_strings = []
( local_ratings, remote_ratings ) = self._current_display_media.GetRatings()
+ services_manager = wx.GetApp().GetManager( 'services' )
+
+ like_services = services_manager.GetServices( ( HC.LOCAL_RATING_LIKE, ) )
+
+ like_services.reverse()
+
+ like_rating_current_x = client_width - 16
+
+ for like_service in like_services:
+
+ service_key = like_service.GetServiceKey()
+
+ rating_state = ClientRatings.GetLikeStateFromMedia( ( self._current_display_media, ), service_key )
+
+ ( pen_colour, brush_colour ) = ClientRatings.GetPenAndBrushColours( service_key, rating_state )
+
+ ClientRatings.DrawLike( dc, like_rating_current_x, current_y, pen_colour, brush_colour )
+
+ like_rating_current_x -= 16
+
+
+ if len( like_services ) > 0: current_y += 16
+
service_keys_to_ratings = local_ratings.GetServiceKeysToRatings()
for ( service_key, rating ) in service_keys_to_ratings.items():
@@ -1001,29 +1033,18 @@ class CanvasWithDetails( Canvas ):
service_type = service.GetServiceType()
- if service_type == HC.LOCAL_RATING_LIKE:
-
- ( like, dislike ) = service.GetLikeDislike()
-
- if rating == 1: s = like
- elif rating == 0: s = dislike
-
- elif service_type == HC.LOCAL_RATING_NUMERICAL:
+ if service_type == HC.LOCAL_RATING_NUMERICAL:
( lower, upper ) = service.GetLowerUpper()
s = HydrusData.ConvertNumericalRatingToPrettyString( lower, upper, rating )
-
- top_right_strings.append( s )
+ top_right_strings.append( s )
+
if len( top_right_strings ) > 0:
- current_y = 3
-
- if len( icons_to_show ) > 0: current_y += 16
-
for s in top_right_strings:
( x, y ) = dc.GetTextExtent( s )
@@ -1034,6 +1055,8 @@ class CanvasWithDetails( Canvas ):
+ # middle
+
current_y = 3
title_string = self._current_display_media.GetTitleString()
@@ -2674,7 +2697,7 @@ class FullscreenHoverFrameCommands( FullscreenHoverFrame ):
( my_width, my_height ) = self.GetClientSize()
- my_ideal_width = parent_width * 0.6
+ my_ideal_width = int( parent_width * 0.6 )
if my_height != parent_height or my_ideal_width != my_width:
@@ -2683,9 +2706,7 @@ class FullscreenHoverFrameCommands( FullscreenHoverFrame ):
self.SetSize( ( my_ideal_width, -1 ) )
- x = ( parent_width - my_ideal_width ) / 2
-
- self.SetPosition( parent.ClientToScreenXY( x, 0 ) )
+ self.SetPosition( parent.ClientToScreenXY( int( parent_width * 0.2 ), 0 ) )
def AddCommand( self, label, callback ):
@@ -2798,6 +2819,129 @@ class FullscreenHoverFrameCommands( FullscreenHoverFrame ):
+class FullscreenHoverFrameRatings( FullscreenHoverFrame ):
+
+ def __init__( self, parent, canvas_key ):
+
+ FullscreenHoverFrame.__init__( self, parent, canvas_key )
+
+ vbox = wx.BoxSizer( wx.VERTICAL )
+
+ self._icon_panel = wx.Panel( self )
+
+ self._inbox_icon = ClientGUICommon.BufferedWindowIcon( self._icon_panel, CC.GlobalBMPs.inbox_bmp )
+
+ icon_hbox = wx.BoxSizer( wx.HORIZONTAL )
+
+ icon_hbox.AddF( ( 16, 16 ), CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
+ icon_hbox.AddF( self._inbox_icon, CC.FLAGS_MIXED )
+
+ self._icon_panel.SetSizer( icon_hbox )
+
+ like_hbox = wx.BoxSizer( wx.HORIZONTAL )
+
+ like_hbox.AddF( ( 16, 16 ), CC.FLAGS_EXPAND_BOTH_WAYS )
+
+ like_services = wx.GetApp().GetManager( 'services' ).GetServices( ( HC.LOCAL_RATING_LIKE, ) )
+
+ for service in like_services:
+
+ service_key = service.GetServiceKey()
+
+ control = ClientGUICommon.RatingLikeCanvas( self, service_key, canvas_key )
+
+ like_hbox.AddF( control, CC.FLAGS_NONE )
+
+
+ # each numerical one in turn
+
+ vbox.AddF( self._icon_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
+ vbox.AddF( ( 1, 2 ), CC.FLAGS_NONE )
+ vbox.AddF( like_hbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
+
+ self.SetSizer( vbox )
+
+ self._ShowHideIcons()
+
+ HydrusGlobals.pubsub.sub( self, 'ProcessContentUpdates', 'content_updates_gui' )
+
+
+ def _ShowHideIcons( self ):
+
+ if self._current_media is not None:
+
+ if self._current_media.HasInbox():
+
+ self._icon_panel.Show()
+
+ else:
+
+ self._icon_panel.Hide()
+
+
+ self.Fit()
+
+
+ self._SizeAndPosition()
+
+
+ def _SizeAndPosition( self ):
+
+ parent = self.GetParent()
+
+ ( parent_width, parent_height ) = parent.GetClientSize()
+
+ ( my_width, my_height ) = self.GetClientSize()
+
+ my_ideal_width = int( parent_width * 0.2 )
+
+ my_ideal_height = my_height
+
+ if my_ideal_width != my_width or my_ideal_height != my_height:
+
+ self.Fit()
+
+ self.SetSize( ( my_ideal_width, -1 ) )
+
+
+ self.SetPosition( parent.ClientToScreenXY( int( parent_width * 0.8 ), 0 ) )
+
+
+ def ProcessContentUpdates( self, service_keys_to_content_updates ):
+
+ if self._current_media is not None:
+
+ my_hash = self._current_media.GetHash()
+
+ do_redraw = False
+
+ for ( service_key, content_updates ) in service_keys_to_content_updates.items():
+
+ if True in ( my_hash in content_update.GetHashes() for content_update in content_updates ):
+
+ do_redraw = True
+
+ break
+
+
+
+ if do_redraw:
+
+ self._ShowHideIcons()
+
+
+
+
+ def SetDisplayMedia( self, canvas_key, media ):
+
+ if canvas_key == self._canvas_key:
+
+ FullscreenHoverFrame.SetDisplayMedia( self, canvas_key, media )
+
+ self._ShowHideIcons()
+
+
+
class FullscreenHoverFrameTags( FullscreenHoverFrame ):
def __init__( self, parent, canvas_key ):
@@ -2812,7 +2956,6 @@ class FullscreenHoverFrameTags( FullscreenHoverFrame ):
self.SetSizer( vbox )
- HydrusGlobals.pubsub.sub( self, 'SetDisplayMedia', 'canvas_new_display_media' )
HydrusGlobals.pubsub.sub( self, 'ProcessContentUpdates', 'content_updates_gui' )
@@ -2844,7 +2987,7 @@ class FullscreenHoverFrameTags( FullscreenHoverFrame ):
( my_width, my_height ) = self.GetClientSize()
- my_ideal_width = parent_width / 5
+ my_ideal_width = int( parent_width * 0.2 )
my_ideal_height = parent_height
@@ -3166,7 +3309,7 @@ class FullscreenPopoutFilterNumerical( FullscreenPopout ):
HC.options[ 'ratings_filter_accuracy' ] = 1
- wx.GetApp().Write( 'save_options' )
+ wx.GetApp().Write( 'save_options', HC.options )
value = HC.options[ 'ratings_filter_accuracy' ]
@@ -3186,7 +3329,7 @@ class FullscreenPopoutFilterNumerical( FullscreenPopout ):
HC.options[ 'ratings_filter_compare_same' ] = False
- wx.GetApp().Write( 'save_options' )
+ wx.GetApp().Write( 'save_options', HC.options )
compare_same = HC.options[ 'ratings_filter_compare_same' ]
@@ -3205,7 +3348,7 @@ class FullscreenPopoutFilterNumerical( FullscreenPopout ):
HC.options[ 'ratings_filter_left_right' ] = 'left'
- wx.GetApp().Write( 'save_options' )
+ wx.GetApp().Write( 'save_options', HC.options )
left_right = HC.options[ 'ratings_filter_left_right' ]
@@ -4013,14 +4156,14 @@ class RatingsFilterFrameNumerical( ClientGUICommon.FrameThatResizes ):
HC.options[ 'ratings_filter_accuracy' ] = accuracy
- wx.GetApp().Write( 'save_options' )
+ wx.GetApp().Write( 'save_options', HC.options )
def SetCompareSame( self, compare_same ):
HC.options[ 'ratings_filter_compare_same' ] = compare_same
- wx.GetApp().Write( 'save_options' )
+ wx.GetApp().Write( 'save_options', HC.options )
self._compare_same = compare_same
@@ -4029,7 +4172,7 @@ class RatingsFilterFrameNumerical( ClientGUICommon.FrameThatResizes ):
HC.options[ 'ratings_filter_left_right' ] = left_right
- wx.GetApp().Write( 'save_options' )
+ wx.GetApp().Write( 'save_options', HC.options )
self._left_right = left_right
diff --git a/include/ClientGUICommon.py b/include/ClientGUICommon.py
index 98a6f2eb..d56dda6d 100755
--- a/include/ClientGUICommon.py
+++ b/include/ClientGUICommon.py
@@ -3,6 +3,7 @@ import HydrusConstants as HC
import ClientCaches
import ClientData
import ClientConstants as CC
+import ClientRatings
import itertools
import os
import random
@@ -949,7 +950,6 @@ class BufferedWindow( wx.Window ):
self.Bind( wx.EVT_SIZE, self.EventResize )
self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
-
def GetDC( self ): return wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
@@ -966,6 +966,25 @@ class BufferedWindow( wx.Window ):
if my_width != current_bmp_width or my_height != current_bmp_height: self._canvas_bmp = wx.EmptyBitmap( my_width, my_height, 24 )
+class BufferedWindowIcon( BufferedWindow ):
+
+ def __init__( self, parent, bmp ):
+
+ BufferedWindow.__init__( self, parent, size = bmp.GetSize() )
+
+ self._bmp = bmp
+
+ dc = self.GetDC()
+
+ background_colour = self.GetParent().GetBackgroundColour()
+
+ dc.SetBackground( wx.Brush( background_colour ) )
+
+ dc.Clear()
+
+ dc.DrawBitmap( bmp, 0, 0 )
+
+
class BetterChoice( wx.Choice ):
def GetChoice( self ):
@@ -1617,7 +1636,9 @@ class ListBook( wx.Panel ):
def GetNameToPageDict( self ):
- return self._names_to_pages
+ result = { name : page_info for ( name, page_info ) in self._names_to_pages.items() if type( page_info ) != tuple }
+
+ return result
def NameExists( self, name, panel = None ): return self._list_box.FindString( name ) != wx.NOT_FOUND
@@ -3834,6 +3855,218 @@ class PopupMessageManager( wx.Frame ):
self._SizeAndPositionAndShow()
+class RatingLike( wx.Window ):
+
+ def __init__( self, parent, service_key ):
+
+ wx.Window.__init__( self, parent )
+
+ self._service_key = service_key
+
+ self._canvas_bmp = wx.EmptyBitmap( 16, 16, 24 )
+
+ self.Bind( wx.EVT_PAINT, self.EventPaint )
+ self.Bind( wx.EVT_ERASE_BACKGROUND, self.EventEraseBackground )
+
+ self.Bind( wx.EVT_LEFT_DOWN, self.EventLeftDown )
+ self.Bind( wx.EVT_LEFT_DCLICK, self.EventLeftDown )
+ self.Bind( wx.EVT_RIGHT_DOWN, self.EventRightDown )
+ self.Bind( wx.EVT_RIGHT_DCLICK, self.EventRightDown )
+
+ self.SetMinSize( ( 16, 16 ) )
+
+ self._dirty = True
+
+
+ def _Draw( self ):
+
+ raise NotImplementedError()
+
+
+ def GetDC( self ): return wx.BufferedDC( wx.ClientDC( self ), self._canvas_bmp )
+
+ def EventEraseBackground( self, event ): pass
+
+ def EventLeftDown( self, event ):
+
+ raise NotImplementedError()
+
+
+ def EventPaint( self, event ):
+
+ if self._dirty:
+
+ self._Draw()
+
+
+ wx.BufferedPaintDC( self, self._canvas_bmp )
+
+
+ def EventRightDown( self, event ):
+
+ raise NotImplementedError()
+
+
+class RatingLikeDialog( RatingLike ):
+
+ def __init__( self, parent, service_key ):
+
+ RatingLike.__init__( self, parent, service_key )
+
+ self._rating_state = ClientRatings.ALL_NULL
+
+
+ def _Draw( self ):
+
+ dc = self.GetDC()
+
+ dc.SetBackground( wx.Brush( self.GetParent().GetBackgroundColour() ) )
+
+ dc.Clear()
+
+ ( pen_colour, brush_colour ) = ClientRatings.GetPenAndBrushColours( self._service_key, self._rating_state )
+
+ ClientRatings.DrawLike( dc, 0, 0, pen_colour, brush_colour )
+
+ self._dirty = False
+
+
+ def EventLeftDown( self, event ):
+
+ if self._rating_state == ClientRatings.ALL_ON: self._rating_state = ClientRatings.ALL_NULL
+ else: self._rating_state = ClientRatings.ALL_ON
+
+ self._dirty = True
+
+ self.Refresh()
+
+
+ def EventRightDown( self, event ):
+
+ if self._rating_state == ClientRatings.ALL_OFF: self._rating_state = ClientRatings.ALL_NULL
+ else: self._rating_state = ClientRatings.ALL_OFF
+
+ self._dirty = True
+
+ self.Refresh()
+
+
+ def GetRatingState( self ):
+
+ return self._rating_state
+
+
+ def SetRatingState( self, rating_state ):
+
+ self._rating_state = rating_state
+
+
+class RatingLikeCanvas( RatingLike ):
+
+ def __init__( self, parent, service_key, canvas_key ):
+
+ RatingLike.__init__( self, parent, service_key )
+
+ self._canvas_key = canvas_key
+ self._current_media = None
+ self._rating_state = None
+
+ service = wx.GetApp().GetManager( 'services' ).GetService( service_key )
+
+ name = service.GetName()
+
+ self.SetToolTipString( name )
+
+ HydrusGlobals.pubsub.sub( self, 'ProcessContentUpdates', 'content_updates_gui' )
+ HydrusGlobals.pubsub.sub( self, 'SetDisplayMedia', 'canvas_new_display_media' )
+
+
+ def _Draw( self ):
+
+ dc = self.GetDC()
+
+ dc.SetBackground( wx.Brush( self.GetParent().GetBackgroundColour() ) )
+
+ dc.Clear()
+
+ if self._current_media is not None:
+
+ self._rating_state = ClientRatings.GetLikeStateFromMedia( ( self._current_media, ), self._service_key )
+
+ ( pen_colour, brush_colour ) = ClientRatings.GetPenAndBrushColours( self._service_key, self._rating_state )
+
+ ClientRatings.DrawLike( dc, 0, 0, pen_colour, brush_colour )
+
+
+ self._dirty = False
+
+
+ def EventLeftDown( self, event ):
+
+ if self._current_media is not None:
+
+ if self._rating_state == ClientRatings.ALL_ON: rating = None
+ else: rating = 1
+
+ content_update = HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, self._hashes ) )
+
+ wx.GetApp().Write( 'content_updates', { self._service_key : ( content_update, ) } )
+
+
+
+ def EventRightDown( self, event ):
+
+ if self._current_media is not None:
+
+ if self._rating_state == ClientRatings.ALL_OFF: rating = None
+ else: rating = 0
+
+ content_update = HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, self._hashes ) )
+
+ wx.GetApp().Write( 'content_updates', { self._service_key : ( content_update, ) } )
+
+
+
+ def ProcessContentUpdates( self, service_keys_to_content_updates ):
+
+ if self._current_media is not None:
+
+ for ( service_key, content_updates ) in service_keys_to_content_updates.items():
+
+ for content_update in content_updates:
+
+ ( data_type, action, row ) = content_update.ToTuple()
+
+ if data_type == HC.CONTENT_DATA_TYPE_RATINGS:
+
+ hashes = content_update.GetHashes()
+
+ if len( self._hashes.intersection( hashes ) ) > 0:
+
+ self._dirty = True
+
+ self.Refresh()
+
+ return
+
+
+
+
+
+
+
+ def SetDisplayMedia( self, canvas_key, media ):
+
+ if canvas_key == self._canvas_key:
+
+ self._current_media = media
+
+ self._hashes = self._current_media.GetHashes()
+
+ self._dirty = True
+
+
+
class RegexButton( wx.Button ):
ID_REGEX_WHITESPACE = 0
diff --git a/include/ClientGUIDialogsManage.py b/include/ClientGUIDialogsManage.py
index 0fcad730..9c73d39c 100644
--- a/include/ClientGUIDialogsManage.py
+++ b/include/ClientGUIDialogsManage.py
@@ -11,6 +11,7 @@ import ClientGUICommon
import ClientGUIDialogs
import ClientGUIPredicates
import ClientMedia
+import ClientRatings
import collections
import HydrusNATPunch
import HydrusNetworking
@@ -516,11 +517,6 @@ class DialogManageBoorus( ClientGUIDialogs.Dialog ):
def EventOK( self, event ):
- for ( name, page ) in self._boorus.GetNameToPageDict().items():
-
- if page.HasChanges(): self._edit_log.append( ( HC.SET, ( name, page.GetBooru() ) ) )
-
-
try:
for ( action, data ) in self._edit_log:
@@ -539,6 +535,11 @@ class DialogManageBoorus( ClientGUIDialogs.Dialog ):
+ for ( name, page ) in self._boorus.GetNameToPageDict().items():
+
+ if page.HasChanges(): self._edit_log.append( ( HC.SET, ( name, page.GetBooru() ) ) )
+
+
finally: self.EndModal( wx.ID_OK )
@@ -1883,11 +1884,6 @@ class DialogManageImageboards( ClientGUIDialogs.Dialog ):
def EventOK( self, event ):
- for ( name, page ) in self._sites.GetNameToPageDict().items():
-
- if page.HasChanges(): self._edit_log.append( ( HC.SET, ( name, page.GetImageboards() ) ) )
-
-
try:
for ( action, data ) in self._edit_log:
@@ -1906,6 +1902,11 @@ class DialogManageImageboards( ClientGUIDialogs.Dialog ):
+ for ( name, page ) in self._sites.GetNameToPageDict().items():
+
+ if page.HasChanges(): self._edit_log.append( ( HC.SET, ( name, page.GetImageboards() ) ) )
+
+
finally: self.EndModal( wx.ID_OK )
@@ -2022,6 +2023,7 @@ class DialogManageImageboards( ClientGUIDialogs.Dialog ):
wx.Panel.__init__( self, parent )
+ self._original_imageboards = imageboards
self._has_changes = False
InitialiseControls()
@@ -2099,7 +2101,19 @@ class DialogManageImageboards( ClientGUIDialogs.Dialog ):
- def GetImageboards( self ): return [ page.GetImageboard() for page in self._imageboards.GetNameToPageDict().values() ]
+ def GetImageboards( self ):
+
+ names_to_imageboards = { imageboard.GetName() : imageboard for imageboard in self._original_imageboards }
+
+ for page in self._imageboards.GetNameToPageDict().values():
+
+ imageboard = page.GetImageboard()
+
+ names_to_imageboards[ imageboard.GetName() ] = imageboard
+
+
+ return names_to_imageboards.values()
+
def HasChanges( self ): return self._has_changes or True in ( page.HasChanges() for page in self._imageboards.GetNameToPageDict().values() )
@@ -3744,7 +3758,7 @@ class DialogManageOptions( ClientGUIDialogs.Dialog ):
HC.options[ 'thread_checker_timings' ] = ( self._thread_times_to_check.GetValue(), self._thread_check_period.GetValue() )
- try: wx.GetApp().Write( 'save_options' )
+ try: wx.GetApp().Write( 'save_options', HC.options )
except: wx.MessageBox( traceback.format_exc() )
self.EndModal( wx.ID_OK )
@@ -3938,15 +3952,17 @@ class DialogManageRatings( ClientGUIDialogs.Dialog ):
def InitialiseControls():
- services = wx.GetApp().GetManager( 'services' ).GetServices( HC.RATINGS_SERVICES )
-
- # sort according to local/remote, I guess
- # and maybe sub-sort according to name?
- # maybe just do two get service_key queries
+ like_services = wx.GetApp().GetManager( 'services' ).GetServices( ( HC.LOCAL_RATING_LIKE, ) )
+ numerical_services = wx.GetApp().GetManager( 'services' ).GetServices( ( HC.LOCAL_RATING_NUMERICAL, ) )
self._panels = []
- for service in services: self._panels.append( self._Panel( self, service.GetServiceKey(), media ) )
+ if len( like_services ) > 0:
+
+ self._panels.append( self._LikePanel( self, like_services, media ) )
+
+
+ for service in numerical_services: self._panels.append( self._NumericalPanel( self, service.GetServiceKey(), media ) )
self._apply = wx.Button( self, id = wx.ID_OK, label = 'apply' )
self._apply.Bind( wx.EVT_BUTTON, self.EventOK )
@@ -3977,7 +3993,7 @@ class DialogManageRatings( ClientGUIDialogs.Dialog ):
( x, y ) = self.GetEffectiveMinSize()
- self.SetInitialSize( ( x + 200, y ) )
+ self.SetInitialSize( ( x, y ) )
self._hashes = set()
@@ -4021,9 +4037,9 @@ class DialogManageRatings( ClientGUIDialogs.Dialog ):
if panel.HasChanges():
- ( service_key, content_updates ) = panel.GetContentUpdates()
+ sub_service_keys_to_content_updates = panel.GetContentUpdates()
- service_keys_to_content_updates[ service_key ] = content_updates
+ service_keys_to_content_updates.update( sub_service_keys_to_content_updates )
@@ -4043,7 +4059,90 @@ class DialogManageRatings( ClientGUIDialogs.Dialog ):
self.SetAcceleratorTable( wx.AcceleratorTable( entries ) )
- class _Panel( wx.Panel ):
+ class _LikePanel( wx.Panel ):
+
+ def __init__( self, parent, services, media ):
+
+ wx.Panel.__init__( self, parent )
+
+ self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
+
+ self._services = services
+
+ self._media = media
+
+ self._service_keys_to_controls = {}
+ self._service_keys_to_original_ratings_states = {}
+
+ gridbox = wx.FlexGridSizer( 0, 2 )
+
+ gridbox.AddGrowableCol( 0, 1 )
+
+ for service in self._services:
+
+ name = service.GetName()
+
+ service_key = service.GetServiceKey()
+
+ rating_state = ClientRatings.GetLikeStateFromMedia( self._media, service_key )
+
+ control = ClientGUICommon.RatingLikeDialog( self, service_key )
+
+ control.SetRatingState( rating_state )
+
+ self._service_keys_to_controls[ service_key ] = control
+ self._service_keys_to_original_ratings_states[ service_key ] = rating_state
+
+ gridbox.AddF( wx.StaticText( self, label = name ), CC.FLAGS_MIXED )
+ gridbox.AddF( control, CC.FLAGS_MIXED )
+
+
+ self.SetSizer( gridbox )
+
+
+ def GetContentUpdates( self ):
+
+ service_keys_to_content_updates = {}
+
+ hashes = { hash for hash in itertools.chain.from_iterable( ( media.GetHashes() for media in self._media ) ) }
+
+ for ( service_key, control ) in self._service_keys_to_controls.items():
+
+ original_rating_state = self._service_keys_to_original_ratings_states[ service_key ]
+
+ rating_state = control.GetRatingState()
+
+ if rating_state != original_rating_state:
+
+ if rating_state == ClientRatings.ALL_ON: rating = 1
+ elif rating_state == ClientRatings.ALL_OFF: rating = 0
+ else: rating = None
+
+ content_update = HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, hashes ) )
+
+ service_keys_to_content_updates[ service_key ] = ( content_update, )
+
+
+
+ return service_keys_to_content_updates
+
+
+ def HasChanges( self ):
+
+ for ( service_key, control ) in self._service_keys_to_controls.items():
+
+ original_rating_state = self._service_keys_to_original_ratings_states[ service_key ]
+
+ rating_state = control.GetRatingState()
+
+ if rating_state != original_rating_state: return True
+
+
+ return False
+
+
+
+ class _NumericalPanel( wx.Panel ):
def __init__( self, parent, service_key, media ):
@@ -4299,7 +4398,7 @@ class DialogManageRatings( ClientGUIDialogs.Dialog ):
content_update = HydrusData.ContentUpdate( HC.CONTENT_DATA_TYPE_RATINGS, HC.CONTENT_UPDATE_ADD, ( rating, hashes ) )
- return ( self._service_key, [ content_update ] )
+ return { self._service_key : ( content_update, ) }
def HasChanges( self ):
@@ -5626,7 +5725,6 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
def InitialiseControls():
self._listbook = ClientGUICommon.ListBook( self )
- self._listbook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGING, self.EventPageChanging )
self._add = wx.Button( self, label = 'add' )
self._add.Bind( wx.EVT_BUTTON, self.EventAdd )
@@ -5697,24 +5795,6 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
wx.CallAfter( self._ok.SetFocus )
- def _CheckCurrentSubscriptionIsValid( self ):
-
- panel = self._listbook.GetCurrentPage()
-
- if panel is not None:
-
- name = panel.GetName()
- old_name = self._listbook.GetCurrentName()
-
- if old_name is not None and name != old_name:
-
- if self._listbook.NameExists( name ): raise Exception( 'That name is already in use!' )
-
- self._listbook.RenamePage( old_name, name )
-
-
-
-
def EventAdd( self, event ):
with ClientGUIDialogs.DialogTextEntry( self, 'Enter name for subscription.' ) as dlg:
@@ -5745,14 +5825,6 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
def EventExport( self, event ):
- try: self._CheckCurrentSubscriptionIsValid()
- except Exception as e:
-
- wx.MessageBox( HydrusData.ToString( e ) )
-
- return
-
-
panel = self._listbook.GetCurrentPage()
if panel is not None:
@@ -5784,14 +5856,6 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
def EventOK( self, event ):
- try: self._CheckCurrentSubscriptionIsValid()
- except Exception as e:
-
- wx.MessageBox( HydrusData.ToString( e ) )
-
- return
-
-
all_pages = self._listbook.GetNameToPageDict().values()
try:
@@ -5802,10 +5866,6 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
( name, info ) = page.GetSubscription()
- original_name = page.GetOriginalName()
-
- if original_name != name: wx.GetApp().Write( 'delete_subscription', original_name )
-
wx.GetApp().Write( 'subscription', name, info )
@@ -5816,38 +5876,17 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
finally: self.EndModal( wx.ID_OK )
- def EventPageChanging( self, event ):
-
- try: self._CheckCurrentSubscriptionIsValid()
- except Exception as e:
-
- wx.MessageBox( HydrusData.ToString( e ) )
-
- event.Veto()
-
-
-
def EventRemove( self, event ):
- panel = self._listbook.GetCurrentPage()
-
- name = panel.GetOriginalName()
+ name = self._listbook.GetCurrentName()
self._names_to_delete.add( name )
- if panel is not None: self._listbook.DeleteCurrentPage()
+ self._listbook.DeleteCurrentPage()
def Import( self, paths ):
- try: self._CheckCurrentSubscriptionIsValid()
- except Exception as e:
-
- wx.MessageBox( HydrusData.ToString( e ) )
-
- return
-
-
for path in paths:
try:
@@ -5864,15 +5903,19 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
if dlg.ShowModal() == wx.ID_YES:
+ self._listbook.Select( name )
+
page = self._listbook.GetNameToPageDict()[ name ]
- page.Update( name, info )
+ page.Update( info )
else:
- page = self._Panel( self._listbook, name, info )
+ page = self._Panel( self._listbook, name, new_subscription = True )
+
+ page.Update( info )
self._listbook.AddPage( page, name, select = True )
@@ -5890,10 +5933,6 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
def InitialiseControls():
- self._name_panel = ClientGUICommon.StaticBox( self, 'name' )
-
- self._name = wx.TextCtrl( self._name_panel )
-
self._query_panel = ClientGUICommon.StaticBox( self, 'site and query' )
self._site_type = ClientGUICommon.BetterChoice( self._query_panel )
@@ -5940,15 +5979,13 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
def PopulateControls():
- self._SetControls( name, info )
+ self._SetControls( info )
def ArrangeControls():
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
- self._name_panel.AddF( self._name, CC.FLAGS_EXPAND_PERPENDICULAR )
-
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( wx.StaticText( self._query_panel, label = 'Check subscription every ' ), CC.FLAGS_MIXED )
@@ -5978,7 +6015,6 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
vbox = wx.BoxSizer( wx.VERTICAL )
- vbox.AddF( self._name_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._query_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._info_panel, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._advanced_tag_options, CC.FLAGS_EXPAND_PERPENDICULAR )
@@ -5989,6 +6025,8 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
wx.ScrolledWindow.__init__( self, parent )
+ self._name = name
+
if new_subscription:
info = {}
@@ -6009,12 +6047,11 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
else:
- info = wx.GetApp().Read( 'subscription', name )
+ info = wx.GetApp().Read( 'subscription', self._name )
self._new_subscription = False
- self._original_name = name
self._original_info = info
InitialiseControls()
@@ -6100,7 +6137,7 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
self.Layout()
- def _SetControls( self, name, info ):
+ def _SetControls( self, info ):
site_type = info[ 'site_type' ]
query_type = info[ 'query_type' ]
@@ -6116,8 +6153,6 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
#
- self._name.SetValue( name )
-
self._site_type.SelectClientData( site_type )
self._PresentForSiteType()
@@ -6190,8 +6225,6 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
def GetSubscription( self ):
- name = self._name.GetValue()
-
info = dict( self._original_info )
info[ 'site_type' ] = self._site_type.GetChoice()
@@ -6227,18 +6260,16 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
info[ 'paused' ] = self._paused.GetValue()
- return ( name, info )
+ return ( self._name, info )
- def GetOriginalName( self ): return self._original_name
+ def GetName( self ): return self._name
- def GetName( self ): return self._name.GetValue()
-
- def Update( self, name, info ):
+ def Update( self, info ):
self._original_info = info
- self._SetControls( name, info )
+ self._SetControls( info )
@@ -7577,9 +7608,9 @@ class DialogManageTags( ClientGUIDialogs.Dialog ):
service_type = service.GetServiceType()
name = service.GetName()
- page_info = ( self._Panel, ( self._tag_repositories, self._file_service_key, service.GetServiceKey(), media ), {} )
+ page = self._Panel( self._tag_repositories, self._file_service_key, service.GetServiceKey(), media )
- self._tag_repositories.AddPage( page_info, name )
+ self._tag_repositories.AddPage( page, name )
if service_key == HC.options[ 'default_tag_repository' ]: name_to_select = name
diff --git a/include/ClientGUIOptionsPanels.py b/include/ClientGUIOptionsPanels.py
index c6d27ac3..de1f9117 100644
--- a/include/ClientGUIOptionsPanels.py
+++ b/include/ClientGUIOptionsPanels.py
@@ -4,6 +4,7 @@ import ClientGUICommon
import ClientCaches
import HydrusConstants as HC
import wx
+import wx.lib.masked.timectrl
import HydrusGlobals
class OptionsPanel( wx.Panel ):
@@ -252,6 +253,106 @@ class OptionsPanelImport( OptionsPanel ):
+class OptionsPanelPeriodic( OptionsPanel ):
+
+ def __init__( self, parent ):
+
+ OptionsPanel.__init__( self, parent )
+
+ self._multiplier = wx.SpinCtrl( self, min = 1, max = 1000 )
+
+ self._wavelength = wx.Choice( self )
+
+ self._wavelength.Append( 'days', CC.DAY )
+ self._wavelength.Append( 'weeks', CC.WEEK )
+ self._wavelength.Append( 'months', CC.MONTH )
+
+ self._wavelength.Bind( wx.EVT_CHOICE, self.EventWavelength )
+
+ self._weekday_phase = wx.Choice( self )
+
+ self._weekday_phase.Append( 'monday', 0 )
+ self._weekday_phase.Append( 'tuesday', 1 )
+ self._weekday_phase.Append( 'wednesday', 2 )
+ self._weekday_phase.Append( 'thursday', 3 )
+ self._weekday_phase.Append( 'friday', 4 )
+ self._weekday_phase.Append( 'saturday', 5 )
+ self._weekday_phase.Append( 'sunday', 6 )
+
+ self._monthday_phase = wx.SpinCtrl( self, min = 1, max = 28 )
+
+ self._time_phase = wx.lib.masked.timectrl.TimeCtrl( self, fmt24hr = True, spinButton = True )
+
+ self._reset = wx.Button( self, label = 'forget failure' )
+ self._reset.Bind( wx.EVT_BUTTON, self.EventReset )
+
+ self._paused = wx.CheckBox( self, label = 'paused' )
+
+ vbox = wx.BoxSizer( wx.VERTICAL )
+
+ # this is complicated, with the statictexts to be hidden and so on
+
+ vbox.AddF( self._auto_archive, CC.FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( self._exclude_deleted, CC.FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( self._min_size, CC.FLAGS_EXPAND_PERPENDICULAR )
+ vbox.AddF( self._min_resolution, CC.FLAGS_EXPAND_PERPENDICULAR )
+
+ self.SetSizer( vbox )
+
+ self.SetInfo( {} )
+
+ self._wavelength.Select( 0 )
+
+
+ def EventReset( self, event ):
+
+ # tell the periodic to reset itself
+
+ raise NotImplementedError()
+
+
+ def EventWavelength( self, event ):
+
+ selection = self._wavelength.GetSelection()
+
+ if selection != wx.NOT_FOUND:
+
+ # this is more complicated, since there will be a bit of statictext as well
+
+ wavelength = self._wavelength.GetClientData( selection )
+
+ if wavelength == CC.DAY:
+
+ self._weekday_phase.Hide()
+ self._monthday_phase.Hide()
+
+ elif wavelength == CC.WEEK:
+
+ self._weekday_phase.Show()
+ self._monthday_phase.Hide()
+
+ elif wavelength == CC.MONTH:
+
+ self._weekday_phase.Hide()
+ self._monthday_phase.Show()
+
+
+ # maybe a layout here as well?
+
+
+
+ def GetInfo( self ):
+
+ raise NotImplementedError()
+
+
+ def SetInfo( self, info ):
+
+ # 7 days, at 8pm
+
+ raise NotImplementedError()
+
+
class OptionsPanelTags( OptionsPanel ):
def __init__( self, parent ):
diff --git a/include/ClientRatings.py b/include/ClientRatings.py
index 80ec96a5..0a6e7654 100644
--- a/include/ClientRatings.py
+++ b/include/ClientRatings.py
@@ -1,5 +1,70 @@
import HydrusConstants as HC
+import wx
+ALL_ON = 0
+ALL_OFF = 1
+ALL_NULL = 2
+MIXED = 3
+
+def DrawLike( dc, x, y, pen_colour, brush_colour ):
+
+ dc.SetPen( wx.Pen( pen_colour ) )
+ dc.SetBrush( wx.Brush( brush_colour ) )
+
+ dc.DrawCircle( x + 7, y + 7, 6 )
+
+def GetLikeStateFromMedia( media, service_key ):
+
+ on_exists = False
+ off_exists = False
+ null_exists = False
+
+ for m in media:
+
+ ( local_ratings, remote_ratings ) = m.GetRatings()
+
+ rating = local_ratings.GetRating( service_key )
+
+ if rating == 1: on_exists = True
+ elif rating == 0: off_exists = True
+ elif rating is None: null_exists = True
+
+
+ if len( [ b for b in ( on_exists, off_exists, null_exists ) if b ] ) == 1:
+
+ if on_exists: return ALL_ON
+ elif off_exists: return ALL_OFF
+ else: return ALL_NULL
+
+ else: return MIXED
+
+def GetLikeStateFromRating( rating ):
+
+ if rating == 1: return ALL_ON
+ elif rating == 0: return ALL_OFF
+ else: return ALL_NULL
+
+def GetPenAndBrushColours( service_key, rating_state ):
+
+ if rating_state == ALL_ON:
+
+ brush_colour = wx.Colour( 80, 200, 120 )
+
+ elif rating_state == ALL_OFF:
+
+ brush_colour = wx.Colour( 200, 80, 120 )
+
+ elif rating_state == ALL_NULL:
+
+ brush_colour = wx.WHITE
+
+ else:
+
+ brush_colour = wx.Colour( 127, 127, 127 )
+
+
+ return ( wx.BLACK, brush_colour )
+
class CPRemoteRatingsServiceKeys( object ):
def __init__( self, service_keys_to_cp ):
@@ -100,7 +165,4 @@ class LocalRatingsManager( object ):
if service_key in self._service_keys_to_ratings: del self._service_keys_to_ratings[ service_key ]
-
-
-
-
+
\ No newline at end of file
diff --git a/include/HydrusConstants.py b/include/HydrusConstants.py
index a0fee3d9..e6448534 100755
--- a/include/HydrusConstants.py
+++ b/include/HydrusConstants.py
@@ -49,7 +49,7 @@ options = {}
# Misc
NETWORK_VERSION = 15
-SOFTWARE_VERSION = 156
+SOFTWARE_VERSION = 157
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )
diff --git a/include/HydrusSerialisable.py b/include/HydrusSerialisable.py
index 448ae47e..f4f4139f 100644
--- a/include/HydrusSerialisable.py
+++ b/include/HydrusSerialisable.py
@@ -4,9 +4,22 @@ import lz4
SERIALISABLE_TYPE_BASE = 0
SERIALISABLE_TYPE_BASE_NAMED = 1
SERIALISABLE_TYPE_SHORTCUTS = 2
+SERIALISABLE_TYPE_SUBSCRIPTION = 3
+SERIALISABLE_TYPE_PERIODIC = 4
+SERIALISABLE_TYPE_GALLERY_QUERY = 5
+SERIALISABLE_TYPE_IMPORT_TAG_OPTIONS = 6
+SERIALISABLE_TYPE_IMPORT_FILE_OPTIONS = 7
SERIALISABLE_TYPES_TO_OBJECT_TYPES = {}
+def CreateFromEasy( ( serialisable_type, version, serialised_info ) ):
+
+ obj = SERIALISABLE_TYPES_TO_OBJECT_TYPES[ serialisable_type ]()
+
+ obj.InitialiseFromSerialisedInfo( version, serialised_info )
+
+ return obj
+
class SerialisableBase( object ):
SERIALISABLE_TYPE = SERIALISABLE_TYPE_BASE
@@ -27,15 +40,27 @@ class SerialisableBase( object ):
return old_info
+ def GetCompressedSerialisedInfo( self ):
+
+ serialised_info = self.GetSerialisedInfo()
+
+ compressed_serialised_info = lz4.dumps( serialised_info )
+
+ return compressed_serialised_info
+
+
+ def GetEasySerialisedInfo( self ):
+
+ return ( self.SERIALISABLE_TYPE, self.VERSION, self.GetSerialisedInfo() )
+
+
def GetSerialisedInfo( self ):
serialisable_info = self._GetSerialisableInfo()
serialised_info = json.dumps( serialisable_info )
- compressed_serialised_info = lz4.dumps( serialised_info )
-
- return compressed_serialised_info
+ return serialised_info
def GetTypeAndVersion( self ):
@@ -43,10 +68,15 @@ class SerialisableBase( object ):
return ( self.SERIALISABLE_TYPE, self.VERSION )
- def InitialiseFromSerialisedInfo( self, version, compressed_serialised_info ):
+ def InitialiseFromCompressedSerialisedInfo( self, version, compressed_serialised_info ):
serialised_info = lz4.loads( compressed_serialised_info )
+ self.InitialiseFromSerialisedInfo( version, serialised_info )
+
+
+ def InitialiseFromSerialisedInfo( self, version, serialised_info ):
+
serialisable_info = json.loads( serialised_info )
if version != self.VERSION:
@@ -60,7 +90,6 @@ class SerialisableBase( object ):
class SerialisableBaseNamed( SerialisableBase ):
SERIALISABLE_TYPE = SERIALISABLE_TYPE_BASE_NAMED
- VERSION = 1
def __init__( self, name ):
diff --git a/include/TestDB.py b/include/TestDB.py
index 7f8dc0f5..15233950 100644
--- a/include/TestDB.py
+++ b/include/TestDB.py
@@ -275,39 +275,6 @@ class TestClientDB( unittest.TestCase ):
self.assertEqual( result, set() )
- def test_shortcuts( self ):
-
- result = self._read( 'shortcuts' )
-
- self.assertEqual( result, [] )
-
- #
-
- shortcuts = ClientData.Shortcuts( 'test' )
-
- shortcuts.SetKeyboardAction( wx.ACCEL_NORMAL, wx.WXK_NUMPAD1, ( os.urandom( 32 ), 'action_data' ) )
- shortcuts.SetKeyboardAction( wx.ACCEL_SHIFT, wx.WXK_END, ( None, 'other_action_data' ) )
-
- self._write( 'shortcuts', shortcuts )
-
- result = self._read( 'shortcuts' )
-
- self.assertEqual( len( result ), 1 )
-
- result = self._read( 'shortcuts', 'test' )
-
- self.assertEqual( result.GetKeyboardAction( wx.ACCEL_NORMAL, wx.WXK_NUMPAD1 ), shortcuts.GetKeyboardAction( wx.ACCEL_NORMAL, wx.WXK_NUMPAD1 ) )
- self.assertEqual( result.GetKeyboardAction( wx.ACCEL_SHIFT, wx.WXK_END ), shortcuts.GetKeyboardAction( wx.ACCEL_SHIFT, wx.WXK_END ) )
-
- #
-
- self._write( 'delete_shortcuts', 'test' )
-
- result = self._read( 'shortcuts' )
-
- self.assertEqual( result, [] )
-
-
def test_file_query_ids( self ):
self._clear_db()
@@ -1120,6 +1087,39 @@ class TestClientDB( unittest.TestCase ):
self.assertEqual( result, [ session ] )
+ def test_shortcuts( self ):
+
+ result = self._read( 'shortcuts' )
+
+ self.assertEqual( result, [] )
+
+ #
+
+ shortcuts = ClientData.Shortcuts( 'test' )
+
+ shortcuts.SetKeyboardAction( wx.ACCEL_NORMAL, wx.WXK_NUMPAD1, ( os.urandom( 32 ), 'action_data' ) )
+ shortcuts.SetKeyboardAction( wx.ACCEL_SHIFT, wx.WXK_END, ( None, 'other_action_data' ) )
+
+ self._write( 'shortcuts', shortcuts )
+
+ result = self._read( 'shortcuts' )
+
+ self.assertEqual( len( result ), 1 )
+
+ result = self._read( 'shortcuts', 'test' )
+
+ self.assertEqual( result.GetKeyboardAction( wx.ACCEL_NORMAL, wx.WXK_NUMPAD1 ), shortcuts.GetKeyboardAction( wx.ACCEL_NORMAL, wx.WXK_NUMPAD1 ) )
+ self.assertEqual( result.GetKeyboardAction( wx.ACCEL_SHIFT, wx.WXK_END ), shortcuts.GetKeyboardAction( wx.ACCEL_SHIFT, wx.WXK_END ) )
+
+ #
+
+ self._write( 'delete_shortcuts', 'test' )
+
+ result = self._read( 'shortcuts' )
+
+ self.assertEqual( result, [] )
+
+
def test_shutdown_timestamps( self ):
result = self._read( 'shutdown_timestamps' )
diff --git a/test.py b/test.py
index f361dcd2..ab821b50 100644
--- a/test.py
+++ b/test.py
@@ -122,6 +122,11 @@ class App( wx.App ):
def GetHTTP( self ): return self._http
+ def GetOptions( self ):
+
+ return HC.options
+
+
def GetManager( self, manager_type ): return self._managers[ manager_type ]
def GetWrite( self, name ):