#!/usr/bin/python
u"""
test cover flow, TODO
"""

# The MIT License (MIT)
#
# Copyright (c) 2014 Andrea Griffini
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import sys, gc, os, time, math, re, subprocess
from PyQt4.Qt import *

try:
    unicode
except:
    unicode = str

QW = QWidget

def between(x, a, b, eps=0):
    return min(a, b)-eps <= x <= max(a, b)+eps

def near(x, a, eps):
    return a-eps <= x <= a+eps

class Viewer(QW):
    def __init__(self, parent, imglist):
        QW.__init__(self, parent)

        bg = QImage(32, 32, QImage.Format_ARGB32)
        dc = QPainter(bg)
        dc.fillRect(0, 0, 32, 32, QColor(96, 96, 96))
        dc.fillRect(0, 0, 16, 16, QColor(128, 128, 128))
        dc.fillRect(16, 16, 16, 16, QColor(128, 128, 128))
        dc = None
        self.bbrush = QBrush(bg)

        self.setFocusPolicy(Qt.StrongFocus)
        self.setFocus()
        self.imglist = imglist[:]
        self.setMouseTracking(True)
        self.fullscreen = False
        self.titleOverlay = False
        self.timer = QTimer(self)
        self.connect(self.timer, SIGNAL("timeout()"), self.checkmtime)
        self.timer.start(250)
        self.pos = None
        self.tracking = None
        self.tools = []
        self.load(0)

    def load(self, index):
        self.imglist[:] = self.imglist[index:] + self.imglist[:index]
        self.img = QImage(self.imglist[0])
        self.scaled = None
        self.imgmtime = os.stat(self.imglist[0]).st_mtime
        self.parent().setWindowTitle(self.mkTitle())
        self.update()
        gc.collect()

    def checkmtime(self):
        imgmtime = os.stat(self.imglist[0]).st_mtime
        if imgmtime != self.imgmtime:
            ow = self.img.width()
            oh = self.img.height()
            self.load(0)
            if self.img.width() != ow or self.img.height() != oh:
                self.zoomAll()

    def resizeEvent(self, e):
        self.zoomAll()
        QW.resizeEvent(self, e)

    def zoomAll(self):
        self.scale = min(float(self.width()) / max(1, self.img.width()),
                         float(self.height()) / max(1, self.img.height()))
        self.tx = (self.width() - self.img.width()*self.scale) / 2
        self.ty = (self.height() - self.img.height()*self.scale) / 2
        self.update()

    def keyPressEvent(self, e):
        for t in self.tools:
            if t.key(e):
                self.update()
                return
        if e.key() == Qt.Key_Right:
            w, h = self.img.width(), self.img.height()
            self.load(1)
            if (w, h) != (self.img.width(), self.img.height()):
                self.zoomAll()
        elif e.key() == Qt.Key_Left:
            w, h = self.img.width(), self.img.height()
            self.load(len(self.imglist) - 1)
            if (w, h) != (self.img.width(), self.img.height()):
                self.zoomAll()
        elif e.key() == Qt.Key_1:
            self.setZoom(1)
        elif e.key() == Qt.Key_2:
            self.setZoom(2)
        elif e.key() == Qt.Key_3:
            self.setZoom(3)
        elif e.key() == Qt.Key_4:
            self.setZoom(4)
        elif e.key() in (Qt.Key_Home, Qt.Key_0):
            self.zoomAll()
        elif e.key() in (Qt.Key_Escape, Qt.Key_Q):
            self.parent().deleteLater()
        elif e.key() == Qt.Key_U:
            w, h = self.img.width(), self.img.height()
            self.load(0)
            if (w, h) != (self.img.width(), self.img.height()):
                self.zoomAll()
        elif e.key() in (Qt.Key_F, Qt.Key_Return, Qt.Key_Enter):
            self.fullscreen = not self.fullscreen
            if self.fullscreen:
                self.parent().showFullScreen()
            else:
                self.parent().showNormal()
        elif e.key() == Qt.Key_R:
            img = QImage(self.img.height(), self.img.width(), QImage.Format_ARGB32)
            img.fill(0)
            dc = QPainter(img)
            dc.rotate(90)
            dc.drawImage(0, -self.img.height(), self.img)
            dc = None
            self.img = img
            self.zoomAll()
            gc.collect()
        elif e.key() == Qt.Key_C:
            self.tools = [CropTool(self)]
        elif e.key() == Qt.Key_E:
            self.tools = [EllipseTool(self)]
        elif e.key() == Qt.Key_P:
            self.tools = [PixColorTool(self)]
        elif e.key() == Qt.Key_A:
            self.tools = [ArrowTool(self)]
        elif e.key() == Qt.Key_M:
            self.tools = [MeasuringTool(self)]
        elif e.key() == Qt.Key_Y:
            self.to32bpp()
            b = self.img.bits()
            b.setsize(self.img.height() * self.img.bytesPerLine())
            b.setwriteable(True)
            for i in range(0, self.img.height()*self.img.bytesPerLine(), 4):
                b[i] = b[i+2] = b[i+1]
            self.scaled = None
        elif e.key() == Qt.Key_T:
            self.tools = [TextTool(self)]
        elif e.key() == Qt.Key_S:
            fname = str(QFileDialog.getSaveFileName(self, "Save image as", self.imglist[0]))
            if fname != "":
                supported_extensions = [bytes(x).decode() for x in QImageWriter.supportedImageFormats()]
                if re.match("^.*\\.(" + "|".join(supported_extensions) + ")$", fname, re.IGNORECASE):
                    if self.img.save(fname):
                        if fname not in self.imglist:
                            self.imglist.insert(0, fname)
                            self.load(0)
                    else:
                        QMessageBox.critical(self, "IView: Error saving image file",
                                             "There was a problem saving the image file.")
                elif fname[-4:].lower() == ".pgm":
                    # Hand-made PGM writer
                    try:
                        self.to32bpp()
                        data = self.img.bits().asstring(self.img.height() * self.img.bytesPerLine())
                        f = open(fname, "wb")
                        f.write(("P5\n%i %i 255\n" % (self.img.width(), self.img.height()))
                                .encode("ascii"))
                        for y in range(self.img.height()):
                            f.write(data[y*self.img.width()*4+1:(y+1)*self.img.width()*4+1:4])
                        f.close()
                        if fname not in self.imglist:
                            self.imglist.insert(0, fname)
                            self.load(0)
                    except Exception:
                        QMessageBox.critical(self, "IView: Error saving image file",
                                             "There was a problem saving the image file.")
                else:
                    QMessageBox.critical(self, "IView: Unable to save image file",
                                         "The requested image format is not supported, supported extensions are "+
                                         ", ".join(supported_extensions))
        elif e.key() == Qt.Key_X:
            subprocess.Popen([os.getenv("EDITOR"), __file__])
        elif e.key() == Qt.Key_Delete:
            if QMessageBox.question(self,
                                    "Image deletion",
                                    "Are you sure you want to delete this image?",
                                    QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes:
                try:
                    os.unlink(self.imglist[0])
                except OSError:
                    QMessageBox.information(self, "IView",
                                            "Error deleting image file")
                    return
                self.scaled = None
                self.imglist.pop(0)
                if len(self.imglist) == 0:
                    sys.exit(0)
                self.load(0)
                self.zoomAll()
        elif e.key() == Qt.Key_F2:
            name = self.imglist[0]
            d = QDialog(self)
            d.setWindowTitle("Rename image")
            vb = QVBoxLayout(d)
            l = QLabel("New name"); vb.addWidget(l)
            sle = QLineEdit(d); vb.addWidget(sle)
            hb = QHBoxLayout(); vb.addLayout(hb);
            hb.addWidget(QWidget(d))
            ok = QPushButton("OK", d); hb.addWidget(ok)
            ok.setFixedWidth(80)
            cancel = QPushButton("Cancel", d); hb.addWidget(cancel)
            cancel.setFixedWidth(80)
            hb.addWidget(QWidget(d))
            sle.setText(name)
            basename_start = 1+name.rindex("/") if "/" in name else 0
            if "." in name[basename_start:]:
                sle.setSelection(basename_start, name[basename_start:].rindex("."))
            sle.setFocus()
            width = sle.fontMetrics().boundingRect(name).width()
            sle.setMinimumWidth(width*9//8)
            d.connect(cancel, SIGNAL("clicked()"), d.reject)
            d.connect(ok, SIGNAL("clicked()"), d.accept)
            if d.exec_() == QDialog.Accepted:
                try:
                    s = sle.text()
                    os.rename(self.imglist[0], unicode(s))
                    self.imglist[0] = unicode(s)
                    self.load(0)
                except Exception:
                    QMessageBox.critical(self, "IView: Unable to rename the file",
                                         "There was a problem renaming the file")
        elif e.key() == Qt.Key_O:
            self.titleOverlay = not self.titleOverlay
        elif e.key() == Qt.Key_F1:
            QMessageBox.information(self, "IView",
                                    "<center><h1>IView</h1>A simple image viewer<br>"
                                    "by Andrea \"6502\" Griffini</center>"
                                    "<ul>"
                                    "  <li> <b>F1</b> This help message</li>"
                                    "  <li> <b>Mouse drag</b> Pan</li>"
                                    "  <li> <b>Mouse wheel</b> Zoom</li>"
                                    "  <li> <b>Left/Right</b> Previous/next image</li>"
                                    "  <li> <b>Home</b> Zoom fit</li>"
                                    "  <li> <b>1/2/3/4</b> Display resolution to Nx</li>"
                                    "  <li> <b>F or Enter</b> Fullscreen mode</li>"
                                    "  <li> <b>O</b> Toggle file name overlay in fullscreen mode</li>"
                                    "  <li> <b>A</b> Arrow markup mode</li>"
                                    "  <li> <b>P</b> Pixel inspection mode</li>"
                                    "  <li> <b>M</b> Measure mode</li>"
                                    "  <li> <b>E</b> Ellipse markup mode</li>"
                                    "  <li> <b>T</b> Text markup mode</li>"
                                    "  <li> <b>C</b> Cropping/yanking mode</li>"
                                    "  <li> <b>Y</b> Convert to grayscale</li>"
                                    "  <li> <b>R</b> Rotate image 90 degrees</li>"
                                    "  <li> <b>U</b> Reload original image</li>"
                                    "  <li> <b>S</b> Save modified image</li>"
                                    "  <li> <b>F2</b> Rename current file</li>"
                                    "  <li> <b>Del</b> Delete current file</li>"
                                    "  <li> <b>X</b> Customize</li>"
                                    "</ul>")
        self.update()

    def sizeHint(self):
        return QSize(900, 700)

    def setZoom(self, sf):
        if self.pos:
            mx, my = self.pos[0] * self.scale + self.tx, self.pos[1] * self.scale + self.ty
            self.scale = sf
            self.tx = mx - self.pos[0] * self.scale
            self.ty = my - self.pos[1] * self.scale
        else:
            mx, my = self.width()/2, self.height()/2
            lx, ly = (mx - self.tx)/self.scale, (my - self.ty)/self.scale
            self.scale = sf
            self.tx = mx - lx * self.scale
            self.ty = my - ly * self.scale
        self.update()

    def wheelEvent(self, e):
        self.pos = (e.x() - self.tx) / self.scale, (e.y() - self.ty) / self.scale
        sf = self.scale
        d = int(e.delta() / 120)
        while d > 0:
            sf *= 1.2
            d -= 1
        while d < 0:
            sf /= 1.2
            d += 1
        if sf < 0.01:
            sf = 0.01
        if sf > 10000:
            sf = 10000
        self.setZoom(sf)

    def paintEvent(self, e):
        dc = QPainter(self)
        w, h = self.width(), self.height()
        dc.fillRect(0, 0, w, h, QBrush(QColor(64, 64, 64)))

        dc.fillRect(self.tx+1, self.ty+1, self.img.width()*self.scale-2, self.img.height()*self.scale-2, self.bbrush)
        if self.scale > 1:
            dc.save()
            dc.translate(self.tx, self.ty)
            dc.scale(self.scale, self.scale)
            dc.drawImage(0, 0, self.img)
            dc.restore()
        else:
            if self.scaled is None or self.scaled[0] != self.scale:
                sw = int(self.img.width() * self.scale)
                sh = int(self.img.height() * self.scale)
                simg = self.img.scaled(QSize(sw, sh), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
                self.scaled = [self.scale, simg]
            dc.drawImage(self.tx, self.ty, self.scaled[1])

        y = 16
        dc.setFont(QFont("sans-serif", 11))

        if len(self.tools)==0 and self.fullscreen and self.titleOverlay:
            self.statusText(dc, self.mkTitle())

        for t in self.tools:
            t.draw(dc)

    def mkTitle(self):
        if self.img.isNull():
            name = self.imglist[0] + " (unable to load as image)"
        else:
            name = self.imglist[0] + " (%i x %i x %ibpp = %0.2f MB)" % (
                self.img.width(),
                self.img.height(),
                self.img.depth(),
                self.img.height() * self.img.bytesPerLine() / (1024.0*1024.0))
        if "/" in name:
            name = name[name.rindex("/")+1:]
        return name

    def mousePressEvent(self, e):
        self.pos = ((e.x() - self.tx) / self.scale,
                    (e.y() - self.ty) / self.scale)
        for t in self.tools:
            self.tracking = t.hit(e.x(), e.y(), e.button())
            if self.tracking:
                break
        else:
            cur = [e.x(), e.y()]
            def pan(mx, my):
                if mx is not None:
                    self.tx += mx - cur[0]
                    self.ty += my - cur[1]
                    cur[0] = mx
                    cur[1] = my
            self.tracking = pan

    def mouseMoveEvent(self, e):
        self.pos = ((e.x() - self.tx) / self.scale,
                    (e.y() - self.ty) / self.scale)
        if self.tracking:
            self.tracking(e.x(), e.y())
            self.update()

    def mouseReleaseEvent(self, e):
        if self.tracking:
            self.tracking(None, None)
            self.tracking = None

    def map(self, x, y):
        return self.tx + x*self.scale, self.ty + y*self.scale

    def revmap(self, x, y):
        return (x - self.tx)/self.scale, (y - self.ty)/self.scale

    def to32bpp(self):
        if self.img.format() != QImage.Format_ARGB32:
            self.img = self.img.convertToFormat(QImage.Format_ARGB32)
            self.scaled = None
            self.update()

    def statusText(self, dc, msg):
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.drawText(4, 20, msg )
        dc.drawText(7, 20, msg )
        dc.drawText(5, 19, msg )
        dc.drawText(5, 21, msg )
        dc.setPen(QPen(QColor(0, 255, 0)))
        dc.drawText(5, 20, msg )

class Tool(object):
    pen = 4
    color = 0
    colors = [QColor(255, 0, 0),
              QColor(0, 255, 0),
              QColor(0, 0, 255)]

    def __init__(self, iview):
        self.iview = iview
        self.font = QFont("sans-serif")
        self.font.setPixelSize(self.pen * 8 + 11)

    def key(self, e):
        if e.key() == Qt.Key_R:
            Tool.color = 0
            self.iview.update()
            return True
        if e.key() == Qt.Key_G:
            Tool.color = 1
            self.iview.update()
            return True
        if e.key() == Qt.Key_B:
            Tool.color = 2
            self.iview.update()
            return True
        if e.key() >= Qt.Key_1 and e.key() <= Qt.Key_9:
            Tool.pen = (e.key() - Qt.Key_1)*2
            self.font.setPixelSize(self.pen * 8 + 11)
            self.iview.update()
            return True
        if e.key() == Qt.Key_Escape:
            self.iview.tools.remove(self)
            return True

    def text(self, dc, msg):
        return self.iview.statusText(dc, msg)

class RectTool(Tool):
    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.geo = [0, 0, 0, 0]

    def rect(self):
        x0, y0, x1, y1 = self.geo
        x0, y0 = map(int, self.iview.map(self.geo[0], self.geo[1]))
        x1, y1 = map(int, self.iview.map(self.geo[2], self.geo[3]))
        if x0 > x1: x0, x1 = x1, x0
        if y0 > y1: y0, y1 = y1, y0
        return x0, y0, x1, y1

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dc.setBrush(QBrush())
        dc.setPen(QPen(QColor(0, 0, 0), 4))
        dc.drawRect(QRectF(x0, y0, x1-x0, y1-y0))
        dc.setPen(QPen(QColor(255, 255, 255), 2, Qt.DashLine))
        dc.drawRect(QRectF(x0, y0, x1-x0, y1-y0))
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.setBrush(QColor(255, 0, 0))
        for x, y in ((x0, y0), (x1, y0), (x0, y1), (x1, y1)):
            dc.drawEllipse(QRectF(x-4, y-4, 8, 8))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0, x1, y1 = self.geo
            x0, y0 = map(int, self.iview.map(self.geo[0], self.geo[1]))
            x1, y1 = map(int, self.iview.map(self.geo[2], self.geo[3]))
            f = 0
            if near(mx, x0, 8) and between(my, y0, y1, 8): f |= 1
            if near(my, y0, 8) and between(mx, x0, x1, 8): f |= 2
            if near(mx, x1, 8) and between(my, y0, y1, 8): f |= 4
            if near(my, y1, 8) and between(mx, x0, x1, 8): f |= 8
            if f == 0 and between(mx, x0, x1) and between(my, y0, y1):
                f = 15
            else:
                if (f & 5) == 5: f ^= 4
                if (f & 10) == 10: f ^= 8
            if f == 0:
                xx, yy = map(int, self.iview.revmap(mx, my))
                self.geo = [xx, yy, xx, yy]
                f = 4+8

            if f:
                cur = list(map(int, self.iview.revmap(mx, my)))
                def tracking(x, y):
                    if x is not None:
                        x, y = map(int, self.iview.revmap(x, y))
                        dx, dy = x - cur[0], y - cur[1]
                        cur[0], cur[1] = x, y
                        if f & 1: self.geo[0] += dx
                        if f & 2: self.geo[1] += dy
                        if f & 4: self.geo[2] += dx
                        if f & 8: self.geo[3] += dy
                        self.geo[0] = max(0, min(self.geo[0], self.iview.img.width()))
                        self.geo[1] = max(0, min(self.geo[1], self.iview.img.height()))
                        self.geo[2] = max(0, min(self.geo[2], self.iview.img.width()))
                        self.geo[3] = max(0, min(self.geo[3], self.iview.img.height()))
                return tracking

class CropTool(RectTool):
    def __init__(self, iview):
        RectTool.__init__(self, iview)

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dark = QBrush(QColor(0, 0, 0, 128))
        w, h = self.iview.width(), self.iview.height()
        if y0 > 0: dc.fillRect(0, 0, w, y0, dark)
        if x0 > 0: dc.fillRect(0, y0, x0, y1-y0, dark)
        if x1 < w: dc.fillRect(x1, y0, w-x1, y1-y0, dark)
        if y1 < h: dc.fillRect(0, y1, w, h-y1, dark)
        RectTool.draw(self, dc)
        self.text(dc, "Crop: (%i, %i) - (%i, %i) = %i x %i (Y to copy)" %
                      (self.geo[0], self.geo[1],
                       self.geo[2], self.geo[3],
                       abs(self.geo[2]-self.geo[0]),
                       abs(self.geo[3]-self.geo[1])))

    def key(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_C, Qt.Key_Enter, Qt.Key_Y):
            x0, y0, x1, y1 = self.geo
            if x0 > x1: x0, x1 = x1, x0
            if y0 > y1: y0, y1 = y1, y0
            x0, y0 = max(0, x0), max(0, y0)
            x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.height(), y1)
            if x0 < x1 and y0 < y1:
                cropped = self.iview.img.copy(x0, y0, x1-x0, y1-y0)
                if e.key() == Qt.Key_Y:
                    QApplication.clipboard().setImage(cropped)
                else:
                    self.iview.img = cropped
                    self.iview.scaled = None
                    self.iview.zoomAll()
            self.iview.tools.remove(self)
            return True
        else:
            return Tool.key(self, e)

class EllipseTool(RectTool):
    def __init__(self, iview):
        RectTool.__init__(self, iview)

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dc.setPen(QPen(self.colors[self.color], self.pen*self.iview.scale))
        dc.setBrush(QBrush())
        dc.drawEllipse(x0, y0, x1-x0, y1-y0)
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.setBrush(QColor(255, 0, 0))
        for x, y in (((x0+x1)/2, y0), ((x0+x1)/2, y1),
                     (x0, (y0+y1)/2), (x1, (y0+y1)/2),
                     (x0, y0), (x1, y0), (x0, y1), (x1, y1)):
            dc.drawEllipse(QRectF(x-4, y-4, 8, 8))
        self.text(dc, "Ellipse drawing mode: 1-9=thickness, r/g/b=color")

    def key(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_E, Qt.Key_Enter):
            x0, y0, x1, y1 = self.geo
            if x0 > x1: x0, x1 = x1, x0
            if y0 > y1: y0, y1 = y1, y0
            x0, y0 = max(0, x0), max(0, y0)
            x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.height(), y1)
            if x0 < x1 and y0 < y1:
                self.iview.to32bpp()
                dc = QPainter(self.iview.img)
                dc.setPen(QPen(self.colors[self.color], self.pen))
                dc.setBrush(QBrush())
                dc.drawEllipse(x0, y0, x1-x0, y1-y0)
                dc = None
                self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        else:
            return Tool.key(self, e)

class PixColorTool(Tool):
    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.p = None

    def draw(self, dc):
        if self.p is None:
            self.text(dc, "Pixel inspection; click for color/position")
        else:
            x, y = self.p
            col = self.iview.img.pixel(x, y)
            a = (col >> 24) & 255
            r = (col >> 16) & 255
            g = (col >> 8) & 255
            b = (col >> 0) & 255
            c0 = QBrush(QColor(0, 0, 0))
            c1 = QBrush(QColor(255, 255, 255))

            x0, y0 = self.iview.map(x, y)
            x1, y1 = self.iview.map(x+1, y+1)
            h = (y1 - y0) * 10
            s = (y1 - y0) * 2
            dc.fillRect(x0-1, y0-h-s-1, x1-x0+2, h+2, c0)
            dc.fillRect(x0-1, y1+s-1, x1-x0+2, h+2, c0)
            dc.fillRect(x0-h-s-1, y0-1, h+2, y1-y0+2, c0)
            dc.fillRect(x1+s-1, y0-1, h+2, y1-y0+2, c0)
            dc.fillRect(x0, y0-h-s, x1-x0, h, c1)
            dc.fillRect(x0, y1+s, x1-x0, h, c1)
            dc.fillRect(x0-h-s, y0, h, y1-y0, c1)
            dc.fillRect(x1+s, y0, h, y1-y0, c1)
            self.text(dc, "Pixel(x=%i, y=%i): rgb(%i, %i, %i), #%02x%02x%02x, alpha=%i" % (x, y, r, g, b, r, g, b, a))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x, y = map(int, self.iview.revmap(mx, my))
            if 0 <= x < self.iview.img.width() and 0 <= y < self.iview.img.height():
                self.p = (x, y)
                self.iview.update()

class ArrowTool(Tool):
    k = 8

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.pts = [0, 0, 0, 0]

    def draw(self, dc):
        x0, y0 = map(int, self.iview.map(*self.pts[:2]))
        x1, y1 = map(int, self.iview.map(*self.pts[2:]))
        dx, dy = (x1 - x0)//self.k, (y1 - y0)//self.k
        nx, ny = (y1 - y0)//self.k//2, (x0 - x1)//self.k//2
        dc.setPen(QPen(self.colors[self.color], self.pen*self.iview.scale,
                       Qt.SolidLine, Qt.RoundCap))
        dc.setBrush(QBrush())
        dc.drawLine(x0, y0, x1, y1)
        dc.drawLine(x1-dx-nx, y1-dy-ny, x1, y1)
        dc.drawLine(x1-dx+nx, y1-dy+ny, x1, y1)
        self.text(dc, "Arrow drawing mode, 1-9=thickness, r/g/b=color, +/- changes head")

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0 = map(int, self.iview.map(*self.pts[:2]))
            x1, y1 = map(int, self.iview.map(*self.pts[2:]))
            i = -1
            if (mx - x0)**2 + (my - y0)**2 < 8**2:
                i = 0
            elif (mx - x1)**2 + (my - y1)**2 < 8**2:
                i = 2
            else:
                xx, yy = self.iview.revmap(mx, my)
                self.pts = [xx, yy, xx, yy]
                i = 2
            def tracking(x, y):
                if x is not None:
                    self.pts[i:i+2] = list(map(int, self.iview.revmap(x, y)))
            return tracking

    def key(self, e):
        if e.key() == Qt.Key_Minus:
            if ArrowTool.k < 100: ArrowTool.k += 1
        if e.key() == Qt.Key_Plus:
            if ArrowTool.k > 4: ArrowTool.k -= 1
        if e.key() in (Qt.Key_Return, Qt.Key_A, Qt.Key_Enter):
            self.iview.to32bpp()
            x0, y0, x1, y1 = map(int, self.pts)
            dx, dy = (x1 - x0)//self.k, (y1 - y0)//self.k
            nx, ny = (y1 - y0)//self.k//2, (x0 - x1)//self.k//2
            dc = QPainter(self.iview.img)
            dc.setPen(QPen(self.colors[self.color], self.pen,
                           Qt.SolidLine, Qt.RoundCap))
            dc.setBrush(QBrush())
            dc.drawLine(x0, y0, x1, y1)
            dc.drawLine(x1-dx-nx, y1-dy-ny, x1, y1)
            dc.drawLine(x1-dx+nx, y1-dy+ny, x1, y1)
            dc = None
            self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        return Tool.key(self, e)

class MeasuringTool(Tool):
    k = 8

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.pts = [0, 0, 0, 0]

    def draw(self, dc):
        x0, y0 = map(int, self.iview.map(*self.pts[:2]))
        x1, y1 = map(int, self.iview.map(*self.pts[2:]))
        dx, dy = (x1 - x0)//self.k, (y1 - y0)//self.k
        nx, ny = (y1 - y0)//self.k//2, (x0 - x1)//self.k//2
        dc.setPen(QPen(self.colors[self.color]))
        dc.setBrush(QBrush())
        dc.drawLine(x0, y0, x1, y1)
        dc.drawLine(x1-dx-nx, y1-dy-ny, x1, y1)
        dc.drawLine(x1-dx+nx, y1-dy+ny, x1, y1)
        dc.drawLine(x0+dx-nx, y0+dy-ny, x0, y0)
        dc.drawLine(x0+dx+nx, y0+dy+ny, x0, y0)
        dc.drawLine(x0+nx, y0+ny, x0-nx, y0-ny)
        dc.drawLine(x1+nx, y1+ny, x1-nx, y1-ny)
        x0, y0, x1, y1 = self.pts
        dx, dy = x1 - x0, y1 - y0
        L = (dx**2 + dy**2)**0.5
        angle = 0 if L == 0 else math.atan2(dy, dx)*180/math.pi
        self.text(dc, "Measuring mode: (%i,%i)-(%i,%i) : %ix%i = %0.3fpx, %0.3f degrees" % (
                x0, y0, x1, y1, dx, dy, L, angle))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0 = map(int, self.iview.map(*self.pts[:2]))
            x1, y1 = map(int, self.iview.map(*self.pts[2:]))
            i = -1
            if (mx - x0)**2 + (my - y0)**2 < 8**2:
                i = 0
            elif (mx - x1)**2 + (my - y1)**2 < 8**2:
                i = 2
            else:
                xx, yy = map(int, self.iview.revmap(mx, my))
                self.pts = [xx, yy, xx, yy]
                i = 2
            def tracking(x, y):
                if x is not None:
                    self.pts[i:i+2] = list(map(int, self.iview.revmap(x, y)))
                    if app.keyboardModifiers() == Qt.ShiftModifier:
                        if (abs(self.pts[i] - self.pts[0+2-i]) <
                            abs(self.pts[i+1] - self.pts[1+3-(i+1)])):
                            self.pts[i] = self.pts[0+2-i]
                        else:
                            self.pts[i+1] = self.pts[1+3-(i+1)]
            return tracking

    def key(self, e):
        if e.key() == Qt.Key_Minus:
            if MeasuringTool.k < 100: MeasuringTool.k += 1
        if e.key() == Qt.Key_Plus:
            if MeasuringTool.k > 4: MeasuringTool.k -= 1
        return Tool.key(self, e)

class TextTool(Tool):
    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.text = ""
        x0, y0 = map(int, self.iview.revmap(0, 0))
        x1, y1 = map(int, self.iview.revmap(self.iview.width(), self.iview.height()))
        self.pos = [(x0 + x1)//2, (y0 + y1)//2]
        self.blink = int(time.time() * 2) + 1
        QTimer.singleShot(250, lambda : self.iview.update())

    def draw(self, dc):
        x, y = self.pos
        dc.save()
        dc.translate(self.iview.tx, self.iview.ty)
        dc.scale(self.iview.scale, self.iview.scale)
        dc.setFont(self.font)
        dc.setPen(QPen(self.colors[self.color]))
        caret = (int(time.time() * 2) - self.blink) & 1
        dc.drawText(x, y, self.text + "|" if caret else self.text)
        dc.restore()
        Tool.text(self, dc, "Text tool: ctrl-1...9=Font size, ctrl-R/G/B color, ENTER=Accept")
        QTimer.singleShot(250, lambda : self.iview.update())

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            cur = list(map(int, self.iview.revmap(mx, my)))
            def tracking(mx, my):
                if mx is not None:
                    x, y = map(int, self.iview.revmap(mx, my))
                    self.pos[0] += x - cur[0]
                    self.pos[1] += y - cur[1]
                    cur[0] = x
                    cur[1] = y
            return tracking

    def key(self, e):
        self.blink = int(time.time() * 2) + 1
        if e.key() == Qt.Key_Backspace:
            self.text = self.text[:-1]
            return True
        elif e.key() == Qt.Key_Escape:
            return Tool.key(self, e)
        elif e.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.iview.to32bpp()
            x, y = self.pos
            dc = QPainter(self.iview.img)
            dc.setFont(self.font)
            dc.setPen(QPen(self.colors[self.color]))
            dc.drawText(x, y, self.text)
            dc = None
            self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        elif e.text() and e.modifiers() != Qt.ControlModifier:
            self.text += unicode(e.text())
            return True
        return Tool.key(self, e)

class MyDialog(QDialog):
    def __init__(self, parent, imglist):
        QDialog.__init__(self, parent, Qt.Window)
        self.ws = Viewer(self, imglist)
        L = QVBoxLayout(self)
        L.setContentsMargins(0, 0, 0, 0)
        L.addWidget(self.ws)
        self.setModal(True)
        self.show()

uargv = sys.argv
if len(uargv) == 1:
    print("Missing filename")
    sys.exit(1)

app = QApplication(sys.argv)

if sys.version_info[0]<3:
    # if we are on Python 2, let Qt deal with the encoding madness
    # (on Python 3 sys.argv is already a list of unicode strings)
    uargv = [unicode(x) for x in app.arguments()]

if len(uargv) in (2, 3) and uargv[1] == "--screenshot":
    img = QPixmap.grabWindow(app.desktop().winId())
    fname = "iview-screenshot.png" if len(uargv) == 2 else uargv[2]
    img.save(fname)
    uargv[1:] = [fname]

class SortWrapper:
    def __init__(self, x):
        self.x = x

    def __lt__(self, other):
        if type(self.x) is type(other.x):
            return self.x < other.x
        else:
            return id(type(self.x)) < id(type(other.x))

def smartSort(s):
    return tuple(SortWrapper(int(x)) if x[0] in "0123456789" else SortWrapper(x)
                 for x in re.findall("[0-9]+|[^0-9]+", s.upper()))

filelist = uargv[1:]
if len(filelist) == 1 and filelist[0][:8] == "file:///":
    filelist[0] = filelist[0][7:]
if len(filelist) == 1:
    slash = filelist[0].rindex("/") if "/" in filelist[0] else -1
    fl = []
    supported_extensions = [bytes(x).decode() for x in QImageReader.supportedImageFormats()]
    fre = re.compile("^.*\\.(" + "|".join(supported_extensions) + ")$", re.IGNORECASE)
    for f in os.listdir(filelist[0][:slash+1] + "."):
        if fre.match(f):
            fl.append(filelist[0][:slash+1] + f)
    fl.sort(key = smartSort)
    if filelist[0] in fl:
        i = fl.index(filelist[0])
        filelist = fl if i == 0 else fl[i:] + fl[:i]
aa = MyDialog(None, filelist)
aa.exec_()
aa = None

1f41439cab124e6d7b6947a4d256b44172e66845
<mask token>


class CropTool(RectTool):

    def __init__(self, iview):
        RectTool.__init__(self, iview)

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dark = QBrush(QColor(0, 0, 0, 128))
        w, h = self.iview.width(), self.iview.height()
        if y0 > 0:
            dc.fillRect(0, 0, w, y0, dark)
        if x0 > 0:
            dc.fillRect(0, y0, x0, y1 - y0, dark)
        if x1 < w:
            dc.fillRect(x1, y0, w - x1, y1 - y0, dark)
        if y1 < h:
            dc.fillRect(0, y1, w, h - y1, dark)
        RectTool.draw(self, dc)
        self.text(dc, 'Crop: (%i, %i) - (%i, %i) = %i x %i (Y to copy)' % (
            self.geo[0], self.geo[1], self.geo[2], self.geo[3], abs(self.
            geo[2] - self.geo[0]), abs(self.geo[3] - self.geo[1])))

    def key(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_C, Qt.Key_Enter, Qt.Key_Y):
            x0, y0, x1, y1 = self.geo
            if x0 > x1:
                x0, x1 = x1, x0
            if y0 > y1:
                y0, y1 = y1, y0
            x0, y0 = max(0, x0), max(0, y0)
            x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.
                height(), y1)
            if x0 < x1 and y0 < y1:
                cropped = self.iview.img.copy(x0, y0, x1 - x0, y1 - y0)
                if e.key() == Qt.Key_Y:
                    QApplication.clipboard().setImage(cropped)
                else:
                    self.iview.img = cropped
                    self.iview.scaled = None
                    self.iview.zoomAll()
            self.iview.tools.remove(self)
            return True
        else:
            return Tool.key(self, e)


class EllipseTool(RectTool):

    def __init__(self, iview):
        RectTool.__init__(self, iview)

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dc.setPen(QPen(self.colors[self.color], self.pen * self.iview.scale))
        dc.setBrush(QBrush())
        dc.drawEllipse(x0, y0, x1 - x0, y1 - y0)
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.setBrush(QColor(255, 0, 0))
        for x, y in (((x0 + x1) / 2, y0), ((x0 + x1) / 2, y1), (x0, (y0 +
            y1) / 2), (x1, (y0 + y1) / 2), (x0, y0), (x1, y0), (x0, y1), (
            x1, y1)):
            dc.drawEllipse(QRectF(x - 4, y - 4, 8, 8))
        self.text(dc, 'Ellipse drawing mode: 1-9=thickness, r/g/b=color')

    def key(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_E, Qt.Key_Enter):
            x0, y0, x1, y1 = self.geo
            if x0 > x1:
                x0, x1 = x1, x0
            if y0 > y1:
                y0, y1 = y1, y0
            x0, y0 = max(0, x0), max(0, y0)
            x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.
                height(), y1)
            if x0 < x1 and y0 < y1:
                self.iview.to32bpp()
                dc = QPainter(self.iview.img)
                dc.setPen(QPen(self.colors[self.color], self.pen))
                dc.setBrush(QBrush())
                dc.drawEllipse(x0, y0, x1 - x0, y1 - y0)
                dc = None
                self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        else:
            return Tool.key(self, e)


