예제 #1
0
 def insertRow(self, row, parent_index, node, reinserting=False):
     ''' Override the default insertRow to catch updates to models. '''
     # Catch the events where we alter the list of available models and
     # notify interested objects
     XmlModel.insertRow(self, row, parent_index, node, reinserting)
     if node.tag == 'model':
         update_models_to_run_lists()
예제 #2
0
 def setUp(self):
     self.app = QApplication([], True)
     self.testdatapath = os.path.split(__file__)[0]
     self.testdatapath = os.path.join(self.testdatapath, 'testdata')
     manager_xml_file = os.path.join(self.testdatapath, 'model_manager.xml')
     self.xml = ElementTree(file=manager_xml_file).getroot()
     self.instance = XmlModel(self.xml)
예제 #3
0
    def data(self, index, role):
        ''' PyQt API Method -- see PyQt for documentation '''

        if not index.isValid():
            return QVariant()

        node = index.internalPointer().node

        if node is None:
            return QVariant()

        # only override displaying of left column
        if index.column() != 0:
            return XmlModel.data(self, index, role)

        # give some nodes special icons
        if role == Qt.DecorationRole:
            if node.tag in ['structure', 'specification']:

                if node.getparent().tag == 'model':
                    return QVariant(IconLibrary.icon('folder_development'))

                elif node.getparent().tag == 'structure':
                    return QVariant(IconLibrary.icon('method'))

                elif node.getparent().tag == 'specification' and node.tag != 'submodel':
                    # assume it's a submodel group
                    return QVariant(IconLibrary.icon('folder_development'))

        # fall back on default
        return XmlModel.data(self, index, role)
예제 #4
0
    def data(self, index, role):
        ''' PyQt API Method -- see PyQt for documentation '''

        if not index.isValid():
            return QVariant()

        node = index.internalPointer().node

        if node is None:
            return QVariant()

        # only override displaying of left column
        if index.column() != 0:
            return XmlModel.data(self, index, role)

        # give some nodes special icons
        if role == Qt.DecorationRole:
            if node.tag in ['structure', 'specification']:

                if node.getparent().tag == 'model':
                    return QVariant(IconLibrary.icon('folder_development'))

                elif node.getparent().tag == 'structure':
                    return QVariant(IconLibrary.icon('method'))

                elif node.getparent(
                ).tag == 'specification' and node.tag != 'submodel':
                    # assume it's a submodel group
                    return QVariant(IconLibrary.icon('folder_development'))

        # fall back on default
        return XmlModel.data(self, index, role)
예제 #5
0
 def insertRow(self, row, parent_index, node, reinserting = False):
     ''' Override the default insertRow to catch updates to models. '''
     # Catch the events where we alter the list of available models and
     # notify interested objects
     XmlModel.insertRow(self, row, parent_index, node, reinserting)
     if node.tag == 'model':
         update_models_to_run_lists()
예제 #6
0
    def add_model_view_delegate(self):
        '''
        Initialize and bind the Model, View and Delegate for this controller.

        This method is called before any initialization of the widgets.
        Subclasses that wish to use their own model, view and/or delegate
        should override this method and initialize their own widgets.
        '''
        self.model = XmlModel(self.xml_root, self.project)
        self.view = XmlView(self.manager.base_widget)
        self.delegate = XmlItemDelegate(self.view)
예제 #7
0
 def removeRow(self, row, parent_index):
     ''' Override the default removeRow to catch updates to models. '''
     # Catch the events where we alter the list of available models and
     # notify interested objects
     node = self.index(row, 0, parent_index).internalPointer().node
     notify = False
     if node is not None and node.tag == 'model':
         notify = True
     XmlModel.removeRow(self, row, parent_index)
     if notify:
         update_models_to_run_lists()
예제 #8
0
 def removeRow(self, row, parent_index):
     ''' Override the default removeRow to catch updates to models. '''
     # Catch the events where we alter the list of available models and
     # notify interested objects
     node = self.index(row, 0, parent_index).internalPointer().node
     notify = False
     if node is not None and node.tag == 'model':
         notify = True
     XmlModel.removeRow(self, row, parent_index)
     if notify:
         update_models_to_run_lists()
예제 #9
0
    def __init__(self, parent_widget):
        QDialog.__init__(self, parent_widget)
        self.setupUi(self)

        settings_directory = os.path.join(os.environ['OPUS_HOME'], 'settings')
        self._config_filename = os.path.join(settings_directory, 'database_server_configurations.xml')
        try:
            root = ElementTree(file=self._config_filename).getroot()
            view = XmlView(self)
            model = XmlModel(root)
            delegate = XmlItemDelegate(view)
            view.setModel(model)
            # Turns out that Qt Garbage collects the model (and delegate) if we don't explicitly
            # bind it to a Python object in addition to using the PyQt .setModel() method.
            view._model = model
            view._delegate = delegate
            view.setItemDelegate(delegate)
            view.openDefaultItems()

            self.gridlayout.addWidget(view)

            self.tree_view = view
            self.xml_root = root
            return

        except IOError, ex:
            MessageBox.error(mainwindow = self,
                          text = 'Could not initialize Database Settings',
                          detailed_text = str(ex))
            self.xml_root = None
            self._config_filename = ''
            self.configFile = None
