root/gaphor/tags/gaphor-0.12.5/gaphor/services/filemanager.py

Revision 2175, 12.0 kB (checked in by wrobe..@pld-linux.org, 1 year ago)

- applied change from trunk, so we are able to create open file dialog box

Line 
1 """
2 The file service is responsible for loading and saving the user data.
3 """
4
5 import gc
6 import gobject, pango, gtk
7 from zope import interface, component
8 from gaphor.interfaces import IService, IActionProvider, IServiceEvent
9 from gaphor.core import _, inject, action, build_action_group
10 from gaphor import UML
11 from gaphor.misc.gidlethread import GIdleThread, Queue, QueueEmpty
12 from gaphor.misc.xmlwriter import XMLWriter
13 from gaphor.misc.errorhandler import error_handler, ErrorHandlerAspect, weave_method
14 DEFAULT_EXT='.gaphor'
15
16 class FileManagerStateChanged(object):
17     """
18     Event class used to send state changes on the ndo Manager.
19     """
20     interface.implements(IServiceEvent)
21
22     def __init__(self, service):
23         self.service = service
24
25
26 class FileManager(object):
27     """
28     The file service, responsible for loading and saving Gaphor models.
29     """
30
31     interface.implements(IService, IActionProvider)
32
33     element_factory = inject('element_factory')
34     gui_manager = inject('gui_manager')
35     properties = inject('properties')
36
37     menu_xml = """
38       <ui>
39         <menubar name="mainwindow">
40           <menu action="file">
41             <placeholder name="primary">
42               <menuitem action="file-new" />
43               <menuitem action="file-new-template" />
44               <menuitem action="file-open" />
45               <menu name="recent" action="file-recent-files">
46                 <menuitem action="file-recent-0" />
47                 <menuitem action="file-recent-1" />
48                 <menuitem action="file-recent-2" />
49                 <menuitem action="file-recent-3" />
50                 <menuitem action="file-recent-4" />
51                 <menuitem action="file-recent-5" />
52                 <menuitem action="file-recent-6" />
53                 <menuitem action="file-recent-7" />
54                 <menuitem action="file-recent-8" />
55               </menu>
56               <separator />
57               <menuitem action="file-save" />
58               <menuitem action="file-save-as" />
59               <separator />
60             </placeholder>
61           </menu>
62         </menubar>
63         <toolbar action="mainwindow-toolbar">
64           <toolitem action="file-open" />
65           <separator />
66           <toolitem action="file-save" />
67           <toolitem action="file-save-as" />
68           <separator />
69         </toolbar>
70       </ui>
71     """
72
73     def __init__(self):
74         self._filename = None
75
76     def init(self, app):
77         self._app = app
78         self.action_group = build_action_group(self)
79         for name, label in (('file-recent-files', '_Recent files'),):
80             a = gtk.Action(name, label, None, None)
81             a.set_property('hide-if-empty', False)
82             self.action_group.add_action(a)
83
84         #recent_files = self.properties('recent-files', [])
85         for i in xrange(0, 9):
86             a = gtk.Action('file-recent-%d' % i, None, None, None)
87             a.set_property('visible', False)
88             self.action_group.add_action(a)
89             a.connect('activate', self.load_recent, i)
90         self.update_recent_files()
91
92     def shutdown(self):
93         pass
94
95     def _set_filename(self, filename):
96         if filename != self._filename:
97             self._filename = filename
98             self.update_recent_files(filename)
99
100     filename = property(lambda s: s._filename, _set_filename)
101
102     def update_recent_files(self, new_filename=None):
103         recent_files = self.properties.get('recent-files', [])
104         if new_filename and new_filename not in recent_files:
105             recent_files.insert(0, new_filename)
106             recent_files = recent_files[0:9]
107             self.properties.set('recent-files', recent_files)
108
109         for i in xrange(0, 9):
110             self.action_group.get_action('file-recent-%d' % i).set_property('visible', False)
111
112         for i, f in enumerate(recent_files):
113             id = 'file-recent%d' % i
114             a = self.action_group.get_action('file-recent-%d' % i)
115             a.props.label = '_%d. %s' % (i+1, f.replace('_', '__'))
116             a.props.tooltip = 'Load %s.' % f
117             a.props.visible = True
118
119     def load_recent(self, action, index):
120         recent_files = self.properties.get('recent-files', [])
121         filename = recent_files[index]
122         self._load(filename)
123         component.handle(FileManagerStateChanged(self))
124        
125     @action(name='file-new', stock_id='gtk-new')
126     def new(self):
127         element_factory = self.element_factory
128         main_window = self.gui_manager.main_window
129         if element_factory.size():
130             dialog = gtk.MessageDialog(main_window.window,
131                 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
132                 gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
133                 _("Opening a new model will flush the currently loaded model.\nAny changes made will not be saved. Do you want to continue?"))
134             answer = dialog.run()
135             dialog.destroy()
136             if answer != gtk.RESPONSE_YES:
137                 return
138
139         element_factory.flush()
140         gc.collect()
141         model = element_factory.create(UML.Package)
142         model.name = _('New model')
143         diagram = element_factory.create(UML.Diagram)
144         diagram.package = model
145         diagram.name= _('main')
146         self.filename = None
147         element_factory.notify_model()
148
149         main_window.select_element(diagram)
150         main_window.show_diagram(diagram)
151
152         component.handle(FileManagerStateChanged(self))
153
154
155     def _load(self, filename):
156         try:
157             from gaphor import storage
158             log.debug('Loading from: %s' % filename)
159             main_window = self.gui_manager.main_window
160             queue = Queue()
161             win = show_status_window(_('Loading...'), _('Loading model from %s') % filename, main_window.window, queue)
162             gc.collect()
163             worker = GIdleThread(storage.load_generator(filename, self.element_factory), queue)
164             #self._window.action_pool.insensivate_actions()
165             #undo_manager.clear_undo_stack()
166             #get_undo_manager().clear_redo_stack()
167             worker.start()
168             worker.wait()
169             if worker.error:
170                 log.error('Error while loading model from file %s: %s' % (filename, worker.error))
171                 error_handler(message='Error while loading model from file %s' % filename, exc_info=worker.exc_info)
172
173             # Let this be handled by the main window itself:
174             #self._window.set_message('Model loaded successfully')
175             view = main_window.tree_view
176
177             self.filename = filename
178
179             # Expand all root elements:
180             view.expand_root_nodes()
181         finally:
182             try:
183                 win.destroy()
184             except:
185                 pass
186
187
188     def _save(self, filename):
189         if filename and len(filename) > 0:
190             from gaphor import storage
191             if not filename.endswith(DEFAULT_EXT):
192                 filename = filename + DEFAULT_EXT
193
194             queue = Queue()
195             log.debug('Saving to: %s' % filename)
196             win = show_status_window('Saving...', 'Saving model to %s' % filename, self.gui_manager.main_window.window, queue)
197             try:
198                 out = open(filename, 'w')
199
200                 worker = GIdleThread(storage.save_generator(XMLWriter(out), self.element_factory), queue)
201                 #action_states = self._window.action_pool.get_action_states()
202                 #self._window.action_pool.insensivate_actions()
203                 worker.start()
204                 worker.wait()
205                 if worker.error:
206                     log.error('Error while saving model to file %s: %s' % (filename, worker.error))
207                     error_handler(message='Error while saving model to file %s' % filename, exc_info=worker.exc_info)
208                 out.close()
209
210                 self.filename = filename
211
212                 # Restore states of actions
213                 #self._window.action_pool.set_action_states(action_states)
214             finally:
215                 win.destroy()
216
217     def _open_dialog(self, title):
218         filesel = gtk.FileChooserDialog(title=title,
219                                         action=gtk.FILE_CHOOSER_ACTION_OPEN,
220                                         buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
221
222         filter = gtk.FileFilter()
223         filter.set_name("Gaphor models")
224         filter.add_pattern("*.gaphor")
225         filesel.add_filter(filter)
226
227         filter = gtk.FileFilter()
228         filter.set_name("All files")
229         filter.add_pattern("*")
230         filesel.add_filter(filter)
231
232         if self.filename:
233             filesel.set_current_name(self.filename)
234
235         response = filesel.run()
236         filename = filesel.get_filename()
237         filesel.destroy()
238         if not filename or response != gtk.RESPONSE_OK:
239             return
240         return filename
241
242
243     @action(name='file-new-template', label=_('New from template'))
244     def new_from_template(self):
245         filename = self._open_dialog('New Gaphor model from template')
246         if filename:
247             self._load(filename)
248
249             # It's a template: unset filename
250             self.filename = None
251             component.handle(FileManagerStateChanged(self))
252
253
254     @action(name='file-open', stock_id='gtk-open')
255     def open(self):
256         filename = self._open_dialog('Open Gaphor model')
257         if filename:
258             self._load(filename)
259             component.handle(FileManagerStateChanged(self))
260
261
262     @action(name='file-save', stock_id='gtk-save')
263     def save(self):
264         filename = self.filename
265         if filename:
266             self._save(filename)
267             component.handle(FileManagerStateChanged(self))
268         else:
269             self.save_as()
270
271
272     @action(name='file-save-as', stock_id='gtk-save-as')
273     def save_as(self):
274         filename = self.filename
275         filesel = gtk.FileChooserDialog(title=_('Save Gaphor model as'),
276                                         action=gtk.FILE_CHOOSER_ACTION_SAVE,
277                                         buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK))
278         if filename:
279             filesel.set_current_name(filename)
280         response = filesel.run()
281         filename = None
282         if response == gtk.RESPONSE_OK:
283             filename = filesel.get_filename()
284         filesel.destroy()
285         self._save(filename)
286         component.handle(FileManagerStateChanged(self))
287
288
289 def show_status_window(title, message, parent=None, queue=None):
290     """
291     Create a borderless window on the parent (main window), with a label and
292     a progress bar.
293     """
294     win = gtk.Window(gtk.WINDOW_TOPLEVEL)
295     win.set_title(title)
296     win.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
297     win.set_transient_for(parent)
298     win.set_modal(True)
299     win.set_resizable(False)
300     win.set_decorated(False)
301     win.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_SPLASHSCREEN)
302     frame = gtk.Frame()
303     win.add(frame)
304     frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
305     vbox = gtk.VBox(spacing=12)
306     frame.add(vbox)
307     vbox.set_border_width(12)
308     label = gtk.Label(message)
309     label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
310     vbox.pack_start(label)
311     progress_bar = gtk.ProgressBar()
312     progress_bar.set_size_request(400, -1)
313     vbox.pack_start(progress_bar, expand=False, fill=False, padding=0)
314
315     def progress_idle_handler(progress_bar, queue):
316         #print '.',
317         percentage = 0
318         try:
319             while True:
320                 percentage = queue.get()
321         except QueueEmpty:
322             pass
323         if percentage:
324             progress_bar.set_fraction(min(percentage / 100.0, 100.0))
325         return True
326
327     if queue:
328         idle_id = gobject.idle_add(progress_idle_handler, progress_bar, queue,
329                                    priority=gobject.PRIORITY_LOW)
330         # Make sure the idle fucntion is removed as soon as the window
331         # is destroyed.
332         def remove_progress_idle_handler(window, idle_id):
333             #print 'remove_progress_idle_handler', idle_id
334             gobject.source_remove(idle_id)
335         win.connect('destroy', remove_progress_idle_handler, idle_id)
336
337     win.show_all()
338     return win
339
340 # vim:sw=4:et:ai
Note: See TracBrowser for help on using the browser.