Ejemplo n.º 1
0
class TaurusForm(TaurusWidget):
    '''A form containing specific widgets for interacting with
    a given list of taurus attributes and/or devices.

    Its model is a list of attribute and/or device names to be shown. Each item
    is represented in a row consisting of a label, a read widget, a write
    widget, a units widget and an "extra" widget (some of them may not be shown)
    which are vertically aligned with their counterparts from other items.

    By default a :class:`TaurusValue` object is used for each item, but this
    can be changed and specific mappings can be defined using the
    :meth:`setCustomWidgetMap` method.

    Item objects can be accessed by index using a list-like notation::

      form = TaurusForm()
      form.model = ['sys/tg_test/1'+a for a in ('short_image','/float_scalar','/double_scalar')]
      form[0].labelConfig = 'dev_alias'
      form[-1].writeWidgetClass = 'TaurusWheelEdit'
      print(len(form))  # --> outputs '3' (the length of the form is the number of items)

    By default, the form provides global Apply and Cancel buttons.

    You can also see some code that exemplifies the use of TaurusForm in :ref:`Taurus
    coding examples <examples>` '''

    def __init__(self, parent=None,
                 formWidget=None,
                 buttons=None,
                 withButtons=True,
                 designMode=False):

        self._children = []
        TaurusWidget.__init__(self, parent, designMode)

        if buttons is None:
            buttons = Qt.QDialogButtonBox.Apply | \
                Qt.QDialogButtonBox.Reset
        self._customWidgetMap = {}
        self._model = []
        # self._children = []
        self.setFormWidget(formWidget)

        self.setLayout(Qt.QVBoxLayout())

        frame = TaurusWidget()
        frame.setLayout(Qt.QGridLayout())

        self.scrollArea = TaurusScrollArea(self)
        self.scrollArea.setWidget(frame)
        self.scrollArea.setWidgetResizable(True)
        self.layout().addWidget(self.scrollArea)
        self.__modelChooserDlg = None

        self.buttonBox = QButtonBox(buttons=buttons, parent=self)
        self.layout().addWidget(self.buttonBox)

        self._connectButtons()

        # Actions (they automatically populate the context menu)
        self.setContextMenuPolicy(Qt.Qt.ActionsContextMenu)

        self.chooseModelsAction = Qt.QAction('Modify Contents', self)
        self.addAction(self.chooseModelsAction)
        self.chooseModelsAction.triggered.connect(self.chooseModels)

        self.showButtonsAction = Qt.QAction('Show Buttons', self)
        self.showButtonsAction.setCheckable(True)
        self.addAction(self.showButtonsAction)
        self.showButtonsAction.triggered[bool].connect(self.setWithButtons)
        self.setWithButtons(withButtons)

        self.changeLabelsAction = Qt.QAction('Change labels (all items)', self)
        self.addAction(self.changeLabelsAction)
        self.changeLabelsAction.triggered.connect(self.onChangeLabelsAction)

        self.compactModeAction = Qt.QAction('Compact mode (all items)', self)
        self.compactModeAction.setCheckable(True)
        self.addAction(self.compactModeAction)
        self.compactModeAction.triggered[bool].connect(self.setCompact)

        self.setFormatterAction = Qt.QAction('Set formatter (all items)', self)
        self.addAction(self.setFormatterAction)
        self.setFormatterAction.triggered.connect(self.onSetFormatter)

        self.resetModifiableByUser()
        self.setSupportedMimeTypes([TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_DEV_MIME_TYPE,
                                    TAURUS_ATTR_MIME_TYPE, TAURUS_MODEL_MIME_TYPE, 'text/plain'])

        self.resetCompact()

        # properties
        self.registerConfigProperty(
            self.isWithButtons, self.setWithButtons, 'withButtons')
        self.registerConfigProperty(self.isCompact, self.setCompact, 'compact')

    def __getitem__(self, key):
        '''provides a list-like interface: items of the form can be accessed using slice notation'''
        return self.getItemByIndex(key)

    def __len__(self):
        '''returns the number of items contained by the form'''
        return len(self.getItems())

    def _splitModel(self, modelNames):
        '''convert str to list if needed (commas and whitespace are considered as separators)'''
        if isinstance(modelNames, binary_type):
            modelNames = modelNames.decode()
        if isinstance(modelNames, string_types):
            modelNames = str(modelNames).replace(',', ' ')
            modelNames = modelNames.split()
        return modelNames

    def setCustomWidgetMap(self, cwmap):
        '''Sets a map map for custom widgets.

        :param cwmap: (dict<str,tuple>) a dictionary whose keys are device
                      type strings (i.e. see :class:`PyTango.DeviceInfo`) and
                      whose values are tuples of classname,args,kwargs
        '''
        # TODO: tango-centric
        self._customWidgetMap = cwmap

    def getCustomWidgetMap(self):
        '''Returns the map used to create custom widgets.

        :return: (dict<str,tuple>) a dictionary whose keys are device
                 type strings (i.e. see :class:`PyTango.DeviceInfo`) and whose
                 values are tuples of classname,args,kwargs
        '''
        # TODO: tango-centric
        return self._customWidgetMap

    @Qt.pyqtSlot('QString', name='modelChanged')
    def parentModelChanged(self, parentmodel_name):
        self.info("Parent model changed to '%s'" % parentmodel_name)
        parentmodel_name = str(parentmodel_name)
        if self.getUseParentModel():
            # reset the model of childs
            for obj, model in zip(self.getItems(), self.getModel()):
                obj.setModel('%s/%s' % (parentmodel_name, str(model)))
        else:
            self.debug(
                "received event from parent although not using parent model")

    def chooseModels(self):
        '''launches a model chooser dialog to modify the contents of the form'''
        if self.__modelChooserDlg is None:
            self.__modelChooserDlg = Qt.QDialog(self)
            self.__modelChooserDlg.setWindowTitle(
                "%s - Model Chooser" % str(self.windowTitle()))
            self.__modelChooserDlg.modelChooser = TaurusModelChooser()
            layout = Qt.QVBoxLayout()
            layout.addWidget(self.__modelChooserDlg.modelChooser)
            self.__modelChooserDlg.setLayout(layout)
            self.__modelChooserDlg.modelChooser.updateModels.connect(self.setModel)

        models_and_labels = []
        indexdict = {}
        for m in self.getModel():
            key = _normalize_model_name_case(m)
            indexdict[key] = indexdict.get(key, -1) + 1
            item = self.getItemByModel(m, indexdict[key])
            if item is None:
                label = None
            else:
                try:
                    # this assumes that the readwidget is a QLabel subclass (or
                    # something that has text())
                    label = str(item.labelWidget().text())
                except:
                    label = None
            models_and_labels.append((m, label))

        self.__modelChooserDlg.modelChooser.setListedModels(models_and_labels)
        self.__modelChooserDlg.show()
        self.__modelChooserDlg.raise_()

    def chooseAttrs(self):
        self.info(
            'TaurusForm.chooseAttrs() ahs been deprecated. Use TaurusForm.chooseModels() instead')
        self.chooseModels()

    def sizeHint(self):
        return Qt.QWidget.sizeHint(self)

    def _connectButtons(self):
        self.buttonBox.applyClicked.connect(self.apply)
        self.buttonBox.resetClicked.connect(self.reset)

    def getModel(self):
        return self._model

    @Qt.pyqtSlot('QStringList')
    def addModels(self, modelNames):
        '''Adds models to the existing ones:

        :param modelNames:  (sequence<str>) the names of the models to be added

        .. seealso:: :meth:`removeModels`
        '''
        modelNames = self._splitModel(modelNames)
        self.setModel(self.getModel() + modelNames)

    @Qt.pyqtSlot('QStringList')
    def removeModels(self, modelNames):
        '''Removes models from those already in the form.

        :param modelNames:  (sequence<str>) the names of the models to be removed

        .. seealso:: :meth:`addModels`
        '''
        modelNames = self._splitModel(modelNames)
        currentModels = self.getModel()
        for name in modelNames:
            try:
                currentModels.remove(name)
            except:
                self.warning("'%s' not in model list" % name)
        self.setModel(currentModels)

    def setModelCheck(self, model, check=True):
        if model is None:
            model = []
        model = [str(m or '') for m in self._splitModel(model)]
        self.destroyChildren()
        self._model = model
        self.fillWithChildren()
        # update the modelchooser list
        if self.__modelChooserDlg is not None:
            self.__modelChooserDlg.modelChooser.setListedModels(self._model)

    def resetModel(self):
        self.destroyChildren()
        self._model = []

    def getFormWidget(self, model=None):
        '''Returns a tuple that can be used for creating a widget for a given model.

        :param model: (str) a taurus model name for which the new item of the
                      form will be created

        :return: (tuple<type,list,dict>) a tuple containing a class, a list of
                 args and a dict of keyword args. The args and the keyword args
                 can be passed to the class constructor
        '''
        if model is None:
            return self._defaultFormWidget, (), {}
        # If a model is given, check if is in the custom widget map
        try:
            # if it is not an attribute, it will get an exception here
            obj = taurus.Attribute(model)
            return self._defaultFormWidget, (), {}
        except:
            try:
                obj = taurus.Device(model)
            except:
                self.warning(
                    'Cannot handle model "%s". Using default widget.' % (model))
                return self._defaultFormWidget, (), {}
            try:
                key = obj.getDeviceProxy().info().dev_class  # TODO: Tango-centric
            except:
                return self._defaultFormWidget, (), {}
            #value = self._formWidgetsMap.get(key, self._defaultFormWidget)
            cwmap = self.getCustomWidgetMap()
            value = cwmap.get(key, self._defaultFormWidget)
            if isinstance(value, type):  # for backwards compatibility
                if issubclass(value, self._defaultFormWidget):
                    return value, (), {}
                else:
                    return self._defaultFormWidget, (), {'customWidgetMap': {key: value}}
            # we expect a tuple of str,list,dict -->
            # (classname_including_full_module, args, kwargs)
            name, args, kwargs = value
            pkgname, klassname = name.rsplit('.', 1)
            try:
                pkg = __import__(pkgname, fromlist=[klassname])
                klass = getattr(pkg, klassname)
            except:
                self.warning(
                    'Cannot import "%s". Using default widget for "%s".' % (name, model))
                return self._defaultFormWidget, (), {}
            if not issubclass(klass, self._defaultFormWidget):
                cwmap = kwargs.get('customWidgetMap', {})
                cwmap.update({key: klass})
                kwargs['customWidgetMap'] = cwmap
                klass = self._defaultFormWidget
            return klass, args, kwargs

    def setFormWidget(self, formWidget):
        if formWidget is None:
            from taurus.qt.qtgui.panel import TaurusValue
            self._defaultFormWidget = TaurusValue
        elif issubclass(formWidget, Qt.QWidget):
            self._defaultFormWidget = formWidget
        else:
            raise TypeError(
                'formWidget must be one of None, QWidget. %s passed' % repr(type(formWidget)))

    def resetFormWidget(self):
        self.setFormWidget(self, None)

    def isWithButtons(self):
        return self._withButtons

    def setWithButtons(self, trueFalse):
        self._withButtons = trueFalse
        self.buttonBox.setVisible(self._withButtons)
        self.showButtonsAction.setChecked(self._withButtons)

    def resetWithButtons(self):
        self.setWithButtons(True)

    def onSetFormatter(self):
        """Reimplemented from TaurusBaseWidget"""
        # Form delegates se to the taurusvalues
        format = TaurusWidget.onSetFormatter(self)
        if format is not None:
            for item in self.getItems():
                rw = item.readWidget(followCompact=True)
                if hasattr(rw, 'setFormat'):
                    rw.setFormat(format)
        return format

    def setFormat(self, format):
        """
        Reimplemented to call setFormat on the taurusvalues
        """
        TaurusWidget.setFormat(self, format)
        for item in self.getItems():
            if hasattr(item, 'setFormat'):
                item.setFormat(format)

    def setCompact(self, compact):
        self._compact = compact
        for item in self.getItems():
            item.setCompact(compact)
        self.compactModeAction.setChecked(compact)

    def isCompact(self):
        return self._compact

    def resetCompact(self):
        from taurus import tauruscustomsettings
        self.setCompact(getattr(tauruscustomsettings, 'T_FORM_COMPACT', {}))

    def dropEvent(self, event):
        '''reimplemented to support dropping of modelnames in forms'''
        mtype = self.handleMimeData(event.mimeData(), self.addModels)
        if mtype is None:
            self.info('Invalid model in dropped data')
        else:
            event.acceptProposedAction()

    def setModifiableByUser(self, modifiable):
        '''
        sets whether the user can change the contents of the form
        (e.g., via Modify Contents in the context menu)
        Reimplemented from :meth:`TaurusWidget.setModifiableByUser`

        :param modifiable: (bool)

        .. seealso:: :meth:`TaurusWidget.setModifiableByUser`
        '''
        TaurusWidget.setModifiableByUser(self, modifiable)
        self.chooseModelsAction.setEnabled(modifiable)
        self.showButtonsAction.setEnabled(modifiable)
        self.changeLabelsAction.setEnabled(modifiable)
        self.compactModeAction.setEnabled(modifiable)
        for item in self.getItems():
            try:
                item.setModifiableByUser(modifiable)
            except:
                pass

    def setRegExp(self, regExp):
        pass

    def getRegExp(self):
        pass

    def destroyChildren(self):
        for child in self._children:
            self.unregisterConfigurableItem(child)
            # child.destroy()
            child.setModel(None)
            child.deleteLater()
        self._children = []

    def fillWithChildren(self):
        frame = TaurusWidget()
        frame.setLayout(Qt.QGridLayout())
        frame.layout().addItem(Qt.QSpacerItem(
            0, 0, Qt.QSizePolicy.Minimum, Qt.QSizePolicy.MinimumExpanding))

        parent_name = None
        if self.getUseParentModel():
            parent_model = self.getParentModelObj()
            if parent_model:
                parent_name = parent_model.getFullName()

        for i, model in enumerate(self.getModel()):
            if not model:
                continue
            if parent_name:
                # @todo: Change this (it assumes tango model naming!)
                model = "%s/%s" % (parent_name, model)
            klass, args, kwargs = self.getFormWidget(model=model)
            widget = klass(frame, *args, **kwargs)
            # @todo UGLY... See if this can be done in other ways... (this causes trouble with widget that need more vertical space , like PoolMotorTV)
            widget.setMinimumHeight(20)

            try:
                widget.setCompact(self.isCompact())
                widget.setModel(model)
                widget.setParent(frame)
            except:
                # raise
                self.warning(
                    'an error occurred while adding the child "%s". Skipping' % model)
                self.traceback(level=taurus.Debug)
            try:
                widget.setModifiableByUser(self.isModifiableByUser())
            except:
                pass
            try:
                widget.setFormat(self.getFormat())
            except Exception:
                self.debug('Cannot set format %s to child %s',
                           self.getFormat(), model)
            widget.setObjectName("__item%i" % i)
            self.registerConfigDelegate(widget)
            self._children.append(widget)

        frame.layout().addItem(Qt.QSpacerItem(
            0, 0, Qt.QSizePolicy.Minimum, Qt.QSizePolicy.MinimumExpanding))
        self.scrollArea.setWidget(frame)
