Version 232

This commit is contained in:
Hydrus Network Developer 2016-11-16 14:21:43 -06:00
parent ab1bd6d208
commit 0341eff594
26 changed files with 5657 additions and 4648 deletions

View File

@ -8,6 +8,38 @@
<div class="content">
<h3>changelog</h3>
<ul>
<li><h3>version 232</h3></li>
<ul>
<li>finished png object sharing system, created some gui for it</li>
<li>switched the top half of the new png export to wx code, which can render with prettier system fonts</li>
<li>you can now 'export' parsing scripts to png from manage parsing scripts for easy sharing</li>
<li>you can also 'import' parsing script pngs in the same dialog!</li>
<li>the file lookup tag suggestions panel now listens for media update events (so if you browse the media viewer with manage tags open, it'll keep up with the current file)</li>
<li>added a 'add all' button to quickly add all tags from file lookup suggested tags panel</li>
<li>created a 'script management' control for file lookup suggested tags panel and added some script text status updates for it (progress gauge doesn't do anything yet)</li>
<li>added basic 'cancel' support to script management control--mid-http cancel support will come with the gauges</li>
<li>added url support to thread notification objects, and hooked script code into it</li>
<li>added a url menu button to script management control, so you can launch any parsed urls the script found in your browser</li>
<li>html parsing formulas now support positive and negative front and end character culling</li>
<li>html parsing formulas now support prepending and appending arbitrary text</li>
<li>added some more pretty text to the html formula summary to explain when they do the new culling or adding</li>
<li>script 404s and other network errors are now caught neatly and reported</li>
<li>link node 404s and other network errors are now reported and recovered from</li>
<li>fixed favourite file lookup script selection on initialisation</li>
<li>fixed parsing veto test to include membership of search string in result, not just exact match</li>
<li>fixed html formula tag attribute fetching</li>
<li>updated os x release to python 2.7.10</li>
<li>fixed os x about window</li>
<li>fixed v231 linux release image rendering crash</li>
<li>updated linux release to wx 3.0.2.0</li>
<li>updated linux release to opencv 3.1.0</li>
<li>fixed rendering for 16-bit-per-channel images</li>
<li>fixed a potential out-of-order settagboxfocus event issue on panel initialisation</li>
<li>created a 'menubutton' control and switched existing code over</li>
<li>created some 'nullipotent panel' dialog code to handle some new stuff</li>
<li>misc fixes</li>
<li>bunch of misc refactoring</li>
</ul>
<li><h3>version 231</h3></li>
<ul>
<li>added file lookup scripts suggested tags control and appropriate options panel</li>

View File

@ -11,17 +11,13 @@
<h3>what you will need</h3>
<p>You will need to install python 2.7 and a number of python modules. Most of it you can get through pip. I think this will do for most systems:</p>
<ul>
<li>(sudo) pip install beautifulsoup4 hsaudiotag lxml lz4 mp3play nose numpy pafy Pillow psutil pycrypto PyPDF2 PySocks python-potr PyYAML requests Send2Trash twisted</li>
<li>pip install beautifulsoup4 hsaudiotag lxml lz4 nose numpy pafy Pillow psutil pycrypto PyPDF2 PySocks python-potr PyYAML requests Send2Trash twisted</li>
</ul>
<p>Although you may want to do them one at a time, or in a different order. Your specific system may also need some of them from different sources and will need some complicated things installed separately. The best way to figure it out is just to keep running client.pyw and see what it complains about missing.</p>
<p>I use Ubuntu 14.04, which also requires something like:</p>
<p>I use Ubuntu 16.10, which also requires something like:</p>
<p><ul>
<li>sudo apt-get install python2.7-dev</li>
<li>sudo apt-get install python-numpy</li>
<li>sudo apt-get install python-opencv</li>
<li>sudo apt-get install python-wxversion</li>
<li>and installing a recent version of wxPython, which is difficult and best done <a href="http://wiki.wxpython.org/CheckInstall">like so</a></li>
<li>If you install wxPython as above and you get missing .so errors, try running 'sudo ldconfig'.</li>
<li>sudo apt-get install python-wxgtk3.0</li>
</ul></p>
<p>YMMV. Feel free to email me if you run into trouble or discover any neat tricks.</p>
<p>OS X 10.9 is similar, though wx is simpler. I use the cocoa package <a href="http://wxpython.org/download.php#osx">straight from wxPython's site</a>. This should do the rest:</p>

View File

@ -13,7 +13,8 @@ import ClientGUIManagement
import ClientGUIMenus
import ClientGUIPages
import ClientGUIParsing
import ClientGUIScrolledPanels
import ClientGUIScrolledPanelsManagement
import ClientGUIScrolledPanelsReview
import ClientGUITopLevelWindows
import ClientDownloading
import ClientMedia
@ -372,6 +373,8 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
time.sleep( 5 )
HydrusData.ShowText( u'Creating admin service\u2026' )
admin_service_key = HydrusData.GenerateKey()
@ -397,8 +400,6 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
self._controller.WriteSynchronous( 'update_services', edit_log )
time.sleep( 2 )
HydrusData.ShowText( 'Admin service initialised.' )
wx.CallAfter( ClientGUIFrames.ShowKeys, 'access', ( access_key, ) )
@ -407,6 +408,8 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
#
time.sleep( 5 )
HydrusData.ShowText( u'Creating tag and file services\u2026' )
tag_options = HC.DEFAULT_OPTIONS[ HC.TAG_REPOSITORY ]
@ -1372,7 +1375,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
with ClientGUITopLevelWindows.DialogManage( self, title, frame_key ) as dlg:
panel = ClientGUIScrolledPanels.ManageOptionsPanel( dlg )
panel = ClientGUIScrolledPanelsManagement.ManageOptionsPanel( dlg )
dlg.SetPanel( panel )
@ -1782,7 +1785,7 @@ class FrameGUI( ClientGUITopLevelWindows.FrameThatResizes ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, self._controller.PrepStringForDisplay( 'Review Services' ), 'review_services' )
panel = ClientGUIScrolledPanels.ReviewServices( frame, self._controller )
panel = ClientGUIScrolledPanelsReview.ReviewServicesPanel( frame, self._controller )
frame.SetPanel( panel )

View File

@ -7,7 +7,7 @@ import ClientGUICommon
import ClientGUIDialogs
import ClientGUIDialogsManage
import ClientGUIHoverFrames
import ClientGUIScrolledPanels
import ClientGUIScrolledPanelsManagement
import ClientGUITopLevelWindows
import ClientMedia
import ClientRatings
@ -26,7 +26,6 @@ import time
import traceback
import urllib
import wx
import wx.media
import ClientRendering
import HydrusData
import HydrusGlobals
@ -1189,7 +1188,7 @@ class Canvas( wx.Window ):
manage_tags = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, title, frame_key )
panel = ClientGUIScrolledPanels.ManageTagsPanel( manage_tags, self._file_service_key, ( self._current_display_media, ), immediate_commit = True, canvas_key = self._canvas_key )
panel = ClientGUIScrolledPanelsManagement.ManageTagsPanel( manage_tags, self._file_service_key, ( self._current_display_media, ), immediate_commit = True, canvas_key = self._canvas_key )
manage_tags.SetPanel( panel )

View File

@ -3,6 +3,7 @@ import HydrusConstants as HC
import ClientCaches
import ClientData
import ClientConstants as CC
import ClientGUIMenus
import ClientRatings
import itertools
import os
@ -3179,6 +3180,27 @@ class ListCtrlAutoWidth( wx.ListCtrl, ListCtrlAutoWidthMixin ):
for index in indices: self.DeleteItem( index )
class MenuButton( BetterButton ):
def __init__( self, parent, label, menu_items ):
BetterButton.__init__( self, parent, label, self.DoMenu )
self._menu_items = menu_items
def DoMenu( self ):
menu = wx.Menu()
for ( title, description, callable ) in self._menu_items:
ClientGUIMenus.AppendMenuItem( menu, title, description, self, callable )
HydrusGlobals.client_controller.PopupMenu( self, menu )
class NoneableSpinCtrl( wx.Panel ):
def __init__( self, parent, message, none_phrase = 'no limit', min = 0, max = 1000000, unit = None, multiplier = 1, num_dimensions = 1 ):

View File

@ -11,7 +11,7 @@ import ClientGUIDialogs
import ClientDownloading
import ClientGUIOptionsPanels
import ClientGUIPredicates
import ClientGUIScrolledPanels
import ClientGUIScrolledPanelsEdit
import ClientGUITopLevelWindows
import ClientImporting
import ClientMedia
@ -3063,7 +3063,7 @@ class DialogManageImportFoldersEdit( ClientGUIDialogs.Dialog ):
with ClientGUITopLevelWindows.DialogEdit( self, 'file import status' ) as dlg:
panel = ClientGUIScrolledPanels.EditSeedCachePanel( dlg, HydrusGlobals.client_controller, dupe_seed_cache )
panel = ClientGUIScrolledPanelsEdit.EditSeedCachePanel( dlg, HydrusGlobals.client_controller, dupe_seed_cache )
dlg.SetPanel( panel )
@ -5561,7 +5561,7 @@ class DialogManageSubscriptions( ClientGUIDialogs.Dialog ):
with ClientGUITopLevelWindows.DialogEdit( self, 'file import status' ) as dlg:
panel = ClientGUIScrolledPanels.EditSeedCachePanel( dlg, HydrusGlobals.client_controller, dupe_seed_cache )
panel = ClientGUIScrolledPanelsEdit.EditSeedCachePanel( dlg, HydrusGlobals.client_controller, dupe_seed_cache )
dlg.SetPanel( panel )
@ -5677,7 +5677,10 @@ class DialogManageTagCensorship( ClientGUIDialogs.Dialog ):
page = self._tag_services.GetCurrentPage()
page.SetTagBoxFocus()
if page is not None:
page.SetTagBoxFocus()
def EventOK( self, event ):
@ -5695,7 +5698,10 @@ class DialogManageTagCensorship( ClientGUIDialogs.Dialog ):
page = self._tag_services.GetCurrentPage()
wx.CallAfter( page.SetTagBoxFocus )
if page is not None:
wx.CallAfter( page.SetTagBoxFocus )
class _Panel( wx.Panel ):
@ -5846,7 +5852,10 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
page = self._tag_repositories.GetCurrentPage()
page.SetTagBoxFocus()
if page is not None:
page.SetTagBoxFocus()
def EventMenu( self, event ):
@ -5884,7 +5893,10 @@ class DialogManageTagParents( ClientGUIDialogs.Dialog ):
page = self._tag_repositories.GetCurrentPage()
wx.CallAfter( page.SetTagBoxFocus )
if page is not None:
wx.CallAfter( page.SetTagBoxFocus )
class _Panel( wx.Panel ):
@ -6432,7 +6444,10 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
page = self._tag_repositories.GetCurrentPage()
page.SetTagBoxFocus()
if page is not None:
page.SetTagBoxFocus()
def EventMenu( self, event ):
@ -6470,7 +6485,10 @@ class DialogManageTagSiblings( ClientGUIDialogs.Dialog ):
page = self._tag_repositories.GetCurrentPage()
wx.CallAfter( page.SetTagBoxFocus )
if page is not None:
wx.CallAfter( page.SetTagBoxFocus )
class _Panel( wx.Panel ):

View File

@ -15,7 +15,7 @@ import ClientGUICollapsible
import ClientGUICommon
import ClientGUIDialogs
import ClientGUIMedia
import ClientGUIScrolledPanels
import ClientGUIScrolledPanelsEdit
import ClientGUITopLevelWindows
import ClientImporting
import ClientMedia
@ -1844,7 +1844,7 @@ class ManagementPanelGalleryImport( ManagementPanel ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, title, frame_key )
panel = ClientGUIScrolledPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
panel = ClientGUIScrolledPanelsEdit.EditSeedCachePanel( frame, self._controller, seed_cache )
frame.SetPanel( panel )
@ -1992,7 +1992,7 @@ class ManagementPanelHDDImport( ManagementPanel ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, title, frame_key )
panel = ClientGUIScrolledPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
panel = ClientGUIScrolledPanelsEdit.EditSeedCachePanel( frame, self._controller, seed_cache )
frame.SetPanel( panel )
@ -2369,7 +2369,7 @@ class ManagementPanelPageOfImagesImport( ManagementPanel ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, title, frame_key )
panel = ClientGUIScrolledPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
panel = ClientGUIScrolledPanelsEdit.EditSeedCachePanel( frame, self._controller, seed_cache )
frame.SetPanel( panel )
@ -3170,7 +3170,7 @@ class ManagementPanelThreadWatcherImport( ManagementPanel ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, title, frame_key )
panel = ClientGUIScrolledPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
panel = ClientGUIScrolledPanelsEdit.EditSeedCachePanel( frame, self._controller, seed_cache )
frame.SetPanel( panel )
@ -3448,7 +3448,7 @@ class ManagementPanelURLsImport( ManagementPanel ):
frame = ClientGUITopLevelWindows.FrameThatTakesScrollablePanel( self, title, frame_key )
panel = ClientGUIScrolledPanels.EditSeedCachePanel( frame, self._controller, seed_cache )
panel = ClientGUIScrolledPanelsEdit.EditSeedCachePanel( frame, self._controller, seed_cache )
frame.SetPanel( panel )

View File

@ -7,7 +7,7 @@ import ClientGUICommon
import ClientGUIDialogs
import ClientGUIDialogsManage
import ClientGUICanvas
import ClientGUIScrolledPanels
import ClientGUIScrolledPanelsManagement
import ClientGUITopLevelWindows
import ClientMedia
import collections
@ -799,7 +799,7 @@ class MediaPanel( ClientMedia.ListeningMediaList, wx.ScrolledWindow ):
with ClientGUITopLevelWindows.DialogManage( self, title, frame_key ) as dlg:
panel = ClientGUIScrolledPanels.ManageTagsPanel( dlg, self._file_service_key, self._selected_media )
panel = ClientGUIScrolledPanelsManagement.ManageTagsPanel( dlg, self._file_service_key, self._selected_media )
dlg.SetPanel( panel )

View File

@ -7,12 +7,16 @@ menus_to_menu_item_data = collections.defaultdict( set )
def AppendMenu( menu, submenu, label ):
label.replace( '&', '&&' )
menu.AppendMenu( CC.ID_NULL, label, submenu )
menus_to_submenus[ menu ].add( submenu )
def AppendMenuItem( menu, label, description, event_handler, callable, *args, **kwargs ):
label.replace( '&', '&&' )
menu_item = menu.Append( wx.ID_ANY, label, description )
l_callable = GetLambdaCallable( callable, *args, **kwargs )

View File

@ -3,17 +3,24 @@ import ClientGUICommon
import ClientGUIDialogs
import ClientGUIMenus
import ClientGUIScrolledPanels
import ClientGUISerialisable
import ClientGUITopLevelWindows
import ClientNetworking
import ClientParsing
import ClientSerialisable
import ClientThreading
import HydrusConstants as HC
import HydrusData
import HydrusGlobals
import HydrusSerialisable
import HydrusTags
import os
import threading
import webbrowser
import wx
ID_TIMER_SCRIPT_UPDATE = wx.NewId()
class EditHTMLTagRulePanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, rule ):
@ -91,6 +98,12 @@ class EditHTMLFormulaPanel( ClientGUIScrolledPanels.EditPanel ):
self._content_rule = wx.TextCtrl( edit_panel )
self._cull_front = wx.SpinCtrl( edit_panel, min = -65535, max = 65535 )
self._cull_back = wx.SpinCtrl( edit_panel, min = -65535, max = 65535 )
self._prepend = wx.TextCtrl( edit_panel )
self._append = wx.TextCtrl( edit_panel )
#
test_panel = wx.Panel( notebook )
@ -123,7 +136,13 @@ So, to find the 'src' of the first <img> tag beneath all <span> tags with the cl
1st img tag'
attribute: src'
Leave the 'attribute' blank to fetch the string of the tag (i.e. <p>This part</p>).'''
Leave the 'attribute' blank to fetch the string of the tag (i.e. <p>This part</p>).
Note that you can set _negative_ numbers for the 'remove characters' parts, which will remove all but that many of the opposite end's characters. For instance:
remove 2 from the beginning of 'abcdef' gives 'cdef'
remove -2 from the beginning of 'abcdef' gives 'ef'.'''
info_st = wx.StaticText( info_panel, label = message )
@ -131,7 +150,7 @@ Leave the 'attribute' blank to fetch the string of the tag (i.e. <p>This part</p
#
( tag_rules, content_rule ) = formula.ToTuple()
( tag_rules, content_rule, culling_and_adding ) = formula.ToTuple()
for rule in tag_rules:
@ -169,13 +188,21 @@ Leave the 'attribute' blank to fetch the string of the tag (i.e. <p>This part</p
ae_button_hbox.AddF( self._add_rule, CC.FLAGS_VCENTER )
ae_button_hbox.AddF( self._edit_rule, CC.FLAGS_VCENTER )
rows = []
rows.append( ( 'attribute to fetch: ', self._content_rule ) )
rows.append( ( 'remove this number of characters from the beginning: ', self._cull_front ) )
rows.append( ( 'remove this number of characters from the end: ', self._cull_back ) )
rows.append( ( 'prepend this: ', self._prepend ) )
rows.append( ( 'append this: ', self._append ) )
gridbox = ClientGUICommon.WrapInGrid( edit_panel, rows )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( tag_rules_hbox, CC.FLAGS_EXPAND_BOTH_WAYS )
vbox.AddF( ae_button_hbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( ClientGUICommon.WrapInText( self._content_rule, edit_panel, 'attribute to fetch: ' ), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
edit_panel.SetSizer( vbox )
@ -296,7 +323,9 @@ Leave the 'attribute' blank to fetch the string of the tag (i.e. <p>This part</p
content_rule = None
formula = ClientParsing.ParseFormulaHTML( tags_rules, content_rule )
culling_and_adding = ( self._cull_front.GetValue(), self._cull_back.GetValue(), self._prepend.GetValue(), self._append.GetValue() )
formula = ClientParsing.ParseFormulaHTML( tags_rules, content_rule, culling_and_adding )
return formula
@ -368,7 +397,12 @@ class EditNodes( wx.Panel ):
self._nodes = ClientGUICommon.SaneListCtrl( self, 200, [ ( 'name', 120 ), ( 'node type', 80 ), ( 'produces', -1 ) ], delete_key_callback = self.Delete, activation_callback = self.Edit, use_display_tuple_for_sort = True )
self._add_button = ClientGUICommon.BetterButton( self, 'add', self.Add )
menu_items = []
menu_items.append( ( 'content node', 'A node that parses the given data for content.', self.AddContentNode ) )
menu_items.append( ( 'link node', 'A node that parses the given data for a link, which it then pursues.', self.AddLinkNode ) )
self._add_button = ClientGUICommon.MenuButton( self, 'add', menu_items )
self._copy_button = ClientGUICommon.BetterButton( self, 'copy', self.Copy )
@ -415,16 +449,6 @@ class EditNodes( wx.Panel ):
return ( ( name, node_type, produces ), ( node, node_type, produces ) )
def Add( self ):
menu = wx.Menu()
ClientGUIMenus.AppendMenuItem( menu, 'content node', 'A node that parses the given data for content.', self, self.AddContentNode )
ClientGUIMenus.AppendMenuItem( menu, 'link node', 'A node that parses the given data for a link, which it then pursues.', self, self.AddLinkNode )
HydrusGlobals.client_controller.PopupMenu( self, menu )
def AddContentNode( self ):
dlg_title = 'edit content node'
@ -868,11 +892,15 @@ The 'veto' type will tell the parent panel that this page, while it returned 200
try:
stop_time = HydrusData.GetNow() + 30
job_key = ClientThreading.JobKey( cancellable = True, stop_time = stop_time )
data = self._example_data.GetValue()
referral_url = self._referral_url
desired_content = 'all'
results = node.Parse( data, referral_url, desired_content )
results = node.Parse( job_key, data, referral_url, desired_content )
result_lines = [ '*** RESULTS BEGIN ***' ]
@ -934,8 +962,7 @@ class EditParseNodeContentLinkPanel( ClientGUIScrolledPanels.EditPanel ):
self._formula_description.Disable()
self._edit_formula = wx.Button( formula_panel, label = 'edit formula' )
self._edit_formula.Bind( wx.EVT_BUTTON, self.EventEditFormula )
self._edit_formula = ClientGUICommon.BetterButton( formula_panel, 'edit formula', self.EditFormula )
children_panel = ClientGUICommon.StaticBox( edit_panel, 'content parsing children' )
@ -1043,7 +1070,7 @@ The formula should attempt to parse full or relative urls. If the url is relativ
def EventEditFormula( self, event ):
def EditFormula( self ):
dlg_title = 'edit html formula'
@ -1088,11 +1115,15 @@ The formula should attempt to parse full or relative urls. If the url is relativ
try:
stop_time = HydrusData.GetNow() + 30
job_key = ClientThreading.JobKey( cancellable = True, stop_time = stop_time )
data = self._example_data.GetValue()
referral_url = self._referral_url
desired_content = 'all'
parsed_urls = node.ParseURLs( data, referral_url )
parsed_urls = node.ParseURLs( job_key, data, referral_url )
if len( parsed_urls ) > 0:
@ -1362,7 +1393,11 @@ And pass that html to a number of 'parsing children' that will each look through
try:
example_data = script.FetchData( file_identifier )
stop_time = HydrusData.GetNow() + 30
job_key = ClientThreading.JobKey( cancellable = True, stop_time = stop_time )
example_data = script.FetchData( job_key, file_identifier )
self._example_data.SetValue( example_data )
@ -1384,10 +1419,14 @@ And pass that html to a number of 'parsing children' that will each look through
try:
stop_time = HydrusData.GetNow() + 30
job_key = ClientThreading.JobKey( cancellable = True, stop_time = stop_time )
data = self._example_data.GetValue()
desired_content = 'all'
results = script.Parse( data, desired_content )
results = script.Parse( job_key, data, desired_content )
result_lines = [ '*** RESULTS BEGIN ***' ]
@ -1443,23 +1482,31 @@ class ManageParsingScriptsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._scripts = ClientGUICommon.SaneListCtrl( self, 200, [ ( 'name', 140 ), ( 'query type', 80 ), ( 'script type', 80 ), ( 'produces', -1 ) ], delete_key_callback = self.Delete, activation_callback = self.Edit, use_display_tuple_for_sort = True )
self._add_button = wx.Button( self, label = 'add' )
self._add_button.Bind( wx.EVT_BUTTON, self.EventAdd )
menu_items = []
self._copy_button = wx.Button( self, label = 'copy' )
self._copy_button.Bind( wx.EVT_BUTTON, self.EventCopy )
menu_items.append( ( 'file lookup script', 'A script that fetches content for a known file.', self.AddFileLookupScript ) )
self._paste_button = wx.Button( self, label = 'paste' )
self._paste_button.Bind( wx.EVT_BUTTON, self.EventPaste )
self._add_button = ClientGUICommon.MenuButton( self, 'add', menu_items )
self._duplicate_button = wx.Button( self, label = 'duplicate' )
self._duplicate_button.Bind( wx.EVT_BUTTON, self.EventDuplicate )
menu_items = []
self._edit_button = wx.Button( self, label = 'edit' )
self._edit_button.Bind( wx.EVT_BUTTON, self.EventEdit )
menu_items.append( ( 'to clipboard', 'Serialise the script and put it on your clipboard.', self.ExportToClipboard ) )
menu_items.append( ( 'to png', 'Serialise the script and encode it to an image file you can easily share with other hydrus users.', self.ExportToPng ) )
self._delete_button = wx.Button( self, label = 'delete' )
self._delete_button.Bind( wx.EVT_BUTTON, self.EventDelete )
self._export_button = ClientGUICommon.MenuButton( self, 'export', menu_items )
menu_items = []
menu_items.append( ( 'from clipboard', 'Load a script from text in your clipboard.', self.ImportFromClipboard ) )
menu_items.append( ( 'from png', 'Load a script from an encoded png.', self.ImportFromPng ) )
self._paste_button = ClientGUICommon.MenuButton( self, 'import', menu_items )
self._duplicate_button = ClientGUICommon.BetterButton( self, 'duplicate', self.Duplicate )
self._edit_button = ClientGUICommon.BetterButton( self, 'edit', self.Edit )
self._delete_button = ClientGUICommon.BetterButton( self, 'delete', self.Delete )
#
@ -1479,7 +1526,7 @@ class ManageParsingScriptsPanel( ClientGUIScrolledPanels.ManagePanel ):
button_hbox = wx.BoxSizer( wx.HORIZONTAL )
button_hbox.AddF( self._add_button, CC.FLAGS_VCENTER )
button_hbox.AddF( self._copy_button, CC.FLAGS_VCENTER )
button_hbox.AddF( self._export_button, CC.FLAGS_VCENTER )
button_hbox.AddF( self._paste_button, CC.FLAGS_VCENTER )
button_hbox.AddF( self._duplicate_button, CC.FLAGS_VCENTER )
button_hbox.AddF( self._edit_button, CC.FLAGS_VCENTER )
@ -1521,15 +1568,6 @@ class ManageParsingScriptsPanel( ClientGUIScrolledPanels.ManagePanel ):
def Add( self ):
menu = wx.Menu()
ClientGUIMenus.AppendMenuItem( menu, 'file lookup script', 'A script that fetches content for a known file.', self, self.AddFileLookupScript )
HydrusGlobals.client_controller.PopupMenu( self, menu )
def AddFileLookupScript( self ):
name = 'new script'
@ -1601,18 +1639,6 @@ class ManageParsingScriptsPanel( ClientGUIScrolledPanels.ManagePanel ):
def Copy( self ):
for i in self._scripts.GetAllSelected():
( script, query_type, script_type, produces ) = self._scripts.GetClientData( i )
script_json = script.DumpToString()
HydrusGlobals.client_controller.pub( 'clipboard', 'text', script_json )
def Delete( self ):
self._scripts.RemoveAllSelected()
@ -1682,7 +1708,36 @@ class ManageParsingScriptsPanel( ClientGUIScrolledPanels.ManagePanel ):
def Paste( self ):
def ExportToClipboard( self ):
for i in self._scripts.GetAllSelected():
( script, query_type, script_type, produces ) = self._scripts.GetClientData( i )
script_json = script.DumpToString()
HydrusGlobals.client_controller.pub( 'clipboard', 'text', script_json )
def ExportToPng( self ):
for i in self._scripts.GetAllSelected():
( script, query_type, script_type, produces ) = self._scripts.GetClientData( i )
with ClientGUITopLevelWindows.DialogNullipotent( self, 'export script to png' ) as dlg:
panel = ClientGUISerialisable.PngExportPanel( dlg, script )
dlg.SetPanel( panel )
dlg.ShowModal()
def ImportFromClipboard( self ):
if wx.TheClipboard.Open():
@ -1708,6 +1763,10 @@ class ManageParsingScriptsPanel( ClientGUIScrolledPanels.ManagePanel ):
self._scripts.Append( display_tuple, data_tuple )
else:
wx.MessageBox( 'That was not a script--it was a: ' + type( obj ).__name__ )
except Exception as e:
@ -1720,33 +1779,244 @@ class ManageParsingScriptsPanel( ClientGUIScrolledPanels.ManagePanel ):
def EventAdd( self, event ):
def ImportFromPng( self ):
self.Add()
with wx.FileDialog( self, 'select the png with the encoded script', wildcard = 'PNG (*.png)|*.png' ) as dlg:
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
try:
payload = ClientSerialisable.LoadFromPng( path )
except Exception as e:
wx.MessageBox( str( e ) )
return
try:
obj = HydrusSerialisable.CreateFromNetworkString( payload )
if isinstance( obj, ClientParsing.ParseRootFileLookup ):
script = obj
self._SetNonDupeName( script )
( display_tuple, data_tuple ) = self._ConvertScriptToTuples( script )
self._scripts.Append( display_tuple, data_tuple )
else:
wx.MessageBox( 'That was not a script--it was a: ' + type( obj ).__name__ )
except:
wx.MessageBox( 'I could not understand what was encoded in the png!' )
def EventCopy( self, event ):
class ScriptManagementControl( wx.Panel ):
def __init__( self, parent ):
self.Copy()
wx.Panel.__init__( self, parent )
self._job_key = None
self._lock = threading.Lock()
self._recent_urls = []
main_panel = ClientGUICommon.StaticBox( self, 'script control' )
self._status = wx.StaticText( main_panel )
self._gauge = ClientGUICommon.Gauge( main_panel )
self._link_button = wx.BitmapButton( main_panel, bitmap = CC.GlobalBMPs.link )
self._link_button.Bind( wx.EVT_BUTTON, self.EventLinkButton )
self._link_button.SetToolTipString( 'urls found by the script' )
self._cancel_button = wx.BitmapButton( main_panel, bitmap = CC.GlobalBMPs.stop )
self._cancel_button.Bind( wx.EVT_BUTTON, self.EventCancelButton )
self.Bind( wx.EVT_TIMER, self.TIMEREventUpdate, id = ID_TIMER_SCRIPT_UPDATE )
self._update_timer = wx.Timer( self, id = ID_TIMER_SCRIPT_UPDATE )
#
hbox = wx.BoxSizer( wx.HORIZONTAL )
hbox.AddF( self._gauge, CC.FLAGS_EXPAND_BOTH_WAYS )
hbox.AddF( self._link_button, CC.FLAGS_VCENTER )
hbox.AddF( self._cancel_button, CC.FLAGS_VCENTER )
main_panel.AddF( self._status, CC.FLAGS_EXPAND_PERPENDICULAR )
main_panel.AddF( hbox, CC.FLAGS_EXPAND_PERPENDICULAR )
#
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( main_panel, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
self.SetSizer( vbox )
#
self._Reset()
def EventDelete( self, event ):
def _Reset( self ):
self.Delete()
self._status.SetLabelText( '' )
self._gauge.SetRange( 1 )
self._gauge.SetValue( 0 )
self._link_button.Disable()
self._cancel_button.Disable()
def EventDuplicate( self, event ):
def _Update( self ):
self.Duplicate()
if self._job_key is None:
self._Reset()
else:
if self._job_key.HasVariable( 'script_status' ):
status = self._job_key.GetIfHasVariable( 'script_status' )
else:
status = ''
if status != self._status.GetLabelText():
self._status.SetLabelText( status )
if self._job_key.HasVariable( 'script_gauge' ):
( value, range ) = self._job_key.GetIfHasVariable( 'script_gauge' )
else:
( value, range ) = ( 0, 1 )
if value is None or range is None:
self._gauge.Pulse()
else:
self._gauge.SetRange( range )
self._gauge.SetValue( value )
urls = self._job_key.GetURLs()
if len( urls ) == 0:
if self._link_button.IsEnabled():
self._link_button.Disable()
else:
if not self._link_button.IsEnabled():
self._link_button.Enable()
if self._job_key.IsDone():
if self._cancel_button.IsEnabled():
self._cancel_button.Disable()
else:
if not self._cancel_button.IsEnabled():
self._cancel_button.Enable()
def EventEdit( self, event ):
def TIMEREventUpdate( self, event ):
self.Edit()
with self._lock:
self._Update()
if self._job_key is not None:
self._update_timer.Start( 100, wx.TIMER_ONE_SHOT )
def EventPaste( self, event ):
def EventCancelButton( self, event ):
self.Paste()
with self._lock:
if self._job_key is not None:
self._job_key.Cancel()
def EventLinkButton( self, event ):
with self._lock:
if self._job_key is None:
return
urls = self._job_key.GetURLs()
menu = wx.Menu()
for url in urls:
ClientGUIMenus.AppendMenuItem( menu, url, 'launch this url in your browser', self, webbrowser.open, url )
HydrusGlobals.client_controller.PopupMenu( self, menu )
def SetJobKey( self, job_key ):
with self._lock:
self._job_key = job_key
self._update_timer.Start( 100, wx.TIMER_ONE_SHOT )

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,327 @@
import ClientConstants as CC
import ClientGUICommon
import ClientGUIScrolledPanels
import HydrusConstants as HC
import wx
class EditFrameLocationPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, info ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._original_info = info
self._remember_size = wx.CheckBox( self, label = 'remember size' )
self._remember_position = wx.CheckBox( self, label = 'remember position' )
self._last_size = ClientGUICommon.NoneableSpinCtrl( self, 'last size', none_phrase = 'none set', min = 100, max = 1000000, unit = None, num_dimensions = 2 )
self._last_position = ClientGUICommon.NoneableSpinCtrl( self, 'last position', none_phrase = 'none set', min = -1000000, max = 1000000, unit = None, num_dimensions = 2 )
self._default_gravity_x = ClientGUICommon.BetterChoice( self )
self._default_gravity_x.Append( 'by default, expand to width of parent', 1 )
self._default_gravity_x.Append( 'by default, expand width as much as needed', -1 )
self._default_gravity_y = ClientGUICommon.BetterChoice( self )
self._default_gravity_y.Append( 'by default, expand to height of parent', 1 )
self._default_gravity_y.Append( 'by default, expand height as much as needed', -1 )
self._default_position = ClientGUICommon.BetterChoice( self )
self._default_position.Append( 'by default, position off the top-left corner of parent', 'topleft' )
self._default_position.Append( 'by default, position centered on the parent', 'center' )
self._maximised = wx.CheckBox( self, label = 'start maximised' )
self._fullscreen = wx.CheckBox( self, label = 'start fullscreen' )
#
( name, remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ) = self._original_info
self._remember_size.SetValue( remember_size )
self._remember_position.SetValue( remember_position )
self._last_size.SetValue( last_size )
self._last_position.SetValue( last_position )
( x, y ) = default_gravity
self._default_gravity_x.SelectClientData( x )
self._default_gravity_y.SelectClientData( y )
self._default_position.SelectClientData( default_position )
self._maximised.SetValue( maximised )
self._fullscreen.SetValue( fullscreen )
#
vbox = wx.BoxSizer( wx.VERTICAL )
text = 'Setting frame location info for ' + name + '.'
vbox.AddF( wx.StaticText( self, label = text ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._remember_size, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._remember_position, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._last_size, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._last_position, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._default_gravity_x, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._default_gravity_y, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._default_position, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._maximised, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._fullscreen, CC.FLAGS_EXPAND_PERPENDICULAR )
self.SetSizer( vbox )
def GetValue( self ):
( name, remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen ) = self._original_info
remember_size = self._remember_size.GetValue()
remember_position = self._remember_position.GetValue()
last_size = self._last_size.GetValue()
last_position = self._last_position.GetValue()
x = self._default_gravity_x.GetChoice()
y = self._default_gravity_y.GetChoice()
default_gravity = [ x, y ]
default_position = self._default_position.GetChoice()
maximised = self._maximised.GetValue()
fullscreen = self._fullscreen.GetValue()
return ( name, remember_size, remember_position, last_size, last_position, default_gravity, default_position, maximised, fullscreen )
class EditMediaViewOptionsPanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, info ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._original_info = info
( self._mime, media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) ) = self._original_info
possible_actions = CC.media_viewer_capabilities[ self._mime ]
self._media_show_action = ClientGUICommon.BetterChoice( self )
self._preview_show_action = ClientGUICommon.BetterChoice( self )
for action in possible_actions:
self._media_show_action.Append( CC.media_viewer_action_string_lookup[ action ], action )
if action != CC.MEDIA_VIEWER_ACTION_DO_NOT_SHOW:
self._preview_show_action.Append( CC.media_viewer_action_string_lookup[ action ], action )
self._media_show_action.Bind( wx.EVT_CHOICE, self.EventActionChange )
self._preview_show_action.Bind( wx.EVT_CHOICE, self.EventActionChange )
self._media_scale_up = ClientGUICommon.BetterChoice( self )
self._media_scale_down = ClientGUICommon.BetterChoice( self )
self._preview_scale_up = ClientGUICommon.BetterChoice( self )
self._preview_scale_down = ClientGUICommon.BetterChoice( self )
for scale_action in ( CC.MEDIA_VIEWER_SCALE_100, CC.MEDIA_VIEWER_SCALE_MAX_REGULAR, CC.MEDIA_VIEWER_SCALE_TO_CANVAS ):
text = CC.media_viewer_scale_string_lookup[ scale_action ]
self._media_scale_up.Append( text, scale_action )
self._preview_scale_up.Append( text, scale_action )
if scale_action != CC.MEDIA_VIEWER_SCALE_100:
self._media_scale_down.Append( text, scale_action )
self._preview_scale_down.Append( text, scale_action )
self._exact_zooms_only = wx.CheckBox( self, label = 'only permit half and double zooms' )
self._exact_zooms_only.SetToolTipString( 'This limits zooms to 25%, 50%, 100%, 200%, 400%, and so on. It makes for fast resize and is useful for files that often have flat colours and hard edges, which often scale badly otherwise. The \'canvas fit\' zoom will still be inserted.' )
self._scale_up_quality = ClientGUICommon.BetterChoice( self )
for zoom in ( CC.ZOOM_NEAREST, CC.ZOOM_LINEAR, CC.ZOOM_CUBIC, CC.ZOOM_LANCZOS4 ):
self._scale_up_quality.Append( CC.zoom_string_lookup[ zoom ], zoom )
self._scale_down_quality = ClientGUICommon.BetterChoice( self )
for zoom in ( CC.ZOOM_NEAREST, CC.ZOOM_LINEAR, CC.ZOOM_AREA ):
self._scale_down_quality.Append( CC.zoom_string_lookup[ zoom ], zoom )
#
self._media_show_action.SelectClientData( media_show_action )
self._preview_show_action.SelectClientData( preview_show_action )
self._media_scale_up.SelectClientData( media_scale_up )
self._media_scale_down.SelectClientData( media_scale_down )
self._preview_scale_up.SelectClientData( preview_scale_up )
self._preview_scale_down.SelectClientData( preview_scale_down )
self._exact_zooms_only.SetValue( exact_zooms_only )
self._scale_up_quality.SelectClientData( scale_up_quality )
self._scale_down_quality.SelectClientData( scale_down_quality )
#
vbox = wx.BoxSizer( wx.VERTICAL )
text = 'Setting media view options for ' + HC.mime_string_lookup[ self._mime ] + '.'
vbox.AddF( wx.StaticText( self, label = text ), CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( ClientGUICommon.WrapInText( self._media_show_action, self, 'media viewer show action:' ), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( ClientGUICommon.WrapInText( self._preview_show_action, self, 'preview show action:' ), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
if possible_actions == CC.no_support:
self._media_scale_up.Hide()
self._media_scale_down.Hide()
self._preview_scale_up.Hide()
self._preview_scale_down.Hide()
self._exact_zooms_only.Hide()
self._scale_up_quality.Hide()
self._scale_down_quality.Hide()
else:
rows = []
rows.append( ( 'if the media is smaller than the media viewer canvas: ', self._media_scale_up ) )
rows.append( ( 'if the media is larger than the media viewer canvas: ', self._media_scale_down ) )
rows.append( ( 'if the media is smaller than the preview canvas: ', self._preview_scale_up) )
rows.append( ( 'if the media is larger than the preview canvas: ', self._preview_scale_down ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
vbox.AddF( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( self._exact_zooms_only, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( wx.StaticText( self, label = 'Nearest neighbour is fast and ugly, 8x8 lanczos and area resampling are slower but beautiful.' ), CC.FLAGS_VCENTER )
vbox.AddF( ClientGUICommon.WrapInText( self._scale_up_quality, self, '>100% (interpolation) quality:' ), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
vbox.AddF( ClientGUICommon.WrapInText( self._scale_down_quality, self, '<100% (decimation) quality:' ), CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
if self._mime == HC.APPLICATION_FLASH:
self._scale_up_quality.Disable()
self._scale_down_quality.Disable()
self.SetSizer( vbox )
def EventActionChange( self, event ):
if self._media_show_action.GetChoice() in CC.no_support and self._preview_show_action.GetChoice() in CC.no_support:
self._media_scale_up.Disable()
self._media_scale_down.Disable()
self._preview_scale_up.Disable()
self._preview_scale_down.Disable()
self._exact_zooms_only.Disable()
self._scale_up_quality.Disable()
self._scale_down_quality.Disable()
else:
self._media_scale_up.Enable()
self._media_scale_down.Enable()
self._preview_scale_up.Enable()
self._preview_scale_down.Enable()
self._exact_zooms_only.Enable()
self._scale_up_quality.Enable()
self._scale_down_quality.Enable()
if self._mime == HC.APPLICATION_FLASH:
self._scale_up_quality.Disable()
self._scale_down_quality.Disable()
def GetValue( self ):
media_show_action = self._media_show_action.GetChoice()
preview_show_action = self._preview_show_action.GetChoice()
media_scale_up = self._media_scale_up.GetChoice()
media_scale_down = self._media_scale_down.GetChoice()
preview_scale_up = self._preview_scale_up.GetChoice()
preview_scale_down = self._preview_scale_down.GetChoice()
exact_zooms_only = self._exact_zooms_only.GetValue()
scale_up_quality = self._scale_up_quality.GetChoice()
scale_down_quality = self._scale_down_quality.GetChoice()
return ( self._mime, media_show_action, preview_show_action, ( media_scale_up, media_scale_down, preview_scale_up, preview_scale_down, exact_zooms_only, scale_up_quality, scale_down_quality ) )
class EditSeedCachePanel( ClientGUIScrolledPanels.EditPanel ):
def __init__( self, parent, controller, seed_cache ):
ClientGUIScrolledPanels.EditPanel.__init__( self, parent )
self._controller = controller
self._seed_cache = seed_cache
self._text = wx.StaticText( self, label = 'initialising' )
self._seed_cache_control = ClientGUICommon.SeedCacheControl( self, self._seed_cache )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._text, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._seed_cache_control, CC.FLAGS_EXPAND_BOTH_WAYS )
self.SetSizer( vbox )
self._controller.sub( self, 'NotifySeedUpdated', 'seed_cache_seed_updated' )
wx.CallAfter( self._UpdateText )
def _UpdateText( self ):
( status, ( total_processed, total ) ) = self._seed_cache.GetStatus()
self._text.SetLabelText( status )
self.Layout()
def GetValue( self ):
return self._seed_cache
def NotifySeedUpdated( self, seed ):
self._UpdateText()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,99 @@
import ClientConstants as CC
import ClientGUICommon
import ClientGUIScrolledPanels
import ClientParsing
import ClientSerialisable
import HydrusConstants as HC
import wx
class PngExportPanel( ClientGUIScrolledPanels.ReviewPanel ):
def __init__( self, parent, payload_obj ):
ClientGUIScrolledPanels.ReviewPanel.__init__( self, parent )
self._payload_obj = payload_obj
self._filepicker = wx.FilePickerCtrl( self, style = wx.FLP_SAVE, wildcard = 'PNG (*.png)|*.png' )
self._filepicker.Bind( wx.EVT_FILEPICKER_CHANGED, self.EventChanged )
self._title = wx.TextCtrl( self )
self._title.Bind( wx.EVT_TEXT, self.EventChanged )
self._payload_type = wx.TextCtrl( self )
self._text = wx.TextCtrl( self )
self._export = ClientGUICommon.BetterButton( self, 'export', self.Export )
#
( payload_type, payload_string ) = ClientSerialisable.GetPayloadTypeAndString( self._payload_obj )
self._payload_type.SetValue( payload_type )
self._payload_type.Disable()
self.EventChanged( None )
#
rows = []
rows.append( ( 'export path: ', self._filepicker ) )
rows.append( ( 'title: ', self._title ) )
rows.append( ( 'payload type: ', self._payload_type ) )
rows.append( ( 'description (optional): ', self._text ) )
rows.append( ( '', self._export ) )
gridbox = ClientGUICommon.WrapInGrid( self, rows )
self.SetSizer( gridbox )
def EventChanged( self, event ):
problems = []
path = self._filepicker.GetPath()
if path == '' or path is None:
problems.append( 'select a path' )
if self._title.GetValue() == '':
problems.append( 'set a title' )
if len( problems ) == 0:
self._export.SetLabelText( 'export' )
self._export.Enable()
else:
self._export.SetLabelText( ' and '.join( problems ) )
self._export.Disable()
def Export( self ):
( payload_type, payload_string ) = ClientSerialisable.GetPayloadTypeAndString( self._payload_obj )
title = self._title.GetValue()
text = self._text.GetValue()
path = self._filepicker.GetPath()
if not path.endswith( '.png' ):
path += '.png'
ClientSerialisable.DumpToPng( payload_string, title, payload_type, text, path )

View File

@ -2,10 +2,13 @@ import ClientConstants as CC
import ClientData
import ClientGUICommon
import ClientGUIDialogs
import ClientGUIParsing
import ClientParsing
import ClientSearch
import ClientThreading
import collections
import HydrusConstants as HC
import HydrusData
import HydrusGlobals
import HydrusSerialisable
import wx
@ -35,6 +38,11 @@ class ListBoxTagsSuggestionsFavourites( ClientGUICommon.ListBoxTagsStrings ):
self._activate_callable( tags )
def ActivateAll( self ):
self._activate_callable( self.GetTags() )
'''
# Maybe reinclude this if per-column autoresizing is desired and not completely buggy
def SetTags( self, tags ):
@ -255,18 +263,21 @@ class RelatedTagsPanel( wx.Panel ):
class FileLookupScriptTagsPanel( wx.Panel ):
def __init__( self, parent, service_key, media, activate_callable ):
def __init__( self, parent, service_key, media, activate_callable, canvas_key = None ):
wx.Panel.__init__( self, parent )
self._service_key = service_key
self._media = media
self._canvas_key = canvas_key
scripts = HydrusGlobals.client_controller.Read( 'serialisable_named', HydrusSerialisable.SERIALISABLE_TYPE_PARSE_ROOT_FILE_LOOKUP )
script_names_to_scripts = { script.GetName() : script for script in scripts }
self._script_choice = ClientGUICommon.BetterChoice( self )
for script in scripts:
for ( name, script ) in script_names_to_scripts.items():
self._script_choice.Append( script.GetName(), script )
@ -275,20 +286,64 @@ class FileLookupScriptTagsPanel( wx.Panel ):
favourite_file_lookup_script = new_options.GetNoneableString( 'favourite_file_lookup_script' )
self._script_choice.SelectClientData( favourite_file_lookup_script )
if favourite_file_lookup_script in script_names_to_scripts:
self._script_choice.SelectClientData( script_names_to_scripts[ favourite_file_lookup_script ] )
else:
self._script_choice.Select( 0 )
fetch_button = ClientGUICommon.BetterButton( self, 'fetch tags', self.FetchTags )
self._script_management = ClientGUIParsing.ScriptManagementControl( self )
self._tags = ListBoxTagsSuggestionsFavourites( self, activate_callable, sort_tags = True )
self._add_all = ClientGUICommon.BetterButton( self, 'add all', self._tags.ActivateAll )
vbox = wx.BoxSizer( wx.VERTICAL )
vbox.AddF( self._script_choice, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( fetch_button, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._script_management, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._add_all, CC.FLAGS_EXPAND_PERPENDICULAR )
vbox.AddF( self._tags, CC.FLAGS_EXPAND_BOTH_WAYS )
self._SetTags( [] )
self.SetSizer( vbox )
if self._canvas_key is not None:
HydrusGlobals.client_controller.sub( self, 'CanvasHasNewMedia', 'canvas_new_display_media' )
def _SetTags( self, tags ):
self._tags.SetTags( tags )
if len( tags ) == 0:
self._add_all.Disable()
else:
self._add_all.Enable()
def CanvasHasNewMedia( self, canvas_key, new_media_singleton ):
if canvas_key == self._canvas_key:
self._media = ( new_media_singleton.Duplicate(), )
self._SetTags( [] )
def FetchTags( self ):
@ -315,11 +370,24 @@ class FileLookupScriptTagsPanel( wx.Panel ):
file_identifier = script.ConvertMediaToFileIdentifier( m )
content_results = script.DoQuery( file_identifier, 'all' )
stop_time = HydrusData.GetNow() + 30
job_key = ClientThreading.JobKey( cancellable = True, stop_time = stop_time )
self._script_management.SetJobKey( job_key )
desired_content = 'all'
HydrusGlobals.client_controller.CallToThread( self.THREADFetchTags, script, job_key, file_identifier, desired_content )
def THREADFetchTags( self, script, job_key, file_identifier, desired_content ):
content_results = script.DoQuery( job_key, file_identifier, desired_content )
tags = ClientParsing.GetTagsFromContentResults( content_results )
self._tags.SetTags( tags )
wx.CallAfter( self._SetTags, tags )
class SuggestedTagsPanel( wx.Panel ):
@ -369,7 +437,7 @@ class SuggestedTagsPanel( wx.Panel ):
if self._new_options.GetBoolean( 'show_file_lookup_script_tags' ) and len( media ) == 1:
file_lookup_script_tags = FileLookupScriptTagsPanel( panel_parent, service_key, media, activate_callable )
file_lookup_script_tags = FileLookupScriptTagsPanel( panel_parent, service_key, media, activate_callable, canvas_key = self._canvas_key )
panels.append( ( 'file lookup scripts', file_lookup_script_tags ) )

View File

@ -240,17 +240,22 @@ class DialogThatTakesScrollablePanel( DialogThatResizes ):
DialogThatResizes.__init__( self, parent, title, frame_key )
self._apply = wx.Button( self, id = wx.ID_OK, label = 'apply' )
self._apply.Bind( wx.EVT_BUTTON, self.EventOk )
self._apply.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
self._InitialiseButtons()
self.Bind( wx.EVT_MENU, self.EventMenu )
self.Bind( CC.EVT_SIZE_CHANGED, self.EventChildSizeChanged )
def _GetButtonBox( self ):
raise NotImplementedError()
def _InitialiseButtons( self ):
raise NotImplementedError()
def EventChildSizeChanged( self, event ):
if self._panel is not None:
@ -299,10 +304,7 @@ class DialogThatTakesScrollablePanel( DialogThatResizes ):
self._panel = panel
buttonbox = wx.BoxSizer( wx.HORIZONTAL )
buttonbox.AddF( self._apply, CC.FLAGS_VCENTER )
buttonbox.AddF( self._cancel, CC.FLAGS_VCENTER )
buttonbox = self._GetButtonBox()
vbox = wx.BoxSizer( wx.VERTICAL )
@ -316,11 +318,31 @@ class DialogThatTakesScrollablePanel( DialogThatResizes ):
self._panel.SetupScrolling()
class DialogEdit( DialogThatTakesScrollablePanel ):
class DialogThatTakesScrollablePanelClose( DialogThatTakesScrollablePanel ):
def _GetButtonBox( self ):
buttonbox = wx.BoxSizer( wx.HORIZONTAL )
buttonbox.AddF( self._close, CC.FLAGS_VCENTER )
return buttonbox
def _InitialiseButtons( self ):
self._close = wx.Button( self, id = wx.ID_OK, label = 'close' )
self._close.Bind( wx.EVT_BUTTON, self.EventOk )
self._cancel = wx.Button( self, id = wx.ID_CANCEL )
self._cancel.Hide()
class DialogNullipotent( DialogThatTakesScrollablePanelClose ):
def __init__( self, parent, title ):
DialogThatTakesScrollablePanel.__init__( self, parent, title, 'regular_dialog' )
DialogThatTakesScrollablePanelClose.__init__( self, parent, title, 'regular_dialog' )
def EventOk( self, event ):
@ -330,7 +352,43 @@ class DialogEdit( DialogThatTakesScrollablePanel ):
self.EndModal( wx.ID_OK )
class DialogManage( DialogThatTakesScrollablePanel ):
class DialogThatTakesScrollablePanelApplyCancel( DialogThatTakesScrollablePanel ):
def _GetButtonBox( self ):
buttonbox = wx.BoxSizer( wx.HORIZONTAL )
buttonbox.AddF( self._apply, CC.FLAGS_VCENTER )
buttonbox.AddF( self._cancel, CC.FLAGS_VCENTER )
return buttonbox
def _InitialiseButtons( self ):
self._apply = wx.Button( self, id = wx.ID_OK, label = 'apply' )
self._apply.Bind( wx.EVT_BUTTON, self.EventOk )
self._apply.SetForegroundColour( ( 0, 128, 0 ) )
self._cancel = wx.Button( self, id = wx.ID_CANCEL, label = 'cancel' )
self._cancel.SetForegroundColour( ( 128, 0, 0 ) )
class DialogEdit( DialogThatTakesScrollablePanelApplyCancel ):
def __init__( self, parent, title ):
DialogThatTakesScrollablePanelApplyCancel.__init__( self, parent, title, 'regular_dialog' )
def EventOk( self, event ):
SaveTLWSizeAndPosition( self, self._frame_key )
self.EndModal( wx.ID_OK )
class DialogManage( DialogThatTakesScrollablePanelApplyCancel ):
def EventOk( self, event ):

View File

@ -56,6 +56,13 @@ def GenerateNumpyImage( path ):
numpy_image = cv2.imread( path, flags = IMREAD_UNCHANGED )
if numpy_image.dtype == 'uint16':
numpy_image /= 256
numpy_image = numpy.array( numpy_image, dtype = 'uint8' )
if numpy_image is None: # doesn't support static gifs and some random other stuff
pil_image = HydrusImageHandling.GeneratePILImage( path )
@ -104,6 +111,7 @@ def GenerateNumPyImageFromPILImage( pil_image ):
def GeneratePerceptualHash( path ):
numpy_image = GenerateNumpyImage( path )
( y, x, depth ) = numpy_image.shape
@ -118,7 +126,7 @@ def GeneratePerceptualHash( path ):
numpy_image_bgr = numpy_image[ :, :, :3 ]
numpy_image_gray_bare = cv2.cvtColor( numpy_image_bgr, cv2.COLOR_BGR2GRAY )
numpy_image_gray_bare = cv2.cvtColor( numpy_image_bgr, cv2.COLOR_RGB2GRAY )
# create a white greyscale canvas
@ -130,7 +138,7 @@ def GeneratePerceptualHash( path ):
else:
numpy_image_gray = cv2.cvtColor( numpy_image, cv2.COLOR_BGR2GRAY )
numpy_image_gray = cv2.cvtColor( numpy_image, cv2.COLOR_RGB2GRAY )
numpy_image_tiny = cv2.resize( numpy_image_gray, ( 32, 32 ), interpolation = cv2.INTER_AREA )

View File

@ -7,6 +7,7 @@ import HydrusGlobals
import HydrusSerialisable
import HydrusTags
import os
import time
import urlparse
def ChildHasDesiredContent( child, desired_content ):
@ -65,7 +66,7 @@ def ConvertParsableContentToPrettyString( parsable_content, include_veto = False
return ', '.join( pretty_strings )
def GetChildrenContent( children, data, referral_url, desired_content ):
def GetChildrenContent( job_key, children, data, referral_url, desired_content ):
for child in children:
@ -81,7 +82,7 @@ def GetChildrenContent( children, data, referral_url, desired_content ):
if ChildHasDesiredContent( child, desired_content ):
child_content = child.Parse( data, referral_url, desired_content )
child_content = child.Parse( job_key, data, referral_url, desired_content )
content.extend( child_content )
@ -155,30 +156,73 @@ def RenderTagRule( ( name, attrs, index ) ):
class ParseFormulaHTML( HydrusSerialisable.SerialisableBase ):
SERIALISABLE_TYPE = HydrusSerialisable.SERIALISABLE_TYPE_PARSE_FORMULA_HTML
SERIALISABLE_VERSION = 1
SERIALISABLE_VERSION = 2
def __init__( self, tag_rules = None, content_rule = None ):
def __init__( self, tag_rules = None, content_rule = None, culling_and_adding = None ):
if tag_rules is None:
tag_rules = [ ( 'a', {}, None ) ]
if culling_and_adding is None:
culling_and_adding = ( 0, 0, '', '' )
self._tag_rules = tag_rules
self._content_rule = content_rule
# I need extra rules here for chopping stuff off the beginning or end and appending or prepending strings
self._culling_and_adding = culling_and_adding
def _CullAndAdd( self, text ):
( cull_front, cull_back, prepend, append ) = self._culling_and_adding
if cull_front != 0:
text = text[ cull_front : ]
if cull_back != 0:
text = text[ : - cull_back ]
if text == '':
return None
text = prepend + text + append
return text
def _GetSerialisableInfo( self ):
return ( self._tag_rules, self._content_rule )
return ( self._tag_rules, self._content_rule, self._culling_and_adding )
def _InitialiseFromSerialisableInfo( self, serialisable_info ):
( self._tag_rules, self._content_rule ) = serialisable_info
( self._tag_rules, self._content_rule, self._culling_and_adding ) = serialisable_info
def _UpdateSerialisableInfo( self, version, old_serialisable_info ):
if version == 1:
( tag_rules, content_rule ) = old_serialisable_info
culling_and_adding = ( 0, 0, '', '' )
new_serialisable_info = ( tag_rules, content_rule, culling_and_adding )
return ( 2, new_serialisable_info )
def _ParseContent( self, root ):
@ -191,7 +235,7 @@ class ParseFormulaHTML( HydrusSerialisable.SerialisableBase ):
if root.has_attr( self._content_rule ):
result = root[ self._content_rule ]
result = root[ self._content_rule ][0]
else:
@ -199,13 +243,13 @@ class ParseFormulaHTML( HydrusSerialisable.SerialisableBase ):
if result == '':
if result == '' or result is None:
return None
else:
return result
return self._CullAndAdd( result )
@ -291,6 +335,50 @@ class ParseFormulaHTML( HydrusSerialisable.SerialisableBase ):
pretty_strings.append( 'get the ' + self._content_rule + ' attribute of those tags' )
cull_munge_strings = []
( cull_front, cull_back, prepend, append ) = self._culling_and_adding
if cull_front > 0:
cull_munge_strings.append( 'the first ' + HydrusData.ConvertIntToPrettyString( cull_front ) + ' characters' )
elif cull_front < 0:
cull_munge_strings.append( 'all but the last ' + HydrusData.ConvertIntToPrettyString( abs( cull_front ) ) + ' characters' )
if cull_back > 0:
cull_munge_strings.append( 'the last ' + HydrusData.ConvertIntToPrettyString( cull_back ) + ' characters' )
elif cull_back < 0:
cull_munge_strings.append( 'all but the first ' + HydrusData.ConvertIntToPrettyString( abs( cull_back ) ) + ' characters' )
if len( cull_munge_strings ) > 0:
pretty_strings.append( 'remove ' + ' and '.join( cull_munge_strings ) )
add_munge_strings = []
if prepend != '':
add_munge_strings.append( 'prepend "' + prepend + '"' )
if append != '':
add_munge_strings.append( 'append "' + append + '"' )
if len( add_munge_strings ) > 0:
pretty_strings.append( ' and '.join( add_munge_strings ) )
separator = os.linesep + 'and then '
pretty_multiline_string = separator.join( pretty_strings )
@ -300,7 +388,7 @@ class ParseFormulaHTML( HydrusSerialisable.SerialisableBase ):
def ToTuple( self ):
return ( self._tag_rules, self._content_rule )
return ( self._tag_rules, self._content_rule, self._culling_and_adding )
HydrusSerialisable.SERIALISABLE_TYPES_TO_OBJECT_TYPES[ HydrusSerialisable.SERIALISABLE_TYPE_PARSE_FORMULA_HTML ] = ParseFormulaHTML
@ -365,7 +453,7 @@ class ParseNodeContent( HydrusSerialisable.SerialisableBase ):
return { ( self._name, self._content_type, self._additional_info ) }
def Parse( self, data, referral_url, desired_content ):
def Parse( self, job_key, data, referral_url, desired_content ):
content_description = ( self._name, self._content_type, self._additional_info )
@ -466,34 +554,67 @@ class ParseNodeContentLink( HydrusSerialisable.SerialisableBase ):
return children_parsable_content
def Parse( self, data, referral_url, desired_content ):
def Parse( self, job_key, data, referral_url, desired_content ):
search_urls = self.ParseURLs( data, referral_url )
search_urls = self.ParseURLs( job_key, data, referral_url )
content = []
for search_url in search_urls:
headers = { 'Referer' : referral_url }
response = ClientNetworking.RequestsGet( search_url, headers = headers )
try:
job_key.SetVariable( 'script_status', 'fetching ' + search_url )
headers = { 'Referer' : referral_url }
response = ClientNetworking.RequestsGet( search_url, headers = headers )
except HydrusExceptions.NotFoundException:
job_key.SetVariable( 'script_status', '404 - nothing found' )
time.sleep( 2 )
continue
except HydrusExceptions.NetworkException as e:
job_key.SetVariable( 'script_status', 'Network error! Details written to log.' )
HydrusData.PrintException( e )
time.sleep( 2 )
continue
linked_data = response.content
children_content = GetChildrenContent( self._children, linked_data, search_url, desired_content )
children_content = GetChildrenContent( job_key, self._children, linked_data, search_url, desired_content )
content.extend( children_content )
if job_key.IsCancelled():
raise HydrusExceptions.CancelledException()
return content
def ParseURLs( self, data, referral_url ):
def ParseURLs( self, job_key, data, referral_url ):
basic_urls = self._formula.Parse( data )
absolute_urls = [ urlparse.urljoin( referral_url, basic_url ) for basic_url in basic_urls ]
for url in absolute_urls:
job_key.AddURL( url )
return absolute_urls
@ -620,7 +741,9 @@ class ParseRootFileLookup( HydrusSerialisable.SerialisableBaseNamed ):
def FetchData( self, file_identifier ):
def FetchData( self, job_key, file_identifier ):
# add gauge report hook and cancel support here
request_args = dict( self._static_args )
@ -636,24 +759,39 @@ class ParseRootFileLookup( HydrusSerialisable.SerialisableBaseNamed ):
raise Exception( 'Cannot have a file as an argument on a GET query!' )
rendered_url = self._url + '?' + '&'.join( ( HydrusData.ToByteString( key ) + '=' + HydrusData.ToByteString( value ) for ( key, value ) in request_args.items() ) )
job_key.SetVariable( 'script_status', 'fetching ' + rendered_url )
job_key.AddURL( rendered_url )
response = ClientNetworking.RequestsGet( self._url, params = request_args )
elif self._query_type == HC.POST:
if self._file_identifier_type == FILE_IDENTIFIER_TYPE_FILE:
job_key.SetVariable( 'script_status', 'uploading file' )
path = file_identifier
files = { self._file_identifier_arg_name : open( path, 'rb' ) }
else:
job_key.SetVariable( 'script_status', 'uploading identifier' )
files = None
response = ClientNetworking.RequestsPost( self._url, data = request_args, files = files )
if job_key.IsCancelled():
raise HydrusExceptions.CancelledException()
data = response.content
return data
@ -671,13 +809,52 @@ class ParseRootFileLookup( HydrusSerialisable.SerialisableBaseNamed ):
return children_parsable_content
def DoQuery( self, file_identifier, desired_content ):
def DoQuery( self, job_key, file_identifier, desired_content ):
# this should eventually take a job_key that will be propagated down and will have obeyed cancel and so on
data = self.FetchData( file_identifier )
return self.Parse( data, desired_content )
try:
try:
data = self.FetchData( job_key, file_identifier )
except HydrusExceptions.NotFoundException:
job_key.SetVariable( 'script_status', '404 - nothing found' )
return []
except HydrusExceptions.NetworkException as e:
job_key.SetVariable( 'script_status', 'Network error!' )
HydrusData.ShowException( e )
return []
content_results = self.Parse( job_key, data, desired_content )
if len( content_results ) == 0:
job_key.SetVariable( 'script_status', 'Did not find anything.' )
else:
job_key.SetVariable( 'script_status', 'Found ' + HydrusData.ConvertIntToPrettyString( len( content_results ) ) + ' rows.' )
return content_results
except HydrusExceptions.CancelledException:
job_key.SetVariable( 'script_status', 'Cancelled!' )
return []
finally:
job_key.Finish()
def UsesUserInput( self ):
@ -685,9 +862,9 @@ class ParseRootFileLookup( HydrusSerialisable.SerialisableBaseNamed ):
return self._file_identifier_type == FILE_IDENTIFIER_TYPE_USER_INPUT
def Parse( self, data, desired_content ):
def Parse( self, job_key, data, desired_content ):
content = GetChildrenContent( self._children, data, self._url, desired_content )
content = GetChildrenContent( job_key, self._children, data, self._url, desired_content )
return content

View File

@ -1,9 +1,140 @@
import ClientConstants as CC
import ClientImageHandling
import ClientParsing
import cv2
import HydrusConstants as HC
import HydrusData
import HydrusSerialisable
import numpy
import os
import struct
import wx
if cv2.__version__.startswith( '2' ):
IMREAD_UNCHANGED = cv2.CV_LOAD_IMAGE_UNCHANGED
else:
IMREAD_UNCHANGED = cv2.IMREAD_UNCHANGED
png_font = cv2.FONT_HERSHEY_TRIPLEX
greyscale_text_color = 0
title_size = 0.7
payload_type_size = 0.5
text_size = 0.4
def CreateTopImage( width, title, payload_type, text ):
text_extent_bmp = wx.EmptyBitmap( 20, 20, 24 )
dc = wx.MemoryDC( text_extent_bmp )
text_font = wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT )
basic_font_size = text_font.GetPointSize()
payload_type_font = wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT )
payload_type_font.SetPointSize( int( basic_font_size * 1.4 ) )
title_font = wx.SystemSettings.GetFont( wx.SYS_DEFAULT_GUI_FONT )
title_font.SetPointSize( int( basic_font_size * 2.0 ) )
dc.SetFont( text_font )
( gumpf, text_line_height ) = dc.GetTextExtent( 'abcdefghijklmnopqrstuvwxyz' )
dc.SetFont( payload_type_font )
( gumpf, payload_type_line_height ) = dc.GetTextExtent( 'abcdefghijklmnopqrstuvwxyz' )
dc.SetFont( title_font )
( gumpf, title_line_height ) = dc.GetTextExtent( 'abcdefghijklmnopqrstuvwxyz' )
del dc
del text_extent_bmp
text_lines = WrapText( text, width, text_size, 1 )
if len( text_lines ) == 0:
text_total_height = 0
else:
text_total_height = ( text_line_height + 4 ) * len( text_lines )
text_total_height += 6 # to bring the last 4 padding up to 10 padding
top_height = 10 + title_line_height + 10 + payload_type_line_height + 10 + text_total_height
#
top_bmp = wx.EmptyBitmap( width, top_height, 24 )
dc = wx.MemoryDC( top_bmp )
dc.SetBackground( wx.Brush( wx.WHITE ) )
dc.Clear()
#
dc.DrawBitmap( CC.GlobalBMPs.file_repository, width - 16 - 5, 5 )
#
current_y = 10
dc.SetFont( title_font )
( t_width, t_height ) = dc.GetTextExtent( title )
dc.DrawText( title, ( width - t_width ) / 2, current_y )
current_y += t_height + 10
dc.SetFont( payload_type_font )
( t_width, t_height ) = dc.GetTextExtent( payload_type )
dc.DrawText( payload_type, ( width - t_width ) / 2, current_y )
current_y += t_height + 10
dc.SetFont( text_font )
for text_line in text_lines:
( t_width, t_height ) = dc.GetTextExtent( text_line )
dc.DrawText( text_line, ( width - t_width ) / 2, current_y )
current_y += t_height + 4
del dc
data = top_bmp.ConvertToImage().GetData()
top_image_rgb = numpy.fromstring( data, dtype = 'uint8' ).reshape( ( top_height, width, 3 ) )
top_bmp.Destroy()
top_image = cv2.cvtColor( top_image_rgb, cv2.COLOR_RGB2GRAY )
top_height_header = struct.pack( '!H', top_height )
( byte0, byte1 ) = top_height_header
top_image[0][0] = ord( byte0 )
top_image[0][1] = ord( byte1 )
return top_image
def DumpToPng( payload, title, payload_type, text, path ):
payload_length = len( payload )
@ -14,35 +145,14 @@ def DumpToPng( payload, title, payload_type, text, path ):
width = max( 512, square_width )
payload_height = float( payload_string_length ) / width
payload_height = int( float( payload_string_length ) / width )
if float( payload_string_length ) / width % 1.0 > 0:
payload_height += 1
# given this width, figure out how much height we need for title and text and object type
# does cv have gettextentent or similar?
# getTextSize looks like the one
# intelligently wrap the text to fit into our width sans padding
# we now know our height
top_height = 250
top_image = numpy.empty( ( top_height, width ), dtype = 'uint8' )
top_image.fill( 255 )
# draw hydrus icon in the corner
# draw title
# draw payload_type
# draw text
top_height_header = struct.pack( '!H', top_height )
( byte0, byte1 ) = top_height_header
top_image[0][0] = ord( byte0 )
top_image[0][1] = ord( byte1 )
top_image = CreateTopImage( width, title, payload_type, text )
payload_length_header = struct.pack( '!I', payload_length )
@ -52,31 +162,113 @@ def DumpToPng( payload, title, payload_type, text, path ):
payload_image = numpy.fromstring( full_payload_string, dtype = 'uint8' ).reshape( ( payload_height, width ) )
# if this works out wrong, you can change axis or do stack_vertically instead or something. one of those will do the trick
finished_image = numpy.concatenate( top_image, payload_image )
finished_image = numpy.concatenate( ( top_image, payload_image ) )
# make sure this is how cv 'params' work
cv2.imwrite( path, finished_image, params = { cv2.IMWRITE_PNG_COMPRESSION : 9 } )
cv2.imwrite( path, finished_image, [ cv2.IMWRITE_PNG_COMPRESSION, 9 ] )
def GetPayloadTypeAndString( payload_obj ):
payload_string = payload_obj.DumpToNetworkString()
if isinstance( payload_obj, ClientParsing.ParseRootFileLookup ):
payload_obj_type_string = 'File Lookup Script'
payload_type = payload_obj_type_string + ' - ' + HydrusData.ConvertIntToBytes( len( payload_string ) )
return ( payload_type, payload_string )
def LoadFromPng( path ):
numpy_image = cv2.imread( path )
try:
numpy_image = cv2.imread( path, flags = IMREAD_UNCHANGED )
except Exception as e:
HydrusData.ShowException( e )
raise Exception( 'That did not appear to be a valid image!' )
( height, width ) = numpy_image.shape
complete_data = numpy_image.tostring()
top_height_header = complete_data[:2]
( top_height, ) = struct.unpack( '!H', top_height_header )
full_payload_string = complete_data[ width * top_height : ]
payload_length_header = full_payload_string[:4]
( payload_length, ) = struct.unpack( '!I', payload_length_header )
payload = full_payload_string[ 4 : 4 + payload_length ]
try:
( height, width ) = numpy_image.shape
complete_data = numpy_image.tostring()
top_height_header = complete_data[:2]
( top_height, ) = struct.unpack( '!H', top_height_header )
full_payload_string = complete_data[ width * top_height : ]
payload_length_header = full_payload_string[:4]
( payload_length, ) = struct.unpack( '!I', payload_length_header )
payload = full_payload_string[ 4 : 4 + payload_length ]
except Exception as e:
HydrusData.ShowException( e )
raise Exception( 'The image was fine, but it did not seem to have hydrus data encoded in it!' )
return payload
def TextExceedsWidth( text, width, size, thickness ):
( ( tw, th ), baseline ) = cv2.getTextSize( text, png_font, size, thickness )
return tw > width
def WrapText( text, width, size, thickness ):
words = text.split( ' ' )
lines = []
next_line = []
for word in words:
if word == '':
continue
potential_next_line = list( next_line )
potential_next_line.append( word )
if TextExceedsWidth( ' '.join( potential_next_line ), width, size, thickness ):
if len( potential_next_line ) == 1: # one very long word
lines.append( ' '.join( potential_next_line ) )
next_line = []
else:
lines.append( ' '.join( next_line ) )
next_line = [ word ]
else:
next_line = potential_next_line
if len( next_line ) > 0:
lines.append( ' '.join( next_line ) )
return lines

View File

@ -37,6 +37,7 @@ class JobKey( object ):
self._longer_pause_period = 1000
self._next_longer_pause = HydrusData.GetNow() + self._longer_pause_period
self._urls = []
self._variable_lock = threading.Lock()
self._variables = dict()
@ -91,6 +92,14 @@ class JobKey( object ):
def AddURL( self, url ):
with self._variable_lock:
self._urls.append( url )
def Begin( self ): self._begun.set()
def CanBegin( self ):
@ -110,11 +119,18 @@ class JobKey( object ):
return True
def Cancel( self ):
def Cancel( self, seconds = None ):
self._cancelled.set()
self.Finish()
if seconds is None:
self._cancelled.set()
self.Finish()
else:
wx.CallAfter( wx.CallLater, seconds * 1000, self.Cancel )
def Delete( self, seconds = None ):
@ -139,7 +155,17 @@ class JobKey( object ):
time.sleep( 0.00001 )
def Finish( self ): self._done.set()
def Finish( self, seconds = None ):
if seconds is None:
self._done.set()
else:
wx.CallAfter( wx.CallLater, seconds * 1000, self.Finish )
def GetIfHasVariable( self, name ):
@ -156,7 +182,18 @@ class JobKey( object ):
def GetKey( self ): return self._key
def GetKey( self ):
return self._key
def GetURLs( self ):
with self._variable_lock:
return list( self._urls )
def HasVariable( self, name ):

View File

@ -9,7 +9,7 @@ import threading
import time
import traceback
if HC.PLATFORM_WINDOWS: import mp3play
#if HC.PLATFORM_WINDOWS: import mp3play
parsed_noises = {}
@ -60,7 +60,8 @@ def GetWMADuration( path ):
length_in_ms = int( length_in_seconds * 1000 )
return length_in_ms
'''
def PlayNoise( name ):
if HC.PLATFORM_OSX: return
@ -80,4 +81,4 @@ def PlayNoise( name ):
noise = parsed_noises[ name ]
noise.play()
'''

View File

@ -44,7 +44,7 @@ options = {}
# Misc
NETWORK_VERSION = 17
SOFTWARE_VERSION = 231
SOFTWARE_VERSION = 232
UNSCALED_THUMBNAIL_DIMENSIONS = ( 200, 200 )

View File

@ -1,5 +1,6 @@
import os
class CancelledException( Exception ): pass
class CantRenderWithCVException( Exception ): pass
class DataMissing( Exception ): pass

BIN
static/hydrus_small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B