From 0693bcacca51a505e0e4d2eaf875f28135ba607a Mon Sep 17 00:00:00 2001 From: slmm Date: Thu, 27 Oct 2005 12:45:05 +0000 Subject: [PATCH] Added Zope 3 stuff Completed xmi export using Kid git-svn-id: file:///Users/arjan/backup/gaphor/trunk/gaphor@610 a8418922-720d-0410-834f-a69b97ada669 --- ChangeLog | 8 ++ data/plugins/kidexport/xmi.kid | 68 ++++++------- gaphor/diagram/attribute.py | 4 + gaphor/diagram/klass.py | 4 + gaphor/diagram/nameditem.py | 5 + gaphor/event.py | 8 ++ gaphor/interfaces.py | 57 +++++++++++ gaphor/ui/diagramtab.py | 4 + gaphor/ui/mainactions.py | 1 + gaphor/ui/mainwindow.py | 21 +++- gaphor/ui/objectinspector.py | 180 +++++++++++++++++++++++++++++++++ setup.py | 13 ++- 12 files changed, 334 insertions(+), 39 deletions(-) create mode 100644 gaphor/event.py create mode 100644 gaphor/interfaces.py create mode 100644 gaphor/ui/objectinspector.py diff --git a/ChangeLog b/ChangeLog index 93233df83..abddaa4c4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2005-10-27 Jeroen Vlootuis + * Added new object inspector panel at bottom of diagram. This makes use + of the Zope 3 component architecture. Therefore Gaphor now depends on + Zope 3. This dependency should be diminshed to component and + interface. At the moment there is no seperate package for component. + + * Made Kid export handle tagged values. + 2005-10-27 wrobell * gaphor/diagram/activitynodes.py: object node has upper bound specification; join node has join specification; initial node naming diff --git a/data/plugins/kidexport/xmi.kid b/data/plugins/kidexport/xmi.kid index acaa90e45..bce775ec8 100644 --- a/data/plugins/kidexport/xmi.kid +++ b/data/plugins/kidexport/xmi.kid @@ -39,16 +39,29 @@ def getLowerAndUpperValuesFromAssociationEnd(end): from gaphor.misc.uniqueid import generate_id - -def getTagDefinitions(): - items = [] - for node in elements: - if hasattr(node, 'taggedValue'): - for tag in node.taggedValue: - items.append(tag) - return items +tagDefinitions = [] +class TaggedValue(object): + def __init__(self, name, value): + self.name = name + self.value = value + self.id = generate_id() + + def getTypeRef(self): + """Return a auto generated typeref for use with type definitions.""" + return self.id+"_typeref" + + typeref = property(getTypeRef) + +def convertTaggedValue(taggedValue): + tags = taggedValue.value.split(',') + converted = [map(unicode.strip, tag.split("=")) for tag in tags] + data = dict(converted) + tags = [TaggedValue(key, value) for (key, value) in data.items()] + tagDefinitions.extend(tags) + return tags + ?> - - - + + + py:for="taggedValue in convertTaggedValue(attribute.taggedValue)" + py:content="processTaggedValue(taggedValue)"> + @@ -126,23 +139,18 @@ def getTagDefinitions(): - - + py:attrs="{'xmi.id':tagDefinition.typeref+'multi'}"> + py:attrs="{'xmi.id':tagDefinition.typeref+'multirange'}"/> @@ -153,10 +161,10 @@ def getTagDefinitions(): xmi.id = 'I5bd6b6fm106dbda4889mm7f21' isSpecification = 'false' py:attrs="{'xmi.id':taggedValue.id}"> someTagValue + py:content="taggedValue.value">someTagValue + py:attrs="{'xmi.idref':taggedValue.typeref}"/> @@ -178,8 +186,6 @@ def getTagDefinitions(): - @@ -195,8 +201,6 @@ def getTagDefinitions(): visibility = 'public' isSpecification = 'false' isRoot = 'false' isLeaf = 'false' isAbstract = 'false' py:attrs="{'xmi.id':interface.id, 'name':interface.name}"> - @@ -207,8 +211,6 @@ def getTagDefinitions(): isAbstract = 'true' isActive = 'false' py:attrs="{'xmi.id':cls.id, 'name':cls.name, 'isAbstract':cls.isAbstract and 'true' or 'false'}"> - - Class @@ -244,8 +244,6 @@ def getTagDefinitions(): xmi.id = 'I48de81cbm106d41f950cmm7e01' name = 'aPackage' visibility = 'public' isSpecification = 'false' isRoot = 'false' isLeaf = 'false' isAbstract = 'false' py:attrs="{'xmi.idref':package.id, 'name':package.name}"> - @@ -258,8 +256,6 @@ def getTagDefinitions(): xmi.id = 'I48de81cbm106d41f950cmm7d2f' isSpecification = 'false' isRoot = 'false' isLeaf = 'false' isAbstract = 'false' py:attrs="{'xmi.id':association.id, 'name':association.name}"> - - diff --git a/gaphor/diagram/attribute.py b/gaphor/diagram/attribute.py index 67f127c41..3ec3591d5 100644 --- a/gaphor/diagram/attribute.py +++ b/gaphor/diagram/attribute.py @@ -8,7 +8,11 @@ from gaphor.diagram import initialize_item from feature import FeatureItem +from zope import interface +from gaphor.interfaces import IAttributeView + class AttributeItem(FeatureItem): + interface.implements(IAttributeView) popup_menu = FeatureItem.popup_menu + ( 'EditItem', diff --git a/gaphor/diagram/klass.py b/gaphor/diagram/klass.py index e1716bba6..7a04e36c1 100644 --- a/gaphor/diagram/klass.py +++ b/gaphor/diagram/klass.py @@ -19,6 +19,8 @@ from classifier import ClassifierItem from attribute import AttributeItem from operation import OperationItem +from gaphor.interfaces import IClassView +from zope import interface class ClassItem(ClassifierItem, diacanvas.CanvasGroupable): """This item visualizes a Class instance. @@ -29,6 +31,8 @@ class ClassItem(ClassifierItem, diacanvas.CanvasGroupable): Items can be added by callling class.add() and class.remove(). This is used to handle CanvasItems, not UML objects! """ + interface.implements(IClassView) + __gproperties__ = { 'show-attributes': (gobject.TYPE_BOOLEAN, 'show attributes', '', diff --git a/gaphor/diagram/nameditem.py b/gaphor/diagram/nameditem.py index 80553af74..ac496bac2 100644 --- a/gaphor/diagram/nameditem.py +++ b/gaphor/diagram/nameditem.py @@ -169,7 +169,12 @@ class TextElement(diacanvas.CanvasItem, diacanvas.CanvasEditable, DiagramItem): +from zope import interface +from gaphor.interfaces import INamedItemView + class NamedItem(ElementItem, diacanvas.CanvasEditable): + interface.implements(INamedItemView) + __gproperties__ = { 'name': (gobject.TYPE_STRING, 'name', '', '', gobject.PARAM_READWRITE) } diff --git a/gaphor/event.py b/gaphor/event.py new file mode 100644 index 000000000..9672f4cb4 --- /dev/null +++ b/gaphor/event.py @@ -0,0 +1,8 @@ +from zope import interface +from gaphor.interfaces import * + +class DiagramItemFocused(object): + interface.implements(IDiagramElementReceivedFocus) + + def __init__(self, diagramItem): + self.diagramItem = diagramItem \ No newline at end of file diff --git a/gaphor/interfaces.py b/gaphor/interfaces.py new file mode 100644 index 000000000..048cfb32e --- /dev/null +++ b/gaphor/interfaces.py @@ -0,0 +1,57 @@ +from zope import interface +from zope import component +class IGaphorAction(interface.Interface): + """Action interface for use in Gaphor""" + +class IMenuAction(interface.Interface): + """An interface to hook up items to.""" + + +class TestAdapter(object): + def __init__(self, context): + self.context = context + +#component.provideAdapter( + #factory=TestAdapter, + #adapts=[IGaphorAction], + #provides=IZopeMenu, + #name="Test adapter name") + +#class TestAdapter2(object): + #def __init__(self, context): + #self.context = context + +#component.provideAdapter( + #factory=TestAdapter2, + #adapts=[IGaphorAction], + #provides=IZopeMenu, + #name="Test adapter 2 name") + +#class TestAction(object): + #interface.implements(IGaphorAction) + +class IDiagramElementReceivedFocus(interface.Interface): + """A diagram item received focus""" + diagramItem = interface.Attribute("The diagram item that received focus") + + +class IWidget(interface.Interface): + """A GTK widget""" + +class IDetailsPage(IWidget): + """A property page which can display itself in a notebook""" + +class IDiagramElement(interface.Interface): + """A diagram element""" + subject = interface.Attribute("The model element connect to this view") + +class INamedItemView(IDiagramElement): + """A view on an attribute (part of a class, interface etc.).""" + + +class IClassView(INamedItemView): + """The graphical view on a class.""" + +class IAttributeView(INamedItemView): + """A view on an attribute (part of a class, interface etc.).""" + diff --git a/gaphor/ui/diagramtab.py b/gaphor/ui/diagramtab.py index 19b4025a2..3b5a3bad7 100644 --- a/gaphor/ui/diagramtab.py +++ b/gaphor/ui/diagramtab.py @@ -14,6 +14,9 @@ from gaphor.ui.abstractwindow import AbstractWindow import gaphor.ui.diagramactions import gaphor.diagram.placementactions +from gaphor.event import DiagramItemFocused +from zope import component + class DiagramTab(object): def __init__(self, owning_window): @@ -126,6 +129,7 @@ class DiagramTab(object): def __on_view_focus_item(self, view, focus_item): self.owning_window.execute_action('ItemFocus') + component.handle(DiagramItemFocused(focus_item)) def __on_view_select_item(self, view, select_item): self.owning_window.execute_action('ItemSelect') diff --git a/gaphor/ui/mainactions.py b/gaphor/ui/mainactions.py index 3ec7d37d4..15f54326b 100644 --- a/gaphor/ui/mainactions.py +++ b/gaphor/ui/mainactions.py @@ -109,6 +109,7 @@ class NewAction(Action): weave_method(NewAction.execute, ErrorHandlerAspect, message='Could not create a new model.') register_action(NewAction) + class RevertAction(Action): id = 'FileRevert' label = _('_Revert...') diff --git a/gaphor/ui/mainwindow.py b/gaphor/ui/mainwindow.py index 611c8ff95..6393b70e8 100644 --- a/gaphor/ui/mainwindow.py +++ b/gaphor/ui/mainwindow.py @@ -15,6 +15,11 @@ from gaphor.ui.menufactory import toolbox_to_menu # Load actions from gaphor.ui import mainactions, diagramactions +from gaphor.ui.objectinspector import ObjectInspector + + +from gaphor.interfaces import * +from zope import component class MainWindow(AbstractWindow): """The main window for the application. @@ -122,7 +127,7 @@ class MainWindow(AbstractWindow): _('_Help'), ( 'Manual', 'About', - '') + ''), ) toolbar = ('FileOpen', @@ -243,6 +248,7 @@ class MainWindow(AbstractWindow): paned = gtk.HPaned() paned.set_property('position', 160) paned.pack1(vbox) + notebook = gtk.Notebook() #notebook.popup_enable() notebook.set_scrollable(True) @@ -250,7 +256,17 @@ class MainWindow(AbstractWindow): notebook.connect_after('switch-page', self.on_notebook_switch_page) - paned.pack2(notebook) + self.objectInspector = ObjectInspector() + diagramReceivedFocus = component.adapter(IDiagramElementReceivedFocus)( + self.objectInspector) + component.provideHandler(diagramReceivedFocus) + + secondPaned = gtk.VPaned() + secondPaned.set_property('position', 600) + secondPaned.pack1(notebook) + secondPaned.pack2(self.objectInspector) + secondPaned.show_all() + paned.pack2(secondPaned) paned.show_all() self.notebook = notebook @@ -360,6 +376,7 @@ class MainWindow(AbstractWindow): a Diagram). """ path = self.get_model().path_from_element(element) + #log.debug("PATH = %s" % path) # Expand the first row: self.get_tree_view().expand_row(path[:-1], False) # Select the diagram, so it can be opened by the OpenModelElement action diff --git a/gaphor/ui/objectinspector.py b/gaphor/ui/objectinspector.py new file mode 100644 index 000000000..243b9ec31 --- /dev/null +++ b/gaphor/ui/objectinspector.py @@ -0,0 +1,180 @@ +import gtk +from gaphor.interfaces import * +from zope import component +from gaphor.UML.uml2 import LiteralSpecification + +class TestDiagramElement(object): + interface.implements(IDiagramElement) + +class NamedItemPropertyPage(gtk.Table): + """An adapter which works for any named item view. + + It also sets up a table view which can be extended.""" + def __init__(self, context): + super(NamedItemPropertyPage, self).__init__(rows=1, columns=1) + + self.context = context + + self.attach(gtk.Label("Name"), 0, 1, 0, 1, xoptions=gtk.FILL, yoptions=0) + nameEntry = gtk.Entry() + nameEntry.set_text(context.subject.name or '') + nameEntry.connect('changed', self.changeName) + self.attach(nameEntry, 1, 2, 0, 1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=0) + + def changeName(self, entry): + self.context.subject.name = entry.get_text() + +component.provideAdapter( + factory=NamedItemPropertyPage, + adapts=[INamedItemView], + provides=IDetailsPage, + name='Properties') + + +class PropertyPropertyPage(NamedItemPropertyPage): + """Adapter which shows a property page for a property (attributes etc.).""" + + +component.provideAdapter( + factory=PropertyPropertyPage, + adapts=[IAttributeView], + provides=IDetailsPage, + name='Properties') + + +class ClassPropertyPage(NamedItemPropertyPage): + """Adapter which shows a property page for a class view.""" + def __init__(self, context): + super(ClassPropertyPage, self).__init__(context) + + # Abstract toggle + self.attach(gtk.Label("Abstract"), 0, 1, 1, 2, xoptions=gtk.FILL, yoptions=0) + abstractCheckButton = gtk.CheckButton() + abstractCheckButton.set_active(context.subject.isAbstract) + abstractCheckButton.connect('toggled', self.changeAbstract) + self.attach(abstractCheckButton, 1, 2, 1, 2, xoptions=gtk.FILL|gtk.EXPAND, yoptions=0) + + # Attributes + attributes = gtk.ListStore(str) + for attr in context.subject.ownedAttribute: + attributes.append([attr.name]) + attributesTreeView = gtk.TreeView(attributes) + attributesTreeView.set_rules_hint(True) + + textRenderer = gtk.CellRendererText() + + attributesColumn = gtk.TreeViewColumn('Attributes', textRenderer, text=0) + + attributesTreeView.append_column(attributesColumn) + self.attach(attributesTreeView, 0, 2, 2, 3, xoptions=gtk.FILL|gtk.EXPAND, yoptions=0) + + def changeName(self, entry): + self.context.subject.name = entry.get_text() + + def changeAbstract(self, checkButton): + self.context.subject.isAbstract=checkButton.get_active() + + +component.provideAdapter( + factory=ClassPropertyPage, + adapts=[IClassView], + provides=IDetailsPage, + name='Properties') + +class TaggedValuePage(gtk.VBox): + """An editor for tagged values associated with elements.""" + def __init__(self, context): + super(TaggedValuePage, self).__init__() + self.context = context + + taggedValues = gtk.ListStore(str, str) + + + for taggedValue in self.context.subject.taggedValue: + tag, value = taggedValue.value.split("=") + taggedValues.append([tag, value]) + taggedValues.append(['','']) + + self.taggedValues = taggedValues + + treeView = gtk.TreeView(taggedValues) + treeView.set_rules_hint(True) + + tagTextRenderer = gtk.CellRendererText() + tagTextRenderer.set_property('editable', True) + tagTextRenderer.connect("edited", self.cellEdited, 0) + + valueTextRenderer = gtk.CellRendererText() + valueTextRenderer.set_property('editable', True) + valueTextRenderer.connect("edited", self.cellEdited, 1) + + tagColumn = gtk.TreeViewColumn('Tag', tagTextRenderer, text=0) + treeView.append_column(tagColumn) + valueColumn = gtk.TreeViewColumn('Value', valueTextRenderer, text=1) + treeView.append_column(valueColumn) + + self.pack_start(treeView) + + def cellEdited(self, cellrenderertext, path, new_text, col): + self.taggedValues[path][col]=new_text + + iter = self.taggedValues.get_iter(path) + + if not new_text and not self.taggedValues[path][1-col] and self.isLastRow(self.taggedValues, iter): + self.taggedValues.remove(iter) + + # Create a new row to enter next value + elif new_text and not self.isLastRow(self.taggedValues, iter): + self.taggedValues.append(['','']) + + self.updateTaggedValuesInModel() + + def updateTaggedValuesInModel(self): + """Write the current list out the model.""" + klass = self.context.subject + while klass.taggedValue: + klass.taggedValue[0].unlink() + for tag, value in self.taggedValues: + taggedValue = klass._factory.create(LiteralSpecification) + taggedValue.value = "%s=%s"%(tag, value) + klass.taggedValue.append(taggedValue) + + def isLastRow(self, model, iter): + return bool(model.iter_next(iter)) + + + +component.provideAdapter( + factory=TaggedValuePage, + adapts=[INamedItemView], + provides=IDetailsPage, + name='Tagged values') + + +class ObjectInspector(gtk.Notebook): + def __init__(self): + super(ObjectInspector, self).__init__() + self.set_scrollable(False) + self.set_show_border(True) + + def loadTabsForCurrentItem(self, item): + """Load all tabs that can operate on the given item.""" + for name, adapter in component.getAdapters( + [item,], IDetailsPage): + self.prepend_page(adapter, gtk.Label(name)) + self.show_all() + + def clearAllTabs(self): + """Remove all tabs from the notebook.""" + [self.remove_page(0) for i in range(self.get_n_pages())] + + def __call__(self, event): + """Called when a diagram item receives focus. + + This reloads all tabs based on the current selection.""" + diagramItem = event.diagramItem + self.clearAllTabs() + if diagramItem is None: + return + self.loadTabsForCurrentItem(diagramItem.item) + \ No newline at end of file diff --git a/setup.py b/setup.py index 0d602311b..661083199 100644 --- a/setup.py +++ b/setup.py @@ -371,7 +371,18 @@ setup(name='gaphor', 'gaphor.UML', 'gaphor.diagram', 'gaphor.ui', - 'gaphor.misc' + 'gaphor.misc', + 'zope', + 'zope.interface', + 'zope.component.bbb', + 'zope.component.bbb.tests', + 'zope.interface.common', + 'zope.component', + 'zope.exceptions', + 'zope.proxy', + 'zope.deprecation', + 'zope.testing', + ], # ext_modules=ext_modules, # data files are relative to /share/gaphor (see setup.cfg)