#        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setMinimumWidth(frame.layout().sizeHint().width() + 20)

    def getItemByModel(self, model, index=0):
        '''returns the child item with given model. If there is more than one item
        with the same model, the index parameter can be used to distinguish among them
        Please note that his index is only relative to same-model items!'''
        for child in self._children:
            if (_normalize_model_name_case(child.getModel()) ==
                    _normalize_model_name_case(model)):
                if index <= 0:
                    return child
                else:
                    index -= 1

    def getItemByIndex(self, index):
        '''returns the child item with at the given index position. '''
        return self.getItems()[index]

    def getItems(self):
        '''returns a list of the objects that have been created as childs of the form'''
        return self._children

#    def _manageButtonBox(self):
#        if self.isWithButtons():
#            self.buttonBox.setVisible(True)
#        else:
#            self.buttonBox.setVisible(False)

    def onChangeLabelsAction(self):
        '''changes the labelConfig of all its items'''
        keys = ['{attr.label}', '{attr.name}', '{attr.fullname}', '{dev.name}',
                '{dev.fullname}']

        msg = 'Choose the label format. \n' + \
              'You may use Python format() syntax. The TaurusDevice object\n' + \
              'can be referenced as "dev" and the TaurusAttribute object\n' + \
              'as "attr"'
        labelConfig, ok = Qt.QInputDialog.getItem(self, 'Change Label', msg,
                                                  keys, 0, True)
        if ok:
            for item in self.getItems():
                item.labelConfig = (str(labelConfig))

    @Qt.pyqtSlot()
    def apply(self):
        self.safeApplyOperations()

    @Qt.pyqtSlot()
    def reset(self):
        self.resetPendingOperations()

    @classmethod
    def getQtDesignerPluginInfo(cls):
        ret = TaurusWidget.getQtDesignerPluginInfo()
        ret['module'] = 'taurus.qt.qtgui.panel'
        ret['container'] = False
        return ret

    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
    # QT properties
    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-

    model = Qt.pyqtProperty("QStringList", getModel,
                            TaurusWidget.setModel,
                            resetModel)

    useParentModel = Qt.pyqtProperty("bool",
                                     TaurusWidget.getUseParentModel,
                                     TaurusWidget.setUseParentModel,
                                     TaurusWidget.resetUseParentModel)

    withButtons = Qt.pyqtProperty("bool", isWithButtons,
                                  setWithButtons,
                                  resetWithButtons)

    modifiableByUser = Qt.pyqtProperty("bool", TaurusWidget.isModifiableByUser,
                                       setModifiableByUser,
                                       TaurusWidget.resetModifiableByUser)

    compact = Qt.pyqtProperty("bool", isCompact, setCompact, resetCompact)