class PixColorTool(Tool):

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.p = None

    def draw(self, dc):
        if self.p is None:
            self.text(dc, 'Pixel inspection; click for color/position')
        else:
            x, y = self.p
            col = self.iview.img.pixel(x, y)
            a = col >> 24 & 255
            r = col >> 16 & 255
            g = col >> 8 & 255
            b = col >> 0 & 255
            c0 = QBrush(QColor(0, 0, 0))
            c1 = QBrush(QColor(255, 255, 255))
            x0, y0 = self.iview.map(x, y)
            x1, y1 = self.iview.map(x + 1, y + 1)
            h = (y1 - y0) * 10
            s = (y1 - y0) * 2
            dc.fillRect(x0 - 1, y0 - h - s - 1, x1 - x0 + 2, h + 2, c0)
            dc.fillRect(x0 - 1, y1 + s - 1, x1 - x0 + 2, h + 2, c0)
            dc.fillRect(x0 - h - s - 1, y0 - 1, h + 2, y1 - y0 + 2, c0)
            dc.fillRect(x1 + s - 1, y0 - 1, h + 2, y1 - y0 + 2, c0)
            dc.fillRect(x0, y0 - h - s, x1 - x0, h, c1)
            dc.fillRect(x0, y1 + s, x1 - x0, h, c1)
            dc.fillRect(x0 - h - s, y0, h, y1 - y0, c1)
            dc.fillRect(x1 + s, y0, h, y1 - y0, c1)
            self.text(dc, 
                'Pixel(x=%i, y=%i): rgb(%i, %i, %i), #%02x%02x%02x, alpha=%i' %
                (x, y, r, g, b, r, g, b, a))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x, y = map(int, self.iview.revmap(mx, my))
            if 0 <= x < self.iview.img.width(
                ) and 0 <= y < self.iview.img.height():
                self.p = x, y
                self.iview.update()


