Source code for guidata.qthelpers
# -*- coding: utf-8 -*-
#
# Copyright © 2009-2010 CEA
# Pierre Raybaut
# Licensed under the terms of the CECILL License
# (see guidata/__init__.py for details)
"""
qthelpers
---------
The ``guidata.qthelpers`` module provides helper functions for developing
easily Qt-based graphical user interfaces.
"""
import sys, os, os.path as osp
from PyQt4.QtGui import (QAction, QApplication, QColor, QCursor, QFileDialog,
QFrame, QGridLayout, QHBoxLayout, QIcon, QKeySequence,
QLabel, QLineEdit, QListWidget, QListWidgetItem, QMenu,
QPainter, QPen, QPushButton, QStyle, QToolButton,
QVBoxLayout, QWidget)
from PyQt4.QtCore import SIGNAL, QString, QSize, Qt
# Local imports:
from guidata.configtools import get_icon, get_family
from guidata.config import _
[docs]def text_to_qcolor(text):
"""Create a QColor from specified string"""
color = QColor()
if isinstance(text, QString):
text = str(text)
if not isinstance(text, (unicode, str)):
return color
if text.startswith('#') and len(text)==7:
correct = '#0123456789abcdef'
for char in text:
if char.lower() not in correct:
return color
elif text not in list(QColor.colorNames()):
return color
color.setNamedColor(text)
return color
[docs]def create_action(parent, title, triggered=None, toggled=None,
shortcut=None, icon=None, tip=None,
context=Qt.WindowShortcut):
"""
Create a new QAction
"""
action = QAction(title, parent)
if triggered:
parent.connect(action, SIGNAL("triggered(bool)"), triggered)
if toggled:
parent.connect(action, SIGNAL("toggled(bool)"), toggled)
action.setCheckable(True)
if icon is not None:
assert isinstance(icon, QIcon)
action.setIcon( icon )
if shortcut is not None:
action.setShortcut(shortcut)
if tip is not None:
action.setToolTip(tip)
action.setStatusTip(tip)
action.setShortcutContext(context)
return action
[docs]def keybinding(attr):
"""Return keybinding"""
ks = getattr(QKeySequence, attr)
return QKeySequence.keyBindings(ks)[0].toString()
[docs]def add_separator(target):
"""Add separator to target only if last action is not a separator"""
target_actions = list(target.actions())
if target_actions:
if not target_actions[-1].isSeparator():
target.addSeparator()
[docs]def add_actions(target, actions):
"""
Add actions (list of QAction instances) to target (menu, toolbar)
"""
for action in actions:
if isinstance(action, QAction):
target.addAction(action)
elif isinstance(action, QMenu):
target.addMenu(action)
elif action is None:
add_separator(target)
[docs]def get_std_icon(name, size=None):
"""
Get standard platform icon
Call 'show_std_icons()' for details
"""
if not name.startswith('SP_'):
name = 'SP_'+name
icon = QWidget().style().standardIcon( getattr(QStyle, name) )
if size is None:
return icon
else:
return QIcon( icon.pixmap(size, size) )
[docs]class ShowStdIcons(QWidget):
"""
Dialog showing standard icons
"""
def __init__(self, parent):
super(ShowStdIcons, self).__init__(parent)
layout = QHBoxLayout()
row_nb = 14
cindex = 0
for child in dir(QStyle):
if child.startswith('SP_'):
if cindex == 0:
col_layout = QVBoxLayout()
icon_layout = QHBoxLayout()
icon = get_std_icon(child)
label = QLabel()
label.setPixmap(icon.pixmap(32, 32))
icon_layout.addWidget( label )
icon_layout.addWidget( QLineEdit(child.replace('SP_', '')) )
col_layout.addLayout(icon_layout)
cindex = (cindex+1) % row_nb
if cindex == 0:
layout.addLayout(col_layout)
self.setLayout(layout)
self.setWindowTitle('Standard Platform Icons')
self.setWindowIcon(get_std_icon('TitleBarMenuButton'))
[docs]def show_std_icons():
"""
Show all standard Icons
"""
app = QApplication(sys.argv)
dialog = ShowStdIcons(None)
dialog.show()
sys.exit(app.exec_())
from math import cos, sin, pi
[docs]class RotatedLabel(QLabel):
"""
Rotated QLabel
(rich text is not supported)
Arguments:
* parent: parent widget
* angle=270 (int): rotation angle in degrees
* family (string): font family
* bold (bool): font weight
* italic (bool): font italic style
* color (QColor): font color
"""
def __init__(self, text, parent=None, angle=270,
family=None, bold=False, italic=False, color=None):
QLabel.__init__(self, text, parent)
font = self.font()
if family is not None:
font.setFamily(get_family(family))
font.setBold(bold)
font.setItalic(italic)
self.setFont(font)
self.color = color
self.angle = angle
self.setAlignment(Qt.AlignCenter)
def paintEvent(self, evt):
painter = QPainter(self)
if self.color is not None:
painter.setPen(QPen(self.color))
painter.rotate(self.angle)
transform = painter.transform().inverted()[0]
rct = transform.mapRect(self.rect())
painter.drawText(rct, self.alignment(), self.text())
def sizeHint(self):
hint = QLabel.sizeHint(self)
width, height = hint.width(), hint.height()
angle = self.angle*pi/180
rotated_width = abs(width*cos(angle))+abs(height*sin(angle))
rotated_height = abs(width*sin(angle))+abs(height*cos(angle))
return QSize(rotated_width, rotated_height)
def minimumSizeHint(self):
return self.sizeHint()
class QExpander(QFrame):
def __init__(self, parent, title, widget):
QFrame.__init__(self, parent)
self.btn = QPushButton(get_icon("expander_right.png"), title)
self.btn.setFlat(True)
self.connect(self.btn, SIGNAL("clicked()"), self.arrow_clicked)
self.widget = widget
self.widget.setVisible(False)
vbox = QVBoxLayout(self)
vbox.addWidget(self.btn)
vbox.addWidget(self.widget)
self.setLayout(vbox)
vbox.setContentsMargins(0, 0, 0, 0)
def arrow_clicked(self):
if self.widget.isHidden():
self.widget.setVisible(True)
self.btn.setIcon(get_icon("expander_down.png"))
else:
self.widget.setVisible(False)
self.btn.setIcon(get_icon("expander_right.png"))
class ToolbarListWidget(QWidget):
def __init__(self, parent, title, multiple_selection=False):
super(ToolbarListWidget, self).__init__(parent)
self.title = title
self.data = None
self.layout = QVBoxLayout()
# Titre
title_label = QLabel(title)
title_label.setAlignment(Qt.AlignCenter)
self.layout.addWidget(title_label)
# Liste
self._listwidget = ListWidget_with_ContextMenu(parent,
multiple_selection)
self.layout.addWidget(self._listwidget)
self.connect(self._listwidget, SIGNAL("itemSelectionChanged()"),
self.selection_changed)
self.connect(self._listwidget, SIGNAL("currentRowChanged(int)"),
self.current_row_changed)
# One Item Selection actions
self.single_actions = []
# Multiple Item Selection actions
self.multiple_actions = []
self.setLayout(self.layout)
def set_selected_item_actions(self, single=[], multiple=[]):
"""Set selection dependent actions"""
self.single_actions = single
self.multiple_actions = multiple
self.selection_changed()
def set_toolbar(self, actions):
"""Create toolbar"""
if not isinstance(actions, list):
actions = [actions]
layout = QGridLayout()
lig = 0
col = 0
nbcol = 2
for index, action in enumerate(actions):
if isinstance(action, QAction):
button = QToolButton()
button.setDefaultAction(action)
button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
button.setAutoRaise(True)
if index == len(actions)-1 and col == 0:
colspan = -1
else:
colspan = 1
layout.addWidget(button, lig, col, 1, colspan, Qt.AlignCenter)
col += 1
if col == nbcol:
lig += 1
col = 0
else:
# On ignore les séparateurs
pass
self.layout.addLayout(layout)
def set_items(self, data):
"""data: liste d'objets ayant un attribut 'titre'"""
self._listwidget.clear()
self.data = list(data)
for item in self.data:
listitem = QListWidgetItem(item.titre, self._listwidget)
self.set_item_style(listitem, item)
self._listwidget.addItem(listitem)
def set_item_style(self, listitem, item):
pass
def get_row(self, i):
return self.data[i]
def select_row(self, i):
if i<0:
i = self._listwidget.count()+i
self._listwidget.setCurrentRow(i)
def select_guid(self, guid):
for i,item in enumerate(self.data):
if item.guid == guid:
self.select_row(i)
return
def row_count(self):
return self._listwidget.count()
def get_selected_rows(self):
items = self._listwidget.selectedItems()
return [self.data[self._listwidget.row(itm)] for itm in items]
def selection_changed(self):
"""Refreshing actions"""
selitems = self._listwidget.selectedItems()
for action in self.multiple_actions:
action.setEnabled(len(selitems) > 1)
for action in self.single_actions:
action.setEnabled(len(selitems) >= 1)
def current_row_changed(self, i_row):
"""Current row has just changed"""
pass
[docs]def open_file(parent, filename=None,
title=_(u"Open a file"),
filetypes=_(u"All")+" (*.*)",
callback=None,
opening_message=_(u"Opening ")):
"""
Generic method for opening a file.
Returns the file name and the result of the callback.
"""
if not filename:
# For recent files
action = parent.sender()
if isinstance(action, QAction):
filename = unicode(action.data().toString())
if not filename:
_temp1, _temp2 = sys.stdout, sys.stderr
sys.stdout, sys.stderr = None, None
filename = QFileDialog.getOpenFileName(parent, title,
os.getcwdu(), filetypes)
sys.stdout, sys.stderr = _temp1, _temp2
if filename:
filename = unicode(filename)
os.chdir(osp.dirname(filename))
parent.statusBar().showMessage(opening_message+filename)
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
parent.repaint()
try:
if callback is not None:
result = callback(filename)
else:
result = None
finally:
parent.statusBar().clearMessage()
QApplication.restoreOverrideCursor()
return filename, result
if __name__ == "__main__":
from guidata.utils import pairs
print list( pairs( range(5) ) )
show_std_icons()