mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-27 21:52:25 -06:00
2592 lines
84 KiB
Python
2592 lines
84 KiB
Python
# --------------------------------------------------------------------------- #
|
|
# THUMBNAILCTRL Control wxPython IMPLEMENTATION
|
|
# Python Code By:
|
|
#
|
|
# 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!!!
|
|
#
|
|
# Tags: phoenix-port, documented, unittest, py3-port
|
|
#
|
|
# End Of Comments
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
"""
|
|
:class:`ThumbnailCtrl` is a widget that can be used to display a series of images in
|
|
a "thumbnail" format.
|
|
|
|
|
|
Description
|
|
===========
|
|
|
|
:class:`ThumbnailCtrl` is 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 is partly based on `wxVillaLib`, a wxWidgets implementation of this
|
|
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.
|
|
|
|
|
|
Usage
|
|
=====
|
|
|
|
Usage example::
|
|
|
|
import os
|
|
|
|
import wx
|
|
import wx.lib.agw.thumbnailctrl as TC
|
|
|
|
class MyFrame(wx.Frame):
|
|
|
|
def __init__(self, parent):
|
|
|
|
wx.Frame.__init__(self, parent, -1, "ThumbnailCtrl Demo")
|
|
|
|
panel = wx.Panel(self)
|
|
|
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
thumbnail = TC.ThumbnailCtrl(panel, imagehandler=TC.NativeImageHandler)
|
|
sizer.Add(thumbnail, 1, wx.EXPAND | wx.ALL, 10)
|
|
|
|
thumbnail.ShowDir(os.getcwd())
|
|
panel.SetSizer(sizer)
|
|
|
|
|
|
# our normal wxApp-derived class, as usual
|
|
|
|
app = wx.App(0)
|
|
|
|
frame = MyFrame(None)
|
|
app.SetTopWindow(frame)
|
|
frame.Show()
|
|
|
|
app.MainLoop()
|
|
|
|
|
|
|
|
Methods and Settings
|
|
====================
|
|
|
|
With :class:`ThumbnailCtrl` 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.
|
|
|
|
- Delete files/thumbnails (via the ``del`` key);
|
|
- Drag and drop thumbnails from :class:`ThumbnailCtrl` to whatever application you want;
|
|
- Use local (when at least one thumbnail is selected) or global (no need for
|
|
thumbnail selection) popup menus;
|
|
- Show/hide a :class:`ComboBox` at the top of :class:`ThumbnailCtrl`: this combobox contains
|
|
working directory information and it has history entries;
|
|
- 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:
|
|
|
|
================================== ==================================================
|
|
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.
|
|
================================== ==================================================
|
|
|
|
|
|
License And Version
|
|
===================
|
|
|
|
:class:`ThumbnailCtrl` is distributed under the wxPython license.
|
|
|
|
Latest revision: Andrea Gavana @ 27 Dec 2012, 21.00 GMT
|
|
|
|
Version 0.9
|
|
|
|
"""
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
# Beginning Of ThumbnailCtrl wxPython Code
|
|
#----------------------------------------------------------------------
|
|
|
|
import wx
|
|
import os
|
|
import time
|
|
import zlib
|
|
|
|
import wx.lib.six as 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:`Bitmap`. """
|
|
|
|
return wx.Bitmap(GetMondrianImage())
|
|
|
|
|
|
def GetMondrianImage():
|
|
""" Returns a default image placeholder as a :class:`Image`. """
|
|
|
|
stream = six.StringIO(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<C8\x80\xa0\x86#\xa5\x83\x81\x81y\x96\xa7\x8bcH\xc5\
|
|
\x9c\xb7\xd7\xd7\xf6\x85D2\xb4^\xbc\x1b\xd0\xd0p\xa6\x85\x9d\xa1\xf1\xc0\xc7\
|
|
\x7f\xef\x8d\x98\xf89_:p]\xaew\x0c\xe9\x16[\xbc\x8bSt\xdf\x9aT\xad\xef\xcb\
|
|
\x8e\x98\xc5\xbf\xb3\x94\x9ePT\xf8\xff\xf7\xfbm\xf5\xdb\xfeZ<\x16{\xf01o[l\
|
|
\xee\xee\xbd7\xbe\x95\xdd\xde\x9d+\xbf\xfdo\xf9\xb9\xd0\x03\x8be\xb7\xc7\xe6\
|
|
Y\xcb\xbd\x8b\xdfs\xe3[\xd6\xed\xe5\x9b}\x99\xe6=:\xbd\xed\xfc\xedu|\xfcq\
|
|
\xfb\xec/K<\xf8\xfec\xd7\xdb\xdb\x87W\xec\xcf\xfd]\xb0\xcc\xf0\xc0\xe5=\xf7^\
|
|
\x1e\xf9\xfb\xe6\xe6\xce\xe9\x0c\xfb\xa7\xafPt\xbb"\xa0\x9c\xd5!hz\xa4C*\xc9\
|
|
\x85\xd7pQ\x9bD\xa0s\xcf\xa8\xf0\xa8\xf0\x00\x0b\x9fyX\x7fo\xef\xdf\xc7\xda\
|
|
\r\xcbw\xd4\xfcx\xe0\xcdk\xd8\x9e[~{\xdd\xf6\xbfw\xbe\xfd\xddS\xdc\xe0\xfec_\
|
|
\xf0\xfe\xeb\xb7\xdf\xf1\xdd\xce\xdb&\xbb\xbdv\xe7\xff\xc3\xaf\x8dy\x99\xe5\
|
|
\x9e?\xf7\xfb+\xb7\xfdnLN\xf5\xc6\xb7g\xfd\xca_\x9a\x7f?\x7f\xe0\xfe\xe3\xaa\
|
|
\xe5\x0b\xf4\xb7\xcb\xea\x97\xed\n\xb7\xbf\xff\xadh\xf9\xe3\x1d\xd5f\x7fb\
|
|
\xdf\x95Y\x15\xc6\xe7\xee\xfe\xcbz7Y\xbd\xde[\xf3y\x1f0\xd72x\xba\xfa\xb9\
|
|
\xacsJh\x02\x00\xc4i\x8dN' )
|
|
|
|
|
|
def getDataBL():
|
|
""" Return the second 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\xac \xcc\xc1\
|
|
\x06${\xf3\xd5\x9e\x02)\x96b'\xcf\x10\x0e \xa8\xe1H\xe9\x00\xf2\xed=]\x1cC8f\
|
|
\xea\x9e\xde\xcb\xd9` \xc2\xf0P\xdf~\xc9y\xaeu\x0f\xfe1\xdf\xcc\x14\x1482A\
|
|
\xe9\xfd\x83\x1d\xaf\x84\xac\xf8\xe6\\\x8c3\xfc\x98\xf8\xa0\xb1\xa9K\xec\x9f\
|
|
\xc4\xd1\xb4GG{\xb5\x15\x8f_|t\x8a[a\x1fWzG\xa9\xc4,\xa0Q\x0c\x9e\xae~.\xeb\
|
|
\x9c\x12\x9a\x00\x7f1,7" )
|
|
|
|
|
|
def getDataTR():
|
|
""" Return the third 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\xac \xcc\xc1\
|
|
\x06${\xf3\xd5\x9e\x02)\x96b\'\xcf\x10\x0e \xa8\xe1H\xe9\x00\xf2m=]\x1cC8f\
|
|
\xe6\x9e\xd9\xc8\xd9` \xc2p\x91\xbd\xaei\xeeL\x85\xdcUo\xf6\xf7\xd6\xb2\x88\
|
|
\x0bp\x9a\x89i\x16=-\x94\xe16\x93\xb9!\xb8y\xcd\t\x0f\x89\n\xe6\xb7\xfcV~6\
|
|
\x8dFo\xf5\xee\xc8\x1fOaw\xc9\x88\x0c\x16\x05\x1a\xc4\xe0\xe9\xea\xe7\xb2\
|
|
\xce)\xa1\t\x00"\xf9$\x83' )
|
|
|
|
|
|
def getShadow():
|
|
""" Creates a shadow behind every thumbnail. """
|
|
|
|
sh_tr = wx.Image(six.BytesIO(getDataTR())).ConvertToBitmap()
|
|
sh_bl = wx.Image(six.BytesIO(getDataBL())).ConvertToBitmap()
|
|
sh_sh = wx.Image(six.BytesIO(getDataSH())).Rescale(500, 500, wx.IMAGE_QUALITY_HIGH)
|
|
return (sh_tr, sh_bl, sh_sh.ConvertToBitmap())
|
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# PATH & FILE FILLING (OS INDEPENDENT)
|
|
#-----------------------------------------------------------------------------
|
|
|
|
def opj(path):
|
|
"""
|
|
Convert paths to the platform-specific separator.
|
|
|
|
:param `path`: the path to convert.
|
|
"""
|
|
|
|
strs = os.path.join(*tuple(path.split('/')))
|
|
# HACK: on Linux, a leading / gets lost...
|
|
if path.startswith('/'):
|
|
strs = '/' + strs
|
|
|
|
return strs
|
|
|
|
#-----------------------------------------------------------------------------
|
|
|
|
# Different Outline On Thumb Selection:
|
|
# THUMB_OUTLINE_NONE: No Outline Drawn On Selection
|
|
# THUMB_OUTLINE_FULL: Full Outline Drawn On Selection
|
|
# THUMB_OUTLINE_RECT: Only Maximum Image Rect Outlined On Selection
|
|
# THUMB_OUTLINE_IMAGE: Only Image Rect Outlined On Selection
|
|
|
|
THUMB_OUTLINE_NONE = 0
|
|
""" No outline drawn on selection. """
|
|
THUMB_OUTLINE_FULL = 1
|
|
""" Full outline drawn on selection. """
|
|
THUMB_OUTLINE_RECT = 2
|
|
""" Only the maximum image rectangle outlined on selection. """
|
|
THUMB_OUTLINE_IMAGE = 4
|
|
""" Only the image rectangle outlined on selection. """
|
|
|
|
# Options For Filtering Files
|
|
THUMB_FILTER_IMAGES = 1
|
|
""" Only load images. We dont know yet how to create thumbnails for other file types... """
|
|
|
|
# THUMB_FILTER_VIDEOS = 2 Don't Know How To Create Thumbnails For Videos!!!
|
|
|
|
# ThumbnailCtrl Orientation: Not Fully Implemented Till Now
|
|
THUMB_HORIZONTAL = wx.HORIZONTAL
|
|
""" The :class:`ThumbnailCtrl` will be horizontal. """
|
|
THUMB_VERTICAL = wx.VERTICAL
|
|
""" The :class:`ThumbnailCtrl` will be vertical. """
|
|
|
|
# Image File Name Extensions: Am I Missing Some Extensions Here?
|
|
extensions = [".jpeg", ".jpg", ".bmp", ".png", ".ico", ".tiff", ".ani", ".cur", ".gif",
|
|
".iff", ".icon", ".pcx", ".tif", ".xpm", ".xbm", ".mpeg", ".mpg", ".mov"]
|
|
|
|
# ThumbnailCtrl Events:
|
|
# wxEVT_THUMBNAILS_SEL_CHANGED: Event Fired When You Change Thumb Selection
|
|
# wxEVT_THUMBNAILS_POINTED: Event Fired When You Point A Thumb
|
|
# wxEVT_THUMBNAILS_DCLICK: Event Fired When You Double-Click A Thumb
|
|
# wxEVT_THUMBNAILS_CAPTION_CHANGED: Not Used At Present
|
|
# wxEVT_THUMBNAILS_THUMB_CHANGED: Used Internally
|
|
|
|
wxEVT_THUMBNAILS_SEL_CHANGED = wx.NewEventType()
|
|
wxEVT_THUMBNAILS_POINTED = wx.NewEventType()
|
|
wxEVT_THUMBNAILS_DCLICK = wx.NewEventType()
|
|
wxEVT_THUMBNAILS_CAPTION_CHANGED = wx.NewEventType()
|
|
wxEVT_THUMBNAILS_THUMB_CHANGED = wx.NewEventType()
|
|
|
|
#-----------------------------------#
|
|
# ThumbnailCtrlEvent
|
|
#-----------------------------------#
|
|
|
|
EVT_THUMBNAILS_SEL_CHANGED = wx.PyEventBinder(wxEVT_THUMBNAILS_SEL_CHANGED, 1)
|
|
""" The user has changed the selected thumbnail. """
|
|
EVT_THUMBNAILS_POINTED = wx.PyEventBinder(wxEVT_THUMBNAILS_POINTED, 1)
|
|
""" The mouse cursor is hovering over a thumbnail. """
|
|
EVT_THUMBNAILS_DCLICK = wx.PyEventBinder(wxEVT_THUMBNAILS_DCLICK, 1)
|
|
""" The user has double-clicked on a thumbnail. """
|
|
EVT_THUMBNAILS_CAPTION_CHANGED = wx.PyEventBinder(wxEVT_THUMBNAILS_CAPTION_CHANGED, 1)
|
|
""" The thumbnail caption has been changed. Not used at present. """
|
|
EVT_THUMBNAILS_THUMB_CHANGED = wx.PyEventBinder(wxEVT_THUMBNAILS_THUMB_CHANGED, 1)
|
|
""" The thumbnail of an image has changed. Used internally"""
|
|
|
|
TN_USE_PIL = 0
|
|
""" If set to ``True`` or to ``1``, :class:`ThumbnailCtrl` will use PIL to load the images (much faster). """
|
|
|
|
TIME_FMT = '%d %b %Y, %H:%M:%S'
|
|
""" Time format string for the :class:`Thumb` representation on screen. """
|
|
|
|
|
|
def KeyThumb(item):
|
|
"""
|
|
Return the key to be used for sorting???
|
|
"""
|
|
return item.GetFileName()
|
|
|
|
def SortFiles(items, sorteditems, filenames):
|
|
"""
|
|
Sort files in alphabetical order.
|
|
|
|
:param `sorteditems`: a list of :class:`Thumb` objects;
|
|
:param `filenames`: a list of image filenames.
|
|
"""
|
|
|
|
newfiles = []
|
|
for item in sorteditems:
|
|
newfiles.append(filenames[items.index(item)])
|
|
|
|
return newfiles
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
# Class PILImageHandler, handles loading and highlighting images with PIL
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
class PILImageHandler(object):
|
|
"""
|
|
This image handler loads and manipulates the thumbnails with the help
|
|
of PIL (the Python Imaging Library).
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:note: If PIL is not installed, this will raise an exception. PIL
|
|
can be downloaded from http://www.pythonware.com/products/pil/ .
|
|
"""
|
|
|
|
try:
|
|
|
|
import PIL.Image as Image
|
|
import PIL.ImageEnhance as ImageEnhance
|
|
|
|
except ImportError:
|
|
|
|
errstr = ("\nThumbnailCtrl *requires* PIL (Python Imaging Library).\n"
|
|
"You can get it at:\n\n"
|
|
"http://www.pythonware.com/products/pil/\n\n"
|
|
"ThumbnailCtrl can not continue. Exiting...\n")
|
|
|
|
raise Exception(errstr)
|
|
|
|
|
|
def LoadThumbnail(self, filename, thumbnailsize):
|
|
"""
|
|
Load the file and rescale it.
|
|
|
|
:param `filename`: a file containing an image;
|
|
:param `thumbnailsize`: the desired size of the thumbnail.
|
|
"""
|
|
|
|
import PIL.Image as Image
|
|
|
|
pil = Image.open(filename)
|
|
originalsize = pil.size
|
|
|
|
pil.thumbnail(thumbnailsize)
|
|
img = wx.Image(pil.size[0], pil.size[1])
|
|
|
|
img.SetData(pil.convert("RGB").tostring())
|
|
|
|
alpha = False
|
|
if "A" in pil.getbands():
|
|
img.SetAlpha(pil.convert("RGBA").tostring()[3::4])
|
|
alpha = True
|
|
|
|
return img, originalsize, alpha
|
|
|
|
|
|
def HighlightImage(self, img, factor):
|
|
"""
|
|
Adjust overall image brightness to highlight.
|
|
|
|
:param `img`: an instance of :class:`Image`;
|
|
:param `factor`: unused in :class:`PILImageHandler`.
|
|
"""
|
|
|
|
import PIL.Image as Image
|
|
import PIL.ImageEnhance as ImageEnhance
|
|
|
|
pil = Image.new('RGB', (img.GetWidth(), img.GetHeight()))
|
|
pil.fromstring(img.GetData())
|
|
enh = ImageEnhance.Brightness(pil)
|
|
enh = enh.enhance(1.5)
|
|
img.SetData(enh.convert('RGB').tostring())
|
|
return img
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
# Class NativeImageHandler, handles loading and highlighting images with wx
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
class NativeImageHandler(object):
|
|
"""
|
|
This image handler loads and manipulates the thumbnails with the help of
|
|
wxPython's own image related functions.
|
|
"""
|
|
|
|
def LoadThumbnail(self, filename, thumbnailsize):
|
|
"""
|
|
Load the file and rescale it.
|
|
|
|
:param `filename`: a file containing an image;
|
|
:param `thumbnailsize`: the desired size of the thumbnail.
|
|
"""
|
|
|
|
img = wx.Image(filename)
|
|
|
|
# Don't stop when a corrupt file is to be loaded, show Mondrian instead
|
|
try:
|
|
originalsize = (img.GetWidth(), img.GetHeight())
|
|
except:
|
|
img = file_broken.GetImage()
|
|
originalsize = (img.GetWidth(), img.GetHeight())
|
|
|
|
img.Rescale(min(thumbnailsize[0], originalsize[0]), min(thumbnailsize[1], originalsize[1]), wx.IMAGE_QUALITY_NORMAL)
|
|
alpha = img.HasAlpha()
|
|
|
|
return img, originalsize, alpha
|
|
|
|
|
|
def HighlightImage(self, img, factor):
|
|
"""
|
|
Adjust overall image brightness to highlight.
|
|
|
|
:param `img`: an instance of :class:`Image`;
|
|
:param `factor`: a floating point number representing the highlight factor.
|
|
"""
|
|
|
|
return img.AdjustChannels(factor, factor, factor, factor)
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
# 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):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `evtType`: the event type;
|
|
:param `evtId`: the event identifier.
|
|
"""
|
|
|
|
wx.PyCommandEvent.__init__(self, evtType, evtId)
|
|
self._eventType = evtType
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
# Class Thumb
|
|
# Auxiliary Class, To Handle Single Thumb Information For Every Thumb.
|
|
# Used Internally.
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
class Thumb(object):
|
|
"""
|
|
This is an auxiliary class, to handle single thumbnail information for every thumb.
|
|
|
|
Used internally.
|
|
"""
|
|
|
|
def __init__(self, parent, folder, filename, caption="", size=0, lastmod=0):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `parent`: the main :class:`ThumbnailCtrl` window;
|
|
:param `folder`: the directory containing the images;
|
|
:param `filename`: a file containing an image;
|
|
:param `caption`: the thumbnail caption string;
|
|
:param `size`: the file size;
|
|
:param `lastmod`: the file last modification time.
|
|
"""
|
|
|
|
self._filename = filename
|
|
self.SetCaption(caption)
|
|
self._id = 0
|
|
self._dir = folder
|
|
self._filesize = size
|
|
self._lastmod = lastmod
|
|
self._parent = parent
|
|
self._captionbreaks = []
|
|
self._bitmap = wx.Bitmap(1, 1)
|
|
self._image = wx.Image(1, 1)
|
|
self._rotation = 0
|
|
self._alpha = None
|
|
|
|
|
|
def SetCaption(self, caption=""):
|
|
"""
|
|
Sets the thumbnail caption.
|
|
|
|
:param `caption`: the thumbnail caption string.
|
|
"""
|
|
|
|
self._caption = caption
|
|
self._captionbreaks = []
|
|
|
|
|
|
def GetImage(self):
|
|
""" Returns the thumbnail image. """
|
|
|
|
return self._image
|
|
|
|
|
|
def SetImage(self, image):
|
|
"""
|
|
Sets the thumbnail image.
|
|
|
|
:param `image`: a :class:`Image` object.
|
|
"""
|
|
|
|
self._image = image
|
|
|
|
|
|
def SetBitmap(self, bmp):
|
|
"""
|
|
Sets the thumbnail bitmap.
|
|
|
|
:param `bmp`: a :class:`Bitmap` object.
|
|
"""
|
|
|
|
self._bitmap = bmp
|
|
|
|
|
|
def GetFileName(self):
|
|
""" Returns the file name associated with this thumbnail. """
|
|
|
|
return self._filename
|
|
|
|
|
|
def SetFileName(self, filename):
|
|
"""
|
|
Sets the file name associated with this thumbnail.
|
|
|
|
:param `filename`: the file containing the image.
|
|
"""
|
|
|
|
self._filename = filename
|
|
self._bitmap = wx.Bitmap(1, 1)
|
|
|
|
|
|
def GetId(self):
|
|
""" Returns the thumbnail identifier. """
|
|
|
|
return self._id
|
|
|
|
|
|
def SetId(self, id=-1):
|
|
"""
|
|
Sets the thumbnail identifier.
|
|
|
|
:param `id`: an integer specifying the thumbnail identifier.
|
|
"""
|
|
|
|
self._id = id
|
|
|
|
|
|
def SetRotatedImage(self, image):
|
|
"""
|
|
Sets the image as rotated (fast).
|
|
|
|
:param `image`: the rotated image, an instance of :class:`Image`.
|
|
"""
|
|
|
|
self._rotatedimage = image
|
|
|
|
|
|
def GetRotatedImage(self):
|
|
""" Returns a rotated image. """
|
|
|
|
return self._rotatedimage
|
|
|
|
|
|
def GetBitmap(self, width, height):
|
|
"""
|
|
Returns the associated bitmap.
|
|
|
|
:param `width`: the associated bitmap width;
|
|
:param `height`: the associated bitmap height.
|
|
"""
|
|
|
|
if self.GetRotation() % (2*pi) < 1e-6:
|
|
if not self._bitmap.IsOk():
|
|
if not hasattr(self, "_threadedimage"):
|
|
img = GetMondrianImage()
|
|
else:
|
|
img = self._threadedimage
|
|
|
|
else:
|
|
if not hasattr(self, "_threadedimage"):
|
|
img = GetMondrianImage()
|
|
else:
|
|
img = self._threadedimage
|
|
|
|
else:
|
|
|
|
img = self.GetRotatedImage()
|
|
|
|
if hasattr(self, "_originalsize"):
|
|
imgwidth, imgheight = self._originalsize
|
|
else:
|
|
imgwidth, imgheight = (img.GetWidth(), img.GetHeight())
|
|
|
|
if width < imgwidth or height < imgheight:
|
|
scale = float(width)/imgwidth
|
|
|
|
if scale > 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
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
# Class ThumbnailCtrl
|
|
# Auxiliary Class, All Useful Methods Are Defined On ScrolledThumbnail Class.
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
class ThumbnailCtrl(wx.Panel):
|
|
"""
|
|
:class:`ThumbnailCtrl` is a widget that can be used to display a series of images in
|
|
a "thumbnail" format.
|
|
"""
|
|
|
|
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
|
|
size=wx.DefaultSize, thumboutline=THUMB_OUTLINE_IMAGE,
|
|
thumbfilter=THUMB_FILTER_IMAGES, imagehandler=PILImageHandler):
|
|
"""
|
|
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:`ThumbnailCtrl`, 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.
|
|
"""
|
|
|
|
wx.Panel.__init__(self, parent, id, pos, size)
|
|
|
|
self._sizer = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
self._combo = wx.ComboBox(self, -1, style=wx.CB_DROPDOWN | wx.CB_READONLY)
|
|
self._scrolled = ScrolledThumbnail(self, -1, thumboutline=thumboutline,
|
|
thumbfilter=thumbfilter, imagehandler = imagehandler)
|
|
|
|
subsizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
subsizer.Add((3, 0), 0)
|
|
subsizer.Add(self._combo, 0, wx.EXPAND | wx.TOP, 3)
|
|
subsizer.Add((3, 0), 0)
|
|
self._sizer.Add(subsizer, 0, wx.EXPAND | wx.ALL, 3)
|
|
self._sizer.Add(self._scrolled, 1, wx.EXPAND)
|
|
|
|
self.SetSizer(self._sizer)
|
|
|
|
self._sizer.Show(0, False)
|
|
self._sizer.Layout()
|
|
|
|
methods = ["GetSelectedItem", "GetPointed", "GetHighlightPointed", "SetHighlightPointed",
|
|
"SetThumbOutline", "GetThumbOutline", "GetPointedItem", "GetItem",
|
|
"GetItemCount", "GetThumbWidth", "GetThumbHeight", "GetThumbBorder",
|
|
"ShowFileNames", "SetPopupMenu", "GetPopupMenu", "SetGlobalPopupMenu",
|
|
"GetGlobalPopupMenu", "SetSelectionColour", "GetSelectionColour",
|
|
"EnableDragging", "SetThumbSize", "GetThumbSize", "ShowThumbs", "ShowDir",
|
|
"GetShowDir", "SetSelection", "GetSelection", "SetZoomFactor",
|
|
"GetZoomFactor", "SetCaptionFont", "GetCaptionFont", "GetItemIndex",
|
|
"InsertItem", "RemoveItemAt", "IsSelected", "Rotate", "ZoomIn", "ZoomOut",
|
|
"EnableToolTips", "GetThumbInfo", "GetOriginalImage", "SetDropShadow", "GetDropShadow"]
|
|
|
|
for method in methods:
|
|
setattr(self, method, getattr(self._scrolled, method))
|
|
|
|
self._combochoices = []
|
|
self._showcombo = False
|
|
self._subsizer = subsizer
|
|
|
|
self._combo.Bind(wx.EVT_COMBOBOX, self.OnComboBox)
|
|
|
|
|
|
def ShowComboBox(self, show=True):
|
|
"""
|
|
Shows/Hide the top folder :class:`ComboBox`.
|
|
|
|
:param `show`: ``True`` to show the combobox, ``False`` otherwise.
|
|
"""
|
|
|
|
if show:
|
|
self._showcombo = True
|
|
self._sizer.Show(0, True)
|
|
self._sizer.Layout()
|
|
else:
|
|
self._showcombo = False
|
|
self._sizer.Show(0, False)
|
|
self._sizer.Layout()
|
|
|
|
self._scrolled.Refresh()
|
|
|
|
|
|
def GetShowComboBox(self):
|
|
""" Returns whether the folder combobox is shown. """
|
|
|
|
return self._showcombo
|
|
|
|
|
|
def OnComboBox(self, event):
|
|
"""
|
|
Handles the ``wx.EVT_COMBOBOX`` for the folder combobox.
|
|
|
|
:param `event`: a :class:`CommandEvent` event to be processed.
|
|
"""
|
|
|
|
dirs = self._combo.GetValue()
|
|
|
|
if os.path.isdir(opj(dirs)):
|
|
self._scrolled.ShowDir(opj(dirs))
|
|
|
|
event.Skip()
|
|
|
|
|
|
def RecreateComboBox(self, newdir):
|
|
"""
|
|
Recreates the folder combobox every time a new directory is explored.
|
|
|
|
:param `newdir`: the new folder to be explored.
|
|
"""
|
|
|
|
newdir = newdir.strip()
|
|
|
|
if opj(newdir) in self._combochoices:
|
|
return
|
|
|
|
self.Freeze()
|
|
|
|
self._sizer.Detach(0)
|
|
self._subsizer.Detach(1)
|
|
self._subsizer.Destroy()
|
|
self._combo.Destroy()
|
|
|
|
subsizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
|
|
self._combochoices.insert(0, opj(newdir))
|
|
|
|
self._combo = wx.ComboBox(self, -1, value=newdir, choices=self._combochoices,
|
|
style=wx.CB_DROPDOWN | wx.CB_READONLY)
|
|
|
|
subsizer.Add((3, 0), 0)
|
|
subsizer.Add(self._combo, 1, wx.EXPAND | wx.TOP, 3)
|
|
subsizer.Add((3, 0), 0)
|
|
self._sizer.Insert(0, subsizer, 0, wx.EXPAND | wx.ALL, 3)
|
|
|
|
self._subsizer = subsizer
|
|
|
|
self._subsizer.Layout()
|
|
|
|
if not self.GetShowComboBox():
|
|
self._sizer.Show(0, False)
|
|
|
|
self._sizer.Layout()
|
|
|
|
self._combo.Bind(wx.EVT_COMBOBOX, self.OnComboBox)
|
|
|
|
self.Thaw()
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
# 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):
|
|
"""
|
|
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 `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.
|
|
"""
|
|
|
|
wx.ScrolledWindow.__init__(self, parent, id, pos, size)
|
|
|
|
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._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 = []
|
|
|
|
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 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:`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:`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:`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 ListDirectory(self, directory, fileExtList):
|
|
"""
|
|
Returns list of file info objects for files of particular extensions.
|
|
|
|
:param `directory`: the folder containing the images to thumbnail;
|
|
:param `fileExtList`: a Python list of file extensions to consider.
|
|
"""
|
|
|
|
fileList = [os.path.normcase(f) for f in os.listdir(directory)]
|
|
fileList = [f for f in fileList \
|
|
if os.path.splitext(f)[1] in fileExtList]
|
|
|
|
return fileList
|
|
|
|
|
|
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
|
|
|
|
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:`Font` object. If defaulted to ``None``, a standard
|
|
font will be generated.
|
|
"""
|
|
|
|
if font is None:
|
|
font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.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:`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.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.SOLID))
|
|
dc.SetBrush(wx.Brush(wx.BLACK, wx.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.TRANSPARENT))
|
|
dc.SetBrush(wx.Brush(self.GetBackgroundColour(), wx.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:`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, event.GetPosition())
|
|
elif self._selected >= 0 and not self._pmenu and self._gpmenu:
|
|
self.PopupMenu(self._gpmenu, event.GetPosition())
|
|
elif self._selected == -1 and self._gpmenu:
|
|
self.PopupMenu(self._gpmenu, event.GetPosition())
|
|
|
|
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())
|
|
pil = Image.open(fil).rotate(newangle)
|
|
img = wx.Image(pil.size[0], pil.size[1])
|
|
img.SetData(pil.convert('RGB').tostring())
|
|
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):
|
|
"""
|
|
Deletes the selected thumbnails and their associated files.
|
|
|
|
.. warning:: This method deletes the original files too.
|
|
"""
|
|
|
|
dlg = wx.MessageDialog(self, 'Are you sure you want to delete the files?',
|
|
'Confirmation',
|
|
wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
|
|
|
|
if dlg.ShowModal() == wx.ID_YES:
|
|
errordelete = []
|
|
count = 0
|
|
|
|
dlg.Destroy()
|
|
|
|
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)
|
|
|
|
wx.EndBusyCursor()
|
|
|
|
if errordelete:
|
|
strs = "Unable to remove the following files:\n\n"
|
|
for fil in errordelete:
|
|
strs = strs + fil + "\n"
|
|
strs = strs + "\n"
|
|
strs = strs + "Please check your privileges and file permissions."
|
|
dlg = wx.MessageDialog(self, strs,
|
|
'Error in removing files',
|
|
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()
|
|
|
|
|
|
|