class ArrowTool(Tool):
    k = 8

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.pts = [0, 0, 0, 0]

    def draw(self, dc):
        x0, y0 = map(int, self.iview.map(*self.pts[:2]))
        x1, y1 = map(int, self.iview.map(*self.pts[2:]))
        dx, dy = (x1 - x0) // self.k, (y1 - y0) // self.k
        nx, ny = (y1 - y0) // self.k // 2, (x0 - x1) // self.k // 2
        dc.setPen(QPen(self.colors[self.color], self.pen * self.iview.scale,
            Qt.SolidLine, Qt.RoundCap))
        dc.setBrush(QBrush())
        dc.drawLine(x0, y0, x1, y1)
        dc.drawLine(x1 - dx - nx, y1 - dy - ny, x1, y1)
        dc.drawLine(x1 - dx + nx, y1 - dy + ny, x1, y1)
        self.text(dc,
            'Arrow drawing mode, 1-9=thickness, r/g/b=color, +/- changes head')

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0 = map(int, self.iview.map(*self.pts[:2]))
            x1, y1 = map(int, self.iview.map(*self.pts[2:]))
            i = -1
            if (mx - x0) ** 2 + (my - y0) ** 2 < 8 ** 2:
                i = 0
            elif (mx - x1) ** 2 + (my - y1) ** 2 < 8 ** 2:
                i = 2
            else:
                xx, yy = self.iview.revmap(mx, my)
                self.pts = [xx, yy, xx, yy]
                i = 2

            def tracking(x, y):
                if x is not None:
                    self.pts[i:i + 2] = list(map(int, self.iview.revmap(x, y)))
            return tracking

    def key(self, e):
        if e.key() == Qt.Key_Minus:
            if ArrowTool.k < 100:
                ArrowTool.k += 1
        if e.key() == Qt.Key_Plus:
            if ArrowTool.k > 4:
                ArrowTool.k -= 1
        if e.key() in (Qt.Key_Return, Qt.Key_A, Qt.Key_Enter):
            self.iview.to32bpp()
            x0, y0, x1, y1 = map(int, self.pts)
            dx, dy = (x1 - x0) // self.k, (y1 - y0) // self.k
            nx, ny = (y1 - y0) // self.k // 2, (x0 - x1) // self.k // 2
            dc = QPainter(self.iview.img)
            dc.setPen(QPen(self.colors[self.color], self.pen, Qt.SolidLine,
                Qt.RoundCap))
            dc.setBrush(QBrush())
            dc.drawLine(x0, y0, x1, y1)
            dc.drawLine(x1 - dx - nx, y1 - dy - ny, x1, y1)
            dc.drawLine(x1 - dx + nx, y1 - dy + ny, x1, y1)
            dc = None
            self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        return Tool.key(self, e)


class MeasuringTool(Tool):
    k = 8

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.pts = [0, 0, 0, 0]

    def draw(self, dc):
        x0, y0 = map(int, self.iview.map(*self.pts[:2]))
        x1, y1 = map(int, self.iview.map(*self.pts[2:]))
        dx, dy = (x1 - x0) // self.k, (y1 - y0) // self.k
        nx, ny = (y1 - y0) // self.k // 2, (x0 - x1) // self.k // 2
        dc.setPen(QPen(self.colors[self.color]))
        dc.setBrush(QBrush())
        dc.drawLine(x0, y0, x1, y1)
        dc.drawLine(x1 - dx - nx, y1 - dy - ny, x1, y1)
        dc.drawLine(x1 - dx + nx, y1 - dy + ny, x1, y1)
        dc.drawLine(x0 + dx - nx, y0 + dy - ny, x0, y0)
        dc.drawLine(x0 + dx + nx, y0 + dy + ny, x0, y0)
        dc.drawLine(x0 + nx, y0 + ny, x0 - nx, y0 - ny)
        dc.drawLine(x1 + nx, y1 + ny, x1 - nx, y1 - ny)
        x0, y0, x1, y1 = self.pts
        dx, dy = x1 - x0, y1 - y0
        L = (dx ** 2 + dy ** 2) ** 0.5
        angle = 0 if L == 0 else math.atan2(dy, dx) * 180 / math.pi
        self.text(dc, 
            'Measuring mode: (%i,%i)-(%i,%i) : %ix%i = %0.3fpx, %0.3f degrees'
             % (x0, y0, x1, y1, dx, dy, L, angle))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0 = map(int, self.iview.map(*self.pts[:2]))
            x1, y1 = map(int, self.iview.map(*self.pts[2:]))
            i = -1
            if (mx - x0) ** 2 + (my - y0) ** 2 < 8 ** 2:
                i = 0
            elif (mx - x1) ** 2 + (my - y1) ** 2 < 8 ** 2:
                i = 2
            else:
                xx, yy = map(int, self.iview.revmap(mx, my))
                self.pts = [xx, yy, xx, yy]
                i = 2

            def tracking(x, y):
                if x is not None:
                    self.pts[i:i + 2] = list(map(int, self.iview.revmap(x, y)))
                    if app.keyboardModifiers() == Qt.ShiftModifier:
                        if abs(self.pts[i] - self.pts[0 + 2 - i]) < abs(
                            self.pts[i + 1] - self.pts[1 + 3 - (i + 1)]):
                            self.pts[i] = self.pts[0 + 2 - i]
                        else:
                            self.pts[i + 1] = self.pts[1 + 3 - (i + 1)]
            return tracking

    def key(self, e):
        if e.key() == Qt.Key_Minus:
            if MeasuringTool.k < 100:
                MeasuringTool.k += 1
        if e.key() == Qt.Key_Plus:
            if MeasuringTool.k > 4:
                MeasuringTool.k -= 1
        return Tool.key(self, e)


class TextTool(Tool):

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.text = ''
        x0, y0 = map(int, self.iview.revmap(0, 0))
        x1, y1 = map(int, self.iview.revmap(self.iview.width(), self.iview.
            height()))
        self.pos = [(x0 + x1) // 2, (y0 + y1) // 2]
        self.blink = int(time.time() * 2) + 1
        QTimer.singleShot(250, lambda : self.iview.update())

    def draw(self, dc):
        x, y = self.pos
        dc.save()
        dc.translate(self.iview.tx, self.iview.ty)
        dc.scale(self.iview.scale, self.iview.scale)
        dc.setFont(self.font)
        dc.setPen(QPen(self.colors[self.color]))
        caret = int(time.time() * 2) - self.blink & 1
        dc.drawText(x, y, self.text + '|' if caret else self.text)
        dc.restore()
        Tool.text(self, dc,
            'Text tool: ctrl-1...9=Font size, ctrl-R/G/B color, ENTER=Accept')
        QTimer.singleShot(250, lambda : self.iview.update())

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            cur = list(map(int, self.iview.revmap(mx, my)))

            def tracking(mx, my):
                if mx is not None:
                    x, y = map(int, self.iview.revmap(mx, my))
                    self.pos[0] += x - cur[0]
                    self.pos[1] += y - cur[1]
                    cur[0] = x
                    cur[1] = y
            return tracking

    def key(self, e):
        self.blink = int(time.time() * 2) + 1
        if e.key() == Qt.Key_Backspace:
            self.text = self.text[:-1]
            return True
        elif e.key() == Qt.Key_Escape:
            return Tool.key(self, e)
        elif e.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.iview.to32bpp()
            x, y = self.pos
            dc = QPainter(self.iview.img)
            dc.setFont(self.font)
            dc.setPen(QPen(self.colors[self.color]))
            dc.drawText(x, y, self.text)
            dc = None
            self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        elif e.text() and e.modifiers() != Qt.ControlModifier:
            self.text += unicode(e.text())
            return True
        return Tool.key(self, e)


class MyDialog(QDialog):

    def __init__(self, parent, imglist):
        QDialog.__init__(self, parent, Qt.Window)
        self.ws = Viewer(self, imglist)
        L = QVBoxLayout(self)
        L.setContentsMargins(0, 0, 0, 0)
        L.addWidget(self.ws)
        self.setModal(True)
        self.show()


<mask token>


class SortWrapper:

    def __init__(self, x):
        self.x = x

    def __lt__(self, other):
        if type(self.x) is type(other.x):
            return self.x < other.x
        else:
            return id(type(self.x)) < id(type(other.x))


<mask token>

<mask token>


class Tool(object):
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>

    def text(self, dc, msg):
        return self.iview.statusText(dc, msg)


class RectTool(Tool):

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.geo = [0, 0, 0, 0]

    def rect(self):
        x0, y0, x1, y1 = self.geo
        x0, y0 = map(int, self.iview.map(self.geo[0], self.geo[1]))
        x1, y1 = map(int, self.iview.map(self.geo[2], self.geo[3]))
        if x0 > x1:
            x0, x1 = x1, x0
        if y0 > y1:
            y0, y1 = y1, y0
        return x0, y0, x1, y1

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dc.setBrush(QBrush())
        dc.setPen(QPen(QColor(0, 0, 0), 4))
        dc.drawRect(QRectF(x0, y0, x1 - x0, y1 - y0))
        dc.setPen(QPen(QColor(255, 255, 255), 2, Qt.DashLine))
        dc.drawRect(QRectF(x0, y0, x1 - x0, y1 - y0))
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.setBrush(QColor(255, 0, 0))
        for x, y in ((x0, y0), (x1, y0), (x0, y1), (x1, y1)):
            dc.drawEllipse(QRectF(x - 4, y - 4, 8, 8))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0, x1, y1 = self.geo
            x0, y0 = map(int, self.iview.map(self.geo[0], self.geo[1]))
            x1, y1 = map(int, self.iview.map(self.geo[2], self.geo[3]))
            f = 0
            if near(mx, x0, 8) and between(my, y0, y1, 8):
                f |= 1
            if near(my, y0, 8) and between(mx, x0, x1, 8):
                f |= 2
            if near(mx, x1, 8) and between(my, y0, y1, 8):
                f |= 4
            if near(my, y1, 8) and between(mx, x0, x1, 8):
                f |= 8
            if f == 0 and between(mx, x0, x1) and between(my, y0, y1):
                f = 15
            else:
                if f & 5 == 5:
                    f ^= 4
                if f & 10 == 10:
                    f ^= 8
            if f == 0:
                xx, yy = map(int, self.iview.revmap(mx, my))
                self.geo = [xx, yy, xx, yy]
                f = 4 + 8
            if f:
                cur = list(map(int, self.iview.revmap(mx, my)))

                def tracking(x, y):
                    if x is not None:
                        x, y = map(int, self.iview.revmap(x, y))
                        dx, dy = x - cur[0], y - cur[1]
                        cur[0], cur[1] = x, y
                        if f & 1:
                            self.geo[0] += dx
                        if f & 2:
                            self.geo[1] += dy
                        if f & 4:
                            self.geo[2] += dx
                        if f & 8:
                            self.geo[3] += dy
                        self.geo[0] = max(0, min(self.geo[0], self.iview.
                            img.width()))
                        self.geo[1] = max(0, min(self.geo[1], self.iview.
                            img.height()))
                        self.geo[2] = max(0, min(self.geo[2], self.iview.
                            img.width()))
                        self.geo[3] = max(0, min(self.geo[3], self.iview.
                            img.height()))
                return tracking


class CropTool(RectTool):

    def __init__(self, iview):
        RectTool.__init__(self, iview)

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dark = QBrush(QColor(0, 0, 0, 128))
        w, h = self.iview.width(), self.iview.height()
        if y0 > 0:
            dc.fillRect(0, 0, w, y0, dark)
        if x0 > 0:
            dc.fillRect(0, y0, x0, y1 - y0, dark)
        if x1 < w:
            dc.fillRect(x1, y0, w - x1, y1 - y0, dark)
        if y1 < h:
            dc.fillRect(0, y1, w, h - y1, dark)
        RectTool.draw(self, dc)
        self.text(dc, 'Crop: (%i, %i) - (%i, %i) = %i x %i (Y to copy)' % (
            self.geo[0], self.geo[1], self.geo[2], self.geo[3], abs(self.
            geo[2] - self.geo[0]), abs(self.geo[3] - self.geo[1])))

    def key(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_C, Qt.Key_Enter, Qt.Key_Y):
            x0, y0, x1, y1 = self.geo
            if x0 > x1:
                x0, x1 = x1, x0
            if y0 > y1:
                y0, y1 = y1, y0
            x0, y0 = max(0, x0), max(0, y0)
            x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.
                height(), y1)
            if x0 < x1 and y0 < y1:
                cropped = self.iview.img.copy(x0, y0, x1 - x0, y1 - y0)
                if e.key() == Qt.Key_Y:
                    QApplication.clipboard().setImage(cropped)
                else:
                    self.iview.img = cropped
                    self.iview.scaled = None
                    self.iview.zoomAll()
            self.iview.tools.remove(self)
            return True
        else:
            return Tool.key(self, e)


class EllipseTool(RectTool):

    def __init__(self, iview):
        RectTool.__init__(self, iview)

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dc.setPen(QPen(self.colors[self.color], self.pen * self.iview.scale))
        dc.setBrush(QBrush())
        dc.drawEllipse(x0, y0, x1 - x0, y1 - y0)
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.setBrush(QColor(255, 0, 0))
        for x, y in (((x0 + x1) / 2, y0), ((x0 + x1) / 2, y1), (x0, (y0 +
            y1) / 2), (x1, (y0 + y1) / 2), (x0, y0), (x1, y0), (x0, y1), (
            x1, y1)):
            dc.drawEllipse(QRectF(x - 4, y - 4, 8, 8))
        self.text(dc, 'Ellipse drawing mode: 1-9=thickness, r/g/b=color')

    def key(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_E, Qt.Key_Enter):
            x0, y0, x1, y1 = self.geo
            if x0 > x1:
                x0, x1 = x1, x0
            if y0 > y1:
                y0, y1 = y1, y0
            x0, y0 = max(0, x0), max(0, y0)
            x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.
                height(), y1)
            if x0 < x1 and y0 < y1:
                self.iview.to32bpp()
                dc = QPainter(self.iview.img)
                dc.setPen(QPen(self.colors[self.color], self.pen))
                dc.setBrush(QBrush())
                dc.drawEllipse(x0, y0, x1 - x0, y1 - y0)
                dc = None
                self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        else:
            return Tool.key(self, e)


class PixColorTool(Tool):

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.p = None

    def draw(self, dc):
        if self.p is None:
            self.text(dc, 'Pixel inspection; click for color/position')
        else:
            x, y = self.p
            col = self.iview.img.pixel(x, y)
            a = col >> 24 & 255
            r = col >> 16 & 255
            g = col >> 8 & 255
            b = col >> 0 & 255
            c0 = QBrush(QColor(0, 0, 0))
            c1 = QBrush(QColor(255, 255, 255))
            x0, y0 = self.iview.map(x, y)
            x1, y1 = self.iview.map(x + 1, y + 1)
            h = (y1 - y0) * 10
            s = (y1 - y0) * 2
            dc.fillRect(x0 - 1, y0 - h - s - 1, x1 - x0 + 2, h + 2, c0)
            dc.fillRect(x0 - 1, y1 + s - 1, x1 - x0 + 2, h + 2, c0)
            dc.fillRect(x0 - h - s - 1, y0 - 1, h + 2, y1 - y0 + 2, c0)
            dc.fillRect(x1 + s - 1, y0 - 1, h + 2, y1 - y0 + 2, c0)
            dc.fillRect(x0, y0 - h - s, x1 - x0, h, c1)
            dc.fillRect(x0, y1 + s, x1 - x0, h, c1)
            dc.fillRect(x0 - h - s, y0, h, y1 - y0, c1)
            dc.fillRect(x1 + s, y0, h, y1 - y0, c1)
            self.text(dc, 
                'Pixel(x=%i, y=%i): rgb(%i, %i, %i), #%02x%02x%02x, alpha=%i' %
                (x, y, r, g, b, r, g, b, a))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x, y = map(int, self.iview.revmap(mx, my))
            if 0 <= x < self.iview.img.width(
                ) and 0 <= y < self.iview.img.height():
                self.p = x, y
                self.iview.update()


class ArrowTool(Tool):
    k = 8

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.pts = [0, 0, 0, 0]

    def draw(self, dc):
        x0, y0 = map(int, self.iview.map(*self.pts[:2]))
        x1, y1 = map(int, self.iview.map(*self.pts[2:]))
        dx, dy = (x1 - x0) // self.k, (y1 - y0) // self.k
        nx, ny = (y1 - y0) // self.k // 2, (x0 - x1) // self.k // 2
        dc.setPen(QPen(self.colors[self.color], self.pen * self.iview.scale,
            Qt.SolidLine, Qt.RoundCap))
        dc.setBrush(QBrush())
        dc.drawLine(x0, y0, x1, y1)
        dc.drawLine(x1 - dx - nx, y1 - dy - ny, x1, y1)
        dc.drawLine(x1 - dx + nx, y1 - dy + ny, x1, y1)
        self.text(dc,
            'Arrow drawing mode, 1-9=thickness, r/g/b=color, +/- changes head')

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0 = map(int, self.iview.map(*self.pts[:2]))
            x1, y1 = map(int, self.iview.map(*self.pts[2:]))
            i = -1
            if (mx - x0) ** 2 + (my - y0) ** 2 < 8 ** 2:
                i = 0
            elif (mx - x1) ** 2 + (my - y1) ** 2 < 8 ** 2:
                i = 2
            else:
                xx, yy = self.iview.revmap(mx, my)
                self.pts = [xx, yy, xx, yy]
                i = 2

            def tracking(x, y):
                if x is not None:
                    self.pts[i:i + 2] = list(map(int, self.iview.revmap(x, y)))
            return tracking

    def key(self, e):
        if e.key() == Qt.Key_Minus:
            if ArrowTool.k < 100:
                ArrowTool.k += 1
        if e.key() == Qt.Key_Plus:
            if ArrowTool.k > 4:
                ArrowTool.k -= 1
        if e.key() in (Qt.Key_Return, Qt.Key_A, Qt.Key_Enter):
            self.iview.to32bpp()
            x0, y0, x1, y1 = map(int, self.pts)
            dx, dy = (x1 - x0) // self.k, (y1 - y0) // self.k
            nx, ny = (y1 - y0) // self.k // 2, (x0 - x1) // self.k // 2
            dc = QPainter(self.iview.img)
            dc.setPen(QPen(self.colors[self.color], self.pen, Qt.SolidLine,
                Qt.RoundCap))
            dc.setBrush(QBrush())
            dc.drawLine(x0, y0, x1, y1)
            dc.drawLine(x1 - dx - nx, y1 - dy - ny, x1, y1)
            dc.drawLine(x1 - dx + nx, y1 - dy + ny, x1, y1)
            dc = None
            self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        return Tool.key(self, e)