Ejemplo n.º 2
0
    def __init__(self, parent=None,
                 formWidget=None,
                 buttons=None,
                 withButtons=True,
                 designMode=False):
        TaurusWidget.__init__(self, parent, designMode)

        if buttons is None:
            buttons = Qt.QDialogButtonBox.Apply | \
                Qt.QDialogButtonBox.Reset
        self._customWidgetMap = {}
        self._model = []
        self._children = []
        self.setFormWidget(formWidget)

        self.setLayout(Qt.QVBoxLayout())

        frame = TaurusWidget()
        frame.setLayout(Qt.QGridLayout())

        self.scrollArea = TaurusScrollArea(self)
        self.scrollArea.setWidget(frame)
        self.scrollArea.setWidgetResizable(True)
        self.layout().addWidget(self.scrollArea)
        self.__modelChooserDlg = None

        self.buttonBox = QButtonBox(buttons=buttons, parent=self)
        self.layout().addWidget(self.buttonBox)

        self._connectButtons()

        # Actions (they automatically populate the context menu)
        self.setContextMenuPolicy(Qt.Qt.ActionsContextMenu)

        self.chooseModelsAction = Qt.QAction('Modify Contents', self)
        self.addAction(self.chooseModelsAction)
        self.chooseModelsAction.triggered[()].connect(self.chooseModels)

        self.showButtonsAction = Qt.QAction('Show Buttons', self)
        self.showButtonsAction.setCheckable(True)
        self.addAction(self.showButtonsAction)
        self.showButtonsAction.triggered[bool].connect(self.setWithButtons)
        self.setWithButtons(withButtons)

        self.changeLabelsAction = Qt.QAction('Change labels (all items)', self)
        self.addAction(self.changeLabelsAction)
        self.changeLabelsAction.triggered[()].connect(self.onChangeLabelsAction)

        self.compactModeAction = Qt.QAction('Compact mode (all items)', self)
        self.compactModeAction.setCheckable(True)
        self.addAction(self.compactModeAction)
        self.compactModeAction.triggered[bool].connect(self.setCompact)

        self.resetModifiableByUser()
        self.setSupportedMimeTypes([TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_DEV_MIME_TYPE,
                                    TAURUS_ATTR_MIME_TYPE, TAURUS_MODEL_MIME_TYPE, 'text/plain'])

        self.resetCompact()

        # properties
        self.registerConfigProperty(
            self.isWithButtons, self.setWithButtons, 'withButtons')
        self.registerConfigProperty(self.isCompact, self.setCompact, 'compact')
