/umbra/ui/views.py
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)