class MeasuringTool(Tool):
    k = 8

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.pts = [0, 0, 0, 0]

    def draw(self, dc):
        x0, y0 = map(int, self.iview.map(*self.pts[:2]))
        x1, y1 = map(int, self.iview.map(*self.pts[2:]))
        dx, dy = (x1 - x0) // self.k, (y1 - y0) // self.k
        nx, ny = (y1 - y0) // self.k // 2, (x0 - x1) // self.k // 2
        dc.setPen(QPen(self.colors[self.color]))
        dc.setBrush(QBrush())
        dc.drawLine(x0, y0, x1, y1)
        dc.drawLine(x1 - dx - nx, y1 - dy - ny, x1, y1)
        dc.drawLine(x1 - dx + nx, y1 - dy + ny, x1, y1)
        dc.drawLine(x0 + dx - nx, y0 + dy - ny, x0, y0)
        dc.drawLine(x0 + dx + nx, y0 + dy + ny, x0, y0)
        dc.drawLine(x0 + nx, y0 + ny, x0 - nx, y0 - ny)
        dc.drawLine(x1 + nx, y1 + ny, x1 - nx, y1 - ny)
        x0, y0, x1, y1 = self.pts
        dx, dy = x1 - x0, y1 - y0
        L = (dx ** 2 + dy ** 2) ** 0.5
        angle = 0 if L == 0 else math.atan2(dy, dx) * 180 / math.pi
        self.text(dc, 
            'Measuring mode: (%i,%i)-(%i,%i) : %ix%i = %0.3fpx, %0.3f degrees'
             % (x0, y0, x1, y1, dx, dy, L, angle))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0 = map(int, self.iview.map(*self.pts[:2]))
            x1, y1 = map(int, self.iview.map(*self.pts[2:]))
            i = -1
            if (mx - x0) ** 2 + (my - y0) ** 2 < 8 ** 2:
                i = 0
            elif (mx - x1) ** 2 + (my - y1) ** 2 < 8 ** 2:
                i = 2
            else:
                xx, yy = map(int, self.iview.revmap(mx, my))
                self.pts = [xx, yy, xx, yy]
                i = 2

            def tracking(x, y):
                if x is not None:
                    self.pts[i:i + 2] = list(map(int, self.iview.revmap(x, y)))
                    if app.keyboardModifiers() == Qt.ShiftModifier:
                        if abs(self.pts[i] - self.pts[0 + 2 - i]) < abs(
                            self.pts[i + 1] - self.pts[1 + 3 - (i + 1)]):
                            self.pts[i] = self.pts[0 + 2 - i]
                        else:
                            self.pts[i + 1] = self.pts[1 + 3 - (i + 1)]
            return tracking

    def key(self, e):
        if e.key() == Qt.Key_Minus:
            if MeasuringTool.k < 100:
                MeasuringTool.k += 1
        if e.key() == Qt.Key_Plus:
            if MeasuringTool.k > 4:
                MeasuringTool.k -= 1
        return Tool.key(self, e)


class TextTool(Tool):

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.text = ''
        x0, y0 = map(int, self.iview.revmap(0, 0))
        x1, y1 = map(int, self.iview.revmap(self.iview.width(), self.iview.
            height()))
        self.pos = [(x0 + x1) // 2, (y0 + y1) // 2]
        self.blink = int(time.time() * 2) + 1
        QTimer.singleShot(250, lambda : self.iview.update())

    def draw(self, dc):
        x, y = self.pos
        dc.save()
        dc.translate(self.iview.tx, self.iview.ty)
        dc.scale(self.iview.scale, self.iview.scale)
        dc.setFont(self.font)
        dc.setPen(QPen(self.colors[self.color]))
        caret = int(time.time() * 2) - self.blink & 1
        dc.drawText(x, y, self.text + '|' if caret else self.text)
        dc.restore()
        Tool.text(self, dc,
            'Text tool: ctrl-1...9=Font size, ctrl-R/G/B color, ENTER=Accept')
        QTimer.singleShot(250, lambda : self.iview.update())

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            cur = list(map(int, self.iview.revmap(mx, my)))

            def tracking(mx, my):
                if mx is not None:
                    x, y = map(int, self.iview.revmap(mx, my))
                    self.pos[0] += x - cur[0]
                    self.pos[1] += y - cur[1]
                    cur[0] = x
                    cur[1] = y
            return tracking

    def key(self, e):
        self.blink = int(time.time() * 2) + 1
        if e.key() == Qt.Key_Backspace:
            self.text = self.text[:-1]
            return True
        elif e.key() == Qt.Key_Escape:
            return Tool.key(self, e)
        elif e.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.iview.to32bpp()
            x, y = self.pos
            dc = QPainter(self.iview.img)
            dc.setFont(self.font)
            dc.setPen(QPen(self.colors[self.color]))
            dc.drawText(x, y, self.text)
            dc = None
            self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        elif e.text() and e.modifiers() != Qt.ControlModifier:
            self.text += unicode(e.text())
            return True
        return Tool.key(self, e)


class MyDialog(QDialog):

    def __init__(self, parent, imglist):
        QDialog.__init__(self, parent, Qt.Window)
        self.ws = Viewer(self, imglist)
        L = QVBoxLayout(self)
        L.setContentsMargins(0, 0, 0, 0)
        L.addWidget(self.ws)
        self.setModal(True)
        self.show()


<mask token>


class SortWrapper:

    def __init__(self, x):
        self.x = x

    def __lt__(self, other):
        if type(self.x) is type(other.x):
            return self.x < other.x
        else:
            return id(type(self.x)) < id(type(other.x))


<mask token>

<mask token>


class Viewer(QW):
    <mask token>

    def load(self, index):
        self.imglist[:] = self.imglist[index:] + self.imglist[:index]
        self.img = QImage(self.imglist[0])
        self.scaled = None
        self.imgmtime = os.stat(self.imglist[0]).st_mtime
        self.parent().setWindowTitle(self.mkTitle())
        self.update()
        gc.collect()
    <mask token>
    <mask token>

    def zoomAll(self):
        self.scale = min(float(self.width()) / max(1, self.img.width()), 
            float(self.height()) / max(1, self.img.height()))
        self.tx = (self.width() - self.img.width() * self.scale) / 2
        self.ty = (self.height() - self.img.height() * self.scale) / 2
        self.update()

    def keyPressEvent(self, e):
        for t in self.tools:
            if t.key(e):
                self.update()
                return
        if e.key() == Qt.Key_Right:
            w, h = self.img.width(), self.img.height()
            self.load(1)
            if (w, h) != (self.img.width(), self.img.height()):
                self.zoomAll()
        elif e.key() == Qt.Key_Left:
            w, h = self.img.width(), self.img.height()
            self.load(len(self.imglist) - 1)
            if (w, h) != (self.img.width(), self.img.height()):
                self.zoomAll()
        elif e.key() == Qt.Key_1:
            self.setZoom(1)
        elif e.key() == Qt.Key_2:
            self.setZoom(2)
        elif e.key() == Qt.Key_3:
            self.setZoom(3)
        elif e.key() == Qt.Key_4:
            self.setZoom(4)
        elif e.key() in (Qt.Key_Home, Qt.Key_0):
            self.zoomAll()
        elif e.key() in (Qt.Key_Escape, Qt.Key_Q):
            self.parent().deleteLater()
        elif e.key() == Qt.Key_U:
            w, h = self.img.width(), self.img.height()
            self.load(0)
            if (w, h) != (self.img.width(), self.img.height()):
                self.zoomAll()
        elif e.key() in (Qt.Key_F, Qt.Key_Return, Qt.Key_Enter):
            self.fullscreen = not self.fullscreen
            if self.fullscreen:
                self.parent().showFullScreen()
            else:
                self.parent().showNormal()
        elif e.key() == Qt.Key_R:
            img = QImage(self.img.height(), self.img.width(), QImage.
                Format_ARGB32)
            img.fill(0)
            dc = QPainter(img)
            dc.rotate(90)
            dc.drawImage(0, -self.img.height(), self.img)
            dc = None
            self.img = img
            self.zoomAll()
            gc.collect()
        elif e.key() == Qt.Key_C:
            self.tools = [CropTool(self)]
        elif e.key() == Qt.Key_E:
            self.tools = [EllipseTool(self)]
        elif e.key() == Qt.Key_P:
            self.tools = [PixColorTool(self)]
        elif e.key() == Qt.Key_A:
            self.tools = [ArrowTool(self)]
        elif e.key() == Qt.Key_M:
            self.tools = [MeasuringTool(self)]
        elif e.key() == Qt.Key_Y:
            self.to32bpp()
            b = self.img.bits()
            b.setsize(self.img.height() * self.img.bytesPerLine())
            b.setwriteable(True)
            for i in range(0, self.img.height() * self.img.bytesPerLine(), 4):
                b[i] = b[i + 2] = b[i + 1]
            self.scaled = None
        elif e.key() == Qt.Key_T:
            self.tools = [TextTool(self)]
        elif e.key() == Qt.Key_S:
            fname = str(QFileDialog.getSaveFileName(self, 'Save image as',
                self.imglist[0]))
            if fname != '':
                supported_extensions = [bytes(x).decode() for x in
                    QImageWriter.supportedImageFormats()]
                if re.match('^.*\\.(' + '|'.join(supported_extensions) +
                    ')$', fname, re.IGNORECASE):
                    if self.img.save(fname):
                        if fname not in self.imglist:
                            self.imglist.insert(0, fname)
                            self.load(0)
                    else:
                        QMessageBox.critical(self,
                            'IView: Error saving image file',
                            'There was a problem saving the image file.')
                elif fname[-4:].lower() == '.pgm':
                    try:
                        self.to32bpp()
                        data = self.img.bits().asstring(self.img.height() *
                            self.img.bytesPerLine())
                        f = open(fname, 'wb')
                        f.write(('P5\n%i %i 255\n' % (self.img.width(),
                            self.img.height())).encode('ascii'))
                        for y in range(self.img.height()):
                            f.write(data[y * self.img.width() * 4 + 1:(y + 
                                1) * self.img.width() * 4 + 1:4])
                        f.close()
                        if fname not in self.imglist:
                            self.imglist.insert(0, fname)
                            self.load(0)
                    except Exception:
                        QMessageBox.critical(self,
                            'IView: Error saving image file',
                            'There was a problem saving the image file.')
                else:
                    QMessageBox.critical(self,
                        'IView: Unable to save image file', 
                        'The requested image format is not supported, supported extensions are '
                         + ', '.join(supported_extensions))
        elif e.key() == Qt.Key_X:
            subprocess.Popen([os.getenv('EDITOR'), __file__])
        elif e.key() == Qt.Key_Delete:
            if QMessageBox.question(self, 'Image deletion',
                'Are you sure you want to delete this image?', QMessageBox.
                Yes | QMessageBox.No) == QMessageBox.Yes:
                try:
                    os.unlink(self.imglist[0])
                except OSError:
                    QMessageBox.information(self, 'IView',
                        'Error deleting image file')
                    return
                self.scaled = None
                self.imglist.pop(0)
                if len(self.imglist) == 0:
                    sys.exit(0)
                self.load(0)
                self.zoomAll()
        elif e.key() == Qt.Key_F2:
            name = self.imglist[0]
            d = QDialog(self)
            d.setWindowTitle('Rename image')
            vb = QVBoxLayout(d)
            l = QLabel('New name')
            vb.addWidget(l)
            sle = QLineEdit(d)
            vb.addWidget(sle)
            hb = QHBoxLayout()
            vb.addLayout(hb)
            hb.addWidget(QWidget(d))
            ok = QPushButton('OK', d)
            hb.addWidget(ok)
            ok.setFixedWidth(80)
            cancel = QPushButton('Cancel', d)
            hb.addWidget(cancel)
            cancel.setFixedWidth(80)
            hb.addWidget(QWidget(d))
            sle.setText(name)
            basename_start = 1 + name.rindex('/') if '/' in name else 0
            if '.' in name[basename_start:]:
                sle.setSelection(basename_start, name[basename_start:].
                    rindex('.'))
            sle.setFocus()
            width = sle.fontMetrics().boundingRect(name).width()
            sle.setMinimumWidth(width * 9 // 8)
            d.connect(cancel, SIGNAL('clicked()'), d.reject)
            d.connect(ok, SIGNAL('clicked()'), d.accept)
            if d.exec_() == QDialog.Accepted:
                try:
                    s = sle.text()
                    os.rename(self.imglist[0], unicode(s))
                    self.imglist[0] = unicode(s)
                    self.load(0)
                except Exception:
                    QMessageBox.critical(self,
                        'IView: Unable to rename the file',
                        'There was a problem renaming the file')
        elif e.key() == Qt.Key_O:
            self.titleOverlay = not self.titleOverlay
        elif e.key() == Qt.Key_F1:
            QMessageBox.information(self, 'IView',
                '<center><h1>IView</h1>A simple image viewer<br>by Andrea "6502" Griffini</center><ul>  <li> <b>F1</b> This help message</li>  <li> <b>Mouse drag</b> Pan</li>  <li> <b>Mouse wheel</b> Zoom</li>  <li> <b>Left/Right</b> Previous/next image</li>  <li> <b>Home</b> Zoom fit</li>  <li> <b>1/2/3/4</b> Display resolution to Nx</li>  <li> <b>F or Enter</b> Fullscreen mode</li>  <li> <b>O</b> Toggle file name overlay in fullscreen mode</li>  <li> <b>A</b> Arrow markup mode</li>  <li> <b>P</b> Pixel inspection mode</li>  <li> <b>M</b> Measure mode</li>  <li> <b>E</b> Ellipse markup mode</li>  <li> <b>T</b> Text markup mode</li>  <li> <b>C</b> Cropping/yanking mode</li>  <li> <b>Y</b> Convert to grayscale</li>  <li> <b>R</b> Rotate image 90 degrees</li>  <li> <b>U</b> Reload original image</li>  <li> <b>S</b> Save modified image</li>  <li> <b>F2</b> Rename current file</li>  <li> <b>Del</b> Delete current file</li>  <li> <b>X</b> Customize</li></ul>'
                )
        self.update()

    def sizeHint(self):
        return QSize(900, 700)
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>
    <mask token>

    def map(self, x, y):
        return self.tx + x * self.scale, self.ty + y * self.scale
    <mask token>
    <mask token>

    def statusText(self, dc, msg):
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.drawText(4, 20, msg)
        dc.drawText(7, 20, msg)
        dc.drawText(5, 19, msg)
        dc.drawText(5, 21, msg)
        dc.setPen(QPen(QColor(0, 255, 0)))
        dc.drawText(5, 20, msg)


class Tool(object):
    pen = 4
    color = 0
    colors = [QColor(255, 0, 0), QColor(0, 255, 0), QColor(0, 0, 255)]

    def __init__(self, iview):
        self.iview = iview
        self.font = QFont('sans-serif')
        self.font.setPixelSize(self.pen * 8 + 11)

    def key(self, e):
        if e.key() == Qt.Key_R:
            Tool.color = 0
            self.iview.update()
            return True
        if e.key() == Qt.Key_G:
            Tool.color = 1
            self.iview.update()
            return True
        if e.key() == Qt.Key_B:
            Tool.color = 2
            self.iview.update()
            return True
        if e.key() >= Qt.Key_1 and e.key() <= Qt.Key_9:
            Tool.pen = (e.key() - Qt.Key_1) * 2
            self.font.setPixelSize(self.pen * 8 + 11)
            self.iview.update()
            return True
        if e.key() == Qt.Key_Escape:
            self.iview.tools.remove(self)
            return True

    def text(self, dc, msg):
        return self.iview.statusText(dc, msg)


class RectTool(Tool):

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.geo = [0, 0, 0, 0]

    def rect(self):
        x0, y0, x1, y1 = self.geo
        x0, y0 = map(int, self.iview.map(self.geo[0], self.geo[1]))
        x1, y1 = map(int, self.iview.map(self.geo[2], self.geo[3]))
        if x0 > x1:
            x0, x1 = x1, x0
        if y0 > y1:
            y0, y1 = y1, y0
        return x0, y0, x1, y1

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dc.setBrush(QBrush())
        dc.setPen(QPen(QColor(0, 0, 0), 4))
        dc.drawRect(QRectF(x0, y0, x1 - x0, y1 - y0))
        dc.setPen(QPen(QColor(255, 255, 255), 2, Qt.DashLine))
        dc.drawRect(QRectF(x0, y0, x1 - x0, y1 - y0))
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.setBrush(QColor(255, 0, 0))
        for x, y in ((x0, y0), (x1, y0), (x0, y1), (x1, y1)):
            dc.drawEllipse(QRectF(x - 4, y - 4, 8, 8))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0, x1, y1 = self.geo
            x0, y0 = map(int, self.iview.map(self.geo[0], self.geo[1]))
            x1, y1 = map(int, self.iview.map(self.geo[2], self.geo[3]))
            f = 0
            if near(mx, x0, 8) and between(my, y0, y1, 8):
                f |= 1
            if near(my, y0, 8) and between(mx, x0, x1, 8):
                f |= 2
            if near(mx, x1, 8) and between(my, y0, y1, 8):
                f |= 4
            if near(my, y1, 8) and between(mx, x0, x1, 8):
                f |= 8
            if f == 0 and between(mx, x0, x1) and between(my, y0, y1):
                f = 15
            else:
                if f & 5 == 5:
                    f ^= 4
                if f & 10 == 10:
                    f ^= 8
            if f == 0:
                xx, yy = map(int, self.iview.revmap(mx, my))
                self.geo = [xx, yy, xx, yy]
                f = 4 + 8
            if f:
                cur = list(map(int, self.iview.revmap(mx, my)))

                def tracking(x, y):
                    if x is not None:
                        x, y = map(int, self.iview.revmap(x, y))
                        dx, dy = x - cur[0], y - cur[1]
                        cur[0], cur[1] = x, y
                        if f & 1:
                            self.geo[0] += dx
                        if f & 2:
                            self.geo[1] += dy
                        if f & 4:
                            self.geo[2] += dx
                        if f & 8:
                            self.geo[3] += dy
                        self.geo[0] = max(0, min(self.geo[0], self.iview.
                            img.width()))
                        self.geo[1] = max(0, min(self.geo[1], self.iview.
                            img.height()))
                        self.geo[2] = max(0, min(self.geo[2], self.iview.
                            img.width()))
                        self.geo[3] = max(0, min(self.geo[3], self.iview.
                            img.height()))
                return tracking


class CropTool(RectTool):

    def __init__(self, iview):
        RectTool.__init__(self, iview)

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dark = QBrush(QColor(0, 0, 0, 128))
        w, h = self.iview.width(), self.iview.height()
        if y0 > 0:
            dc.fillRect(0, 0, w, y0, dark)
        if x0 > 0:
            dc.fillRect(0, y0, x0, y1 - y0, dark)
        if x1 < w:
            dc.fillRect(x1, y0, w - x1, y1 - y0, dark)
        if y1 < h:
            dc.fillRect(0, y1, w, h - y1, dark)
        RectTool.draw(self, dc)
        self.text(dc, 'Crop: (%i, %i) - (%i, %i) = %i x %i (Y to copy)' % (
            self.geo[0], self.geo[1], self.geo[2], self.geo[3], abs(self.
            geo[2] - self.geo[0]), abs(self.geo[3] - self.geo[1])))

    def key(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_C, Qt.Key_Enter, Qt.Key_Y):
            x0, y0, x1, y1 = self.geo
            if x0 > x1:
                x0, x1 = x1, x0
            if y0 > y1:
                y0, y1 = y1, y0
            x0, y0 = max(0, x0), max(0, y0)
            x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.
                height(), y1)
            if x0 < x1 and y0 < y1:
                cropped = self.iview.img.copy(x0, y0, x1 - x0, y1 - y0)
                if e.key() == Qt.Key_Y:
                    QApplication.clipboard().setImage(cropped)
                else:
                    self.iview.img = cropped
                    self.iview.scaled = None
                    self.iview.zoomAll()
            self.iview.tools.remove(self)
            return True
        else:
            return Tool.key(self, e)


class EllipseTool(RectTool):

    def __init__(self, iview):
        RectTool.__init__(self, iview)

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dc.setPen(QPen(self.colors[self.color], self.pen * self.iview.scale))
        dc.setBrush(QBrush())
        dc.drawEllipse(x0, y0, x1 - x0, y1 - y0)
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.setBrush(QColor(255, 0, 0))
        for x, y in (((x0 + x1) / 2, y0), ((x0 + x1) / 2, y1), (x0, (y0 +
            y1) / 2), (x1, (y0 + y1) / 2), (x0, y0), (x1, y0), (x0, y1), (
            x1, y1)):
            dc.drawEllipse(QRectF(x - 4, y - 4, 8, 8))
        self.text(dc, 'Ellipse drawing mode: 1-9=thickness, r/g/b=color')

    def key(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_E, Qt.Key_Enter):
            x0, y0, x1, y1 = self.geo
            if x0 > x1:
                x0, x1 = x1, x0
            if y0 > y1:
                y0, y1 = y1, y0
            x0, y0 = max(0, x0), max(0, y0)
            x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.
                height(), y1)
            if x0 < x1 and y0 < y1:
                self.iview.to32bpp()
                dc = QPainter(self.iview.img)
                dc.setPen(QPen(self.colors[self.color], self.pen))
                dc.setBrush(QBrush())
                dc.drawEllipse(x0, y0, x1 - x0, y1 - y0)
                dc = None
                self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        else:
            return Tool.key(self, e)


