changelog
-
+
version 83
+ - sort by longest fixed for files with no duration +
- reverse sort by rating fixed for files with no rating +
- sort by largest, newest fixed for files with unknown size or timestamp +
- sort by unknown/absent values sorts more accurately +
- search by num_tags and min_num_tags fixed +
- remove media thumbnail update bug fixed +
- currently viewable indices screwed up after a remove media bug fixed +
- remove all media entire black screen bug fixed +
- delete media redraw optimised +
- several message print unicode/raw byte errors fixed +
- scrollbar position calculation improved, so 'black rectangle on scrolled thumbnail canvas when num_cols changed after resize' bug should be fixed +
- 'tiny black lines on small slider drag' bug fixed +
- moved a bunch of right_up events to right_down, see what you think +
- popup message cleanup is better on shutdown +
- pubsub typeerrors handled more gracefully +
- improved how options init and update works +
- client will now remember the restored size, restored position, maximised state, and screen the gui was last left at +
- client will remember all the same details for fullscreen, separately +
- these sizes _should_ rescue from offscreen if you disconnect/reposition a non-primary display +
- shift+home/end now scrolls and selects, rather than only buggily scrolling +
- I fixed a content_update bug re petitions +
- 'show in new page' pages will no longer show search subbox +
- added hentai foundry tests +
- fixed hentai foundry title and creator tag parsing in certain cases +
- fixed some related html unicode issues +
- initial value on tag siblings dialog fixed +
- tag siblings and parent dialogs now start sorted by the right column (parent/older sibling) +
- setting a default collect will no longer cause a nasty crash! +
- setting an orphaned default collect will no longer cause problems +
- deselect thumbnails has improved focussed media management +
- can now select inbox or archive from right click menu +
version 82
- a bug where slow search results would sometimes appear after search predicates were removed has been fixed diff --git a/include/ClientConstants.py b/include/ClientConstants.py index bcb73474..1a7704d1 100755 --- a/include/ClientConstants.py +++ b/include/ClientConstants.py @@ -171,6 +171,130 @@ sort_string_lookup[ SORT_BY_RANDOM ] = 'random order' THUMBNAIL_MARGIN = 2 THUMBNAIL_BORDER = 1 +# default options + +CLIENT_DEFAULT_OPTIONS = {} + +CLIENT_DEFAULT_OPTIONS[ 'play_dumper_noises' ] = True +CLIENT_DEFAULT_OPTIONS[ 'default_sort' ] = 0 +CLIENT_DEFAULT_OPTIONS[ 'default_collect' ] = None +CLIENT_DEFAULT_OPTIONS[ 'export_path' ] = 'export' +CLIENT_DEFAULT_OPTIONS[ 'hpos' ] = 400 +CLIENT_DEFAULT_OPTIONS[ 'vpos' ] = 700 +CLIENT_DEFAULT_OPTIONS[ 'exclude_deleted_files' ] = False +CLIENT_DEFAULT_OPTIONS[ 'thumbnail_cache_size' ] = 100 * 1048576 +CLIENT_DEFAULT_OPTIONS[ 'preview_cache_size' ] = 25 * 1048576 +CLIENT_DEFAULT_OPTIONS[ 'fullscreen_cache_size' ] = 200 * 1048576 +CLIENT_DEFAULT_OPTIONS[ 'thumbnail_dimensions' ] = [ 150, 125 ] +CLIENT_DEFAULT_OPTIONS[ 'password' ] = None +CLIENT_DEFAULT_OPTIONS[ 'num_autocomplete_chars' ] = 1 +CLIENT_DEFAULT_OPTIONS[ 'gui_capitalisation' ] = False + +system_predicates = {} + +system_predicates[ 'age' ] = ( 0, 0, 0, 7 ) +system_predicates[ 'duration' ] = ( 3, 0, 0 ) +system_predicates[ 'height' ] = ( 1, 1200 ) +system_predicates[ 'limit' ] = 600 +system_predicates[ 'mime' ] = ( 0, 0 ) +system_predicates[ 'num_tags' ] = ( 0, 4 ) +system_predicates[ 'local_rating_numerical' ] = ( 0, 3 ) +system_predicates[ 'local_rating_like' ] = 0 +system_predicates[ 'ratio' ] = ( 0, 16, 9 ) +system_predicates[ 'size' ] = ( 0, 200, 1 ) +system_predicates[ 'width' ] = ( 1, 1920 ) +system_predicates[ 'num_words' ] = ( 0, 30000 ) + +CLIENT_DEFAULT_OPTIONS[ 'file_system_predicates' ] = system_predicates + +default_namespace_colours = {} + +default_namespace_colours[ 'system' ] = ( 153, 101, 21 ) +default_namespace_colours[ 'creator' ] = ( 170, 0, 0 ) +default_namespace_colours[ 'character' ] = ( 0, 170, 0 ) +default_namespace_colours[ 'series' ] = ( 170, 0, 170 ) +default_namespace_colours[ None ] = ( 114, 160, 193 ) +default_namespace_colours[ '' ] = ( 0, 111, 250 ) + +CLIENT_DEFAULT_OPTIONS[ 'namespace_colours' ] = default_namespace_colours + +default_sort_by_choices = [] + +default_sort_by_choices.append( ( 'namespaces', [ 'series', 'creator', 'title', 'volume', 'chapter', 'page' ] ) ) +default_sort_by_choices.append( ( 'namespaces', [ 'creator', 'series', 'title', 'volume', 'chapter', 'page' ] ) ) + +CLIENT_DEFAULT_OPTIONS[ 'sort_by' ] = default_sort_by_choices +CLIENT_DEFAULT_OPTIONS[ 'show_all_tags_in_autocomplete' ] = True +CLIENT_DEFAULT_OPTIONS[ 'fullscreen_borderless' ] = True + +shortcuts = {} + +shortcuts[ wx.ACCEL_NORMAL ] = {} +shortcuts[ wx.ACCEL_CTRL ] = {} +shortcuts[ wx.ACCEL_ALT ] = {} +shortcuts[ wx.ACCEL_SHIFT ] = {} + +shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F3 ] = 'manage_tags' +shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F4 ] = 'manage_ratings' +shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F5 ] = 'refresh' +shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F7 ] = 'archive' +shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F11 ] = 'ratings_filter' +shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F12 ] = 'filter' +shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F9 ] = 'new_page' +shortcuts[ wx.ACCEL_NORMAL ][ ord( 'F' ) ] = 'fullscreen_switch' +shortcuts[ wx.ACCEL_SHIFT ][ wx.WXK_F7 ] = 'inbox' +shortcuts[ wx.ACCEL_CTRL ][ ord( 'B' ) ] = 'frame_back' +shortcuts[ wx.ACCEL_CTRL ][ ord( 'N' ) ] = 'frame_next' +shortcuts[ wx.ACCEL_CTRL ][ ord( 'T' ) ] = 'new_page' +shortcuts[ wx.ACCEL_CTRL ][ ord( 'W' ) ] = 'close_page' +shortcuts[ wx.ACCEL_CTRL ][ ord( 'R' ) ] = 'show_hide_splitters' +shortcuts[ wx.ACCEL_CTRL ][ ord( 'S' ) ] = 'set_search_focus' +shortcuts[ wx.ACCEL_CTRL ][ ord( 'M' ) ] = 'set_media_focus' +shortcuts[ wx.ACCEL_CTRL ][ ord( 'I' ) ] = 'synchronised_wait_switch' + +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_UP ] = 'previous' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_LEFT ] = 'previous' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_UP ] = 'previous' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_LEFT ] = 'previous' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_PAGEUP ] = 'previous' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_PAGEUP ] = 'previous' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_DOWN ] = 'next' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_RIGHT ] = 'next' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_DOWN ] = 'next' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_RIGHT ] = 'next' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_PAGEDOWN ] = 'next' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_PAGEDOWN ] = 'next' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_HOME ] = 'first' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_HOME ] = 'first' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_END ] = 'last' +shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_END ] = 'last' + +shortcuts[ wx.ACCEL_SHIFT ][ wx.WXK_UP ] = 'pan_up' +shortcuts[ wx.ACCEL_SHIFT ][ wx.WXK_DOWN ] = 'pan_down' +shortcuts[ wx.ACCEL_SHIFT ][ wx.WXK_LEFT ] = 'pan_left' +shortcuts[ wx.ACCEL_SHIFT ][ wx.WXK_RIGHT ] = 'pan_right' + +CLIENT_DEFAULT_OPTIONS[ 'shortcuts' ] = shortcuts + +CLIENT_DEFAULT_OPTIONS[ 'confirm_client_exit' ] = False + +CLIENT_DEFAULT_OPTIONS[ 'default_tag_repository' ] = HC.LOCAL_TAG_SERVICE_IDENTIFIER +CLIENT_DEFAULT_OPTIONS[ 'default_tag_sort' ] = SORT_BY_LEXICOGRAPHIC_ASC + +CLIENT_DEFAULT_OPTIONS[ 'pause_repo_sync' ] = False +CLIENT_DEFAULT_OPTIONS[ 'pause_subs_sync' ] = False + +client_size = {} + +client_size[ 'gui_maximised' ] = True +client_size[ 'gui_restored_size' ] = [ 640, 480 ] +client_size[ 'gui_restored_position' ] = [ 20, 20 ] +client_size[ 'fs_maximised' ] = True +client_size[ 'fs_restored_size' ] = [ 640, 480 ] +client_size[ 'fs_restored_position' ] = [ 20, 20 ] + +CLIENT_DEFAULT_OPTIONS[ 'client_size' ] = client_size + def AddPaddingToDimensions( dimensions, padding ): ( x, y ) = dimensions @@ -1678,8 +1802,8 @@ class FileSystemPredicates(): ( operator, num_tags ) = info if operator == '<': self._max_num_tags = num_tags - elif operator == '>': self._num_tags = num_tags - elif operator == '=': self._min_num_tags = num_tags + elif operator == '=': self._num_tags = num_tags + elif operator == '>': self._min_num_tags = num_tags if system_predicate_type == HC.SYSTEM_PREDICATE_TYPE_WIDTH: diff --git a/include/ClientController.py b/include/ClientController.py index 455a6283..b8a230e4 100755 --- a/include/ClientController.py +++ b/include/ClientController.py @@ -110,6 +110,10 @@ class Controller( wx.App ): try: callable( *args, **kwargs ) except wx._core.PyDeadObjectError: pass + except TypeError as e: + + if '_wxPyDeadObject' not in str( e ): raise + finally: pubsubs_queue.task_done() @@ -198,7 +202,7 @@ class Controller( wx.App ): except HydrusExceptions.DBAccessException as e: - print( HC.u( e ) ) + print( repr( HC.u( e ) ) ) message = 'This instance of the client had a problem connecting to the database, which probably means an old instance is still closing.' message += os.linesep + os.linesep diff --git a/include/ClientDB.py b/include/ClientDB.py index 47bd14a4..54bfa123 100755 --- a/include/ClientDB.py +++ b/include/ClientDB.py @@ -1870,19 +1870,19 @@ class ServiceDB( FileDB, MessageDB, TagDB, RatingDB ): if min_num_tags is not None: if min_num_tags == 1: num_tags_nonzero = True - else: tag_predicates.append( lambda num_tags: num_tags >= min_num_tags ) + else: tag_predicates.append( lambda x: x >= min_num_tags ) if num_tags is not None: if num_tags == 0: num_tags_zero = True - else: tag_predicates.append( lambda num_tags: num_tags == num_tags ) + else: tag_predicates.append( lambda x: x == num_tags ) if max_num_tags is not None: if max_num_tags == 1: num_tags_nonzero = True - else: tag_predicates.append( lambda num_tags: num_tags <= max_num_tags ) + else: tag_predicates.append( lambda x: x <= max_num_tags ) statuses = [] @@ -4251,7 +4251,7 @@ class DB( ServiceDB ): self._combined_file_service_id = self._GetServiceId( c, HC.COMBINED_FILE_SERVICE_IDENTIFIER ) self._combined_tag_service_id = self._GetServiceId( c, HC.COMBINED_TAG_SERVICE_IDENTIFIER ) - ( options, ) = c.execute( 'SELECT options FROM options;' ).fetchone() + options = self._GetOptions( c ) HC.options = options @@ -4327,6 +4327,29 @@ class DB( ServiceDB ): return all_imageboards + def _GetOptions( self, c ): + + result = c.execute( 'SELECT options FROM options;' ).fetchone() + + if result is None: + + options = CC.CLIENT_DEFAULT_OPTIONS + + c.execute( 'INSERT INTO options ( options ) VALUES ( ? );', ( options, ) ) + + else: + + ( options, ) = result + + for key in CC.CLIENT_DEFAULT_OPTIONS: + + if key not in options: options[ key ] = CC.CLIENT_DEFAULT_OPTIONS[ key ] + + + + return options + + def _GetRowCount( self, c ): row_count = c.rowcount @@ -4566,119 +4589,6 @@ class DB( ServiceDB ): c.execute( 'INSERT INTO namespaces ( namespace_id, namespace ) VALUES ( ?, ? );', ( 1, '' ) ) - CLIENT_DEFAULT_OPTIONS = {} - - CLIENT_DEFAULT_OPTIONS[ 'play_dumper_noises' ] = True - CLIENT_DEFAULT_OPTIONS[ 'default_sort' ] = 0 - CLIENT_DEFAULT_OPTIONS[ 'default_collect' ] = None - CLIENT_DEFAULT_OPTIONS[ 'export_path' ] = 'export' - CLIENT_DEFAULT_OPTIONS[ 'hpos' ] = 400 - CLIENT_DEFAULT_OPTIONS[ 'vpos' ] = 700 - CLIENT_DEFAULT_OPTIONS[ 'exclude_deleted_files' ] = False - CLIENT_DEFAULT_OPTIONS[ 'thumbnail_cache_size' ] = 100 * 1048576 - CLIENT_DEFAULT_OPTIONS[ 'preview_cache_size' ] = 25 * 1048576 - CLIENT_DEFAULT_OPTIONS[ 'fullscreen_cache_size' ] = 200 * 1048576 - CLIENT_DEFAULT_OPTIONS[ 'thumbnail_dimensions' ] = [ 150, 125 ] - CLIENT_DEFAULT_OPTIONS[ 'password' ] = None - CLIENT_DEFAULT_OPTIONS[ 'num_autocomplete_chars' ] = 1 - CLIENT_DEFAULT_OPTIONS[ 'gui_capitalisation' ] = False - - system_predicates = {} - - system_predicates[ 'age' ] = ( 0, 0, 0, 7 ) - system_predicates[ 'duration' ] = ( 3, 0, 0 ) - system_predicates[ 'height' ] = ( 1, 1200 ) - system_predicates[ 'limit' ] = 600 - system_predicates[ 'mime' ] = ( 0, 0 ) - system_predicates[ 'num_tags' ] = ( 0, 4 ) - system_predicates[ 'local_rating_numerical' ] = ( 0, 3 ) - system_predicates[ 'local_rating_like' ] = 0 - system_predicates[ 'ratio' ] = ( 0, 16, 9 ) - system_predicates[ 'size' ] = ( 0, 200, 1 ) - system_predicates[ 'width' ] = ( 1, 1920 ) - system_predicates[ 'num_words' ] = ( 0, 30000 ) - - CLIENT_DEFAULT_OPTIONS[ 'file_system_predicates' ] = system_predicates - - default_namespace_colours = {} - - default_namespace_colours[ 'system' ] = ( 153, 101, 21 ) - default_namespace_colours[ 'creator' ] = ( 170, 0, 0 ) - default_namespace_colours[ 'character' ] = ( 0, 170, 0 ) - default_namespace_colours[ 'series' ] = ( 170, 0, 170 ) - default_namespace_colours[ None ] = ( 114, 160, 193 ) - default_namespace_colours[ '' ] = ( 0, 111, 250 ) - - CLIENT_DEFAULT_OPTIONS[ 'namespace_colours' ] = default_namespace_colours - - default_sort_by_choices = [] - - default_sort_by_choices.append( ( 'namespaces', [ 'series', 'creator', 'title', 'volume', 'chapter', 'page' ] ) ) - default_sort_by_choices.append( ( 'namespaces', [ 'creator', 'series', 'title', 'volume', 'chapter', 'page' ] ) ) - - CLIENT_DEFAULT_OPTIONS[ 'sort_by' ] = default_sort_by_choices - CLIENT_DEFAULT_OPTIONS[ 'show_all_tags_in_autocomplete' ] = True - CLIENT_DEFAULT_OPTIONS[ 'fullscreen_borderless' ] = True - - shortcuts = {} - - shortcuts[ wx.ACCEL_NORMAL ] = {} - shortcuts[ wx.ACCEL_CTRL ] = {} - shortcuts[ wx.ACCEL_ALT ] = {} - shortcuts[ wx.ACCEL_SHIFT ] = {} - - shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F3 ] = 'manage_tags' - shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F4 ] = 'manage_ratings' - shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F5 ] = 'refresh' - shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F7 ] = 'archive' - shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F11 ] = 'ratings_filter' - shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F12 ] = 'filter' - shortcuts[ wx.ACCEL_NORMAL ][ wx.WXK_F9 ] = 'new_page' - shortcuts[ wx.ACCEL_NORMAL ][ ord( 'F' ) ] = 'fullscreen_switch' - shortcuts[ wx.ACCEL_SHIFT ][ wx.WXK_F7 ] = 'inbox' - shortcuts[ wx.ACCEL_CTRL ][ ord( 'B' ) ] = 'frame_back' - shortcuts[ wx.ACCEL_CTRL ][ ord( 'N' ) ] = 'frame_next' - shortcuts[ wx.ACCEL_CTRL ][ ord( 'T' ) ] = 'new_page' - shortcuts[ wx.ACCEL_CTRL ][ ord( 'W' ) ] = 'close_page' - shortcuts[ wx.ACCEL_CTRL ][ ord( 'R' ) ] = 'show_hide_splitters' - shortcuts[ wx.ACCEL_CTRL ][ ord( 'S' ) ] = 'set_search_focus' - shortcuts[ wx.ACCEL_CTRL ][ ord( 'M' ) ] = 'set_media_focus' - shortcuts[ wx.ACCEL_CTRL ][ ord( 'I' ) ] = 'synchronised_wait_switch' - - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_UP ] = 'previous' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_LEFT ] = 'previous' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_UP ] = 'previous' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_LEFT ] = 'previous' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_PAGEUP ] = 'previous' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_PAGEUP ] = 'previous' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_DOWN ] = 'next' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_RIGHT ] = 'next' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_DOWN ] = 'next' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_RIGHT ] = 'next' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_PAGEDOWN ] = 'next' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_PAGEDOWN ] = 'next' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_HOME ] = 'first' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_HOME ] = 'first' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_END ] = 'last' - shortcuts[ wx.ACCEL_CTRL ][ wx.WXK_NUMPAD_END ] = 'last' - - shortcuts[ wx.ACCEL_SHIFT ][ wx.WXK_UP ] = 'pan_up' - shortcuts[ wx.ACCEL_SHIFT ][ wx.WXK_DOWN ] = 'pan_down' - shortcuts[ wx.ACCEL_SHIFT ][ wx.WXK_LEFT ] = 'pan_left' - shortcuts[ wx.ACCEL_SHIFT ][ wx.WXK_RIGHT ] = 'pan_right' - - CLIENT_DEFAULT_OPTIONS[ 'shortcuts' ] = shortcuts - - CLIENT_DEFAULT_OPTIONS[ 'confirm_client_exit' ] = False - - CLIENT_DEFAULT_OPTIONS[ 'default_tag_repository' ] = HC.LOCAL_TAG_SERVICE_IDENTIFIER - CLIENT_DEFAULT_OPTIONS[ 'default_tag_sort' ] = CC.SORT_BY_LEXICOGRAPHIC_ASC - - CLIENT_DEFAULT_OPTIONS[ 'pause_repo_sync' ] = False - CLIENT_DEFAULT_OPTIONS[ 'pause_subs_sync' ] = False - - c.execute( 'INSERT INTO options ( options ) VALUES ( ? );', ( CLIENT_DEFAULT_OPTIONS, ) ) - c.execute( 'INSERT INTO contacts ( contact_id, contact_key, public_key, name, host, port ) VALUES ( ?, ?, ?, ?, ?, ? );', ( 1, None, None, 'Anonymous', 'internet', 0 ) ) with open( HC.STATIC_DIR + os.sep + 'contact - hydrus admin.yaml', 'rb' ) as f: hydrus_admin = yaml.safe_load( f.read() ) diff --git a/include/ClientGUI.py b/include/ClientGUI.py index 40ea0259..d894caa2 100755 --- a/include/ClientGUI.py +++ b/include/ClientGUI.py @@ -38,11 +38,11 @@ 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 ) -class FrameGUI( ClientGUICommon.Frame ): +class FrameGUI( ClientGUICommon.FrameThatResizes ): def __init__( self ): - ClientGUICommon.Frame.__init__( self, None, title = HC.app.PrepStringForDisplay( 'Hydrus Client' ) ) + ClientGUICommon.FrameThatResizes.__init__( self, None, resize_option_prefix = 'gui_', title = HC.app.PrepStringForDisplay( 'Hydrus Client' ) ) self.SetDropTarget( ClientGUICommon.FileDropTarget( self.ImportFiles ) ) @@ -56,28 +56,6 @@ class FrameGUI( ClientGUICommon.Frame ): self._statusbar_downloads = '' self._statusbar_db_locked = '' - self.SetIcon( wx.Icon( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', wx.BITMAP_TYPE_ICO ) ) - - min_width = 920 - min_height = 600 - - display_index = wx.Display.GetFromWindow( self ) - - if display_index != wx.NOT_FOUND: - - display = wx.Display( display_index ) - - ( display_width, display_height ) = display.GetGeometry().GetSize() - - initial_width = max( int( display_width * 0.75 ), min_width ) - initial_height = max( int( display_height * 0.75 ), min_height ) - - self.SetInitialSize( ( initial_width, initial_height ) ) - self.SetMinSize( ( 480, 360 ) ) - - - self.Maximize() - self._notebook = wx.Notebook( self ) self._notebook.Bind( wx.EVT_MIDDLE_DOWN, self.EventNotebookMiddleClick ) self._notebook.Bind( wx.EVT_RIGHT_DCLICK, self.EventNotebookMiddleClick ) @@ -901,12 +879,12 @@ The password is cleartext here but obscured in the entry dialog. Enter a blank p page = self._notebook.GetCurrentPage() - if page is not None and self.IsMaximized(): + if page is not None: ( HC.options[ 'hpos' ], HC.options[ 'vpos' ] ) = page.GetSashPositions() - with wx.BusyCursor(): HC.app.Write( 'save_options' ) - + + HC.app.Write( 'save_options' ) for page in [ self._notebook.GetPage( i ) for i in range( self._notebook.GetPageCount() ) ]: @@ -1451,8 +1429,6 @@ class FrameComposeMessage( ClientGUICommon.Frame ): ClientGUICommon.Frame.__init__( self, None, title = HC.app.PrepStringForDisplay( 'Compose Message' ) ) - self.SetIcon( wx.Icon( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', wx.BITMAP_TYPE_ICO ) ) - self.SetInitialSize( ( 920, 600 ) ) vbox = wx.BoxSizer( wx.VERTICAL ) @@ -1530,8 +1506,6 @@ class FramePageChooser( ClientGUICommon.Frame ): self.Center() - self.SetIcon( wx.Icon( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', wx.BITMAP_TYPE_ICO ) ) - self._keycodes_to_ids = {} self._keycodes_to_ids[ wx.WXK_NUMPAD1 ] = 1 @@ -1739,8 +1713,6 @@ class FrameReviewServices( ClientGUICommon.Frame ): ClientGUICommon.Frame.__init__( self, None, title = HC.app.PrepStringForDisplay( 'Review Services' ), pos = pos ) - self.SetIcon( wx.Icon( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', wx.BITMAP_TYPE_ICO ) ) - InitialiseControls() PopulateControls() @@ -2287,8 +2259,6 @@ class FrameSplash( ClientGUICommon.Frame ): wx.Frame.__init__( self, None, style = wx.FRAME_NO_TASKBAR | wx.FRAME_SHAPED, title = 'hydrus client' ) - self.SetIcon( wx.Icon( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', wx.BITMAP_TYPE_ICO ) ) - self._bmp = wx.EmptyBitmap( 154, 220, 32 ) # 32 bit for transparency? self.SetSize( ( 154, 220 ) ) diff --git a/include/ClientGUICanvas.py b/include/ClientGUICanvas.py index dda781ec..692b3f4d 100755 --- a/include/ClientGUICanvas.py +++ b/include/ClientGUICanvas.py @@ -403,6 +403,8 @@ class Canvas(): else: self._DrawBackgroundBitmap() + event.Skip() + def KeepCursorAlive( self ): pass @@ -495,11 +497,11 @@ class CanvasPanel( Canvas, wx.Window ): -class CanvasFullscreenMediaList( ClientGUIMixins.ListeningMediaList, Canvas, ClientGUICommon.Frame ): +class CanvasFullscreenMediaList( ClientGUIMixins.ListeningMediaList, Canvas, ClientGUICommon.FrameThatResizes ): def __init__( self, my_parent, page_key, file_service_identifier, predicates, media_results ): - ClientGUICommon.Frame.__init__( self, my_parent, title = 'hydrus client fullscreen media viewer' ) + ClientGUICommon.FrameThatResizes.__init__( self, my_parent, resize_option_prefix = 'fs_', title = 'hydrus client fullscreen media viewer' ) Canvas.__init__( self, file_service_identifier, HC.app.GetFullscreenImageCache() ) ClientGUIMixins.ListeningMediaList.__init__( self, file_service_identifier, predicates, media_results ) @@ -509,38 +511,14 @@ class CanvasFullscreenMediaList( ClientGUIMixins.ListeningMediaList, Canvas, Cli self._just_started = True - self.SetIcon( wx.Icon( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', wx.BITMAP_TYPE_ICO ) ) - self.SetCursor( wx.StockCursor( wx.CURSOR_BLANK ) ) - min_width = 920 - min_height = 600 + self.Show( True ) - display_index = wx.Display.GetFromWindow( self ) - - if display_index != wx.NOT_FOUND: - - display = wx.Display( display_index ) - - ( display_width, display_height ) = display.GetGeometry().GetSize() - - initial_width = max( int( display_width * 0.75 ), min_width ) - initial_height = max( int( display_height * 0.75 ), min_height ) - - self.SetInitialSize( ( initial_width, initial_height ) ) - self.SetMinSize( ( 480, 360 ) ) - - - if HC.options[ 'fullscreen_borderless' ]: + if self.IsMaximized() and HC.options[ 'fullscreen_borderless' ]: self.ShowFullScreen( True, wx.FULLSCREEN_ALL ) - else: - - self.Maximize() - - self.Show( True ) - HC.app.SetTopWindow( self ) @@ -568,16 +546,8 @@ class CanvasFullscreenMediaList( ClientGUIMixins.ListeningMediaList, Canvas, Cli def _FullscreenSwitch( self ): - if self.IsFullScreen(): - - self.ShowFullScreen( False ) - - self.Maximize() - - else: - - self.ShowFullScreen( True, wx.FULLSCREEN_ALL ) - + if self.IsFullScreen(): self.ShowFullScreen( False ) + else: self.ShowFullScreen( True, wx.FULLSCREEN_ALL ) def _GetCollectionsString( self ): @@ -1007,7 +977,7 @@ class CanvasFullscreenMediaListBrowser( CanvasFullscreenMediaList ): self.Bind( wx.EVT_LEFT_DCLICK, self.EventClose ) self.Bind( wx.EVT_MIDDLE_DOWN, self.EventClose ) self.Bind( wx.EVT_MOUSEWHEEL, self.EventMouseWheel ) - self.Bind( wx.EVT_RIGHT_UP, self.EventShowMenu ) + self.Bind( wx.EVT_RIGHT_DOWN, self.EventShowMenu ) self.Bind( wx.EVT_MENU, self.EventMenu ) @@ -1279,7 +1249,7 @@ class CanvasFullscreenMediaListCustomFilter( CanvasFullscreenMediaList ): self.Bind( wx.EVT_LEFT_DCLICK, self.EventClose ) self.Bind( wx.EVT_MIDDLE_DOWN, self.EventClose ) self.Bind( wx.EVT_MOUSEWHEEL, self.EventMouseWheel ) - self.Bind( wx.EVT_RIGHT_UP, self.EventShowMenu ) + self.Bind( wx.EVT_RIGHT_DOWN, self.EventShowMenu ) self.Bind( wx.EVT_MENU, self.EventMenu ) @@ -2261,7 +2231,7 @@ class RatingsFilterFrameLike( CanvasFullscreenMediaListFilter ): -class RatingsFilterFrameNumerical( ClientGUICommon.Frame ): +class RatingsFilterFrameNumerical( ClientGUICommon.FrameThatResizes ): RATINGS_FILTER_INEQUALITY_FULL = 0 RATINGS_FILTER_INEQUALITY_HALF = 1 @@ -2273,7 +2243,7 @@ class RatingsFilterFrameNumerical( ClientGUICommon.Frame ): def __init__( self, parent, page_key, service_identifier, media_results ): - ClientGUICommon.Frame.__init__( self, parent, title = 'hydrus client ratings frame' ) + ClientGUICommon.FrameThatResizes.__init__( self, parent, resize_option_prefix = 'fs_', title = 'hydrus client ratings frame' ) self._page_key = page_key self._service_identifier = service_identifier @@ -2319,15 +2289,11 @@ class RatingsFilterFrameNumerical( ClientGUICommon.Frame ): self._splitter.SetMinimumPaneSize( 120 ) self._splitter.SetSashGravity( 0.5 ) # stay in the middle - if True: # if borderless fullscreen + self.Show( True ) + + if self.IsMaximized() and HC.options[ 'fullscreen_borderless' ]: - self.ShowFullScreen( True, wx.FULLSCREEN_ALL ^ wx.FULLSCREEN_NOSTATUSBAR ) - - else: - - self.Maximize() - - self.Show( True ) + self.ShowFullScreen( True, wx.FULLSCREEN_ALL ) HC.app.SetTopWindow( self ) @@ -2359,16 +2325,8 @@ class RatingsFilterFrameNumerical( ClientGUICommon.Frame ): def _FullscreenSwitch( self ): - if self.IsFullScreen(): - - self.ShowFullScreen( False ) - - self.Maximize() - - else: - - self.ShowFullScreen( True, wx.FULLSCREEN_ALL ) - + if self.IsFullScreen(): self.ShowFullScreen( False ) + else: self.ShowFullScreen( True, wx.FULLSCREEN_ALL ) def _GoBack( self ): diff --git a/include/ClientGUICommon.py b/include/ClientGUICommon.py index 06d94333..ad16d6a1 100755 --- a/include/ClientGUICommon.py +++ b/include/ClientGUICommon.py @@ -948,7 +948,14 @@ class CheckboxCollect( wx.combo.ComboCtrl ): default = HC.options[ 'default_collect' ] - if default is not None: self.SetCheckedStrings( default ) + if default is not None: + + strings_we_added = { text for ( text, data ) in collect_types } + + strings_to_check = [ s for ( namespace_gumpf, s ) in default if s in strings_we_added ] + + self.SetCheckedStrings( strings_to_check ) + self.Bind( wx.EVT_CHECKLISTBOX, self.EventChanged ) @@ -987,40 +994,6 @@ class CheckboxCollect( wx.combo.ComboCtrl ): -class ChoiceCollect( BetterChoice ): - - def __init__( self, parent, page_key = None, sort_by = None ): - - BetterChoice.__init__( self, parent ) - - self._page_key = page_key - - if sort_by is None: sort_by = HC.options[ 'sort_by' ] - - collect_choices = CC.GenerateCollectByChoices( sort_by ) - - for ( string, data ) in collect_choices: self.Append( string, data ) - - self.SetSelection( HC.options[ 'default_collect' ] ) - - self.Bind( wx.EVT_CHOICE, self.EventChoice ) - - - def EventChoice( self, event ): - - if self._page_key is not None: - - selection = self.GetSelection() - - if selection != wx.NOT_FOUND: - - collect_by = self.GetClientData( selection ) - - HC.pubsub.pub( 'collect_media', self._page_key, collect_by ) - - - - class ChoiceSort( BetterChoice ): def __init__( self, parent, page_key = None, sort_by = None ): @@ -1102,6 +1075,96 @@ class Frame( wx.Frame ): self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) ) + self.SetIcon( wx.Icon( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', wx.BITMAP_TYPE_ICO ) ) + + +class FrameThatResizes( Frame ): + + def __init__( self, *args, **kwargs ): + + self._resize_option_prefix = kwargs[ 'resize_option_prefix' ] + + del kwargs[ 'resize_option_prefix' ] + + Frame.__init__( self, *args, **kwargs ) + + client_size = HC.options[ 'client_size' ] + + self.SetInitialSize( client_size[ self._resize_option_prefix + 'restored_size' ] ) + + self.SetMinSize( ( 480, 360 ) ) + + self._TryToSetPosition() + + if client_size[ self._resize_option_prefix + 'maximised' ]: self.Maximize() + + self.Bind( wx.EVT_SIZE, self.EventSpecialResize ) + self.Bind( wx.EVT_MOVE_END, self.EventSpecialMoveEnd ) + + + def _TryToSetPosition( self ): + + client_size = HC.options[ 'client_size' ] + + position = client_size[ self._resize_option_prefix + 'restored_position' ] + + display_index = wx.Display.GetFromPoint( position ) + + if display_index == wx.NOT_FOUND: client_size[ self._resize_option_prefix + 'restored_position' ] = [ 20, 20 ] + else: + + display = wx.Display( display_index ) + + geometry = display.GetGeometry() + + ( p_x, p_y ) = position + + x_bad = p_x < geometry.x or p_x > geometry.x + geometry.width + y_bad = p_y < geometry.y or p_y > geometry.y + geometry.height + + if x_bad or y_bad: client_size[ self._resize_option_prefix + 'restored_position' ] = [ 20, 20 ] + + + self.SetPosition( client_size[ self._resize_option_prefix + 'restored_position' ] ) + + + def EventSpecialMoveEnd( self, event ): + + client_size = HC.options[ 'client_size' ] + + client_size[ self._resize_option_prefix + 'restored_position' ] = list( self.GetPosition() ) + + event.Skip() + + + def EventSpecialResize( self, event ): + + client_size = HC.options[ 'client_size' ] + + if self.IsMaximized() or self.IsFullScreen(): + + client_size[ self._resize_option_prefix + 'maximised' ] = True + + else: + + if client_size[ self._resize_option_prefix + 'maximised' ]: # we have just restored, so set size + + self.SetSize( client_size[ self._resize_option_prefix + 'restored_size' ] ) + + self._TryToSetPosition() + + else: # we have resized manually, so set new size + + client_size[ self._resize_option_prefix + 'restored_size' ] = list( self.GetSize() ) + + client_size[ self._resize_option_prefix + 'restored_position' ] = list( self.GetPosition() ) + + + client_size[ self._resize_option_prefix + 'maximised' ] = False + + + event.Skip() + class Gauge( wx.Gauge ): @@ -1951,7 +2014,7 @@ class PopupMessage( wx.Window ): wx.Window.__init__( self, parent, style = wx.BORDER_SIMPLE ) - self.Bind( wx.EVT_RIGHT_UP, self.EventDismiss ) + self.Bind( wx.EVT_RIGHT_DOWN, self.EventDismiss ) def EventDismiss( self, event ): @@ -2188,20 +2251,23 @@ class PopupMessageManager( wx.Frame ): if message_type == HC.MESSAGE_TYPE_TEXT: - message_string = info + message_string = HC.u( info ) elif message_type == HC.MESSAGE_TYPE_ERROR: ( etype, value, trace ) = info - message_string = HC.u( etype.__name__ ) + ': ' + HC.u( value ) + os.linesep + trace + message_string = HC.u( etype.__name__ ) + ': ' + HC.u( value ) + os.linesep + HC.u( trace ) elif message_type == HC.MESSAGE_TYPE_FILES: ( message_string, hashes ) = info + message_string = HC.u( message_string ) + - print( message_string ) + try: print( message_string ) + except: print( repr( message_string ) ) def _SizeAndPositionAndShow( self ): @@ -2240,6 +2306,10 @@ class PopupMessageManager( wx.Frame ): HC.ShowException = self._old_show_exception + self.DismissAll() + + self.Hide() + def Dismiss( self, window ): diff --git a/include/ClientGUIDialogsManage.py b/include/ClientGUIDialogsManage.py index 16590566..7e23d5a8 100644 --- a/include/ClientGUIDialogsManage.py +++ b/include/ClientGUIDialogsManage.py @@ -5748,7 +5748,6 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ): self._tag_parents = ClientGUICommon.SaneListCtrl( self, 250, [ ( '', 30 ), ( 'child', 160 ), ( 'parent', -1 ) ] ) self._tag_parents.Bind( wx.EVT_LIST_ITEM_ACTIVATED, self.EventActivated ) - self._tag_parents.Bind( wx.EVT_LIST_ITEM_SELECTED, self.EventItemSelected ) self._tag_parents.Bind( wx.EVT_LIST_ITEM_DESELECTED, self.EventItemSelected ) @@ -5772,6 +5771,8 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ): for ( child, parent ) in pairs: self._tag_parents.Append( ( sign, child, parent ), ( status, child, parent ) ) + self._tag_parents.SortListItems( 2 ) + if tag is not None: self.SetChild( tag ) @@ -6233,6 +6234,10 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ): for ( old, new ) in pairs: self._tag_siblings.Append( ( sign, old, new ), ( status, old, new ) ) + self._tag_siblings.SortListItems( 2 ) + + if tag is not None: self.SetOld( tag ) + def ArrangeControls(): diff --git a/include/ClientGUIManagement.py b/include/ClientGUIManagement.py index 9a5f0d0b..63a6eac8 100755 --- a/include/ClientGUIManagement.py +++ b/include/ClientGUIManagement.py @@ -2605,7 +2605,7 @@ class ManagementPanelPetitions( ManagementPanel ): class ManagementPanelQuery( ManagementPanel ): - def __init__( self, parent, page, page_key, file_service_identifier, initial_predicates = [] ): + def __init__( self, parent, page, page_key, file_service_identifier, show_search = True, initial_predicates = [] ): ManagementPanel.__init__( self, parent, page, page_key, file_service_identifier ) @@ -2614,21 +2614,24 @@ class ManagementPanelQuery( ManagementPanel ): self._include_current_tags = True self._include_pending_tags = True - self._search_panel = ClientGUICommon.StaticBox( self, 'search' ) - - self._current_predicates_box = ClientGUICommon.TagsBoxPredicates( self._search_panel, self._page_key, initial_predicates ) - - self._searchbox = ClientGUICommon.AutoCompleteDropdownTagsRead( self._search_panel, self._page_key, self._file_service_identifier, HC.COMBINED_TAG_SERVICE_IDENTIFIER, self._page.GetMedia ) - - self._search_panel.AddF( self._current_predicates_box, FLAGS_EXPAND_PERPENDICULAR ) - self._search_panel.AddF( self._searchbox, FLAGS_EXPAND_PERPENDICULAR ) + if show_search: + + self._search_panel = ClientGUICommon.StaticBox( self, 'search' ) + + self._current_predicates_box = ClientGUICommon.TagsBoxPredicates( self._search_panel, self._page_key, initial_predicates ) + + self._searchbox = ClientGUICommon.AutoCompleteDropdownTagsRead( self._search_panel, self._page_key, self._file_service_identifier, HC.COMBINED_TAG_SERVICE_IDENTIFIER, self._page.GetMedia ) + + self._search_panel.AddF( self._current_predicates_box, FLAGS_EXPAND_PERPENDICULAR ) + self._search_panel.AddF( self._searchbox, FLAGS_EXPAND_PERPENDICULAR ) + vbox = wx.BoxSizer( wx.VERTICAL ) self._MakeSort( vbox ) self._MakeCollect( vbox ) - vbox.AddF( self._search_panel, FLAGS_EXPAND_PERPENDICULAR ) + if show_search: vbox.AddF( self._search_panel, FLAGS_EXPAND_PERPENDICULAR ) self._MakeCurrentSelectionTagsBox( vbox ) @@ -2785,7 +2788,11 @@ class ManagementPanelQuery( ManagementPanel ): def SetSearchFocus( self, page_key ): - if page_key == self._page_key: self._searchbox.SetFocus() + if page_key == self._page_key: + + try: self._searchbox.SetFocus() # there's a chance this doesn't exist! + except: pass + def ShowQuery( self, query_key, media_results ): diff --git a/include/ClientGUIMedia.py b/include/ClientGUIMedia.py index f72d2898..be82dd2c 100755 --- a/include/ClientGUIMedia.py +++ b/include/ClientGUIMedia.py @@ -219,14 +219,6 @@ class MediaPanel( ClientGUIMixins.ListeningMediaList, wx.ScrolledWindow ): - def _DeselectAll( self ): - - self._DeselectSelect( self._selected_media, [] ) - - self._SetFocussedMedia( None ) - self._shift_focussed_media = None - - def _DeselectSelect( self, media_to_deselect, media_to_select ): if len( media_to_deselect ) > 0: @@ -338,7 +330,12 @@ class MediaPanel( ClientGUIMixins.ListeningMediaList, wx.ScrolledWindow ): if media is None: - if not ctrl and not shift: self._DeselectAll() + if not ctrl and not shift: + + self._Select( 'none' ) + self._SetFocussedMedia( None ) + self._shift_focussed_media = None + else: @@ -553,6 +550,8 @@ class MediaPanel( ClientGUIMixins.ListeningMediaList, wx.ScrolledWindow ): self._RefitCanvas() + self._RedrawCanvas() + self._PublishSelectionChange() HC.pubsub.pub( 'sorted_media_pulse', self._page_key, self.GenerateMediaResults() ) @@ -578,40 +577,61 @@ class MediaPanel( ClientGUIMixins.ListeningMediaList, wx.ScrolledWindow ): - def _ScrollEnd( self ): + def _ScrollEnd( self, shift = False ): if len( self._sorted_media ) > 0: end_media = self._sorted_media[ -1 ] - self._HitMedia( end_media, False, False ) + self._HitMedia( end_media, False, shift ) self._ScrollToMedia( end_media ) - def _ScrollHome( self ): + def _ScrollHome( self, shift = False ): if len( self._sorted_media ) > 0: home_media = self._sorted_media[ 0 ] - self._HitMedia( home_media, False, False ) + self._HitMedia( home_media, False, shift ) self._ScrollToMedia( home_media ) - def _SelectAll( self ): + def _Select( self, select_type ): self._RedrawCanvas() - self._DeselectSelect( [], self._sorted_media ) - - def _SelectNone( self ): - - self._RedrawCanvas() - self._DeselectSelect( self._selected_media, [] ) + if select_type == 'all': self._DeselectSelect( [], self._sorted_media ) + else: + + if select_type == 'none': ( media_to_deselect, media_to_select ) = ( self._selected_media, [] ) + else: + + inbox_media = { m for m in self._sorted_media if m.HasInbox() } + archive_media = { m for m in self._sorted_media if m not in inbox_media } + + if select_type == 'inbox': + + media_to_deselect = [ m for m in archive_media if m in self._selected_media ] + media_to_select = [ m for m in inbox_media if m not in self._selected_media ] + + elif select_type == 'archive': + + media_to_deselect = [ m for m in inbox_media if m in self._selected_media ] + media_to_select = [ m for m in archive_media if m not in self._selected_media ] + + + + if self._focussed_media in media_to_deselect: self._SetFocussedMedia( None ) + + self._DeselectSelect( media_to_deselect, media_to_select ) + + self._shift_focussed_media = None + def _SetFocussedMedia( self, media ): @@ -671,7 +691,7 @@ class MediaPanel( ClientGUIMixins.ListeningMediaList, wx.ScrolledWindow ): if page_key == self._page_key: - self._DeselectAll() + self._Select( 'none' ) ClientGUIMixins.ListeningMediaList.Collect( self, collect_by ) @@ -726,13 +746,6 @@ class MediaPanel( ClientGUIMixins.ListeningMediaList, wx.ScrolledWindow ): affected_media = self._GetMedia( hashes ) - if action == HC.CONTENT_UPDATE_DELETE and service_type in ( HC.FILE_REPOSITORY, HC.LOCAL_FILE ): - - if service_identifier == self._file_service_identifier: self._RedrawCanvas() - - if self._focussed_media in affected_media: self._SetFocussedMedia( None ) - - if len( affected_media ) > 0: self._RedrawMediaIfLoaded( affected_media ) @@ -857,7 +870,7 @@ class MediaPanelThumbnails( MediaPanel ): self._timer_animation = wx.Timer( self, ID_TIMER_ANIMATION ) self._thumbnails_being_faded_in = {} - self._current_y_start = 0 + self._current_y_offset = 0 self._thumbnail_span_dimensions = CC.AddPaddingToDimensions( HC.options[ 'thumbnail_dimensions' ], ( CC.THUMBNAIL_BORDER + CC.THUMBNAIL_MARGIN ) * 2 ) @@ -869,7 +882,7 @@ class MediaPanelThumbnails( MediaPanel ): self.Bind( wx.EVT_SCROLLWIN, self.EventScroll ) self.Bind( wx.EVT_LEFT_DOWN, self.EventSelection ) - self.Bind( wx.EVT_RIGHT_UP, self.EventShowMenu ) + self.Bind( wx.EVT_RIGHT_DOWN, self.EventShowMenu ) self.Bind( wx.EVT_LEFT_DCLICK, self.EventMouseFullScreen ) self.Bind( wx.EVT_MIDDLE_DOWN, self.EventMouseFullScreen ) self.Bind( wx.EVT_PAINT, self.EventPaint ) @@ -905,7 +918,9 @@ class MediaPanelThumbnails( MediaPanel ): ( xUnit, yUnit ) = self.GetScrollPixelsPerUnit() - earliest_y = self._current_y_start * yUnit + y_start = self._GetYStart() + + earliest_y = y_start * yUnit ( my_client_width, my_client_height ) = self.GetClientSize() @@ -936,7 +951,9 @@ class MediaPanelThumbnails( MediaPanel ): ( xUnit, yUnit ) = self.GetScrollPixelsPerUnit() - y_offset = self._current_y_start * yUnit + y_start = self._GetYStart() + + y_offset = y_start * yUnit ( my_client_width, my_client_height ) = self.GetClientSize() @@ -1197,6 +1214,29 @@ class MediaPanelThumbnails( MediaPanel ): return self._sorted_media[ thumbnail_index ] + def _GetYStart( self ): + + ( my_virtual_width, my_virtual_height ) = self.GetVirtualSize() + + ( my_width, my_height ) = self.GetClientSize() + + ( xUnit, yUnit ) = self.GetScrollPixelsPerUnit() + + max_y = ( my_virtual_height - my_height ) / yUnit + + if ( my_virtual_height - my_height ) % yUnit > 0: max_y += 1 + + ( x, y ) = self.GetViewStart() + + y += self._current_y_offset + + y = max( 0, y ) + + y = min( y, max_y ) + + return y + + def _MoveFocussedThumbnail( self, rows, columns, shift ): if self._focussed_media is not None: @@ -1350,7 +1390,7 @@ class MediaPanelThumbnails( MediaPanel ): # accelerator tables can't handle escape key in windows, gg - if event.GetKeyCode() == wx.WXK_ESCAPE: self._DeselectAll() + if event.GetKeyCode() == wx.WXK_ESCAPE: self._Select( 'none' ) else: event.Skip() @@ -1375,7 +1415,6 @@ class MediaPanelThumbnails( MediaPanel ): elif command == 'custom_filter': self._CustomFilter() elif command == 'delete': self._Delete( data ) - elif command == 'deselect': self._DeselectAll() elif command == 'download': HC.app.Write( 'content_updates', { HC.LOCAL_FILE_SERVICE_IDENTIFIER : [ HC.ContentUpdate( HC.CONTENT_DATA_TYPE_FILES, HC.CONTENT_UPDATE_PENDING, self._GetSelectedHashes( CC.DISCRIMINANT_NOT_LOCAL ) ) ] } ) elif command == 'export': self._ExportFiles() elif command == 'filter': self._Filter() @@ -1391,10 +1430,11 @@ class MediaPanelThumbnails( MediaPanel ): elif command == 'remove': self._Remove() elif command == 'rescind_petition': self._RescindPetitionFiles( data ) elif command == 'rescind_upload': self._RescindUploadFiles( data ) - elif command == 'scroll_end': self._ScrollEnd() - elif command == 'scroll_home': self._ScrollHome() - elif command == 'select_all': self._SelectAll() - elif command == 'select_none': self._SelectNone() + elif command == 'scroll_end': self._ScrollEnd( False ) + elif command == 'scroll_home': self._ScrollHome( False ) + elif command == 'shift_scroll_end': self._ScrollEnd( True ) + elif command == 'shift_scroll_home': self._ScrollHome( True ) + elif command == 'select': self._Select( data ) elif command == 'show_selection_in_new_query_page': self._ShowSelectionInNewQueryPage() elif command == 'upload': self._UploadFiles( data ) elif command == 'key_up': self._MoveFocussedThumbnail( -1, 0, False ) @@ -1442,6 +1482,8 @@ class MediaPanelThumbnails( MediaPanel ): self._RefitCanvas() + self.Refresh() # in case of small resizes where a dc isn't created, I think, where we get tiny black lines + def EventSelection( self, event ): @@ -1464,8 +1506,14 @@ class MediaPanelThumbnails( MediaPanel ): menu.AppendSeparator() - menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select_all' ), 'select all' ) - menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select_none' ), 'select none' ) + select_menu = wx.Menu() + + select_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select', 'all' ), 'all' ) + select_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select', 'inbox' ), 'inbox' ) + select_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select', 'archive' ), 'archive' ) + select_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select', 'none' ), 'none' ) + + menu.AppendMenu( CC.ID_NULL, 'select', select_menu ) else: @@ -1766,8 +1814,14 @@ class MediaPanelThumbnails( MediaPanel ): menu.AppendSeparator() - menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select_all' ), 'select all' ) - menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select_none' ), 'select none' ) + select_menu = wx.Menu() + + select_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select', 'all' ), 'all' ) + select_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select', 'inbox' ), 'inbox' ) + select_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select', 'archive' ), 'archive' ) + select_menu.Append( CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select', 'none' ), 'none' ) + + menu.AppendMenu( CC.ID_NULL, 'select', select_menu ) menu.AppendSeparator() @@ -1794,29 +1848,25 @@ class MediaPanelThumbnails( MediaPanel ): # it seems that some scroll events happen after the viewstart has changed, some happen before # so I have to keep track of a manual current_y_start - ( my_virtual_width, my_virtual_height ) = self.GetVirtualSize() - ( my_width, my_height ) = self.GetClientSize() ( xUnit, yUnit ) = self.GetScrollPixelsPerUnit() - max_y_units = ( my_virtual_height - my_height ) / yUnit - - if ( my_virtual_height - my_height ) % yUnit > 0: max_y_units += 1 - page_of_y_units = my_height / yUnit event_type = event.GetEventType() - if event_type == wx.wxEVT_SCROLLWIN_LINEUP: self._current_y_start = max( 0, self._current_y_start - 1 ) - elif event_type == wx.wxEVT_SCROLLWIN_LINEDOWN: self._current_y_start = min( max_y_units, self._current_y_start + 1 ) - elif event_type == wx.wxEVT_SCROLLWIN_THUMBTRACK: self._current_y_start = event.GetPosition() - elif event_type == wx.wxEVT_SCROLLWIN_THUMBRELEASE: self._current_y_start = event.GetPosition() - elif event_type == wx.wxEVT_SCROLLWIN_PAGEUP: self._current_y_start = max( 0, self._current_y_start - page_of_y_units ) - elif event_type == wx.wxEVT_SCROLLWIN_PAGEDOWN: self._current_y_start = min( max_y_units, self._current_y_start + page_of_y_units ) + if event_type == wx.wxEVT_SCROLLWIN_LINEUP: self._current_y_offset = -1 + elif event_type == wx.wxEVT_SCROLLWIN_LINEDOWN: self._current_y_offset = 1 + elif event_type == wx.wxEVT_SCROLLWIN_THUMBTRACK: self._current_y_offset = 0 + elif event_type == wx.wxEVT_SCROLLWIN_THUMBRELEASE: self._current_y_offset = 0 + elif event_type == wx.wxEVT_SCROLLWIN_PAGEUP: self._current_y_offset = - page_of_y_units + elif event_type == wx.wxEVT_SCROLLWIN_PAGEDOWN: self._current_y_offset = page_of_y_units self._RefitCanvas() + self._current_y_offset = 0 + event.Skip() @@ -1921,6 +1971,10 @@ class MediaPanelThumbnails( MediaPanel ): ( wx.ACCEL_NORMAL, wx.WXK_NUMPAD_LEFT, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'key_left' ) ), ( wx.ACCEL_NORMAL, wx.WXK_RIGHT, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'key_right' ) ), ( wx.ACCEL_NORMAL, wx.WXK_NUMPAD_RIGHT, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'key_right' ) ), + ( wx.ACCEL_SHIFT, wx.WXK_HOME, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'shift_scroll_home' ) ), + ( wx.ACCEL_SHIFT, wx.WXK_NUMPAD_HOME, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'shift_scroll_home' ) ), + ( wx.ACCEL_SHIFT, wx.WXK_END, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'shift_scroll_end' ) ), + ( wx.ACCEL_SHIFT, wx.WXK_NUMPAD_END, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'shift_scroll_end' ) ), ( wx.ACCEL_SHIFT, wx.WXK_UP, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'key_shift_up' ) ), ( wx.ACCEL_SHIFT, wx.WXK_NUMPAD_UP, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'key_shift_up' ) ), ( wx.ACCEL_SHIFT, wx.WXK_DOWN, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'key_shift_down' ) ), @@ -1929,7 +1983,7 @@ class MediaPanelThumbnails( MediaPanel ): ( wx.ACCEL_SHIFT, wx.WXK_NUMPAD_LEFT, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'key_shift_left' ) ), ( wx.ACCEL_SHIFT, wx.WXK_RIGHT, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'key_shift_right' ) ), ( wx.ACCEL_SHIFT, wx.WXK_NUMPAD_RIGHT, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'key_shift_right' ) ), - ( wx.ACCEL_CMD, ord( 'A' ), CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select_all' ) ), + ( wx.ACCEL_CMD, ord( 'A' ), CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'select', 'all' ) ), ( wx.ACCEL_CTRL, ord( 'c' ), CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'copy_files' ) ), ( wx.ACCEL_CTRL, wx.WXK_SPACE, CC.MENU_EVENT_ID_TO_ACTION_CACHE.GetId( 'ctrl-space' ) ) ] diff --git a/include/ClientGUIMixins.py b/include/ClientGUIMixins.py index 1f4b1a6f..f8d49c64 100755 --- a/include/ClientGUIMixins.py +++ b/include/ClientGUIMixins.py @@ -296,15 +296,21 @@ class MediaList(): ( sort_by_type, sort_by_data ) = sort_by + def deal_with_none( x ): + + if x == None: return -1 + else: return x + + if sort_by_type == 'system': if sort_by_data == CC.SORT_BY_RANDOM: sort_function = lambda x: random.random() - elif sort_by_data == CC.SORT_BY_SMALLEST: sort_function = lambda x: x.GetSize() - elif sort_by_data == CC.SORT_BY_LARGEST: sort_function = lambda x: -x.GetSize() - elif sort_by_data == CC.SORT_BY_SHORTEST: sort_function = lambda x: x.GetDuration() - elif sort_by_data == CC.SORT_BY_LONGEST: sort_function = lambda x: -x.GetDuration() - elif sort_by_data == CC.SORT_BY_OLDEST: sort_function = lambda x: x.GetTimestamp() - elif sort_by_data == CC.SORT_BY_NEWEST: sort_function = lambda x: -x.GetTimestamp() + elif sort_by_data == CC.SORT_BY_SMALLEST: sort_function = lambda x: deal_with_none( x.GetSize() ) + elif sort_by_data == CC.SORT_BY_LARGEST: sort_function = lambda x: -deal_with_none( x.GetSize() ) + elif sort_by_data == CC.SORT_BY_SHORTEST: sort_function = lambda x: deal_with_none( x.GetDuration() ) + elif sort_by_data == CC.SORT_BY_LONGEST: sort_function = lambda x: -deal_with_none( x.GetDuration() ) + elif sort_by_data == CC.SORT_BY_OLDEST: sort_function = lambda x: deal_with_none( x.GetTimestamp() ) + elif sort_by_data == CC.SORT_BY_NEWEST: sort_function = lambda x: -deal_with_none( x.GetTimestamp() ) elif sort_by_data == CC.SORT_BY_MIME: sort_function = lambda x: x.GetMime() elif sort_by_type == 'namespaces': @@ -314,51 +320,7 @@ class MediaList(): x_tags_manager = x.GetTagsManager() return [ x_tags_manager.GetComparableNamespaceSlice( ( namespace, ) ) for namespace in namespaces ] - ''' - for namespace in sort_by_data: - - x_namespace_slice = x_tags_manager.GetNamespaceSlice( ( namespace, ) ) - y_namespace_slice = y_tags_manager.GetNamespaceSlice( ( namespace, ) ) - - if x_namespace_slice == y_namespace_slice: continue # this covers len == 0 for both, too - else: - - if len( x_namespace_slice ) == 1 and len( y_namespace_slice ) == 1: - - #convert from frozenset to tuple to extract the single member, then get the t from the n:t concat. - x_value = tuple( x_namespace_slice )[0].split( ':', 1 )[1] - y_value = tuple( y_namespace_slice )[0].split( ':', 1 )[1] - - try: return cmp( int( x_value ), int( y_value ) ) - except: return cmp( x_value, y_value ) - - elif len( x_namespace_slice ) == 0: return 1 # I'm sure the 1 and -1 should be the other way around, but that seems to be a wrong thought - elif len( y_namespace_slice ) == 0: return -1 # any membership has precedence over non-membership, right? I'm understanding it wrong, clearly. - else: - - # compare the earliest/smallest/lexicographically-first non-common values - - x_list = list( x_namespace_slice ) - - x_list.sort() - - for x_value in x_list: - - if x_value not in y_namespace_slice: - - x_value = x_value.split( ':', 1 )[1] - y_value = min( y_namespace_slice ).split( ':', 1 )[1] - - try: return cmp( int( x_value ), int( y_value ) ) - except: return cmp( x_value, y_value ) - - - - - - return cmp( x.GetSize(), y.GetSize() ) - ''' sort_function = lambda x: namespace_sort_function( sort_by_data, x ) @@ -370,8 +332,8 @@ class MediaList(): ( x_local_ratings, x_remote_ratings ) = x.GetRatings() - if service_identifier.GetType() in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ): rating = x_local_ratings.GetRating( service_identifier ) - else: rating = x_remote_ratings.GetScore( service_identifier ) + if service_identifier.GetType() in ( HC.LOCAL_RATING_LIKE, HC.LOCAL_RATING_NUMERICAL ): rating = deal_with_none( x_local_ratings.GetRating( service_identifier ) ) + else: rating = deal_with_none( x_remote_ratings.GetScore( service_identifier ) ) if reverse: rating *= -1 diff --git a/include/ClientGUIPages.py b/include/ClientGUIPages.py index b0692bfd..5d1ee1d1 100755 --- a/include/ClientGUIPages.py +++ b/include/ClientGUIPages.py @@ -394,7 +394,12 @@ class PageQuery( PageWithMedia ): PageWithMedia.__init__( self, parent, file_service_identifier ) - def _InitManagementPanel( self ): self._management_panel = ClientGUIManagement.ManagementPanelQuery( self._search_preview_split, self, self._page_key, self._file_service_identifier, initial_predicates = self._initial_predicates ) + def _InitManagementPanel( self ): + + show_search = self._initial_media_results == [] + + self._management_panel = ClientGUIManagement.ManagementPanelQuery( self._search_preview_split, self, self._page_key, self._file_service_identifier, show_search = show_search, initial_predicates = self._initial_predicates ) + def _InitMediaPanel( self ): diff --git a/include/ClientParsers.py b/include/ClientParsers.py index ac8740a5..04261072 100755 --- a/include/ClientParsers.py +++ b/include/ClientParsers.py @@ -12,7 +12,7 @@ def Parse4chanPostScreen( html ): if title_tag.string == 'Post successful!': return ( 'success', None ) elif title_tag.string == '4chan - Banned': - print( soup ) + print( repr( soup ) ) message = 'You are banned from this board! html written to log.' @@ -28,7 +28,7 @@ def Parse4chanPostScreen( html ): if problem_tag is None: - try: print( soup ) + try: print( repr( soup ) ) except: pass message = 'Unknown problem; html written to log.' diff --git a/include/HydrusConstants.py b/include/HydrusConstants.py index 97d16459..6132431a 100755 --- a/include/HydrusConstants.py +++ b/include/HydrusConstants.py @@ -1,4 +1,5 @@ import bisect +import bs4 import collections import httplib import HydrusExceptions @@ -37,7 +38,7 @@ TEMP_DIR = BASE_DIR + os.path.sep + 'temp' # Misc NETWORK_VERSION = 10 -SOFTWARE_VERSION = 82 +SOFTWARE_VERSION = 83 UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 ) @@ -974,9 +975,9 @@ def ShowExceptionDefault( e ): trace = ''.join( trace_list ) - message = u( etype.__name__ ) + ': ' + value + os.linesep + trace + message = u( etype.__name__ ) + ': ' + u( value ) + os.linesep + u( trace ) - print( message ) + print( repr( message ) ) ShowException = ShowExceptionDefault @@ -1043,7 +1044,7 @@ def MergeKeyToListDicts( key_to_list_dicts ): def u( text_producing_object ): - if type( text_producing_object ) in ( str, unicode ): text = text_producing_object + if type( text_producing_object ) in ( str, unicode, bs4.element.NavigableString ): text = text_producing_object else: text = str( text_producing_object ) # dealing with exceptions, etc... try: return unicode( text ) @@ -2568,13 +2569,7 @@ class SortedList(): def remove_items( self, items ): - try: deletee_indices = [ self.index( item ) for item in items ] - except: - - print( self._items_to_indices ) - - raise - + deletee_indices = [ self.index( item ) for item in items ] deletee_indices.sort() diff --git a/include/HydrusServer.py b/include/HydrusServer.py index cd1628c6..00307dad 100755 --- a/include/HydrusServer.py +++ b/include/HydrusServer.py @@ -625,7 +625,7 @@ class HydrusHTTPRequestHandler( BaseHTTPServer.BaseHTTPRequestHandler ): def log_request( self, *args ): pass - def log_string( self, message ): print( message ) + def log_string( self, message ): print( repr( message ) ) # this overrides the base method to no longer use the class variable server_version def version_string( self ): diff --git a/include/ServerController.py b/include/ServerController.py index da695027..04f95448 100755 --- a/include/ServerController.py +++ b/include/ServerController.py @@ -111,6 +111,6 @@ class TaskBarIcon( wx.TaskBarIcon ): self._tbmenu.Append( wx.ID_EXIT, 'exit' ) - self.Bind( wx.EVT_TASKBAR_RIGHT_UP, lambda event: self.PopupMenu( self._tbmenu ) ) + self.Bind( wx.EVT_TASKBAR_RIGHT_DOWN, lambda event: self.PopupMenu( self._tbmenu ) ) \ No newline at end of file diff --git a/include/TestHydrusDownloading.py b/include/TestHydrusDownloading.py index 6584f040..56b67add 100644 --- a/include/TestHydrusDownloading.py +++ b/include/TestHydrusDownloading.py @@ -107,4 +107,101 @@ class TestDownloaders( unittest.TestCase ): HC.get_connection = HC.AdvancedHTTPConnection + + def test_hentai_foundry( self ): + + with open( HC.STATIC_DIR + os.path.sep + 'testing' + os.path.sep + 'hf_picture_gallery.html' ) as f: picture_gallery = f.read() + with open( HC.STATIC_DIR + os.path.sep + 'testing' + os.path.sep + 'hf_scrap_gallery.html' ) as f: scrap_gallery = f.read() + with open( HC.STATIC_DIR + os.path.sep + 'testing' + os.path.sep + 'hf_picture_page.html' ) as f: picture_page = f.read() + with open( HC.STATIC_DIR + os.path.sep + 'testing' + os.path.sep + 'hf_scrap_page.html' ) as f: scrap_page = f.read() + + fake_connection = TestConstants.FakeHTTPConnection( host = 'www.hentai-foundry.com' ) + + # what about page/1 or whatever? + + fake_connection.SetResponse( 'GET', '/pictures/user/Sparrow/page/1', picture_gallery ) + fake_connection.SetResponse( 'GET', '/pictures/user/Sparrow/scraps/page/1', scrap_gallery ) + fake_connection.SetResponse( 'GET', '/pictures/user/Sparrow/226304/Ashantae', picture_page ) + fake_connection.SetResponse( 'GET', '/pictures/user/Sparrow/226084/Swegabe-Sketches--Gabrielle-027', scrap_page ) + + TestConstants.fake_http_connection_manager.SetConnection( fake_connection, host = 'www.hentai-foundry.com' ) + + HC.get_connection = TestConstants.fake_http_connection_manager.GetConnection + + cookies = { 'YII_CSRF_TOKEN' : '19b05b536885ec60b8b37650a32f8deb11c08cd1s%3A40%3A%222917dcfbfbf2eda2c1fbe43f4d4c4ec4b6902b32%22%3B' } + + HC.app.SetWebCookies( 'hentai foundry', cookies ) + + # + + info = {} + + info[ 'rating_nudity' ] = 3 + info[ 'rating_violence' ] = 3 + info[ 'rating_profanity' ] = 3 + info[ 'rating_racism' ] = 3 + info[ 'rating_sex' ] = 3 + info[ 'rating_spoilers' ] = 3 + + 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 + info[ 'rating_beast' ] = 1 + info[ 'rating_male' ] = 1 + info[ 'rating_female' ] = 1 + info[ 'rating_futa' ] = 1 + info[ 'rating_other' ] = 1 + + info[ 'filter_media' ] = 'A' + info[ 'filter_order' ] = 0 + info[ 'filter_type' ] = 0 + + pictures_downloader = HydrusDownloading.GetDownloader( HC.SITE_DOWNLOAD_TYPE_HENTAI_FOUNDRY, 'artist pictures', 'Sparrow', info ) + scraps_downloader = HydrusDownloading.GetDownloader( HC.SITE_DOWNLOAD_TYPE_HENTAI_FOUNDRY, 'artist scraps', 'Sparrow', info ) + + # + + gallery_urls = pictures_downloader.GetAnotherPage() + + expected_gallery_urls = [('http://www.hentai-foundry.com/pictures/user/Sparrow/226304/Ashantae',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/225935/Coco-VS-Admiral-Swiggins',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/225472/Poon-Cellar',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/225063/Goal-Tending',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/223991/Coco-VS-StarStorm',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/221783/Gala-Event',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/221379/Linda-Rinda',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/220615/Farahs-Day-Off--27',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/219856/Viewing-Room-Workout',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/219284/Farahs-Day-Off--26',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/218886/Nyaow-Streaming',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/218035/Farahs-Day-Off--25',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/216981/A-Vivi-draws-near.-Command',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/216642/Farahs-Day-Off--24',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/215266/Farahs-Day-Off--23',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/213132/Relative-Risk',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/212665/Farahs-Day-Off--21',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/212282/Sticky-Sheva-Situation',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/211269/Farahs-Day-Off-20-2',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/211268/Farahs-Day-Off-20-1',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/211038/Newcomers',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/209967/Farahs-Day-Off-19',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/209292/The-New-Adventures-of-Helena-Lovelace-01',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/208609/Farahs-Day-Off--18',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/207979/Wonderful-Backlit-Foreign-Boyfriend-Experience',)] + + self.assertEqual( gallery_urls, expected_gallery_urls ) + + gallery_urls = scraps_downloader.GetAnotherPage() + + expected_gallery_urls = [('http://www.hentai-foundry.com/pictures/user/Sparrow/226084/Swegabe-Sketches--Gabrielle-027',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/224103/Make-Trade',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/220618/Swegabe-Sketches--Gabrielle-020',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/216451/Bigger-Dipper',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/213985/Swegabe-Sketches--Gabrielle-008',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/211271/Swegabe-Sketches--Gabrielle-003',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/210311/Himari-Says-Hi',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/209971/Swegabe-Sketches--Gabrielle-002',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/209970/Swegabe-Sketches--Gabrielle-001',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/204463/Minobred-Overkill',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/203723/Single-File-Please',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/202593/Kneel-O-April',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/201296/McPie-2',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/195882/HANDLED',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/184275/Relative-Frequency',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/183458/Coco-VS-Voltar',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/183085/Coco-VS-Froggy-G',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/181508/Mystra-Meets-Mister-18',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/180699/Tunnel-Trouble',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/177549/Coco-VS-Leon',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/175824/The-Ladies-Boyle',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/168744/Coco-VS-Yuri',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/166167/VVVVViewtiful',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/165429/Walled',), ('http://www.hentai-foundry.com/pictures/user/Sparrow/164936/Coco-VS-Lonestar',)] + + self.assertEqual( gallery_urls, expected_gallery_urls ) + + # + + fake_connection = TestConstants.FakeHTTPConnection( host = 'pictures.hentai-foundry.com' ) + + fake_connection.SetResponse( 'GET', '//s/Sparrow/226304.jpg', 'picture' ) + fake_connection.SetResponse( 'GET', '//s/Sparrow/226084.jpg', 'scrap' ) + + TestConstants.fake_http_connection_manager.SetConnection( fake_connection, host = 'pictures.hentai-foundry.com' ) + + # ask for specific url + + info = pictures_downloader.GetFileAndTags( 'http://www.hentai-foundry.com/pictures/user/Sparrow/226304/Ashantae' ) + + expected_info = ('picture', [u'creator:Sparrow', u'title:Ashantae!', u'Shantae', u'Asha', u'Monster_World', u'cosplay', u'nips']) + + self.assertEqual( info, expected_info ) + + info = scraps_downloader.GetFileAndTags( 'http://www.hentai-foundry.com/pictures/user/Sparrow/226084/Swegabe-Sketches--Gabrielle-027' ) + + expected_info = ('scrap', [u'creator:Sparrow', u'title:Swegabe Sketches \u2013 Gabrielle 027', u'bukkake', u'horsecock', u'gokkun', u'prom_night']) + + self.assertEqual( info, expected_info ) + + # + + HC.get_connection = HC.AdvancedHTTPConnection + \ No newline at end of file diff --git a/static/testing/hf_picture_gallery.html b/static/testing/hf_picture_gallery.html new file mode 100644 index 00000000..a0afef7b --- /dev/null +++ b/static/testing/hf_picture_gallery.html @@ -0,0 +1,347 @@ + + + + + + + + + + + + + + + + + +
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/testing/hf_picture_page.html b/static/testing/hf_picture_page.html
new file mode 100644
index 00000000..91833553
--- /dev/null
+++ b/static/testing/hf_picture_page.html
@@ -0,0 +1,400 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+ |||||
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+ |||||
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+ |||||
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+ |||||
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/testing/hf_scrap_gallery.html b/static/testing/hf_scrap_gallery.html
new file mode 100644
index 00000000..c296b813
--- /dev/null
+++ b/static/testing/hf_scrap_gallery.html
@@ -0,0 +1,346 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Ashantae! by Sparrow
+
+
+
+
+Description
+
+
+
+General Info
+
+
+
+
+
Ratings | Comments | 17 | |
Category | +Games | Media | Digital drawing or painting |
Date Submitted | August 31, 2013 7:08:03 PM | Time Taken | 2-3h |
Views | 8431 | Reference | Nope |
Favorites... | 197 | Keywords | Shantae, Asha, Monster_World, cosplay, nips |
Vote Score | 100 | License | Berne Convention |
+
+
+
+
+
+You are not authorized to comment here. Your must be registered and logged in to comment
+
+
+AlCiao on September 3, 2013 8:09:35 AM
+
+
+
+
+
+
+Pordama on September 3, 2013 7:26:28 AM
+
+
+
+
+masterZ on September 1, 2013 12:20:13 PM
+
+
+
+
+Bandichar on August 31, 2013 8:04:31 PM
+
+
+
+
+notmenotyou on August 31, 2013 7:09:55 PM
+
+
+
+
+darkminou on August 31, 2013 12:09:52 PM
+
+
+
+
+Sparrow on August 31, 2013 1:06:59 PM
+
+
+
+
+darkminou on August 31, 2013 4:18:32 PM
+
+
+
+
+Doorman on August 31, 2013 3:41:30 PM
+
+
+
+
+TheReigndeer on August 31, 2013 12:31:52 PM
+
+
+
+
+Sparrow on August 31, 2013 1:07:53 PM
+
+
+
+
+TheRedDakkar on August 31, 2013 1:00:06 PM
+
+
+
+
+Batanen on August 31, 2013 12:52:02 PM
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/testing/hf_scrap_page.html b/static/testing/hf_scrap_page.html
new file mode 100644
index 00000000..3e092b4c
--- /dev/null
+++ b/static/testing/hf_scrap_page.html
@@ -0,0 +1,402 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+ |||||
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+ |||||
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+ |||||
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+ |||||
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test.py b/test.py
index 5e957d63..724a83fa 100644
--- a/test.py
+++ b/test.py
@@ -31,6 +31,8 @@ class App( wx.App ):
self._tag_parents_manager = HydrusTags.TagParentsManager()
self._tag_siblings_manager = HydrusTags.TagSiblingsManager()
+ self._cookies = {}
+
suites = []
suites.append( unittest.TestLoader().loadTestsFromModule( TestClientConstants ) )
@@ -53,6 +55,8 @@ class App( wx.App ):
def GetTagParentsManager( self ): return self._tag_parents_manager
def GetTagSiblingsManager( self ): return self._tag_siblings_manager
+ def GetWebCookies( self, name ): return self._cookies[ name ]
+
def GetWrite( self, name ):
write = self._writes[ name ]
@@ -68,6 +72,8 @@ class App( wx.App ):
def SetRead( self, name, value ): self._reads[ name ] = value
+ def SetWebCookies( self, name, value ): self._cookies[ name ] = value
+
def Write( self, name, *args, **kwargs ):
self._writes[ name ].append( ( args, kwargs ) )
+
+
+
+
+Swegabe Sketches – Gabrielle 027 by Sparrow
+
+
+
+
+Description
+
+
+
+General Info
+
+
+
+
+
Ratings | Comments | 16 | |
Category | +Original » Bukkake | Media | Digital drawing or painting |
Date Submitted | August 30, 2013 2:25:31 PM | Time Taken | Yes |
Views | 7929 | Reference | No |
Favorites... | 113 | Keywords | bukkake, horsecock, gokkun, prom_night |
Vote Score | 45 | License | Berne Convention |
+
+
+
+
+
+You are not authorized to comment here. Your must be registered and logged in to comment
+
+
+
+Marienne on September 1, 2013 8:59:26 PM
+
+
+
+
+Sparrow on September 2, 2013 9:33:42 AM
+
+
+
+
+earwig on August 30, 2013 9:23:52 AM
+
+
+
+
+Swegabe on August 30, 2013 10:19:06 AM
+
+
+
+
+Marienne on September 1, 2013 9:00:24 PM
+
+
+
+
+Swegabe on September 2, 2013 3:33:54 AM
+
+
+
+
+Elderickan on August 30, 2013 5:23:57 PM
+
+
+
+
+earwig on August 30, 2013 10:30:05 AM
+
+
+
+
+Swegabe on August 30, 2013 10:39:21 AM
+
+
+
+
+ashraam on August 30, 2013 10:40:38 AM
+
+
+
+
+KingofKings4life on August 30, 2013 4:51:30 PM
+
+
+
+ashraam on August 30, 2013 10:33:48 AM
+
+
+Quote picture of series #24: "One is going to jam his cock in her slutty mouth, another will stuff her greedy pussy way past her cervix, another will stretch out her ass widening her hips permanently and the last one will just jerk off in one of the mugs and the picture following that will be her drinking from said cum filled mug or multiple mugs."
+
+Apparently I've been tainted by sparrow's work soo much I can nearly predict his ideas.
+
+SPARROW!! WHAT HAVE YOU DONE TO ME!!!???
+
+Apparently I've been tainted by sparrow's work soo much I can nearly predict his ideas.
+
+SPARROW!! WHAT HAVE YOU DONE TO ME!!!???
+
+
+
+Sparrow on August 30, 2013 2:48:17 PM
+
+
+
+
+Melkhiordarkblade on August 30, 2013 9:34:00 AM
+
+
+
+
+Swegabe on August 30, 2013 8:33:19 AM
+
+
+Seriously, though, great job. I like the similarity to enchante too; that's actually the first thing that came to my head once I saw the title of this piece. Voted.