Source code for qconcurrency.widgets._dictmodelqmenu_

#!/usr/bin/env python
"""
Name :          qconcurrency.widgets._dictmodelqmenu_.py
Created :       Apr 16, 2017
Author :        Will Pittman
Contact :       willjpittman@gmail.com
________________________________________________________________________________
Description :   A QMenu, designed to work with a `DictModel`

                Supports nested-tables, and is kept up-to-date
                with changes from the model it is representing.
________________________________________________________________________________
"""
#builtin
from   __future__           import unicode_literals
from   __future__           import absolute_import
from   __future__           import division
from   __future__           import print_function
from   collections          import OrderedDict
import functools
import uuid
#external
from   Qt                   import QtWidgets, QtGui, QtCore
from   Qt.QtWidgets         import QSizePolicy
#internal
from   qconcurrency.models  import DictModel



[docs]class DictModelQMenu( QtWidgets.QMenu ): """ QMenu, whose actions/submenus are based/rebuilt from a :py:obj:`DictModel`. There are 2x display methods: * `indented`: every :py:obj:`DictModelRow` in the model is a action. Items in nested-tables are indented, and positioned under their parent. * `submenu`: :py:obj:`DictModelRow` s that contain child-tables are presented as submenus. Only bottom-level :py:obj:`DictModelRow` items are actions. Example: .. figure:: ../media/qconcurrency.widgets.DictModelQMenu_indented.png ``menustyle='indented'`` .. figure:: ../media/qconcurrency.widgets.DictModelQMenu_submenu.png ``menustyle='submenu'`` """ triggered_row = QtCore.Signal( QtCore.QModelIndex )
[docs] def __init__(self, dictmodel, menustyle='submenu', indexinfo={'id':'_id','name':'name'} ): """ dictmodel (DictModel): The model whose contents we are using to generate the popup menu contents. Nested tables will appear in sub-menus. menustyle (str, optional): ``(ex: 'submenu', 'indented' )`` This argument determines how to handle nested tables in the DictModel. * `submenu`: each item containing children will be implemented in the menu as a submenu. Only the items from the bottom-most nested-table can be selected. * `indented`: every item is selectable, child items are merely indented to indicate that they are children of their parent. indexinfo (dict, optional): ``(ex: {'id':'_id', 'name': 'name'} )`` A dictionary containing the keys `id` and `name`. The value of these keys represent the name (appears in qmenu) and it's corresponding databaseId. The values of these keys are column-names from the dictmodel. See :py:meth:`DictModel.__init__` """ QtWidgets.QMenu.__init__(self) if not isinstance( dictmodel, DictModel ): raise TypeError( '`dictmodel` argument expects a DictModel object. Received: %s' % repr(self) ) if menustyle not in ('submenu','indented'): raise RuntimeError(( 'the only available menustyles are "submenu", "indented". ' 'Received "%s"') % menustyle ) self._dictmodel = dictmodel self._indexinfo = indexinfo self._menustyle = menustyle self._submenus = {} # { uuid : submenu-widget } # (simply keeps references to qmenus so # they are note deleted when scope ends) # Connections self._dictmodel.itemChanged.connect( self._handle_modelchange ) # Load self._create_actions()
def _create_actions(self): if self._menustyle == 'submenu': return self._create_submenu_actions() elif self._menustyle == 'indented': return self._create_indented_actions() else: raise RuntimeError(( 'the only available menustyles are "submenu", "indented". ' 'Received "%s"') % menustyle ) def _create_submenu_actions(self, baseitem=None, submenu_of=None ): if baseitem is None: self.clear() self._submenus = {} baseitem = self._dictmodel for key in baseitem.keys(): modelitem = baseitem[ key ] _id = modelitem.columnval( self._indexinfo['id'] ) name = modelitem.columnval( self._indexinfo['name'] ) # if modelitem has children, create menu # instead of action menu = None if modelitem.rowCount(): menu = QtWidgets.QMenu( title=name ) self._submenus[ uuid.uuid4().hex ] = menu # keep reference on object, so not # deleted when method scope ends if menu: if submenu_of: submenu_of.addMenu( menu ) else: self.addMenu( menu ) # if modelitem does not have children, # add to menu as an action else: if submenu_of: submenu_of.addAction( name, functools.partial( self._emit_triggered_row, modelitem ) ) else: self.addAction( name, functools.partial( self._emit_triggered_row, modelitem ) ) # if modelitem has children, # recurse through children, adding # them this item's menu if modelitem.rowCount(): self._create_submenu_actions( baseitem=modelitem, submenu_of=menu ) def _create_indented_actions(self, baseitem=None, indent_lv=0 ): if baseitem is None: self.clear() baseitem = self._dictmodel for key in baseitem.keys(): modelitem = baseitem[ key ] _id = modelitem.columnval( self._indexinfo['id'] ) name = modelitem.columnval( self._indexinfo['name'] ) indent = ' '* (3*indent_lv) self.addAction( indent+name, functools.partial( self._emit_triggered_row, modelitem ) ) # if modelitem has children, # recurse through children, adding # them this item's menu if modelitem.rowCount(): self._create_indented_actions( baseitem=modelitem, indent_lv=indent_lv+1 ) def _emit_triggered_row(self, modelitem ): """ We cannot meaningfully bind actions to the menu from a Model, but we can emit a signal with information about what was just selected. """ if modelitem: self.triggered_row.emit( modelitem.index() ) def _handle_modelchange(self, *args, **kwds): """ Whenever the model changes, recreate the QMenu actions/submenus. """ self._create_actions()
if __name__ == '__main__': #external from Qt import QtWidgets #internal from qconcurrency import QApplication from qconcurrency.models import DictModel def printargs(*args,**kwds): print( args ) print( kwds ) def test_submenu_style(): with QApplication(): model = DictModel( columns=['name'] ) model.add_row( 1, {'name':'one'} ) model.add_row( 2, {'name':'two'} ) model.add_row( 3, {'name':'three'} ) model[1].add_child( '1a', {'name':'one-a'} ) model[1].add_child( '1b', {'name':'one-b'} ) menu = DictModelQMenu( model, menustyle='submenu', indexinfo={'id':'_id','name':'name'} ) button = QtWidgets.QPushButton('press me') button.setMenu( menu ) menu.triggered_row.connect( printargs ) button.show() def test_indented_style(): with QApplication(): model = DictModel( columns=['name'] ) model.add_row( 1, {'name':'one'} ) model.add_row( 2, {'name':'two'} ) model.add_row( 3, {'name':'three'} ) model[1].add_child( '1a', {'name':'one-a'} ) model[1].add_child( '1b', {'name':'one-b'} ) menu = DictModelQMenu( model, menustyle='indented', indexinfo={'id':'_id','name':'name'} ) button = QtWidgets.QPushButton('press me') button.setMenu( menu ) menu.triggered_row.connect( printargs ) button.show() test_submenu_style() #test_indented_style()