PageRenderTime 2ms CodeModel.GetById 23ms app.highlight 10ms RepoModel.GetById 0ms app.codeStats 0ms

/umbra/ui/views.py

https://github.com/KelSolaar/Umbra
Python | 541 lines | 193 code | 98 blank | 250 comment | 37 complexity | fb69fa59a86222ce9351cbd3a5409094 MD5 | raw file
  1#!/usr/bin/env python
  2# -*- coding: utf-8 -*-
  3
  4"""
  5**views.py**
  6
  7**Platform:**
  8    Windows, Linux, Mac Os X.
  9
 10**Description:**
 11    Defines the Application Views classes.
 12
 13**Others:**
 14
 15"""
 16
 17from __future__ import unicode_literals
 18
 19import re
 20from PyQt4.QtCore import QAbstractItemModel
 21from PyQt4.QtCore import QAbstractListModel
 22from PyQt4.QtCore import QAbstractTableModel
 23from PyQt4.QtCore import QEvent
 24from PyQt4.QtCore import QObject
 25from PyQt4.QtCore import QString
 26from PyQt4.QtGui import QColor
 27from PyQt4.QtGui import QItemSelection
 28from PyQt4.QtGui import QItemSelectionModel
 29from PyQt4.QtGui import QListView
 30from PyQt4.QtGui import QTableView
 31from PyQt4.QtGui import QTreeView
 32from PyQt4.QtGui import QListWidget
 33from PyQt4.QtGui import QTableWidget
 34from PyQt4.QtGui import QTreeWidget
 35
 36import foundations.exceptions
 37import foundations.walkers
 38import foundations.verbose
 39import umbra.exceptions
 40import umbra.ui.common
 41from umbra.ui.models import GraphModel
 42from umbra.ui.widgets.notification_QLabel import Notification_QLabel
 43
 44__author__ = "Thomas Mansencal"
 45__copyright__ = "Copyright (C) 2008 - 2014 - Thomas Mansencal"
 46__license__ = "GPL V3.0 - http://www.gnu.org/licenses/"
 47__maintainer__ = "Thomas Mansencal"
 48__email__ = "thomas.mansencal@gmail.com"
 49__status__ = "Production"
 50
 51__all__ = ["LOGGER",
 52           "ReadOnlyFilter",
 53           "Mixin_AbstractBase"
 54           "Mixin_AbstractView",
 55           "Mixin_AbstractWidget",
 56           "Abstract_QListView",
 57           "Abstract_QTableView",
 58           "Abstract_QTreeView",
 59           "Abstract_QListWidget",
 60           "Abstract_QTableWidget",
 61           "Abstract_QTreeWidget"]
 62
 63LOGGER = foundations.verbose.install_logger()
 64
 65
 66class ReadOnlyFilter(QObject):
 67    """
 68    Defines a `QObject <http://doc.qt.nokia.com/qobject.html>`_ subclass used as an event filter
 69    for the :class:`Abstract_QListView` and :class:`Abstract_QTreeView` classes.
 70    """
 71
 72    def eventFilter(self, object, event):
 73        """
 74        Reimplements the **QObject.eventFilter** method.
 75
 76        :param object: Object.
 77        :type object: QObject
 78        :param event: Event.
 79        :type event: QEvent
 80        :return: Event filtered.
 81        :rtype: bool
 82        """
 83
 84        if event.type() == QEvent.MouseButtonDblClick:
 85            view = object.parent()
 86            if view.read_only:
 87                self.__raise_user_error(view)
 88                return True
 89        return False
 90
 91    @foundations.exceptions.handle_exceptions(umbra.exceptions.notify_exception_handler,
 92                                              foundations.exceptions.UserError)
 93    def __raise_user_error(self, view):
 94        """
 95        Raises an error if the given View has been set read only and the user attempted to edit its content.
 96
 97        :param view: View.
 98        :type view: QWidget
 99        """
