Migrated main window to gtk.UIManager menus. All functionality has still to be ported though.

git-svn-id: file:///Users/arjan/backup/gaphor/gaphor/trunk@1262 a8418922-720d-0410-834f-a69b97ada669
This commit is contained in:
Arjan Molenaar 2007-05-10 04:10:43 +00:00
parent 79e573b23b
commit 280300b4cd
9 changed files with 208 additions and 187 deletions

7
TODO
View File

@ -6,6 +6,13 @@ For 0.10.0 and later:
- bug in zope.component package: in zope.component.globalregistry.py zope.testing is included. This package is only a dependency for the [test] setting. zope.testing is not mandatory.
Use a more recent version
- Placement actions should be instantiated on a per-view basis. the view's
action_group should be added/removed when visible/hidden.
- Toolbox should proxy gtk.Action in stead of using the Menu Factory.
- fix settings for new actions (PlacementEvent?) by using subscribtions or handlers.
- filename should not be part of the main window
Is it a transient property?

View File

@ -1,4 +1,3 @@
# vim: sw=4:et
"""
Main window actions.
"""
@ -104,7 +103,7 @@ class NewAction(Action):
#stereotypes = element_factory.create(UML.Profile)
#stereotypes.name = 'Stereotypes'
self._window.set_filename(None)
self._window.set_message(_('Created a new model'))
#self._window.set_message(_('Created a new model'))
element_factory.notify_model()
self._window.select_element(diagram)
@ -154,7 +153,7 @@ class RevertAction(Action):
log.error('Error while loading model from file %s: %s' % (filename, worker.error))
error_handler(message='Error while loading model from file %s' % filename, exc_info=worker.exc_info)
self._window.set_message('Model loaded successfully')
#self._window.set_message('Model loaded successfully')
model = self._window.get_model()
view = self._window.get_tree_view()
@ -346,7 +345,7 @@ class OpenEditorWindowAction(Action):
ew = EditorWindow()
ew.construct()
self._window.add_transient_window(ew)
self._window.set_message('Editor launched')
#self._window.set_message('Editor launched')
register_action(OpenEditorWindowAction)
@ -365,7 +364,7 @@ class OpenConsoleWindowAction(Action):
ew = ConsoleWindow()
ew.construct()
self._window.add_transient_window(ew)
self._window.set_message('Console launched')
#self._window.set_message('Console launched')
register_action(OpenConsoleWindowAction)
@ -722,3 +721,4 @@ class RecentFilesSlot(DynamicMenu):
register_slot('RecentFiles', RecentFilesSlot)
# vim: sw=4:et

View File

@ -88,7 +88,9 @@ class PlacementAction(RadioAction):
tool_changed(self, self._window)
def item_factory(self):
"""Create a new instance of the item and return it."""
"""
Create a new instance of the item and return it.
"""
subject = None
if self.subject_type:
subject = self.element_factory.create(self.subject_type)

View File

