• Facebook
  • Twitter
  • Reddit
  • StumbleUpon
  • Digg
  • email

"""
 
: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