Ejemplo n.º 3
0
    def __init__(self, parent=None,
                 formWidget=None,
                 buttons=None,
                 withButtons=True,
                 designMode=False):

        self._children = []
        TaurusWidget.__init__(self, parent, designMode)

        if buttons is None:
            buttons = Qt.QDialogButtonBox.Apply | \
                Qt.QDialogButtonBox.Reset
        self._customWidgetMap = {}
        self._model = []
        # self._children = []
        self.setFormWidget(formWidget)

        self.setLayout(Qt.QVBoxLayout())

        frame = TaurusWidget()
        frame.setLayout(Qt.QGridLayout())

        self.scrollArea = TaurusScrollArea(self)
        self.scrollArea.setWidget(frame)
        self.scrollArea.setWidgetResizable(True)
        self.layout().addWidget(self.scrollArea)
        self.__modelChooserDlg = None

        self.buttonBox = QButtonBox(buttons=buttons, parent=self)
        self.layout().addWidget(self.buttonBox)

        self._connectButtons()

        # Actions (they automatically populate the context menu)
        self.setContextMenuPolicy(Qt.Qt.ActionsContextMenu)

        self.chooseModelsAction = Qt.QAction('Modify Contents', self)
        self.addAction(self.chooseModelsAction)
        self.chooseModelsAction.triggered.connect(self.chooseModels)

        self.showButtonsAction = Qt.QAction('Show Buttons', self)
        self.showButtonsAction.setCheckable(True)
        self.addAction(self.showButtonsAction)
        self.showButtonsAction.triggered[bool].connect(self.setWithButtons)
        self.setWithButtons(withButtons)

        self.changeLabelsAction = Qt.QAction('Change labels (all items)', self)
        self.addAction(self.changeLabelsAction)
        self.changeLabelsAction.triggered.connect(self.onChangeLabelsAction)

        self.compactModeAction = Qt.QAction('Compact mode (all items)', self)
        self.compactModeAction.setCheckable(True)
        self.addAction(self.compactModeAction)
        self.compactModeAction.triggered[bool].connect(self.setCompact)

        self.setFormatterAction = Qt.QAction('Set formatter (all items)', self)
        self.addAction(self.setFormatterAction)
        self.setFormatterAction.triggered.connect(self.onSetFormatter)

        self.resetModifiableByUser()
        self.setSupportedMimeTypes([TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_DEV_MIME_TYPE,
                                    TAURUS_ATTR_MIME_TYPE, TAURUS_MODEL_MIME_TYPE, 'text/plain'])

        self.resetCompact()

        # properties
        self.registerConfigProperty(
            self.isWithButtons, self.setWithButtons, 'withButtons')
        self.registerConfigProperty(self.isCompact, self.setCompact, 'compact')