class PixColorTool(Tool):

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.p = None

    def draw(self, dc):
        if self.p is None:
            self.text(dc, 'Pixel inspection; click for color/position')
        else:
            x, y = self.p
            col = self.iview.img.pixel(x, y)
            a = col >> 24 & 255
            r = col >> 16 & 255
            g = col >> 8 & 255
            b = col >> 0 & 255
            c0 = QBrush(QColor(0, 0, 0))
            c1 = QBrush(QColor(255, 255, 255))
            x0, y0 = self.iview.map(x, y)
            x1, y1 = self.iview.map(x + 1, y + 1)
            h = (y1 - y0) * 10
            s = (y1 - y0) * 2
            dc.fillRect(x0 - 1, y0 - h - s - 1, x1 - x0 + 2, h + 2, c0)
            dc.fillRect(x0 - 1, y1 + s - 1, x1 - x0 + 2, h + 2, c0)
            dc.fillRect(x0 - h - s - 1, y0 - 1, h + 2, y1 - y0 + 2, c0)
            dc.fillRect(x1 + s - 1, y0 - 1, h + 2, y1 - y0 + 2, c0)
            dc.fillRect(x0, y0 - h - s, x1 - x0, h, c1)
            dc.fillRect(x0, y1 + s, x1 - x0, h, c1)
            dc.fillRect(x0 - h - s, y0, h, y1 - y0, c1)
            dc.fillRect(x1 + s, y0, h, y1 - y0, c1)
            self.text(dc, 
                'Pixel(x=%i, y=%i): rgb(%i, %i, %i), #%02x%02x%02x, alpha=%i' %
                (x, y, r, g, b, r, g, b, a))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x, y = map(int, self.iview.revmap(mx, my))
            if 0 <= x < self.iview.img.width(
                ) and 0 <= y < self.iview.img.height():
                self.p = x, y
                self.iview.update()


class ArrowTool(Tool):
    k = 8

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.pts = [0, 0, 0, 0]

    def draw(self, dc):
        x0, y0 = map(int, self.iview.map(*self.pts[:2]))
        x1, y1 = map(int, self.iview.map(*self.pts[2:]))
        dx, dy = (x1 - x0) // self.k, (y1 - y0) // self.k
        nx, ny = (y1 - y0) // self.k // 2, (x0 - x1) // self.k // 2
        dc.setPen(QPen(self.colors[self.color], self.pen * self.iview.scale,
            Qt.SolidLine, Qt.RoundCap))
        dc.setBrush(QBrush())
        dc.drawLine(x0, y0, x1, y1)
        dc.drawLine(x1 - dx - nx, y1 - dy - ny, x1, y1)
        dc.drawLine(x1 - dx + nx, y1 - dy + ny, x1, y1)
        self.text(dc,
            'Arrow drawing mode, 1-9=thickness, r/g/b=color, +/- changes head')

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0 = map(int, self.iview.map(*self.pts[:2]))
            x1, y1 = map(int, self.iview.map(*self.pts[2:]))
            i = -1
            if (mx - x0) ** 2 + (my - y0) ** 2 < 8 ** 2:
                i = 0
            elif (mx - x1) ** 2 + (my - y1) ** 2 < 8 ** 2:
                i = 2
            else:
                xx, yy = self.iview.revmap(mx, my)
                self.pts = [xx, yy, xx, yy]
                i = 2

            def tracking(x, y):
                if x is not None:
                    self.pts[i:i + 2] = list(map(int, self.iview.revmap(x, y)))
            return tracking

    def key(self, e):
        if e.key() == Qt.Key_Minus:
            if ArrowTool.k < 100:
                ArrowTool.k += 1
        if e.key() == Qt.Key_Plus:
            if ArrowTool.k > 4:
                ArrowTool.k -= 1
        if e.key() in (Qt.Key_Return, Qt.Key_A, Qt.Key_Enter):
            self.iview.to32bpp()
            x0, y0, x1, y1 = map(int, self.pts)
            dx, dy = (x1 - x0) // self.k, (y1 - y0) // self.k
            nx, ny = (y1 - y0) // self.k // 2, (x0 - x1) // self.k // 2
            dc = QPainter(self.iview.img)
            dc.setPen(QPen(self.colors[self.color], self.pen, Qt.SolidLine,
                Qt.RoundCap))
            dc.setBrush(QBrush())
            dc.drawLine(x0, y0, x1, y1)
            dc.drawLine(x1 - dx - nx, y1 - dy - ny, x1, y1)
            dc.drawLine(x1 - dx + nx, y1 - dy + ny, x1, y1)
            dc = None
            self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        return Tool.key(self, e)


class MeasuringTool(Tool):
    k = 8

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.pts = [0, 0, 0, 0]

    def draw(self, dc):
        x0, y0 = map(int, self.iview.map(*self.pts[:2]))
        x1, y1 = map(int, self.iview.map(*self.pts[2:]))
        dx, dy = (x1 - x0) // self.k, (y1 - y0) // self.k
        nx, ny = (y1 - y0) // self.k // 2, (x0 - x1) // self.k // 2
        dc.setPen(QPen(self.colors[self.color]))
        dc.setBrush(QBrush())
        dc.drawLine(x0, y0, x1, y1)
        dc.drawLine(x1 - dx - nx, y1 - dy - ny, x1, y1)
        dc.drawLine(x1 - dx + nx, y1 - dy + ny, x1, y1)
        dc.drawLine(x0 + dx - nx, y0 + dy - ny, x0, y0)
        dc.drawLine(x0 + dx + nx, y0 + dy + ny, x0, y0)
        dc.drawLine(x0 + nx, y0 + ny, x0 - nx, y0 - ny)
        dc.drawLine(x1 + nx, y1 + ny, x1 - nx, y1 - ny)
        x0, y0, x1, y1 = self.pts
        dx, dy = x1 - x0, y1 - y0
        L = (dx ** 2 + dy ** 2) ** 0.5
        angle = 0 if L == 0 else math.atan2(dy, dx) * 180 / math.pi
        self.text(dc, 
            'Measuring mode: (%i,%i)-(%i,%i) : %ix%i = %0.3fpx, %0.3f degrees'
             % (x0, y0, x1, y1, dx, dy, L, angle))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0 = map(int, self.iview.map(*self.pts[:2]))
            x1, y1 = map(int, self.iview.map(*self.pts[2:]))
            i = -1
            if (mx - x0) ** 2 + (my - y0) ** 2 < 8 ** 2:
                i = 0
            elif (mx - x1) ** 2 + (my - y1) ** 2 < 8 ** 2:
                i = 2
            else:
                xx, yy = map(int, self.iview.revmap(mx, my))
                self.pts = [xx, yy, xx, yy]
                i = 2

            def tracking(x, y):
                if x is not None:
                    self.pts[i:i + 2] = list(map(int, self.iview.revmap(x, y)))
                    if app.keyboardModifiers() == Qt.ShiftModifier:
                        if abs(self.pts[i] - self.pts[0 + 2 - i]) < abs(
                            self.pts[i + 1] - self.pts[1 + 3 - (i + 1)]):
                            self.pts[i] = self.pts[0 + 2 - i]
                        else:
                            self.pts[i + 1] = self.pts[1 + 3 - (i + 1)]
            return tracking

    def key(self, e):
        if e.key() == Qt.Key_Minus:
            if MeasuringTool.k < 100:
                MeasuringTool.k += 1
        if e.key() == Qt.Key_Plus:
            if MeasuringTool.k > 4:
                MeasuringTool.k -= 1
        return Tool.key(self, e)


