Source

pilpreview / main.py

Full commit
# -*- coding: utf-8 -*-

# Copyright 2008-2012 Alexandre `Zopieux` Macabies
# This module is part of avatarsio and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php

from PySide import QtCore, QtGui, QtUiTools
from collections import OrderedDict
import sys
import traceback
import inspect
import resrc
import io
import cgi

from utils import RedirectedStream
from delegates import ListviewDelegate

sys.stdout = RedirectedStream(sys.__stdout__)

try:
    import Image, ImageDraw
except ImportError:
    from PIL import Image, ImageDraw

import cairo

class PilPreview(QtGui.QWidget):
    INITIAL_CODE = """### For PIL, please note Image and ImageDraw modules are already imported.
### For cairo, the cairo module is already imported.

## PIL example
# open a path:
# im = Image.open('path')

# create a transparent image:
size = (255, 255)
im = Image.new('RGBA', size)

## cairo example
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, *size)
ctx = cairo.Context(surface)
ctx.scale(*size)

pat = cairo.LinearGradient(0.0, 0.0, 0.0, 1.0)
pat.add_color_stop_rgba(1, 0.7, 0, 0, 0.5) # First stop, 50% opacity
pat.add_color_stop_rgba(0, 0.9, 0.7, 0.2, 1) # Last stop, 100% opacity

ctx.rectangle(0, 0, 1, 1) # Rectangle(x0, y0, x1, y1)
ctx.set_source(pat)
ctx.fill()
"""

    def __init__(self, parent=None):
        super().__init__(parent)

        loader = QtUiTools.QUiLoader()
        file = QtCore.QFile(":/main.ui")
        file.open(QtCore.QFile.ReadOnly)
        self.ui = loader.load(file, self)
        file.close()
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.ui)
        layout.setContentsMargins(4, 4, 4, 4)
        self.setLayout(layout)
        self.setWindowTitle("PIL Preview")
        self.setWindowIcon(QtGui.QIcon(":/img/pilpreview.png"))

        self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope,
            "ZopiSoftware", "pilpreview", self)

        # Models
        self.hiddenImagesModel = QtGui.QStandardItemModel(self)
        self.hiddenImagesModel.setColumnCount(1)
        self.ui.inspectList.setModel(self.hiddenImagesModel)

        self.imagePreviewModel = QtGui.QStandardItemModel(self)
        self.imagePreviewModel.setColumnCount(1)
        self.ui.imageView.setModel(self.imagePreviewModel)

        # Delgates
        listDelegate = ListviewDelegate()
        self.ui.imageView.setItemDelegate(listDelegate)

        # Timers
        self.autoUpdateTimer = QtCore.QTimer()
        self.autoUpdateTimer.setSingleShot(False)

        # Actions

        # Connections
        self.ui.settingReset.clicked.connect(self.reinitSettings)
        self.ui.settingAutoUpdate.stateChanged.connect(self.updateUi)
        self.ui.settingAutoUpdateDelay.valueChanged.connect(self.updateUi)
        self.ui.settingThumb.stateChanged.connect(self.generatePreview)
        self.ui.updateNow.clicked.connect(self.inspectCode)
        self.ui.inspectAll.clicked.connect(lambda: self.clearInspect(True))
        self.ui.inspectNone.clicked.connect(lambda: self.clearInspect(False))

        self.hiddenImagesModel.itemChanged.connect(self.updateInspector)
        self.autoUpdateTimer.timeout.connect(self.inspectCode)

        # Inits
        self.lastSource = ''
        self.inspectedImages = {}
        self.hiddenImageNames = set()

        self.readSettings()
        self.updateUi()

    def reinitSettings(self):
        self.settings.clear()
        self.readSettings()

    def readSettings(self):
        self.settings.beginGroup('main')
        self.restoreGeometry(self.settings.value('geometry', self.saveGeometry()))
        self.ui.splitterH.restoreState(self.settings.value('splitterHState', self.ui.splitterH.saveState()))
        self.ui.splitterV.restoreState(self.settings.value('splitterVState', self.ui.splitterV.saveState()))
        self.ui.sourceEditor.setPlainText(self.settings.value('sourceText', PilPreview.INITIAL_CODE))
        self.ui.settingAutoUpdate.setChecked(bool(self.settings.value('settingAutoUpdate', '1')))
        self.ui.settingAutoUpdateDelay.setValue(int(self.settings.value('settingAutoUpdateDelay', '2')))
        self.ui.settingThumb.setChecked(bool(self.settings.value('settingThumb', '1')))
        self.settings.endGroup()

    def writeSettings(self):
        self.settings.beginGroup('main')
        self.settings.setValue('geometry', self.saveGeometry())
        self.settings.setValue('splitterHState', self.ui.splitterH.saveState())
        self.settings.setValue('splitterVState', self.ui.splitterV.saveState())
        self.settings.setValue('sourceText', self.ui.sourceEditor.toPlainText())
        self.settings.setValue('settingAutoUpdate', self.ui.settingAutoUpdate.isChecked() and '1' or '')
        self.settings.setValue('settingAutoUpdateDelay', self.ui.settingAutoUpdateDelay.value())
        self.settings.setValue('settingThumb', self.ui.settingThumb.isChecked() and '1' or '')
        self.settings.endGroup()

    def closeEvent(self, event):
        self.writeSettings()
        super(PilPreview, self).closeEvent(event)

    def updateUi(self):
        autoUpdate = self.ui.settingAutoUpdate.isChecked()
        self.ui.settingAutoUpdateDelay.setEnabled(autoUpdate)
        self.autoUpdateTimer.stop()
        if autoUpdate:
            self.autoUpdateTimer.start(self.ui.settingAutoUpdateDelay.value() * 1000)

    def clearInspect(self, all):
        if all:
            self.hiddenImageNames = set()
        else:
            self.hiddenImageNames = set(self.inspectedImages.keys())

        self.buildInspectorList()
        self.generatePreview()

    def inspectCode(self):
        newSource = self.ui.sourceEditor.toPlainText()
        if self.lastSource.strip() == newSource.strip():
            return

        self.lastSource = newSource

        # First, run and inspect
        fake_stdout = io.StringIO()
        sys.stdout.redirect_start(fake_stdout)

        execGlobals = {'Image': Image, 'ImageDraw': ImageDraw, 'cairo': cairo}
        execLocals = {}

        try:
            exec(newSource, execGlobals, execLocals)
            self.ui.consoleOutput.setPlainText(fake_stdout.getvalue())
        except: # ya, except *everything*
            self.ui.consoleOutput.setHtml(
                '<span style="color: #F92672;">An exception was catched</span>'
                '<pre style="margin:0; padding:0;"><code>%s</code></pre>' % \
                cgi.escape(traceback.format_exc().strip()))
            return
        finally:
            sys.stdout.redirect_end()

        self.inspectedImages = OrderedDict((name, obj) for name, obj in sorted(execLocals.items()) if isinstance(obj, (Image.Image, cairo.Surface)))

        # Build the inspector list
        self.buildInspectorList()
        # Display images
        self.generatePreview()

    def updateInspector(self, item):
        name = item.data(QtCore.Qt.DisplayRole)
        if item.checkState() == QtCore.Qt.Checked:
            self.hiddenImageNames.discard(name)
        else:
            self.hiddenImageNames.add(name)

        self.generatePreview()

    def buildInspectorList(self):
        self.hiddenImagesModel.clear()

        for row, (name, obj) in enumerate(self.inspectedImages.items()):
            item = QtGui.QStandardItem()
            item.setCheckable(True)
            item.setEditable(False)
            item.setData('[%s] %s' % (obj.__class__.__name__, name), QtCore.Qt.DisplayRole)
            item.setData(QtCore.Qt.Unchecked if name in self.hiddenImageNames else QtCore.Qt.Checked, QtCore.Qt.CheckStateRole)

            self.hiddenImagesModel.insertRow(row, item)

    def generatePreview(self):
        self.imagePreviewModel.clear()

        for row, (name, obj) in enumerate(_ for _ in self.inspectedImages.items() if _[0] not in self.hiddenImageNames):
            buff = io.BytesIO()
            if isinstance(obj, Image.Image):
                obj.save(buff, 'PNG')
            elif isinstance(obj, cairo.Surface):
                obj.write_to_png(buff)
            image = QtGui.QImage.fromData(buff.getvalue(), 'PNG')

            item = QtGui.QStandardItem()
            item.setEditable(False)
            item.setCheckable(False)
            item.setData('[%s] %s' % (obj.__class__.__name__, name), QtCore.Qt.DisplayRole)
            item.setData(image, ListviewDelegate.imageRole)
            item.setData(self.ui.settingThumb.isChecked(), ListviewDelegate.isThumbRole)

            self.imagePreviewModel.insertRow(row, item)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    mainw = PilPreview()
    mainw.show()
    sys.exit(app.exec_())