@ -23,14 +23,14 @@ class _Application(object):
def __init__(self):
self._uninitialized_services = {}
def init(self):
def init(self, services=None):
"""
Initialize the application.
"""
self.load_services()
self.load_services(services)
self.init_all_services()
def load_services(self):
def load_services(self, services=None):
"""
Load services from resources.
@ -43,8 +43,9 @@ class _Application(object):
cls = ep.load()
if not IService.implementedBy(cls):
raise 'MisConfigurationException', 'Entry point %s doesn''t provide IService' % ep.name
srv = cls()
self._uninitialized_services[ep.name] = srv
if services is None or ep.name in services:
srv = cls()
self._uninitialized_services[ep.name] = srv
def init_all_services(self):
while self._uninitialized_services:
@ -91,6 +92,7 @@ class _Application(object):
Application = _Application()
def restart():
global Application
Application.shutdown()
Application = _Application()

View File

@ -3,7 +3,7 @@
import pkg_resources
from zope import interface
from gaphor.interfaces import IService
from gaphor.interfaces import IService, IActionProvider
class GUIManager(object):
@ -52,24 +52,21 @@ class GUIManager(object):
uicomp = cls()
uicomp.ui_manager = self.ui_manager
self._ui_components[ep.name] = uicomp
if IActionProvider.providedBy(uicomp):
uicomp.__ui_merge_id = self.ui_manager.add_ui_from_string(uicomp.menu_xml)
self.ui_manager.insert_action_group(uicomp.action_group, -1)
def init_main_window(self):
from gaphor.ui.accelmap import load_accel_map
from gaphor.ui.mainwindow import MainWindow
import gtk
load_accel_map()
self._main_window = MainWindow()
# Backwards compat
# TODO: remove
#from gaphor import resource
#resource.set(MainWindow, self._main_window)
#resource.set('MainWindow', self._main_window)
self._main_window = self._ui_components['mainwindow']
self._main_window.construct()
# When the state changes to CLOSED, quit the application
self._main_window.connect(lambda win: win.get_state() == MainWindow.STATE_CLOSED and gtk.main_quit())
#self._main_window.connect(lambda win: win.get_state() == MainWindow.STATE_CLOSED and gtk.main_quit())
def init_actions(self):
from gaphor.actions import diagramactions, editoractions, mainactions
from gaphor.actions import itemactions, placementactions

View File

@ -3,26 +3,28 @@
import sys
import gtk
from zope import interface
import gaphor
from gaphor.interfaces import IActionProvider
from gaphor.ui.interfaces import IUIComponent
from gaphor.action import action, build_action_group
from gaphor.misc.console import GTKInterpreterConsole
from zope import interface
from toplevelwindow import ToplevelWindow
class ConsoleWindow(object):
class ConsoleWindow(ToplevelWindow):
interface.implements(IActionProvider, IUIComponent)
interface.implements(IActionProvider)
menu_xml = """
<ui>
<menubar action="mainwindow">
<menu name="_Window" action="Menu">
<menubar name="mainwindow">
<menu name="WindowMenu">
<menuitem name='_Console' action="ConsoleWindow:open" />
</menu>
</menubar>
<menubar action="consolewindow">
<menu name="_File" action="Menu">
<menubar name="consolewindow">
<menu name="_File" action="FileMenu">
<menuitem name='_Close' action="ConsoleWindow:close" />
</menu>
</menubar>
@ -44,36 +46,6 @@ class ConsoleWindow(object):
console.show()
return console
def construct(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_title(self.title)
self.window.set_size_request(*self.size)
self.window.set_resizable(True)
vbox = gtk.VBox()
self.window.add(vbox)
vbox.show()
self.ui_manager.insert_action_group(self.action_group, 0)
self.ui_manager.add_ui_from_string(self.menu_xml)
self.window.add_accel_group(self.ui_manager.get_accel_group())
menubar = self.ui_manager.get_widget(self.menubar_path)
vbox.pack_start(menubar, expand=False)
if self.toolbar_path:
toolbar = self.ui_manager.get_widget(self.toolbar_path)
vbox.pack_start(toolbar, expand=False)
vbox.pack_end(self.ui_component(), expand=True)
# TODO: add statusbar
self.window.show_all()
@action(name='Menu', label='_File')
def dummy(self): pass
@action(name='ConsoleWindow:open', label='Console')
def open(self):
if not self.window:

View File

@ -1,28 +1,32 @@
# vim:sw=4:et
"""
The main application window.
"""
import gtk
from zope import interface
from gaphor.interfaces import IService, IActionProvider
from zope import interface, component
from gaphor.interfaces import IActionProvider
from interfaces import IUIComponent
from gaphor import UML
from gaphor.core import inject
from gaphor.action import action, build_action_group
from gaphor.i18n import _
from gaphor.ui import namespace
from gaphor.ui.abstractwindow import AbstractWindow
#from gaphor.ui.abstractwindow import AbstractWindow
from gaphor.ui.diagramtab import DiagramTab
from gaphor.ui.toolbox import Toolbox
from gaphor.ui.menufactory import toolbox_to_menu
from toplevelwindow import ToplevelWindow
from gaphor.ui.objectinspector import ObjectInspector
from interfaces import IUIComponent, IDiagramElementReceivedFocus
from gaphor.interfaces import IServiceEvent
from zope import component
class MainWindow(AbstractWindow):
class MainWindow(ToplevelWindow):
"""
The main window for the application.
It contains a Namespace-based tree view and a menu and a statusbar.
@ -31,7 +35,9 @@ class MainWindow(AbstractWindow):
properties = inject('properties')
element_factory = inject('element_factory')
action_manager = inject('action_manager')
# <old>
toolbox = (
('', (
'Pointer',
@ -157,47 +163,24 @@ class MainWindow(AbstractWindow):
'separator',
'RefreshNamespaceModel',
'<NamespacePopupSlot>')
# </old>
title = 'Gaphor'
size = property(lambda s: s.properties.get('ui.window-size', (760, 580)))
menubar_path = '/mainwindow'
toolbar_path = '/mainwindow_toolbar'
menu_xml = """
<ui>
<menubar action="mainmenu">
<menubar action="mainwindow">
<menu name="FileMenu" action="FileMenu">
<menuitem name="New" action="FileNew" />
<menuitem name="Open" action="FileOpen" />
<menuitem name="Revert" action="FileRevert" />
<menu name="Recent files" action="FileRecent">
<placeholder action="RecentFiles" />
</menu>
<separator />
<menuitem action="FileSave" />
<menuitem action="FileSaveAs" />
<placeholder action="SaveSlot" />
<separator />
<menu action="Import">
<placeholder action="FileImportSlot" />
</menu>
<menu action="Export">
<placeholder action="FileExportSlot" />
</menu>
<separator />
<menuitem action="FileCloseTab" />
<placeholder action="FileSlot" />
<separator>
<menuitem action="FileQuit" />
</menu>
<menu name="EditMenu" action="EditMenu"/>
<menu name="DiagramMenu" action="DiagramMenu"/>
<menu name="WindowMenu" action="WindowMenu"/>
</menubar>
<toolbar>
<toolitem action="FileOpen" />
<toolitem action="separator" />
<toolitem action="FileSave" />
<toolitem action="FileSaveAs" />
<separator />
<toolitem action="Undo" />
<toolitem action="Redo" />
<separator />
<toolitem action="ViewZoomIn" />
<toolitem action="ViewZoomOut" />
<toolitem action="ViewZoom100" />
<toolbar name='mainwindow_toolbar'>
</toolbar>
<toolbar action="tools">
</toolbar>
@ -213,27 +196,71 @@ class MainWindow(AbstractWindow):
</popup>
</ui>
"""
# <menuitem name="New" action="FileNew" />
# <menuitem name="Open" action="FileOpen" />
# <menuitem name="Revert" action="FileRevert" />
# <menu name="Recent files" action="FileRecent">
# <placeholder action="RecentFiles" />
# </menu>
# <separator />
# <menuitem action="FileSave" />
# <menuitem action="FileSaveAs" />
# <placeholder action="SaveSlot" />
# <separator />
# <menu action="Import">
# <placeholder action="FileImportSlot" />
# </menu>
# <menu action="Export">
# <placeholder action="FileExportSlot" />
# </menu>
# <separator />
# <menuitem action="FileCloseTab" />
# <placeholder action="FileSlot" />
# <separator />
# <toolitem action="FileOpen" />
# <toolitem action="separator" />
# <toolitem action="FileSave" />
# <toolitem action="FileSaveAs" />
# <separator />
# <toolitem action="Undo" />
# <toolitem action="Redo" />
# <separator />
# <toolitem action="ViewZoomIn" />
# <toolitem action="ViewZoomOut" />
# <toolitem action="ViewZoom100" />
def __init__(self):
AbstractWindow.__init__(self)
ToplevelWindow.__init__(self)
self._filename = None
#self._transient_windows = []
self.notebook_map = {}
# Tree view:
self._model = None
self._view = None
self.action_group = build_action_group(self)
for name, label in (('FileMenu', '_File'),
('EditMenu', '_Edit'),
('DiagramMenu', '_Diagram'),
('WindowMenu', '_Window')):
self.action_group.add_action(gtk.Action(name, label, None, None))
def get_model(self):
"""
Return the gtk.TreeModel associated with the main window
(shown on the left side in a TreeView).
"""
self._check_state(AbstractWindow.STATE_ACTIVE)
return self.model
return self._model
tree_model = property(lambda s: s._model)
def get_tree_view(self):
"""
Get the gtk.TreeView widget that visualized the TreeModel.
See also get_model().
"""
return self.view
return self._view
tree_view = property(lambda s: s._view)
def set_filename(self, filename):
"""
@ -247,7 +274,8 @@ class MainWindow(AbstractWindow):
if filename not in recent_files:
recent_files = [filename] + recent_files[:8]
self.properties.set('recent-files', recent_files)
self.action_manager.get_slot('RecentFiles').rebuild()
# TODO: notify recent files manager
#self.action_manager.get_slot('RecentFiles').rebuild()
def get_filename(self):
"""
@ -255,11 +283,6 @@ class MainWindow(AbstractWindow):
"""
return self._filename
# def get_transient_windows(self):
# """Get the windows that act as child windows of the main window.
# """
# return self._transient_windows
def get_current_diagram_tab(self):
"""
Get the currently opened and viewed DiagramTab, shown on the right
@ -277,7 +300,8 @@ class MainWindow(AbstractWindow):
return tab and tab.get_diagram()
def get_current_diagram_view(self):
"""Return the DiagramView associated with the viewed DiagramTab.
"""
Return the DiagramView associated with the viewed DiagramTab.
See also: get_current_diagram_tab(), get_current_diagram().
"""
tab = self.get_current_diagram_tab()
@ -287,7 +311,7 @@ class MainWindow(AbstractWindow):
"""
Ask user to close window.
"""
dialog = gtk.MessageDialog(self.get_window(),
dialog = gtk.MessageDialog(self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO,
_("Quit Gaphor?"))
@ -296,7 +320,8 @@ class MainWindow(AbstractWindow):
return answer == gtk.RESPONSE_YES
def show_diagram(self, diagram):
"""Show a Diagram element in a new tab.
"""
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:
@ -310,8 +335,9 @@ class MainWindow(AbstractWindow):
tab.construct()
return tab
def construct(self):
"""Create the widgets that make up the main window.
def ui_component(self):
"""
Create the widgets that make up the main window.
"""
model = namespace.NamespaceModel(self.element_factory)
view = namespace.NamespaceView(model, self.element_factory)
@ -346,70 +372,61 @@ class MainWindow(AbstractWindow):
self.objectInspector)
component.provideHandler(diagramReceivedFocus)
secondPaned = gtk.VPaned()
secondPaned.set_property('position',
second_paned = gtk.VPaned()
second_paned.set_property('position',
int(self.properties.get('ui.object-inspector-position', 600)))
secondPaned.pack1(notebook)
secondPaned.pack2(self.objectInspector)
secondPaned.show_all()
paned.pack2(secondPaned)
second_paned.pack1(notebook)
second_paned.pack2(self.objectInspector)
second_paned.show_all()
paned.pack2(second_paned)
paned.show_all()
secondPaned.connect('notify::position',
second_paned.connect('notify::position',
self.on_object_inspector_notify_position)
self.notebook = notebook
self.model = model
self.view = view
window_size = self.properties.get('ui.window-size', (760, 580))
self._construct_window(name='main',
title='Gaphor',
size=window_size,
contents=paned)
self.window.connect('delete-event', self._on_window_delete)
self._model = model
self._view = view
vbox.set_border_width(3)
toolbox = Toolbox(self.menu_factory, self.toolbox)
toolbox.construct()
#toolbox.connect('toggled', self.on_toolbox_toggled)
vbox.pack_start(toolbox, expand=False)
toolbox.show()
#toolbox = Toolbox(self.menu_factory, self.toolbox)
#toolbox.construct()
#vbox.pack_start(toolbox, expand=False)
#toolbox.show()
self._toolbox = toolbox
#self._toolbox = toolbox
return paned
def construct(self):
super(MainWindow, self).construct()
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)
def add_transient_window(self, window):
"""Add a window as a sub-window of the main application.
"""
# Assign the window the accelerators od the main window too
pass #window.get_window().add_accel_group(self.accel_group)
#self._transient_windows.append(window)
#window.connect(self.on_transient_window_closed)
# TODO: add action_groups and menu_xml from NamespaceView
# def add_transient_window(self, window):
# """Add a window as a sub-window of the main application.
# """
# # Assign the window the accelerators od the main window too
# pass #window.get_window().add_accel_group(self.accel_group)
# #self._transient_windows.append(window)
# #window.connect(self.on_transient_window_closed)
# Notebook methods:
def new_tab(self, window, contents, label):
"""Create a new tab on the notebook with window as its contents.
"""
Create a new tab on the notebook with window as its contents.
Returns: The page number of the tab.
"""
#contents = tab.get_contents()
l = gtk.Label(label)
#img = gtk.Image()
#img.set_from_stock('gtk-close', gtk.ICON_SIZE_MENU)
#b = gtk.Button()
#b.set_border_width(0)
#b.add(img)
#h = gtk.HBox()
#h.set_spacing(4)
#h.pack_start(l, expand=False)
#h.pack_start(b, expand=False)
#h.show_all()
self.notebook.append_page(contents, l)
page_num = self.notebook.page_num(contents)
self.notebook.set_current_page(page_num)
@ -418,7 +435,8 @@ class MainWindow(AbstractWindow):
return page_num
def get_current_tab(self):
"""Return the window (DiagramTab) that is currently visible on the
"""
Return the window (DiagramTab) that is currently visible on the
notebook.
"""
current = self.notebook.get_current_page()
@ -426,7 +444,8 @@ class MainWindow(AbstractWindow):
return self.notebook_map.get(content)
def set_current_page(self, tab):
"""Force a specific tab (DiagramTab) to the foreground.
"""
Force a specific tab (DiagramTab) to the foreground.
"""
for p, t in self.notebook_map.iteritems():
if tab is t:
@ -458,7 +477,8 @@ class MainWindow(AbstractWindow):
return
def select_element(self, element):
"""Select an element from the Namespace view.
"""
Select an element from the Namespace view.
The element is selected. After this an action may be executed,
such as OpenModelElement, which will try to open the element (if it's
a Diagram).
@ -475,39 +495,42 @@ class MainWindow(AbstractWindow):
# Signal callbacks:
def _on_window_destroy(self, window):
"""Window is destroyed... Quit the application.
"""
AbstractWindow._on_window_destroy(self, window)
del self.model
del self.view
Window is destroyed... Quit the application.
"""
self._model = None
self._view = None
self.window = None
gtk.main_quit()
def _on_window_delete(self, window = None, event = None):
return not self.ask_to_close()
def on_view_event(self, view, event):
"""Show a popup menu if button3 was pressed on the TreeView.
"""
self._check_state(AbstractWindow.STATE_ACTIVE)
Show a popup menu if button3 was pressed on the TreeView.
"""
# handle mouse button 3:
if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
self._construct_popup_menu(menu_def=self.ns_popup, event=event)
def on_view_row_activated(self, view, path, column):
"""Double click on an element in the tree view.
"""
self._check_state(AbstractWindow.STATE_ACTIVE)
Double click on an element in the tree view.
"""
self.action_manager.execute('OpenModelElement')
# Set the pointer tool as default tool.
self.action_manager.execute('Pointer')
def on_view_cursor_changed(self, view):
"""Another row is selected, execute a dummy action.
"""
Another row is selected, execute a dummy action.
"""
self.action_manager.execute('SelectRow')
def on_notebook_switch_page(self, notebook, tab, page_num):
"""Another page (tab) is put on the front of the diagram notebook.
"""
Another page (tab) is put on the front of the diagram notebook.
A dummy action is executed.
"""
self.action_manager.execute('TabChange')
@ -515,6 +538,9 @@ class MainWindow(AbstractWindow):
def on_window_size_allocate(self, window, allocation):
self.properties.set('ui.window-size', (allocation.width, allocation.height))
def on_window_destroy(self, window):
self.quit()
def on_object_inspector_notify_position(self, paned, arg):
self.properties.set('ui.object-inspector-position',
paned.get_position())
@ -524,15 +550,19 @@ class MainWindow(AbstractWindow):
# log.debug('%s closed.' % window)
# self._transient_windows.remove(window)
def _on_transient_window_notify_title(self, window):
pass
# Actions:
@action(name='FileQuit', stock_id='gtk-quit')
def quit(self):
gtk.main_quit()
gtk.accel_map_add_filter('gaphor')
@component.adapter(IServiceEvent)
def on_undo(*args):
from gaphor.application import Application
Application.get_service('action_manager').execute('UndoStack')
component.provideHandler(on_undo)
#@component.adapter(IServiceEvent)
#def on_undo(*args):
# from gaphor.application import Application
# Application.get_service('action_manager').execute('UndoStack')
#
#component.provideHandler(on_undo)
# vim:sw=4:et:ai

View File

@ -1,18 +1,23 @@
import gtk
import unittest
from gaphor.ui.mainwindow import MainWindow
from gaphor import resource
class MainWindowTestCase(unittest.TestCase):
def setUp(self):
from gaphor.application import restart, Application
restart()
Application.init(services=['element_factory', 'properties', 'action_manager'])
def test_creation(self):
# MainWindow should be created as resource
main_w = resource(MainWindow)
try:
main_w.construct()
except:
pass
main_w = MainWindow()
ui_manager = gtk.UIManager()
main_w.ui_manager = ui_manager
main_w.construct()
self.assertEqual(main_w.get_current_diagram(), None)

View File

@ -7,6 +7,9 @@ import gtk
from gaphor.core import inject
#from gaphor.ui.wrapbox import WrapBox
class Toolbox(gtk.VBox):
"""
A toolbox is a widget that contains a set of buttons (a Wrapbox widget)
@ -17,6 +20,10 @@ class Toolbox(gtk.VBox):
The toolbox is generated based on a definition with the form:
('name', ('boxAction1', 'boxAction2',...), 'name2', ('BoxActionN',...))
1 Create action pool for placement actions
2 Create gtk.RadioButtons for each item.
3 connect to action
"""
__gsignals__ = {
@ -86,8 +93,6 @@ class Toolbox(gtk.VBox):
button.connect('clicked', self.on_wrapbox_decorator_toggled, content)
#AJM: vbox.pack_start(content, True, True)
vbox.label = label
vbox.content = content
@ -139,4 +144,5 @@ class Toolbox(gtk.VBox):
wrapbox.show()
self.boxes.append(wrapbox)
# vim:sw=4:et:ai