PageRenderTime 116ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/umbra/ui/views.py

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