class TextTool(Tool):

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.text = ''
        x0, y0 = map(int, self.iview.revmap(0, 0))
        x1, y1 = map(int, self.iview.revmap(self.iview.width(), self.iview.
            height()))
        self.pos = [(x0 + x1) // 2, (y0 + y1) // 2]
        self.blink = int(time.time() * 2) + 1
        QTimer.singleShot(250, lambda : self.iview.update())

    def draw(self, dc):
        x, y = self.pos
        dc.save()
        dc.translate(self.iview.tx, self.iview.ty)
        dc.scale(self.iview.scale, self.iview.scale)
        dc.setFont(self.font)
        dc.setPen(QPen(self.colors[self.color]))
        caret = int(time.time() * 2) - self.blink & 1
        dc.drawText(x, y, self.text + '|' if caret else self.text)
        dc.restore()
        Tool.text(self, dc,
            'Text tool: ctrl-1...9=Font size, ctrl-R/G/B color, ENTER=Accept')
        QTimer.singleShot(250, lambda : self.iview.update())

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            cur = list(map(int, self.iview.revmap(mx, my)))

            def tracking(mx, my):
                if mx is not None:
                    x, y = map(int, self.iview.revmap(mx, my))
                    self.pos[0] += x - cur[0]
                    self.pos[1] += y - cur[1]
                    cur[0] = x
                    cur[1] = y
            return tracking

    def key(self, e):
        self.blink = int(time.time() * 2) + 1
        if e.key() == Qt.Key_Backspace:
            self.text = self.text[:-1]
            return True
        elif e.key() == Qt.Key_Escape:
            return Tool.key(self, e)
        elif e.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.iview.to32bpp()
            x, y = self.pos
            dc = QPainter(self.iview.img)
            dc.setFont(self.font)
            dc.setPen(QPen(self.colors[self.color]))
            dc.drawText(x, y, self.text)
            dc = None
            self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        elif e.text() and e.modifiers() != Qt.ControlModifier:
            self.text += unicode(e.text())
            return True
        return Tool.key(self, e)


class MyDialog(QDialog):

    def __init__(self, parent, imglist):
        QDialog.__init__(self, parent, Qt.Window)
        self.ws = Viewer(self, imglist)
        L = QVBoxLayout(self)
        L.setContentsMargins(0, 0, 0, 0)
        L.addWidget(self.ws)
        self.setModal(True)
        self.show()


<mask token>


class SortWrapper:

    def __init__(self, x):
        self.x = x

    def __lt__(self, other):
        if type(self.x) is type(other.x):
            return self.x < other.x
        else:
            return id(type(self.x)) < id(type(other.x))


<mask token>

<mask token>


class Viewer(QW):
    <mask token>

    def load(self, index):
        self.imglist[:] = self.imglist[index:] + self.imglist[:index]
        self.img = QImage(self.imglist[0])
        self.scaled = None
        self.imgmtime = os.stat(self.imglist[0]).st_mtime
        self.parent().setWindowTitle(self.mkTitle())
        self.update()
        gc.collect()
    <mask token>

    def resizeEvent(self, e):
        self.zoomAll()
        QW.resizeEvent(self, e)

    def zoomAll(self):
        self.scale = min(float(self.width()) / max(1, self.img.width()), 
            float(self.height()) / max(1, self.img.height()))
        self.tx = (self.width() - self.img.width() * self.scale) / 2
        self.ty = (self.height() - self.img.height() * self.scale) / 2
        self.update()

    def keyPressEvent(self, e):
        for t in self.tools:
            if t.key(e):
                self.update()
                return
        if e.key() == Qt.Key_Right:
            w, h = self.img.width(), self.img.height()
            self.load(1)
            if (w, h) != (self.img.width(), self.img.height()):
                self.zoomAll()
        elif e.key() == Qt.Key_Left:
            w, h = self.img.width(), self.img.height()
            self.load(len(self.imglist) - 1)
            if (w, h) != (self.img.width(), self.img.height()):
                self.zoomAll()
        elif e.key() == Qt.Key_1:
            self.setZoom(1)
        elif e.key() == Qt.Key_2:
            self.setZoom(2)
        elif e.key() == Qt.Key_3:
            self.setZoom(3)
        elif e.key() == Qt.Key_4:
            self.setZoom(4)
        elif e.key() in (Qt.Key_Home, Qt.Key_0):
            self.zoomAll()
        elif e.key() in (Qt.Key_Escape, Qt.Key_Q):
            self.parent().deleteLater()
        elif e.key() == Qt.Key_U:
            w, h = self.img.width(), self.img.height()
            self.load(0)
            if (w, h) != (self.img.width(), self.img.height()):
                self.zoomAll()
        elif e.key() in (Qt.Key_F, Qt.Key_Return, Qt.Key_Enter):
            self.fullscreen = not self.fullscreen
            if self.fullscreen:
                self.parent().showFullScreen()
            else:
                self.parent().showNormal()
        elif e.key() == Qt.Key_R:
            img = QImage(self.img.height(), self.img.width(), QImage.
                Format_ARGB32)
            img.fill(0)
            dc = QPainter(img)
            dc.rotate(90)
            dc.drawImage(0, -self.img.height(), self.img)
            dc = None
            self.img = img
            self.zoomAll()
            gc.collect()
        elif e.key() == Qt.Key_C:
            self.tools = [CropTool(self)]
        elif e.key() == Qt.Key_E:
            self.tools = [EllipseTool(self)]
        elif e.key() == Qt.Key_P:
            self.tools = [PixColorTool(self)]
        elif e.key() == Qt.Key_A:
            self.tools = [ArrowTool(self)]
        elif e.key() == Qt.Key_M:
            self.tools = [MeasuringTool(self)]
        elif e.key() == Qt.Key_Y:
            self.to32bpp()
            b = self.img.bits()
            b.setsize(self.img.height() * self.img.bytesPerLine())
            b.setwriteable(True)
            for i in range(0, self.img.height() * self.img.bytesPerLine(), 4):
                b[i] = b[i + 2] = b[i + 1]
            self.scaled = None
        elif e.key() == Qt.Key_T:
            self.tools = [TextTool(self)]
        elif e.key() == Qt.Key_S:
            fname = str(QFileDialog.getSaveFileName(self, 'Save image as',
                self.imglist[0]))
            if fname != '':
                supported_extensions = [bytes(x).decode() for x in
                    QImageWriter.supportedImageFormats()]
                if re.match('^.*\\.(' + '|'.join(supported_extensions) +
                    ')$', fname, re.IGNORECASE):
                    if self.img.save(fname):
                        if fname not in self.imglist:
                            self.imglist.insert(0, fname)
                            self.load(0)
                    else:
                        QMessageBox.critical(self,
                            'IView: Error saving image file',
                            'There was a problem saving the image file.')
                elif fname[-4:].lower() == '.pgm':
                    try:
                        self.to32bpp()
                        data = self.img.bits().asstring(self.img.height() *
                            self.img.bytesPerLine())
                        f = open(fname, 'wb')
                        f.write(('P5\n%i %i 255\n' % (self.img.width(),
                            self.img.height())).encode('ascii'))
                        for y in range(self.img.height()):
                            f.write(data[y * self.img.width() * 4 + 1:(y + 
                                1) * self.img.width() * 4 + 1:4])
                        f.close()
                        if fname not in self.imglist:
                            self.imglist.insert(0, fname)
                            self.load(0)
                    except Exception:
                        QMessageBox.critical(self,
                            'IView: Error saving image file',
                            'There was a problem saving the image file.')
                else:
                    QMessageBox.critical(self,
                        'IView: Unable to save image file', 
                        'The requested image format is not supported, supported extensions are '
                         + ', '.join(supported_extensions))
        elif e.key() == Qt.Key_X:
            subprocess.Popen([os.getenv('EDITOR'), __file__])
        elif e.key() == Qt.Key_Delete:
            if QMessageBox.question(self, 'Image deletion',
                'Are you sure you want to delete this image?', QMessageBox.
                Yes | QMessageBox.No) == QMessageBox.Yes:
                try:
                    os.unlink(self.imglist[0])
                except OSError:
                    QMessageBox.information(self, 'IView',
                        'Error deleting image file')
                    return
                self.scaled = None
                self.imglist.pop(0)
                if len(self.imglist) == 0:
                    sys.exit(0)
                self.load(0)
                self.zoomAll()
        elif e.key() == Qt.Key_F2:
            name = self.imglist[0]
            d = QDialog(self)
            d.setWindowTitle('Rename image')
            vb = QVBoxLayout(d)
            l = QLabel('New name')
            vb.addWidget(l)
            sle = QLineEdit(d)
            vb.addWidget(sle)
            hb = QHBoxLayout()
            vb.addLayout(hb)
            hb.addWidget(QWidget(d))
            ok = QPushButton('OK', d)
            hb.addWidget(ok)
            ok.setFixedWidth(80)
            cancel = QPushButton('Cancel', d)
            hb.addWidget(cancel)
            cancel.setFixedWidth(80)
            hb.addWidget(QWidget(d))
            sle.setText(name)
            basename_start = 1 + name.rindex('/') if '/' in name else 0
            if '.' in name[basename_start:]:
                sle.setSelection(basename_start, name[basename_start:].
                    rindex('.'))
            sle.setFocus()
            width = sle.fontMetrics().boundingRect(name).width()
            sle.setMinimumWidth(width * 9 // 8)
            d.connect(cancel, SIGNAL('clicked()'), d.reject)
            d.connect(ok, SIGNAL('clicked()'), d.accept)
            if d.exec_() == QDialog.Accepted:
                try:
                    s = sle.text()
                    os.rename(self.imglist[0], unicode(s))
                    self.imglist[0] = unicode(s)
                    self.load(0)
                except Exception:
                    QMessageBox.critical(self,
                        'IView: Unable to rename the file',
                        'There was a problem renaming the file')
        elif e.key() == Qt.Key_O:
            self.titleOverlay = not self.titleOverlay
        elif e.key() == Qt.Key_F1:
            QMessageBox.information(self, 'IView',
                '<center><h1>IView</h1>A simple image viewer<br>by Andrea "6502" Griffini</center><ul>  <li> <b>F1</b> This help message</li>  <li> <b>Mouse drag</b> Pan</li>  <li> <b>Mouse wheel</b> Zoom</li>  <li> <b>Left/Right</b> Previous/next image</li>  <li> <b>Home</b> Zoom fit</li>  <li> <b>1/2/3/4</b> Display resolution to Nx</li>  <li> <b>F or Enter</b> Fullscreen mode</li>  <li> <b>O</b> Toggle file name overlay in fullscreen mode</li>  <li> <b>A</b> Arrow markup mode</li>  <li> <b>P</b> Pixel inspection mode</li>  <li> <b>M</b> Measure mode</li>  <li> <b>E</b> Ellipse markup mode</li>  <li> <b>T</b> Text markup mode</li>  <li> <b>C</b> Cropping/yanking mode</li>  <li> <b>Y</b> Convert to grayscale</li>  <li> <b>R</b> Rotate image 90 degrees</li>  <li> <b>U</b> Reload original image</li>  <li> <b>S</b> Save modified image</li>  <li> <b>F2</b> Rename current file</li>  <li> <b>Del</b> Delete current file</li>  <li> <b>X</b> Customize</li></ul>'
                )
        self.update()

    def sizeHint(self):
        return QSize(900, 700)

    def setZoom(self, sf):
        if self.pos:
            mx, my = self.pos[0] * self.scale + self.tx, self.pos[1
                ] * self.scale + self.ty
            self.scale = sf
            self.tx = mx - self.pos[0] * self.scale
            self.ty = my - self.pos[1] * self.scale
        else:
            mx, my = self.width() / 2, self.height() / 2
            lx, ly = (mx - self.tx) / self.scale, (my - self.ty) / self.scale
            self.scale = sf
            self.tx = mx - lx * self.scale
            self.ty = my - ly * self.scale
        self.update()
    <mask token>

    def paintEvent(self, e):
        dc = QPainter(self)
        w, h = self.width(), self.height()
        dc.fillRect(0, 0, w, h, QBrush(QColor(64, 64, 64)))
        dc.fillRect(self.tx + 1, self.ty + 1, self.img.width() * self.scale -
            2, self.img.height() * self.scale - 2, self.bbrush)
        if self.scale > 1:
            dc.save()
            dc.translate(self.tx, self.ty)
            dc.scale(self.scale, self.scale)
            dc.drawImage(0, 0, self.img)
            dc.restore()
        else:
            if self.scaled is None or self.scaled[0] != self.scale:
                sw = int(self.img.width() * self.scale)
                sh = int(self.img.height() * self.scale)
                simg = self.img.scaled(QSize(sw, sh), Qt.IgnoreAspectRatio,
                    Qt.SmoothTransformation)
                self.scaled = [self.scale, simg]
            dc.drawImage(self.tx, self.ty, self.scaled[1])
        y = 16
        dc.setFont(QFont('sans-serif', 11))
        if len(self.tools) == 0 and self.fullscreen and self.titleOverlay:
            self.statusText(dc, self.mkTitle())
        for t in self.tools:
            t.draw(dc)
    <mask token>
    <mask token>

    def mouseMoveEvent(self, e):
        self.pos = (e.x() - self.tx) / self.scale, (e.y() - self.ty
            ) / self.scale
        if self.tracking:
            self.tracking(e.x(), e.y())
            self.update()

    def mouseReleaseEvent(self, e):
        if self.tracking:
            self.tracking(None, None)
            self.tracking = None

    def map(self, x, y):
        return self.tx + x * self.scale, self.ty + y * self.scale

    def revmap(self, x, y):
        return (x - self.tx) / self.scale, (y - self.ty) / self.scale

    def to32bpp(self):
        if self.img.format() != QImage.Format_ARGB32:
            self.img = self.img.convertToFormat(QImage.Format_ARGB32)
            self.scaled = None
            self.update()

    def statusText(self, dc, msg):
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.drawText(4, 20, msg)
        dc.drawText(7, 20, msg)
        dc.drawText(5, 19, msg)
        dc.drawText(5, 21, msg)
        dc.setPen(QPen(QColor(0, 255, 0)))
        dc.drawText(5, 20, msg)


class Tool(object):
    pen = 4
    color = 0
    colors = [QColor(255, 0, 0), QColor(0, 255, 0), QColor(0, 0, 255)]

    def __init__(self, iview):
        self.iview = iview
        self.font = QFont('sans-serif')
        self.font.setPixelSize(self.pen * 8 + 11)

    def key(self, e):
        if e.key() == Qt.Key_R:
            Tool.color = 0
            self.iview.update()
            return True
        if e.key() == Qt.Key_G:
            Tool.color = 1
            self.iview.update()
            return True
        if e.key() == Qt.Key_B:
            Tool.color = 2
            self.iview.update()
            return True
        if e.key() >= Qt.Key_1 and e.key() <= Qt.Key_9:
            Tool.pen = (e.key() - Qt.Key_1) * 2
            self.font.setPixelSize(self.pen * 8 + 11)
            self.iview.update()
            return True
        if e.key() == Qt.Key_Escape:
            self.iview.tools.remove(self)
            return True

    def text(self, dc, msg):
        return self.iview.statusText(dc, msg)


class RectTool(Tool):

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.geo = [0, 0, 0, 0]

    def rect(self):
        x0, y0, x1, y1 = self.geo
        x0, y0 = map(int, self.iview.map(self.geo[0], self.geo[1]))
        x1, y1 = map(int, self.iview.map(self.geo[2], self.geo[3]))
        if x0 > x1:
            x0, x1 = x1, x0
        if y0 > y1:
            y0, y1 = y1, y0
        return x0, y0, x1, y1

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dc.setBrush(QBrush())
        dc.setPen(QPen(QColor(0, 0, 0), 4))
        dc.drawRect(QRectF(x0, y0, x1 - x0, y1 - y0))
        dc.setPen(QPen(QColor(255, 255, 255), 2, Qt.DashLine))
        dc.drawRect(QRectF(x0, y0, x1 - x0, y1 - y0))
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.setBrush(QColor(255, 0, 0))
        for x, y in ((x0, y0), (x1, y0), (x0, y1), (x1, y1)):
            dc.drawEllipse(QRectF(x - 4, y - 4, 8, 8))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0, x1, y1 = self.geo
            x0, y0 = map(int, self.iview.map(self.geo[0], self.geo[1]))
            x1, y1 = map(int, self.iview.map(self.geo[2], self.geo[3]))
            f = 0
            if near(mx, x0, 8) and between(my, y0, y1, 8):
                f |= 1
            if near(my, y0, 8) and between(mx, x0, x1, 8):
                f |= 2
            if near(mx, x1, 8) and between(my, y0, y1, 8):
                f |= 4
            if near(my, y1, 8) and between(mx, x0, x1, 8):
                f |= 8
            if f == 0 and between(mx, x0, x1) and between(my, y0, y1):
                f = 15
            else:
                if f & 5 == 5:
                    f ^= 4
                if f & 10 == 10:
                    f ^= 8
            if f == 0:
                xx, yy = map(int, self.iview.revmap(mx, my))
                self.geo = [xx, yy, xx, yy]
                f = 4 + 8
            if f:
                cur = list(map(int, self.iview.revmap(mx, my)))

                def tracking(x, y):
                    if x is not None:
                        x, y = map(int, self.iview.revmap(x, y))
                        dx, dy = x - cur[0], y - cur[1]
                        cur[0], cur[1] = x, y
                        if f & 1:
                            self.geo[0] += dx
                        if f & 2:
                            self.geo[1] += dy
                        if f & 4:
                            self.geo[2] += dx
                        if f & 8:
                            self.geo[3] += dy
                        self.geo[0] = max(0, min(self.geo[0], self.iview.
                            img.width()))
                        self.geo[1] = max(0, min(self.geo[1], self.iview.
                            img.height()))
                        self.geo[2] = max(0, min(self.geo[2], self.iview.
                            img.width()))
                        self.geo[3] = max(0, min(self.geo[3], self.iview.
                            img.height()))
                return tracking


class CropTool(RectTool):

    def __init__(self, iview):
        RectTool.__init__(self, iview)

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dark = QBrush(QColor(0, 0, 0, 128))
        w, h = self.iview.width(), self.iview.height()
        if y0 > 0:
            dc.fillRect(0, 0, w, y0, dark)
        if x0 > 0:
            dc.fillRect(0, y0, x0, y1 - y0, dark)
        if x1 < w:
            dc.fillRect(x1, y0, w - x1, y1 - y0, dark)
        if y1 < h:
            dc.fillRect(0, y1, w, h - y1, dark)
        RectTool.draw(self, dc)
        self.text(dc, 'Crop: (%i, %i) - (%i, %i) = %i x %i (Y to copy)' % (
            self.geo[0], self.geo[1], self.geo[2], self.geo[3], abs(self.
            geo[2] - self.geo[0]), abs(self.geo[3] - self.geo[1])))

    def key(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_C, Qt.Key_Enter, Qt.Key_Y):
            x0, y0, x1, y1 = self.geo
            if x0 > x1:
                x0, x1 = x1, x0
            if y0 > y1:
                y0, y1 = y1, y0
            x0, y0 = max(0, x0), max(0, y0)
            x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.
                height(), y1)
            if x0 < x1 and y0 < y1:
                cropped = self.iview.img.copy(x0, y0, x1 - x0, y1 - y0)
                if e.key() == Qt.Key_Y:
                    QApplication.clipboard().setImage(cropped)
                else:
                    self.iview.img = cropped
                    self.iview.scaled = None
                    self.iview.zoomAll()
            self.iview.tools.remove(self)
            return True
        else:
            return Tool.key(self, e)


class EllipseTool(RectTool):

    def __init__(self, iview):
        RectTool.__init__(self, iview)

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dc.setPen(QPen(self.colors[self.color], self.pen * self.iview.scale))
        dc.setBrush(QBrush())
        dc.drawEllipse(x0, y0, x1 - x0, y1 - y0)
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.setBrush(QColor(255, 0, 0))
        for x, y in (((x0 + x1) / 2, y0), ((x0 + x1) / 2, y1), (x0, (y0 +
            y1) / 2), (x1, (y0 + y1) / 2), (x0, y0), (x1, y0), (x0, y1), (
            x1, y1)):
            dc.drawEllipse(QRectF(x - 4, y - 4, 8, 8))
        self.text(dc, 'Ellipse drawing mode: 1-9=thickness, r/g/b=color')

    def key(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_E, Qt.Key_Enter):
            x0, y0, x1, y1 = self.geo
            if x0 > x1:
                x0, x1 = x1, x0
            if y0 > y1:
                y0, y1 = y1, y0
            x0, y0 = max(0, x0), max(0, y0)
            x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.
                height(), y1)
            if x0 < x1 and y0 < y1:
                self.iview.to32bpp()
                dc = QPainter(self.iview.img)
                dc.setPen(QPen(self.colors[self.color], self.pen))
                dc.setBrush(QBrush())
                dc.drawEllipse(x0, y0, x1 - x0, y1 - y0)
                dc = None
                self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        else:
            return Tool.key(self, e)


class PixColorTool(Tool):

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.p = None

    def draw(self, dc):
        if self.p is None:
            self.text(dc, 'Pixel inspection; click for color/position')
        else:
            x, y = self.p
            col = self.iview.img.pixel(x, y)
            a = col >> 24 & 255
            r = col >> 16 & 255
            g = col >> 8 & 255
            b = col >> 0 & 255
            c0 = QBrush(QColor(0, 0, 0))
            c1 = QBrush(QColor(255, 255, 255))
            x0, y0 = self.iview.map(x, y)
            x1, y1 = self.iview.map(x + 1, y + 1)
            h = (y1 - y0) * 10
            s = (y1 - y0) * 2
            dc.fillRect(x0 - 1, y0 - h - s - 1, x1 - x0 + 2, h + 2, c0)
            dc.fillRect(x0 - 1, y1 + s - 1, x1 - x0 + 2, h + 2, c0)
            dc.fillRect(x0 - h - s - 1, y0 - 1, h + 2, y1 - y0 + 2, c0)
            dc.fillRect(x1 + s - 1, y0 - 1, h + 2, y1 - y0 + 2, c0)
            dc.fillRect(x0, y0 - h - s, x1 - x0, h, c1)
            dc.fillRect(x0, y1 + s, x1 - x0, h, c1)
            dc.fillRect(x0 - h - s, y0, h, y1 - y0, c1)
            dc.fillRect(x1 + s, y0, h, y1 - y0, c1)
            self.text(dc, 
                'Pixel(x=%i, y=%i): rgb(%i, %i, %i), #%02x%02x%02x, alpha=%i' %
                (x, y, r, g, b, r, g, b, a))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x, y = map(int, self.iview.revmap(mx, my))
            if 0 <= x < self.iview.img.width(
                ) and 0 <= y < self.iview.img.height():
                self.p = x, y
                self.iview.update()


class ArrowTool(Tool):
    k = 8

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.pts = [0, 0, 0, 0]

    def draw(self, dc):
        x0, y0 = map(int, self.iview.map(*self.pts[:2]))
        x1, y1 = map(int, self.iview.map(*self.pts[2:]))
        dx, dy = (x1 - x0) // self.k, (y1 - y0) // self.k
        nx, ny = (y1 - y0) // self.k // 2, (x0 - x1) // self.k // 2
        dc.setPen(QPen(self.colors[self.color], self.pen * self.iview.scale,
            Qt.SolidLine, Qt.RoundCap))
        dc.setBrush(QBrush())
        dc.drawLine(x0, y0, x1, y1)
        dc.drawLine(x1 - dx - nx, y1 - dy - ny, x1, y1)
        dc.drawLine(x1 - dx + nx, y1 - dy + ny, x1, y1)
        self.text(dc,
            'Arrow drawing mode, 1-9=thickness, r/g/b=color, +/- changes head')

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0 = map(int, self.iview.map(*self.pts[:2]))
            x1, y1 = map(int, self.iview.map(*self.pts[2:]))
            i = -1
            if (mx - x0) ** 2 + (my - y0) ** 2 < 8 ** 2:
                i = 0
            elif (mx - x1) ** 2 + (my - y1) ** 2 < 8 ** 2:
                i = 2
            else:
                xx, yy = self.iview.revmap(mx, my)
                self.pts = [xx, yy, xx, yy]
                i = 2

            def tracking(x, y):
                if x is not None:
                    self.pts[i:i + 2] = list(map(int, self.iview.revmap(x, y)))
            return tracking

    def key(self, e):
        if e.key() == Qt.Key_Minus:
            if ArrowTool.k < 100:
                ArrowTool.k += 1
        if e.key() == Qt.Key_Plus:
            if ArrowTool.k > 4:
                ArrowTool.k -= 1
        if e.key() in (Qt.Key_Return, Qt.Key_A, Qt.Key_Enter):
            self.iview.to32bpp()
            x0, y0, x1, y1 = map(int, self.pts)
            dx, dy = (x1 - x0) // self.k, (y1 - y0) // self.k
            nx, ny = (y1 - y0) // self.k // 2, (x0 - x1) // self.k // 2
            dc = QPainter(self.iview.img)
            dc.setPen(QPen(self.colors[self.color], self.pen, Qt.SolidLine,
                Qt.RoundCap))
            dc.setBrush(QBrush())
            dc.drawLine(x0, y0, x1, y1)
            dc.drawLine(x1 - dx - nx, y1 - dy - ny, x1, y1)
            dc.drawLine(x1 - dx + nx, y1 - dy + ny, x1, y1)
            dc = None
            self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        return Tool.key(self, e)