예제 #10
0
 def setUp(self):
     self.app = QApplication([], True)
     self.testdatapath = os.path.split(__file__)[0]
     self.testdatapath = os.path.join(self.testdatapath, 'testdata')
     manager_xml_file = os.path.join(self.testdatapath, 'model_manager.xml')
     self.xml = ElementTree(file=manager_xml_file).getroot()
     self.instance = XmlModel(self.xml)
예제 #11
0
    def add_model_view_delegate(self):
        '''
        Initialize and bind the Model, View and Delegate for this controller.

        This method is called before any initialization of the widgets.
        Subclasses that wish to use their own model, view and/or delegate
        should override this method and initialize their own widgets.
        '''
        self.model = XmlModel(self.xml_root, self.project)
        self.view = XmlView(self.manager.base_widget)
        self.delegate = XmlItemDelegate(self.view)
    def data(self, index, role):
        """ PyQt API Method -- See the PyQt documentation for a description """

        # Handle special drawing of missing models
        node = index.internalPointer().node
        if node.tag != "model" or node.get("name") not in self.missing_models:
            # Not a missing model -- use default data handler
            return XmlModel.data(self, index, role)

        # Missing models get a colored description label and a special icon
        if index.column() == 1:
            if role == Qt.ForegroundRole:
                return QVariant(QColor(Qt.red))
            elif role == Qt.DisplayRole:
                return QVariant("(no such model)")
        # Give it a special icon
        elif role == Qt.DecorationRole and index.column() == 0:
            return QVariant(IconLibrary.icon("missing_model"))

        # Other data properties are handled by the default data() method
        return XmlModel.data(self, index, role)
예제 #13
0
    def data(self, index, role):
        ''' PyQt API Method -- See the PyQt documentation for a description '''

        # Handle special drawing of missing models
        node = index.internalPointer().node
        if node.tag != 'model' or node.get('name') not in self.missing_models:
            # Not a missing model -- use default data handler
            return XmlModel.data(self, index, role)

        # Missing models get a colored description label and a special icon
        if index.column() == 1:
            if role == Qt.ForegroundRole:
                return QVariant(QColor(Qt.red))
            elif role == Qt.DisplayRole:
                return QVariant("(no such model)")
        # Give it a special icon
        elif role == Qt.DecorationRole and index.column() == 0:
            return QVariant(IconLibrary.icon('missing_model'))

        # Other data properties are handled by the default data() method
        return XmlModel.data(self, index, role)
예제 #14
0
 def __init__(self, model_root_node, project=None, parent_widget=None):
     ''' See XmlModel.__init__ for documentation '''
     XmlModel.__init__(self, model_root_node, project, parent_widget)
     self.missing_models = set()
예제 #15
0
 def __init__(self, model_root_node, project=None, parent_widget=None):
     XmlModel.__init__(self, model_root_node, project, parent_widget)
 def __init__(self, model_root_node, project=None, parent_widget=None):
     """ See XmlModel.__init__ for documentation """
     XmlModel.__init__(self, model_root_node, project, parent_widget)
     self.missing_models = set()
예제 #17
0
 def __init__(self, model_root_node, project = None, parent_widget = None):
     XmlModel.__init__(self, model_root_node, project, parent_widget)