Ejemplo n.º 4
0
class TaurusForm(TaurusWidget):
    '''A form containing specific widgets for interacting with
    a given list of taurus attributes and/or devices.

    Its model is a list of attribute and/or device names to be shown. Each item
    is represented in a row consisting of a label, a read widget, a write
    widget, a units widget and an "extra" widget (some of them may not be shown)
    which are vertically aligned with their counterparts from other items.

    By default a :class:`TaurusValue` object is used for each item, but this
    can be changed and specific mappings can be defined using the
    :meth:`setCustomWidgetMap` method.

    Item objects can be accessed by index using a list-like notation::

      form = TaurusForm()
      form.model = ['sys/tg_test/1'+a for a in ('short_image','/float_scalar','/double_scalar')]
      form[0].labelConfig = 'dev_alias'
      form[-1].writeWidgetClass = 'TaurusWheelEdit'
      print(len(form))  # --> outputs '3' (the length of the form is the number of items)

    By default, the form provides global Apply and Cancel buttons.

    You can also see some code that exemplifies the use of TaurusForm in :ref:`Taurus
    coding examples <examples>` '''

    def __init__(self, parent=None,
                 formWidget=None,
                 buttons=None,
                 withButtons=True,
                 designMode=False):
        TaurusWidget.__init__(self, parent, designMode)

        if buttons is None:
            buttons = Qt.QDialogButtonBox.Apply | \
                Qt.QDialogButtonBox.Reset
        self._customWidgetMap = {}
        self._model = []
        self._children = []
        self.setFormWidget(formWidget)

        self.setLayout(Qt.QVBoxLayout())

        frame = TaurusWidget()
        frame.setLayout(Qt.QGridLayout())

        self.scrollArea = TaurusScrollArea(self)
        self.scrollArea.setWidget(frame)
        self.scrollArea.setWidgetResizable(True)
        self.layout().addWidget(self.scrollArea)
        self.__modelChooserDlg = None

        self.buttonBox = QButtonBox(buttons=buttons, parent=self)
        self.layout().addWidget(self.buttonBox)

        self._connectButtons()

        # Actions (they automatically populate the context menu)
        self.setContextMenuPolicy(Qt.Qt.ActionsContextMenu)

        self.chooseModelsAction = Qt.QAction('Modify Contents', self)
        self.addAction(self.chooseModelsAction)
        self.chooseModelsAction.triggered[()].connect(self.chooseModels)

        self.showButtonsAction = Qt.QAction('Show Buttons', self)
        self.showButtonsAction.setCheckable(True)
        self.addAction(self.showButtonsAction)
        self.showButtonsAction.triggered[bool].connect(self.setWithButtons)
        self.setWithButtons(withButtons)

        self.changeLabelsAction = Qt.QAction('Change labels (all items)', self)
        self.addAction(self.changeLabelsAction)
        self.changeLabelsAction.triggered[()].connect(self.onChangeLabelsAction)

        self.compactModeAction = Qt.QAction('Compact mode (all items)', self)
        self.compactModeAction.setCheckable(True)
        self.addAction(self.compactModeAction)
        self.compactModeAction.triggered[bool].connect(self.setCompact)

        self.resetModifiableByUser()
        self.setSupportedMimeTypes([TAURUS_MODEL_LIST_MIME_TYPE, TAURUS_DEV_MIME_TYPE,
                                    TAURUS_ATTR_MIME_TYPE, TAURUS_MODEL_MIME_TYPE, 'text/plain'])

        self.resetCompact()

        # properties
        self.registerConfigProperty(
            self.isWithButtons, self.setWithButtons, 'withButtons')
        self.registerConfigProperty(self.isCompact, self.setCompact, 'compact')

    def __getitem__(self, key):
        '''provides a list-like interface: items of the form can be accessed using slice notation'''
        return self.getItemByIndex(key)

    def __len__(self):
        '''returns the number of items contained by the form'''
        return len(self.getItems())

    def _splitModel(self, modelNames):
        '''convert str to list if needed (commas and whitespace are considered as separators)'''
        if isinstance(modelNames, (basestring, Qt.QString)):
            modelNames = str(modelNames).replace(',', ' ')
            modelNames = modelNames.split()
        return modelNames

    def setCustomWidgetMap(self, cwmap):
        '''Sets a map map for custom widgets.

        :param cwmap: (dict<str,tuple>) a dictionary whose keys are device
                      type strings (i.e. see :class:`PyTango.DeviceInfo`) and
                      whose values are tuples of classname,args,kwargs
        '''
        self._customWidgetMap = cwmap

    def getCustomWidgetMap(self):
        '''Returns the map used to create custom widgets.

        :return: (dict<str,tuple>) a dictionary whose keys are device
                 type strings (i.e. see :class:`PyTango.DeviceInfo`) and whose
                 values are tuples of classname,args,kwargs
        '''
        return self._customWidgetMap

    @Qt.pyqtSlot('QString', name='modelChanged')
    def parentModelChanged(self, parentmodel_name):
        self.info("Parent model changed to '%s'" % parentmodel_name)
        parentmodel_name = str(parentmodel_name)
        if self.getUseParentModel():
            # reset the model of childs
            for obj, model in zip(self.getItems(), self.getModel()):
                obj.setModel('%s/%s' % (parentmodel_name, str(model)))
        else:
            self.debug(
                "received event from parent although not using parent model")

    def chooseModels(self):
        '''launches a model chooser dialog to modify the contents of the form'''
        if self.__modelChooserDlg is None:
            self.__modelChooserDlg = Qt.QDialog(self)
            self.__modelChooserDlg.setWindowTitle(
                "%s - Model Chooser" % unicode(self.windowTitle()))
            self.__modelChooserDlg.modelChooser = TaurusModelChooser()
            layout = Qt.QVBoxLayout()
            layout.addWidget(self.__modelChooserDlg.modelChooser)
            self.__modelChooserDlg.setLayout(layout)
            self.__modelChooserDlg.modelChooser.updateModels.connect(self.setModel)

        models_and_labels = []
        models = [m.lower() for m in self.getModel()]
        indexdict = {}
        for m in models:
            indexdict[m] = indexdict.get(m, -1) + 1
            item = self.getItemByModel(m, indexdict[m])
            if item is None:
                label = None
            else:
                try:
                    # this assumes that the readwidget is a QLabel subclass (or
                    # something that has text())
                    label = str(item.labelWidget().text())
                except:
                    label = None
            models_and_labels.append((m, label))

        self.__modelChooserDlg.modelChooser.setListedModels(models_and_labels)
        self.__modelChooserDlg.show()
        self.__modelChooserDlg.raise_()

    def chooseAttrs(self):
        self.info(
            'TaurusForm.chooseAttrs() ahs been deprecated. Use TaurusForm.chooseModels() instead')
        self.chooseModels()

    def sizeHint(self):
        return Qt.QWidget.sizeHint(self)

    def _connectButtons(self):
        self.buttonBox.applyClicked.connect(self.apply)
        self.buttonBox.resetClicked.connect(self.reset)

    def getModel(self):
        return self._model

    @Qt.pyqtSlot('QStringList')
    def addModels(self, modelNames):
        '''Adds models to the existing ones:

        :param modelNames:  (sequence<str>) the names of the models to be added

        .. seealso:: :meth:`removeModels`
        '''
        modelNames = self._splitModel(modelNames)
        self.setModel(self.getModel() + modelNames)

    @Qt.pyqtSlot('QStringList')
    def removeModels(self, modelNames):
        '''Removes models from those already in the form.

        :param modelNames:  (sequence<str>) the names of the models to be removed

        .. seealso:: :meth:`addModels`
        '''
        modelNames = self._splitModel(modelNames)
        currentModels = self.getModel()
        for name in modelNames:
            try:
                currentModels.remove(name)
            except:
                self.warning("'%s' not in model list" % name)
        self.setModel(currentModels)

    def setModelCheck(self, model, check=True):
        if model is None:
            model = []
        model = [str(m or '') for m in self._splitModel(model)]
        self.destroyChildren()
        self._model = model
        self.fillWithChildren()
        # update the modelchooser list
        if self.__modelChooserDlg is not None:
            self.__modelChooserDlg.modelChooser.setListedModels(self._model)

    def resetModel(self):
        self.destroyChildren()
        self._model = []

    def getFormWidget(self, model=None):
        '''Returns a tuple that can be used for creating a widget for a given model.

        :param model: (str) a taurus model name for which the new item of the
                      form will be created

        :return: (tuple<type,list,dict>) a tuple containing a class, a list of
                 args and a dict of keyword args. The args and the keyword args
                 can be passed to the class constructor
        '''
        if model is None:
            return self._defaultFormWidget, (), {}
        # If a model is given, check if is in the custom widget map
        try:
            # if it is not an attribute, it will get an exception here
            obj = taurus.Attribute(model)
            return self._defaultFormWidget, (), {}
        except:
            try:
                obj = taurus.Device(model)
            except:
                self.warning(
                    'Cannot handle model "%s". Using default widget.' % (model))
                return self._defaultFormWidget, (), {}
            try:
                key = obj.getDeviceProxy().info().dev_class  # TODO: Tango-centric
            except:
                return self._defaultFormWidget, (), {}
            #value = self._formWidgetsMap.get(key, self._defaultFormWidget)
            cwmap = self.getCustomWidgetMap()
            value = cwmap.get(key, self._defaultFormWidget)
            if isinstance(value, type):  # for backwards compatibility
                if issubclass(value, self._defaultFormWidget):
                    return value, (), {}
                else:
                    return self._defaultFormWidget, (), {'customWidgetMap': {key: value}}
            # we expect a tuple of str,list,dict -->
            # (classname_including_full_module, args, kwargs)
            name, args, kwargs = value
            pkgname, klassname = name.rsplit('.', 1)
            try:
                pkg = __import__(pkgname, fromlist=[klassname])
                klass = getattr(pkg, klassname)
            except:
                self.warning(
                    'Cannot import "%s". Using default widget for "%s".' % (name, model))
                return self._defaultFormWidget, (), {}
            if not issubclass(klass, self._defaultFormWidget):
                cwmap = kwargs.get('customWidgetMap', {})
                cwmap.update({key: klass})
                kwargs['customWidgetMap'] = cwmap
                klass = self._defaultFormWidget
            return klass, args, kwargs

    def setFormWidget(self, formWidget):
        if formWidget is None:
            from taurus.qt.qtgui.panel import TaurusValue
            self._defaultFormWidget = TaurusValue
        elif issubclass(formWidget, Qt.QWidget):
            self._defaultFormWidget = formWidget
        else:
            raise TypeError(
                'formWidget must be one of None, QWidget. %s passed' % repr(type(formWidget)))

    def resetFormWidget(self):
        self.setFormWidget(self, None)

    def isWithButtons(self):
        return self._withButtons

    def setWithButtons(self, trueFalse):
        self._withButtons = trueFalse
        self.buttonBox.setVisible(self._withButtons)
        self.showButtonsAction.setChecked(self._withButtons)

    def resetWithButtons(self):
        self.setWithButtons(True)

    def setCompact(self, compact):
        self._compact = compact
        for item in self.getItems():
            item.setCompact(compact)
        self.compactModeAction.setChecked(compact)

    def isCompact(self):
        return self._compact

    def resetCompact(self):
        from taurus import tauruscustomsettings
        self.setCompact(getattr(tauruscustomsettings, 'T_FORM_COMPACT', {}))

    def dropEvent(self, event):
        '''reimplemented to support dropping of modelnames in forms'''
        mtype = self.handleMimeData(event.mimeData(), self.addModels)
        if mtype is None:
            self.info('Invalid model in dropped data')
        else:
            event.acceptProposedAction()

    def setModifiableByUser(self, modifiable):
        '''
        sets whether the user can change the contents of the form
        (e.g., via Modify Contents in the context menu)
        Reimplemented from :meth:`TaurusWidget.setModifiableByUser`

        :param modifiable: (bool)

        .. seealso:: :meth:`TaurusWidget.setModifiableByUser`
        '''
        TaurusWidget.setModifiableByUser(self, modifiable)
        self.chooseModelsAction.setEnabled(modifiable)
        self.showButtonsAction.setEnabled(modifiable)
        self.changeLabelsAction.setEnabled(modifiable)
        self.compactModeAction.setEnabled(modifiable)
        for item in self.getItems():
            try:
                item.setModifiableByUser(modifiable)
            except:
                pass

    def setRegExp(self, regExp):
        pass

    def getRegExp(self):
        pass

    def destroyChildren(self):
        for child in self._children:
            self.unregisterConfigurableItem(child)
            # child.destroy()
            child.setModel(None)
            child.deleteLater()
        self._children = []

    def fillWithChildren(self):
        frame = TaurusWidget()
        frame.setLayout(Qt.QGridLayout())
        frame.layout().addItem(Qt.QSpacerItem(
            0, 0, Qt.QSizePolicy.Minimum, Qt.QSizePolicy.MinimumExpanding))

        parent_name = None
        if self.getUseParentModel():
            parent_model = self.getParentModelObj()
            if parent_model:
                parent_name = parent_model.getFullName()

        for i, model in enumerate(self.getModel()):
            if not model:
                continue
            if parent_name:
                # @todo: Change this (it assumes tango model naming!)
                model = "%s/%s" % (parent_name, model)
            klass, args, kwargs = self.getFormWidget(model=model)
            widget = klass(frame, *args, **kwargs)
            # @todo UGLY... See if this can be done in other ways... (this causes trouble with widget that need more vertical space , like PoolMotorTV)
            widget.setMinimumHeight(20)

            try:
                widget.setCompact(self.isCompact())
                widget.setModel(model)
                widget.setParent(frame)
            except:
                # raise
                self.warning(
                    'an error occurred while adding the child "%s". Skipping' % model)
                self.traceback(level=taurus.Debug)
            try:
                widget.setModifiableByUser(self.isModifiableByUser())
            except:
                pass
            widget.setObjectName("__item%i" % i)
            self.registerConfigDelegate(widget)
            self._children.append(widget)

        frame.layout().addItem(Qt.QSpacerItem(
            0, 0, Qt.QSizePolicy.Minimum, Qt.QSizePolicy.MinimumExpanding))
        self.scrollArea.setWidget(frame)