class MeasuringTool(Tool):
    k = 8

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.pts = [0, 0, 0, 0]

    def draw(self, dc):
        x0, y0 = map(int, self.iview.map(*self.pts[:2]))
        x1, y1 = map(int, self.iview.map(*self.pts[2:]))
        dx, dy = (x1 - x0) // self.k, (y1 - y0) // self.k
        nx, ny = (y1 - y0) // self.k // 2, (x0 - x1) // self.k // 2
        dc.setPen(QPen(self.colors[self.color]))
        dc.setBrush(QBrush())
        dc.drawLine(x0, y0, x1, y1)
        dc.drawLine(x1 - dx - nx, y1 - dy - ny, x1, y1)
        dc.drawLine(x1 - dx + nx, y1 - dy + ny, x1, y1)
        dc.drawLine(x0 + dx - nx, y0 + dy - ny, x0, y0)
        dc.drawLine(x0 + dx + nx, y0 + dy + ny, x0, y0)
        dc.drawLine(x0 + nx, y0 + ny, x0 - nx, y0 - ny)
        dc.drawLine(x1 + nx, y1 + ny, x1 - nx, y1 - ny)
        x0, y0, x1, y1 = self.pts
        dx, dy = x1 - x0, y1 - y0
        L = (dx ** 2 + dy ** 2) ** 0.5
        angle = 0 if L == 0 else math.atan2(dy, dx) * 180 / math.pi
        self.text(dc, 
            'Measuring mode: (%i,%i)-(%i,%i) : %ix%i = %0.3fpx, %0.3f degrees'
             % (x0, y0, x1, y1, dx, dy, L, angle))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0 = map(int, self.iview.map(*self.pts[:2]))
            x1, y1 = map(int, self.iview.map(*self.pts[2:]))
            i = -1
            if (mx - x0) ** 2 + (my - y0) ** 2 < 8 ** 2:
                i = 0
            elif (mx - x1) ** 2 + (my - y1) ** 2 < 8 ** 2:
                i = 2
            else:
                xx, yy = map(int, self.iview.revmap(mx, my))
                self.pts = [xx, yy, xx, yy]
                i = 2

            def tracking(x, y):
                if x is not None:
                    self.pts[i:i + 2] = list(map(int, self.iview.revmap(x, y)))
                    if app.keyboardModifiers() == Qt.ShiftModifier:
                        if abs(self.pts[i] - self.pts[0 + 2 - i]) < abs(
                            self.pts[i + 1] - self.pts[1 + 3 - (i + 1)]):
                            self.pts[i] = self.pts[0 + 2 - i]
                        else:
                            self.pts[i + 1] = self.pts[1 + 3 - (i + 1)]
            return tracking

    def key(self, e):
        if e.key() == Qt.Key_Minus:
            if MeasuringTool.k < 100:
                MeasuringTool.k += 1
        if e.key() == Qt.Key_Plus:
            if MeasuringTool.k > 4:
                MeasuringTool.k -= 1
        return Tool.key(self, e)