예제 #18
0
class TestXmlModel(opus_unittest.TestCase):

    def setUp(self):
        self.app = QApplication([], True)
        self.testdatapath = os.path.split(__file__)[0]
        self.testdatapath = os.path.join(self.testdatapath, 'testdata')
        manager_xml_file = os.path.join(self.testdatapath, 'model_manager.xml')
        self.xml = ElementTree(file=manager_xml_file).getroot()
        self.instance = XmlModel(self.xml)

    def test_iconFromType(self):
        # just test some of the icons
        expected_results = {
            'dir_path': self.instance.folderIcon,
            'path': self.instance.folderDatabaseIcon,
            '-- NOT IN DICT --.': QVariant(),
            'defValue': QVariant()
            }

        for key, value in expected_results.items():
            self.assertEqual(self.instance.iconFromType(key), value)

    def test_columnCount(self):
        self.assertEqual(self.instance.columnCount(None), len(self.instance._headers))

    def test_rebuild_tree(self):
        pass

    def test_index_and_parent(self):
        idx_child1 = self.instance.index(0, 0, QModelIndex())
        idx_child2 = self.instance.index(1, 0, QModelIndex())
        idx_child21 = self.instance.index(0, 0, idx_child2)

        node_child1 = self.instance.root_node().find('child_1')
        node_child2 = self.instance.root_node().find('child_2')
        node_child21 = self.instance.root_node().find('child_2/child_21')

        # Make sure the nodes are actually there
        self.assertFalse(node_child1 is None)
        self.assertFalse(node_child2 is None)
        self.assertFalse(node_child21 is None)

        # Check to see if the indexes contain the actual nodes
        self.assertTrue(idx_child1.internalPointer().node is node_child1)
        self.assertTrue(idx_child21.internalPointer().node is node_child21)

        # Check parent lookups
        self.assertEquals(self.instance.parent(idx_child21), idx_child2)
        self.assertEquals(self.instance.parent(idx_child2), QModelIndex())
        self.assertNotEqual(self.instance.parent(idx_child21),
                            self.instance.parent(idx_child2))

    def test_data(self):
        # test that the tag name is returned as display
        # test that the correct icon is returned for the types
        # test that QVariant() is returned for bogus values
        idx_child1 = self.instance.index(0, 1, QModelIndex())
        idx_child2 = self.instance.index(1, 0, QModelIndex())
        idx_child21 = self.instance.index(0, 0, idx_child2)
        idx_child21_value = self.instance.index(0, 1, idx_child2)

        node_child2 = self.instance.root_node().find('child_2')
        node_child21 = self.instance.root_node().find('child_2/child_21')

        self.assertEqual(str(self.instance.data(idx_child1, Qt.DisplayRole).toString()),
                         '*********') # password values should be secret

        self.assertEqual(str(self.instance.data(idx_child2, Qt.DisplayRole).toString()),
                 node_child2.tag)

        self.assertTrue(node_child21.text is not None)
        self.assertEqual(str(self.instance.data(idx_child21_value, Qt.DisplayRole).toString()),
                 node_child21.text.strip())

        self.assertTrue(self.instance.data(idx_child21, Qt.DecorationRole) is not None)
        self.assertEqual(self.instance.data(idx_child21, Qt.DecorationRole),
                         QVariant(self.instance.iconFromType(node_child21.get('type'))))

    def test_rowCount(self):
        idx_child1 = self.instance.index(0, 1, QModelIndex())
        idx_child2 = self.instance.index(1, 0, QModelIndex())

        self.assertEquals(self.instance.rowCount(QModelIndex()), 2)
        self.assertEquals(self.instance.rowCount(idx_child2), 1)
        self.assertEquals(self.instance.rowCount(idx_child1), 0)

    def test_remove_node(self):
        pass

    def test_removeRow(self):
        pass

    def test_index_for_item(self):
        pass

    def test_update_node(self):
        pass

    def test_item_for_node(self):
        pass

    def test_index_for_node(self):
        pass

    def test_add_node(self):
        pass

    def test_insert_node(self):
        pass

    def test_index(self):
        pass

    def test_make_item_local(self):
        pass

    def test_insertRow(self):
        pass

    def test_insert_sibling(self):
        pass

    def test_move_up(self):
        pass

    def test_move_down(self):
        pass

    def test_root_node(self):
        self.assertTrue(self.instance.root_node() is self.instance._root_node and
                        self.instance._root_node is not None)

    def test_root_item(self):
        self.assertTrue(self.instance.root_item() is self.instance._root_item and
                        self.instance._root_item is not None)