100
101        raise foundations.exceptions.UserError("{0} | Cannot perform action, '{1}' View has been set read only!".format(
102            self.__class__.__name__, view.objectName() or view))
103
104
105class Mixin_AbstractBase(object):
106    """
107    Defines the base mixin used to bring common capabilities in Application Views classes.
108    """
109
110    def __init__(self, message=None):
111        """
112        Initializes the class.
113
114        :param parent: Object parent.
115        :type parent: QObject
116        :param message: View default message when Model is empty.
117        :type message: unicode
118        """
119
120        LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
121
122        # --- Setting class attributes. ---
123        self.__message = None
124        self.message = message or "No Item to view!"
125
126        self.__notifier = Notification_QLabel(self,
127                                              color=QColor(192, 192, 192),
128                                              background_color=QColor(24, 24, 24),
129                                              border_color=QColor(32, 32, 32),
130                                              anchor=8)
131
132    @property
133    def message(self):
134        """
135        Property for **self.__message** attribute.
136
137        :return: self.__message.
138        :rtype: unicode
139        """
140
141        return self.__message
142
143    @message.setter
144    @foundations.exceptions.handle_exceptions(AssertionError)
145    def message(self, value):
146        """
147        Setter for **self.__message** attribute.
148
149        :param value: Attribute value.
150        :type value: unicode
151        """
152
153        if value is not None:
154            assert type(value) in (unicode, QString), \
155                "'{0}' attribute: '{1}' type is not 'unicode' or 'QString'!".format("message", value)
156        self.__message = value
157
158    @message.deleter
159    @foundations.exceptions.handle_exceptions(foundations.exceptions.ProgrammingError)
160    def message(self):
161        """
162        Deleter for **self.__message** attribute.
163        """
164
165        raise foundations.exceptions.ProgrammingError(
166            "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "message"))
167
168    def resizeEvent(self, event):
169        """
170        Reimplements the :meth:`*.resizeEvent` method.
171
172        :param event: QEvent.
173        :type event: QEvent
174        """
175
176        super(type(self), self).resizeEvent(event)
177
178        self.__notifier.refresh_position()
179
180    def paintEvent(self, event):
181        """
182        Reimplements the :meth:`*.paintEvent` method.
183
184        :param event: QEvent.
185        :type event: QEvent
186        """
187
188        super(type(self), self).paintEvent(event)
189
190        show_message = True
191        model = self.model()
192        if issubclass(type(model), GraphModel):
193            if model.has_nodes():
194                show_message = False
195        elif issubclass(type(model), QAbstractItemModel) or \
196                issubclass(type(model), QAbstractListModel) or \
197                issubclass(type(model), QAbstractTableModel):
198            if model.rowCount():
199                show_message = False
200
201        if show_message:
202            self.__notifier.show_message(self.__message, 0)
203        else:
204            self.__notifier.hide_message()
205
206
207class Mixin_AbstractView(Mixin_AbstractBase):
208    """
209    Defines a mixin used to bring common capabilities in Application Views classes.
210    """
211
212    def __init__(self, read_only=None, message=None):
213        """
214        Initializes the class.
215
216        :param parent: Object parent.
217        :type parent: QObject
218        :param read_only: View is read only.
219        :type read_only: bool
220        :param message: View default message when Model is empty.
221        :type message: unicode
222        """
223
224        LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
225
226        Mixin_AbstractBase.__init__(self, message or "No Node to view!")
227
228        # --- Setting class attributes. ---
229        self.__read_only = read_only
230
231        Mixin_AbstractView.__initialize_ui(self)
232
233    @property
234    def read_only(self):
235        """
236        Property for **self.__read_only** attribute.
237
238        :return: self.__read_only.
239        :rtype: bool
240        """
241
242        return self.__read_only
243
244    @read_only.setter
245    @foundations.exceptions.handle_exceptions(AssertionError)
246    def read_only(self, value):
247        """
248        Setter for **self.__read_only** attribute.
249
250        :param value: Attribute value.
251        :type value: bool
252        """
253
254        if value is not None:
255            assert type(value) is bool, "'{0}' attribute: '{1}' type is not 'bool'!".format("read_only", value)
256        self.__read_only = value
257
258    @read_only.deleter
259    @foundations.exceptions.handle_exceptions(foundations.exceptions.ProgrammingError)
260    def read_only(self):
261        """
262        Deleter for **self.__read_only** attribute.
263        """
264
265        raise foundations.exceptions.ProgrammingError(
266            "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "read_only"))
267
268    def __initialize_ui(self):
269        """
270        Initializes the View ui.
271        """
272
273        self.viewport().installEventFilter(ReadOnlyFilter(self))
274
275        if issubclass(type(self), QListView):
276            super(type(self), self).setUniformItemSizes(True)
277        elif issubclass(type(self), QTreeView):
278            super(type(self), self).setUniformRowHeights(True)
279
280    def get_nodes(self):
281        """
282        Returns the View nodes.
283
284        :return: View nodes.
285        :rtype: list
286        """
287
288        return [node for node in foundations.walkers.nodes_walker(self.model().root_node)]
289
290    def filter_nodes(self, pattern, attribute, flags=re.IGNORECASE):
291        """
292        Filters the View Nodes on given attribute using given pattern.
293
294        :param pattern: Filtering pattern.
295        :type pattern: unicode
296        :param attribute: Filtering attribute.
297        :type attribute: unicode
298        :param flags: Regex filtering flags.
299        :type flags: int
300        :return: View filtered nodes.
301        :rtype: list
302        """
303
304        return [node for node in self.get_nodes() if re.search(pattern, getattr(node, attribute), flags)]
305
306    @foundations.exceptions.handle_exceptions(NotImplementedError)
307    # TODO: Implement a way to invalidate indexes in the cache, disabling the cache until yet.
308    # @foundations.decorators.memoize(None)
309    def get_view_nodes_from_indexes(self, *indexes):
310        """
311        Returns the View Nodes from given indexes.
312
313        :param view: View.
314        :type view: QWidget
315        :param \*indexes: Indexes.
316        :type \*indexes: list
317        :return: View nodes.
318        :rtype: dict
319        """
320
321        nodes = {}
322        model = self.model()
323        if not model:
324            return nodes
325
326        if not hasattr(model, "get_node"):
327            raise NotImplementedError(
328                "{0} | '{1}' Model doesn't implement a 'get_node' method!".format(__name__, model))
329
330        if not hasattr(model, "get_attribute"):
331            raise NotImplementedError(
332                "{0} | '{1}' Model doesn't implement a 'get_attribute' method!".format(__name__, model))
333
334        for index in indexes:
335            node = model.get_node(index)
336            if not node in nodes:
337                nodes[node] = []
338            attribute = model.get_attribute(node, index.column())
339            attribute and nodes[node].append(attribute)
340        return nodes
341
342    def get_view_selected_nodes(self):
343        """
344        Returns the View selected nodes.
345
346        :param view: View.
347        :type view: QWidget
348        :return: View selected nodes.
349        :rtype: dict
350        """
351
352        return self.get_view_nodes_from_indexes(*self.selectedIndexes())
353
354    def get_selected_nodes(self):
355        """
356        Returns the View selected nodes.
357
358        :return: View selected nodes.
359        :rtype: dict
360        """
361
362        return self.get_view_selected_nodes()
363
364    def select_view_indexes(self, indexes, flags=QItemSelectionModel.Select | QItemSelectionModel.Rows):
365        """
366        Selects the View given indexes.
367
368        :param view: View.
369        :type view: QWidget
370        :param indexes: Indexes to select.
371        :type indexes: list
372        :param flags: Selection flags. ( QItemSelectionModel.SelectionFlags )
373        :return: Definition success.
374        :rtype: bool
375        """
376
377        if self.selectionModel():
378            selection = QItemSelection()
379            for index in indexes:
380                selection.merge(QItemSelection(index, index), flags)
381            self.selectionModel().select(selection, flags)
382        return True
383
384    def select_indexes(self, indexes, flags=QItemSelectionModel.Select | QItemSelectionModel.Rows):
385        """
386        Selects given indexes.
387
388        :param indexes: Indexes to select.
389        :type indexes: list
390        :param flags: Selection flags. ( QItemSelectionModel.SelectionFlags )
391        :return: Method success.
392        :rtype: bool
393        """
394
395        return self.select_view_indexes(indexes, flags)
396
397
398class Mixin_AbstractWidget(Mixin_AbstractBase):
399    """
400    Defines a mixin used to bring common capabilities in Application Widgets Views classes.
401    """
402
403    pass
404
405
406class Abstract_QListView(QListView, Mixin_AbstractView):
407    """
408    Defines a `QListView <http://doc.qt.nokia.com/qlistview.html>`_ subclass used as base
409    by others Application Views classes.
410    """
411
412    def __init__(self, parent=None, read_only=False, message=None):
413        """
414        Initializes the class.
415
416        :param parent: Object parent.
417        :type parent: QObject
418        :param read_only: View is read only.
419        :type read_only: bool
420        :param message: View default message when Model is empty.
421        :type message: unicode
422        """
423
424        LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
425
426        QListView.__init__(self, parent)
427        Mixin_AbstractView.__init__(self, read_only, message)
428
429
430class Abstract_QTableView(QTableView, Mixin_AbstractView):
431    """
432    Defines a `QTableView <http://doc.qt.nokia.com/qtableview.html>`_ subclass used as base
433    by others Application Views classes.
434    """
435
436    def __init__(self, parent=None, read_only=False, message=None):
437        """
438        Initializes the class.
439
440        :param parent: Object parent.
441        :type parent: QObject
442        :param read_only: View is read only.
443        :type read_only: bool
444        :param message: View default message when Model is empty.
445        :type message: unicode
446        """
447
448        LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
449
450        QTableView.__init__(self, parent)
451        Mixin_AbstractView.__init__(self, read_only, message)
452
453
454class Abstract_QTreeView(QTreeView, Mixin_AbstractView):
455    """
456    Defines a `QTreeView <http://doc.qt.nokia.com/qtreeview.html>`_ subclass used as base
457    by others Application Views classes.
458    """
459
460    def __init__(self, parent=None, read_only=False, message=None):
461        """
462        Initializes the class.
463
464        :param parent: Object parent.
465        :type parent: QObject
466        :param read_only: View is read only.
467        :type read_only: bool
468        :param message: View default message when Model is empty.
469        :type message: unicode
470        """
471
472        LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
473
474        QTreeView.__init__(self, parent)
475        Mixin_AbstractView.__init__(self, read_only, message)
476
477
478class Abstract_QListWidget(QListWidget, Mixin_AbstractWidget):
479    """
480    Defines a `QListWidget <http://doc.qt.nokia.com/qlistwidget.html>`_ subclass used as base
481    by others Application Widgets Views classes.
482    """
483
484    def __init__(self, parent=None, message=None):
485        """
486        Initializes the class.
487
488        :param parent: Object parent.
489        :type parent: QObject
490        :param message: View default message when Model is empty.
491        :type message: unicode
492        """
493
494        LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
495
496        QListWidget.__init__(self, parent)
497        Mixin_AbstractWidget.__init__(self, message)
498
499
500class Abstract_QTableWidget(QTableWidget, Mixin_AbstractWidget):
501    """
502    Defines a `QTableWidget <http://doc.qt.nokia.com/qtablewidget.html>`_ subclass used as base
503    by others Application Widgets Views classes.
504    """
505
506    def __init__(self, parent=None, read_only=False, message=None):
507        """
508        Initializes the class.
509
510        :param parent: Object parent.
511        :type parent: QObject
512        :param message: View default message when Model is empty.
513        :type message: unicode
514        """
515
516        LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
517
518        QTableWidget.__init__(self, parent)
519        Mixin_AbstractWidget.__init__(self, message)
520
521
522class Abstract_QTreeWidget(QTreeWidget, Mixin_AbstractWidget):
523    """
524    Defines a `QTreeWidget <http://doc.qt.nokia.com/qtreewidget.html>`_ subclass used as base
525    by others Application Widgets Views classes.
526    """
527
528    def __init__(self, parent=None, message=None):
529        """
530        Initializes the class.
531
532        :param parent: Object parent.
533        :type parent: QObject
534        :param message: View default message when Model is empty.
535        :type message: unicode
536        """
537
538        LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__))
539
540        QTreeWidget.__init__(self, parent)
541        Mixin_AbstractWidget.__init__(self, message)