# -*- coding: utf-8 -*-
### Copyright (C) 2008-2012 Antonio Valentino <a_valentino@users.sf.net>
### This file is part of GSDView.
### GSDView is free software; you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
### the Free Software Foundation; either version 2 of the License, or
### (at your option) any later version.
### GSDView is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
### GNU General Public License for more details.
### You should have received a copy of the GNU General Public License
### along with GSDView; if not, write to the Free Software
### Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
'''Utility functions and classes for Qt4 applicaions.'''
import os
import csv
import logging
from cStringIO import StringIO
from ConfigParser import ConfigParser
from qt import QtCore, QtGui, QtSvg
from . import utils
__author__ = 'Antonio Valentino <a_valentino@users.sf.net>'
__date__ = '$Date$'
__revision__ = '$Revision$'
intToWinState = {
int(QtCore.Qt.WindowNoState): QtCore.Qt.WindowNoState,
int(QtCore.Qt.WindowMinimized): QtCore.Qt.WindowMinimized,
int(QtCore.Qt.WindowMaximized): QtCore.Qt.WindowMaximized,
int(QtCore.Qt.WindowFullScreen): QtCore.Qt.WindowFullScreen,
int(QtCore.Qt.WindowActive): QtCore.Qt.WindowActive,
}
### Menus and toolbars helpers ###############################################
[docs]def overrideCursor(func):
def aux(*args, **kwargs):
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
try:
return func(*args, **kwargs)
finally:
QtGui.QApplication.restoreOverrideCursor()
return aux
[docs]def callExpensiveFunc(func, *args, **kwargs):
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
try:
return func(*args, **kwargs)
finally:
QtGui.QApplication.restoreOverrideCursor()
### Table model/view helpers ##################################################
[docs]def clearTable(tablewidget):
'''Remove contents from a table widget preserving labels. '''
labels = [str(tablewidget.horizontalHeaderItem(col).text())
for col in range(tablewidget.columnCount())]
tablewidget.clear()
tablewidget.setHorizontalHeaderLabels(labels)
tablewidget.setRowCount(0)
[docs]def selectAllItems(itemview):
'''Select all items in an QAbstractItemView.'''
model = itemview.model()
topleft = model.index(0, 0)
try:
bottomright = model.index(model.rowCount() - 1,
model.columnCount() - 1)
except (TypeError, AttributeError):
# columnCount is a private method in QAbstractListModel
# assume it is a list
bottomright = model.index(model.rowCount() - 1)
selection = QtGui.QItemSelection(topleft, bottomright)
itemview.selectionModel().select(selection,
QtGui.QItemSelectionModel.Select)
#@QtCore.Slot(QtGui.QWidget) # @TODO: check
[docs]def copySelectedItems(itemview):
'''Copy selected items of an QAbstractItemView to the clipboard and
also return copied data.'''
selection = itemview.selectionModel().selection()
lines = []
for itemrange in selection:
model = itemrange.model()
parent = itemrange.parent()
for row in range(itemrange.top(), itemrange.bottom() + 1):
parts = []
for col in range(itemrange.left(), itemrange.right() + 1):
index = model.index(row, col, parent)
parts.append(str(model.data(index)))
line = '\t'.join(parts)
lines.append(line)
data = '\n'.join(lines)
if data:
clipboard = QtGui.QApplication.clipboard()
clipboard.setText(data, QtGui.QClipboard.Clipboard)
clipboard.setText(data, QtGui.QClipboard.Selection)
# @TODO: check
#data = QtCore.QByteArray()
#data.append('\n'.join(lines))
#mimedata = QtCore.QMimeData()
#mimedata.setData('text/csv', data)
#clipboard = QtGui.QApplication.clipboard()
#clipboard.setMimeData(mimedata, QtGui.QClipboard.Clipboard)
#clipboard.setMimeData(mimedata, QtGui.QClipboard.Selection)
return data
[docs]def modelToIni(model, section=None, cfg=None):
assert model.columnCount() == 2
if cfg is None:
cfg = ConfigParser()
for row in range(model.rowCount()):
name = model.index(row, 0).data()
value = model.index(row, 1).data()
cfg.set(section, name, value)
return cfg
[docs]def modelToCsv(model, dialect='excel'):
fp = StringIO()
writer = csv.writer(fp, dialect)
try:
ncols = model.columnCount()
except TypeError:
# columnCount is a private method in QAbstractListModel
ncols = 1
for row in range(model.rowCount()):
line = []
for col in range(ncols):
line.append(model.index(row, col).data())
writer.writerow(line)
return fp.getvalue()
[docs]def modelToTextDocument(model, doc=None):
if doc is None:
doc = QtGui.QTextDocument()
cursor = QtGui.QTextCursor(doc)
cursor.movePosition(QtGui.QTextCursor.End)
cursor.beginEditBlock()
format = QtGui.QTextTableFormat()
format.setCellPadding(5)
format.setCellSpacing(0)
format.setBorderStyle(QtGui.QTextFrameFormat.BorderStyle_Solid)
format.setHeaderRowCount(1)
nrows = model.rowCount()
try:
ncols = model.columnCount()
except TypeError:
# columnCount is a private method in QAbstractListModel
ncols = 1
table = cursor.insertTable(nrows, ncols, format)
#textformat = QtGui.QTextFormat()
for row in range(nrows):
for col in range(ncols):
text = model.index(row, col).data()
if text is None:
text = ''
else:
text = str(text)
cell = table.cellAt(row, col)
cellCursor = cell.firstCursorPosition()
cellCursor.insertText(text) # , textformat)
# headers style
headerformat = QtGui.QTextCharFormat()
headerformat.setFontWeight(QtGui.QFont.Bold)
brush = headerformat.background()
brush.setColor(QtCore.Qt.lightGray)
brush.setStyle(QtCore.Qt.SolidPattern)
headerformat.setBackground(brush)
# horizontal header
headers = [model.headerData(col, QtCore.Qt.Horizontal)
for col in range(ncols)]
if any(headers):
table.insertRows(0, 1)
for col, text in enumerate(headers):
if text is None:
text = ''
else:
text = str(text)
cell = table.cellAt(0, col)
cell.setFormat(headerformat)
cellCursor = cell.firstCursorPosition()
cellCursor.insertText(text)
# vertical header
headers = [model.headerData(row, QtCore.Qt.Vertical)
for row in range(nrows)]
if any(headers):
table.insertColumns(0, 1)
for row, text in enumerate(headers):
if text is None:
text = ''
else:
text = str(text)
cell = table.cellAt(row + 1, 0)
cell.setFormat(headerformat)
cellCursor = cell.firstCursorPosition()
cellCursor.insertText(text, headerformat)
cursor.endEditBlock()
return doc
[docs]def exportTable(model, parent=None):
filters = [
'CSV file (*.csv)',
'CSV TAB-delimited file (*.csv)',
'HTML file (*.html)',
'All files (*)',
]
try:
ncols = model.columnCount()
except TypeError:
# columnCount is a private method in QAbstractListModel
ncols = 1
if ncols == 1:
filters.insert(0, 'Text file (*.txt)')
target = os.path.join(utils.default_workdir(), 'data.txt')
if ncols == 2:
filters.insert(0, 'INI file format (*.ini)')
target = os.path.join(utils.default_workdir(), 'data.ini')
else:
target = os.path.join(utils.default_workdir(), 'data.csv')
# @TODO: check
if parent is None:
try:
parent = model.window()
except AttributeError:
parent = None
filename, filter_ = QtGui.QFileDialog.getSaveFileNameAndFilter(
parent,
model.tr('Save'),
target,
';;'.join(filters))
if filename:
ext = os.path.splitext(filename)[-1]
ext = ext.lower()
if ext == '.csv' or ext == '.txt':
if 'TAB' in filter_:
dialect = 'excel-tab'
else:
dialect = 'excel'
data = modelToCsv(model, dialect)
elif ext == '.ini':
cfg = modelToIni(model)
fp = StringIO()
cfg.write(fp)
data = fp.getvalue()
elif ext == '.html':
doc = modelToTextDocument(model)
data = doc.toHtml()
else:
# default
data = modelToCsv(model, 'excel-tab')
with open(filename, 'w') as fd:
fd.write(data)
[docs]def setViewContextActions(widget):
assert (widget.contextMenuPolicy() == QtCore.Qt.ActionsContextMenu), \
'menu policy is not "QtCore.Qt.ActionsContextMenu"'
#if widget.contextMenuPolicy() != QtCore.Qt.ActionsContextMenu:
# widget.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
icon = geticon('copy.svg', __name__)
action = QtGui.QAction(icon, widget.tr('&Copy'), widget,
objectName='copyAction',
shortcut=widget.tr('Ctrl+C'),
toolTip=widget.tr('Copy selected items'),
triggered=lambda: copySelectedItems(widget))
widget.addAction(action)
#':/trolltech/dialogs/qprintpreviewdialog/images/view-page-multi-32.png'
icon = QtGui.QIcon(
':/trolltech/styles/commonstyle/images/viewlist-128.png')
action = QtGui.QAction(icon, widget.tr('Select &All'), widget,
objectName='selectAllAction',
#shortcut=widget.tr('Ctrl+A'),
toolTip=widget.tr('Select all items'),
triggered=lambda: selectAllItems(widget))
widget.addAction(action)
icon = widget.style().standardIcon(QtGui.QStyle.SP_DialogSaveButton)
action = QtGui.QAction(icon, widget.tr('&Save As'), widget,
objectName='saveAsAction',
shortcut=widget.tr('Ctrl+S'),
statusTip=widget.tr('Save as'),
triggered=lambda: exportTable(widget.model()))
widget.addAction(action)
icon = QtGui.QIcon(
':/trolltech/dialogs/qprintpreviewdialog/images/print-32.png')
action = QtGui.QAction(icon, widget.tr('&Print'), widget,
objectName='printAction',
shortcut=widget.tr('Ctrl+P'),
statusTip=widget.tr('Print'),
triggered=lambda: printObject(widget))
widget.addAction(action)
#~ icon = QtGui.QIcon(
#~ ':/trolltech/styles/commonstyle/images/filecontents-128.png')
#~ action = QtGui.QAction(icon, widget.tr('Print Preview'), widget,
#~ objectName='printPreviewAction',
#~ statusTip=widget.tr('Print Preview'))#,
#~ #triggered=tablePrintPreview)
#~ # @TODO: tablePrintPreview
#~ widget.addAction(action)
### Printing helpers ##########################################################
[docs]def coreprint(obj, printer):
painter = QtGui.QPainter(printer)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
obj.render(painter)
painter.end()
[docs]def printObject(obj, printer=None, parent=None):
if printer is None:
printer = QtGui.QPrinter(QtGui.QPrinter.PrinterResolution)
#printer.setOutputFile(os.path.join(utils.default_workdir().
# 'filename.pdf'))
# @TODO: check
if parent is None:
try:
parent = obj.window()
except AttributeError:
parent = None
#dialog = QtGui.QPrintDialog(printer)
#try:
# window = obj.window()
#except AttributeError:
# window = = None
#preview = QtGui.QPrintPreviewWidget(printer, window)
#preview.paintRequested.connect(coreprint)
#dialog.setOptionTabs([preview])
#ret = d.exec_()
ret = QtGui.QPrintDialog(printer, parent).exec_()
if ret == QtGui.QDialog.Accepted:
if isinstance(obj, (QtGui.QTextDocument, QtGui.QTextEdit)):
obj.print_(printer)
elif hasattr(obj, 'model'):
model = obj.model()
doc = modelToTextDocument(model)
doc.print_(printer)
elif isinstance(obj, QtCore.QAbstractItemModel):
doc = modelToTextDocument(obj)
doc.print_(printer)
else:
coreprint(obj, printer)
[docs]def printPreview(obj, printer=None, parent=None):
if printer is None:
printer = QtGui.QPrinter(QtGui.QPrinter.PrinterResolution)
# @TODO: check
if parent is None:
try:
parent = obj.window()
except AttributeError:
parent = None
dialog = QtGui.QPrintPreviewDialog(printer, parent)
dialog.paintRequested.connect(coreprint)
ret = dialog.exec_()
# @WARNING: duplicate code
ret = QtGui.QPrintDialog(printer, parent).exec_()
if ret == QtGui.QDialog.Accepted:
if isinstance(obj, (QtGui.QTextDocument, QtGui.QTextEdit)):
obj.print_(printer)
elif hasattr(object, 'model'):
model = obj.model()
doc = modelToTextDocument(model)
obj.print_(printer)
elif isinstance(obj, QtCore.QAbstractItemModel):
doc = modelToTextDocument(obj)
doc.print_(printer)
else:
coreprint(obj, printer)
### QImage helpers ###########################################################
#from PyQt4.Qwt5 import toQImage as _toQImage
#def numpy2qimage(data):
# # @NOTE: for Qwt5 < 5.2.0
# # return toQImage(data.transpose())
# return _toQImage(data)
import numpy as np
GRAY_COLORTABLE = [QtGui.QColor(i, i, i).rgb() for i in range(256)]
def _aligned(data, nbyes=4):
h, w = data.shape
fact = nbyes / data.itemsize
shape = (h, np.ceil(w / float(fact)) * nbyes)
if shape != data.shape:
# build aligned matrix
image = np.zeros(shape, data.dtype)
image[:, 0:w] = data[:, 0:w]
else:
image = np.require(data, data.dtype, 'CO') # 'CAO'
return image
[docs]def numpy2qimage(data):
'''Convert a numpy array into a QImage.
.. note:: requires sip >= 4.7.5.
'''
colortable = None
if data.dtype in (np.uint8, np.ubyte, np.byte):
if data.ndim == 2:
h, w = data.shape
image = _aligned(data)
format_ = QtGui.QImage.Format_Indexed8
colortable = GRAY_COLORTABLE
elif data.ndim == 3 and data.shape[2] == 3:
h, w = data.shape[:2]
image = np.zeros((h, w, 4), data.dtype)
image[:, :, 2::-1] = data
image[..., -1] = 255
format_ = QtGui.QImage.Format_RGB32
elif data.ndim == 3 and data.shape[2] == 4:
h, w = data.shape[:2]
image = np.require(data, np.uint8, 'CO') # 'CAO'
format_ = QtGui.QImage.Format_ARGB32
else:
raise ValueError('unable to convert data: shape=%s, '
'dtype="%s"' % (data.shape,
np.dtype(data.dtype)))
elif data.dtype == np.uint16 and data.ndim == 2:
# @TODO: check
h, w = data.shape
image = _aligned(data)
format_ = QtGui.QImage.Format_RGB16
elif data.dtype == np.uint32 and data.ndim == 2:
h, w = data.shape
image = np.require(data, data.dtype, 'CO') # 'CAO'
#format_ = QtGui.QImage.Format_ARGB32
format_ = QtGui.QImage.Format_RGB32
else:
raise ValueError('unable to convert data: shape=%s, dtype="%s"' % (
data.shape, np.dtype(data.dtype)))
result = QtGui.QImage(image.data, w, h, format_)
result.ndarray = image
if colortable:
result.setColorTable(colortable)
return result
### Resources helpers #########################################################
[docs]def getuifile(name, package=None):
'''Return the ui file path.
It is assumed that Qt UI files are located in the "ui" subfolfer of
the package.
.. seealso:: :func:`gsdview.utils.getresource`
'''
return utils.getresource(os.path.join('ui', name), package)
[docs]def geticonfile(name, package=None):
'''Return the icon file path.
It is assumed that icon files are located in the "images" subfolder
of the package.
.. seealso:: :func:`gsdview.utils.getresource`
'''
return utils.getresource(os.path.join('images', name), package)
[docs]def geticon(name, package=None):
'''Build and return requested icon.
It is assumed that icon files are located in the "images" subfolder
of the package.
.. seealso:: :func:`gsdview.utils.getresource`
'''
iconfile = utils.getresource(os.path.join('images', name), package)
return QtGui.QIcon(iconfile)
### Misc helpers ##############################################################
[docs]def cfgToTextDocument(cfg, doc=None):
if doc is None:
doc = QtGui.QTextDocument()
cursor = QtGui.QTextCursor(doc)
cursor.movePosition(QtGui.QTextCursor.End)
# table style
tableformat = QtGui.QTextTableFormat()
tableformat.setTopMargin(10)
tableformat.setBottomMargin(10)
tableformat.setCellPadding(5)
tableformat.setCellSpacing(0)
tableformat.setBorderStyle(QtGui.QTextFrameFormat.BorderStyle_Solid)
tableformat.setHeaderRowCount(1)
# headers style
titleblockformat = QtGui.QTextBlockFormat()
titleblockformat.setTopMargin(20)
titleblockformat.setBottomMargin(10)
titleformat = QtGui.QTextCharFormat()
titleformat.setFontWeight(QtGui.QFont.Bold)
#titleformat.setPointSze(12)
# headers style
headerformat = QtGui.QTextCharFormat()
headerformat.setFontWeight(QtGui.QFont.Bold)
brush = headerformat.background()
brush.setColor(QtCore.Qt.lightGray)
brush.setStyle(QtCore.Qt.SolidPattern)
headerformat.setBackground(brush)
for section in cfg.sections():
items = sorted(cfg.items(section))
if not items:
continue
cursor.beginEditBlock()
cursor.movePosition(QtGui.QTextCursor.End)
# title
cursor.insertBlock(titleblockformat)
cursor.insertText(section, titleformat)
nrows = len(items)
ncols = 2
table = cursor.insertTable(nrows, ncols, tableformat)
#textformat = QtGui.QTextFormat()
for index, (key, value) in enumerate(items):
cell = table.cellAt(index, 0)
cellCursor = cell.firstCursorPosition()
cellCursor.insertText(key)
cell = table.cellAt(index, 1)
cellCursor = cell.firstCursorPosition()
cellCursor.insertText(value)
# horizontal header
headers = [doc.tr('Key'), doc.tr('Value')]
table.insertRows(0, 1)
for col, text in enumerate(headers):
cell = table.cellAt(0, col)
cell.setFormat(headerformat)
cellCursor = cell.firstCursorPosition()
cellCursor.insertText(text)
# vertical header
table.insertColumns(0, 1)
for row in range(1, nrows + 1):
text = str(row)
cell = table.cellAt(row, 0)
cell.setFormat(headerformat)
cellCursor = cell.firstCursorPosition()
cellCursor.insertText(text, headerformat)
cursor.endEditBlock()
return doc
[docs]def imgexport(obj, parent=None):
filters = [
obj.tr('All files (*)'),
obj.tr('Simple Vector Graphics file (*.svg)'),
obj.tr('PDF file (*.pdf)'),
obj.tr('PostScript file (*.ps)'),
]
filters.extend('%s file (*.%s)' % (str(fmt).upper(), str(fmt))
for fmt in QtGui.QImageWriter.supportedImageFormats())
formats = set(str(fmt).lower() for fmt in
QtGui.QImageWriter.supportedImageFormats())
formats.update(('svg', 'pdf', 'ps'))
# @TODO: check
if parent is None:
try:
parent = obj.window()
except AttributeError:
parent = None
target = os.path.join(utils.default_workdir(), 'image.jpeg')
filename, filter_ = QtGui.QFileDialog.getSaveFileNameAndFilter(
parent,
obj.tr('Save picture'),
target,
';;'.join(filters))
ext = 'unknown'
while filename and (ext not in formats):
ext = os.path.splitext(filename)[1]
if ext:
ext = ext[1:].lower()
if ext in formats:
break
else:
QtGui.QMessageBox.information(
parent,
obj.tr('Unknown file format'),
obj.tr('Unknown file format "%s".\n'
'Please retry.') % ext)
filename, filter_ = QtGui.QFileDialog.getSaveFileNameAndFilter(
parent,
obj.tr('Save draw'),
filename,
';;'.join(filters),
filter_)
else:
ext = 'unknown'
if filename:
if hasattr(obj, 'viewport'):
srcsize = obj.viewport().rect().size()
elif hasattr(obj, 'sceneRect'):
# QGraphicsViews alsa has a viewport method so they should be
# trapped by the previous check
srcsize = obj.sceneRect().toRect().size()
else:
srcsize = QtGui.QSize(800, 600)
if ext in ('pdf', 'ps'):
device = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
device.setOutputFileName(filename)
if ext == 'pdf':
device.setOutputFormat(QtGui.QPrinter.PdfFormat)
else:
# ext == 'ps'
device.setOutputFormat(QtGui.QPrinter.PostScriptFormat)
elif ext == 'svg':
device = QtSvg.QSvgGenerator()
device.setFileName(filename)
device.setSize(srcsize)
#device.setViewBox(obj.sceneRect().toRect())
#device.setTitle(obj.tr('Graphics Draw'))
#device.setDescription(obj.tr('Qt SVG drawing.'))
else:
device = QtGui.QPixmap(srcsize)
# @TODO: check
device.fill(QtCore.Qt.white)
painter = QtGui.QPainter()
if painter.begin(device):
#painter.setRenderHint(QtGui.QPainter.Antialiasing)
obj.render(painter)
painter.end()
if hasattr(device, 'save'):
device.save(filename)
else:
QtGui.QMessageBox.warning(
parent,
obj.tr('Warning'),
obj.tr('Unable initialize painting device.'))