예제 #19
0
class XmlController(object):

    '''
    Controller class for XML Trees.
    '''

    def __init__(self, manager):
        '''
        @param manager (AbstractManager) The parent manager for this XmlController
        The XmlController will attach itself to manger.base_widget on initialization.
        '''
        self.manager = manager
        self.project = self.manager.project
        self.xml_root = manager.xml_root
        self.model = None
        self.view = None
        self.delegate = None

        self.add_model_view_delegate()
        self.view.setItemDelegate(self.delegate)
        self.view.setModel(self.model)
        self.view.openDefaultItems()
        self.manager.base_widget.layout().addWidget(self.view)
        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        QObject.connect(self.view,
                        SIGNAL("customContextMenuRequested(const QPoint &)"),
                        self.process_custom_menu)

        # Actions for common menu choices
        # Note that revert and delete are the same action, but we want to present them differently
        # to show that deleting an inherited node (reverting) will keep the node around
        self.act_remove_selected = self.create_action('delete', 'Delete', self.remove_selected_node)
        self.act_revert = self.create_action('revert', 'Revert to inherited value', self.remove_selected_node)
        self.act_make_editable = self.create_action('make_editable', 'Make node local', self.make_selected_editable)
        self.act_clone_node = self.create_action('clone', 'Duplicate', self.clone_selected_node)
        self.act_rename_node = self.create_action('rename', 'Rename', self.rename_selected_node)

    def add_model_view_delegate(self):
        '''
        Initialize and bind the Model, View and Delegate for this controller.

        This method is called before any initialization of the widgets.
        Subclasses that wish to use their own model, view and/or delegate
        should override this method and initialize their own widgets.
        '''
        self.model = XmlModel(self.xml_root, self.project)
        self.view = XmlView(self.manager.base_widget)
        self.delegate = XmlItemDelegate(self.view)

    def create_action(self, icon_name, text, callback):
        return create_qt_action(icon_name, text, callback, self.view)

    def rebuild_tree(self):
        ''' Rebuild the model tree '''
        if self.model:
            self.model.rebuild_tree()

    # CK: TODO This is a method left from before the refactoring in december, don't know
    # if it's needed/used
    def close(self):
        '''
        Closes the controller and removes it from the parent if it is not empty.
        @return: True. Previous behavior was to return False if self.model was
        dirty. This is no longer done as dirty checks have been moved.
        '''
        self.view.hide()
        self.manager.base_widget.layout().removeWidget(self.view)
        return True

    def selected_item(self):
        '''
        Get the currently selected item in the controller's view.
        @return: the selected item (XmlItem) or None if no item is selected.
        '''
        index = self.selected_index()
        return index.internalPointer() if index else None

    def selected_index(self):
        '''
        Get the index for the currently selected item in the controller's view.
        @return: the index (QModelIndex) for the selected item or None
        '''
        index = self.view.currentIndex()
        if index.isValid():
            return index
        return None

    def has_selected_item(self):
        '''
        Tests if the controller's view has a selected item or not.
        @return: True if there is a selected item, otherwise False.
        '''
        return self.selected_index() is not None

    def process_custom_menu(self, position):
        '''
        Abstract method for creating a context sensitive popup menu.
        @param position (QPoint) point of request for the popupmenu.
        '''
        item = self.select_item_at(position)
        if not item:
            return
        node = item.node

        menu = QMenu(self.view)
        self.add_default_menu_items_for_node(node, menu)
        if not menu.isEmpty():
            menu.exec_(QCursor.pos())

    def remove_selected_node(self):
        ''' Removes the selected item from the model. '''
        if not self.has_selected_item():
            return
        index = self.selected_index()
        self.model.removeRow(index.row(), self.model.parent(index))
        self.view.clearSelection()

    # CK: this is a helper function for the clone_node method, but maybe its general enough to be
    # promoted to a higher abstraction layer?
    def _get_unique_name_like_node(self, node):
        '''
        Search sibling items with the same tag to find a name that is guaranteed to be unique.
        First it tries to insert the item with it's current name. If that fails it appends
        _copy to the name and tries again. Subsequent tries have _copyX appended to them where
        X is a number from 1 ->
        @param node (Element) the item holding the node to find a new name for
        @return (String) a unique name for the new node.
        '''
        base_name = node.get('name')
        # Get all the names that currently exist in the same level
        if node.getparent() is None: # no parent = no siblings
            return base_name
        sibling_nodes = node.getparent().getchildren()
        taken_names = [n.get('name') for n in sibling_nodes]
        if base_name not in taken_names:
            return base_name

        try_name = 'Copy of %s' % base_name
        copy_number = 0
        while try_name in taken_names:
            copy_number += 1
            try_name = 'Copy %d of %s' % (copy_number, base_name)
        return try_name

    def clone_selected_node(self):
        ''' Clone the selected node and insert it as a sibling with a unique name '''
        if not self.has_selected_item():
            return
        index = self.selected_index()
        item = self.selected_item()

        cloned_node = deepcopy(item.node)
        cloned_node.set('name', self._get_unique_name_like_node(item.node))

        # Insert the cloned node into the tree
        if self.model.insert_sibling(cloned_node, index) is not None:
            index_of_clone = self.model.last_inserted_index
            # Select the new clone if it was inserted
            if index_of_clone is not None:
                self.view.setCurrentIndex(index_of_clone)

    def rename_selected_node(self):
        ''' Opens a dialog box for changing the node name. '''
        if not self.has_selected_item():
            return
        item = self.selected_item()
        node = item.node
        taken_names = [n.get('name') for n in node.getparent().getchildren() if not n is node]
        dialog = RenameDialog(node.get('name'), taken_names, self.view)
        if dialog.exec_() == dialog.Accepted:
            node.set('name', dialog.accepted_name)

    def make_selected_editable(self):
        '''
        Copies the selected node to this project and strips the inhertied flag
        from all it's immidiate parents and all it's child nodes.
        '''
        if not self.has_selected_item():
            return
        self.model.make_item_local(self.selected_item())

    def select_item_at(self, point):
        '''
        Select the item at "point" to visualize which item we are working on and
        making the item accessible through self.selected_item().

        @param point (QPoint): coordinates for where to get the item.
        @return: The selected item if the point was valid, None otherwise
        '''
        index = self.view.indexAt(point)
        if not index.isValid or index.column() != 0: # only allow right-clicking on left side nodes
            return None
        self.view.setCurrentIndex(index)
        return index.internalPointer()

    def add_default_menu_items_for_node(self, node, menu):
        '''
        Append a list of menu items that is common for all nodes regardless of
        which manager they are in.
        @param node (Element): node to inspect
        @param menu (QMenu): menu to append actions to
        '''
        added_actions = []

        # Inherited nodes can be made local
        if node.get('inherited'):
            added_actions.append(self.act_make_editable)
        # nodes that do not have a 'name' attribute are special nodes that should not be copyable
        # for example; <expression_library>, <model_manager> etc..
        if node.get('name') is not None: # or node.get('copyable') == 'True':
            added_actions.append(self.act_clone_node)
        # named nodes that are not inherited can be renamed
        if node.get('name') is not None and not node.get('inherited'):
            added_actions.append(self.act_rename_node)
        if self.project.is_shadowing(node):
            added_actions.append(self.act_revert)
        elif node.tag in _REMOVABLE_NODE_TYPES and not node.get('inherited'):
            added_actions.append(self.act_remove_selected)

        # Separate from other items
        if added_actions and not menu.isEmpty():
            menu.addSeparator()
        map(lambda x: menu.addAction(x), added_actions)
