File: copyservice.py

package info (click to toggle)
gaphor 0.13.0-1
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 3,692 kB
  • ctags: 2,971
  • sloc: python: 19,981; xml: 247; makefile: 54; sh: 40
file content (163 lines) | stat: -rw-r--r-- 5,389 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
"""
Copy / Paste functionality
"""

from zope import interface, component
import gaphas
from gaphor import UML
from gaphor.interfaces import IService, IActionProvider
from gaphor.ui.interfaces import IDiagramSelectionChange
from gaphor.core import _, inject, action, build_action_group, transactional


class CopyService(object):
    """
    Copy/Cut/Paste functionality required a lot of thinking:

    Store a list of DiagramItems that have to be copied in a global
    'copy-buffer'.

    - in order to make copy/paste work, the load/save functions should be
      generatlised to allow a subset to be saved/loaded (which is needed
      anyway for exporting/importing stereotype Profiles).
    - How many data should be saved? (e.g. we copy a diagram item, remove it
      (the underlaying UML element is removed) and the paste the copied item.
      The diagram should act as if we have placed a copy of the removed item
      on the canvas and make the uml element visible again.
    """

    interface.implements(IService, IActionProvider)

    element_factory = inject('element_factory')
    gui_manager = inject('gui_manager')

    menu_xml = """
      <ui>
        <menubar action="mainwindow">
          <menu action="edit">
            <placeholder name="primary">
              <menuitem action="edit-copy" />
              <menuitem action="edit-paste" />
            </placeholder>
          </menu>
        </menubar>
      </ui>
    """

    def __init__(self):
        self.copy_buffer = set()
        self.action_group = build_action_group(self)

    def init(self, app):
        self._app = app
        self.action_group.get_action('edit-copy').props.sensitive = False
        self.action_group.get_action('edit-paste').props.sensitive = False
        
        app.register_handler(self._update)

    def shutdown(self):
        self.copy_buffer = set()
        self._app.unregister_handler(self._update)

    @component.adapter(IDiagramSelectionChange)
    def _update(self, event):
        diagram_view = event.diagram_view
        self.action_group.get_action('edit-copy').props.sensitive = bool(diagram_view.selected_items)

    def copy(self, items):
        if items:
            self.copy_buffer = set(items)
            self.action_group.get_action('edit-paste').props.sensitive = True

    def _load_element(self, name, value):
        """
        Copy an element, preferbly from the list of new items,
        otherwise from the element factory.
        If it does not exist there, do not copy it!
        """
        item = self._new_items.get(value.id)
        if item:
            self._item.load(name, item)
        else:
            item = self.element_factory.lookup(value.id)
            if item:
                self._item.load(name, item)

    def copy_func(self, name, value, reference=False):
        if reference or isinstance(value, UML.Element):
            self._load_element(name, value)
        elif isinstance(value, UML.collection):
            for item in value:
                self._load_element(name, item)
        elif isinstance(value, gaphas.Item):
            self._load_element(name, value)
        else:
            # Plain attribute
            self._item.load(name, str(value))

    @transactional
    def paste(self, diagram):
        """
        Paste items in the copy-buffer to the diagram
        """
        canvas = diagram.canvas
        if not canvas:
            return

        copy_items = [ c for c in self.copy_buffer if c.canvas ]

        # Mapping original id -> new item
        self._new_items = {}

        # Create new id's that have to be used to create the items:
        for ci in copy_items:
            self._new_items[ci.id] = diagram.create(type(ci))

        # Copy attributes and references. References should be
        #  1. in the ElementFactory (hence they are model elements)
        #  2. refered to in new_items
        #  3. canvas property is overridden
        for ci in copy_items:
            self._item = self._new_items[ci.id]
            ci.save(self.copy_func)

        # move pasted items a bit, so user can see result of his action :)
        # update items' matrix immediately
        # TODO: if it is new canvas, then let's not move, how to do it?
        for item in self._new_items.values():
            item.matrix.translate(10, 10)
            canvas.update_matrix(item)

        # solve internal constraints of items immediately as item.postload
        # reconnects items and all handles has to be in place
        canvas.solver.solve()
        for item in self._new_items.values():
            item.postload()


    @action(name='edit-copy', stock_id='gtk-copy')
    def _copy(self):
        view = self.gui_manager.main_window.get_current_diagram_view()
        if view.is_focus():
            items = view.selected_items
            copy_items = []
            for i in items:
                copy_items.append(i)
            self.copy(copy_items)

    @action(name='edit-paste', stock_id='gtk-paste')
    def paste_action(self):
        view = self.gui_manager.main_window.get_current_diagram_view()
        diagram = self.gui_manager.main_window.get_current_diagram()
        if not view:
            return

        self.paste(diagram)

        view.unselect_all()

        for item in self._new_items.values():
            view.select_item(item)


# vim:sw=4:et:ai