"""
:author: Philip Lorenz (lorenzph@users.sourceforge.net)
:copyright: Copyright 2009 - 2010, Bergen Helms, The Virtual Product Development Group (Institute of Product Development, Technische Universitaet Muenchen)
:license: GPL
:contact: Bergen Helms (helms@pe.mw.tum.de)
:status: Development
"""
from PyQt4 import QtCore, QtGui
from model.project.project import Project
import PyQt4
class RequiredDocklet(object):
"""
Wrapper class to describe a required docklet.
"""
def __init__(self, clazz, initial_placement=None):
"""
Creates a new required docklet.
:param clazz: The class of the docklet.
:param initial_placement: The initial placement (must be of type
QtCore.Qt.DockWidgetArea).
"""
self._clazz = clazz
self._initial_placement = initial_placement
def __str__(self):
return "(clazz: %s initial_placement: %s)" % (self._clazz.__name__, self._initial_placement)
clazz = property(lambda self: self._clazz)
initial_placement = property(lambda self: self._initial_placement)
class StaticSignal(object):
"""
Helper class to enable static signals. This class
constructs a QObject instance and uses it to emit
the signals. Usage is completely opaque and it
behaves exactly like a pyqtSignal().
Example:::
my_signal = StaticSignal(QtCore.pyqtSignal())
...
# In a static context:
my_signal.connect(...)
my_signal.emit()
"""
def __init__(self, pyqt_signal):
super(StaticSignal, self).__init__()
class Wrapper(QtCore.QObject):
signal = pyqt_signal
self._wrapper = Wrapper()
def __getattr__(self, name):
return getattr(self._wrapper.signal, name)
class CentralWidget(QtGui.QWidget):
"""
This signal can be emitted to show a custom status message in the
status bar.
"""
show_status_message = QtCore.pyqtSignal(QtCore.QString)
"""
This signal is emitted if a new widget should be added to the top-level
container. In the case of a MainWindow instance a new tab containing
the given widget should be added.
"""
add_widget = StaticSignal(QtCore.pyqtSignal(QtGui.QWidget))
"""
This signal is emitted if a widget requests focus. In the case of
a MainWindow instance the tab containing the given widget should
be activated.
"""
raise_widget = StaticSignal(QtCore.pyqtSignal(QtGui.QWidget))
"""
Called when a widget should be removed.
"""
remove_widget = StaticSignal(QtCore.pyqtSignal(QtGui.QWidget))
"""
A set of required docklets. These docklets will be activated by the
:class:`MainWindow` when this CentralWidget is activated. The set should
contain one or more :class:`RequiredDocklet` instances which describe
the actual docklets.
Grouping docklets into a tabbed interface is possible by grouping them in a
sequence.
"""
_required_docklets = set()
"""
The name of this central widget. It will be used by the default
action() implementation to set the action's name.
"""
_name = "Unnamed"
_action = None
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
"""
Used to save the state of the dockables and toolbars.
"""
state = None
@classmethod
def name(cls):
"""
Returns the name of this central widget.
"""
return cls._name
@classmethod
def required_docklets(cls):
return set(cls._required_docklets)
def toolbars(self):
"""
This method is called by the parent main window in order to allow
this CentralWidget to own its own toolbar. You may return None if
no toolbar is needed. Otherwise either a single QToolBar object
or a list of QToolBar objects may be returned.
"""
return None
def equals(self, other):
"""
This method checks for semantic equality. I.e. a central widget
responsible for editing a type of instance A is equal to another
instance of this central widget editing the same instance of A.
Essentially this function is used to determine whether a new editor
widget should be created or another one can be reused.
"""
raise NotImplementedError()
def activated(self):
"""
This method gets called when this central widget is activated (i.e. shown to the user).
"""
pass
def deactivated(self):
"""
This method gets called when this central widget is deactived
(i.e. the user switches to a different tab).
"""
pass
def register_docklets(self, *docklets):
"""
This method gets called after all required docklets needed by this central widget have been instantiated.
"""
pass
@classmethod
def icon(cls):
"""
Returns the icon of this central widget. It will be displayed in the tab
bar.
:return: Either a :class:`QtCore.QICon` or None if no icon should be
displayed.
"""
return None
@classmethod
def action(cls):
"""
Returns a toolbar Action
:return: :class:`QtGui.QAction`
"""
raise NotImplementedError()
@classmethod
def _create_action(cls, callable, icon=None, name=None):
"""
Helper method to create a QAction instance for the action function.
This function fills the _action class variable according to the given
parameters.
"""
if cls._action is None:
name = name or cls.name()
icon = icon or cls.icon()
cls._action = QtGui.QAction(icon, QtCore.QCoreApplication.translate(cls.__name__, name), None)
cls._action.setCheckable(True)
cls._action.triggered.connect(callable)
return cls._action
@classmethod
def project(cls):
# TODO - Proper implementation
return Project.cur_project
class CentralProxyWidget(CentralWidget):
"""
Specialization of the :class:`CentralWidget` providing functionality
for proxying another CentralWidget until its value has been set.
For example this can be used to display a label until the model
which should be edited has been specified by the user.
"""
__active_widgets = []
_action = None
def __init__(self, parent = None):
#initialize Widget
CentralWidget.__init__(self, parent)
# Since at least SIP 4.10.5 and PyQt4 4.7.4 the destroyed()
# signal is not delivered to the object being destroyed any more
# Hence we need to connect the signal to a static slot defined
# on the class. However the obj parameter of destroyed does not
# equal the actual Python object being destroyed so a lookup
# in the active_widgets dict fails. This helper method works
# around this by storing a reference to self and invoking the
# destroyed handler using this reference.
def destroyHelper(obj=self, clazz=self.__class__):
def destroyMethod():
clazz._remove_from_active(obj)
return destroyMethod
self.destroyed.connect(destroyHelper())
self._proxy_widget = None
self._central_widget = None
self._docklets = []
self.setWindowTitle(self.tr("Untitled"))
self.setLayout(QtGui.QVBoxLayout())
@classmethod
def _remove_from_active(cls, obj=None):
"""
Removes this widget from the list of active widgets of this class.
:param obj: The object which should removed.
"""
if obj in cls._active_widgets():
cls._active_widgets().remove(obj)
def activated(self):
self.__proxy_call('activated')
# Move ourself to the back of _active_widgets - used to determine
# the previously opened tab.
self.__class__._active_widgets().remove(self)
self.__class__._active_widgets().append(self)
self.__class__._action.setChecked(True)
def deactivated(self):
self.__proxy_call('deactivated')
self._docklets = []
self.__class__._action.setChecked(False)
def closed(self):
self.__proxy_call('closed', None)
self.__class__._action.setChecked(False)
def toolbars(self):
return self.__proxy_call('toolbars')
def register_docklets(self, *docklets):
self._docklets = docklets
return self.__proxy_call('register_docklets', *self._docklets)
def equals(self, other):
equal = self.__proxy_call('equals', other)
if equal is None:
return self.__class__ == other.__class__
return equal
def _init(self, *args, **kw):
raise NotImplementedError()
@classmethod
def _active_widgets(cls):
# Add _active_widgets class variable if it hasn't been set.
if not '__active_widgets' in cls.__dict__:
setattr(cls, '__active_widgets', [])
return getattr(cls, '__active_widgets')
@classmethod
def open(cls, *args, **kw):
for widget in cls._active_widgets():
if widget._proxy_widget is None:
widget._init(*args, **kw)
cls.raise_widget.emit(widget)
return
widget = cls(*args, **kw)
cls._active_widgets().append(widget)
cls.add_widget.emit(widget)
@classmethod
def open_new(cls, *args, **kw):
active_widgets = cls._active_widgets()
if len(cls._active_widgets()) > 0:
cls.raise_widget.emit(active_widgets[len(active_widgets) - 1])
else:
cls.open(*args, **kw)
def _activate_proxy_widget(self, widget):
self._proxy_widget = widget
self.__proxy_call('activated')
self.__proxy_call('register_docklets', *self._docklets)
self.setWindowTitle(self._proxy_widget.windowTitle())
# Activate event filter - it propagates window title and modified changes
self._proxy_widget.installEventFilter(CentralProxyWidget.WindowFilter(self))
self._set_central_widget(widget)
def _connect_signals(self, docklets, mappings):
self.__handle_signals("connect", docklets, mappings)
def _disconnect_signals(self, docklets, mappings):
self.__handle_signals("disconnect", docklets, mappings)
def __proxy_call(self, method, *args, **kw):
"""
Proxies the given method to the _proxy_widget. If
_proxy_widget is None or the proxied object does not
implement the super class's method is called.
"""
callable = self._proxy_widget
if callable is None:
return None
if not hasattr(callable, method):
callable = super(CentralProxyWidget, self)
return getattr(callable, method)(*args, **kw)
def __handle_signals(self, action, docklets, mappings):
"""
Helper method connecting the signals of docklets to
the slots specified in mappings.
:param action: The action (either connect or disconnect)
:param docklets: a list of docklets
:param mappings: A mapping from docklet class and signal to slot.
For example:
{MyDocklet: {'mysignal': self._myslot}}
"""
if not hasattr(docklets, '__iter__'):
return
for docklet in docklets:
if docklet.__class__ not in mappings:
continue
maps = mappings[docklet.__class__]
for signal, slot in maps.iteritems():
getattr(getattr(docklet, signal), action)(slot)
def _set_empty_label(self, message):
"""
Helper function which sets the central widget of this placeholder to a
vertically & horizontally centered label displaying the given message.
:param message: The message to display.
"""
widget = QtGui.QLabel(message)
widget.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
self._set_central_widget(widget)
def _set_central_widget(self, widget):
"""
Sets the central widget of this placeholder, removing the existing one
if needed.
:param widget: The widget to set.
"""
if self._central_widget is not None:
self._central_widget.deleteLater()
self._central_widget = widget
self.layout().addWidget(widget)
class WindowFilter(QtCore.QObject):
"""
Handles changes of this widget's window title and updates the
title of the parent tab bar accordingly.
"""
def __init__(self, parent):
super(CentralProxyWidget.WindowFilter, self).__init__(parent)
def eventFilter(self, obj, evt):
if evt.type() == QtCore.QEvent.Close:
self.parent().__class__.remove_widget.emit(self.parent())
elif evt.type() in (QtCore.QEvent.WindowTitleChange, QtCore.QEvent.ModifiedChange):
self.parent().setWindowTitle(obj.windowTitle())
self.parent().setWindowModified(obj.isWindowModified())
return False