class TextTool(Tool):

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.text = ''
        x0, y0 = map(int, self.iview.revmap(0, 0))
        x1, y1 = map(int, self.iview.revmap(self.iview.width(), self.iview.
            height()))
        self.pos = [(x0 + x1) // 2, (y0 + y1) // 2]
        self.blink = int(time.time() * 2) + 1
        QTimer.singleShot(250, lambda : self.iview.update())

    def draw(self, dc):
        x, y = self.pos
        dc.save()
        dc.translate(self.iview.tx, self.iview.ty)
        dc.scale(self.iview.scale, self.iview.scale)
        dc.setFont(self.font)
        dc.setPen(QPen(self.colors[self.color]))
        caret = int(time.time() * 2) - self.blink & 1
        dc.drawText(x, y, self.text + '|' if caret else self.text)
        dc.restore()
        Tool.text(self, dc,
            'Text tool: ctrl-1...9=Font size, ctrl-R/G/B color, ENTER=Accept')
        QTimer.singleShot(250, lambda : self.iview.update())

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            cur = list(map(int, self.iview.revmap(mx, my)))

            def tracking(mx, my):
                if mx is not None:
                    x, y = map(int, self.iview.revmap(mx, my))
                    self.pos[0] += x - cur[0]
                    self.pos[1] += y - cur[1]
                    cur[0] = x
                    cur[1] = y
            return tracking

    def key(self, e):
        self.blink = int(time.time() * 2) + 1
        if e.key() == Qt.Key_Backspace:
            self.text = self.text[:-1]
            return True
        elif e.key() == Qt.Key_Escape:
            return Tool.key(self, e)
        elif e.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.iview.to32bpp()
            x, y = self.pos
            dc = QPainter(self.iview.img)
            dc.setFont(self.font)
            dc.setPen(QPen(self.colors[self.color]))
            dc.drawText(x, y, self.text)
            dc = None
            self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        elif e.text() and e.modifiers() != Qt.ControlModifier:
            self.text += unicode(e.text())
            return True
        return Tool.key(self, e)


class MyDialog(QDialog):

    def __init__(self, parent, imglist):
        QDialog.__init__(self, parent, Qt.Window)
        self.ws = Viewer(self, imglist)
        L = QVBoxLayout(self)
        L.setContentsMargins(0, 0, 0, 0)
        L.addWidget(self.ws)
        self.setModal(True)
        self.show()


<mask token>


class SortWrapper:

    def __init__(self, x):
        self.x = x

    def __lt__(self, other):
        if type(self.x) is type(other.x):
            return self.x < other.x
        else:
            return id(type(self.x)) < id(type(other.x))


<mask token>

#!/usr/bin/python
u"""
test cover flow, TODO
"""

# The MIT License (MIT)
#
# Copyright (c) 2014 Andrea Griffini
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import sys, gc, os, time, math, re, subprocess
from PyQt4.Qt import *

try:
    unicode
except:
    unicode = str

QW = QWidget

def between(x, a, b, eps=0):
    return min(a, b)-eps <= x <= max(a, b)+eps

def near(x, a, eps):
    return a-eps <= x <= a+eps

class Viewer(QW):
    def __init__(self, parent, imglist):
        QW.__init__(self, parent)

        bg = QImage(32, 32, QImage.Format_ARGB32)
        dc = QPainter(bg)
        dc.fillRect(0, 0, 32, 32, QColor(96, 96, 96))
        dc.fillRect(0, 0, 16, 16, QColor(128, 128, 128))
        dc.fillRect(16, 16, 16, 16, QColor(128, 128, 128))
        dc = None
        self.bbrush = QBrush(bg)

        self.setFocusPolicy(Qt.StrongFocus)
        self.setFocus()
        self.imglist = imglist[:]
        self.setMouseTracking(True)
        self.fullscreen = False
        self.titleOverlay = False
        self.timer = QTimer(self)
        self.connect(self.timer, SIGNAL("timeout()"), self.checkmtime)
        self.timer.start(250)
        self.pos = None
        self.tracking = None
        self.tools = []
        self.load(0)

    def load(self, index):
        self.imglist[:] = self.imglist[index:] + self.imglist[:index]
        self.img = QImage(self.imglist[0])
        self.scaled = None
        self.imgmtime = os.stat(self.imglist[0]).st_mtime
        self.parent().setWindowTitle(self.mkTitle())
        self.update()
        gc.collect()

    def checkmtime(self):
        imgmtime = os.stat(self.imglist[0]).st_mtime
        if imgmtime != self.imgmtime:
            ow = self.img.width()
            oh = self.img.height()
            self.load(0)
            if self.img.width() != ow or self.img.height() != oh:
                self.zoomAll()

    def resizeEvent(self, e):
        self.zoomAll()
        QW.resizeEvent(self, e)

    def zoomAll(self):
        self.scale = min(float(self.width()) / max(1, self.img.width()),
                         float(self.height()) / max(1, self.img.height()))
        self.tx = (self.width() - self.img.width()*self.scale) / 2
        self.ty = (self.height() - self.img.height()*self.scale) / 2
        self.update()

    def keyPressEvent(self, e):
        for t in self.tools:
            if t.key(e):
                self.update()
                return
        if e.key() == Qt.Key_Right:
            w, h = self.img.width(), self.img.height()
            self.load(1)
            if (w, h) != (self.img.width(), self.img.height()):
                self.zoomAll()
        elif e.key() == Qt.Key_Left:
            w, h = self.img.width(), self.img.height()
            self.load(len(self.imglist) - 1)
            if (w, h) != (self.img.width(), self.img.height()):
                self.zoomAll()
        elif e.key() == Qt.Key_1:
            self.setZoom(1)
        elif e.key() == Qt.Key_2:
            self.setZoom(2)
        elif e.key() == Qt.Key_3:
            self.setZoom(3)
        elif e.key() == Qt.Key_4:
            self.setZoom(4)
        elif e.key() in (Qt.Key_Home, Qt.Key_0):
            self.zoomAll()
        elif e.key() in (Qt.Key_Escape, Qt.Key_Q):
            self.parent().deleteLater()
        elif e.key() == Qt.Key_U:
            w, h = self.img.width(), self.img.height()
            self.load(0)
            if (w, h) != (self.img.width(), self.img.height()):
                self.zoomAll()
        elif e.key() in (Qt.Key_F, Qt.Key_Return, Qt.Key_Enter):
            self.fullscreen = not self.fullscreen
            if self.fullscreen:
                self.parent().showFullScreen()
            else:
                self.parent().showNormal()
        elif e.key() == Qt.Key_R:
            img = QImage(self.img.height(), self.img.width(), QImage.Format_ARGB32)
            img.fill(0)
            dc = QPainter(img)
            dc.rotate(90)
            dc.drawImage(0, -self.img.height(), self.img)
            dc = None
            self.img = img
            self.zoomAll()
            gc.collect()
        elif e.key() == Qt.Key_C:
            self.tools = [CropTool(self)]
        elif e.key() == Qt.Key_E:
            self.tools = [EllipseTool(self)]
        elif e.key() == Qt.Key_P:
            self.tools = [PixColorTool(self)]
        elif e.key() == Qt.Key_A:
            self.tools = [ArrowTool(self)]
        elif e.key() == Qt.Key_M:
            self.tools = [MeasuringTool(self)]
        elif e.key() == Qt.Key_Y:
            self.to32bpp()
            b = self.img.bits()
            b.setsize(self.img.height() * self.img.bytesPerLine())
            b.setwriteable(True)
            for i in range(0, self.img.height()*self.img.bytesPerLine(), 4):
                b[i] = b[i+2] = b[i+1]
            self.scaled = None
        elif e.key() == Qt.Key_T:
            self.tools = [TextTool(self)]
        elif e.key() == Qt.Key_S:
            fname = str(QFileDialog.getSaveFileName(self, "Save image as", self.imglist[0]))
            if fname != "":
                supported_extensions = [bytes(x).decode() for x in QImageWriter.supportedImageFormats()]
                if re.match("^.*\\.(" + "|".join(supported_extensions) + ")$", fname, re.IGNORECASE):
                    if self.img.save(fname):
                        if fname not in self.imglist:
                            self.imglist.insert(0, fname)
                            self.load(0)
                    else:
                        QMessageBox.critical(self, "IView: Error saving image file",
                                             "There was a problem saving the image file.")
                elif fname[-4:].lower() == ".pgm":
                    # Hand-made PGM writer
                    try:
                        self.to32bpp()
                        data = self.img.bits().asstring(self.img.height() * self.img.bytesPerLine())
                        f = open(fname, "wb")
                        f.write(("P5\n%i %i 255\n" % (self.img.width(), self.img.height()))
                                .encode("ascii"))
                        for y in range(self.img.height()):
                            f.write(data[y*self.img.width()*4+1:(y+1)*self.img.width()*4+1:4])
                        f.close()
                        if fname not in self.imglist:
                            self.imglist.insert(0, fname)
                            self.load(0)
                    except Exception:
                        QMessageBox.critical(self, "IView: Error saving image file",
                                             "There was a problem saving the image file.")
                else:
                    QMessageBox.critical(self, "IView: Unable to save image file",
                                         "The requested image format is not supported, supported extensions are "+
                                         ", ".join(supported_extensions))
        elif e.key() == Qt.Key_X:
            subprocess.Popen([os.getenv("EDITOR"), __file__])
        elif e.key() == Qt.Key_Delete:
            if QMessageBox.question(self,
                                    "Image deletion",
                                    "Are you sure you want to delete this image?",
                                    QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes:
                try:
                    os.unlink(self.imglist[0])
                except OSError:
                    QMessageBox.information(self, "IView",
                                            "Error deleting image file")
                    return
                self.scaled = None
                self.imglist.pop(0)
                if len(self.imglist) == 0:
                    sys.exit(0)
                self.load(0)
                self.zoomAll()
        elif e.key() == Qt.Key_F2:
            name = self.imglist[0]
            d = QDialog(self)
            d.setWindowTitle("Rename image")
            vb = QVBoxLayout(d)
            l = QLabel("New name"); vb.addWidget(l)
            sle = QLineEdit(d); vb.addWidget(sle)
            hb = QHBoxLayout(); vb.addLayout(hb);
            hb.addWidget(QWidget(d))
            ok = QPushButton("OK", d); hb.addWidget(ok)
            ok.setFixedWidth(80)
            cancel = QPushButton("Cancel", d); hb.addWidget(cancel)
            cancel.setFixedWidth(80)
            hb.addWidget(QWidget(d))
            sle.setText(name)
            basename_start = 1+name.rindex("/") if "/" in name else 0
            if "." in name[basename_start:]:
                sle.setSelection(basename_start, name[basename_start:].rindex("."))
            sle.setFocus()
            width = sle.fontMetrics().boundingRect(name).width()
            sle.setMinimumWidth(width*9//8)
            d.connect(cancel, SIGNAL("clicked()"), d.reject)
            d.connect(ok, SIGNAL("clicked()"), d.accept)
            if d.exec_() == QDialog.Accepted:
                try:
                    s = sle.text()
                    os.rename(self.imglist[0], unicode(s))
                    self.imglist[0] = unicode(s)
                    self.load(0)
                except Exception:
                    QMessageBox.critical(self, "IView: Unable to rename the file",
                                         "There was a problem renaming the file")
        elif e.key() == Qt.Key_O:
            self.titleOverlay = not self.titleOverlay
        elif e.key() == Qt.Key_F1:
            QMessageBox.information(self, "IView",
                                    "<center><h1>IView</h1>A simple image viewer<br>"
                                    "by Andrea \"6502\" Griffini</center>"
                                    "<ul>"
                                    "  <li> <b>F1</b> This help message</li>"
                                    "  <li> <b>Mouse drag</b> Pan</li>"
                                    "  <li> <b>Mouse wheel</b> Zoom</li>"
                                    "  <li> <b>Left/Right</b> Previous/next image</li>"
                                    "  <li> <b>Home</b> Zoom fit</li>"
                                    "  <li> <b>1/2/3/4</b> Display resolution to Nx</li>"
                                    "  <li> <b>F or Enter</b> Fullscreen mode</li>"
                                    "  <li> <b>O</b> Toggle file name overlay in fullscreen mode</li>"
                                    "  <li> <b>A</b> Arrow markup mode</li>"
                                    "  <li> <b>P</b> Pixel inspection mode</li>"
                                    "  <li> <b>M</b> Measure mode</li>"
                                    "  <li> <b>E</b> Ellipse markup mode</li>"
                                    "  <li> <b>T</b> Text markup mode</li>"
                                    "  <li> <b>C</b> Cropping/yanking mode</li>"
                                    "  <li> <b>Y</b> Convert to grayscale</li>"
                                    "  <li> <b>R</b> Rotate image 90 degrees</li>"
                                    "  <li> <b>U</b> Reload original image</li>"
                                    "  <li> <b>S</b> Save modified image</li>"
                                    "  <li> <b>F2</b> Rename current file</li>"
                                    "  <li> <b>Del</b> Delete current file</li>"
                                    "  <li> <b>X</b> Customize</li>"
                                    "</ul>")
        self.update()

    def sizeHint(self):
        return QSize(900, 700)

    def setZoom(self, sf):
        if self.pos:
            mx, my = self.pos[0] * self.scale + self.tx, self.pos[1] * self.scale + self.ty
            self.scale = sf
            self.tx = mx - self.pos[0] * self.scale
            self.ty = my - self.pos[1] * self.scale
        else:
            mx, my = self.width()/2, self.height()/2
            lx, ly = (mx - self.tx)/self.scale, (my - self.ty)/self.scale
            self.scale = sf
            self.tx = mx - lx * self.scale
            self.ty = my - ly * self.scale
        self.update()

    def wheelEvent(self, e):
        self.pos = (e.x() - self.tx) / self.scale, (e.y() - self.ty) / self.scale
        sf = self.scale
        d = int(e.delta() / 120)
        while d > 0:
            sf *= 1.2
            d -= 1
        while d < 0:
            sf /= 1.2
            d += 1
        if sf < 0.01:
            sf = 0.01
        if sf > 10000:
            sf = 10000
        self.setZoom(sf)

    def paintEvent(self, e):
        dc = QPainter(self)
        w, h = self.width(), self.height()
        dc.fillRect(0, 0, w, h, QBrush(QColor(64, 64, 64)))

        dc.fillRect(self.tx+1, self.ty+1, self.img.width()*self.scale-2, self.img.height()*self.scale-2, self.bbrush)
        if self.scale > 1:
            dc.save()
            dc.translate(self.tx, self.ty)
            dc.scale(self.scale, self.scale)
            dc.drawImage(0, 0, self.img)
            dc.restore()
        else:
            if self.scaled is None or self.scaled[0] != self.scale:
                sw = int(self.img.width() * self.scale)
                sh = int(self.img.height() * self.scale)
                simg = self.img.scaled(QSize(sw, sh), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
                self.scaled = [self.scale, simg]
            dc.drawImage(self.tx, self.ty, self.scaled[1])

        y = 16
        dc.setFont(QFont("sans-serif", 11))

        if len(self.tools)==0 and self.fullscreen and self.titleOverlay:
            self.statusText(dc, self.mkTitle())

        for t in self.tools:
            t.draw(dc)

    def mkTitle(self):
        if self.img.isNull():
            name = self.imglist[0] + " (unable to load as image)"
        else:
            name = self.imglist[0] + " (%i x %i x %ibpp = %0.2f MB)" % (
                self.img.width(),
                self.img.height(),
                self.img.depth(),
                self.img.height() * self.img.bytesPerLine() / (1024.0*1024.0))
        if "/" in name:
            name = name[name.rindex("/")+1:]
        return name

    def mousePressEvent(self, e):
        self.pos = ((e.x() - self.tx) / self.scale,
                    (e.y() - self.ty) / self.scale)
        for t in self.tools:
            self.tracking = t.hit(e.x(), e.y(), e.button())
            if self.tracking:
                break
        else:
            cur = [e.x(), e.y()]
            def pan(mx, my):
                if mx is not None:
                    self.tx += mx - cur[0]
                    self.ty += my - cur[1]
                    cur[0] = mx
                    cur[1] = my
            self.tracking = pan

    def mouseMoveEvent(self, e):
        self.pos = ((e.x() - self.tx) / self.scale,
                    (e.y() - self.ty) / self.scale)
        if self.tracking:
            self.tracking(e.x(), e.y())
            self.update()

    def mouseReleaseEvent(self, e):
        if self.tracking:
            self.tracking(None, None)
            self.tracking = None

    def map(self, x, y):
        return self.tx + x*self.scale, self.ty + y*self.scale

    def revmap(self, x, y):
        return (x - self.tx)/self.scale, (y - self.ty)/self.scale

    def to32bpp(self):
        if self.img.format() != QImage.Format_ARGB32:
            self.img = self.img.convertToFormat(QImage.Format_ARGB32)
            self.scaled = None
            self.update()

    def statusText(self, dc, msg):
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.drawText(4, 20, msg )
        dc.drawText(7, 20, msg )
        dc.drawText(5, 19, msg )
        dc.drawText(5, 21, msg )
        dc.setPen(QPen(QColor(0, 255, 0)))
        dc.drawText(5, 20, msg )

class Tool(object):
    pen = 4
    color = 0
    colors = [QColor(255, 0, 0),
              QColor(0, 255, 0),
              QColor(0, 0, 255)]

    def __init__(self, iview):
        self.iview = iview
        self.font = QFont("sans-serif")
        self.font.setPixelSize(self.pen * 8 + 11)

    def key(self, e):
        if e.key() == Qt.Key_R:
            Tool.color = 0
            self.iview.update()
            return True
        if e.key() == Qt.Key_G:
            Tool.color = 1
            self.iview.update()
            return True
        if e.key() == Qt.Key_B:
            Tool.color = 2
            self.iview.update()
            return True
        if e.key() >= Qt.Key_1 and e.key() <= Qt.Key_9:
            Tool.pen = (e.key() - Qt.Key_1)*2
            self.font.setPixelSize(self.pen * 8 + 11)
            self.iview.update()
            return True
        if e.key() == Qt.Key_Escape:
            self.iview.tools.remove(self)
            return True

    def text(self, dc, msg):
        return self.iview.statusText(dc, msg)

class RectTool(Tool):
    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.geo = [0, 0, 0, 0]

    def rect(self):
        x0, y0, x1, y1 = self.geo
        x0, y0 = map(int, self.iview.map(self.geo[0], self.geo[1]))
        x1, y1 = map(int, self.iview.map(self.geo[2], self.geo[3]))
        if x0 > x1: x0, x1 = x1, x0
        if y0 > y1: y0, y1 = y1, y0
        return x0, y0, x1, y1

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dc.setBrush(QBrush())
        dc.setPen(QPen(QColor(0, 0, 0), 4))
        dc.drawRect(QRectF(x0, y0, x1-x0, y1-y0))
        dc.setPen(QPen(QColor(255, 255, 255), 2, Qt.DashLine))
        dc.drawRect(QRectF(x0, y0, x1-x0, y1-y0))
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.setBrush(QColor(255, 0, 0))
        for x, y in ((x0, y0), (x1, y0), (x0, y1), (x1, y1)):
            dc.drawEllipse(QRectF(x-4, y-4, 8, 8))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0, x1, y1 = self.geo
            x0, y0 = map(int, self.iview.map(self.geo[0], self.geo[1]))
            x1, y1 = map(int, self.iview.map(self.geo[2], self.geo[3]))
            f = 0
            if near(mx, x0, 8) and between(my, y0, y1, 8): f |= 1
            if near(my, y0, 8) and between(mx, x0, x1, 8): f |= 2
            if near(mx, x1, 8) and between(my, y0, y1, 8): f |= 4
            if near(my, y1, 8) and between(mx, x0, x1, 8): f |= 8
            if f == 0 and between(mx, x0, x1) and between(my, y0, y1):
                f = 15
            else:
                if (f & 5) == 5: f ^= 4
                if (f & 10) == 10: f ^= 8
            if f == 0:
                xx, yy = map(int, self.iview.revmap(mx, my))
                self.geo = [xx, yy, xx, yy]
                f = 4+8

            if f:
                cur = list(map(int, self.iview.revmap(mx, my)))
                def tracking(x, y):
                    if x is not None:
                        x, y = map(int, self.iview.revmap(x, y))
                        dx, dy = x - cur[0], y - cur[1]
                        cur[0], cur[1] = x, y
                        if f & 1: self.geo[0] += dx
                        if f & 2: self.geo[1] += dy
                        if f & 4: self.geo[2] += dx
                        if f & 8: self.geo[3] += dy
                        self.geo[0] = max(0, min(self.geo[0], self.iview.img.width()))
                        self.geo[1] = max(0, min(self.geo[1], self.iview.img.height()))
                        self.geo[2] = max(0, min(self.geo[2], self.iview.img.width()))
                        self.geo[3] = max(0, min(self.geo[3], self.iview.img.height()))
                return tracking

class CropTool(RectTool):
    def __init__(self, iview):
        RectTool.__init__(self, iview)

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dark = QBrush(QColor(0, 0, 0, 128))
        w, h = self.iview.width(), self.iview.height()
        if y0 > 0: dc.fillRect(0, 0, w, y0, dark)
        if x0 > 0: dc.fillRect(0, y0, x0, y1-y0, dark)
        if x1 < w: dc.fillRect(x1, y0, w-x1, y1-y0, dark)
        if y1 < h: dc.fillRect(0, y1, w, h-y1, dark)
        RectTool.draw(self, dc)
        self.text(dc, "Crop: (%i, %i) - (%i, %i) = %i x %i (Y to copy)" %
                      (self.geo[0], self.geo[1],
                       self.geo[2], self.geo[3],
                       abs(self.geo[2]-self.geo[0]),
                       abs(self.geo[3]-self.geo[1])))

    def key(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_C, Qt.Key_Enter, Qt.Key_Y):
            x0, y0, x1, y1 = self.geo
            if x0 > x1: x0, x1 = x1, x0
            if y0 > y1: y0, y1 = y1, y0
            x0, y0 = max(0, x0), max(0, y0)
            x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.height(), y1)
            if x0 < x1 and y0 < y1:
                cropped = self.iview.img.copy(x0, y0, x1-x0, y1-y0)
                if e.key() == Qt.Key_Y:
                    QApplication.clipboard().setImage(cropped)
                else:
                    self.iview.img = cropped
                    self.iview.scaled = None
                    self.iview.zoomAll()
            self.iview.tools.remove(self)
            return True
        else:
            return Tool.key(self, e)

class EllipseTool(RectTool):
    def __init__(self, iview):
        RectTool.__init__(self, iview)

    def draw(self, dc):
        x0, y0, x1, y1 = self.rect()
        dc.setPen(QPen(self.colors[self.color], self.pen*self.iview.scale))
        dc.setBrush(QBrush())
        dc.drawEllipse(x0, y0, x1-x0, y1-y0)
        dc.setPen(QPen(QColor(0, 0, 0)))
        dc.setBrush(QColor(255, 0, 0))
        for x, y in (((x0+x1)/2, y0), ((x0+x1)/2, y1),
                     (x0, (y0+y1)/2), (x1, (y0+y1)/2),
                     (x0, y0), (x1, y0), (x0, y1), (x1, y1)):
            dc.drawEllipse(QRectF(x-4, y-4, 8, 8))
        self.text(dc, "Ellipse drawing mode: 1-9=thickness, r/g/b=color")

    def key(self, e):
        if e.key() in (Qt.Key_Return, Qt.Key_E, Qt.Key_Enter):
            x0, y0, x1, y1 = self.geo
            if x0 > x1: x0, x1 = x1, x0
            if y0 > y1: y0, y1 = y1, y0
            x0, y0 = max(0, x0), max(0, y0)
            x1, y1 = min(self.iview.img.width(), x1), min(self.iview.img.height(), y1)
            if x0 < x1 and y0 < y1:
                self.iview.to32bpp()
                dc = QPainter(self.iview.img)
                dc.setPen(QPen(self.colors[self.color], self.pen))
                dc.setBrush(QBrush())
                dc.drawEllipse(x0, y0, x1-x0, y1-y0)
                dc = None
                self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        else:
            return Tool.key(self, e)

class PixColorTool(Tool):
    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.p = None

    def draw(self, dc):
        if self.p is None:
            self.text(dc, "Pixel inspection; click for color/position")
        else:
            x, y = self.p
            col = self.iview.img.pixel(x, y)
            a = (col >> 24) & 255
            r = (col >> 16) & 255
            g = (col >> 8) & 255
            b = (col >> 0) & 255
            c0 = QBrush(QColor(0, 0, 0))
            c1 = QBrush(QColor(255, 255, 255))

            x0, y0 = self.iview.map(x, y)
            x1, y1 = self.iview.map(x+1, y+1)
            h = (y1 - y0) * 10
            s = (y1 - y0) * 2
            dc.fillRect(x0-1, y0-h-s-1, x1-x0+2, h+2, c0)
            dc.fillRect(x0-1, y1+s-1, x1-x0+2, h+2, c0)
            dc.fillRect(x0-h-s-1, y0-1, h+2, y1-y0+2, c0)
            dc.fillRect(x1+s-1, y0-1, h+2, y1-y0+2, c0)
            dc.fillRect(x0, y0-h-s, x1-x0, h, c1)
            dc.fillRect(x0, y1+s, x1-x0, h, c1)
            dc.fillRect(x0-h-s, y0, h, y1-y0, c1)
            dc.fillRect(x1+s, y0, h, y1-y0, c1)
            self.text(dc, "Pixel(x=%i, y=%i): rgb(%i, %i, %i), #%02x%02x%02x, alpha=%i" % (x, y, r, g, b, r, g, b, a))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x, y = map(int, self.iview.revmap(mx, my))
            if 0 <= x < self.iview.img.width() and 0 <= y < self.iview.img.height():
                self.p = (x, y)
                self.iview.update()

class ArrowTool(Tool):
    k = 8

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.pts = [0, 0, 0, 0]

    def draw(self, dc):
        x0, y0 = map(int, self.iview.map(*self.pts[:2]))
        x1, y1 = map(int, self.iview.map(*self.pts[2:]))
        dx, dy = (x1 - x0)//self.k, (y1 - y0)//self.k
        nx, ny = (y1 - y0)//self.k//2, (x0 - x1)//self.k//2
        dc.setPen(QPen(self.colors[self.color], self.pen*self.iview.scale,
                       Qt.SolidLine, Qt.RoundCap))
        dc.setBrush(QBrush())
        dc.drawLine(x0, y0, x1, y1)
        dc.drawLine(x1-dx-nx, y1-dy-ny, x1, y1)
        dc.drawLine(x1-dx+nx, y1-dy+ny, x1, y1)
        self.text(dc, "Arrow drawing mode, 1-9=thickness, r/g/b=color, +/- changes head")

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0 = map(int, self.iview.map(*self.pts[:2]))
            x1, y1 = map(int, self.iview.map(*self.pts[2:]))
            i = -1
            if (mx - x0)**2 + (my - y0)**2 < 8**2:
                i = 0
            elif (mx - x1)**2 + (my - y1)**2 < 8**2:
                i = 2
            else:
                xx, yy = self.iview.revmap(mx, my)
                self.pts = [xx, yy, xx, yy]
                i = 2
            def tracking(x, y):
                if x is not None:
                    self.pts[i:i+2] = list(map(int, self.iview.revmap(x, y)))
            return tracking

    def key(self, e):
        if e.key() == Qt.Key_Minus:
            if ArrowTool.k < 100: ArrowTool.k += 1
        if e.key() == Qt.Key_Plus:
            if ArrowTool.k > 4: ArrowTool.k -= 1
        if e.key() in (Qt.Key_Return, Qt.Key_A, Qt.Key_Enter):
            self.iview.to32bpp()
            x0, y0, x1, y1 = map(int, self.pts)
            dx, dy = (x1 - x0)//self.k, (y1 - y0)//self.k
            nx, ny = (y1 - y0)//self.k//2, (x0 - x1)//self.k//2
            dc = QPainter(self.iview.img)
            dc.setPen(QPen(self.colors[self.color], self.pen,
                           Qt.SolidLine, Qt.RoundCap))
            dc.setBrush(QBrush())
            dc.drawLine(x0, y0, x1, y1)
            dc.drawLine(x1-dx-nx, y1-dy-ny, x1, y1)
            dc.drawLine(x1-dx+nx, y1-dy+ny, x1, y1)
            dc = None
            self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        return Tool.key(self, e)

class MeasuringTool(Tool):
    k = 8

    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.pts = [0, 0, 0, 0]

    def draw(self, dc):
        x0, y0 = map(int, self.iview.map(*self.pts[:2]))
        x1, y1 = map(int, self.iview.map(*self.pts[2:]))
        dx, dy = (x1 - x0)//self.k, (y1 - y0)//self.k
        nx, ny = (y1 - y0)//self.k//2, (x0 - x1)//self.k//2
        dc.setPen(QPen(self.colors[self.color]))
        dc.setBrush(QBrush())
        dc.drawLine(x0, y0, x1, y1)
        dc.drawLine(x1-dx-nx, y1-dy-ny, x1, y1)
        dc.drawLine(x1-dx+nx, y1-dy+ny, x1, y1)
        dc.drawLine(x0+dx-nx, y0+dy-ny, x0, y0)
        dc.drawLine(x0+dx+nx, y0+dy+ny, x0, y0)
        dc.drawLine(x0+nx, y0+ny, x0-nx, y0-ny)
        dc.drawLine(x1+nx, y1+ny, x1-nx, y1-ny)
        x0, y0, x1, y1 = self.pts
        dx, dy = x1 - x0, y1 - y0
        L = (dx**2 + dy**2)**0.5
        angle = 0 if L == 0 else math.atan2(dy, dx)*180/math.pi
        self.text(dc, "Measuring mode: (%i,%i)-(%i,%i) : %ix%i = %0.3fpx, %0.3f degrees" % (
                x0, y0, x1, y1, dx, dy, L, angle))

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            x0, y0 = map(int, self.iview.map(*self.pts[:2]))
            x1, y1 = map(int, self.iview.map(*self.pts[2:]))
            i = -1
            if (mx - x0)**2 + (my - y0)**2 < 8**2:
                i = 0
            elif (mx - x1)**2 + (my - y1)**2 < 8**2:
                i = 2
            else:
                xx, yy = map(int, self.iview.revmap(mx, my))
                self.pts = [xx, yy, xx, yy]
                i = 2
            def tracking(x, y):
                if x is not None:
                    self.pts[i:i+2] = list(map(int, self.iview.revmap(x, y)))
                    if app.keyboardModifiers() == Qt.ShiftModifier:
                        if (abs(self.pts[i] - self.pts[0+2-i]) <
                            abs(self.pts[i+1] - self.pts[1+3-(i+1)])):
                            self.pts[i] = self.pts[0+2-i]
                        else:
                            self.pts[i+1] = self.pts[1+3-(i+1)]
            return tracking

    def key(self, e):
        if e.key() == Qt.Key_Minus:
            if MeasuringTool.k < 100: MeasuringTool.k += 1
        if e.key() == Qt.Key_Plus:
            if MeasuringTool.k > 4: MeasuringTool.k -= 1
        return Tool.key(self, e)

class TextTool(Tool):
    def __init__(self, iview):
        Tool.__init__(self, iview)
        self.text = ""
        x0, y0 = map(int, self.iview.revmap(0, 0))
        x1, y1 = map(int, self.iview.revmap(self.iview.width(), self.iview.height()))
        self.pos = [(x0 + x1)//2, (y0 + y1)//2]
        self.blink = int(time.time() * 2) + 1
        QTimer.singleShot(250, lambda : self.iview.update())

    def draw(self, dc):
        x, y = self.pos
        dc.save()
        dc.translate(self.iview.tx, self.iview.ty)
        dc.scale(self.iview.scale, self.iview.scale)
        dc.setFont(self.font)
        dc.setPen(QPen(self.colors[self.color]))
        caret = (int(time.time() * 2) - self.blink) & 1
        dc.drawText(x, y, self.text + "|" if caret else self.text)
        dc.restore()
        Tool.text(self, dc, "Text tool: ctrl-1...9=Font size, ctrl-R/G/B color, ENTER=Accept")
        QTimer.singleShot(250, lambda : self.iview.update())

    def hit(self, mx, my, button):
        if button == Qt.LeftButton:
            cur = list(map(int, self.iview.revmap(mx, my)))
            def tracking(mx, my):
                if mx is not None:
                    x, y = map(int, self.iview.revmap(mx, my))
                    self.pos[0] += x - cur[0]
                    self.pos[1] += y - cur[1]
                    cur[0] = x
                    cur[1] = y
            return tracking

    def key(self, e):
        self.blink = int(time.time() * 2) + 1
        if e.key() == Qt.Key_Backspace:
            self.text = self.text[:-1]
            return True
        elif e.key() == Qt.Key_Escape:
            return Tool.key(self, e)
        elif e.key() in (Qt.Key_Return, Qt.Key_Enter):
            self.iview.to32bpp()
            x, y = self.pos
            dc = QPainter(self.iview.img)
            dc.setFont(self.font)
            dc.setPen(QPen(self.colors[self.color]))
            dc.drawText(x, y, self.text)
            dc = None
            self.iview.scaled = None
            self.iview.tools.remove(self)
            return True
        elif e.text() and e.modifiers() != Qt.ControlModifier:
            self.text += unicode(e.text())
            return True
        return Tool.key(self, e)

class MyDialog(QDialog):
    def __init__(self, parent, imglist):
        QDialog.__init__(self, parent, Qt.Window)
        self.ws = Viewer(self, imglist)
        L = QVBoxLayout(self)
        L.setContentsMargins(0, 0, 0, 0)
        L.addWidget(self.ws)
        self.setModal(True)
        self.show()

uargv = sys.argv
if len(uargv) == 1:
    print("Missing filename")
    sys.exit(1)

app = QApplication(sys.argv)

if sys.version_info[0]<3:
    # if we are on Python 2, let Qt deal with the encoding madness
    # (on Python 3 sys.argv is already a list of unicode strings)
    uargv = [unicode(x) for x in app.arguments()]

if len(uargv) in (2, 3) and uargv[1] == "--screenshot":
    img = QPixmap.grabWindow(app.desktop().winId())
    fname = "iview-screenshot.png" if len(uargv) == 2 else uargv[2]
    img.save(fname)
    uargv[1:] = [fname]

class SortWrapper:
    def __init__(self, x):
        self.x = x

    def __lt__(self, other):
        if type(self.x) is type(other.x):
            return self.x < other.x
        else:
            return id(type(self.x)) < id(type(other.x))

def smartSort(s):
    return tuple(SortWrapper(int(x)) if x[0] in "0123456789" else SortWrapper(x)
                 for x in re.findall("[0-9]+|[^0-9]+", s.upper()))

filelist = uargv[1:]
if len(filelist) == 1 and filelist[0][:8] == "file:///":
    filelist[0] = filelist[0][7:]
if len(filelist) == 1:
    slash = filelist[0].rindex("/") if "/" in filelist[0] else -1
    fl = []
    supported_extensions = [bytes(x).decode() for x in QImageReader.supportedImageFormats()]
    fre = re.compile("^.*\\.(" + "|".join(supported_extensions) + ")$", re.IGNORECASE)
    for f in os.listdir(filelist[0][:slash+1] + "."):
        if fre.match(f):
            fl.append(filelist[0][:slash+1] + f)
    fl.sort(key = smartSort)
    if filelist[0] in fl:
        i = fl.index(filelist[0])
        filelist = fl if i == 0 else fl[i:] + fl[:i]
aa = MyDialog(None, filelist)
aa.exec_()
aa = None
