Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomeu Vizoso <tomeu@sugarlabs.org>2009-08-01 13:39:40 (GMT)
committer Tomeu Vizoso <tomeu@sugarlabs.org>2009-08-01 13:39:40 (GMT)
commit0426c0c82775a6be33d6da65ecc01f0ffae56edd (patch)
tree45f494e5d54b5205ff4815e6c18bc91b14f4a6f8
parentf0b78ef406bffb5aa4a22102f7065d477ad1046c (diff)
parent5c9020b18893d260f2007b469139a4284939ea18 (diff)
Merge branch 'master' of git://git.sugarlabs.org/sugar-toolkit/toolbars
Conflicts: src/sugar/graphics/window.py
-rw-r--r--examples/radiopalette.py74
l---------examples/sugar1
-rw-r--r--examples/toolbar.py50
-rw-r--r--m4/.gitignore2
-rw-r--r--src/sugar/__init__.py14
-rw-r--r--src/sugar/activity/Makefile.am19
-rw-r--r--src/sugar/activity/activity.py227
-rw-r--r--src/sugar/activity/widgets.py297
-rw-r--r--src/sugar/graphics/Makefile.am52
-rw-r--r--src/sugar/graphics/palette.py604
-rw-r--r--src/sugar/graphics/radiopalette.py98
-rw-r--r--src/sugar/graphics/style.py8
-rw-r--r--src/sugar/graphics/toolbarbox.py284
-rw-r--r--src/sugar/graphics/toolbutton.py6
-rw-r--r--src/sugar/graphics/window.py50
15 files changed, 1236 insertions, 550 deletions
diff --git a/examples/radiopalette.py b/examples/radiopalette.py
new file mode 100644
index 0000000..85b43ce
--- /dev/null
+++ b/examples/radiopalette.py
@@ -0,0 +1,74 @@
+import gtk
+
+from sugar.graphics.radiopalette import RadioPalette, RadioMenuButton, \
+ RadioToolsButton
+from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics import style
+
+window = gtk.Window()
+
+box = gtk.VBox()
+window.add(box)
+
+toolbar = gtk.Toolbar()
+box.pack_start(toolbar, False)
+
+text_view = gtk.TextView()
+box.pack_start(text_view)
+
+def echo(button, label):
+ if not button.props.active:
+ return
+ text_view.props.buffer.props.text += "\n" + label
+
+# RadioMenuButton
+
+palette = RadioPalette()
+
+group = RadioToolButton(
+ icon_name='document-open')
+group.connect('clicked', lambda button: echo(button, 'document-open'))
+palette.append(group, 'menu.document-open')
+
+button = RadioToolButton(
+ icon_name='document-save',
+ group=group)
+button.connect('clicked', lambda button: echo(button, 'document-save'))
+palette.append(button, 'menu.document-save')
+
+button = RadioToolButton(
+ icon_name='document-send',
+ group=group)
+button.connect('clicked', lambda button: echo(button, 'document-send'))
+palette.append(button, 'menu.document-send')
+
+button = RadioMenuButton(palette=palette)
+toolbar.insert(button, -1)
+
+# RadioToolsButton
+
+palette = RadioPalette()
+
+group = RadioToolButton(
+ icon_name='document-open')
+group.connect('clicked', lambda button: echo(button, 'document-open'))
+palette.append(group, 'menu.document-open')
+
+button = RadioToolButton(
+ icon_name='document-save',
+ group=group)
+button.connect('clicked', lambda button: echo(button, 'document-save'))
+palette.append(button, 'menu.document-save')
+
+button = RadioToolButton(
+ icon_name='document-send',
+ group=group)
+button.connect('clicked', lambda button: echo(button, 'document-send'))
+palette.append(button, 'menu.document-send')
+
+button = RadioToolsButton(palette=palette)
+toolbar.insert(button, -1)
+
+window.show_all()
+gtk.main()
diff --git a/examples/sugar b/examples/sugar
new file mode 120000
index 0000000..12f3164
--- /dev/null
+++ b/examples/sugar
@@ -0,0 +1 @@
+../src/sugar/ \ No newline at end of file
diff --git a/examples/toolbar.py b/examples/toolbar.py
new file mode 100644
index 0000000..2faea1f
--- /dev/null
+++ b/examples/toolbar.py
@@ -0,0 +1,50 @@
+import gtk
+
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.toolbarbox import ToolbarBox, ToolbarButton
+from sugar.graphics import style
+
+window = gtk.Window()
+
+box = gtk.VBox()
+window.add(box)
+
+toolbar = ToolbarBox()
+box.pack_start(toolbar, False)
+
+tollbarbutton_1 = ToolbarButton(
+ page=gtk.Button('sub-widget #1'),
+ icon_name='computer-xo')
+toolbar.toolbar.insert(tollbarbutton_1, -1)
+
+tollbarbutton_2 = ToolbarButton(
+ page=gtk.Button('sub-widget #2'),
+ icon_name='button_cancel',
+ tooltip='with custom palette instead of sub-widget')
+toolbar.toolbar.insert(tollbarbutton_2, -1)
+
+toolbar.toolbar.insert(gtk.SeparatorToolItem(), -1)
+
+def del_cb(widget):
+ toolbar.toolbar.remove(tollbarbutton_3)
+del_b = gtk.Button('delete sub-widget #3')
+del_b.connect('clicked', del_cb)
+tollbarbutton_3 = ToolbarButton(
+ page=del_b,
+ icon_name='activity-journal')
+toolbar.toolbar.insert(tollbarbutton_3, -1)
+
+subbar = gtk.Toolbar()
+subbutton = ToolButton(
+ icon_name='document-send',
+ tooltip='document-send')
+subbar.insert(subbutton, -1)
+subbar.show_all()
+
+tollbarbutton_4 = ToolbarButton(
+ page=subbar,
+ icon_name='document-save')
+toolbar.toolbar.insert(tollbarbutton_4, -1)
+
+window.show_all()
+gtk.main()
diff --git a/m4/.gitignore b/m4/.gitignore
index 9f841b0..e08c7c8 100644
--- a/m4/.gitignore
+++ b/m4/.gitignore
@@ -1 +1,3 @@
intltool.m4
+libtool.m4
+lt*.m4
diff --git a/src/sugar/__init__.py b/src/sugar/__init__.py
new file mode 100644
index 0000000..44acb4d
--- /dev/null
+++ b/src/sugar/__init__.py
@@ -0,0 +1,14 @@
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
diff --git a/src/sugar/activity/Makefile.am b/src/sugar/activity/Makefile.am
index 91f6ea8..e2e6fdc 100644
--- a/src/sugar/activity/Makefile.am
+++ b/src/sugar/activity/Makefile.am
@@ -1,10 +1,11 @@
sugardir = $(pythondir)/sugar/activity
-sugar_PYTHON = \
- __init__.py \
- activity.py \
- activityfactory.py \
- activityhandle.py \
- activityservice.py \
- bundlebuilder.py \
- main.py \
- namingalert.py \ No newline at end of file
+sugar_PYTHON = \
+ __init__.py \
+ activity.py \
+ activityfactory.py \
+ activityhandle.py \
+ activityservice.py \
+ bundlebuilder.py \
+ main.py \
+ namingalert.py \
+ widgets.py
diff --git a/src/sugar/activity/activity.py b/src/sugar/activity/activity.py
index 9809928..f20d402 100644
--- a/src/sugar/activity/activity.py
+++ b/src/sugar/activity/activity.py
@@ -67,16 +67,15 @@ from sugar.activity.activityservice import ActivityService
from sugar.activity.namingalert import NamingAlert
from sugar.graphics import style
from sugar.graphics.window import Window
-from sugar.graphics.toolbox import Toolbox
-from sugar.graphics.toolbutton import ToolButton
-from sugar.graphics.toolcombobox import ToolComboBox
from sugar.graphics.alert import Alert
from sugar.graphics.icon import Icon
-from sugar.graphics.xocolor import XoColor
from sugar.datastore import datastore
from sugar.session import XSMPClient
from sugar import wm
+# support deprecated imports
+from sugar.activity.widgets import ActivityToolbar, EditToolbar, ActivityToolbox
+
_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
SCOPE_PRIVATE = "private"
@@ -87,226 +86,6 @@ J_DBUS_SERVICE = 'org.laptop.Journal'
J_DBUS_PATH = '/org/laptop/Journal'
J_DBUS_INTERFACE = 'org.laptop.Journal'
-class ActivityToolbar(gtk.Toolbar):
- """The Activity toolbar with the Journal entry title, sharing,
- Keep and Stop buttons
-
- All activities should have this toolbar. It is easiest to add it to your
- Activity by using the ActivityToolbox.
- """
- def __init__(self, activity):
- gtk.Toolbar.__init__(self)
-
- self._activity = activity
- self._updating_share = False
-
- activity.connect('shared', self.__activity_shared_cb)
- activity.connect('joined', self.__activity_shared_cb)
- activity.connect('notify::max_participants',
- self.__max_participants_changed_cb)
-
- if activity.metadata:
- self.title = gtk.Entry()
- self.title.set_size_request(int(gtk.gdk.screen_width() / 3), -1)
- self.title.set_text(activity.metadata['title'])
- self.title.connect('changed', self.__title_changed_cb)
- self._add_widget(self.title)
-
- activity.metadata.connect('updated', self.__jobject_updated_cb)
-
- separator = gtk.SeparatorToolItem()
- separator.props.draw = False
- separator.set_expand(True)
- self.insert(separator, -1)
- separator.show()
-
- self.share = ToolComboBox(label_text=_('Share with:'))
- self.share.combo.connect('changed', self.__share_changed_cb)
- self.share.combo.append_item(SCOPE_PRIVATE, _('Private'), 'zoom-home')
- self.share.combo.append_item(SCOPE_NEIGHBORHOOD, _('My Neighborhood'),
- 'zoom-neighborhood')
- self.insert(self.share, -1)
- self.share.show()
-
- self._update_share()
-
- self.keep = ToolButton(tooltip=_('Keep'))
- client = gconf.client_get_default()
- color = XoColor(client.get_string('/desktop/sugar/user/color'))
- keep_icon = Icon(icon_name='document-save', xo_color=color)
- self.keep.set_icon_widget(keep_icon)
- keep_icon.show()
- self.keep.props.accelerator = '<Ctrl>S'
- self.keep.connect('clicked', self.__keep_clicked_cb)
- self.insert(self.keep, -1)
- self.keep.show()
-
- self.stop = ToolButton('activity-stop', tooltip=_('Stop'))
- self.stop.props.accelerator = '<Ctrl>Q'
- self.stop.connect('clicked', self.__stop_clicked_cb)
- self.insert(self.stop, -1)
- self.stop.show()
-
- self._update_title_sid = None
-
- def _update_share(self):
- self._updating_share = True
-
- if self._activity.props.max_participants == 1:
- self.share.hide()
-
- if self._activity.get_shared():
- self.share.set_sensitive(False)
- self.share.combo.set_active(1)
- else:
- self.share.set_sensitive(True)
- self.share.combo.set_active(0)
-
- self._updating_share = False
-
- def __share_changed_cb(self, combo):
- if self._updating_share:
- return
-
- model = self.share.combo.get_model()
- it = self.share.combo.get_active_iter()
- (scope, ) = model.get(it, 0)
- if scope == SCOPE_NEIGHBORHOOD:
- self._activity.share()
-
- def __keep_clicked_cb(self, button):
- self._activity.copy()
-
- def __stop_clicked_cb(self, button):
- self._activity.close()
-
- def __jobject_updated_cb(self, jobject):
- self.title.set_text(jobject['title'])
-
- def __title_changed_cb(self, entry):
- if not self._update_title_sid:
- self._update_title_sid = gobject.timeout_add_seconds(
- 1, self.__update_title_cb)
-
- def __update_title_cb(self):
- title = self.title.get_text()
-
- self._activity.metadata['title'] = title
- self._activity.metadata['title_set_by_user'] = '1'
- self._activity.save()
-
- shared_activity = self._activity.get_shared_activity()
- if shared_activity:
- shared_activity.props.name = title
-
- self._update_title_sid = None
- return False
-
- def _add_widget(self, widget, expand=False):
- tool_item = gtk.ToolItem()
- tool_item.set_expand(expand)
-
- tool_item.add(widget)
- widget.show()
-
- self.insert(tool_item, -1)
- tool_item.show()
-
- def __activity_shared_cb(self, activity):
- self._update_share()
-
- def __max_participants_changed_cb(self, activity, pspec):
- self._update_share()
-
-class EditToolbar(gtk.Toolbar):
- """Provides the standard edit toolbar for Activities.
-
- Members:
- undo -- the undo button
- redo -- the redo button
- copy -- the copy button
- paste -- the paste button
- separator -- A separator between undo/redo and copy/paste
-
- This class only provides the 'edit' buttons in a standard layout,
- your activity will need to either hide buttons which make no sense for your
- Activity, or you need to connect the button events to your own callbacks:
-
- ## Example from Read.activity:
- # Create the edit toolbar:
- self._edit_toolbar = EditToolbar(self._view)
- # Hide undo and redo, they're not needed
- self._edit_toolbar.undo.props.visible = False
- self._edit_toolbar.redo.props.visible = False
- # Hide the separator too:
- self._edit_toolbar.separator.props.visible = False
-
- # As long as nothing is selected, copy needs to be insensitive:
- self._edit_toolbar.copy.set_sensitive(False)
- # When the user clicks the button, call _edit_toolbar_copy_cb()
- self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb)
-
- # Add the edit toolbar:
- toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
- # And make it visible:
- self._edit_toolbar.show()
- """
- def __init__(self):
- gtk.Toolbar.__init__(self)
-
- self.undo = ToolButton('edit-undo')
- self.undo.set_tooltip(_('Undo'))
- self.insert(self.undo, -1)
- self.undo.show()
-
- self.redo = ToolButton('edit-redo')
- self.redo.set_tooltip(_('Redo'))
- self.insert(self.redo, -1)
- self.redo.show()
-
- self.separator = gtk.SeparatorToolItem()
- self.separator.set_draw(True)
- self.insert(self.separator, -1)
- self.separator.show()
-
- self.copy = ToolButton('edit-copy')
- self.copy.set_tooltip(_('Copy'))
- self.insert(self.copy, -1)
- self.copy.show()
-
- self.paste = ToolButton('edit-paste')
- self.paste.set_tooltip(_('Paste'))
- self.insert(self.paste, -1)
- self.paste.show()
-
-class ActivityToolbox(Toolbox):
- """Creates the Toolbox for the Activity
-
- By default, the toolbox contains only the ActivityToolbar. After creating
- the toolbox, you can add your activity specific toolbars, for example the
- EditToolbar.
-
- To add the ActivityToolbox to your Activity in MyActivity.__init__() do:
-
- # Create the Toolbar with the ActivityToolbar:
- toolbox = activity.ActivityToolbox(self)
- ... your code, inserting all other toolbars you need, like EditToolbar
-
- # Add the toolbox to the activity frame:
- self.set_toolbox(toolbox)
- # And make it visible:
- toolbox.show()
- """
- def __init__(self, activity):
- Toolbox.__init__(self)
-
- self._activity_toolbar = ActivityToolbar(activity)
- self.add_toolbar(_('Activity'), self._activity_toolbar)
- self._activity_toolbar.show()
-
- def get_activity_toolbar(self):
- return self._activity_toolbar
-
class _ActivitySession(gobject.GObject):
__gsignals__ = {
'quit-requested': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
diff --git a/src/sugar/activity/widgets.py b/src/sugar/activity/widgets.py
new file mode 100644
index 0000000..e14c1f3
--- /dev/null
+++ b/src/sugar/activity/widgets.py
@@ -0,0 +1,297 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+import gobject
+import gettext
+import gconf
+
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.toolbarbox import ToolbarButton
+from sugar.graphics.radiopalette import RadioPalette, RadioMenuButton
+from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.graphics.toolbox import Toolbox
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics.icon import Icon
+from sugar.bundle.activitybundle import ActivityBundle
+
+_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
+
+class ActivityToolbarButton(ToolbarButton):
+ def __init__(self, activity, **kwargs):
+ toolbar = ActivityToolbar(activity)
+ toolbar.stop.hide()
+
+ ToolbarButton.__init__(self, page=toolbar, **kwargs)
+
+ from sugar.activity.activity import get_bundle_path
+ bundle = ActivityBundle(get_bundle_path())
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ icon = Icon(file=bundle.get_icon(), xo_color=color)
+ icon.show()
+ self.set_icon_widget(icon)
+
+class StopButton(ToolButton):
+ def __init__(self, activity, **kwargs):
+ ToolButton.__init__(self, 'activity-stop', **kwargs)
+ self.props.tooltip = _('Stop')
+ self.props.accelerator = '<Ctrl>Q'
+ self.connect('clicked', self.__stop_button_clicked_cb, activity)
+
+ def __stop_button_clicked_cb(self, button, activity):
+ activity.close()
+
+class UndoButton(ToolButton):
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, 'edit-undo', **kwargs)
+ self.props.tooltip = _('Undo')
+ self.props.accelerator = '<Ctrl>Q'
+
+class RedoButton(ToolButton):
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, 'edit-redo', **kwargs)
+ self.props.tooltip = _('Redo')
+
+class CopyButton(ToolButton):
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, 'edit-copy', **kwargs)
+ self.props.tooltip = _('Copy')
+
+class PasteButton(ToolButton):
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, 'edit-paste', **kwargs)
+ self.props.tooltip = _('Paste')
+
+class ShareButton(RadioMenuButton):
+ def __init__(self, activity, **kwargs):
+ palette = RadioPalette()
+
+ self.private = RadioToolButton(
+ icon_name='zoom-home')
+ palette.append(self.private, _('Private'))
+
+ self.neighborhood = RadioToolButton(
+ icon_name='zoom-neighborhood',
+ group=self.private)
+ self._neighborhood_handle = self.neighborhood.connect(
+ 'clicked', self.__neighborhood_clicked_cb, activity)
+ palette.append(self.neighborhood, _('My Neighborhood'))
+
+ activity.connect('shared', self.__update_share_cb)
+ activity.connect('joined', self.__update_share_cb)
+
+ RadioMenuButton.__init__(self, **kwargs)
+ self.props.palette = palette
+
+ def __neighborhood_clicked_cb(self, button, activity):
+ activity.share()
+
+ def __update_share_cb(self, activity):
+ self.neighborhood.handler_block(self._neighborhood_handle)
+ try:
+ if activity.get_shared():
+ self.private.props.sensitive = False
+ self.neighborhood.props.sensitive = False
+ self.neighborhood.props.active = True
+ else:
+ self.private.props.sensitive = True
+ self.neighborhood.props.sensitive = True
+ self.private.props.active = True
+ finally:
+ self.neighborhood.handler_unblock(self._neighborhood_handle)
+
+class KeepButton(ToolButton):
+ def __init__(self, activity, **kwargs):
+ ToolButton.__init__(self, **kwargs)
+ self.props.tooltip = _('Keep')
+ self.props.accelerator = '<Ctrl>S'
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ keep_icon = Icon(icon_name='document-save', xo_color=color)
+ keep_icon.show()
+
+ self.set_icon_widget(keep_icon)
+ self.connect('clicked', self.__keep_button_clicked_cb, activity)
+
+ def __keep_button_clicked_cb(self, button, activity):
+ activity.copy()
+
+class TitleEntry(gtk.ToolItem):
+ def __init__(self, activity, **kwargs):
+ gtk.ToolItem.__init__(self)
+ self.set_expand(False)
+ self._update_title_sid = None
+
+ self.entry = gtk.Entry(**kwargs)
+ self.entry.set_size_request(int(gtk.gdk.screen_width() / 3), -1)
+ self.entry.set_text(activity.metadata['title'])
+ self.entry.connect('changed', self.__title_changed_cb, activity)
+ self.entry.show()
+ self.add(self.entry)
+
+ activity.metadata.connect('updated', self.__jobject_updated_cb)
+
+ def modify_bg(self, state, color):
+ gtk.ToolItem.modify_bg(self, state, color)
+ if state == gtk.STATE_NORMAL:
+ self.entry.modify_bg(gtk.STATE_INSENSITIVE, color)
+
+ def __jobject_updated_cb(self, jobject):
+ self.entry.set_text(jobject['title'])
+
+ def __title_changed_cb(self, entry, activity):
+ if not self._update_title_sid:
+ self._update_title_sid = gobject.timeout_add_seconds(
+ 1, self.__update_title_cb, activity)
+
+ def __update_title_cb(self, activity):
+ title = self.entry.get_text()
+
+ activity.metadata['title'] = title
+ activity.metadata['title_set_by_user'] = '1'
+ activity.save()
+
+ shared_activity = activity.get_shared_activity()
+ if shared_activity is None:
+ shared_activity.props.name = title
+
+ self._update_title_sid = None
+ return False
+
+class ActivityToolbar(gtk.Toolbar):
+ """The Activity toolbar with the Journal entry title, sharing,
+ Keep and Stop buttons
+
+ All activities should have this toolbar. It is easiest to add it to your
+ Activity by using the ActivityToolbox.
+ """
+ def __init__(self, activity):
+ gtk.Toolbar.__init__(self)
+
+ self._activity = activity
+
+ if activity.metadata:
+ title_button = TitleEntry(activity)
+ title_button.show()
+ self.insert(title_button, -1)
+ self.title = title_button.entry
+
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ self.insert(separator, -1)
+ separator.show()
+
+ self.share = ShareButton(activity)
+ self.share.show()
+ self.insert(self.share, -1)
+
+ self.keep = KeepButton(activity)
+ self.insert(self.keep, -1)
+ self.keep.show()
+
+ self.stop = StopButton(activity)
+ self.insert(self.stop, -1)
+ self.stop.show()
+
+class EditToolbar(gtk.Toolbar):
+ """Provides the standard edit toolbar for Activities.
+
+ Members:
+ undo -- the undo button
+ redo -- the redo button
+ copy -- the copy button
+ paste -- the paste button
+ separator -- A separator between undo/redo and copy/paste
+
+ This class only provides the 'edit' buttons in a standard layout,
+ your activity will need to either hide buttons which make no sense for your
+ Activity, or you need to connect the button events to your own callbacks:
+
+ ## Example from Read.activity:
+ # Create the edit toolbar:
+ self._edit_toolbar = EditToolbar(self._view)
+ # Hide undo and redo, they're not needed
+ self._edit_toolbar.undo.props.visible = False
+ self._edit_toolbar.redo.props.visible = False
+ # Hide the separator too:
+ self._edit_toolbar.separator.props.visible = False
+
+ # As long as nothing is selected, copy needs to be insensitive:
+ self._edit_toolbar.copy.set_sensitive(False)
+ # When the user clicks the button, call _edit_toolbar_copy_cb()
+ self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb)
+
+ # Add the edit toolbar:
+ toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
+ # And make it visible:
+ self._edit_toolbar.show()
+ """
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ self.undo = UndoButton()
+ self.insert(self.undo, -1)
+ self.undo.show()
+
+ self.redo = RedoButton()
+ self.insert(self.redo, -1)
+ self.redo.show()
+
+ self.separator = gtk.SeparatorToolItem()
+ self.separator.set_draw(True)
+ self.insert(self.separator, -1)
+ self.separator.show()
+
+ self.copy = CopyButton()
+ self.insert(self.copy, -1)
+ self.copy.show()
+
+ self.paste = PasteButton()
+ self.insert(self.paste, -1)
+ self.paste.show()
+
+class ActivityToolbox(Toolbox):
+ """Creates the Toolbox for the Activity
+
+ By default, the toolbox contains only the ActivityToolbar. After creating
+ the toolbox, you can add your activity specific toolbars, for example the
+ EditToolbar.
+
+ To add the ActivityToolbox to your Activity in MyActivity.__init__() do:
+
+ # Create the Toolbar with the ActivityToolbar:
+ toolbox = activity.ActivityToolbox(self)
+ ... your code, inserting all other toolbars you need, like EditToolbar
+
+ # Add the toolbox to the activity frame:
+ self.set_toolbox(toolbox)
+ # And make it visible:
+ toolbox.show()
+ """
+ def __init__(self, activity):
+ Toolbox.__init__(self)
+
+ self._activity_toolbar = ActivityToolbar(activity)
+ self.add_toolbar(_('Activity'), self._activity_toolbar)
+ self._activity_toolbar.show()
+
+ def get_activity_toolbar(self):
+ return self._activity_toolbar
diff --git a/src/sugar/graphics/Makefile.am b/src/sugar/graphics/Makefile.am
index c4d5e61..a35fb4a 100644
--- a/src/sugar/graphics/Makefile.am
+++ b/src/sugar/graphics/Makefile.am
@@ -1,27 +1,29 @@
sugardir = $(pythondir)/sugar/graphics
-sugar_PYTHON = \
- __init__.py \
- alert.py \
- animator.py \
- canvastextview.py \
- combobox.py \
- colorbutton.py \
- entry.py \
- icon.py \
- iconentry.py \
- menuitem.py \
- notebook.py \
- objectchooser.py \
- radiotoolbutton.py \
- palette.py \
- palettegroup.py \
- panel.py \
- roundbox.py \
- style.py \
- toggletoolbutton.py \
- toolbox.py \
- toolbutton.py \
- toolcombobox.py \
- tray.py \
- window.py \
+sugar_PYTHON = \
+ alert.py \
+ animator.py \
+ canvastextview.py \
+ colorbutton.py \
+ combobox.py \
+ entry.py \
+ iconentry.py \
+ icon.py \
+ __init__.py \
+ menuitem.py \
+ notebook.py \
+ objectchooser.py \
+ palettegroup.py \
+ palette.py \
+ panel.py \
+ radiopalette.py \
+ radiotoolbutton.py \
+ roundbox.py \
+ style.py \
+ toggletoolbutton.py \
+ toolbarbox.py \
+ toolbox.py \
+ toolbutton.py \
+ toolcombobox.py \
+ tray.py \
+ window.py \
xocolor.py
diff --git a/src/sugar/graphics/palette.py b/src/sugar/graphics/palette.py
index 88145d2..92b04b6 100644
--- a/src/sugar/graphics/palette.py
+++ b/src/sugar/graphics/palette.py
@@ -127,11 +127,11 @@ class MouseSpeedDetector(gobject.GObject):
return True
-class Palette(gtk.Window):
+class PaletteWindow(gtk.Window):
PRIMARY = 0
SECONDARY = 1
- __gtype_name__ = 'SugarPalette'
+ __gtype_name__ = 'SugarPaletteWindow'
__gsignals__ = {
'popup' : (gobject.SIGNAL_RUN_FIRST,
@@ -142,18 +142,291 @@ class Palette(gtk.Window):
gobject.TYPE_NONE, ([]))
}
+ def __init__(self, **kwargs):
+ self._group_id = None
+ self._invoker = None
+ self._invoker_hids = []
+ self._cursor_x = 0
+ self._cursor_y = 0
+ self._alignment = None
+ self._up = False
+ self._old_alloc = None
+ self._palette_state = self.PRIMARY
+
+ self._popup_anim = animator.Animator(.5, 10)
+ self._popup_anim.add(_PopupAnimation(self))
+
+ self._secondary_anim = animator.Animator(2.0, 10)
+ self._secondary_anim.add(_SecondaryAnimation(self))
+
+ self._popdown_anim = animator.Animator(0.6, 10)
+ self._popdown_anim.add(_PopdownAnimation(self))
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ self.set_decorated(False)
+ self.set_resizable(False)
+ # Just assume xthickness and ythickness are the same
+ self.set_border_width(self.get_style().xthickness)
+
+ accel_group = gtk.AccelGroup()
+ self.set_data('sugar-accel-group', accel_group)
+ self.add_accel_group(accel_group)
+
+ self.set_group_id("default")
+
+ self.connect('show', self.__show_cb)
+ self.connect('hide', self.__hide_cb)
+ self.connect('realize', self.__realize_cb)
+ self.connect('destroy', self.__destroy_cb)
+ self.connect('enter-notify-event', self.__enter_notify_event_cb)
+ self.connect('leave-notify-event', self.__leave_notify_event_cb)
+
+ self._mouse_detector = MouseSpeedDetector(self, 200, 5)
+ self._mouse_detector.connect('motion-slow', self._mouse_slow_cb)
+
+ def __destroy_cb(self, palette):
+ self.set_group_id(None)
+
+ def set_invoker(self, invoker):
+ for hid in self._invoker_hids[:]:
+ self._invoker.disconnect(hid)
+ self._invoker_hids.remove(hid)
+
+ self._invoker = invoker
+ if invoker is not None:
+ self._invoker_hids.append(self._invoker.connect(
+ 'mouse-enter', self._invoker_mouse_enter_cb))
+ self._invoker_hids.append(self._invoker.connect(
+ 'mouse-leave', self._invoker_mouse_leave_cb))
+ self._invoker_hids.append(self._invoker.connect(
+ 'right-click', self._invoker_right_click_cb))
+
+ logging.debug(' Invoker set to %r' % self._invoker)
+
+ def get_invoker(self):
+ return self._invoker
+
+ invoker = gobject.property(type=object,
+ getter=get_invoker,
+ setter=set_invoker)
+
+ def __realize_cb(self, widget):
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+
+ def _mouse_slow_cb(self, widget):
+ self._mouse_detector.stop()
+ self._palette_do_popup()
+
+ def _palette_do_popup(self):
+ immediate = False
+
+ if self.is_up():
+ self._popdown_anim.stop()
+ return
+
+ if self._group_id:
+ group = palettegroup.get_group(self._group_id)
+ if group and group.is_up():
+ immediate = True
+ group.popdown()
+
+ self.popup(immediate=immediate)
+
+ def is_up(self):
+ return self._up
+
+ def set_group_id(self, group_id):
+ if self._group_id:
+ group = palettegroup.get_group(self._group_id)
+ group.remove(self)
+ if group_id:
+ self._group_id = group_id
+ group = palettegroup.get_group(group_id)
+ group.add(self)
+
+ def get_group_id(self):
+ return self._group_id
+
+ group_id = gobject.property(type=str,
+ getter=get_group_id,
+ setter=set_group_id)
+
+ def do_size_request(self, requisition):
+ gtk.Window.do_size_request(self, requisition)
+ requisition.width = max(requisition.width, style.GRID_CELL_SIZE * 2)
+
+ def do_size_allocate(self, allocation):
+ gtk.Window.do_size_allocate(self, allocation)
+
+ if self._old_alloc is None or \
+ self._old_alloc.x != allocation.x or \
+ self._old_alloc.y != allocation.y or \
+ self._old_alloc.width != allocation.width or \
+ self._old_alloc.height != allocation.height:
+ self.queue_draw()
+
+ # We need to store old allocation because when size_allocate
+ # is called widget.allocation is already updated.
+ # gtk.Window resizing is different from normal containers:
+ # the X window is resized, widget.allocation is updated from
+ # the configure request handler and finally size_allocate is called.
+ self._old_alloc = allocation
+
+ def do_expose_event(self, event):
+ # We want to draw a border with a beautiful gap
+ if self._invoker is not None and self._invoker.has_rectangle_gap():
+ invoker = self._invoker.get_rect()
+ palette = self.get_rect()
+
+ gap = _calculate_gap(palette, invoker)
+ else:
+ gap = False
+
+ allocation = self.get_allocation()
+ wstyle = self.get_style()
+
+ if gap:
+ wstyle.paint_box_gap(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_IN, event.area, self, "palette",
+ 0, 0, allocation.width, allocation.height,
+ gap[0], gap[1], gap[2])
+ else:
+ wstyle.paint_box(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_IN, event.area, self, "palette",
+ 0, 0, allocation.width, allocation.height)
+
+ # Fall trough to the container expose handler.
+ # (Leaving out the window expose handler which redraws everything)
+ gtk.Bin.do_expose_event(self, event)
+
+ def update_position(self):
+ logging.debug(' update_position 1 %r %r' % (self._invoker, self._alignment))
+ invoker = self._invoker
+ if invoker is None or self._alignment is None:
+ logging.error('Cannot update the palette position.')
+ return
+
+ rect = self.size_request()
+ position = invoker.get_position_for_alignment(self._alignment, rect)
+ if position is None:
+ position = invoker.get_position(rect)
+
+ logging.debug(' update_position %r %r' % (position.x, position.y))
+ self.move(position.x, position.y)
+
+ def get_full_size_request(self):
+ return self.size_request()
+
+ def popup(self, immediate=False):
+ if self._invoker is not None:
+ full_size_request = self.get_full_size_request()
+ self._alignment = self._invoker.get_alignment(full_size_request)
+
+ self.update_position()
+ self.set_transient_for(self._invoker.get_toplevel())
+
+ self._popdown_anim.stop()
+
+ if not immediate:
+ self._popup_anim.start()
+ else:
+ self.show()
+
+ def popdown(self, immediate=False):
+ logging.debug('Palette.popdown immediate %r' % immediate)
+ self._popup_anim.stop()
+
+ self._mouse_detector.stop()
+
+ if not immediate:
+ self._popdown_anim.start()
+ else:
+ self.hide()
+
+ def on_invoker_enter(self):
+ self._mouse_detector.start()
+
+ def on_invoker_leave(self):
+ self._mouse_detector.stop()
+ self.popdown()
+
+ def on_enter(self, event):
+ self._popdown_anim.stop()
+ self._secondary_anim.start()
+
+ def on_leave(self, event):
+ self.popdown()
+
+ def _invoker_mouse_enter_cb(self, invoker):
+ self.on_invoker_enter()
+
+ def _invoker_mouse_leave_cb(self, invoker):
+ self.on_invoker_leave()
+
+ def _invoker_right_click_cb(self, invoker):
+ self.popup(immediate=True)
+
+ def __enter_notify_event_cb(self, widget, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR and \
+ event.mode == gtk.gdk.CROSSING_NORMAL:
+ self.on_enter(event)
+
+ def __leave_notify_event_cb(self, widget, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR and \
+ event.mode == gtk.gdk.CROSSING_NORMAL:
+ self.on_leave(event)
+
+ def __show_cb(self, widget):
+ self._invoker.notify_popup()
+
+ self._up = True
+ self.emit('popup')
+
+ def __hide_cb(self, widget):
+ logging.debug('__hide_cb')
+ self._secondary_anim.stop()
+
+ if self._invoker:
+ self._invoker.notify_popdown()
+
+ self._up = False
+ self.emit('popdown')
+
+ def get_rect(self):
+ win_x, win_y = self.window.get_origin()
+ rectangle = self.get_allocation()
+
+ x = win_x + rectangle.x
+ y = win_y + rectangle.y
+ width = rectangle.width
+ height = rectangle.height
+
+ return gtk.gdk.Rectangle(x, y, width, height)
+
+ def get_palette_state(self):
+ return self._palette_state
+
+ def _set_palette_state(self, state):
+ self._palette_state = state
+
+ def set_palette_state(self, state):
+ self._set_palette_state(state)
+
+ palette_state = property(get_palette_state)
+
+class Palette(PaletteWindow):
+ __gtype_name__ = 'SugarPalette'
+
# DEPRECATED: label is passed with the primary-text property, accel_path
# is set via the invoker property, and menu_after_content is not used
def __init__(self, label=None, accel_path=None, menu_after_content=False,
text_maxlen=60, **kwargs):
- self.palette_state = self.PRIMARY
-
self._primary_text = None
self._secondary_text = None
self._icon = None
self._icon_visible = True
- self._group_id = None
palette_box = gtk.VBox()
@@ -200,49 +473,18 @@ class Palette(gtk.Window):
self._menu_content_separator = gtk.HSeparator()
- self._popup_anim = animator.Animator(.5, 10)
- self._popup_anim.add(_PopupAnimation(self))
-
self._secondary_anim = animator.Animator(2.0, 10)
self._secondary_anim.add(_SecondaryAnimation(self))
- self._popdown_anim = animator.Animator(0.6, 10)
- self._popdown_anim.add(_PopdownAnimation(self))
-
# we init after initializing all of our containers
- gobject.GObject.__init__(self, **kwargs)
-
- self.set_decorated(False)
- self.set_resizable(False)
- # Just assume xthickness and ythickness are the same
- self.set_border_width(self.get_style().xthickness)
-
- accel_group = gtk.AccelGroup()
- self.set_data('sugar-accel-group', accel_group)
- self.add_accel_group(accel_group)
+ PaletteWindow.__init__(self, **kwargs)
primary_box.set_size_request(-1, style.GRID_CELL_SIZE
- 2 * self.get_border_width())
-
- self.connect('show', self.__show_cb)
- self.connect('hide', self.__hide_cb)
- self.connect('realize', self.__realize_cb)
- self.connect('destroy', self.__destroy_cb)
-
- self._alignment = None
- self._old_alloc = None
self._full_request = [0, 0]
- self._cursor_x = 0
- self._cursor_y = 0
- self._invoker = None
- self._group_id = None
- self._up = False
self._menu_box = None
self._content = None
- self._invoker_hids = []
-
- self.set_group_id("default")
# we set these for backward compatibility
if label is not None:
@@ -263,21 +505,63 @@ class Palette(gtk.Window):
self.menu = _Menu(self)
self.menu.connect('item-inserted', self.__menu_item_inserted_cb)
- self.connect('enter-notify-event', self.__enter_notify_event_cb)
- self.connect('leave-notify-event', self.__leave_notify_event_cb)
+ self.connect('realize', self.__realize_cb)
+ self.connect('show', self.__show_cb)
+ self.connect('hide', self.__hide_cb)
+ self.connect('notify::invoker', self.__notify_invoker_cb)
+ self.connect('destroy', self.__destroy_cb)
- self._mouse_detector = MouseSpeedDetector(self, 200, 5)
- self._mouse_detector.connect('motion-slow', self._mouse_slow_cb)
+ def _invoker_right_click_cb(self, invoker):
+ self.popup(immediate=True, state=self.SECONDARY)
+
+ def do_style_set(self, previous_style):
+ # Prevent a warning from pygtk
+ if previous_style is not None:
+ gtk.Window.do_style_set(self, previous_style)
+ self.set_border_width(self.get_style().xthickness)
def __menu_item_inserted_cb(self, menu):
self._update_separators()
def __destroy_cb(self, palette):
- self.set_group_id(None)
-
# Break the reference cycle. It looks like the gc is not able to free
# it, possibly because gtk.Menu memory handling is very special.
self.menu = None
+
+ def __show_cb(self, widget):
+ self.menu.set_active(True)
+
+ def __hide_cb(self, widget):
+ logging.debug('__hide_cb')
+ self.menu.set_active(False)
+
+ def __notify_invoker_cb(self, palette, pspec):
+ invoker = self.props.invoker
+ if invoker is not None and hasattr(invoker.props, 'widget'):
+ logging.debug(('Setup widget', invoker.props.widget))
+ self._update_accel_widget()
+ self._invoker.connect('notify::widget',
+ self.__invoker_widget_changed_cb)
+
+ def __invoker_widget_changed_cb(self, invoker, spec):
+ self._update_accel_widget()
+
+ def get_full_size_request(self):
+ return self._full_request
+
+ def popup(self, immediate=False, state=None):
+ logging.debug('Palette.popup immediate %r' % immediate)
+
+ if self._invoker is not None:
+ self._update_full_request()
+
+ PaletteWindow.popup(self, immediate)
+
+ if state is None:
+ state = self.PRIMARY
+ self.set_palette_state(state)
+
+ self._secondary_anim.start()
def _add_menu(self):
self._menu_box = gtk.VBox()
@@ -290,52 +574,6 @@ class Palette(gtk.Window):
self._content.set_border_width(style.DEFAULT_SPACING)
self._secondary_box.pack_start(self._content)
- def do_style_set(self, previous_style):
- # Prevent a warning from pygtk
- if previous_style is not None:
- gtk.Window.do_style_set(self, previous_style)
- self.set_border_width(self.get_style().xthickness)
-
- def is_up(self):
- return self._up
-
- def get_rect(self):
- win_x, win_y = self.window.get_origin()
- rectangle = self.get_allocation()
-
- x = win_x + rectangle.x
- y = win_y + rectangle.y
- width = rectangle.width
- height = rectangle.height
-
- return gtk.gdk.Rectangle(x, y, width, height)
-
- def set_invoker(self, invoker):
- for hid in self._invoker_hids[:]:
- self._invoker.disconnect(hid)
- self._invoker_hids.remove(hid)
-
- self._invoker = invoker
- if invoker is not None:
- self._invoker_hids.append(self._invoker.connect(
- 'mouse-enter', self._invoker_mouse_enter_cb))
- self._invoker_hids.append(self._invoker.connect(
- 'mouse-leave', self._invoker_mouse_leave_cb))
- self._invoker_hids.append(self._invoker.connect(
- 'right-click', self._invoker_right_click_cb))
- if hasattr(invoker.props, 'widget'):
- self._update_accel_widget()
- logging.debug(('Setup widget', invoker.props.widget))
- self._invoker_hids.append(self._invoker.connect(
- 'notify::widget', self._invoker_widget_changed_cb))
-
- def get_invoker(self):
- return self._invoker
-
- invoker = gobject.property(type=object,
- getter=get_invoker,
- setter=set_invoker)
-
def _update_accel_widget(self):
assert self.props.invoker is not None
self._label.props.accel_widget = self.props.invoker.props.widget
@@ -438,24 +676,8 @@ class Palette(gtk.Window):
self._update_accept_focus()
self._update_separators()
- def set_group_id(self, group_id):
- if self._group_id:
- group = palettegroup.get_group(self._group_id)
- group.remove(self)
- if group_id:
- self._group_id = group_id
- group = palettegroup.get_group(group_id)
- group.add(self)
-
- def get_group_id(self):
- return self._group_id
-
- group_id = gobject.property(type=str,
- getter=get_group_id,
- setter=set_group_id)
-
def do_size_request(self, requisition):
- gtk.Window.do_size_request(self, requisition)
+ PaletteWindow.do_size_request(self, requisition)
# gtk.AccelLabel request doesn't include the accelerator.
label_width = self._label_alignment.size_request()[0] + \
@@ -463,54 +685,9 @@ class Palette(gtk.Window):
2 * self.get_border_width()
requisition.width = max(requisition.width,
- style.GRID_CELL_SIZE * 2,
label_width,
self._full_request[0])
- def do_size_allocate(self, allocation):
- gtk.Window.do_size_allocate(self, allocation)
-
- if self._old_alloc is None or \
- self._old_alloc.x != allocation.x or \
- self._old_alloc.y != allocation.y or \
- self._old_alloc.width != allocation.width or \
- self._old_alloc.height != allocation.height:
- self.queue_draw()
-
- # We need to store old allocation because when size_allocate
- # is called widget.allocation is already updated.
- # gtk.Window resizing is different from normal containers:
- # the X window is resized, widget.allocation is updated from
- # the configure request handler and finally size_allocate is called.
- self._old_alloc = allocation
-
- def do_expose_event(self, event):
- # We want to draw a border with a beautiful gap
- if self._invoker is not None and self._invoker.has_rectangle_gap():
- invoker = self._invoker.get_rect()
- palette = self.get_rect()
-
- gap = _calculate_gap(palette, invoker)
- else:
- gap = False
-
- allocation = self.get_allocation()
- wstyle = self.get_style()
-
- if gap:
- wstyle.paint_box_gap(event.window, gtk.STATE_PRELIGHT,
- gtk.SHADOW_IN, event.area, self, "palette",
- 0, 0, allocation.width, allocation.height,
- gap[0], gap[1], gap[2])
- else:
- wstyle.paint_box(event.window, gtk.STATE_PRELIGHT,
- gtk.SHADOW_IN, event.area, self, "palette",
- 0, 0, allocation.width, allocation.height)
-
- # Fall trough to the container expose handler.
- # (Leaving out the window expose handler which redraws everything)
- gtk.Bin.do_expose_event(self, event)
-
def _update_separators(self):
visible = len(self.menu.get_children()) > 0 or \
len(self._content.get_children()) > 0
@@ -526,68 +703,21 @@ class Palette(gtk.Window):
self.window.set_accept_focus(accept_focus)
def __realize_cb(self, widget):
- self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
self._update_accept_focus()
def _update_full_request(self):
- if self.palette_state == self.PRIMARY:
+ if self._palette_state == self.PRIMARY:
self.menu.embed(self._menu_box)
self._secondary_box.show()
self._full_request = self.size_request()
- if self.palette_state == self.PRIMARY:
+ if self._palette_state == self.PRIMARY:
self.menu.unembed()
self._secondary_box.hide()
- def _update_position(self):
- invoker = self._invoker
- if invoker is None or self._alignment is None:
- logging.error('Cannot update the palette position.')
- return
-
- rect = self.size_request()
- position = invoker.get_position_for_alignment(self._alignment, rect)
- if position is None:
- position = invoker.get_position(rect)
-
- self.move(position.x, position.y)
-
- def popup(self, immediate=False, state=None):
- logging.debug('Palette.popup immediate %r' % immediate)
-
- if state is None:
- state = self.PRIMARY
- self.set_state(state)
-
- if self._invoker is not None:
- self._update_full_request()
- self._alignment = self._invoker.get_alignment(self._full_request)
- self._update_position()
- self.set_transient_for(self._invoker.get_toplevel())
-
- self._popdown_anim.stop()
-
- if not immediate:
- self._popup_anim.start()
- else:
- self.show()
-
- self._secondary_anim.start()
-
- def popdown(self, immediate=False):
- logging.debug('Palette.popdown immediate %r' % immediate)
- self._popup_anim.stop()
-
- self._mouse_detector.stop()
-
- if not immediate:
- self._popdown_anim.start()
- else:
- self.hide()
-
- def set_state(self, state):
- if self.palette_state == state:
+ def _set_palette_state(self, state):
+ if self._palette_state == state:
return
if state == self.PRIMARY:
@@ -596,73 +726,9 @@ class Palette(gtk.Window):
elif state == self.SECONDARY:
self.menu.embed(self._menu_box)
self._secondary_box.show()
- self._update_position()
-
- self.palette_state = state
-
- def _mouse_slow_cb(self, widget):
- self._mouse_detector.stop()
- self._palette_do_popup()
-
- def _palette_do_popup(self):
- immediate = False
-
- if self.is_up():
- self._popdown_anim.stop()
- return
-
- if self._group_id:
- group = palettegroup.get_group(self._group_id)
- if group and group.is_up():
- immediate = True
- group.popdown()
-
- self.popup(immediate=immediate)
-
- def _invoker_widget_changed_cb(self, invoker, spec):
- self._update_accel_widget()
-
- def _invoker_mouse_enter_cb(self, invoker):
- self._mouse_detector.start()
-
- def _invoker_mouse_leave_cb(self, invoker):
- self._mouse_detector.stop()
- self.popdown()
-
- def _invoker_right_click_cb(self, invoker):
- self.popup(immediate=True, state=self.SECONDARY)
-
- def __enter_notify_event_cb(self, widget, event):
- if event.detail != gtk.gdk.NOTIFY_INFERIOR and \
- event.mode == gtk.gdk.CROSSING_NORMAL:
- self._popdown_anim.stop()
- self._secondary_anim.start()
-
- def __leave_notify_event_cb(self, widget, event):
- if event.detail != gtk.gdk.NOTIFY_INFERIOR and \
- event.mode == gtk.gdk.CROSSING_NORMAL:
- self.popdown()
-
- def __show_cb(self, widget):
- self.menu.set_active(True)
-
- self._invoker.notify_popup()
-
- self._up = True
- self.emit('popup')
-
- def __hide_cb(self, widget):
- logging.debug('__hide_cb')
- self.menu.set_active(False)
-
- self._secondary_anim.stop()
-
- if self._invoker:
- self._invoker.notify_popdown()
-
- self._up = False
- self.emit('popdown')
+ self.update_position()
+ self._palette_state = state
class PaletteActionBar(gtk.HButtonBox):
def add_action(self, label, icon_name=None):
@@ -728,7 +794,7 @@ class _SecondaryAnimation(animator.Animation):
def next_frame(self, current):
if current == 1.0:
- self._palette.set_state(Palette.SECONDARY)
+ self._palette.set_palette_state(Palette.SECONDARY)
class _PopdownAnimation(animator.Animation):
def __init__(self, palette):
diff --git a/src/sugar/graphics/radiopalette.py b/src/sugar/graphics/radiopalette.py
new file mode 100644
index 0000000..9a9476d
--- /dev/null
+++ b/src/sugar/graphics/radiopalette.py
@@ -0,0 +1,98 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+
+from sugar.graphics import style
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.palette import Palette
+
+class RadioMenuButton(ToolButton):
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, **kwargs)
+ self.selected_button = None
+
+ if self.props.palette:
+ self.__palette_cb(None, None)
+
+ self.connect('notify::palette', self.__palette_cb)
+
+ def __palette_cb(self, widget, pspec):
+ if not isinstance(self.props.palette, RadioPalette):
+ return
+ self.props.palette.update_button()
+
+ def do_clicked(self):
+ if self.palette is None:
+ return
+ if self.palette.is_up() and \
+ self.palette.palette_state == Palette.SECONDARY:
+ self.palette.popdown(immediate=True)
+ else:
+ self.palette.popup(immediate=True, state=Palette.SECONDARY)
+
+class RadioToolsButton(RadioMenuButton):
+ def __init__(self, **kwargs):
+ RadioMenuButton.__init__(self, **kwargs)
+
+ def do_clicked(self):
+ if not self.selected_button:
+ return
+ self.selected_button.emit('clicked')
+
+class RadioPalette(Palette):
+ def __init__(self, **kwargs):
+ Palette.__init__(self, **kwargs)
+
+ self.button_box = gtk.HBox()
+ self.button_box.show()
+ self.set_content(self.button_box)
+
+ def append(self, button, label):
+ children = self.button_box.get_children()
+
+ if button.palette is not None:
+ raise RuntimeError("Palette's button should not have sub-palettes")
+
+ button.show()
+ button.connect('clicked', self.__clicked_cb)
+ self.button_box.pack_start(button, fill=False)
+ button.palette_label = label
+
+ if not children:
+ self.__clicked_cb(button)
+
+ def update_button(self):
+ for i in self.button_box.get_children():
+ self.__clicked_cb(i)
+
+ def __clicked_cb(self, button):
+ if not button.get_active():
+ return
+
+ self.set_primary_text(button.palette_label)
+ self.popdown(immediate=True)
+
+ if self.invoker is not None:
+ parent = self.invoker.parent
+ else:
+ parent = None
+ if not isinstance(parent, RadioMenuButton):
+ return
+
+ parent.set_icon(button.props.icon_name)
+ parent.selected_button = button
diff --git a/src/sugar/graphics/style.py b/src/sugar/graphics/style.py
index 591957b..e63e5ca 100644
--- a/src/sugar/graphics/style.py
+++ b/src/sugar/graphics/style.py
@@ -28,7 +28,7 @@ import logging
import gtk
import pango
-_FOCUS_LINE_WIDTH = 2
+FOCUS_LINE_WIDTH = 2
_TAB_CURVATURE = 1
def _compute_zoom_factor():
@@ -115,8 +115,8 @@ FONT_BOLD_H = zoom(24)
TOOLBOX_SEPARATOR_HEIGHT = zoom(9)
TOOLBOX_HORIZONTAL_PADDING = zoom(75)
-TOOLBOX_TAB_VBORDER = int((zoom(36) - FONT_NORMAL_H - _FOCUS_LINE_WIDTH) / 2)
-TOOLBOX_TAB_HBORDER = zoom(15) - _FOCUS_LINE_WIDTH - _TAB_CURVATURE
+TOOLBOX_TAB_VBORDER = int((zoom(36) - FONT_NORMAL_H - FOCUS_LINE_WIDTH) / 2)
+TOOLBOX_TAB_HBORDER = zoom(15) - FOCUS_LINE_WIDTH - _TAB_CURVATURE
TOOLBOX_TAB_LABEL_WIDTH = zoom(150 - 15 * 2)
COLOR_BLACK = Color('#000000')
@@ -131,3 +131,5 @@ COLOR_INACTIVE_STROKE = Color('#757575')
COLOR_TEXT_FIELD_GREY = Color('#E5E5E5')
PALETTE_CURSOR_DISTANCE = zoom(10)
+
+TOOLBAR_ARROW_SIZE = zoom(24)
diff --git a/src/sugar/graphics/toolbarbox.py b/src/sugar/graphics/toolbarbox.py
new file mode 100644
index 0000000..54de2bd
--- /dev/null
+++ b/src/sugar/graphics/toolbarbox.py
@@ -0,0 +1,284 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+import gobject
+import logging
+
+from sugar.graphics import style
+from sugar.graphics.palette import PaletteWindow, ToolInvoker
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics import palettegroup
+
+class ToolbarButton(ToolButton):
+ def __init__(self, page=None, **kwargs):
+ ToolButton.__init__(self, **kwargs)
+
+ self.set_page(page)
+
+ self.connect('clicked',
+ lambda widget: self.set_expanded(not self.is_expanded()))
+
+ def get_toolbar_box(self):
+ if not hasattr(self.parent, 'owner'):
+ return None
+ return self.parent.owner
+
+ toolbar_box = property(get_toolbar_box)
+
+ def get_page(self):
+ if self.page_widget is None:
+ return None
+ return self.page_widget.child.child
+
+ def set_page(self, page):
+ if page is None:
+ self.page_widget = None
+ return
+ self.page_widget = _embody_page(_Box, page)
+ self.page_widget.toolbar_button = self
+ page.show()
+ if self.props.palette is None:
+ self.props.palette = _ToolbarPalette(invoker=ToolInvoker(self))
+ self.props.palette.toolbar_button = self
+ self._move_page_to_palette()
+
+ page = gobject.property(type=object, getter=get_page, setter=set_page)
+
+ def _move_page_to_palette(self):
+ if self.page_widget is None:
+ return
+
+ if self.toolbar_box is not None and \
+ self.page_widget in self.toolbar_box.get_children():
+ self.toolbar_box.remove(self.page_widget)
+
+ if isinstance(self.props.palette, _ToolbarPalette):
+ self.props.palette.add(self.page_widget)
+
+ def is_expanded(self):
+ return self.toolbar_box is not None and self.page_widget is not None \
+ and self.toolbar_box.expanded_button == self
+
+ def popdown(self):
+ self.props.palette.popdown(immediate=True)
+
+ def set_expanded(self, expanded):
+ self.popdown()
+
+ box = self.toolbar_box
+
+ if box is None or self.page_widget is None or \
+ self.is_expanded() == expanded:
+ return
+
+ if not expanded:
+ box.remove(self.page_widget)
+ box.expanded_button = None
+ self._move_page_to_palette()
+ return
+
+ if box.expanded_button is not None:
+ # need to redraw it to erase arrow
+ expanded_toolitem = box.expanded_button.page_widget.toolbar_button
+ if expanded_toolitem.window is not None:
+ expanded_toolitem.window.invalidate_rect(None, True)
+ box.expanded_button.set_expanded(False)
+
+ if self.page_widget.parent is not None:
+ self.props.palette.remove(self.page_widget)
+
+ self.modify_bg(gtk.STATE_NORMAL, box.background)
+ _setup_page(self.page_widget, box.background, box.props.padding)
+ box.pack_start(self.page_widget)
+ box.expanded_button = self
+
+ def do_expose_event(self, event):
+ if not self.is_expanded() or self.props.palette is not None and \
+ self.props.palette.is_up():
+ ToolButton.do_expose_event(self, event)
+ _paint_arrow(self, event, gtk.ARROW_DOWN)
+ return
+
+ alloc = self.allocation
+
+ self.get_style().paint_box(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_IN, event.area, self,
+ 'palette-invoker', alloc.x, 0,
+ alloc.width, alloc.height + style.FOCUS_LINE_WIDTH)
+
+ if self.child.state != gtk.STATE_PRELIGHT:
+ self.get_style().paint_box(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, self, None,
+ alloc.x + style.FOCUS_LINE_WIDTH, style.FOCUS_LINE_WIDTH,
+ alloc.width - style.FOCUS_LINE_WIDTH * 2, alloc.height)
+
+ gtk.ToolButton.do_expose_event(self, event)
+ _paint_arrow(self, event, gtk.ARROW_UP)
+
+class ToolbarBox(gtk.VBox):
+ def __init__(self, padding=style.TOOLBOX_HORIZONTAL_PADDING):
+ gtk.VBox.__init__(self)
+ self.expanded_button = None
+ self.background = None
+
+ self._toolbar = gtk.Toolbar()
+ self._toolbar.owner = self
+ self._toolbar.connect('remove', self.__remove_cb)
+
+ top_widget = _embody_page(gtk.EventBox, self._toolbar)
+ self.pack_start(top_widget)
+
+ self.props.padding = padding
+ self.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_TOOLBAR_GREY.get_gdk_color())
+
+ def __remove_cb(self, sender, button):
+ if not isinstance(button, ToolbarButton):
+ return
+ button.popdown()
+ if self.expanded_button == button:
+ self.remove(button.page_widget)
+ self.expanded_button = None
+
+ def get_toolbar(self):
+ return self._toolbar
+
+ toolbar = property(get_toolbar)
+
+ def get_padding(self):
+ return self.toolbar.parent.props.left_padding
+
+ def set_padding(self, pad):
+ self.toolbar.parent.set_padding(0, 0, pad, pad)
+
+ padding = gobject.property(type=object,
+ getter=get_padding, setter=set_padding)
+
+ def modify_bg(self, state, color):
+ if state == gtk.STATE_NORMAL:
+ self.background = color
+ self.toolbar.parent.parent.modify_bg(state, color)
+ self.toolbar.modify_bg(state, color)
+
+class _ToolbarPalette(PaletteWindow):
+ def __init__(self, **kwargs):
+ PaletteWindow.__init__(self, **kwargs)
+ self.toolbar_box = None
+ self.set_border_width(0)
+ self._focus = 0
+
+ group = palettegroup.get_group('default')
+ group.connect('popdown', self.__group_popdown_cb)
+ self.set_group_id('toolbar_box')
+
+ def on_invoker_enter(self):
+ PaletteWindow.on_invoker_enter(self)
+ self._handle_focus(+1)
+
+ def on_invoker_leave(self):
+ PaletteWindow.on_invoker_leave(self)
+ self._handle_focus(-1)
+
+ def on_enter(self, event):
+ PaletteWindow.on_enter(self, event)
+ self._handle_focus(+1)
+
+ def on_leave(self, event):
+ PaletteWindow.on_enter(self, event)
+ self._handle_focus(-1)
+
+ def _handle_focus(self, delta):
+ self._focus += delta
+ if self._focus not in (0, 1):
+ logging.error('_Palette._focus=%s not in (0, 1)' % self._focus)
+
+ if self._focus == 0:
+ group = palettegroup.get_group('default')
+ if not group.is_up():
+ self.popdown()
+
+ def do_size_request(self, requisition):
+ gtk.Window.do_size_request(self, requisition)
+ requisition.width = max(requisition.width,
+ gtk.gdk.screen_width())
+
+ def popup(self, immediate=False):
+ button = self.toolbar_button
+ if button.is_expanded():
+ return
+ box = button.toolbar_box
+ _setup_page(button.page_widget, style.COLOR_BLACK.get_gdk_color(),
+ box.props.padding)
+ PaletteWindow.popup(self, immediate)
+
+ def __group_popdown_cb(self, group):
+ if self._focus == 0:
+ self.popdown(immediate=True)
+
+class _Box(gtk.EventBox):
+ def __init__(self):
+ gtk.EventBox.__init__(self)
+ self.connect('expose-event', self.do_expose_event)
+ self.set_app_paintable(True)
+
+ def do_expose_event(self, widget, event):
+ alloc = self.toolbar_button.allocation
+ self.get_style().paint_box(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_IN, event.area, self,
+ 'palette-invoker', -style.FOCUS_LINE_WIDTH, 0,
+ self.allocation.width + style.FOCUS_LINE_WIDTH * 2,
+ self.allocation.height + style.FOCUS_LINE_WIDTH)
+ self.get_style().paint_box(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, self, None,
+ alloc.x + style.FOCUS_LINE_WIDTH, 0,
+ alloc.width - style.FOCUS_LINE_WIDTH * 2,
+ style.FOCUS_LINE_WIDTH)
+
+def _setup_page(page_widget, color, hpad):
+ vpad = style.FOCUS_LINE_WIDTH
+ page_widget.child.set_padding(vpad, vpad, hpad, hpad)
+
+ page = page_widget.child.child
+ page.modify_bg(gtk.STATE_NORMAL, color)
+ if isinstance(page, gtk.Container):
+ for i in page.get_children():
+ i.modify_bg(gtk.STATE_NORMAL, color)
+
+ page_widget.modify_bg(gtk.STATE_NORMAL, color)
+ page_widget.modify_bg(gtk.STATE_PRELIGHT, color)
+
+def _embody_page(box_class, widget):
+ widget.show()
+ alignment = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
+ alignment.add(widget)
+ alignment.show()
+ box = box_class()
+ box.modify_bg(gtk.STATE_ACTIVE, style.COLOR_BUTTON_GREY.get_gdk_color())
+ box.add(alignment)
+ box.show()
+ return box
+
+def _paint_arrow(widget, event, arrow_type):
+ alloc = widget.allocation
+ x = alloc.x + alloc.width / 2 - style.TOOLBAR_ARROW_SIZE / 2
+ y = alloc.y + alloc.height - int(style.TOOLBAR_ARROW_SIZE * .85)
+
+ widget.get_style().paint_arrow(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, widget,
+ None, arrow_type, True,
+ x, y, style.TOOLBAR_ARROW_SIZE, style.TOOLBAR_ARROW_SIZE)
diff --git a/src/sugar/graphics/toolbutton.py b/src/sugar/graphics/toolbutton.py
index 6205b8a..047df06 100644
--- a/src/sugar/graphics/toolbutton.py
+++ b/src/sugar/graphics/toolbutton.py
@@ -68,7 +68,6 @@ class ToolButton(gtk.ToolButton):
if icon_name:
self.set_icon(icon_name)
- self.connect('clicked', self.__button_clicked_cb)
self.get_child().connect('can-activate-accel',
self.__button_can_activate_accel_cb)
@@ -151,8 +150,7 @@ class ToolButton(gtk.ToolButton):
allocation.width, allocation.height)
gtk.ToolButton.do_expose_event(self, event)
-
- def __button_clicked_cb(self, widget):
+
+ def do_clicked(self):
if self.palette:
self.palette.popdown(True)
-
diff --git a/src/sugar/graphics/window.py b/src/sugar/graphics/window.py
index 9d6a6aa..d1158a7 100644
--- a/src/sugar/graphics/window.py
+++ b/src/sugar/graphics/window.py
@@ -21,6 +21,8 @@ STABLE.
import gobject
import gtk
+import logging
+import warnings
from sugar.graphics.icon import Icon
@@ -84,8 +86,8 @@ class Window(gtk.Window):
self.connect('realize', self.__window_realize_cb)
self.connect('window-state-event', self.__window_state_event_cb)
self.connect('key-press-event', self.__key_press_cb)
-
- self.toolbox = None
+
+ self.__toolbar_box = None
self._alerts = []
self._canvas = None
self.tray = None
@@ -125,14 +127,19 @@ class Window(gtk.Window):
canvas = property(get_canvas, set_canvas)
- def set_toolbox(self, toolbox):
- if self.toolbox:
- self._vbox.remove(self.toolbox)
-
- self._vbox.pack_start(toolbox, False)
- self._vbox.reorder_child(toolbox, 0)
-
- self.toolbox = toolbox
+ def get_toolbar_box(self):
+ return self.__toolbar_box
+
+ def set_toolbar_box(self, toolbar_box):
+ if self.__toolbar_box:
+ self._vbox.remove(self.__toolbar_box)
+
+ self._vbox.pack_start(toolbar_box, False)
+ self._vbox.reorder_child(toolbar_box, 0)
+
+ self.__toolbar_box = toolbar_box
+
+ toolbar_box = property(get_toolbar_box, set_toolbar_box)
def set_tray(self, tray, position):
if self.tray:
@@ -152,7 +159,7 @@ class Window(gtk.Window):
self._alerts.append(alert)
if len(self._alerts) == 1:
self._vbox.pack_start(alert, False)
- if self.toolbox is not None:
+ if self.__toolbar_box is not None:
self._vbox.reorder_child(alert, 1)
else:
self._vbox.reorder_child(alert, 0)
@@ -165,7 +172,7 @@ class Window(gtk.Window):
self._vbox.remove(alert)
if len(self._alerts) >= 1:
self._vbox.pack_start(self._alerts[0], False)
- if self.toolbox is not None:
+ if self.__toolbar_box is not None:
self._vbox.reorder_child(self._alerts[0], 1)
else:
self._vbox.reorder_child(self._alert[0], 0)
@@ -180,8 +187,8 @@ class Window(gtk.Window):
return False
if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
- if self.toolbox is not None:
- self.toolbox.hide()
+ if self.__toolbar_box is not None:
+ self.__toolbar_box.hide()
if self.tray is not None:
self.tray.hide()
@@ -199,8 +206,8 @@ class Window(gtk.Window):
self.__unfullscreen_button_timeout_cb)
else:
- if self.toolbox is not None:
- self.toolbox.show()
+ if self.__toolbar_box is not None:
+ self.__toolbar_box.show()
if self.tray is not None:
self.tray.show()
@@ -258,3 +265,14 @@ class Window(gtk.Window):
setter=set_enable_fullscreen_mode,
getter=get_enable_fullscreen_mode)
+ # DEPRECATED
+
+ def set_toolbox(self, toolbar_box):
+ warnings.warn('use toolbar_box instead of toolbox', DeprecationWarning)
+ self.set_toolbar_box(toolbar_box)
+
+ def get_toolbox(self):
+ warnings.warn('use toolbar_box instead of toolbox', DeprecationWarning)
+ return self.__toolbar_box
+
+ toolbox = property(get_toolbox, set_toolbox)