diff --git a/demo/agw/ThumbnailCtrl.py b/demo/agw/ThumbnailCtrl.py index 7ab5f8f3..f688e670 100644 --- a/demo/agw/ThumbnailCtrl.py +++ b/demo/agw/ThumbnailCtrl.py @@ -12,13 +12,13 @@ except: sys.path.append(os.path.split(dirName)[0]) -try: - from agw import thumbnailctrl as TC -except ImportError: # if it's not there locally, try the wxPython lib. - import wx.lib.agw.thumbnailctrl as TC +import wx.lib.agw.thumbnailctrl as TC import images +from wx.lib.agw.scrolledthumbnail import EVT_THUMBNAILS_SEL_CHANGED, EVT_THUMBNAILS_POINTED, \ +EVT_THUMBNAILS_DCLICK + class ThumbnailCtrlDemo(wx.Frame): @@ -32,7 +32,7 @@ class ThumbnailCtrlDemo(wx.Frame): self.statusbar = self.CreateStatusBar(2) self.statusbar.SetStatusWidths([-2, -1]) # statusbar fields - statusbar_fields = [("ThumbnailCtrl Demo, Andrea Gavana @ 10 Dec 2005"), + statusbar_fields = [("ThumbnailCtrl Demo, Michael Eager @ 15 Oct 2020"), ("Welcome To wxPython!")] for i in range(len(statusbar_fields)): @@ -46,6 +46,7 @@ class ThumbnailCtrlDemo(wx.Frame): sizer = wx.BoxSizer(wx.HORIZONTAL) scroll = TC.ThumbnailCtrl(splitter, -1, imagehandler=TC.NativeImageHandler) + #scroll = TC.ThumbnailCtrl(splitter, -1, imagehandler=TC.PILImageHandler) scroll.ShowFileNames() if os.path.isdir("../bitmaps"): @@ -74,6 +75,9 @@ class ThumbnailCtrlDemo(wx.Frame): self.enabletooltip = wx.CheckBox(self.panel, -1, "Enable thumb tooltips") self.textzoom = wx.TextCtrl(self.panel, -1, "1.4") self.zoombutton = wx.Button(self.panel, -1, "Set zoom factor") + self.textthumbwidth = wx.TextCtrl(self.panel, -1, "96") + self.textthumbheight = wx.TextCtrl(self.panel, -1, "80") + self.thumbsizebutton = wx.Button(self.panel, -1, "Set thumbnail size (WxH)") self.fontbutton = wx.Button(self.panel, -1, "Set caption font") self.colourbutton = wx.Button(self.panel, -1, "Set selection colour") @@ -100,18 +104,19 @@ class ThumbnailCtrlDemo(wx.Frame): self.Bind(wx.EVT_CHECKBOX, self.OnShowComboBox, self.showcombo) self.Bind(wx.EVT_CHECKBOX, self.OnEnableToolTips, self.enabletooltip) self.Bind(wx.EVT_BUTTON, self.OnSetZoom, self.zoombutton) + self.Bind(wx.EVT_BUTTON, self.OnSetThumbSize, self.thumbsizebutton) self.Bind(wx.EVT_BUTTON, self.OnSetFont, self.fontbutton) self.Bind(wx.EVT_BUTTON, self.OnSetColour, self.colourbutton) self.Bind(wx.EVT_BUTTON, self.OnSetDirectory, self.dirbutton) - self.TC.Bind(TC.EVT_THUMBNAILS_SEL_CHANGED, self.OnSelChanged) - self.TC.Bind(TC.EVT_THUMBNAILS_POINTED, self.OnPointed) - self.TC.Bind(TC.EVT_THUMBNAILS_DCLICK, self.OnDClick) + self.TC.Bind(EVT_THUMBNAILS_SEL_CHANGED, self.OnSelChanged) + self.TC.Bind(EVT_THUMBNAILS_POINTED, self.OnPointed) + self.TC.Bind(EVT_THUMBNAILS_DCLICK, self.OnDClick) - splitter.SplitVertically(scroll, self.panel, 180) + splitter.SplitVertically(scroll, self.panel, 400) splitter.SetMinimumPaneSize(140) - self.SetMinSize((700, 590)) + self.SetMinSize((1000, 1000)) self.CenterOnScreen() @@ -131,6 +136,7 @@ class ThumbnailCtrlDemo(wx.Frame): splitsizer = wx.BoxSizer(wx.VERTICAL) optionsizer = wx.StaticBoxSizer(self.optionsizer_staticbox, wx.VERTICAL) zoomsizer = wx.BoxSizer(wx.HORIZONTAL) + thumbsizesizer = wx.BoxSizer(wx.HORIZONTAL) customsizer = wx.StaticBoxSizer(self.customsizer_staticbox, wx.VERTICAL) thumbsizer = wx.StaticBoxSizer(self.thumbsizer_staticbox, wx.VERTICAL) radiosizer = wx.BoxSizer(wx.VERTICAL) @@ -153,7 +159,11 @@ class ThumbnailCtrlDemo(wx.Frame): splitsizer.Add(customsizer, 0, wx.TOP|wx.EXPAND|wx.LEFT, 5) zoomsizer.Add(self.textzoom, 1, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 3) zoomsizer.Add(self.zoombutton, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 3) + thumbsizesizer.Add(self.textthumbwidth, 1, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 3) + thumbsizesizer.Add(self.textthumbheight, 1, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 3) + thumbsizesizer.Add(self.thumbsizebutton, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 3) optionsizer.Add(zoomsizer, 1, wx.EXPAND, 0) + optionsizer.Add(thumbsizesizer, 1, wx.EXPAND, 0) optionsizer.Add(self.fontbutton, 0, wx.ALL|wx.ADJUST_MINSIZE, 3) optionsizer.Add(self.colourbutton, 0, wx.TOP|wx.LEFT|wx.ADJUST_MINSIZE, 3) splitsizer.Add(optionsizer, 0, wx.EXPAND | wx.TOP|wx.LEFT, 5) @@ -193,9 +203,10 @@ class ThumbnailCtrlDemo(wx.Frame): msg = "This Is The About Dialog Of The ThumbnailCtrl Demo.\n\n" + \ "Author: Andrea Gavana @ 10 Dec 2005\n\n" + \ + "Modified: Michael Eager @ 15 Oct 2020\n\n" + \ "Please Report Any Bug/Requests Of Improvements\n" + \ "To Me At The Following Addresses:\n\n" + \ - "andrea.gavana@agip.it\n" + "andrea_gavana@tin.it\n\n" + \ + "eager@eagercon.com\n\n" + \ "Welcome To wxPython " + wx.VERSION_STRING + "!!" dlg = wx.MessageDialog(self, msg, "ThumbnailCtrl Demo", @@ -362,6 +373,24 @@ class ThumbnailCtrlDemo(wx.Frame): event.Skip() + def OnSetThumbSize(self, event): + try: + width = int(self.textthumbwidth.GetValue().strip()) + height = int(self.textthumbheight.GetValue().strip()) + except: + errstr = "Error: thumb size must be integers (min 50x50)." + dlg = wx.MessageDialog(self, errstr, "ThumbnailCtrlDemo Error", + wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() + return + + width = max(width, 50) + height = max(height, 50) + self.log.write("OnSetThumbSize: (%s, %s)\n" % (width, height)) + self.TC.SetThumbSize (width, height) + + event.Skip() def OnSelChanged(self, event): diff --git a/wx/lib/agw/scrolledthumbnail.py b/wx/lib/agw/scrolledthumbnail.py new file mode 100644 index 00000000..340412d9 --- /dev/null +++ b/wx/lib/agw/scrolledthumbnail.py @@ -0,0 +1,2119 @@ +# --------------------------------------------------------------------------- # +# SCROLLEDTHUMBNAIL Control wxPython IMPLEMENTATION +# Python Code By: +# +# Michael Eager (eager@eagercon.com), 26 Sep 2020 +# +# Based on thumnailctrl.py by +# Andrea Gavana And Peter Damoc, @ 12 Dec 2005, revised 27 Dec 2012 +# andrea.gavana@gmail.com +# andrea.gavana@maerskoil.com +# +# Tags: phoenix-port, documented, unittest, py3-port +# +# End Of Comments +# --------------------------------------------------------------------------- # + +""" +:class:`ScrolledThumbnail` is a widget that can be used to display a series of +thumbnails for files in a scrollable window. + +Description +=========== + +:class:`ScrolledThumbnail` is a widget derived from :class:`wx.ScrolledWindow` +which will display the thumbnail image for a file in a scrollable, resizable +window. It supports selecting one or more thumbnails, rotating the thumbnails, +displaying information for each thumbnail, and popups for both the window and +for individual thumbnails. + +The class uses two support classes: :class:`Thumb` and :class:`ImageHandler`. + +:class:`Thumb` contains all of the information for a particular thumbnail, +including filename, bitmaped thumbnail image, caption, and other data. This +class also has methods to perform thumbnail operations such as rotation, +highlighting, setting a file name. + +:class:`ImageHandler` provides file/image handling functions, including loading +a file and creating an image from it, rotating an image, or highlighting an image. + +The implementations of these two classes included in this file support generating +thumbnails from supported image files, such as JPEG, GIF, PNG, etc., using either +WxPythons native support or the PIL image library. Additional file types can be +supported by extending these classes, for example, to provide a thumbnail image +for an MP4 file, perhaps the cover photo, an MPEG by providing the image of a +title frame, or a PDF by providing an image of the cover page. The images for +these files may be generated on the fly by a suitably extended :class:`ImageHandler`, +or may be provided with the instance of :class:`Thumb`. The list of `Thumb` +instances passed to `ScrolledThumbnail` may contain different derived classes, +as long as each contains the required funtions. + +NB: Use of :class:`ScrolledThumbnail` has not been tested with extended classes. + +:class:`ScrolledThumbnail`, :class:`Thumb`, and :class:`ImageHandler`, implemented +here are derived from the similarly named classes included in :class:`agw.ThumbnailCtrl`, +written by Andrea Gavana. That implementation was tightly integrated as a image +file browser application. The current implementation removes dependencies between +the several classes and narrows the scope of `ScrolledThumbnail` to the placement +and management of thumbnails within a window. + +An updated :class:`ThumbnailCtrl` which uses this implementation of +:class:`ScrolledThumbnail`, :class:`Thumb`, and :class:`ImageHandler` provides +all of the previous functionality, which described as a widget that can be used +to display a series of images in a "thumbnail" format; it mimics, for example, +the windows explorer behavior when you select the "view thumbnails" option. +Basically, by specifying a folder that contains some image files, the files +in the folder are displayed as miniature versions of the actual images in +a :class:`ScrolledWindow`. + +The code in the previous implementation is partly based on `wxVillaLib`, a +wxWidgets implementation of the :class:`ThumbnailCtrl` control. Andrea Gavana +notes that :class:`ThumbnailCtrl` wouldn't have been so fast and complete +without the suggestions and hints from Peter Damoc. + +Usage: +===== + +Usage example:: + + import os + import wx + from scrolledthumbnail import ScrolledThumbnail, Thumb, NativeImageHandler + + class MyFrame(wx.Frame): + + def __init__(self, parent): + wx.Frame.__init__(self, parent, -1, "ScrolledThumb Demo", size=(400,300)) + + self.scroll = ScrolledThumbnail(self, -1, size=(400,300)) + + def ShowDir(self, dir): + dir = os.getcwd() + files = os.listdir(dir) + thumbs = [] + for f in files: + if os.path.splitext(f)[1] in [".jpg", ".gif", ".png"]: + thumbs.append(Thumb(dir, f, caption=f, imagehandler=NativeImageHandler)) + self.scroll.ShowThumbs(thumbs) + + + app = wx.App(False) + frame = MyFrame(None) + frame.ShowDir(os.getcwd()) + frame.Show(True) + + app.MainLoop() + + + +Methods and Settings +==================== + +With :class:`ScrolledThumbnail` you can: + +- Create different thumbnail outlines (none, images only, full, etc...); +- Highlight thumbnails on mouse hovering; +- Show/hide file names below thumbnails; +- Change thumbnail caption font; +- Zoom in/out thumbnails (done via ``Ctrl`` key + mouse wheel or with ``+`` and ``-`` chars, + with zoom factor value customizable); +- Rotate thumbnails with these specifications: + + a) ``d`` key rotates 90 degrees clockwise; + b) ``s`` key rotates 90 degrees counter-clockwise; + c) ``a`` key rotates 180 degrees. + +- Drag and drop thumbnails from :class:`ScrolledThumbnail` to whatever application you want; +- Use local (when at least one thumbnail is selected) or global (no need for + thumbnail selection) popup menus; +- possibility to show tooltips on thumbnails, which display file information + (like file name, size, last modification date and thumbnail size). + +:note: Using highlight thumbnails on mouse hovering may be slow on slower + computers. + + +Window Styles +============= + +`No particular window styles are available for this class.` + + +Events Processing +================= + +This class processes the following events: + +This class processes the following events: + +================================== ================================================== +Event Name Description +================================== ================================================== +``EVT_THUMBNAILS_CAPTION_CHANGED`` The thumbnail caption has been changed. Not used at present. +``EVT_THUMBNAILS_DCLICK`` The user has double-clicked on a thumbnail. +``EVT_THUMBNAILS_POINTED`` The mouse cursor is hovering over a thumbnail. +``EVT_THUMBNAILS_SEL_CHANGED`` The user has changed the selected thumbnail. +``EVT_THUMBNAILS_THUMB_CHANGED`` The thumbnail of an image has changed. Used internally. +``EVT_THUMBNAILS_CHAR`` A character has been typed +================================== ================================================== + + +License And Version +=================== + +:class:`ScrolledThumbnail` is distributed under the wxPython license. + +Latest revision: Michael Eager @ 2 Oct 2020 + +Version 1.0 + +""" + +#---------------------------------------------------------------------- +# Beginning Of ThumbnailCtrl wxPython Code +#---------------------------------------------------------------------- + +import os +import wx +import six +import zlib +from math import radians + +from wx.lib.embeddedimage import PyEmbeddedImage + +if six.PY3: + import _thread as thread +else: + import thread + +#---------------------------------------------------------------------- +# Get Default Icon/Data +#---------------------------------------------------------------------- + +#---------------------------------------------------------------------- +file_broken = PyEmbeddedImage( + b"iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAAK/INwWK6QAADU9J" + b"REFUeJzNWn1QE2cefja7yRI+lAKiAgKCVIqegUYMFMsM4kfvzk7nmFrnvHGcOT3bq3P/OJ1p" + b"b+zYf+r0HGxt6VVFqpYqWq9TW23RVnp6R7FXiq0UPAGhyFeQSCQJISHZ7Mf9gZuGkE02IeA9" + b"M5k3u/vuu+/z/H7v7/297y4BPzh//vxfSJIsIgjC5a/eTIPn+UnHgiDIqisIAsUwTNfmzZtf" + b"A8D7qk/5e3B0dPS6wsLCp/09MFTIbdO7ntR9vs5HRESgvr6+E0AlAD2AKZX8CsDzvEKtVsvq" + b"aKjw7LggCO7jcJQqlQoMw6gBpAIYRAgCECGxCgDPTsohEopI4n+e5xXww1MRHkry4d1Zf9fk" + b"uv90MOsCeMKfGN51pO6ZrmizKsBsWt976EjhoQ+BYMQItpSDWRNAjptPZ4xLCfl/PQTCaX2p" + b"+wNhVgSYbet7tumdRXpj1ofAbFhfqn1fmHEBHpb1AYAgAudxMy4AQRB+LReuSB/MGsETMyqA" + b"2WyG1WqVZQl/CDUZeuge0NvbKwwODk7bC7zryYFI/qF6QGNj42BnZ6dZ6rocQtNNhQNhxgQw" + b"Go24fPnyDy0tLTcFQfDpBZ7/w2198RcIMyZAW1vb+KVLlxoGBwfr7927B4Vi8qO8CfnqbCjW" + b"D4Y8MEMCCIKA77777me73d7a1tb25dDQkNXzmud/giDgdDrBsqzkuA1l7CuVSgCA0+n0y3FG" + b"BBgYGMCVK1eub926dRFN04aBgYH/el73JKRUKjE4OIi+vj5QFDWtaVG0fGRkJHiex7lz5xyn" + b"T59uBMBI9TWsAoidaW1tNTc1Nd1cv379zuzs7F91dHT8x+l0TooDnuju7h779ttvjdOdLlUq" + b"FaKjo9HV1YX9+/d379q16+QXX3xxFsAAMHU7DAgggNw1tQiCIOByuXDt2rWbK1asUOfk5KxM" + b"SUlZW19f39LX18eTJDmpbbG8cePGndra2mtDQ0NQKpUhJUPR0dHgeR6ffvqpbdeuXV+9/vrr" + b"R4eGhs4A+ArA3ZAECAXd3d24dOnSD2VlZavy8vKQmppaUFdXN2gwGO561yVJEmazGR0dHV3n" + b"z5//+NatW0MU5XebcgooikJMTAy6urqwb9++zp07d1ZfvXq1GsAn8+bNa6qsrEyGxJY4EGBT" + b"lCAIhVR0ZhgGTqcTTqcTDocDDocDgiDgm2++0Y+MjNxbuXLl7wmCQFpaWtbcuXMje3p6fly9" + b"enWyeL8gCCBJEj09PUJLS0s7z/PXWlpavigoKNhBURRYlnU/S6qMjIwEwzD47LPPbIcOHWq4" + b"cuXKPwF8D+DHF154QV1cXFxpsVjiAGwGYIUPL/ArgNFoNNbV1dmtVqvdarW6LBaLY2xszOFw" + b"OBiLxWKz2WxOs9lsHx0ddZhMJpvZbB7v7u7+effu3elZWVmJAJCUlBS1bt26nNbW1kaj0fj0" + b"I4884iYHAHfu3Lnf2dnZDWC4oaHhH08++eRWrVZLe9bxJk9RFNRqNTo6OvDhhx92Hj16tG5k" + b"ZKQBwHUAneXl5cVarfaQVqtddurUqesAUgC0BysAsWfPngOjo6ONY2NjUQzDOACMA7ADcGAi" + b"sjoBuB6U4jFTWlp6Kj4+HjzPIzExEbm5uSuPHz9+csuWLfaEhIRIl8sFgiDA8zxu3rz585Yt" + b"W3SpqanX9u7d+93GjRuv5eXlrRGvT+oQQUCtVsPhcODcuXO2w4cPi1ZvAnADgP3EiRN7NBrN" + b"ntzcXDXHcXA6nREAJF9u+BNA6OnpuQ1gGAD5gCjrUXIPFOUflAIA7siRI8Xp6ekaYOI1lVKp" + b"RGZmpqatre2QXq/v1mg0y4EJKxoMBrS1tQ2UlJQ8abPZjAD23Lhx46Pi4uI1aWlp7mEl1qdp" + b"Grdu3UJNTU1nVVVV3cjIyDUAPwJof+WVVzJ0Ol1NQUHBbxMTEyHOOoEQKOKMP/jJxoIFC/6Q" + b"mZlJidYTBAHp6emp2dnZCV1dXY0MwywnCAIkSUKv1zu7u7uNu3fvTh8aGtoE4Pj7779/ec2a" + b"NZ0ZGRlZwC9Wt9vt+Pzzz22VlZUNV69eFa3+EwDTkSNHynJyct5ZtWpVikKhgMPhgEKhkJx2" + b"gxFgSv1t27ZRUVFRFMuyZGZmZmJcXNwcpVIZT9N0tMvlWpiSklKmVConBbGkpCRq3bp1Kxsb" + b"G69v3Lhxe3p6OgRBQHt7u2Hx4sVRycnJ9Ny5czO3bdv2VHV19XvNzc2frF69+pXY2FgoFAop" + b"q3ds2rQp6plnnnkzLy9v99KlS8EwDDiOC2r5LSnAiRMnIubNm/dHkiSzaZqOIUkygabpGIqi" + b"4iiKmkuSZDRJkiqVSkWRJKmkaZqMiIhAZGQkOI5zk+c4DnFxccjOztZWVVV9+eKLLw5nZGTM" + b"YxgGzc3Ndx577LGF8fHxiI2NhU6n+111dfWZixcvnispKfmzTqebe+HCBW+rtwK4/8Ybb+Rp" + b"NJojBQUFq2JiYuB0OgH8kgqLpdiXoAVobW2NfvbZZ/cWFhbOly3ngwf6yvczMzNzWJZV9Pb2" + b"/lRUVLR2cHAQt2/fvrt9+3YtSZJQKBRYtmxZYXFx8ar6+vqrTU1NF7/++mtdZWXll0ajsQET" + b"Qa4TE3HmBY1Gsz8/P3+Oy+Vyj3dP8nK9QFKA/v5+Cn7GfzBZmiAISE5OTiwqKsq4fft2k91u" + b"X9vf3283GAyWtLS0ZNFTsrOzI0pKSsrq6+v//c477/xNr9evwMRr7ZsAhl966aUFOp3uzfz8" + b"/C2LFi3C+Pj4JMLeAsjJYiUFYFk2oIRyReA4DikpKSgqKlpZW1t7saysjOvo6NAvXbo0NjEx" + b"MZLneXAchzlz5kCj0TwVGxu7RK/Xt2Mihx8DwBw4cGBDbm5uRWFh4aNKpRJ2u30S8VCsD8hY" + b"CwRzXqouz/OIiopCVlaWtrm5+X57e/tAS0uLPicnJzEhIcE9TnmeR05OTvJzzz33a0xMtyNa" + b"rVY4fvz4a6WlpRdKSkoeFZfPYpSXIi93SyzYWWASMTmlZ/2MjIys+Pj4mI6Ojoa+vj5h/fr1" + b"xaKrikIlJyfj8ccfLwNw7NVXX43TarV/LyoqWhsXF4fx8XF3TPFF2Pv/tPIAu93ul7g/+BJD" + b"EAQsXLgwqrS0NLexsfE8RVHLFi1atNn7PpVKhdzc3OUvv/zyvg0bNvwmPz8/WeyPt8sHEkLO" + b"anZaQyCYDUmO47BgwQIsX758VW1t7bc6nU5ISkpSeuYLHMeBYRhkZWWpd+zY8afCwsJklmXB" + b"MMwUlw9EXjwOhIDL4dHRUQwMDMBms4Hn+YCuJSUKz/NQqVRYsmTJcgDzlixZsnzOnDlu1xfj" + b"AMdxoGkaqampsNvtEATBvZ8olzxBEO57whIDxsbGMD4+DrVajaioKERGRoKiKMmdXn9IT09P" + b"Li4ufiIxMVEHACzLThGW47gp54IhHwz8CiCSEt3P4XDA6XTCYrGApmnQNA2VSuUWwzvyescA" + b"l8uF+fPnK59//vm/zp8/f6FoebF98VlSBOWS99WXkATwfrhnw+JmiEKhgEKhAEmSEDM6KTEI" + b"gkBcXBwRExOTkpCQAJfLNSPkg4EsD/Algncs4Hl+ktv6+onjOS0tDTRNT1q4hJN8MCKE5AFS" + b"nQx0DQAYhkFqaqpbrJkmH5YPJMJFXqFQTBmTM0E+LB5gt9slXTpU8t4E5JKXSnsDkfV+HReU" + b"AJ6YLnlf1pNLXhAEmM1msCw7KSZ598/7PEEQ4DgOLMv6VSHQt8JhIS/HG/y5vV6vh9FohFqt" + b"ducN3r8pxCgKJpMJvb29o/hlzzI4AUSEk7yUtfyJwTAMMjMzsXjxYr/zuuf7wbt376K8vLz/" + b"7NmzlzCxpA5NgHCTl1vHFzkAPjNE774aDAaUl5f3V1RU1HAc9y9MbKr4RMA8QIr4TJP3LEU3" + b"5zjOnTtItWUwGLB//36R/CVMbKXZQhbAV2dnk7zYD3G1KI537/oURbndvqKi4hTHcV9iYvd4" + b"zB/HkFPh6ZL3JurvHIBJXuB9XfzG4MCBA/3vvvtujVzysgTwR2I65KVmAl/3iUtmlmUnDQGR" + b"/P3793H48GH9A/Ki2wckH1AAzxjgS4yZJO/dD899A7EORVEYGRlBVVWV8b333vs4GMvLEsAT" + b"oQ4Ff+Q92/YniGcMEAUQ5/ljx44Nv/XWWx9ZrdaLAJqDIR9QAO/9gHCTlxsERRFEAZRKJUwm" + b"E6qrq4cPHjx4xmq11mLi1bglGPIBBfBF8mGQFyHmACaTCSdPnhx+++23z4yOjtZi4pWZKVjy" + b"sgTwFiIU8v7GuVzyIsxmM06fPj188ODBjx5Y/nsAkl+jBoLsPECEryDl7ziQMHLJkyQJi8WC" + b"mpqa4YqKCtHtmzAN8oCM9wJKpRIRERGyyAQi6CvwyeokRcFsNqOurm64oqLijMVimZbbT2pb" + b"6gJN04TNZlNZLBYwDOOeEgNtMsqpF+y1sbEx1NbWDn/wwQdhJQ8AkmZYsWJFVEZGxtZ79+49" + b"wbIsDa/VlBQJqS0of6QD3UMQBHp7e++YTKYrCIPbe8KfHyoALACwGIAqXA8MEQImPnO7A2Ak" + b"nA3/D+/OyD/Ur3BPAAAAAElFTkSuQmCC") + + +def getDataSH(): + """ Return the first part of the shadow dropped behind thumbnails. """ + + return zlib.decompress( +b'x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2_A\x98\x83\rHvl\ +\xdc\x9c\n\xa4X\x8a\x9d float(height)/imgheight: + scale = float(height)/imgheight + + newW, newH = int(imgwidth*scale), int(imgheight*scale) + if newW < 1: + newW = 1 + if newH < 1: + newH = 1 + + img = img.Scale(newW, newH) + + return img + + + def GetBitmap(self, width, height): + """ + Returns the bitmap of the thumbnail + + :param `width`: the associated bitmap width; + :param `height`: the associated bitmap height. + """ + + if self._bitmap: + if self._bitmap.GetWidth() == width and self._bitmap.GetHeight() == height: + return self._bitmap + + img = self.GetThumbnail(width, height) + bmp = img.ConvertToBitmap() + + return bmp + + + def GetFullFileName(self): + """ Returns the full filename of the thumbnail. """ + + return os.path.join(self._dir, self._filename) + + + def GetCaption(self, line): + """ + Returns the caption associated to a thumbnail. + + :param `line`: the caption line we wish to retrieve (useful for multilines + caption strings). + """ + + if line + 1 >= len(self._captionbreaks): + return "" + + strs = self._caption + + return strs + + + def GetFileSize(self): + """ Returns the file size in bytes associated to a thumbnail. """ + + return self._filesize + + + def GetDisplayFileSize(self): + """ Return printable file size (with bytes, Kb, Mb suffix). """ + + size = self.GetFileSize() + if size < 1000: + size = str(size) + " bytes" + elif size < 1000000: + size = str(int(round(size/1000.0))) + " Kb" + else: + size = str(round(size/1000000.0, 2)) + " Mb" + return size + + + def GetCreationDate(self): + """ Returns the file last modification date associated to a thumbnail. """ + + return self._lastmod + + + def GetOriginalSize(self): + """ Returns a tuple containing the original image width and height, in pixels. """ + + return self._originalsize + + + def GetCaptionLinesCount(self, width): + """ + Returns the number of lines for the caption. + + :param `width`: the maximum width, in pixels, available for the caption text. + """ + + self.BreakCaption(width) + return len(self._captionbreaks) - 1 + + + def BreakCaption(self, width): + """ + Breaks the caption in several lines of text (if needed). + + :param `width`: the maximum width, in pixels, available for the caption text. + """ + + if len(self._captionbreaks) > 0 or width < 16: + return + + self._captionbreaks.append(0) + + if len(self._caption) == 0: + return + + pos = width//16 + beg = 0 + end = 0 + + dc = wx.MemoryDC() + bmp = wx.Bitmap(10, 10) + dc.SelectObject(bmp) + + while 1: + + if pos >= len(self._caption): + + self._captionbreaks.append(len(self._caption)) + break + + sw, sh = dc.GetTextExtent(self._caption[beg:pos-beg]) + + if sw > width: + + if end > 0: + + self._captionbreaks.append(end) + beg = end + + else: + + self._captionbreaks.append(pos) + beg = pos + + pos = beg + width//16 + end = 0 + + if pos < len(self._caption) and self._caption[pos] in [" ", "-", ",", ".", "_"]: + end = pos + 1 + + pos = pos + 1 + + + dc.SelectObject(wx.NullBitmap) + + + def GetInfo(self): + """ Returns info for thumbnain in display format. """ + thumbinfo = "Name: " + self.GetFileName() + "\n" \ + "Size: " + self.GetDisplayFileSize() + "\n" \ + "Modified: " + self.GetCreationDate() + "\n" \ + "Dimensions: " + str(self.GetOriginalSize()) + "\n" + return thumbinfo + + + def LoadImage(self): + """ Load image using imagehandler. """ + filename = self.GetFullFileName() + img, size, alpha = self._imagehandler.Load(filename) + self._image = img + self._originalsize = size + self._alpha = alpha + + + def Rotate(self, angle): + """ Rotate image using imagehandler. """ + img = self._imagehandler.Rotate(self._image, angle) + self._image = img + self._originalsize = (img.GetWidth, img.GetHeight) + self._alpha = img.HasAlpha() + # Clear _bitmap so thumbnail is recreated after rotate + self._bitmap = None + + + def GetHighlightBitmap(self, width, height, factor): + """ Returned highlighted bitmap of thumbnail. """ + + img = self.GetThumbnail(width, height) + img = self._imagehandler.HighlightImage(img, factor) + + bmp = img.ConvertToBitmap() + + return bmp + +# ---------------------------------------------------------------------------- # +# Class ScrolledThumbnail +# This Is The Main Class Implementation +# ---------------------------------------------------------------------------- # + +class ScrolledThumbnail(wx.ScrolledWindow): + """ This is the main class implementation of :class:`ThumbnailCtrl`. """ + + def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, + size=wx.DefaultSize, thumboutline=THUMB_OUTLINE_IMAGE, + imagehandler=None): + """ + Default class constructor. + + :param `parent`: parent window. Must not be ``None``; + :param `id`: window identifier. A value of -1 indicates a default value; + :param `pos`: the control position. A value of (-1, -1) indicates a default position, + chosen by either the windowing system or wxPython, depending on platform; + :param `size`: the control size. A value of (-1, -1) indicates a default size, + chosen by either the windowing system or wxPython, depending on platform; + :param `thumboutline`: outline style for :class:`ScrolledThumbnail`, which may be: + + =========================== ======= ================================== + Outline Flag Value Description + =========================== ======= ================================== + ``THUMB_OUTLINE_NONE`` 0 No outline is drawn on selection + ``THUMB_OUTLINE_FULL`` 1 Full outline (image+caption) is drawn on selection + ``THUMB_OUTLINE_RECT`` 2 Only thumbnail bounding rectangle is drawn on selection (default) + ``THUMB_OUTLINE_IMAGE`` 4 Only image bounding rectangle is drawn. + =========================== ======= ================================== + + :param `imagehandler`: can be :class:`PILImageHandler` if PIL is installed (faster), or + :class:`NativeImageHandler` which only uses wxPython image methods. + """ + + wx.ScrolledWindow.__init__(self, parent, id, pos, size) + + self._items = [] + self.SetThumbSize(96, 80) + self._tOutline = thumboutline + self._selected = -1 + self._pointed = -1 + self._pmenu = None + self._gpmenu = None + self._dragging = False + self._checktext = False + self._dropShadow = True + + self._tCaptionHeight = [] + self._selectedarray = [] + self._tTextHeight = 16 + self._tCaptionBorder = 8 + self._tOutlineNotSelected = True + self._mouseeventhandled = False + self._highlight = False + self._zoomfactor = 1.4 + self.SetCaptionFont() + + self._enabletooltip = False + + self._parent = parent + + self._selectioncolour = "#009EFF" + self.grayPen = wx.Pen("#A2A2D2", 1, wx.SHORT_DASH) + self.grayPen.SetJoin(wx.JOIN_MITER) + self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_LISTBOX)) + + t, b, s = getShadow() + self.shadow = wx.MemoryDC() + self.shadow.SelectObject(s) + + self.ShowFileNames(True) + + self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown) + self.Bind(wx.EVT_LEFT_UP, self.OnMouseUp) + self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseDClick) + self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseDown) + self.Bind(wx.EVT_RIGHT_UP, self.OnMouseUp) + self.Bind(wx.EVT_MOTION, self.OnMouseMove) + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave) + self.Bind(EVT_THUMBNAILS_THUMB_CHANGED, self.OnThumbChanged) + self.Bind(wx.EVT_CHAR, self.OnChar) + self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) + + self.Bind(wx.EVT_SIZE, self.OnResize) + self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + + def GetSelectedItem(self, index): + """ + Returns the selected thumbnail. + + :param `index`: the thumbnail index (i.e., the selection). + """ + + return self.GetItem(self.GetSelection(index)) + + + def GetPointed(self): + """ Returns the pointed thumbnail index. """ + + return self._pointed + + + def GetHighlightPointed(self): + """ + Returns whether the thumbnail pointed should be highlighted or not. + + :note: Please be aware that this functionality may be slow on slower computers. + """ + + return self._highlight + + + def SetHighlightPointed(self, highlight=True): + """ + Sets whether the thumbnail pointed should be highlighted or not. + + :param `highlight`: ``True`` to enable highlight-on-point with the mouse, + ``False`` otherwise. + + :note: Please be aware that this functionality may be slow on slower computers. + """ + + self._highlight = highlight + + + def SetThumbOutline(self, outline): + """ + Sets the thumbnail outline style on selection. + + :param `outline`: the outline to use on selection. This can be one of the following + bits: + + =========================== ======= ================================== + Outline Flag Value Description + =========================== ======= ================================== + ``THUMB_OUTLINE_NONE`` 0 No outline is drawn on selection + ``THUMB_OUTLINE_FULL`` 1 Full outline (image+caption) is drawn on selection + ``THUMB_OUTLINE_RECT`` 2 Only thumbnail bounding rectangle is drawn on selection (default) + ``THUMB_OUTLINE_IMAGE`` 4 Only image bounding rectangle is drawn. + =========================== ======= ================================== + + """ + + if outline not in [THUMB_OUTLINE_NONE, THUMB_OUTLINE_FULL, THUMB_OUTLINE_RECT, + THUMB_OUTLINE_IMAGE]: + return + + self._tOutline = outline + + + def GetThumbOutline(self): + """ + Returns the thumbnail outline style on selection. + + :see: :meth:`~ScrolledThumbnail.SetThumbOutline` for a list of possible return values. + """ + + return self._tOutline + + + def SetDropShadow(self, drop): + """ + Sets whether to drop a shadow behind thumbnails or not. + + :param `drop`: ``True`` to drop a shadow behind each thumbnail, ``False`` otheriwise. + """ + + self._dropShadow = drop + self.Refresh() + + + def GetDropShadow(self): + """ + Returns whether to drop a shadow behind thumbnails or not. + """ + + return self._dropShadow + + + def GetPointedItem(self): + """ Returns the pointed thumbnail. """ + + return self.GetItem(self._pointed) + + + def GetItem(self, index): + """ + Returns the item at position `index`. + + :param `index`: the thumbnail index position. + """ + + return index >= 0 and (index < len(self._items) and [self._items[index]] or [None])[0] + + + def GetItemCount(self): + """ Returns the number of thumbnails. """ + + return len(self._items) + + + def GetThumbWidth(self): + """ Returns the thumbnail width. """ + + return self._tWidth + + + def GetThumbHeight(self): + """ Returns the thumbnail height. """ + + return self._tHeight + + + def GetThumbBorder(self): + """ Returns the thumbnail border. """ + + return self._tBorder + + + def ShowFileNames(self, show=True): + """ + Sets whether the user wants to show file names under the thumbnails or not. + + :param `show`: ``True`` to show file names under the thumbnails, ``False`` otherwise. + """ + + self._showfilenames = show + self.Refresh() + + + def SetPopupMenu(self, menu): + """ + Sets the thumbnails popup menu when at least one thumbnail is selected. + + :param `menu`: an instance of :class:`wx.Menu`. + """ + + self._pmenu = menu + + + def GetPopupMenu(self): + """ Returns the thumbnails popup menu when at least one thumbnail is selected. """ + + return self._pmenu + + + def SetGlobalPopupMenu(self, gpmenu): + """ + Sets the global thumbnails popup menu (no need of thumbnail selection). + + :param `gpmenu`: an instance of :class:`wx.Menu`. + """ + + self._gpmenu = gpmenu + + + def GetGlobalPopupMenu(self): + """ Returns the global thumbnailss popup menu (no need of thumbnail selection). """ + + return self._gpmenu + + + def GetSelectionColour(self): + """ Returns the colour used to indicate a selected thumbnail. """ + + return self._selectioncolour + + + def SetSelectionColour(self, colour=None): + """ + Sets the colour used to indicate a selected thumbnail. + + :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, it + will be taken from the system settings. + """ + + if colour is None: + colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) + + self._selectioncolour = colour + + + def EnableDragging(self, enable=True): + """ + Enables/disables thumbnails drag and drop. + + :param `enable`: ``True`` to enable drag and drop, ``False`` to disable it. + """ + + self._dragging = enable + + + def EnableToolTips(self, enable=True): + """ + Globally enables/disables thumbnail file information. + + :param `enable`: ``True`` to enable thumbnail file information, ``False`` to disable it. + """ + + self._enabletooltip = enable + + if not enable and hasattr(self, "_tipwindow"): + self._tipwindow.Enable(False) + + + def GetThumbInfo(self, thumb=-1): + """ + Returns the thumbnail information. + + :param `thumb`: the index of the thumbnail for which we are collecting information. + """ + + thumbinfo = None + + if thumb >= 0: + thumbinfo = self._items[thumb].GetInfo() + \ + "Thumb: " + str(self.GetThumbSize()[0:2]) + return thumbinfo + + def SetThumbSize(self, width, height, border=6): + """ + Sets the thumbnail size as width, height and border. + + :param `width`: the desired thumbnail width; + :param `height`: the desired thumbnail height; + :param `border`: the spacing between thumbnails. + """ + + if width > 350 or height > 280: + return + + self._tWidth = width + self._tHeight = height + self._tBorder = border + self.SetScrollRate((self._tWidth + self._tBorder)/4, + (self._tHeight + self._tBorder)/4) + self.SetSizeHints(self._tWidth + self._tBorder*2 + 16, + self._tHeight + self._tBorder*2 + 8) + if self._items: + self.UpdateShow() + + + def GetThumbSize(self): + """ Returns the thumbnail size as width, height and border. """ + + return self._tWidth, self._tHeight, self._tBorder + + + def Clear(self): + """ Clears :class:`ThumbnailCtrl`. """ + + self._items = [] + self._selected = -1 + self._selectedarray = [] + self.UpdateProp() + self.Refresh() + + + def ThreadImage(self, filenames): + """ + Threaded method to load images. Used internally. + + :param `filenames`: a Python list of file names containing images. + """ + + count = 0 + + while count < len(filenames): + + if not self._isrunning: + self._isrunning = False + thread.exit() + return + + self.LoadImages(filenames[count], count) + if count < 4: + self.Refresh() + elif count%4 == 0: + self.Refresh() + + count = count + 1 + + self._isrunning = False + thread.exit() + + + def LoadImages(self, newfile, imagecount): + """ + Threaded method to load images. Used internally. + + :param `newfile`: a file name containing an image to thumbnail; + :param `imagecount`: the number of images loaded until now. + """ + + if not self._isrunning: + thread.exit() + return + + self._items[imagecount].LoadImage() + + + def ShowThumbs(self, thumbs): + """ + Shows all the thumbnails. + + :param `thumbs`: should be a sequence with instances of :class:`Thumb`; + """ + + self._isrunning = False + + # update items + self._items = thumbs + myfiles = [thumb.GetFullFileName() for thumb in thumbs] + + self._isrunning = True + + thread.start_new_thread(self.ThreadImage, (myfiles,)) + wx.MilliSleep(20) + + self._selectedarray = [] + self.UpdateProp() + self.Refresh() + + + + def SetSelection(self, value=-1): + """ + Sets thumbnail selection. + + :param `value`: the thumbnail index to select. + """ + + self._selected = value + + if value != -1: + self._selectedarray = [value] + eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_SEL_CHANGED, self.GetId()) + self.GetEventHandler().ProcessEvent(eventOut) + self.ScrollToSelected() + self.Refresh() + + + def SetZoomFactor(self, zoom=1.4): + """ + Sets the zoom factor. + + :param `zoom`: a floating point number representing the zoom factor. Must be + greater than or equal to 1.0. + """ + + if zoom <= 1.0: + raise Exception("\nERROR: Zoom Factor Must Be Greater Than 1.0") + + self._zoomfactor = zoom + + + def GetZoomFactor(self): + """ Returns the zoom factor. """ + + return self._zoomfactor + + + def UpdateItems(self): + """ Updates thumbnail items. """ + + selected = self._selectedarray + selectedfname = [] + selecteditemid = [] + + for ii in range(len(self._selectedarray)): + selectedfname.append(self.GetSelectedItem(ii).GetFileName()) + selecteditemid.append(self.GetSelectedItem(ii).GetId()) + + self.UpdateShow() + + if len(selected) > 0: + self._selectedarray = [] + for ii in range(len(self._items)): + for jj in range(len(selected)): + if self._items[ii].GetFileName() == selectedfname[jj] and \ + self._items[ii].GetId() == selecteditemid[jj]: + + self._selectedarray.append(ii) + if len(self._selectedarray) == 1: + self.ScrollToSelected() + + if len(self._selectedarray) > 0: + self.Refresh() + eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_SEL_CHANGED, self.GetId()) + self.GetEventHandler().ProcessEvent(eventOut) + + + def SetCaptionFont(self, font=None): + """ + Sets the font for all the thumbnail captions. + + :param `font`: a valid :class:`wx.Font` object. If defaulted to ``None``, a standard + font will be generated. + """ + + if font is None: + font = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False) + + self._captionfont = font + + + def GetCaptionFont(self): + """ Returns the font for all the thumbnail captions. """ + + return self._captionfont + + + def UpdateShow(self): + """ Updates thumbnail items. """ + + self.ShowThumbs(self._items) + + + def GetCaptionHeight(self, begRow, count=1): + """ + Returns the height for the file name caption. + + :param `begRow`: the caption line at which we start measuring the height; + :param `count`: the number of lines to measure. + """ + + capHeight = 0 + for ii in range(begRow, begRow + count): + if ii < len(self._tCaptionHeight): + capHeight = capHeight + self._tCaptionHeight[ii] + + return capHeight*self._tTextHeight + + + def GetItemIndex(self, x, y): + """ + Returns the thumbnail index at position (x, y). + + :param `x`: the mouse `x` position; + :param `y`: the mouse `y` position. + """ + + col = (x - self._tBorder)//(self._tWidth + self._tBorder) + + if col >= self._cols: + col = self._cols - 1 + + row = -1 + y = y - self._tBorder + + while y > 0: + + row = row + 1 + y = y - (self._tHeight + self._tBorder + self.GetCaptionHeight(row)) + + if row < 0: + row = 0 + + index = row*self._cols + col + + if index >= len(self._items): + index = -1 + + return index + + + def UpdateProp(self, checkSize=True): + """ + Updates :class:`ThumbnailCtrl` and its visible thumbnails. + + :param `checkSize`: ``True`` to update the items visibility if the window + size has changed. + """ + + width = self.GetClientSize().GetWidth() + self._cols = (width - self._tBorder)//(self._tWidth + self._tBorder) + + if self._cols <= 0: + self._cols = 1 + + tmpvar = (len(self._items)%self._cols and [1] or [0])[0] + self._rows = len(self._items)//self._cols + tmpvar + + self._tCaptionHeight = [] + + for row in range(self._rows): + + capHeight = 0 + + for col in range(self._cols): + + ii = row*self._cols + col + + if len(self._items) > ii and \ + self._items[ii].GetCaptionLinesCount(self._tWidth - self._tCaptionBorder) > capHeight: + + capHeight = self._items[ii].GetCaptionLinesCount(self._tWidth - self._tCaptionBorder) + + self._tCaptionHeight.append(capHeight) + + self.SetVirtualSize((self._cols*(self._tWidth + self._tBorder) + self._tBorder, + self._rows*(self._tHeight + self._tBorder) + \ + self.GetCaptionHeight(0, self._rows) + self._tBorder)) + + self.SetSizeHints(self._tWidth + 2*self._tBorder + 16, + self._tHeight + 2*self._tBorder + 8 + \ + (self._rows and [self.GetCaptionHeight(0)] or [0])[0]) + + if checkSize and width != self.GetClientSize().GetWidth(): + self.UpdateProp(False) + + + def GetItem(self, pos): + """ + Return thumbnail at specified position. + + :param pos: the index of the thumbnail + :return: the Thumb + """ + + return self._items[pos] + + def InsertItem(self, thumb, pos): + """ + Inserts a thumbnail in the specified position. + + :param `pos`: the index at which we wish to insert the new thumbnail. + """ + + if pos < 0 or pos > len(self._items): + self._items.append(thumb) + else: + self._items.insert(pos, thumb) + + self.UpdateProp() + + + def RemoveItemAt(self, pos): + """ + Removes a thumbnail at the specified position. + + :param `pos`: the index at which we wish to remove the thumbnail. + """ + + del self._items[pos] + + self.UpdateProp() + + + def GetPaintRect(self): + """ Returns the paint bounding rect for the :meth:`~ScrolledThumbnail.OnPaint` method. """ + + size = self.GetClientSize() + paintRect = wx.Rect(0, 0, size.GetWidth(), size.GetHeight()) + paintRect.x, paintRect.y = self.GetViewStart() + xu, yu = self.GetScrollPixelsPerUnit() + paintRect.x = paintRect.x*xu + paintRect.y = paintRect.y*yu + + return paintRect + + + def IsSelected(self, indx): + """ + Returns whether a thumbnail is selected or not. + + :param `indx`: the index of the thumbnail to check for selection. + """ + + return self._selectedarray.count(indx) != 0 + + + def GetSelection(self, selIndex=-1): + """ + Returns the selected thumbnail. + + :param `selIndex`: if not equal to -1, the index of the selected thumbnail. + """ + + return (selIndex == -1 and [self._selected] or + [self._selectedarray[selIndex]])[0] + + + def ScrollToSelected(self): + """ Scrolls the :class:`ScrolledWindow` to the selected thumbnail. """ + + if self.GetSelection() == -1: + return + + # get row + row = self.GetSelection()//self._cols + # calc position to scroll view + + paintRect = self.GetPaintRect() + y1 = row*(self._tHeight + self._tBorder) + self.GetCaptionHeight(0, row) + y2 = y1 + self._tBorder + self._tHeight + self.GetCaptionHeight(row) + + if y1 < paintRect.GetTop(): + sy = y1 # scroll top + elif y2 > paintRect.GetBottom(): + sy = y2 - paintRect.height # scroll bottom + else: + return + + # scroll view + xu, yu = self.GetScrollPixelsPerUnit() + sy = sy/yu + (sy%yu and [1] or [0])[0] # convert sy to scroll units + x, y = self.GetViewStart() + + self.Scroll(x,sy) + + + def CalculateBestCaption(self, dc, caption, sw, width): + """ + Calculates the best caption string to show based on the actual zoom factor. + + :param `dc`: an instance of :class:`wx.DC`; + :param `caption`: the original caption string; + :param `sw`: the maximum width allowed for the caption string, in pixels; + :param `width`: the caption string width, in pixels. + """ + + caption = caption + "..." + + while sw > width: + caption = caption[1:] + sw, sh = dc.GetTextExtent(caption) + + return "..." + caption[0:-3] + + + def DrawThumbnail(self, bmp, thumb, index): + """ + Draws a visible thumbnail. + + :param `bmp`: the thumbnail version of the original image; + :param `thumb`: an instance of :class:`Thumb`; + :param `index`: the index of the thumbnail to draw. + """ + + dc = wx.MemoryDC() + dc.SelectObject(bmp) + + x = self._tBorder/2 + y = self._tBorder/2 + + # background + dc.SetPen(wx.Pen(wx.BLACK, 0, wx.TRANSPARENT)) + dc.SetBrush(wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID)) + dc.DrawRectangle(0, 0, bmp.GetWidth(), bmp.GetHeight()) + + # image + if index == self.GetPointed() and self.GetHighlightPointed(): + factor = 1.5 + img = thumb.GetHighlightBitmap(self._tWidth, self._tHeight, factor) + else: + img = thumb.GetBitmap(self._tWidth, self._tHeight) + + ww = img.GetWidth() + hh = img.GetHeight() + imgRect = wx.Rect(x + (self._tWidth - img.GetWidth())/2, + y + (self._tHeight - img.GetHeight())/2, + img.GetWidth(), img.GetHeight()) + + if not thumb._alpha and self._dropShadow: + dc.Blit(imgRect.x+5, imgRect.y+5, imgRect.width, imgRect.height, self.shadow, 500-ww, 500-hh) + dc.DrawBitmap(img, imgRect.x, imgRect.y, True) + + colour = self.GetSelectionColour() + selected = self.IsSelected(index) + + colour = self.GetSelectionColour() + + # draw caption + sw, sh = 0, 0 + if self._showfilenames: + textWidth = 0 + dc.SetFont(self.GetCaptionFont()) + mycaption = thumb.GetCaption(0) + sw, sh = dc.GetTextExtent(mycaption) + + if sw > self._tWidth: + mycaption = self.CalculateBestCaption(dc, mycaption, sw, self._tWidth) + sw = self._tWidth + + textWidth = sw + 8 + tx = x + (self._tWidth - textWidth)/2 + ty = y + self._tHeight + + txtcolour = "#7D7D7D" + dc.SetTextForeground(txtcolour) + + tx = x + (self._tWidth - sw)/2 + if hh >= self._tHeight: + ty = y + self._tHeight + (self._tTextHeight - sh)/2 + 3 + else: + ty = y + hh + (self._tHeight-hh)/2 + (self._tTextHeight - sh)/2 + 3 + + dc.DrawText(mycaption, tx, ty) + + # outline + if self._tOutline != THUMB_OUTLINE_NONE and (self._tOutlineNotSelected or self.IsSelected(index)): + + dotrect = wx.Rect() + dotrect.x = x - 2 + dotrect.y = y - 2 + dotrect.width = bmp.GetWidth() - self._tBorder + 4 + dotrect.height = bmp.GetHeight() - self._tBorder + 4 + + dc.SetPen(wx.Pen((self.IsSelected(index) and [colour] or [wx.LIGHT_GREY])[0], + 0, wx.PENSTYLE_SOLID)) + dc.SetBrush(wx.Brush(wx.BLACK, wx.BRUSHSTYLE_TRANSPARENT)) + + if self._tOutline == THUMB_OUTLINE_FULL or self._tOutline == THUMB_OUTLINE_RECT: + + imgRect.x = x + imgRect.y = y + imgRect.width = bmp.GetWidth() - self._tBorder + imgRect.height = bmp.GetHeight() - self._tBorder + + if self._tOutline == THUMB_OUTLINE_RECT: + imgRect.height = self._tHeight + + dc.SetBrush(wx.TRANSPARENT_BRUSH) + + if selected: + + dc.SetPen(self.grayPen) + dc.DrawRoundedRectangle(dotrect, 2) + + dc.SetPen(wx.Pen(wx.WHITE)) + dc.DrawRectangle(imgRect.x, imgRect.y, + imgRect.width, imgRect.height) + + pen = wx.Pen((selected and [colour] or [wx.LIGHT_GREY])[0], 2) + pen.SetJoin(wx.JOIN_MITER) + dc.SetPen(pen) + if self._tOutline == THUMB_OUTLINE_FULL: + dc.DrawRoundedRectangle(imgRect.x - 1, imgRect.y - 1, + imgRect.width + 3, imgRect.height + 3, 2) + else: + dc.DrawRectangle(imgRect.x - 1, imgRect.y - 1, + imgRect.width + 3, imgRect.height + 3) + else: + dc.SetPen(wx.Pen(wx.LIGHT_GREY)) + + dc.DrawRectangle(imgRect.x - 1, imgRect.y - 1, + imgRect.width + 2, imgRect.height + 2) + + + dc.SelectObject(wx.NullBitmap) + + + def OnPaint(self, event): + """ + Handles the ``wx.EVT_PAINT`` event for :class:`ThumbnailCtrl`. + + :param `event`: a :class:`PaintEvent` event to be processed. + """ + + paintRect = self.GetPaintRect() + + dc = wx.BufferedPaintDC(self) + self.PrepareDC(dc) + + dc.SetPen(wx.Pen(wx.BLACK, 0, wx.PENSTYLE_TRANSPARENT)) + dc.SetBrush(wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID)) + + w, h = self.GetClientSize() + + # items + row = -1 + xwhite = self._tBorder + + for ii in range(len(self._items)): + + col = ii%self._cols + if col == 0: + row = row + 1 + + xwhite = ((w - self._cols*(self._tWidth + self._tBorder)))/(self._cols+1) + tx = xwhite + col*(self._tWidth + self._tBorder) + + ty = self._tBorder/2 + row*(self._tHeight + self._tBorder) + \ + self.GetCaptionHeight(0, row) + tw = self._tWidth + self._tBorder + th = self._tHeight + self.GetCaptionHeight(row) + self._tBorder + # visible? + if not paintRect.Intersects(wx.Rect(tx, ty, tw, th)): + continue + + thmb = wx.Bitmap(tw, th) + self.DrawThumbnail(thmb, self._items[ii], ii) + dc.DrawBitmap(thmb, tx, ty) + + rect = wx.Rect(xwhite, self._tBorder/2, + self._cols*(self._tWidth + self._tBorder), + self._rows*(self._tHeight + self._tBorder) + \ + self.GetCaptionHeight(0, self._rows)) + + w = max(self.GetClientSize().GetWidth(), rect.width) + h = max(self.GetClientSize().GetHeight(), rect.height) + dc.DrawRectangle(0, 0, w, rect.y) + dc.DrawRectangle(0, 0, rect.x, h) + dc.DrawRectangle(rect.GetRight(), 0, w - rect.GetRight(), h + 50) + dc.DrawRectangle(0, rect.GetBottom(), w, h - rect.GetBottom() + 50) + + col = len(self._items)%self._cols + + if col > 0: + rect.x = rect.x + col*(self._tWidth + self._tBorder) + rect.y = rect.y + (self._rows - 1)*(self._tHeight + self._tBorder) + \ + self.GetCaptionHeight(0, self._rows - 1) + dc.DrawRectangle(rect) + + + def OnResize(self, event): + """ + Handles the ``wx.EVT_SIZE`` event for :class:`ThumbnailCtrl`. + + :param `event`: a :class:`wx.SizeEvent` event to be processed. + """ + + self.UpdateProp() + self.ScrollToSelected() + self.Refresh() + + + def OnMouseDown(self, event): + """ + Handles the ``wx.EVT_LEFT_DOWN`` and ``wx.EVT_RIGHT_DOWN`` events for :class:`ThumbnailCtrl`. + + :param `event`: a :class:`MouseEvent` event to be processed. + """ + + x = event.GetX() + y = event.GetY() + x, y = self.CalcUnscrolledPosition(x, y) + # get item number to select + lastselected = self._selected + self._selected = self.GetItemIndex(x, y) + + self._mouseeventhandled = False + update = False + + if event.ControlDown(): + if self._selected == -1: + self._mouseeventhandled = True + elif not self.IsSelected(self._selected): + self._selectedarray.append(self._selected) + update = True + self._mouseeventhandled = True + + elif event.ShiftDown(): + if self._selected != -1: + begindex = self._selected + endindex = lastselected + if lastselected < self._selected: + begindex = lastselected + endindex = self._selected + self._selectedarray = [] + + for ii in range(begindex, endindex+1): + self._selectedarray.append(ii) + + update = True + + self._selected = lastselected + self._mouseeventhandled = True + + else: + + if self._selected == -1: + update = len(self._selectedarray) > 0 + self._selectedarray = [] + self._mouseeventhandled = True + elif len(self._selectedarray) <= 1: + try: + update = len(self._selectedarray)== 0 or self._selectedarray[0] != self._selected + except: + update = True + self._selectedarray = [] + self._selectedarray.append(self._selected) + self._mouseeventhandled = True + + if update: + self.ScrollToSelected() + self.Refresh() + eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_SEL_CHANGED, self.GetId()) + self.GetEventHandler().ProcessEvent(eventOut) + + self.SetFocus() + + + def OnMouseUp(self, event): + """ + Handles the ``wx.EVT_LEFT_UP`` and ``wx.EVT_RIGHT_UP`` events for :class:`ThumbnailCtrl`. + + :param `event`: a :class:`MouseEvent` event to be processed. + """ + + # get item number to select + x = event.GetX() + y = event.GetY() + x, y = self.CalcUnscrolledPosition(x, y) + lastselected = self._selected + self._selected = self.GetItemIndex(x,y) + + if not self._mouseeventhandled: + # set new selection + if event.ControlDown(): + if self._selected in self._selectedarray: + self._selectedarray.remove(self._selected) + + self._selected = -1 + else: + self._selectedarray = [] + self._selectedarray.append(self._selected) + + self.ScrollToSelected() + self.Refresh() + eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_SEL_CHANGED, self.GetId()) + self.GetEventHandler().ProcessEvent(eventOut) + + # Popup menu + if event.RightUp(): + if self._selected >= 0 and self._pmenu: + self.PopupMenu(self._pmenu) + elif self._selected >= 0 and not self._pmenu and self._gpmenu: + self.PopupMenu(self._gpmenu) + elif self._selected == -1 and self._gpmenu: + self.PopupMenu(self._gpmenu) + + if event.ShiftDown(): + self._selected = lastselected + + + def OnMouseDClick(self, event): + """ + Handles the ``wx.EVT_LEFT_DCLICK`` event for :class:`ThumbnailCtrl`. + + :param `event`: a :class:`MouseEvent` event to be processed. + """ + + eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_DCLICK, self.GetId()) + self.GetEventHandler().ProcessEvent(eventOut) + + + def OnMouseMove(self, event): + """ + Handles the ``wx.EVT_MOTION`` event for :class:`ThumbnailCtrl`. + + :param `event`: a :class:`MouseEvent` event to be processed. + """ + + # -- drag & drop -- + if self._dragging and event.Dragging() and len(self._selectedarray) > 0: + + files = wx.FileDataObject() + for ii in range(len(self._selectedarray)): + files.AddFile(opj(self.GetSelectedItem(ii).GetFullFileName())) + + source = wx.DropSource(self) + source.SetData(files) + source.DoDragDrop(wx.Drag_DefaultMove) + + # -- light-effect -- + x = event.GetX() + y = event.GetY() + x, y = self.CalcUnscrolledPosition(x, y) + + # get item number + sel = self.GetItemIndex(x, y) + + if sel == self._pointed: + if self._enabletooltip and sel >= 0: + if not hasattr(self, "_tipwindow"): + self._tipwindow = wx.ToolTip(self.GetThumbInfo(sel)) + self._tipwindow.SetDelay(1000) + self.SetToolTip(self._tipwindow) + else: + self._tipwindow.SetDelay(1000) + self._tipwindow.SetTip(self.GetThumbInfo(sel)) + + event.Skip() + return + + if self._enabletooltip: + if hasattr(self, "_tipwindow"): + self._tipwindow.Enable(False) + + # update thumbnail + self._pointed = sel + + if self._enabletooltip and sel >= 0: + if not hasattr(self, "_tipwindow"): + self._tipwindow = wx.ToolTip(self.GetThumbInfo(sel)) + self._tipwindow.SetDelay(1000) + self._tipwindow.Enable(True) + self.SetToolTip(self._tipwindow) + else: + self._tipwindow.SetDelay(1000) + self._tipwindow.Enable(True) + self._tipwindow.SetTip(self.GetThumbInfo(sel)) + + self.Refresh() + eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_POINTED, self.GetId()) + self.GetEventHandler().ProcessEvent(eventOut) + event.Skip() + + + def OnMouseLeave(self, event): + """ + Handles the ``wx.EVT_LEAVE_WINDOW`` event for :class:`ThumbnailCtrl`. + + :param `event`: a :class:`MouseEvent` event to be processed. + """ + + if self._pointed != -1: + + self._pointed = -1 + self.Refresh() + eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_POINTED, self.GetId()) + self.GetEventHandler().ProcessEvent(eventOut) + + + def OnThumbChanged(self, event): + """ + Handles the ``EVT_THUMBNAILS_THUMB_CHANGED`` event for :class:`ThumbnailCtrl`. + + :param `event`: a :class:`ThumbnailEvent` event to be processed. + """ + + for ii in range(len(self._items)): + if self._items[ii].GetFileName() == event.GetString(): + + self._items[ii].SetFilename(self._items[ii].GetFileName()) + if event.GetClientData(): + + img = wx.Image(event.GetClientData()) + self._items[ii].SetImage(img) + + self.Refresh() + + + def OnChar(self, event): + """ + Handles the ``wx.EVT_CHAR`` event for :class:`ThumbnailCtrl`. + + :param `event`: a :class:`KeyEvent` event to be processed. + + :note: You have these choices: + + (1) ``d`` key rotates 90 degrees clockwise the selected thumbnails; + (2) ``s`` key rotates 90 degrees counter-clockwise the selected thumbnails; + (3) ``a`` key rotates 180 degrees the selected thumbnails; + (4) ``Del`` key deletes the selected thumbnails; + (5) ``+`` key zooms in; + (6) ``-`` key zooms out. + """ + + if event.KeyCode == ord("s"): + self.Rotate() + elif event.KeyCode == ord("d"): + self.Rotate(270) + elif event.KeyCode == ord("a"): + self.Rotate(180) + elif event.KeyCode in [wx.WXK_ADD, wx.WXK_NUMPAD_ADD]: + self.ZoomIn() + elif event.KeyCode in [wx.WXK_SUBTRACT, wx.WXK_NUMPAD_SUBTRACT]: + self.ZoomOut() + + selected = [] + for ii in range(len(self._items)): + if self.IsSelected(ii): + selected.append(ii) + + eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_CHAR, self.GetId(), + thumbs=selected, keycode=event.KeyCode) + self.GetEventHandler().ProcessEvent(eventOut) + + event.Skip() + + def Rotate(self, angle=90): + """ + Rotates the selected thumbnails by the angle specified by `angle`. + + :param `angle`: the rotation angle for the thumbnail, in degrees. + """ + + wx.BeginBusyCursor() + + selected = [] + for ii in range(len(self._items)): + if self.IsSelected(ii): + selected.append(self._items[ii]) + + for thumb in selected: + thumb.Rotate(angle) + + wx.EndBusyCursor() + + if self.GetSelection() != -1: + self.Refresh() + + + def OnMouseWheel(self, event): + """ + Handles the ``wx.EVT_MOUSEWHEEL`` event for :class:`ThumbnailCtrl`. + + :param `event`: a :class:`MouseEvent` event to be processed. + + :note: If you hold down the ``Ctrl`` key, you can zoom in/out with the mouse wheel. + """ + + if event.ControlDown(): + if event.GetWheelRotation() > 0: + self.ZoomIn() + else: + self.ZoomOut() + else: + event.Skip() + + + def ZoomOut(self): + """ Zooms the thumbnails out. """ + + w, h, b = self.GetThumbSize() + + if w < 40 or h < 40: + return + + zoom = self.GetZoomFactor() + neww = float(w)/zoom + newh = float(h)/zoom + + self.SetThumbSize(int(neww), int(newh)) + self.OnResize(None) + self._checktext = True + + self.Refresh() + + + def ZoomIn(self): + """ Zooms the thumbnails in. """ + + size = self.GetClientSize() + w, h, b = self.GetThumbSize() + zoom = self.GetZoomFactor() + + if w*zoom + b > size.GetWidth() or h*zoom + b > size.GetHeight(): + if w*zoom + b > size.GetWidth(): + neww = size.GetWidth() - 2*self._tBorder + newh = (float(h)/w)*neww + else: + newh = size.GetHeight() - 2*self._tBorder + neww = (float(w)/h)*newh + + else: + neww = float(w)*zoom + newh = float(h)*zoom + + self.SetThumbSize(int(neww), int(newh)) + self.OnResize(None) + self._checktext = True + + self.Refresh() + + +# ---------------------------------------------------------------------------- # +# Class ThumbnailEvent +# ---------------------------------------------------------------------------- # + +class ThumbnailEvent(wx.PyCommandEvent): + """ + This class is used to send events when a thumbnail is hovered, selected, + double-clicked or when its caption has been changed. + """ + def __init__(self, evtType, evtId=-1, thumbs=None, keycode=None): + """ + Default class constructor. + + :param `evtType`: the event type; + :param `evtId`: the event identifier. + """ + + wx.PyCommandEvent.__init__(self, evtType, evtId) + self.thumbs = thumbs + self.keycode = keycode diff --git a/wx/lib/agw/thumbnailctrl.py b/wx/lib/agw/thumbnailctrl.py index 0e938164..97ef39e3 100644 --- a/wx/lib/agw/thumbnailctrl.py +++ b/wx/lib/agw/thumbnailctrl.py @@ -4,27 +4,10 @@ # # Andrea Gavana And Peter Damoc, @ 12 Dec 2005 # Latest Revision: 27 Dec 2012, 21.00 GMT -# -# -# TODO List/Caveats -# -# 1. Thumbnail Creation/Display May Be Somewhat Improved From The Execution -# Speed Point Of View; -# -# 2. The Implementation For wx.HORIZONTAL Style Is Still To Be Written; -# -# 3. I Have No Idea On How To Implement Thumbnails For Audio, Video And Other Files. -# -# 4. Other Ideas? -# -# -# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please -# Write To Me At: -# # andrea.gavana@gmail.com # andrea.gavana@maerskoil.com # -# Or, Obviously, To The wxPython Mailing List!!! +# Refactored and reorganized: Michael Eager (eager@eagercon.com), 26 Sep 2020 # # Tags: phoenix-port, documented, unittest, py3-port # @@ -52,6 +35,11 @@ control. However, :class:`ThumbnailCtrl` wouldn't have been so fast and complete without the suggestions and hints from Peter Damoc. So, if he accepts the mention, this control is his as much as mine. +:class:`ThumbnailCtrl` is more of a demo application than a widget. +The :class:`ScrolledThumbnail` is a freestanding widget, accepting a list of +:class:`Thumb` objects which represent files and which returns a thumbnail +on request. :class:`Thumb` can be extended by the user to provide thumbnails +for other data types, such as text, audio, video, or PDF files. Usage ===== @@ -137,11 +125,11 @@ This class processes the following events: ================================== ================================================== Event Name Description ================================== ================================================== -``EVT_THUMBNAILS_CAPTION_CHANGED`` The thumbnail caption has been changed. Not used at present. ``EVT_THUMBNAILS_DCLICK`` The user has double-clicked on a thumbnail. ``EVT_THUMBNAILS_POINTED`` The mouse cursor is hovering over a thumbnail. ``EVT_THUMBNAILS_SEL_CHANGED`` The user has changed the selected thumbnail. ``EVT_THUMBNAILS_THUMB_CHANGED`` The thumbnail of an image has changed. Used internally. +``EVT_THUMBNAILS_CHAR`` A key was typed in the widget ================================== ================================================== @@ -150,9 +138,9 @@ License And Version :class:`ThumbnailCtrl` is distributed under the wxPython license. -Latest revision: Andrea Gavana @ 27 Dec 2012, 21.00 GMT +Latest revision: Michael Eager @ 26 Sep 2020 -Version 0.9 +Version 1.0 """ @@ -164,170 +152,13 @@ Version 0.9 import wx import os import time -import zlib -import six -from math import pi - -from wx.lib.embeddedimage import PyEmbeddedImage - -if six.PY3: - import _thread as thread -else: - import thread - -#---------------------------------------------------------------------- -# Get Default Icon/Data -#---------------------------------------------------------------------- - -def GetMondrianData(): - """ Returns a default image placeholder as a decompressed stream of characters. """ - return \ -b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x06\x00\ -\x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\x00qID\ -ATX\x85\xed\xd6;\n\x800\x10E\xd1{\xc5\x8d\xb9r\x97\x16\x0b\xad$\x8a\x82:\x16\ -o\xda\x84pB2\x1f\x81Fa\x8c\x9c\x08\x04Z{\xcf\xa72\xbcv\xfa\xc5\x08 \x80r\x80\ -\xfc\xa2\x0e\x1c\xe4\xba\xfaX\x1d\xd0\xde]S\x07\x02\xd8>\xe1wa-`\x9fQ\xe9\ -\x86\x01\x04\x10\x00\\(Dk\x1b-\x04\xdc\x1d\x07\x14\x98;\x0bS\x7f\x7f\xf9\x13\ -\x04\x10@\xf9X\xbe\x00\xc9 \x14K\xc1<={\x00\x00\x00\x00IEND\xaeB`\x82' - - -def GetMondrianBitmap(): - """ Returns a default image placeholder as a :class:`wx.Bitmap`. """ - - return wx.Bitmap(GetMondrianImage()) - - -def GetMondrianImage(): - """ Returns a default image placeholder as a :class:`wx.Image`. """ - - stream = six.BytesIO(GetMondrianData()) - return wx.Image(stream) - - -#---------------------------------------------------------------------- -file_broken = PyEmbeddedImage( - b"iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAAK/INwWK6QAADU9J" - b"REFUeJzNWn1QE2cefja7yRI+lAKiAgKCVIqegUYMFMsM4kfvzk7nmFrnvHGcOT3bq3P/OJ1p" - b"b+zYf+r0HGxt6VVFqpYqWq9TW23RVnp6R7FXiq0UPAGhyFeQSCQJISHZ7Mf9gZuGkE02IeA9" - b"M5k3u/vuu+/z/H7v7/297y4BPzh//vxfSJIsIgjC5a/eTIPn+UnHgiDIqisIAsUwTNfmzZtf" - b"A8D7qk/5e3B0dPS6wsLCp/09MFTIbdO7ntR9vs5HRESgvr6+E0AlAD2AKZX8CsDzvEKtVsvq" - b"aKjw7LggCO7jcJQqlQoMw6gBpAIYRAgCECGxCgDPTsohEopI4n+e5xXww1MRHkry4d1Zf9fk" - b"uv90MOsCeMKfGN51pO6ZrmizKsBsWt976EjhoQ+BYMQItpSDWRNAjptPZ4xLCfl/PQTCaX2p" - b"+wNhVgSYbet7tumdRXpj1ofAbFhfqn1fmHEBHpb1AYAgAudxMy4AQRB+LReuSB/MGsETMyqA" - b"2WyG1WqVZQl/CDUZeuge0NvbKwwODk7bC7zryYFI/qF6QGNj42BnZ6dZ6rocQtNNhQNhxgQw" - b"Go24fPnyDy0tLTcFQfDpBZ7/w2198RcIMyZAW1vb+KVLlxoGBwfr7927B4Vi8qO8CfnqbCjW" - b"D4Y8MEMCCIKA77777me73d7a1tb25dDQkNXzmud/giDgdDrBsqzkuA1l7CuVSgCA0+n0y3FG" - b"BBgYGMCVK1eub926dRFN04aBgYH/el73JKRUKjE4OIi+vj5QFDWtaVG0fGRkJHiex7lz5xyn" - b"T59uBMBI9TWsAoidaW1tNTc1Nd1cv379zuzs7F91dHT8x+l0TooDnuju7h779ttvjdOdLlUq" - b"FaKjo9HV1YX9+/d379q16+QXX3xxFsAAMHU7DAgggNw1tQiCIOByuXDt2rWbK1asUOfk5KxM" - b"SUlZW19f39LX18eTJDmpbbG8cePGndra2mtDQ0NQKpUhJUPR0dHgeR6ffvqpbdeuXV+9/vrr" - b"R4eGhs4A+ArA3ZAECAXd3d24dOnSD2VlZavy8vKQmppaUFdXN2gwGO561yVJEmazGR0dHV3n" - b"z5//+NatW0MU5XebcgooikJMTAy6urqwb9++zp07d1ZfvXq1GsAn8+bNa6qsrEyGxJY4EGBT" - b"lCAIhVR0ZhgGTqcTTqcTDocDDocDgiDgm2++0Y+MjNxbuXLl7wmCQFpaWtbcuXMje3p6fly9" - b"enWyeL8gCCBJEj09PUJLS0s7z/PXWlpavigoKNhBURRYlnU/S6qMjIwEwzD47LPPbIcOHWq4" - b"cuXKPwF8D+DHF154QV1cXFxpsVjiAGwGYIUPL/ArgNFoNNbV1dmtVqvdarW6LBaLY2xszOFw" - b"OBiLxWKz2WxOs9lsHx0ddZhMJpvZbB7v7u7+effu3elZWVmJAJCUlBS1bt26nNbW1kaj0fj0" - b"I4884iYHAHfu3Lnf2dnZDWC4oaHhH08++eRWrVZLe9bxJk9RFNRqNTo6OvDhhx92Hj16tG5k" - b"ZKQBwHUAneXl5cVarfaQVqtddurUqesAUgC0BysAsWfPngOjo6ONY2NjUQzDOACMA7ADcGAi" - b"sjoBuB6U4jFTWlp6Kj4+HjzPIzExEbm5uSuPHz9+csuWLfaEhIRIl8sFgiDA8zxu3rz585Yt" - b"W3SpqanX9u7d+93GjRuv5eXlrRGvT+oQQUCtVsPhcODcuXO2w4cPi1ZvAnADgP3EiRN7NBrN" - b"ntzcXDXHcXA6nREAJF9u+BNA6OnpuQ1gGAD5gCjrUXIPFOUflAIA7siRI8Xp6ekaYOI1lVKp" - b"RGZmpqatre2QXq/v1mg0y4EJKxoMBrS1tQ2UlJQ8abPZjAD23Lhx46Pi4uI1aWlp7mEl1qdp" - b"Grdu3UJNTU1nVVVV3cjIyDUAPwJof+WVVzJ0Ol1NQUHBbxMTEyHOOoEQKOKMP/jJxoIFC/6Q" - b"mZlJidYTBAHp6emp2dnZCV1dXY0MwywnCAIkSUKv1zu7u7uNu3fvTh8aGtoE4Pj7779/ec2a" - b"NZ0ZGRlZwC9Wt9vt+Pzzz22VlZUNV69eFa3+EwDTkSNHynJyct5ZtWpVikKhgMPhgEKhkJx2" - b"gxFgSv1t27ZRUVFRFMuyZGZmZmJcXNwcpVIZT9N0tMvlWpiSklKmVConBbGkpCRq3bp1Kxsb" - b"G69v3Lhxe3p6OgRBQHt7u2Hx4sVRycnJ9Ny5czO3bdv2VHV19XvNzc2frF69+pXY2FgoFAop" - b"q3ds2rQp6plnnnkzLy9v99KlS8EwDDiOC2r5LSnAiRMnIubNm/dHkiSzaZqOIUkygabpGIqi" - b"4iiKmkuSZDRJkiqVSkWRJKmkaZqMiIhAZGQkOI5zk+c4DnFxccjOztZWVVV9+eKLLw5nZGTM" - b"YxgGzc3Ndx577LGF8fHxiI2NhU6n+111dfWZixcvnispKfmzTqebe+HCBW+rtwK4/8Ybb+Rp" - b"NJojBQUFq2JiYuB0OgH8kgqLpdiXoAVobW2NfvbZZ/cWFhbOly3ngwf6yvczMzNzWJZV9Pb2" - b"/lRUVLR2cHAQt2/fvrt9+3YtSZJQKBRYtmxZYXFx8ar6+vqrTU1NF7/++mtdZWXll0ajsQET" - b"Qa4TE3HmBY1Gsz8/P3+Oy+Vyj3dP8nK9QFKA/v5+Cn7GfzBZmiAISE5OTiwqKsq4fft2k91u" - b"X9vf3283GAyWtLS0ZNFTsrOzI0pKSsrq6+v//c477/xNr9evwMRr7ZsAhl966aUFOp3uzfz8" - b"/C2LFi3C+Pj4JMLeAsjJYiUFYFk2oIRyReA4DikpKSgqKlpZW1t7saysjOvo6NAvXbo0NjEx" - b"MZLneXAchzlz5kCj0TwVGxu7RK/Xt2Mihx8DwBw4cGBDbm5uRWFh4aNKpRJ2u30S8VCsD8hY" - b"CwRzXqouz/OIiopCVlaWtrm5+X57e/tAS0uLPicnJzEhIcE9TnmeR05OTvJzzz33a0xMtyNa" - b"rVY4fvz4a6WlpRdKSkoeFZfPYpSXIi93SyzYWWASMTmlZ/2MjIys+Pj4mI6Ojoa+vj5h/fr1" - b"xaKrikIlJyfj8ccfLwNw7NVXX43TarV/LyoqWhsXF4fx8XF3TPFF2Pv/tPIAu93ul7g/+BJD" - b"EAQsXLgwqrS0NLexsfE8RVHLFi1atNn7PpVKhdzc3OUvv/zyvg0bNvwmPz8/WeyPt8sHEkLO" - b"anZaQyCYDUmO47BgwQIsX758VW1t7bc6nU5ISkpSeuYLHMeBYRhkZWWpd+zY8afCwsJklmXB" - b"MMwUlw9EXjwOhIDL4dHRUQwMDMBms4Hn+YCuJSUKz/NQqVRYsmTJcgDzlixZsnzOnDlu1xfj" - b"AMdxoGkaqampsNvtEATBvZ8olzxBEO57whIDxsbGMD4+DrVajaioKERGRoKiKMmdXn9IT09P" - b"Li4ufiIxMVEHACzLThGW47gp54IhHwz8CiCSEt3P4XDA6XTCYrGApmnQNA2VSuUWwzvyescA" - b"l8uF+fPnK59//vm/zp8/f6FoebF98VlSBOWS99WXkATwfrhnw+JmiEKhgEKhAEmSEDM6KTEI" - b"gkBcXBwRExOTkpCQAJfLNSPkg4EsD/Algncs4Hl+ktv6+onjOS0tDTRNT1q4hJN8MCKE5AFS" - b"nQx0DQAYhkFqaqpbrJkmH5YPJMJFXqFQTBmTM0E+LB5gt9slXTpU8t4E5JKXSnsDkfV+HReU" - b"AJ6YLnlf1pNLXhAEmM1msCw7KSZ598/7PEEQ4DgOLMv6VSHQt8JhIS/HG/y5vV6vh9FohFqt" - b"ducN3r8pxCgKJpMJvb29o/hlzzI4AUSEk7yUtfyJwTAMMjMzsXjxYr/zuuf7wbt376K8vLz/" - b"7NmzlzCxpA5NgHCTl1vHFzkAPjNE774aDAaUl5f3V1RU1HAc9y9MbKr4RMA8QIr4TJP3LEU3" - b"5zjOnTtItWUwGLB//36R/CVMbKXZQhbAV2dnk7zYD3G1KI537/oURbndvqKi4hTHcV9iYvd4" - b"zB/HkFPh6ZL3JurvHIBJXuB9XfzG4MCBA/3vvvtujVzysgTwR2I65KVmAl/3iUtmlmUnDQGR" - b"/P3793H48GH9A/Ki2wckH1AAzxjgS4yZJO/dD899A7EORVEYGRlBVVWV8b333vs4GMvLEsAT" - b"oQ4Ff+Q92/YniGcMEAUQ5/ljx44Nv/XWWx9ZrdaLAJqDIR9QAO/9gHCTlxsERRFEAZRKJUwm" - b"E6qrq4cPHjx4xmq11mLi1bglGPIBBfBF8mGQFyHmACaTCSdPnhx+++23z4yOjtZi4pWZKVjy" - b"sgTwFiIU8v7GuVzyIsxmM06fPj188ODBjx5Y/nsAkl+jBoLsPECEryDl7ziQMHLJkyQJi8WC" - b"mpqa4YqKCtHtmzAN8oCM9wJKpRIRERGyyAQi6CvwyeokRcFsNqOurm64oqLijMVimZbbT2pb" - b"6gJN04TNZlNZLBYwDOOeEgNtMsqpF+y1sbEx1NbWDn/wwQdhJQ8AkmZYsWJFVEZGxtZ79+49" - b"wbIsDa/VlBQJqS0of6QD3UMQBHp7e++YTKYrCIPbe8KfHyoALACwGIAqXA8MEQImPnO7A2Ak" - b"nA3/D+/OyD/Ur3BPAAAAAElFTkSuQmCC") - - -def getDataSH(): - """ Return the first part of the shadow dropped behind thumbnails. """ - - return zlib.decompress( -b'x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2_A\x98\x83\rHvl\ -\xdc\x9c\n\xa4X\x8a\x9d float(height)/imgheight: - scale = float(height)/imgheight - - newW, newH = int(imgwidth*scale), int(imgheight*scale) - if newW < 1: - newW = 1 - if newH < 1: - newH = 1 - - img = img.Scale(newW, newH) - - bmp = img.ConvertToBitmap() - - self._image = img - - return bmp - - - def GetOriginalImage(self): - """ Returns the bitmap associated to a thumbnail, as a file name. """ - - original = opj((self._dir + "/" + self._filename)) - - return original - - - def GetFullFileName(self): - """ Returns the full filename of the thumbnail. """ - - return self._dir + "/" + self._filename - - - def GetCaption(self, line): - """ - Returns the caption associated to a thumbnail. - - :param `line`: the caption line we wish to retrieve (useful for multilines - caption strings). - """ - - if line + 1 >= len(self._captionbreaks): - return "" - - strs = self._caption - - return strs - - - def GetFileSize(self): - """ Returns the file size associated to a thumbnail. """ - - return self._filesize - - - def GetCreationDate(self): - """ Returns the file last modification date associated to a thumbnail. """ - - return self._lastmod - - - def GetOriginalSize(self): - """ Returns a tuple containing the original image width and height, in pixels. """ - - if hasattr(self, "_threadedimage"): - img = self._threadedimage - else: - img = GetMondrianImage() - - if hasattr(self, "_originalsize"): - imgwidth, imgheight = self._originalsize - else: - imgwidth, imgheight = (img.GetWidth(), img.GetHeight()) - - return imgwidth, imgheight - - - def GetCaptionLinesCount(self, width): - """ - Returns the number of lines for the caption. - - :param `width`: the maximum width, in pixels, available for the caption text. - """ - - self.BreakCaption(width) - return len(self._captionbreaks) - 1 - - - def BreakCaption(self, width): - """ - Breaks the caption in several lines of text (if needed). - - :param `width`: the maximum width, in pixels, available for the caption text. - """ - - if len(self._captionbreaks) > 0 or width < 16: - return - - self._captionbreaks.append(0) - - if len(self._caption) == 0: - return - - pos = width//16 - beg = 0 - end = 0 - - dc = wx.MemoryDC() - bmp = wx.Bitmap(10, 10) - dc.SelectObject(bmp) - - while 1: - - if pos >= len(self._caption): - - self._captionbreaks.append(len(self._caption)) - break - - sw, sh = dc.GetTextExtent(self._caption[beg:pos-beg]) - - if sw > width: - - if end > 0: - - self._captionbreaks.append(end) - beg = end - - else: - - self._captionbreaks.append(pos) - beg = pos - - pos = beg + width//16 - end = 0 - - if pos < len(self._caption) and self._caption[pos] in [" ", "-", ",", ".", "_"]: - end = pos + 1 - - pos = pos + 1 - - - dc.SelectObject(wx.NullBitmap) - - - def SetRotation(self, angle=0): - """ - Sets the thumbnail rotation. - - :param `angle`: the thumbnail rotation, in radians. - """ - - self._rotation = angle - - - def GetRotation(self): - """ Returns the thumbnail rotation, in radians. """ - - return self._rotation # ---------------------------------------------------------------------------- # @@ -909,7 +244,8 @@ class ThumbnailCtrl(wx.Panel): def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, thumboutline=THUMB_OUTLINE_IMAGE, - thumbfilter=THUMB_FILTER_IMAGES, imagehandler=PILImageHandler): + thumbfilter=None, # Ignored, included for backward compatibility + imagehandler=PILImageHandler): """ Default class constructor. @@ -930,8 +266,7 @@ class ThumbnailCtrl(wx.Panel): ``THUMB_OUTLINE_IMAGE`` 4 Only image bounding rectangle is drawn. =========================== ======= ================================== - :param `thumbfilter`: filter for image/video/audio files. Actually only - ``THUMB_FILTER_IMAGES`` is implemented; + :param `thumbfilter`: filter for image/video/audio files. Ignored. :param `imagehandler`: can be :class:`PILImageHandler` if PIL is installed (faster), or :class:`NativeImageHandler` which only uses wxPython image methods. """ @@ -942,7 +277,7 @@ class ThumbnailCtrl(wx.Panel): self._combo = wx.ComboBox(self, -1, style=wx.CB_DROPDOWN | wx.CB_READONLY) self._scrolled = ScrolledThumbnail(self, -1, thumboutline=thumboutline, - thumbfilter=thumbfilter, imagehandler = imagehandler) + imagehandler = imagehandler) subsizer = wx.BoxSizer(wx.HORIZONTAL) subsizer.Add((3, 0), 0) @@ -959,13 +294,13 @@ class ThumbnailCtrl(wx.Panel): methods = ["GetSelectedItem", "GetPointed", "GetHighlightPointed", "SetHighlightPointed", "SetThumbOutline", "GetThumbOutline", "GetPointedItem", "GetItem", "GetItemCount", "GetThumbWidth", "GetThumbHeight", "GetThumbBorder", - "ShowFileNames", "SetPopupMenu", "GetPopupMenu", "SetGlobalPopupMenu", + "SetPopupMenu", "GetPopupMenu", "SetGlobalPopupMenu", "GetGlobalPopupMenu", "SetSelectionColour", "GetSelectionColour", - "EnableDragging", "SetThumbSize", "GetThumbSize", "ShowThumbs", "ShowDir", - "GetShowDir", "SetSelection", "GetSelection", "SetZoomFactor", + "EnableDragging", "SetThumbSize", "GetThumbSize", "ShowThumbs", + "SetSelection", "GetSelection", "SetZoomFactor", "GetZoomFactor", "SetCaptionFont", "GetCaptionFont", "GetItemIndex", "InsertItem", "RemoveItemAt", "IsSelected", "Rotate", "ZoomIn", "ZoomOut", - "EnableToolTips", "GetThumbInfo", "GetOriginalImage", "SetDropShadow", "GetDropShadow"] + "EnableToolTips", "GetThumbInfo", "SetDropShadow", "GetDropShadow"] for method in methods: setattr(self, method, getattr(self._scrolled, method)) @@ -975,6 +310,9 @@ class ThumbnailCtrl(wx.Panel): self._subsizer = subsizer self._combo.Bind(wx.EVT_COMBOBOX, self.OnComboBox) + self.Bind(EVT_THUMBNAILS_CHAR, self.OnThumbChar) + + self._imagehandler = imagehandler def ShowComboBox(self, show=True): @@ -1071,409 +409,45 @@ class ThumbnailCtrl(wx.Panel): return self.thumbnail_ctrl._scrolled.SetBackgroundColour(colour) -# ---------------------------------------------------------------------------- # -# Class ScrolledThumbnail -# This Is The Main Class Implementation -# ---------------------------------------------------------------------------- # - -class ScrolledThumbnail(wx.ScrolledWindow): - """ This is the main class implementation of :class:`ThumbnailCtrl`. """ - - def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, - size=wx.DefaultSize, thumboutline=THUMB_OUTLINE_IMAGE, - thumbfilter=THUMB_FILTER_IMAGES, imagehandler=PILImageHandler): + def ShowDir(self, folder): """ - Default class constructor. + Shows thumbnails for a particular folder. - :param `parent`: parent window. Must not be ``None``; - :param `id`: window identifier. A value of -1 indicates a default value; - :param `pos`: the control position. A value of (-1, -1) indicates a default position, - chosen by either the windowing system or wxPython, depending on platform; - :param `size`: the control size. A value of (-1, -1) indicates a default size, - chosen by either the windowing system or wxPython, depending on platform; - :param `thumboutline`: outline style for :class:`ScrolledThumbnail`, which may be: - - =========================== ======= ================================== - Outline Flag Value Description - =========================== ======= ================================== - ``THUMB_OUTLINE_NONE`` 0 No outline is drawn on selection - ``THUMB_OUTLINE_FULL`` 1 Full outline (image+caption) is drawn on selection - ``THUMB_OUTLINE_RECT`` 2 Only thumbnail bounding rectangle is drawn on selection (default) - ``THUMB_OUTLINE_IMAGE`` 4 Only image bounding rectangle is drawn. - =========================== ======= ================================== - - :param `thumbfilter`: filter for image/video/audio files. Actually only - ``THUMB_FILTER_IMAGES`` is implemented; - :param `imagehandler`: can be :class:`PILImageHandler` if PIL is installed (faster), or - :class:`NativeImageHandler` which only uses wxPython image methods. + :param `folder`: a directory containing the images to thumbnail; """ - wx.ScrolledWindow.__init__(self, parent, id, pos, size) + self._dir = folder - self.SetThumbSize(96, 80) - self._tOutline = thumboutline - self._filter = thumbfilter - self._imageHandler = imagehandler() - self._selected = -1 - self._pointed = -1 - self._labelcontrol = None - self._pmenu = None - self._gpmenu = None - self._dragging = False - self._checktext = False - self._orient = THUMB_VERTICAL - self._dropShadow = True + self.RecreateComboBox(folder) - self._tCaptionHeight = [] - self._selectedarray = [] - self._tTextHeight = 16 - self._tCaptionBorder = 8 - self._tOutlineNotSelected = True - self._mouseeventhandled = False - self._highlight = False - self._zoomfactor = 1.4 - self.SetCaptionFont() - self._items = [] + # update items + thumbs = [] - self._enabletooltip = False + filenames = self.ListDirectory(self._dir, extensions) - self._parent = parent + filenames.sort() + for files in filenames: - self._selectioncolour = "#009EFF" - self.grayPen = wx.Pen("#A2A2D2", 1, wx.SHORT_DASH) - self.grayPen.SetJoin(wx.JOIN_MITER) - self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_LISTBOX)) + caption = (self._showfilenames and [files] or [""])[0] + fullfile = opj(self._dir + "/" + files) + if not os.path.isfile(fullfile): + continue - t, b, s = getShadow() - self.shadow = wx.MemoryDC() - self.shadow.SelectObject(s) + stats = os.stat(fullfile) + size = stats[6] - self.ShowFileNames(True) + lastmod = time.strftime(TIME_FMT, time.localtime(stats[8])) - self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown) - self.Bind(wx.EVT_LEFT_UP, self.OnMouseUp) - self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseDClick) - self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseDown) - self.Bind(wx.EVT_RIGHT_UP, self.OnMouseUp) - self.Bind(wx.EVT_MOTION, self.OnMouseMove) - self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave) - self.Bind(EVT_THUMBNAILS_THUMB_CHANGED, self.OnThumbChanged) - self.Bind(wx.EVT_CHAR, self.OnChar) - self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel) + thumbs.append(Thumb(folder, files, caption, size, lastmod, + imagehandler=self._imagehandler)) - self.Bind(wx.EVT_SIZE, self.OnResize) - self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None) - self.Bind(wx.EVT_PAINT, self.OnPaint) + return self.ShowThumbs(thumbs) - def GetSelectedItem(self, index): - """ - Returns the selected thumbnail. - - :param `index`: the thumbnail index (i.e., the selection). - """ - - return self.GetItem(self.GetSelection(index)) - - - def GetPointed(self): - """ Returns the pointed thumbnail index. """ - - return self._pointed - - - def GetHighlightPointed(self): - """ - Returns whether the thumbnail pointed should be highlighted or not. - - :note: Please be aware that this functionality may be slow on slower computers. - """ - - return self._highlight - - - def SetHighlightPointed(self, highlight=True): - """ - Sets whether the thumbnail pointed should be highlighted or not. - - :param `highlight`: ``True`` to enable highlight-on-point with the mouse, - ``False`` otherwise. - - :note: Please be aware that this functionality may be slow on slower computers. - """ - - self._highlight = highlight - - - def SetThumbOutline(self, outline): - """ - Sets the thumbnail outline style on selection. - - :param `outline`: the outline to use on selection. This can be one of the following - bits: - - =========================== ======= ================================== - Outline Flag Value Description - =========================== ======= ================================== - ``THUMB_OUTLINE_NONE`` 0 No outline is drawn on selection - ``THUMB_OUTLINE_FULL`` 1 Full outline (image+caption) is drawn on selection - ``THUMB_OUTLINE_RECT`` 2 Only thumbnail bounding rectangle is drawn on selection (default) - ``THUMB_OUTLINE_IMAGE`` 4 Only image bounding rectangle is drawn. - =========================== ======= ================================== - - """ - - if outline not in [THUMB_OUTLINE_NONE, THUMB_OUTLINE_FULL, THUMB_OUTLINE_RECT, - THUMB_OUTLINE_IMAGE]: - return - - self._tOutline = outline - - - def GetThumbOutline(self): - """ - Returns the thumbnail outline style on selection. - - :see: :meth:`~ScrolledThumbnail.SetThumbOutline` for a list of possible return values. - """ - - return self._tOutline - - - def SetDropShadow(self, drop): - """ - Sets whether to drop a shadow behind thumbnails or not. - - :param `drop`: ``True`` to drop a shadow behind each thumbnail, ``False`` otheriwise. - """ - - self._dropShadow = drop - self.Refresh() - - - def GetDropShadow(self): - """ - Returns whether to drop a shadow behind thumbnails or not. - """ - - return self._dropShadow - - - def GetPointedItem(self): - """ Returns the pointed thumbnail. """ - - return self.GetItem(self._pointed) - - - def GetItem(self, index): - """ - Returns the item at position `index`. - - :param `index`: the thumbnail index position. - """ - - return index >= 0 and (index < len(self._items) and [self._items[index]] or [None])[0] - - - def GetItemCount(self): - """ Returns the number of thumbnails. """ - - return len(self._items) - - - def SortItems(self): - """ Sorts the items accordingly to the :func:`~CmpThumb` function. """ - - self._items.sort(key=KeyThumb) - - - def GetThumbWidth(self): - """ Returns the thumbnail width. """ - - return self._tWidth - - - def GetThumbHeight(self): - """ Returns the thumbnail height. """ - - return self._tHeight - - - def GetThumbBorder(self): - """ Returns the thumbnail border. """ - - return self._tBorder - - - def GetCaption(self): - """ Returns the thumbnail caption. """ - - return self._caption - - - def SetLabelControl(self, statictext): - """ - Sets the thumbnail label as :class:`StaticText`. - - :param `statictext`: an instance of :class:`StaticText`. - """ - - self._labelcontrol = statictext - - - def ShowFileNames(self, show=True): - """ - Sets whether the user wants to show file names under the thumbnails or not. - - :param `show`: ``True`` to show file names under the thumbnails, ``False`` otherwise. - """ - - self._showfilenames = show - self.Refresh() - - - def SetOrientation(self, orient=THUMB_VERTICAL): - """ - Set the :class:`ThumbnailCtrl` orientation (partially implemented). - - :param `orient`: one of ``THUMB_VERTICAL``, ``THUMB_HORIZONTAL``. - - .. todo:: Correctly implement the ``THUMB_HORIZONTAL`` orientation. - """ - - self._orient = orient - - - def SetPopupMenu(self, menu): - """ - Sets the thumbnails popup menu when at least one thumbnail is selected. - - :param `menu`: an instance of :class:`wx.Menu`. - """ - - self._pmenu = menu - - - def GetPopupMenu(self): - """ Returns the thumbnails popup menu when at least one thumbnail is selected. """ - - return self._pmenu - - - def SetGlobalPopupMenu(self, gpmenu): - """ - Sets the global thumbnails popup menu (no need of thumbnail selection). - - :param `gpmenu`: an instance of :class:`wx.Menu`. - """ - - self._gpmenu = gpmenu - - - def GetGlobalPopupMenu(self): - """ Returns the global thumbnailss popup menu (no need of thumbnail selection). """ - - return self._gpmenu - - - def GetSelectionColour(self): - """ Returns the colour used to indicate a selected thumbnail. """ - - return self._selectioncolour - - - def SetSelectionColour(self, colour=None): - """ - Sets the colour used to indicate a selected thumbnail. - - :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, it - will be taken from the system settings. - """ - - if colour is None: - colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) - - self._selectioncolour = colour - - - def EnableDragging(self, enable=True): - """ - Enables/disables thumbnails drag and drop. - - :param `enable`: ``True`` to enable drag and drop, ``False`` to disable it. - """ - - self._dragging = enable - - - def EnableToolTips(self, enable=True): - """ - Globally enables/disables thumbnail file information. - - :param `enable`: ``True`` to enable thumbnail file information, ``False`` to disable it. - """ - - self._enabletooltip = enable - - if not enable and hasattr(self, "_tipwindow"): - self._tipwindow.Enable(False) - - - def GetThumbInfo(self, thumb=-1): - """ - Returns the thumbnail information. - - :param `thumb`: the index of the thumbnail for which we are collecting information. - """ - - thumbinfo = None - - if thumb >= 0: - thumbinfo = "Name: " + self._items[thumb].GetFileName() + "\n" \ - "Size: " + self._items[thumb].GetFileSize() + "\n" \ - "Modified: " + self._items[thumb].GetCreationDate() + "\n" \ - "Dimensions: " + str(self._items[thumb].GetOriginalSize()) + "\n" \ - "Thumb: " + str(self.GetThumbSize()[0:2]) - - return thumbinfo - - - def SetThumbSize(self, width, height, border=6): - """ - Sets the thumbnail size as width, height and border. - - :param `width`: the desired thumbnail width; - :param `height`: the desired thumbnail height; - :param `border`: the spacing between thumbnails. - """ - - if width > 350 or height > 280: - return - - self._tWidth = width - self._tHeight = height - self._tBorder = border - self.SetScrollRate((self._tWidth + self._tBorder)/4, - (self._tHeight + self._tBorder)/4) - self.SetSizeHints(self._tWidth + self._tBorder*2 + 16, - self._tHeight + self._tBorder*2 + 8) - - - def GetThumbSize(self): - """ Returns the thumbnail size as width, height and border. """ - - return self._tWidth, self._tHeight, self._tBorder - - - def Clear(self): - """ Clears :class:`ThumbnailCtrl`. """ - - self._items = [] - self._selected = -1 - self._selectedarray = [] - self.UpdateProp() - self.Refresh() + def GetShowDir(self): + """ Returns the working directory with images. """ + return self._dir def ListDirectory(self, directory, fileExtList): """ @@ -1487,1016 +461,41 @@ class ScrolledThumbnail(wx.ScrolledWindow): return [f for f in os.listdir(directory) if lSplitExt(f)[1].lower() in fileExtList] - def ThreadImage(self, filenames): + def ShowFileNames(self, show=True): """ - Threaded method to load images. Used internally. + Sets whether the user wants to show file names under the thumbnails or not. - :param `filenames`: a Python list of file names containing images. + :param `show`: ``True`` to show file names under the thumbnails, ``False`` otherwise. """ - count = 0 + self._showfilenames = show + self._scrolled.ShowFileNames(show) - while count < len(filenames): - if not self._isrunning: - self._isrunning = False - thread.exit() - return + def OnThumbChar(self, ev): + for th in ev.thumbs: + thumb = self._scrolled.GetItem(th) - self.LoadImages(filenames[count], count) - if count < 4: - self.Refresh() - elif count%4 == 0: - self.Refresh() + if ev.keycode == wx.WXK_DELETE: + self.DeleteFiles(ev.thumbs) - count = count + 1 - - self._isrunning = False - thread.exit() - - - def LoadImages(self, newfile, imagecount): - """ - Threaded method to load images. Used internally. - - :param `newfile`: a file name containing an image to thumbnail; - :param `imagecount`: the number of images loaded until now. - """ - - if not self._isrunning: - thread.exit() - return - - img, originalsize, alpha = self._imageHandler.LoadThumbnail(newfile, (300, 240)) - try: - self._items[imagecount]._threadedimage = img - self._items[imagecount]._originalsize = originalsize - self._items[imagecount]._bitmap = img - self._items[imagecount]._alpha = alpha - except: - return - - - def ShowThumbs(self, thumbs, caption): - """ - Shows all the thumbnails. - - :param `thumbs`: should be a sequence with instances of :class:`Thumb`; - :param `caption`: the caption text for the current selected thumbnail. - """ - - self.SetCaption(caption) - - self._isrunning = False - - # update items - self._items = thumbs - myfiles = [thumb.GetFullFileName() for thumb in thumbs] - - items = self._items[:] - self._items.sort(key=KeyThumb) - - newfiles = SortFiles(items, self._items, myfiles) - self._isrunning = True - - thread.start_new_thread(self.ThreadImage, (newfiles,)) - wx.MilliSleep(20) - - self._selectedarray = [] - self.UpdateProp() - self.Refresh() - - - def ShowDir(self, folder, filter=THUMB_FILTER_IMAGES): - """ - Shows thumbnails for a particular folder. - - :param `folder`: a directory containing the images to thumbnail; - :param `filter`: filter images, video audio (currently implemented only for - images). - - .. todo:: Find a way to create thumbnails of video, audio and other formats. - """ - - self._dir = folder - if filter >= 0: - self._filter = filter - - self._parent.RecreateComboBox(folder) - - # update items - thumbs = [] - - filenames = self.ListDirectory(self._dir, extensions) - - for files in filenames: - - caption = (self._showfilenames and [files] or [""])[0] - fullfile = opj(self._dir + "/" + files) - if not os.path.isfile(fullfile): - continue - - stats = os.stat(fullfile) - size = stats[6] - - if size < 1000: - size = str(size) + " bytes" - elif size < 1000000: - size = str(int(round(size/1000.0))) + " Kb" - else: - size = str(round(size/1000000.0, 2)) + " Mb" - - lastmod = time.strftime(TIME_FMT, time.localtime(stats[8])) - - if self._filter & THUMB_FILTER_IMAGES: - thumbs.append(Thumb(self, folder, files, caption, size, lastmod)) - - return self.ShowThumbs(thumbs, caption=self._dir) - - - def GetShowDir(self): - """ Returns the working directory with images. """ - - return self._dir - - - def SetSelection(self, value=-1): - """ - Sets thumbnail selection. - - :param `value`: the thumbnail index to select. - """ - - self._selected = value - - if value != -1: - self._selectedarray = [value] - eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_SEL_CHANGED, self.GetId()) - self.GetEventHandler().ProcessEvent(eventOut) - self.ScrollToSelected() - self.Refresh() - - - def SetZoomFactor(self, zoom=1.4): - """ - Sets the zoom factor. - - :param `zoom`: a floating point number representing the zoom factor. Must be - greater than or equal to 1.0. - """ - - if zoom <= 1.0: - raise Exception("\nERROR: Zoom Factor Must Be Greater Than 1.0") - - self._zoomfactor = zoom - - - def GetZoomFactor(self): - """ Returns the zoom factor. """ - - return self._zoomfactor - - - def IsAudioVideo(self, fname): - """ - Returns ``True`` if a file contains either audio or video data. - Currently unused as :class:`ThumbnailCtrl` recognizes only image files. - - :param `fname`: a file name. - - .. todo:: Find a way to create thumbnails of video, audio and other formats. - """ - - return os.path.splitext(fname)[1].lower() in \ - [".mpg", ".mpeg", ".vob"] - - - def IsVideo(self, fname): - """ - Returns ``True`` if a file contains video data. - Currently unused as :class:`ThumbnailCtrl` recognizes only image files. - - :param `fname`: a file name. - - .. todo:: Find a way to create thumbnails of video, audio and other formats. - """ - - return os.path.splitext(fname)[1].lower() in \ - [".m1v", ".m2v"] - - - def IsAudio(self, fname): - """ - Returns ``True`` if a file contains audio data. - Currently unused as :class:`ThumbnailCtrl` recognizes only image files. - - :param `fname`: a file name. - - .. todo:: Find a way to create thumbnails of video, audio and other formats. - """ - - return os.path.splitext(fname)[1].lower() in \ - [".mpa", ".mp2", ".mp3", ".ac3", ".dts", ".pcm"] - - - def UpdateItems(self): - """ Updates thumbnail items. """ - - selected = self._selectedarray - selectedfname = [] - selecteditemid = [] - - for ii in range(len(self._selectedarray)): - selectedfname.append(self.GetSelectedItem(ii).GetFileName()) - selecteditemid.append(self.GetSelectedItem(ii).GetId()) - - self.UpdateShow() - - if len(selected) > 0: - self._selectedarray = [] - for ii in range(len(self._items)): - for jj in range(len(selected)): - if self._items[ii].GetFileName() == selectedfname[jj] and \ - self._items[ii].GetId() == selecteditemid[jj]: - - self._selectedarray.append(ii) - if len(self._selectedarray) == 1: - self.ScrollToSelected() - - if len(self._selectedarray) > 0: - self.Refresh() - eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_SEL_CHANGED, self.GetId()) - self.GetEventHandler().ProcessEvent(eventOut) - - - def SetCaption(self, caption=""): - """ - Sets the current caption string. - - :param `caption`: the current caption string. - """ - - self._caption = caption - if self._labelcontrol: - - maxWidth = self._labelcontrol.GetSize().GetWidth()/8 - if len(caption) > maxWidth: - caption = "..." + caption[len(caption) + 3 - maxWidth] - - self._labelcontrol.SetLabel(caption) - - eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_CAPTION_CHANGED, self.GetId()) - self.GetEventHandler().ProcessEvent(eventOut) - - - def SetCaptionFont(self, font=None): - """ - Sets the font for all the thumbnail captions. - - :param `font`: a valid :class:`wx.Font` object. If defaulted to ``None``, a standard - font will be generated. - """ - - if font is None: - font = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False) - - self._captionfont = font - - - def GetCaptionFont(self): - """ Returns the font for all the thumbnail captions. """ - - return self._captionfont - - - def UpdateShow(self): - """ Updates thumbnail items. """ - - self.ShowThumbs(self._items) - - - def GetCaptionHeight(self, begRow, count=1): - """ - Returns the height for the file name caption. - - :param `begRow`: the caption line at which we start measuring the height; - :param `count`: the number of lines to measure. - """ - - capHeight = 0 - for ii in range(begRow, begRow + count): - if ii < len(self._tCaptionHeight): - capHeight = capHeight + self._tCaptionHeight[ii] - - return capHeight*self._tTextHeight - - - def GetItemIndex(self, x, y): - """ - Returns the thumbnail index at position (x, y). - - :param `x`: the mouse `x` position; - :param `y`: the mouse `y` position. - """ - - col = (x - self._tBorder)//(self._tWidth + self._tBorder) - - if col >= self._cols: - col = self._cols - 1 - - row = -1 - y = y - self._tBorder - - while y > 0: - - row = row + 1 - y = y - (self._tHeight + self._tBorder + self.GetCaptionHeight(row)) - - if row < 0: - row = 0 - - index = row*self._cols + col - - if index >= len(self._items): - index = -1 - - return index - - - def UpdateProp(self, checkSize=True): - """ - Updates :class:`ThumbnailCtrl` and its visible thumbnails. - - :param `checkSize`: ``True`` to update the items visibility if the window - size has changed. - """ - - width = self.GetClientSize().GetWidth() - self._cols = (width - self._tBorder)//(self._tWidth + self._tBorder) - - if self._cols <= 0: - self._cols = 1 - - tmpvar = (len(self._items)%self._cols and [1] or [0])[0] - self._rows = len(self._items)//self._cols + tmpvar - - self._tCaptionHeight = [] - - for row in range(self._rows): - - capHeight = 0 - - for col in range(self._cols): - - ii = row*self._cols + col - - if len(self._items) > ii and \ - self._items[ii].GetCaptionLinesCount(self._tWidth - self._tCaptionBorder) > capHeight: - - capHeight = self._items[ii].GetCaptionLinesCount(self._tWidth - self._tCaptionBorder) - - self._tCaptionHeight.append(capHeight) - - self.SetVirtualSize((self._cols*(self._tWidth + self._tBorder) + self._tBorder, - self._rows*(self._tHeight + self._tBorder) + \ - self.GetCaptionHeight(0, self._rows) + self._tBorder)) - - self.SetSizeHints(self._tWidth + 2*self._tBorder + 16, - self._tHeight + 2*self._tBorder + 8 + \ - (self._rows and [self.GetCaptionHeight(0)] or [0])[0]) - - if checkSize and width != self.GetClientSize().GetWidth(): - self.UpdateProp(False) - - - def InsertItem(self, thumb, pos): - """ - Inserts a thumbnail in the specified position. - - :param `pos`: the index at which we wish to insert the new thumbnail. - """ - - if pos < 0 or pos > len(self._items): - self._items.append(thumb) - else: - self._items.insert(pos, thumb) - - self.UpdateProp() - - - def RemoveItemAt(self, pos): - """ - Removes a thumbnail at the specified position. - - :param `pos`: the index at which we wish to remove the thumbnail. - """ - - del self._items[pos] - - self.UpdateProp() - - - def GetPaintRect(self): - """ Returns the paint bounding rect for the :meth:`~ScrolledThumbnail.OnPaint` method. """ - - size = self.GetClientSize() - paintRect = wx.Rect(0, 0, size.GetWidth(), size.GetHeight()) - paintRect.x, paintRect.y = self.GetViewStart() - xu, yu = self.GetScrollPixelsPerUnit() - paintRect.x = paintRect.x*xu - paintRect.y = paintRect.y*yu - - return paintRect - - - def IsSelected(self, indx): - """ - Returns whether a thumbnail is selected or not. - - :param `indx`: the index of the thumbnail to check for selection. - """ - - return self._selectedarray.count(indx) != 0 - - - def GetSelection(self, selIndex=-1): - """ - Returns the selected thumbnail. - - :param `selIndex`: if not equal to -1, the index of the selected thumbnail. - """ - - return (selIndex == -1 and [self._selected] or \ - [self._selectedarray[selIndex]])[0] - - - def GetOriginalImage(self, index=None): - """ - Returns the original image associated to a thumbnail. - - :param `index`: the index of the thumbnail. If defaulted to ``None``, the current - selection is used. - """ - - if index is None: - index = self.GetSelection() - - return self._items[index].GetOriginalImage() - - - def ScrollToSelected(self): - """ Scrolls the :class:`ScrolledWindow` to the selected thumbnail. """ - - if self.GetSelection() == -1: - return - - # get row - row = self.GetSelection()//self._cols - # calc position to scroll view - - paintRect = self.GetPaintRect() - y1 = row*(self._tHeight + self._tBorder) + self.GetCaptionHeight(0, row) - y2 = y1 + self._tBorder + self._tHeight + self.GetCaptionHeight(row) - - if y1 < paintRect.GetTop(): - sy = y1 # scroll top - elif y2 > paintRect.GetBottom(): - sy = y2 - paintRect.height # scroll bottom - else: - return - - # scroll view - xu, yu = self.GetScrollPixelsPerUnit() - sy = sy/yu + (sy%yu and [1] or [0])[0] # convert sy to scroll units - x, y = self.GetViewStart() - - self.Scroll(x,sy) - - - def CalculateBestCaption(self, dc, caption, sw, width): - """ - Calculates the best caption string to show based on the actual zoom factor. - - :param `dc`: an instance of :class:`wx.DC`; - :param `caption`: the original caption string; - :param `sw`: the maximum width allowed for the caption string, in pixels; - :param `width`: the caption string width, in pixels. - """ - - caption = caption + "..." - - while sw > width: - caption = caption[1:] - sw, sh = dc.GetTextExtent(caption) - - return "..." + caption[0:-3] - - - def DrawThumbnail(self, bmp, thumb, index): - """ - Draws a visible thumbnail. - - :param `bmp`: the thumbnail version of the original image; - :param `thumb`: an instance of :class:`Thumb`; - :param `index`: the index of the thumbnail to draw. - """ - - dc = wx.MemoryDC() - dc.SelectObject(bmp) - - x = self._tBorder/2 - y = self._tBorder/2 - - # background - dc.SetPen(wx.Pen(wx.BLACK, 0, wx.TRANSPARENT)) - dc.SetBrush(wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID)) - dc.DrawRectangle(0, 0, bmp.GetWidth(), bmp.GetHeight()) - - # image - img = thumb.GetBitmap(self._tWidth, self._tHeight) - ww = img.GetWidth() - hh = img.GetHeight() - - if index == self.GetPointed() and self.GetHighlightPointed(): - factor = 1.5 - img = self._imageHandler.HighlightImage(img.ConvertToImage(), factor).ConvertToBitmap() - - imgRect = wx.Rect(x + (self._tWidth - img.GetWidth())/2, - y + (self._tHeight - img.GetHeight())/2, - img.GetWidth(), img.GetHeight()) - - if not thumb._alpha and self._dropShadow: - dc.Blit(imgRect.x+5, imgRect.y+5, imgRect.width, imgRect.height, self.shadow, 500-ww, 500-hh) - dc.DrawBitmap(img, imgRect.x, imgRect.y, True) - - colour = self.GetSelectionColour() - selected = self.IsSelected(index) - - colour = self.GetSelectionColour() - - # draw caption - sw, sh = 0, 0 - if self._showfilenames: - textWidth = 0 - dc.SetFont(self.GetCaptionFont()) - mycaption = thumb.GetCaption(0) - sw, sh = dc.GetTextExtent(mycaption) - - if sw > self._tWidth: - mycaption = self.CalculateBestCaption(dc, mycaption, sw, self._tWidth) - sw = self._tWidth - - textWidth = sw + 8 - tx = x + (self._tWidth - textWidth)/2 - ty = y + self._tHeight - - txtcolour = "#7D7D7D" - dc.SetTextForeground(txtcolour) - - tx = x + (self._tWidth - sw)/2 - if hh >= self._tHeight: - ty = y + self._tHeight + (self._tTextHeight - sh)/2 + 3 - else: - ty = y + hh + (self._tHeight-hh)/2 + (self._tTextHeight - sh)/2 + 3 - - dc.DrawText(mycaption, tx, ty) - - # outline - if self._tOutline != THUMB_OUTLINE_NONE and (self._tOutlineNotSelected or self.IsSelected(index)): - - dotrect = wx.Rect() - dotrect.x = x - 2 - dotrect.y = y - 2 - dotrect.width = bmp.GetWidth() - self._tBorder + 4 - dotrect.height = bmp.GetHeight() - self._tBorder + 4 - - dc.SetPen(wx.Pen((self.IsSelected(index) and [colour] or [wx.LIGHT_GREY])[0], - 0, wx.PENSTYLE_SOLID)) - dc.SetBrush(wx.Brush(wx.BLACK, wx.BRUSHSTYLE_TRANSPARENT)) - - if self._tOutline == THUMB_OUTLINE_FULL or self._tOutline == THUMB_OUTLINE_RECT: - - imgRect.x = x - imgRect.y = y - imgRect.width = bmp.GetWidth() - self._tBorder - imgRect.height = bmp.GetHeight() - self._tBorder - - if self._tOutline == THUMB_OUTLINE_RECT: - imgRect.height = self._tHeight - - dc.SetBrush(wx.TRANSPARENT_BRUSH) - - if selected: - - dc.SetPen(self.grayPen) - dc.DrawRoundedRectangle(dotrect, 2) - - dc.SetPen(wx.Pen(wx.WHITE)) - dc.DrawRectangle(imgRect.x, imgRect.y, - imgRect.width, imgRect.height) - - pen = wx.Pen((selected and [colour] or [wx.LIGHT_GREY])[0], 2) - pen.SetJoin(wx.JOIN_MITER) - dc.SetPen(pen) - if self._tOutline == THUMB_OUTLINE_FULL: - dc.DrawRoundedRectangle(imgRect.x - 1, imgRect.y - 1, - imgRect.width + 3, imgRect.height + 3, 2) - else: - dc.DrawRectangle(imgRect.x - 1, imgRect.y - 1, - imgRect.width + 3, imgRect.height + 3) - else: - dc.SetPen(wx.Pen(wx.LIGHT_GREY)) - - dc.DrawRectangle(imgRect.x - 1, imgRect.y - 1, - imgRect.width + 2, imgRect.height + 2) - - - dc.SelectObject(wx.NullBitmap) - - - def OnPaint(self, event): - """ - Handles the ``wx.EVT_PAINT`` event for :class:`ThumbnailCtrl`. - - :param `event`: a :class:`PaintEvent` event to be processed. - """ - - paintRect = self.GetPaintRect() - - dc = wx.BufferedPaintDC(self) - self.PrepareDC(dc) - - dc.SetPen(wx.Pen(wx.BLACK, 0, wx.PENSTYLE_TRANSPARENT)) - dc.SetBrush(wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID)) - - w, h = self.GetClientSize() - - # items - row = -1 - xwhite = self._tBorder - - for ii in range(len(self._items)): - - col = ii%self._cols - if col == 0: - row = row + 1 - - xwhite = ((w - self._cols*(self._tWidth + self._tBorder)))/(self._cols+1) - tx = xwhite + col*(self._tWidth + self._tBorder) - - ty = self._tBorder/2 + row*(self._tHeight + self._tBorder) + \ - self.GetCaptionHeight(0, row) - tw = self._tWidth + self._tBorder - th = self._tHeight + self.GetCaptionHeight(row) + self._tBorder - # visible? - if not paintRect.Intersects(wx.Rect(tx, ty, tw, th)): - continue - - thmb = wx.Bitmap(tw, th) - self.DrawThumbnail(thmb, self._items[ii], ii) - dc.DrawBitmap(thmb, tx, ty) - - rect = wx.Rect(xwhite, self._tBorder/2, - self._cols*(self._tWidth + self._tBorder), - self._rows*(self._tHeight + self._tBorder) + \ - self.GetCaptionHeight(0, self._rows)) - - w = max(self.GetClientSize().GetWidth(), rect.width) - h = max(self.GetClientSize().GetHeight(), rect.height) - dc.DrawRectangle(0, 0, w, rect.y) - dc.DrawRectangle(0, 0, rect.x, h) - dc.DrawRectangle(rect.GetRight(), 0, w - rect.GetRight(), h + 50) - dc.DrawRectangle(0, rect.GetBottom(), w, h - rect.GetBottom() + 50) - - col = len(self._items)%self._cols - - if col > 0: - rect.x = rect.x + col*(self._tWidth + self._tBorder) - rect.y = rect.y + (self._rows - 1)*(self._tHeight + self._tBorder) + \ - self.GetCaptionHeight(0, self._rows - 1) - dc.DrawRectangle(rect) - - - def OnResize(self, event): - """ - Handles the ``wx.EVT_SIZE`` event for :class:`ThumbnailCtrl`. - - :param `event`: a :class:`wx.SizeEvent` event to be processed. - """ - - self.UpdateProp() - self.ScrollToSelected() - self.Refresh() - - - def OnMouseDown(self, event): - """ - Handles the ``wx.EVT_LEFT_DOWN`` and ``wx.EVT_RIGHT_DOWN`` events for :class:`ThumbnailCtrl`. - - :param `event`: a :class:`MouseEvent` event to be processed. - """ - - x = event.GetX() - y = event.GetY() - x, y = self.CalcUnscrolledPosition(x, y) - # get item number to select - lastselected = self._selected - self._selected = self.GetItemIndex(x, y) - - self._mouseeventhandled = False - update = False - - if event.ControlDown(): - if self._selected == -1: - self._mouseeventhandled = True - elif not self.IsSelected(self._selected): - self._selectedarray.append(self._selected) - update = True - self._mouseeventhandled = True - - elif event.ShiftDown(): - if self._selected != -1: - begindex = self._selected - endindex = lastselected - if lastselected < self._selected: - begindex = lastselected - endindex = self._selected - self._selectedarray = [] - - for ii in range(begindex, endindex+1): - self._selectedarray.append(ii) - - update = True - - self._selected = lastselected - self._mouseeventhandled = True - - else: - - if self._selected == -1: - update = len(self._selectedarray) > 0 - self._selectedarray = [] - self._mouseeventhandled = True - elif len(self._selectedarray) <= 1: - try: - update = len(self._selectedarray)== 0 or self._selectedarray[0] != self._selected - except: - update = True - self._selectedarray = [] - self._selectedarray.append(self._selected) - self._mouseeventhandled = True - - if update: - self.ScrollToSelected() - self.Refresh() - eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_SEL_CHANGED, self.GetId()) - self.GetEventHandler().ProcessEvent(eventOut) - - self.SetFocus() - - - def OnMouseUp(self, event): - """ - Handles the ``wx.EVT_LEFT_UP`` and ``wx.EVT_RIGHT_UP`` events for :class:`ThumbnailCtrl`. - - :param `event`: a :class:`MouseEvent` event to be processed. - """ - - # get item number to select - x = event.GetX() - y = event.GetY() - x, y = self.CalcUnscrolledPosition(x, y) - lastselected = self._selected - self._selected = self.GetItemIndex(x,y) - - if not self._mouseeventhandled: - # set new selection - if event.ControlDown(): - if self._selected in self._selectedarray: - self._selectedarray.remove(self._selected) - - self._selected = -1 - else: - self._selectedarray = [] - self._selectedarray.append(self._selected) - - self.ScrollToSelected() - self.Refresh() - eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_SEL_CHANGED, self.GetId()) - self.GetEventHandler().ProcessEvent(eventOut) - - # Popup menu - if event.RightUp(): - if self._selected >= 0 and self._pmenu: - self.PopupMenu(self._pmenu) - elif self._selected >= 0 and not self._pmenu and self._gpmenu: - self.PopupMenu(self._gpmenu) - elif self._selected == -1 and self._gpmenu: - self.PopupMenu(self._gpmenu) - - if event.ShiftDown(): - self._selected = lastselected - - - def OnMouseDClick(self, event): - """ - Handles the ``wx.EVT_LEFT_DCLICK`` event for :class:`ThumbnailCtrl`. - - :param `event`: a :class:`MouseEvent` event to be processed. - """ - - eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_DCLICK, self.GetId()) - self.GetEventHandler().ProcessEvent(eventOut) - - - def OnMouseMove(self, event): - """ - Handles the ``wx.EVT_MOTION`` event for :class:`ThumbnailCtrl`. - - :param `event`: a :class:`MouseEvent` event to be processed. - """ - - # -- drag & drop -- - if self._dragging and event.Dragging() and len(self._selectedarray) > 0: - - files = wx.FileDataObject() - for ii in range(len(self._selectedarray)): - files.AddFile(opj(self.GetSelectedItem(ii).GetFullFileName())) - - source = wx.DropSource(self) - source.SetData(files) - source.DoDragDrop(wx.Drag_DefaultMove) - - # -- light-effect -- - x = event.GetX() - y = event.GetY() - x, y = self.CalcUnscrolledPosition(x, y) - - # get item number - sel = self.GetItemIndex(x, y) - - if sel == self._pointed: - if self._enabletooltip and sel >= 0: - if not hasattr(self, "_tipwindow"): - self._tipwindow = wx.ToolTip(self.GetThumbInfo(sel)) - self._tipwindow.SetDelay(1000) - self.SetToolTip(self._tipwindow) - else: - self._tipwindow.SetDelay(1000) - self._tipwindow.SetTip(self.GetThumbInfo(sel)) - - event.Skip() - return - - if self._enabletooltip: - if hasattr(self, "_tipwindow"): - self._tipwindow.Enable(False) - - # update thumbnail - self._pointed = sel - - if self._enabletooltip and sel >= 0: - if not hasattr(self, "_tipwindow"): - self._tipwindow = wx.ToolTip(self.GetThumbInfo(sel)) - self._tipwindow.SetDelay(1000) - self._tipwindow.Enable(True) - self.SetToolTip(self._tipwindow) - else: - self._tipwindow.SetDelay(1000) - self._tipwindow.Enable(True) - self._tipwindow.SetTip(self.GetThumbInfo(sel)) - - self.Refresh() - eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_POINTED, self.GetId()) - self.GetEventHandler().ProcessEvent(eventOut) - event.Skip() - - - def OnMouseLeave(self, event): - """ - Handles the ``wx.EVT_LEAVE_WINDOW`` event for :class:`ThumbnailCtrl`. - - :param `event`: a :class:`MouseEvent` event to be processed. - """ - - if self._pointed != -1: - - self._pointed = -1 - self.Refresh() - eventOut = ThumbnailEvent(wxEVT_THUMBNAILS_POINTED, self.GetId()) - self.GetEventHandler().ProcessEvent(eventOut) - - - def OnThumbChanged(self, event): - """ - Handles the ``EVT_THUMBNAILS_THUMB_CHANGED`` event for :class:`ThumbnailCtrl`. - - :param `event`: a :class:`ThumbnailEvent` event to be processed. - """ - - for ii in range(len(self._items)): - if self._items[ii].GetFileName() == event.GetString(): - - self._items[ii].SetFilename(self._items[ii].GetFileName()) - if event.GetClientData(): - - img = wx.Image(event.GetClientData()) - self._items[ii].SetImage(img) - - self.Refresh() - - - def OnChar(self, event): - """ - Handles the ``wx.EVT_CHAR`` event for :class:`ThumbnailCtrl`. - - :param `event`: a :class:`KeyEvent` event to be processed. - - :note: You have these choices: - - (1) ``d`` key rotates 90 degrees clockwise the selected thumbnails; - (2) ``s`` key rotates 90 degrees counter-clockwise the selected thumbnails; - (3) ``a`` key rotates 180 degrees the selected thumbnails; - (4) ``Del`` key deletes the selected thumbnails; - (5) ``+`` key zooms in; - (6) ``-`` key zooms out. - """ - - if event.KeyCode == ord("s"): - self.Rotate() - elif event.KeyCode == ord("d"): - self.Rotate(270) - elif event.KeyCode == ord("a"): - self.Rotate(180) - elif event.KeyCode == wx.WXK_DELETE: - self.DeleteFiles() - elif event.KeyCode in [wx.WXK_ADD, wx.WXK_NUMPAD_ADD]: - self.ZoomIn() - elif event.KeyCode in [wx.WXK_SUBTRACT, wx.WXK_NUMPAD_SUBTRACT]: - self.ZoomOut() - - event.Skip() - - - def Rotate(self, angle=90): - """ - Rotates the selected thumbnails by the angle specified by `angle`. - - :param `angle`: the rotation angle for the thumbnail, in degrees. - """ - - wx.BeginBusyCursor() - - count = 0 - selected = [] - - for ii in range(len(self._items)): - if self.IsSelected(ii): - selected.append(self._items[ii]) - - dlg = wx.ProgressDialog("Thumbnail Rotation", - "Rotating Thumbnail... Please Wait", - maximum = len(selected)+1, - parent=None) - - for thumb in selected: - count = count + 1 - if TN_USE_PIL: - newangle = thumb.GetRotation()*180/pi + angle - fil = opj(thumb.GetFullFileName()) - with Image.open(fil) as fid: - pil = fid.rotate(newangle) - img = wx.Image(pil.size[0], pil.size[1]) - img.SetData(pil.convert('RGB').tobytes()) - thumb.SetRotation(newangle*pi/180) - else: - img = thumb._threadedimage - newangle = thumb.GetRotation() + angle*pi/180 - thumb.SetRotation(newangle) - img = img.Rotate(newangle, (img.GetWidth()/2, img.GetHeight()/2), True) - - thumb.SetRotatedImage(img) - dlg.Update(count) - - wx.EndBusyCursor() - dlg.Destroy() - - if self.GetSelection() != -1: - self.Refresh() - - - def DeleteFiles(self): + def DeleteFiles(self, thumbs): """ Deletes the selected thumbnails and their associated files. - .. warning:: This method deletes the original files too. + + :parem `thumbs`: List of indexs to thumbnails. + """ - dlg = wx.MessageDialog(self, 'Are you sure you want to delete the files?', - 'Confirmation', - wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) + msg = "" + for index in thumbs: + thumb = self._scrolled.GetItem(index) + msg += thumb.GetFileName() + '\n' - if dlg.ShowModal() == wx.ID_YES: + dlg = ScrolledTextDialog(self, 'Delete these files?', msg) + + if dlg.ShowModal() == wx.ID_OK: errordelete = [] count = 0 @@ -2504,16 +503,16 @@ class ScrolledThumbnail(wx.ScrolledWindow): wx.BeginBusyCursor() - for ii in range(len(self._items)): - if self.IsSelected(ii): - thumb = self._items[ii] - files = self._items[ii].GetFullFileName() - filename = opj(files) - try: - os.remove(filename) - count = count + 1 - except: - errordelete.append(files) + # Delete thumbnails & files from highest index to lowest + for index in reversed(thumbs): + thumb = self._scrolled.GetItem(index) + filename = thumb.GetFullFileName() + try: + os.remove(filename) + self._scrolled.RemoveItemAt(index) + count = count + 1 + except: + errordelete.append(self._scrolled.GetFileName(index)) wx.EndBusyCursor() @@ -2528,71 +527,3 @@ class ScrolledThumbnail(wx.ScrolledWindow): wx.OK | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() - - if count: - self.UpdateShow() - - - def OnMouseWheel(self, event): - """ - Handles the ``wx.EVT_MOUSEWHEEL`` event for :class:`ThumbnailCtrl`. - - :param `event`: a :class:`MouseEvent` event to be processed. - - :note: If you hold down the ``Ctrl`` key, you can zoom in/out with the mouse wheel. - """ - - if event.ControlDown(): - if event.GetWheelRotation() > 0: - self.ZoomIn() - else: - self.ZoomOut() - else: - event.Skip() - - - def ZoomOut(self): - """ Zooms the thumbnails out. """ - - w, h, b = self.GetThumbSize() - - if w < 40 or h < 40: - return - - zoom = self.GetZoomFactor() - neww = float(w)/zoom - newh = float(h)/zoom - - self.SetThumbSize(int(neww), int(newh)) - self.OnResize(None) - self._checktext = True - - self.Refresh() - - - def ZoomIn(self): - """ Zooms the thumbnails in. """ - - size = self.GetClientSize() - w, h, b = self.GetThumbSize() - zoom = self.GetZoomFactor() - - if w*zoom + b > size.GetWidth() or h*zoom + b > size.GetHeight(): - if w*zoom + b > size.GetWidth(): - neww = size.GetWidth() - 2*self._tBorder - newh = (float(h)/w)*neww - else: - newh = size.GetHeight() - 2*self._tBorder - neww = (float(w)/h)*newh - - else: - neww = float(w)*zoom - newh = float(h)*zoom - - self.SetThumbSize(int(neww), int(newh)) - self.OnResize(None) - self._checktext = True - - self.Refresh() - -