#        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setMinimumWidth(frame.layout().sizeHint().width() + 20)

    def getItemByModel(self, model, index=0):
        '''returns the child item with given model. If there is more than one item
        with the same model, the index parameter can be used to distinguish among them
        Please note that his index is only relative to same-model items!'''
        for child in self._children:
            if child.getModel().lower() == model.lower():
                if index <= 0:
                    return child
                else:
                    index -= 1

    def getItemByIndex(self, index):
        '''returns the child item with at the given index position. '''
        return self.getItems()[index]

    def getItems(self):
        '''returns a list of the objects that have been created as childs of the form'''
        return self._children

#    def _manageButtonBox(self):
#        if self.isWithButtons():
#            self.buttonBox.setVisible(True)
#        else:
#            self.buttonBox.setVisible(False)

    def onChangeLabelsAction(self):
        '''changes the labelConfig of all its items'''
        keys = ['{attr.label}', '{attr.name}', '{attr.fullname}', '{dev.name}',
                '{dev.fullname}']

        msg = 'Choose the label format. \n' + \
              'You may use Python format() syntax. The TaurusDevice object\n' + \
              'can be referenced as "dev" and the TaurusAttribute object\n' + \
              'as "attr"'
        labelConfig, ok = Qt.QInputDialog.getItem(self, 'Change Label', msg,
                                                  keys, 0, True)
        if ok:
            for item in self.getItems():
                item.labelConfig = (str(labelConfig))

    @Qt.pyqtSlot()
    def apply(self):
        self.safeApplyOperations()

    @Qt.pyqtSlot()
    def reset(self):
        self.resetPendingOperations()

    @classmethod
    def getQtDesignerPluginInfo(cls):
        ret = TaurusWidget.getQtDesignerPluginInfo()
        ret['module'] = 'taurus.qt.qtgui.panel'
        ret['container'] = False
        return ret

    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
    # QT properties
    #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-

    model = Qt.pyqtProperty("QStringList", getModel,
                            TaurusWidget.setModel,
                            resetModel)

    useParentModel = Qt.pyqtProperty("bool",
                                     TaurusWidget.getUseParentModel,
                                     TaurusWidget.setUseParentModel,
                                     TaurusWidget.resetUseParentModel)

    withButtons = Qt.pyqtProperty("bool", isWithButtons,
                                  setWithButtons,
                                  resetWithButtons)

    modifiableByUser = Qt.pyqtProperty("bool", TaurusWidget.isModifiableByUser,
                                       setModifiableByUser,
                                       TaurusWidget.resetModifiableByUser)

    compact = Qt.pyqtProperty("bool", isCompact, setCompact, resetCompact)