예제 #20
0
class XmlController(object):
    '''
    Controller class for XML Trees.
    '''
    def __init__(self, manager):
        '''
        @param manager (AbstractManager) The parent manager for this XmlController
        The XmlController will attach itself to manger.base_widget on initialization.
        '''
        self.manager = manager
        self.project = self.manager.project
        self.xml_root = manager.xml_root
        self.model = None
        self.view = None
        self.delegate = None

        self.add_model_view_delegate()
        self.view.setItemDelegate(self.delegate)
        self.view.setModel(self.model)
        self.view.openDefaultItems()
        self.manager.base_widget.layout().addWidget(self.view)
        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        QObject.connect(self.view,
                        SIGNAL("customContextMenuRequested(const QPoint &)"),
                        self.process_custom_menu)

        QObject.connect(self.view, SIGNAL('activated(const QModelIndex&)'),
                        self.on_activated)

        # Actions for common menu choices
        # Note that revert and delete are the same action, but we want to present them differently
        # to show that deleting an inherited node (reverting) will keep the node around
        self.act_remove_selected = self.create_action(
            'delete', 'Delete', self.remove_selected_node)
        self.act_revert = self.create_action('revert',
                                             'Revert to inherited value',
                                             self.remove_selected_node)
        self.act_make_editable = self.create_action(
            'make_editable', 'Make node local', self.make_selected_editable)
        self.act_clone_node = self.create_action('clone', 'Duplicate',
                                                 self.clone_selected_node)
        self.act_rename_node = self.create_action('rename', 'Rename',
                                                  self.rename_selected_node)
        self.actExportXMLToFile = self.create_action(
            'export', "Export XML Node With Inherited Values To File",
            self.exportXMLToFile)
        self.actImportXMLFromFile = self.create_action(
            'import', "Import XML Node From File", self.importXMLFromFile)
        self.actExportXMLToFile_all = self.create_action(
            'export_all', "Export all XML Nodes With Inherited Values To File",
            self.exportXMLToFile)
        self.actImportXMLFromFile_all = self.create_action(
            'import_all', "Import all XML Nodes From File",
            self.importXMLFromFile)
        self.actExportXMLToFile_without_inherited = self.create_action(
            'export_without_inherited', 'Export XML Node To File',
            self.export_without_inherited)
        self.actExportXMLToFile_all_without_inherited = self.create_action(
            'export_all_without_inherited', 'Export all XML Nodes To File',
            self.export_without_inherited)
        self.act_edit = self.create_action('inspect', 'Edit as XML', self.edit)
        self.act_edit_all = self.create_action('inspect_all',
                                               'Edit all as XML', self.edit)
        self.act_copy_to_parent = self.create_action(
            'copy_to_parent', 'Copy to parent', self.copy_selected_to_parent)
        self.act_move_to_parent = self.create_action(
            'move_to_parent', 'Move to parent', self.move_selected_to_parent)

    def add_model_view_delegate(self):
        '''
        Initialize and bind the Model, View and Delegate for this controller.

        This method is called before any initialization of the widgets.
        Subclasses that wish to use their own model, view and/or delegate
        should override this method and initialize their own widgets.
        '''
        self.model = XmlModel(self.xml_root, self.project)
        self.view = XmlView(self.manager.base_widget)
        self.delegate = XmlItemDelegate(self.view)

    def create_action(self, icon_name, text, callback):
        return create_qt_action(icon_name, text, callback, self.view)

    def rebuild_tree(self):
        ''' Rebuild the model tree '''
        if self.model:
            self.model.rebuild_tree()

    # CK: TODO This is a method left from before the refactoring in december, don't know
    # if it's needed/used
    def close(self):
        '''
        Closes the controller and removes it from the parent if it is not empty.
        @return: True. Previous behavior was to return False if self.model was
        dirty. This is no longer done as dirty checks have been moved.
        '''
        self.view.hide()
        self.manager.base_widget.layout().removeWidget(self.view)
        return True

    def selected_item(self):
        '''
        Get the currently selected item in the controller's view.
        @return: the selected item (XmlItem) or None if no item is selected.
        '''
        index = self.selected_index()
        return index.internalPointer() if index else None

    def selected_index(self):
        '''
        Get the index for the currently selected item in the controller's view.
        @return: the index (QModelIndex) for the selected item or None
        '''
        index = self.view.currentIndex()
        if index.isValid():
            return index
        return None

    def has_selected_item(self):
        '''
        Tests if the controller's view has a selected item or not.
        @return: True if there is a selected item, otherwise False.
        '''
        return self.selected_index() is not None

    def process_custom_menu(self, position):
        '''
        Default method for creating a context sensitive popup menu.
        Calls self.add_custom_menu_items_for_node() to populate the menu
        with context-specific menu items.
        @param position (QPoint) point of request for the popupmenu.
        '''
        item = self.select_item_at(position)
        assert item is not None
        node = item.node
        menu = self.create_custom_menu_for_node(node)
        if not menu.isEmpty():
            menu.exec_(self.view.mapToGlobal(position))

    def on_activated(self, index):
        if not self.execute_default_action(index):
            self.view.setExpanded(index, not self.view.isExpanded(index))

    def execute_default_action(self, index):
        if self.model.hasChildren(index) and not self.view.isExpanded(index):
            return
        node = index.internalPointer().node
        if node is None:
            return
        menu = self.create_custom_menu_for_node(node)
        act = menu.defaultAction()
        if act is None:
            return
        act.activate(QAction.Trigger)
        return True

    def create_custom_menu_for_node(self, node):
        menu = QMenu(self.view)
        if node is self.model.root_node():
            self.add_default_menu_items_for_widget(menu)
        elif not self.add_custom_menu_items_for_node(node, menu):
            if not menu.isEmpty():
                menu.addSeparator()
            self.add_default_menu_items_for_node(node, menu)
        return menu

    def set_project_dirty(self):
        if self.project:
            self.project.dirty = True

    def remove_selected_node(self):
        ''' Removes the selected item from the model. '''
        if not self.has_selected_item():
            return
        index = self.selected_index()
        self.model.removeRow(index.row(), self.model.parent(index))
        self.view.clearSelection()
        self.set_project_dirty()

    # CK: this is a helper function for the clone_node method, but maybe its general enough to be
    # promoted to a higher abstraction layer?
    def _get_unique_name_like_node(self, node):
        '''
        Search sibling items with the same tag to find a name that is guaranteed to be unique.
        First it tries to insert the item with it's current name. If that fails it appends
        _copy to the name and tries again. Subsequent tries have _copyX appended to them where
        X is a number from 1 ->
        @param node (Element) the item holding the node to find a new name for
        @return (String) a unique name for the new node.
        '''
        base_name = node.get('name')
        # Get all the names that currently exist in the same level
        if node.getparent() is None:  # no parent = no siblings
            return base_name
        sibling_nodes = node.getparent().getchildren()
        taken_names = [n.get('name') for n in sibling_nodes]
        if base_name not in taken_names:
            return base_name

        try_name = 'Copy of %s' % base_name
        copy_number = 0
        while try_name in taken_names:
            copy_number += 1
            try_name = 'Copy %d of %s' % (copy_number, base_name)
        return try_name

    def clone_selected_node(self):
        ''' Clone the selected node and insert it as a sibling with a unique name '''
        if not self.has_selected_item():
            return
        index = self.selected_index()
        item = self.selected_item()

        cloned_node = deepcopy(item.node)
        cloned_node.set('name', self._get_unique_name_like_node(item.node))

        # Insert the cloned node into the tree
        if self.model.insert_sibling(cloned_node, index) is not None:
            index_of_clone = self.model.last_inserted_index
            # Select the new clone if it was inserted
            if index_of_clone is not None:
                self.view.setCurrentIndex(index_of_clone)
            self.set_project_dirty()

    def rename_selected_node(self):
        ''' Opens a dialog box for changing the node name. '''
        if not self.has_selected_item():
            return
        item = self.selected_item()
        node = item.node
        taken_names = [
            n.get('name') for n in node.getparent().getchildren()
            if not n is node
        ]
        dialog = RenameDialog(node.get('name'), taken_names, self.view)
        if dialog.exec_() == dialog.Accepted:
            node.set('name', dialog.accepted_name)
            self.set_project_dirty()

    def make_selected_editable(self):
        '''
        Copies the selected node to this project and strips the inhertied flag
        from all it's immidiate parents and all it's child nodes.
        '''
        if not self.has_selected_item():
            return
        self.model.make_item_local(self.selected_item())

    def rebuild(self):
        '''rebuild...'''
        self.model.rebuild_tree()

    def copy_selected_to_parent(self):
        '''
        Copy the selected item to the first parent configuration
        '''
        if not self.has_selected_item():
            return
        if not get_mainwindow_instance().okToCloseProject(
                'copying the node to the parent configuration (reload required)'
        ):
            return
        self.model.copy_to_parent(self.selected_index())
        get_mainwindow_instance().reloadProject()

    def move_selected_to_parent(self):
        '''
        Move the selected item to the first parent configuration
        '''
        if not self.has_selected_item():
            return
        if not get_mainwindow_instance().okToCloseProject(
                'moving the node to the parent configuration (reload required)'
        ):
            return
        self.model.move_to_parent(self.selected_index())
        self.project.save()
        get_mainwindow_instance().reloadProject()

    def select_item_at(self, point):
        '''
        Select the item at "point" to visualize which item we are working on and
        making the item accessible through self.selected_item().
        If the point is invalid, the currently selected item, if any, is deselected,
        and the root item is returned.

        @param point (QPoint): coordinates for where to get the item.
        @return: The selected item if the point was valid, None otherwise
        '''
        index = self.view.indexAt(point)
        if not index.isValid or index.column(
        ) != 0:  # only allow right-clicking on left side nodes
            index = self.view.rootIndex()
            item = self.model.root_item()
        else:
            item = index.internalPointer()
        self.view.setCurrentIndex(index)

        assert item is not None
        return item

    def get_selected_or_root_node(self):
        if not self.has_selected_item():
            return self.model.root_node()
        else:
            return self.selected_item().node

    def get_selected_or_root_node_and_index(self):
        node = self.get_selected_or_root_node()
        if node is self.model.root_node():
            index = self.model.index_for_item(self.model.root_item())
        else:
            index = self.model.index_for_node(node)
        assert index is not None
        return node, index

    def get_clean_copy_of_selected_node(self, inherited=True):
        root_node = self.get_selected_or_root_node()
        root_node = deepcopy(root_node)
        if not inherited:
            self.project.xml_config._clean_tree(root_node)

        # Write out the file
        self.project.xml_config._indent(root_node)
        return root_node

    def export_without_inherited(self):
        self.exportXMLToFile(inherited=False)

    def exportXMLToFile(self, inherited=True):
        ''' NO DOCUMENTATION '''

        # Ask the users where they want to save the file
        start_dir = paths.get_project_configs_path()
        configDialog = QFileDialog()
        filter_str = QString("*.xml")
        fd = configDialog.getSaveFileName(self.manager.base_widget,
                                          QString("Save As..."),
                                          QString(start_dir), filter_str)
        # Check for cancel
        if len(fd) == 0:
            return
        fileNameInfo = QFileInfo(QString(fd))
        fileName = fileNameInfo.fileName().trimmed()
        fileNamePath = fileNameInfo.absolutePath().trimmed()
        saveName = os.path.join(str(fileNamePath), str(fileName))

        root_node = self.get_clean_copy_of_selected_node(inherited)
        ElementTree(root_node).write(saveName)

    def check_import_node(self, clicked_node, xml_node):
        if (clicked_node.tag == xml_node.tag) and (clicked_node.get('name')
                                                   == xml_node.get('name')):
            return

        root_dummy = clicked_node.tag
        if 'name' in clicked_node:
            root_dummy += ' name="%s"' % clicked_node['name']
        root_dummy = '<%s/>' % root_dummy
        raise ValueError('Expected an element like %s as root element' %
                         root_dummy)

    def importXMLFromFile(self):
        ''' NO DOCUMENTATION '''
        # print "importXMLFromFile"
        # First, prompt the user for the filename to read in
        start_dir = paths.get_project_configs_path()
        configDialog = QFileDialog()
        filter_str = QString("*.xml")
        fd = configDialog.getOpenFileName(
            self.manager.base_widget, "Please select an XML file to import...",
            start_dir, filter_str)
        # Check for cancel
        if len(fd) == 0:
            return
        fileName = QString(fd)

        # Pass that in to create a new XMLConfiguration
        try:
            options = ''
            xml_tree = load_xml_file(str(fileName))
            xml_node = xml_tree.getroot()

            options = ' or another node in the tree view'
            self.import_from_node(xml_node)
        except Exception, e:
            MessageBox.error(mainwindow=self.view,
                             text='Cannot insert XML file.',
                             detailed_text='XML insert failed.  '
                             '%s.  '
                             'Please select another XML file%s.' %
                             (e, options))
            return
