def test_construction_after_setting_layout(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paned = DockPaned() group = DockGroup() item = DockItem() layout = DockLayout() layout.add(frame) win.add(frame) frame.add(paned) paned.add(group) group.add(item) assert frame in layout.frames self.assertEquals(4, len(layout._signal_handlers)) self.assertEquals(9, len(layout._signal_handlers[frame])) paned.remove(group) self.assertEquals(2, len(layout._signal_handlers), layout._signal_handlers) assert frame in layout._signal_handlers.keys(), layout._signal_handlers assert paned in layout._signal_handlers.keys(), layout._signal_handlers assert group not in layout._signal_handlers.keys(), layout._signal_handlers assert item not in layout._signal_handlers.keys(), layout._signal_handlers assert frame in layout.frames
def test_get_widgets(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paned = DockPaned() group = DockGroup() item = DockItem() label = gtk.Label() layout = DockLayout() layout.add(frame) win.add(frame) frame.add(paned) paned.add(group) group.add(item) item.add(label) group2 = DockGroup() paned.add(group2) paned.set_name('foo') widgets = list(layout.get_widgets('foo')) assert len(widgets) == 1 assert widgets[0] is paned widgets = list(layout.get_widgets('EtkDockGroup')) assert len(widgets) == 2 assert widgets[0] is group assert widgets[1] is group2
def test_construction(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paned = DockPaned() group = DockGroup() item = DockItem() win.add(frame) frame.add(paned) paned.add(group) group.add(item) layout = DockLayout() layout.add(frame) assert frame in layout.frames print layout._signal_handlers self.assertEquals(4, len(layout._signal_handlers)) self.assertEquals(9, len(layout._signal_handlers[frame])) layout.remove(frame) assert not layout._signal_handlers, layout._signal_handlers assert frame not in layout.frames
def setUp(self): self.layout = DockLayout() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.frame = DockFrame() self.group = DockGroup() self.layout.add(self.frame) self.window.add(self.frame) self.frame.add(self.group)
def test_serialize(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) layout = DockLayout() frame = DockFrame() win.add(frame) layout.add(frame) paned = DockPaned() frame.add(paned) group = DockGroup() paned.add(group) item = DockItem(title='t', title_tooltip_text='xx', icon_name='icon', stock_id="") item.set_name('fillme') group.add(item) s = serialize(layout) assert '<layout><dockframe height="1" width="1">'\ '<dockpaned orientation="horizontal">'\ '<dockgroup weight="100">'\ '<dockitem icon_name="icon" title="t" tooltip="xx" />'\ '</dockgroup></dockpaned></dockframe></layout>' == s, s
def setUp(self): self.layout = DockLayout() def drag_get_data(widget, context, target, timestamp): selection_data = StubSelectionData() context.source_widget.do_drag_data_get(context, selection_data, None, timestamp) self.layout.on_widget_drag_data_received(widget, context, 20, 20, selection_data, None, timestamp) DockGroup.drag_get_data = drag_get_data DockPaned.drag_get_data = drag_get_data DockFrame.drag_get_data = drag_get_data
def default_window(n_groups): world.window = gtk.Window(gtk.WINDOW_TOPLEVEL) world.window.set_default_size(800, 150) world.frame = DockFrame() world.window.add(world.frame) world.layout = DockLayout() world.layout.add(world.frame) paned = DockPaned() world.frame.add(paned) world.window.show() world.frame.show() paned.show() world.groups = [] for i in range(int(n_groups)): group = DockGroup() paned.add(group) group.show() world.groups.append(group)
def test_get_widgets_with_many_frames(self): frame1 = DockFrame() frame2 = DockFrame() frame3 = DockFrame() layout = DockLayout() layout.add(frame1) layout.add(frame2) layout.add(frame3) widgets = list(layout.get_widgets('foo')) assert len(widgets) == 0
class MainWindow(object): """ The main window for the application. It contains a Namespace-based tree view and a menu and a statusbar. """ interface.implements(IService, IActionProvider) component_registry = inject('component_registry') properties = inject('properties') element_factory = inject('element_factory') action_manager = inject('action_manager') file_manager = inject('file_manager') ui_manager = inject('ui_manager') title = 'Gaphor' size = property(lambda s: s.properties.get('ui.window-size', (760, 580))) menubar_path = '/mainwindow' toolbar_path = '/mainwindow-toolbar' resizable = True menu_xml = """ <ui> <menubar name="mainwindow"> <menu action="file"> <placeholder name="primary" /> <separator /> <menu action="file-export" /> <menu action="file-import" /> <separator /> <placeholder name="secondary" /> <placeholder name="ternary" /> <separator /> <menuitem action="file-quit" /> </menu> <menu action="edit"> <placeholder name="primary" /> <placeholder name="secondary" /> <placeholder name="ternary" /> </menu> <menu action="diagram"> <menuitem action="diagram-drawing-style" /> <separator /> <placeholder name="primary" /> <placeholder name="secondary" /> <placeholder name="ternary" /> </menu> <menu action="tools"> <placeholder name="primary" /> <placeholder name="secondary" /> <placeholder name="ternary" /> </menu> <menu action="window"> <placeholder name="primary" /> <placeholder name="secondary" /> <placeholder name="ternary" /> </menu> <menu action="help"> <placeholder name="primary" /> <placeholder name="secondary" /> <placeholder name="ternary" /> </menu> </menubar> <toolbar name='mainwindow-toolbar'> <placeholder name="left" /> <separator expand="true" /> <placeholder name="right" /> </toolbar> </ui> """ def __init__(self): self.window = None self.model_changed = False # Map tab contents to DiagramTab #self.notebook_map = {} self._current_diagram_tab = None self.layout = None def init(self, app=None): #self.init_pygtk() self.init_stock_icons() self.init_action_group() self.init_ui_components() def init_pygtk(self): """ Make sure we have GTK+ >= 2.0 """ import pygtk pygtk.require('2.0') del pygtk def init_stock_icons(self): # Load stock items import gaphor.ui.stock gaphor.ui.stock.load_stock_icons() def init_ui_components(self): component_registry = self.component_registry for ep in pkg_resources.iter_entry_points('gaphor.uicomponents'): log.debug('found entry point uicomponent.%s' % ep.name) cls = ep.load() if not IUIComponent.implementedBy(cls): raise NameError, 'Entry point %s doesn''t provide IUIComponent' % ep.name uicomp = cls() uicomp.ui_name = ep.name component_registry.register_utility(uicomp, IUIComponent, ep.name) if IActionProvider.providedBy(uicomp): self.action_manager.register_action_provider(uicomp) def shutdown(self): if self.window: self.window.destroy() self.window = None save_accel_map() cr = self.component_registry cr.unregister_handler(self._on_file_manager_state_changed) cr.unregister_handler(self._on_undo_manager_state_changed) cr.unregister_handler(self._new_model_content) self.ui_manager.remove_action_group(self.action_group) def init_action_group(self): self.action_group = build_action_group(self) for name, label in (('file', '_File'), ('file-export', '_Export'), ('file-import', '_Import'), ('edit', '_Edit'), ('diagram', '_Diagram'), ('tools', '_Tools'), ('window', '_Window'), ('help', '_Help')): a = gtk.Action(name, label, None, None) a.set_property('hide-if-empty', False) self.action_group.add_action(a) self._tab_ui_settings = None self.action_group.get_action('diagram-drawing-style').set_active(self.properties('diagram.sloppiness', 0) != 0) self.action_manager.register_action_provider(self) def get_filename(self): """ Return the file name of the currently opened model. """ return self.file_manager.filename def get_current_diagram_tab(self): """ Get the currently opened and viewed DiagramTab, shown on the right side of the main window. See also: get_current_diagram(), get_current_diagram_view(). """ return self._current_diagram_tab def get_current_diagram(self): """ Return the Diagram associated with the viewed DiagramTab. See also: get_current_diagram_tab(), get_current_diagram_view(). """ tab = self._current_diagram_tab return tab and tab.get_diagram() def get_current_diagram_view(self): """ Return the DiagramView associated with the viewed DiagramTab. See also: get_current_diagram_tab(), get_current_diagram(). """ tab = self._current_diagram_tab return tab and tab.get_view() def ask_to_close(self): """ Ask user to close window if the model has changed. The user is asked to either discard the changes, keep the application running or save the model and quit afterwards. """ if self.model_changed: dialog = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE, _('Save changed to your model before closing?')) dialog.format_secondary_text( _('If you close without saving, your changes will be discarded.')) dialog.add_buttons('Close _without saving', gtk.RESPONSE_REJECT, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_YES) dialog.set_default_response(gtk.RESPONSE_YES) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: # On filedialog.cancel, the application should not close. return self.file_manager.action_save() return response == gtk.RESPONSE_REJECT return True def show_diagram(self, diagram): """ Show a Diagram element in a new tab. If a tab is already open, show that one instead. """ # Try to find an existing window/tab and let it get focus: for tab in self.get_tabs(): if tab.get_diagram() is diagram: self.set_current_page(tab) return tab tab = DiagramTab(diagram) dock_item = tab.construct() dock_item.set_name('diagram-tab') dock_item.diagram_tab = tab assert dock_item.get_name() == 'diagram-tab' tab.set_drawing_style(self.properties('diagram.sloppiness', 0)) self.add_tab(dock_item) return tab def open(self): load_accel_map() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title(self.title) self.window.set_size_request(*self.size) self.window.set_resizable(self.resizable) # set default icons of gaphor windows icon_dir = os.path.abspath(pkg_resources.resource_filename('gaphor.ui', 'pixmaps')) icons = (gtk.gdk.pixbuf_new_from_file(os.path.join(icon_dir, f)) for f in ICONS) self.window.set_icon_list(*icons) self.window.add_accel_group(self.ui_manager.get_accel_group()) # Create a full featured window. vbox = gtk.VBox() self.window.add(vbox) vbox.show() menubar = self.ui_manager.get_widget(self.menubar_path) if menubar: vbox.pack_start(menubar, expand=False) toolbar = self.ui_manager.get_widget(self.toolbar_path) if toolbar: vbox.pack_start(toolbar, expand=False) def _factory(name): comp = self.component_registry.get_utility(IUIComponent, name) logger.debug('open component %s' % str(comp)) return comp.open() filename = pkg_resources.resource_filename('gaphor.ui', 'layout.xml') self.layout = DockLayout() with open(filename) as f: deserialize(self.layout, vbox, f.read(), _factory) self.layout.connect('item-closed', self._on_item_closed) self.layout.connect('item-selected', self._on_item_selected) vbox.show() # TODO: add statusbar self.window.show() self.window.connect('delete-event', self._on_window_delete) # We want to store the window size, so it can be reloaded on startup self.window.set_property('allow-shrink', True) self.window.connect('size-allocate', self._on_window_size_allocate) self.window.connect('destroy', self._on_window_destroy) #self.window.connect_after('key-press-event', self._on_key_press_event) cr = self.component_registry cr.register_handler(self._on_file_manager_state_changed) cr.register_handler(self._on_undo_manager_state_changed) cr.register_handler(self._new_model_content) # TODO: register on ElementCreate/Delete event def open_welcome_page(self): """ Create a new tab with a textual welcome page, a sort of 101 for Gaphor. """ pass def set_title(self): """ Sets the window title. """ filename = self.file_manager.filename if self.window: if filename: title = '%s - %s' % (self.title, filename) else: title = self.title if self.model_changed: title += ' *' self.window.set_title(title) # Notebook methods: def add_tab(self, item): """ Create a new tab on the notebook with window as its contents. Returns: The page number of the tab. """ group = list(self.layout.get_widgets('diagrams'))[0] group.insert_item(item) item.show_all() #item.diagram_tab = tab #self.notebook_map[item] = tab def set_current_page(self, tab): """ Force a specific tab (DiagramTab) to the foreground. """ for i in self.layout.get_widgets('diagram-tab'): if i.diagram_tab is tab: g = i.get_parent() g.set_current_item(g.item_num(i)) return #for p, t in self.notebook_map.iteritems(): # if tab is t: # num = self.notebook.page_num(p) # self.notebook.set_current_page(num) # return pass def get_tabs(self): tabs = map(lambda i: i.diagram_tab, self.layout.get_widgets('diagram-tab')) return tabs # Signal callbacks: @component.adapter(ModelFactoryEvent) def _new_model_content(self, event): """ Open the toplevel element and load toplevel diagrams. """ # TODO: Make handlers for ModelFactoryEvent from within the GUI obj for diagram in self.element_factory.select(lambda e: e.isKindOf(UML.Diagram) and not (e.namespace and e.namespace.namespace)): self.show_diagram(diagram) @component.adapter(FileManagerStateChanged) def _on_file_manager_state_changed(self, event): # We're only interested in file operations if event.service is self.file_manager: self.model_changed = False self.set_title() @component.adapter(UndoManagerStateChanged) def _on_undo_manager_state_changed(self, event): """ """ undo_manager = event.service if not self.model_changed and undo_manager.can_undo(): self.model_changed = True self.set_title() def _on_window_destroy(self, window): """ Window is destroyed... Quit the application. """ self.window = None if gobject.main_depth() > 0: gtk.main_quit() cr = self.component_registry cr.unregister_handler(self._on_undo_manager_state_changed) cr.unregister_handler(self._on_file_manager_state_changed) cr.unregister_handler(self._new_model_content) def _on_window_delete(self, window = None, event = None): return not self.ask_to_close() def _clear_ui_settings(self): if self._tab_ui_settings: action_group, ui_id = self._tab_ui_settings self.ui_manager.remove_action_group(action_group) self.ui_manager.remove_ui(ui_id) self._tab_ui_settings = None def _on_item_closed(self, layout, group, item): self._clear_ui_settings() try: ui_component = item.ui_component except AttributeError: pass else: ui_component.close() item.destroy() def _on_item_selected(self, layout, group, item): """ Another page (tab) is put on the front of the diagram notebook. A dummy action is executed. """ # TODO: Here the magic happens! # TODO: Need to see what the active view is, or make toolbox actions global self._clear_ui_settings() # Is it a diagram view? try: tab = item.diagram_tab except AttributeError: # Not a diagram tab return self._current_diagram_tab = tab #content = self.notebook.get_nth_page(page_num) #tab = self.notebook_map.get(content) #assert isinstance(tab, DiagramTab), str(tab) self.ui_manager.insert_action_group(tab.action_group, -1) ui_id = self.ui_manager.add_ui_from_string(tab.menu_xml) self._tab_ui_settings = tab.action_group, ui_id log.debug('Menus updated with %s, %d' % self._tab_ui_settings) # Make sure everyone knows the selection has changed. self.component_registry.handle(DiagramTabChange(item), DiagramSelectionChange(tab.view, tab.view.focused_item, tab.view.selected_items)) def _on_window_size_allocate(self, window, allocation): """ Store the window size in a property. """ self.properties.set('ui.window-size', (allocation.width, allocation.height)) # Actions: @action(name='file-quit', stock_id='gtk-quit') def quit(self): # TODO: check for changes (e.g. undo manager), fault-save self.ask_to_close() and gtk.main_quit() self.shutdown() @toggle_action(name='diagram-drawing-style', label='Hand drawn style', active=False) def hand_drawn_style(self, active): """ Toggle between straight diagrams and "hand drawn" diagram style. """ if active: sloppiness = 0.5 else: sloppiness = 0.0 for tab in self.get_tabs(): tab.set_drawing_style(sloppiness) self.properties.set('diagram.sloppiness', sloppiness) def create_item(self, ui_component): #, widget, title, placement=None): """ Create an item for a ui component. This method can be called from UIComponents. """ item = DockItem(ui_component.title) item.add(ui_component.open()) group = DockGroup() group.insert_item(item) placement = ui_component.placement if placement: if placement == 'floating': add_new_group_floating(group, self.layout, ui_component.size) else: location = self.layout.get_widgets(placement[1])[0] { 'left': add_new_group_left, 'right': add_new_group_right, 'above': add_new_group_above, 'below': add_new_group_below }[placement[0]](location, group) else: add_new_group_floating(group) item.show() item.ui_component = ui_component group.show()
def open(self): load_accel_map() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title(self.title) self.window.set_size_request(*self.size) self.window.set_resizable(self.resizable) # set default icons of gaphor windows icon_dir = os.path.abspath(pkg_resources.resource_filename('gaphor.ui', 'pixmaps')) icons = (gtk.gdk.pixbuf_new_from_file(os.path.join(icon_dir, f)) for f in ICONS) self.window.set_icon_list(*icons) self.window.add_accel_group(self.ui_manager.get_accel_group()) # Create a full featured window. vbox = gtk.VBox() self.window.add(vbox) vbox.show() menubar = self.ui_manager.get_widget(self.menubar_path) if menubar: vbox.pack_start(menubar, expand=False) toolbar = self.ui_manager.get_widget(self.toolbar_path) if toolbar: vbox.pack_start(toolbar, expand=False) def _factory(name): comp = self.component_registry.get_utility(IUIComponent, name) logger.debug('open component %s' % str(comp)) return comp.open() filename = pkg_resources.resource_filename('gaphor.ui', 'layout.xml') self.layout = DockLayout() with open(filename) as f: deserialize(self.layout, vbox, f.read(), _factory) self.layout.connect('item-closed', self._on_item_closed) self.layout.connect('item-selected', self._on_item_selected) vbox.show() # TODO: add statusbar self.window.show() self.window.connect('delete-event', self._on_window_delete) # We want to store the window size, so it can be reloaded on startup self.window.set_property('allow-shrink', True) self.window.connect('size-allocate', self._on_window_size_allocate) self.window.connect('destroy', self._on_window_destroy) #self.window.connect_after('key-press-event', self._on_key_press_event) cr = self.component_registry cr.register_handler(self._on_file_manager_state_changed) cr.register_handler(self._on_undo_manager_state_changed) cr.register_handler(self._new_model_content)
class PlacementTest(unittest.TestCase): def setUp(self): self.layout = DockLayout() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.frame = DockFrame() self.group = DockGroup() self.layout.add(self.frame) self.window.add(self.frame) self.frame.add(self.group) def test_placement_left(self): g1, g2 = DockGroup(), DockGroup() docklayout.add_new_group_left(self.group, g1) paned = g1.get_parent() assert isinstance(paned, DockPaned), paned assert self.group.get_parent() is paned assert paned.get_nth_item(0) is g1 assert paned.get_nth_item(1) is self.group docklayout.add_new_group_left(self.group, g2) assert self.group.get_parent() is paned assert g2.get_parent() is paned assert paned.get_nth_item(0) is g1 assert paned.get_nth_item(1) is g2 assert paned.get_nth_item(2) is self.group def test_placement_right(self): g1, g2 = DockGroup(), DockGroup() docklayout.add_new_group_right(self.group, g1) paned = g1.get_parent() assert isinstance(paned, DockPaned), paned assert self.group.get_parent() is paned assert paned.get_nth_item(0) is self.group assert paned.get_nth_item(1) is g1 docklayout.add_new_group_right(self.group, g2) assert self.group.get_parent() is paned assert g2.get_parent() is paned assert paned.get_nth_item(0) is self.group assert paned.get_nth_item(1) is g2 assert paned.get_nth_item(2) is g1 def test_placement_above(self): g1, g2 = DockGroup(), DockGroup() docklayout.add_new_group_above(self.group, g1) paned = g1.get_parent() assert isinstance(paned, DockPaned), paned assert self.group.get_parent() is paned assert paned.get_nth_item(0) is g1 assert paned.get_nth_item(1) is self.group docklayout.add_new_group_above(self.group, g2) assert self.group.get_parent() is paned assert g2.get_parent() is paned assert paned.get_nth_item(0) is g1 assert paned.get_nth_item(1) is g2 assert paned.get_nth_item(2) is self.group def test_placement_below(self): g1, g2 = DockGroup(), DockGroup() docklayout.add_new_group_below(self.group, g1) paned = g1.get_parent() assert isinstance(paned, DockPaned), paned assert self.group.get_parent() is paned assert paned.get_nth_item(0) is self.group assert paned.get_nth_item(1) is g1 docklayout.add_new_group_below(self.group, g2) assert self.group.get_parent() is paned assert g2.get_parent() is paned assert paned.get_nth_item(0) is self.group assert paned.get_nth_item(1) is g2 assert paned.get_nth_item(2) is g1
class TestDockLayoutDnD(unittest.TestCase): def setUp(self): self.layout = DockLayout() def drag_get_data(widget, context, target, timestamp): selection_data = StubSelectionData() context.source_widget.do_drag_data_get(context, selection_data, None, timestamp) self.layout.on_widget_drag_data_received(widget, context, 20, 20, selection_data, None, timestamp) DockGroup.drag_get_data = drag_get_data DockPaned.drag_get_data = drag_get_data DockFrame.drag_get_data = drag_get_data def tearDown(self): del self.layout del DockGroup.drag_get_data del DockPaned.drag_get_data del DockFrame.drag_get_data def test_drag_drop_on_group(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paned = DockPaned() group = DockGroup() item = DockItem() layout = self.layout layout.add(frame) win.add(frame) frame.add(paned) paned.add(group) group.add(item) win.set_default_size(200, 200) win.show_all() while gtk.events_pending(): gtk.main_iteration() context = StubContext(group, [group.items[0]]) group.do_drag_begin(context) x, y = 30, 30 layout.on_widget_drag_motion(group, context, x, y, 0) assert layout._drag_data assert layout._drag_data.drop_widget is group layout.on_widget_drag_drop(group, context, x, y, 0) def test_drag_drop_on_paned(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paned = DockPaned() groups = (DockGroup(), DockGroup()) item = DockItem() layout = self.layout layout.add(frame) win.add(frame) frame.add(paned) map(paned.add, groups) groups[0].add(item) win.set_default_size(200, 200) win.show_all() x, y = 10, 10 context = StubContext(groups[0], [groups[0].items[0]]) layout.on_widget_drag_motion(paned, context, x, y, 0) assert layout._drag_data assert layout._drag_data.drop_widget is paned, '%s != %s' % (layout._drag_data.drop_widget, paned) def test_remove_paned_with_one_child(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paned = DockPaned() groups = (DockGroup(), DockGroup()) item = DockItem() layout = self.layout layout.add(frame) win.add(frame) frame.add(paned) map(paned.add, groups) #groups[0].add(item) win.set_default_size(200, 200) win.show_all() # simulate end of DnD on group context = StubContext(groups[0], None) layout.on_widget_drag_end(groups[0], context) assert not paned.get_parent() assert groups[1].get_parent() is frame def test_remove_nested_paned_with_one_child(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paneds = (DockPaned(), DockPaned()) groups = (DockGroup(), DockGroup()) item = DockItem() layout = self.layout layout.add(frame) win.add(frame) frame.add(paneds[0]) paneds[0].add(groups[0]) paneds[0].add(paneds[1]) paneds[1].add(groups[1]) #groups[0].add(item) win.set_default_size(200, 200) win.show_all() # simulate end of DnD on group, where group is removed and only one # paned remains. context = StubContext(groups[0], None) layout.on_widget_drag_end(paneds[1], context) assert not paneds[1].get_parent() assert groups[1].get_parent() is paneds[0], (paneds, groups[1].get_parent()) def test_remove_empty_groups_recursively(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paneds = (DockPaned(), DockPaned(), DockPaned()) group = DockGroup() item = DockItem() layout = self.layout layout.add(frame) win.add(frame) frame.add(paneds[0]) paneds[0].add(paneds[1]) paneds[1].add(paneds[2]) paneds[2].add(group) win.set_default_size(200, 200) win.show_all() context = StubContext(group, None) layout.on_widget_drag_end(group, context) # TODO: check is paned[0] assert not paneds[0].get_parent()
def __init__(self, docklayout=None, dockframe=None): gtk.Window.__init__(self) self.set_default_size(500, 150) self.set_title("etk.docking demo") self.set_border_width(4) self.file_counter = 1 self.subwindows = [] vbox = gtk.VBox() vbox.set_spacing(4) self.add(vbox) ######################################################################## # Docking ######################################################################## if docklayout and dockframe: self.dockframe = dockframe self.docklayout = docklayout else: self.dockframe = DockFrame() self.dockframe.set_border_width(8) g = DockGroup() g.set_name("main") self.dockframe.add(g) self.docklayout = DockLayout() self.docklayout.add(self.dockframe) settings["main"].auto_remove = False settings["main"].can_float = True settings["main"].inherit_settings = False settings["main"].expand = False # To change default group behaviour: # self.docklayout.settings[None].inherit_settings = False vbox.pack_start(self.dockframe) def on_item_closed(layout, group, item): item.destroy() print "closed item:", item.title self.docklayout.connect("item-closed", on_item_closed) def on_item_selected(layout, group, item): print "Selected item:", item.title self.docklayout.connect("item-selected", on_item_selected) ######################################################################## # Testing Tools ######################################################################## adddibutton = gtk.Button("Create docked items") adddibutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) adddibutton.connect("clicked", self._on_add_di_button_clicked) vbox.pack_start(adddibutton, False, False) orientationbutton = gtk.Button("Switch Orientation") orientationbutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) orientationbutton.connect("clicked", self._on_orientation_button_clicked) vbox.pack_start(orientationbutton, False, False) hbox = gtk.HBox() savebutton = gtk.Button("Save layout") savebutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) savebutton.connect("clicked", self._on_save_button_clicked) hbox.pack_start(savebutton, True, True) loadbutton = gtk.Button("Load layout") loadbutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) loadbutton.connect("clicked", self._on_load_button_clicked) hbox.pack_start(loadbutton, True, True) vbox.pack_start(hbox, False, False) self.show_all()
class MainWindow(gtk.Window): def __init__(self, docklayout=None, dockframe=None): gtk.Window.__init__(self) self.set_default_size(500, 150) self.set_title("etk.docking demo") self.set_border_width(4) self.file_counter = 1 self.subwindows = [] vbox = gtk.VBox() vbox.set_spacing(4) self.add(vbox) ######################################################################## # Docking ######################################################################## if docklayout and dockframe: self.dockframe = dockframe self.docklayout = docklayout else: self.dockframe = DockFrame() self.dockframe.set_border_width(8) g = DockGroup() g.set_name("main") self.dockframe.add(g) self.docklayout = DockLayout() self.docklayout.add(self.dockframe) settings["main"].auto_remove = False settings["main"].can_float = True settings["main"].inherit_settings = False settings["main"].expand = False # To change default group behaviour: # self.docklayout.settings[None].inherit_settings = False vbox.pack_start(self.dockframe) def on_item_closed(layout, group, item): item.destroy() print "closed item:", item.title self.docklayout.connect("item-closed", on_item_closed) def on_item_selected(layout, group, item): print "Selected item:", item.title self.docklayout.connect("item-selected", on_item_selected) ######################################################################## # Testing Tools ######################################################################## adddibutton = gtk.Button("Create docked items") adddibutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) adddibutton.connect("clicked", self._on_add_di_button_clicked) vbox.pack_start(adddibutton, False, False) orientationbutton = gtk.Button("Switch Orientation") orientationbutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) orientationbutton.connect("clicked", self._on_orientation_button_clicked) vbox.pack_start(orientationbutton, False, False) hbox = gtk.HBox() savebutton = gtk.Button("Save layout") savebutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) savebutton.connect("clicked", self._on_save_button_clicked) hbox.pack_start(savebutton, True, True) loadbutton = gtk.Button("Load layout") loadbutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) loadbutton.connect("clicked", self._on_load_button_clicked) hbox.pack_start(loadbutton, True, True) vbox.pack_start(hbox, False, False) self.show_all() # def on_has_toplevel_focus(window, pspec): # print 'Has toplevel focus', window, pspec # print 'Focus widget is', window.get_focus() # self.connect('notify::has-toplevel-focus', on_has_toplevel_focus) def _on_add_di_button_clicked(self, button): def add_dockitems(child): if isinstance(child, DockGroup): self._add_dockitems(child) elif isinstance(child, DockPaned): for child in child: add_dockitems(child) for child in self.dockframe: add_dockitems(child) def _on_orientation_button_clicked(self, button): def switch_orientation(paned): if isinstance(paned, DockPaned): if paned.get_orientation() == gtk.ORIENTATION_HORIZONTAL: paned.set_orientation(gtk.ORIENTATION_VERTICAL) else: paned.set_orientation(gtk.ORIENTATION_HORIZONTAL) for child in paned.get_children(): switch_orientation(child) paned = self.dockframe.get_children()[0] switch_orientation(paned) def _on_save_button_clicked(self, button): file = "demo.sav" s = dockstore.serialize(self.docklayout) with open(file, "w") as f: f.write(s) def _on_load_button_clicked(self, button): file = "demo.sav" with open(file) as f: s = f.read() newlayout = dockstore.deserialize(s, self._create_content) main_frames = list(dockstore.get_main_frames(newlayout)) assert len(main_frames) == 1, main_frames subwindow = MainWindow(newlayout, main_frames[0]) self.subwindows.append(subwindow) dockstore.finish(newlayout, main_frames[0]) for f in newlayout.frames: f.get_toplevel().show_all() def _create_content(self, text=None): # Create a TextView and set some example text scrolledwindow = gtk.ScrolledWindow() scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) textview = gtk.TextView() textview.get_buffer().set_text(text) scrolledwindow.add(textview) return scrolledwindow def _add_dockitems(self, dockgroup): examples = [ (gtk.STOCK_EXECUTE, "calculator", "#!/usr/bin/env python\n\nprint 'Hello!'"), (gtk.STOCK_OPEN, "Hi!", "Hello!"), (gtk.STOCK_FILE, "ABC", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"), (gtk.STOCK_FIND, "abc", "abcdefghijklmnopqrstuvwxyz"), (gtk.STOCK_HARDDISK, "browser", "0123456789"), (gtk.STOCK_HOME, "today", "9876543210"), gtk.Notebook, ] for i in [1]: # range(random.randrange(1, 10, 1)): example = random.choice(examples) if example is gtk.Notebook: child = gtk.Notebook() child.append_page(gtk.Button("Click me"), gtk.Label("New %s" % self.file_counter)) stock_id = "" tooltip_text = "notebook" else: stock_id, tooltip_text, text = example child = self._create_content(text) child.set_name(stock_id) # Create a DockItem and add our TextView di = DockItem(title="New %s" % self.file_counter, title_tooltip_text=tooltip_text, stock_id=stock_id) def on_close(item): print "close:", item di.connect("close", on_close) di.add(child) di.show_all() # Add out DockItem to the DockGroup dockgroup.add(di) # Increment file counter self.file_counter += 1
class MainWindow(object): """ The main window for the application. It contains a Namespace-based tree view and a menu and a statusbar. """ interface.implements(IService, IActionProvider) component_registry = inject('component_registry') properties = inject('properties') element_factory = inject('element_factory') action_manager = inject('action_manager') file_manager = inject('file_manager') ui_manager = inject('ui_manager') title = 'Gaphor' size = property(lambda s: s.properties.get('ui.window-size', (760, 580))) menubar_path = '/mainwindow' toolbar_path = '/mainwindow-toolbar' resizable = True menu_xml = """ <ui> <menubar name="mainwindow"> <menu action="file"> <placeholder name="primary" /> <separator /> <menu action="file-export" /> <menu action="file-import" /> <separator /> <placeholder name="secondary" /> <placeholder name="ternary" /> <separator /> <menuitem action="file-quit" /> </menu> <menu action="edit"> <placeholder name="primary" /> <placeholder name="secondary" /> <placeholder name="ternary" /> </menu> <menu action="diagram"> <menuitem action="diagram-drawing-style" /> <separator /> <placeholder name="primary" /> <placeholder name="secondary" /> <placeholder name="ternary" /> </menu> <menu action="tools"> <placeholder name="primary" /> <placeholder name="secondary" /> <placeholder name="ternary" /> </menu> <menu action="window"> <placeholder name="primary" /> <placeholder name="secondary" /> <placeholder name="ternary" /> </menu> <menu action="help"> <placeholder name="primary" /> <placeholder name="secondary" /> <placeholder name="ternary" /> </menu> </menubar> <toolbar name='mainwindow-toolbar'> <placeholder name="left" /> <separator expand="true" /> <placeholder name="right" /> </toolbar> </ui> """ def __init__(self): self.window = None self.model_changed = False # Map tab contents to DiagramTab #self.notebook_map = {} self._current_diagram_tab = None self.layout = None def init(self, app=None): #self.init_pygtk() self.init_stock_icons() self.init_action_group() self.init_ui_components() def init_pygtk(self): """ Make sure we have GTK+ >= 2.0 """ import pygtk pygtk.require('2.0') del pygtk def init_stock_icons(self): # Load stock items import gaphor.ui.stock gaphor.ui.stock.load_stock_icons() def init_ui_components(self): component_registry = self.component_registry for ep in pkg_resources.iter_entry_points('gaphor.uicomponents'): log.debug('found entry point uicomponent.%s' % ep.name) cls = ep.load() if not IUIComponent.implementedBy(cls): raise NameError('Entry point %s doesn''t provide IUIComponent' % ep.name) uicomp = cls() uicomp.ui_name = ep.name component_registry.register_utility(uicomp, IUIComponent, ep.name) if IActionProvider.providedBy(uicomp): self.action_manager.register_action_provider(uicomp) def shutdown(self): if self.window: self.window.destroy() self.window = None save_accel_map() cr = self.component_registry cr.unregister_handler(self._on_file_manager_state_changed) cr.unregister_handler(self._on_undo_manager_state_changed) cr.unregister_handler(self._new_model_content) #self.ui_manager.remove_action_group(self.action_group) def init_action_group(self): self.action_group = build_action_group(self) for name, label in (('file', '_File'), ('file-export', '_Export'), ('file-import', '_Import'), ('edit', '_Edit'), ('diagram', '_Diagram'), ('tools', '_Tools'), ('window', '_Window'), ('help', '_Help')): a = gtk.Action(name, label, None, None) a.set_property('hide-if-empty', False) self.action_group.add_action(a) self._tab_ui_settings = None self.action_group.get_action('diagram-drawing-style').set_active(self.properties('diagram.sloppiness', 0) != 0) self.action_manager.register_action_provider(self) def get_filename(self): """ Return the file name of the currently opened model. """ return self.file_manager.filename def get_current_diagram_tab(self): """ Get the currently opened and viewed DiagramTab, shown on the right side of the main window. See also: get_current_diagram(), get_current_diagram_view(). """ return self._current_diagram_tab def get_current_diagram(self): """ Return the Diagram associated with the viewed DiagramTab. See also: get_current_diagram_tab(), get_current_diagram_view(). """ tab = self._current_diagram_tab return tab and tab.get_diagram() def get_current_diagram_view(self): """ Return the DiagramView associated with the viewed DiagramTab. See also: get_current_diagram_tab(), get_current_diagram(). """ tab = self._current_diagram_tab return tab and tab.get_view() def ask_to_close(self): """ Ask user to close window if the model has changed. The user is asked to either discard the changes, keep the application running or save the model and quit afterwards. """ if self.model_changed: dialog = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_NONE, _('Save changed to your model before closing?')) dialog.format_secondary_text( _('If you close without saving, your changes will be discarded.')) dialog.add_buttons('Close _without saving', gtk.RESPONSE_REJECT, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_YES) dialog.set_default_response(gtk.RESPONSE_YES) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: # On filedialog.cancel, the application should not close. return self.file_manager.action_save() return response == gtk.RESPONSE_REJECT return True def show_diagram(self, diagram): """ Show a Diagram element in a new tab. If a tab is already open, show that one instead. """ # Try to find an existing window/tab and let it get focus: for tab in self.get_tabs(): if tab.get_diagram() is diagram: self.set_current_page(tab) return tab tab = DiagramTab(diagram) dock_item = tab.construct() dock_item.set_name('diagram-tab') dock_item.diagram_tab = tab assert dock_item.get_name() == 'diagram-tab' tab.set_drawing_style(self.properties('diagram.sloppiness', 0)) self.add_tab(dock_item) return tab def open(self): load_accel_map() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title(self.title) self.window.set_size_request(*self.size) self.window.set_resizable(self.resizable) # set default icons of gaphor windows icon_dir = os.path.abspath(pkg_resources.resource_filename('gaphor.ui', 'pixmaps')) icons = (gtk.gdk.pixbuf_new_from_file(os.path.join(icon_dir, f)) for f in ICONS) self.window.set_icon_list(*icons) self.window.add_accel_group(self.ui_manager.get_accel_group()) # Create a full featured window. vbox = gtk.VBox() self.window.add(vbox) vbox.show() menubar = self.ui_manager.get_widget(self.menubar_path) if menubar: vbox.pack_start(menubar, expand=False) toolbar = self.ui_manager.get_widget(self.toolbar_path) if toolbar: vbox.pack_start(toolbar, expand=False) def _factory(name): comp = self.component_registry.get_utility(IUIComponent, name) logger.debug('open component %s' % str(comp)) return comp.open() filename = pkg_resources.resource_filename('gaphor.ui', 'layout.xml') self.layout = DockLayout() with open(filename) as f: deserialize(self.layout, vbox, f.read(), _factory) self.layout.connect('item-closed', self._on_item_closed) self.layout.connect('item-selected', self._on_item_selected) vbox.show() # TODO: add statusbar self.window.show() self.window.connect('delete-event', self._on_window_delete) # We want to store the window size, so it can be reloaded on startup self.window.set_property('allow-shrink', True) self.window.connect('size-allocate', self._on_window_size_allocate) self.window.connect('destroy', self._on_window_destroy) #self.window.connect_after('key-press-event', self._on_key_press_event) cr = self.component_registry cr.register_handler(self._on_file_manager_state_changed) cr.register_handler(self._on_undo_manager_state_changed) cr.register_handler(self._new_model_content) # TODO: register on ElementCreate/Delete event def open_welcome_page(self): """ Create a new tab with a textual welcome page, a sort of 101 for Gaphor. """ pass def set_title(self): """ Sets the window title. """ filename = self.file_manager.filename if self.window: if filename: title = '%s - %s' % (self.title, filename) else: title = self.title if self.model_changed: title += ' *' self.window.set_title(title) # Notebook methods: def add_tab(self, item): """ Create a new tab on the notebook with window as its contents. Returns: The page number of the tab. """ group = list(self.layout.get_widgets('diagrams'))[0] group.insert_item(item) item.show_all() #item.diagram_tab = tab #self.notebook_map[item] = tab def set_current_page(self, tab): """ Force a specific tab (DiagramTab) to the foreground. """ for i in self.layout.get_widgets('diagram-tab'): if i.diagram_tab is tab: g = i.get_parent() g.set_current_item(g.item_num(i)) return #for p, t in self.notebook_map.iteritems(): # if tab is t: # num = self.notebook.page_num(p) # self.notebook.set_current_page(num) # return pass def get_tabs(self): tabs = [i.diagram_tab for i in self.layout.get_widgets('diagram-tab')] return tabs # Signal callbacks: @component.adapter(ModelFactoryEvent) def _new_model_content(self, event): """ Open the toplevel element and load toplevel diagrams. """ # TODO: Make handlers for ModelFactoryEvent from within the GUI obj for diagram in self.element_factory.select(lambda e: e.isKindOf(uml2.Diagram) and not (e.namespace and e.namespace.namespace)): self.show_diagram(diagram) @component.adapter(FileManagerStateChanged) def _on_file_manager_state_changed(self, event): # We're only interested in file operations if event.service is self.file_manager: self.model_changed = False self.set_title() @component.adapter(UndoManagerStateChanged) def _on_undo_manager_state_changed(self, event): """ """ undo_manager = event.service if not self.model_changed and undo_manager.can_undo(): self.model_changed = True self.set_title() def _on_window_destroy(self, window): """ Window is destroyed... Quit the application. """ self.window = None if gobject.main_depth() > 0: gtk.main_quit() cr = self.component_registry cr.unregister_handler(self._on_undo_manager_state_changed) cr.unregister_handler(self._on_file_manager_state_changed) cr.unregister_handler(self._new_model_content) def _on_window_delete(self, window = None, event = None): return not self.ask_to_close() def _clear_ui_settings(self): try: ui_manager = self.ui_manager except component.ComponentLookupError as e: log.warning('No UI manager service found') else: if self._tab_ui_settings: action_group, ui_id = self._tab_ui_settings self.ui_manager.remove_action_group(action_group) self.ui_manager.remove_ui(ui_id) self._tab_ui_settings = None def _on_item_closed(self, layout, group, item): self._clear_ui_settings() try: ui_component = item.ui_component except AttributeError: log.warning('No ui component defined on item') else: ui_component.close() item.destroy() def _on_item_selected(self, layout, group, item): """ Another page (tab) is put on the front of the diagram notebook. A dummy action is executed. """ # TODO: Here the magic happens! # TODO: Need to see what the active view is, or make toolbox actions global self._clear_ui_settings() # Is it a diagram view? try: tab = item.diagram_tab except AttributeError: # Not a diagram tab return self._current_diagram_tab = tab #content = self.notebook.get_nth_page(page_num) #tab = self.notebook_map.get(content) #assert isinstance(tab, DiagramTab), str(tab) self.ui_manager.insert_action_group(tab.action_group, -1) ui_id = self.ui_manager.add_ui_from_string(tab.menu_xml) self._tab_ui_settings = tab.action_group, ui_id log.debug('Menus updated with %s, %d' % self._tab_ui_settings) # Make sure everyone knows the selection has changed. self.component_registry.handle(DiagramTabChange(item), DiagramSelectionChange(tab.view, tab.view.focused_item, tab.view.selected_items)) def _on_window_size_allocate(self, window, allocation): """ Store the window size in a property. """ self.properties.set('ui.window-size', (allocation.width, allocation.height)) # Actions: @action(name='file-quit', stock_id='gtk-quit') def quit(self): # TODO: check for changes (e.g. undo manager), fault-save self.ask_to_close() and gtk.main_quit() self.shutdown() @toggle_action(name='diagram-drawing-style', label='Hand drawn style', active=False) def hand_drawn_style(self, active): """ Toggle between straight diagrams and "hand drawn" diagram style. """ if active: sloppiness = 0.5 else: sloppiness = 0.0 for tab in self.get_tabs(): tab.set_drawing_style(sloppiness) self.properties.set('diagram.sloppiness', sloppiness) def create_item(self, ui_component): #, widget, title, placement=None): """ Create an item for a ui component. This method can be called from UIComponents. """ item = DockItem(ui_component.title) item.add(ui_component.open()) group = DockGroup() group.insert_item(item) placement = ui_component.placement if placement: if placement == 'floating': add_new_group_floating(group, self.layout, ui_component.size) else: location = self.layout.get_widgets(placement[1])[0] { 'left': add_new_group_left, 'right': add_new_group_right, 'above': add_new_group_above, 'below': add_new_group_below }[placement[0]](location, group) else: add_new_group_floating(group) item.show() item.ui_component = ui_component group.show()