예제 #21
0
class TestXmlModel(opus_unittest.TestCase):
    def setUp(self):
        self.app = QApplication([], True)
        self.testdatapath = os.path.split(__file__)[0]
        self.testdatapath = os.path.join(self.testdatapath, 'testdata')
        manager_xml_file = os.path.join(self.testdatapath, 'model_manager.xml')
        self.xml = ElementTree(file=manager_xml_file).getroot()
        self.instance = XmlModel(self.xml)

    def test_iconFromType(self):
        # just test some of the icons
        expected_results = {
            'dir_path': self.instance.folderIcon,
            'path': self.instance.folderDatabaseIcon,
            '-- NOT IN DICT --.': QVariant(),
            'defValue': QVariant()
        }

        for key, value in expected_results.items():
            self.assertEqual(self.instance.iconFromType(key), value)

    def test_columnCount(self):
        self.assertEqual(self.instance.columnCount(None),
                         len(self.instance._headers))

    def test_rebuild_tree(self):
        pass

    def test_index_and_parent(self):
        idx_child1 = self.instance.index(0, 0, QModelIndex())
        idx_child2 = self.instance.index(1, 0, QModelIndex())
        idx_child21 = self.instance.index(0, 0, idx_child2)

        node_child1 = self.instance.root_node().find('child_1')
        node_child2 = self.instance.root_node().find('child_2')
        node_child21 = self.instance.root_node().find('child_2/child_21')

        # Make sure the nodes are actually there
        self.assertFalse(node_child1 is None)
        self.assertFalse(node_child2 is None)
        self.assertFalse(node_child21 is None)

        # Check to see if the indexes contain the actual nodes
        self.assertTrue(idx_child1.internalPointer().node is node_child1)
        self.assertTrue(idx_child21.internalPointer().node is node_child21)

        # Check parent lookups
        self.assertEquals(self.instance.parent(idx_child21), idx_child2)
        self.assertEquals(self.instance.parent(idx_child2), QModelIndex())
        self.assertNotEqual(self.instance.parent(idx_child21),
                            self.instance.parent(idx_child2))

    def test_data(self):
        # test that the tag name is returned as display
        # test that the correct icon is returned for the types
        # test that QVariant() is returned for bogus values
        idx_child1 = self.instance.index(0, 1, QModelIndex())
        idx_child2 = self.instance.index(1, 0, QModelIndex())
        idx_child21 = self.instance.index(0, 0, idx_child2)
        idx_child21_value = self.instance.index(0, 1, idx_child2)

        node_child2 = self.instance.root_node().find('child_2')
        node_child21 = self.instance.root_node().find('child_2/child_21')

        self.assertEqual(
            str(self.instance.data(idx_child1, Qt.DisplayRole).toString()),
            '*********')  # password values should be secret

        self.assertEqual(
            str(self.instance.data(idx_child2, Qt.DisplayRole).toString()),
            node_child2.tag)

        self.assertTrue(node_child21.text is not None)
        self.assertEqual(
            str(
                self.instance.data(idx_child21_value,
                                   Qt.DisplayRole).toString()),
            node_child21.text.strip())

        self.assertTrue(
            self.instance.data(idx_child21, Qt.DecorationRole) is not None)
        self.assertEqual(
            self.instance.data(idx_child21, Qt.DecorationRole),
            QVariant(self.instance.iconFromType(node_child21.get('type'))))

    def test_rowCount(self):
        idx_child1 = self.instance.index(0, 1, QModelIndex())
        idx_child2 = self.instance.index(1, 0, QModelIndex())

        self.assertEquals(self.instance.rowCount(QModelIndex()), 2)
        self.assertEquals(self.instance.rowCount(idx_child2), 1)
        self.assertEquals(self.instance.rowCount(idx_child1), 0)

    def test_remove_node(self):
        pass

    def test_removeRow(self):
        pass

    def test_index_for_item(self):
        pass

    def test_update_node(self):
        pass

    def test_item_for_node(self):
        pass

    def test_index_for_node(self):
        pass

    def test_add_node(self):
        pass

    def test_insert_node(self):
        pass

    def test_index(self):
        pass

    def test_make_item_local(self):
        pass

    def test_insertRow(self):
        pass

    def test_insert_sibling(self):
        pass

    def test_move_up(self):
        pass

    def test_move_down(self):
        pass

    def test_root_node(self):
        self.assertTrue(self.instance.root_node() is self.instance._root_node
                        and self.instance._root_node is not None)

    def test_root_item(self):
        self.assertTrue(self.instance.root_item() is self.instance._root_item
                        and self.instance._root_item is not None)