Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWalter Bender <walter.bender@gmail.com>2012-10-28 22:42:00 (GMT)
committer Walter Bender <walter.bender@gmail.com>2012-10-28 22:42:00 (GMT)
commitc7ae6d084b5a690b8d55899864ae0a94a5414fe1 (patch)
tree31d98309f8f2296f8735912280aa35bd219fa55f
parentc3e3e4f04d49a283ef72c6a3eccd7f0c4588ba7d (diff)
parent4ea8d1e68c5289ab771dcdc46278e6c6169bc5ac (diff)
gtk3 branch
-rw-r--r--AbacusActivity.py269
-rw-r--r--NEWS57
-rwxr-xr-xabacus.py36
-rw-r--r--abacus_window.py44
-rw-r--r--activity/activity.info3
-rwxr-xr-xsetup.py2
-rw-r--r--sprites.py222
-rw-r--r--toolbar_utils.py135
8 files changed, 316 insertions, 452 deletions
diff --git a/AbacusActivity.py b/AbacusActivity.py
index bd3175e..269736d 100644
--- a/AbacusActivity.py
+++ b/AbacusActivity.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-#Copyright (c) 2010-11, Walter Bender
+#Copyright (c) 2010-12, Walter Bender
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -11,56 +11,43 @@
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
-import pygtk
-pygtk.require('2.0')
-import gtk
-import gobject
-
-from sugar.activity import activity
-from sugar import profile
-try: # 0.86+ toolbar widgets
- from sugar.graphics.toolbarbox import ToolbarBox
- HAS_TOOLBARBOX = True
-except ImportError:
- HAS_TOOLBARBOX = False
-if HAS_TOOLBARBOX:
- from sugar.graphics.toolbarbox import ToolbarButton
- from sugar.activity.widgets import ActivityToolbarButton
- from sugar.activity.widgets import StopButton
-from sugar.datastore import datastore
-from sugar.graphics.alert import NotifyAlert
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import GObject
+from gi.repository import Pango
+
+from sugar3.activity import activity
+from sugar3 import profile
+from sugar3.graphics.toolbarbox import ToolbarBox
+from sugar3.activity.widgets import ActivityToolbarButton
+from sugar3.activity.widgets import StopButton
+from sugar3.graphics.toolbarbox import ToolbarButton
+from sugar3.graphics.alert import NotifyAlert
+from sugar3.graphics import style
from gettext import gettext as _
-import locale
import logging
_logger = logging.getLogger('abacus-activity')
-from abacus_window import Abacus, Custom, Suanpan, Soroban, Schety,\
- Nepohualtzintzin, Binary, Hex, Decimal, Fractions,\
- Caacupe, Cuisenaire, MAX_RODS, MAX_TOP, MAX_BOT
+from abacus_window import Abacus, Custom, MAX_RODS, MAX_TOP, MAX_BOT
from toolbar_utils import separator_factory, radio_factory, label_factory, \
button_factory, spin_factory
-NAMES = {
- # TRANS: http://en.wikipedia.org/wiki/Soroban (Japanese abacus)
- 'suanpan': _('Suanpan'),
- # TRANS: http://en.wikipedia.org/wiki/Suanpan (Chinese abacus)
- 'soroban': _('Soroban'),
- 'decimal': _('Decimal'),
- # TRANS: http://en.wikipedia.org/wiki/Abacus#Native_American_abaci)
- 'nepohualtzintzin': _('Nepohualtzintzin'),
- 'hexadecimal': _('Hexadecimal'),
- 'binary': _('Binary'),
- # TRANS: http://en.wikipedia.org/wiki/Abacus#Russian_abacus
- 'schety': _('Schety'),
- 'fraction': _('Fraction'),
- # TRANS: Caacupé is an abacus invented by teachers in Caacupé, Paraguay
- 'caacupe': _('Caacupé'),
- 'cuisenaire': _('Rods'),
- 'custom': _('Custom')
- }
+NAMES = {'suanpan': _('Suanpan'),
+ 'soroban': _('Soroban'),
+ 'decimal': _('Decimal'),
+ 'nepohualtzintzin': _('Nepohualtzintzin'),
+ 'hexadecimal': _('Hexadecimal'),
+ 'binary': _('Binary'),
+ 'schety': _('Schety'),
+ 'fraction': _('Fraction'),
+ 'caacupe': _('Caacupé'),
+ 'cuisenaire': _('Rods'),
+ 'custom': _('Custom')
+ }
+
class AbacusActivity(activity.Activity):
@@ -74,92 +61,69 @@ class AbacusActivity(activity.Activity):
# no sharing
self.max_participants = 1
- abacus_toolbar = gtk.Toolbar()
- custom_toolbar = gtk.Toolbar()
- edit_toolbar = gtk.Toolbar()
-
- if HAS_TOOLBARBOX:
- # Use 0.86 toolbar design
- toolbox = ToolbarBox()
-
- activity_button = ActivityToolbarButton(self)
- toolbox.toolbar.insert(activity_button, 0)
- activity_button.show()
-
- edit_toolbar_button = ToolbarButton(label=_('Edit'),
- page=edit_toolbar,
- icon_name='toolbar-edit')
- edit_toolbar_button.show()
- toolbox.toolbar.insert(edit_toolbar_button, -1)
- edit_toolbar_button.show()
-
- abacus_toolbar_button = ToolbarButton(
- page=abacus_toolbar,
- icon_name='abacus-list')
- abacus_toolbar.show()
- toolbox.toolbar.insert(abacus_toolbar_button, -1)
- abacus_toolbar_button.show()
-
- custom_toolbar_button = ToolbarButton(
- page=custom_toolbar,
- icon_name='view-source')
- custom_toolbar.show()
- toolbox.toolbar.insert(custom_toolbar_button, -1)
- custom_toolbar_button.show()
-
- separator_factory(toolbox.toolbar, False, True)
+ abacus_toolbar = Gtk.Toolbar()
+ custom_toolbar = Gtk.Toolbar()
+ edit_toolbar = Gtk.Toolbar()
- button_factory('edit-clear', toolbox.toolbar,
- self._reset_cb, tooltip=_('Reset'))
+ toolbox = ToolbarBox()
- separator_factory(toolbox.toolbar, False, True)
+ activity_button = ActivityToolbarButton(self)
+ toolbox.toolbar.insert(activity_button, 0)
+ activity_button.show()
- self._label = label_factory(toolbox.toolbar, NAMES['suanpan'])
+ edit_toolbar_button = ToolbarButton(label=_('Edit'),
+ page=edit_toolbar,
+ icon_name='toolbar-edit')
+ edit_toolbar_button.show()
+ toolbox.toolbar.insert(edit_toolbar_button, -1)
+ edit_toolbar_button.show()
- separator_factory(toolbox.toolbar, True, False)
+ abacus_toolbar_button = ToolbarButton(
+ page=abacus_toolbar,
+ icon_name='abacus-list')
+ abacus_toolbar.show()
+ toolbox.toolbar.insert(abacus_toolbar_button, -1)
+ abacus_toolbar_button.show()
- stop_button = StopButton(self)
- stop_button.props.accelerator = _('<Ctrl>Q')
- toolbox.toolbar.insert(stop_button, -1)
- stop_button.show()
+ custom_toolbar_button = ToolbarButton(
+ page=custom_toolbar,
+ icon_name='view-source')
+ custom_toolbar.show()
+ toolbox.toolbar.insert(custom_toolbar_button, -1)
+ custom_toolbar_button.show()
- self.set_toolbox(toolbox)
- toolbox.show()
+ separator_factory(toolbox.toolbar, False, True)
- else:
- # Use pre-0.86 toolbar design
- toolbox = activity.ActivityToolbox(self)
- self.set_toolbox(toolbox)
-
- toolbox.add_toolbar(_('Project'), abacus_toolbar)
- toolbox.add_toolbar(_('Custom'), custom_toolbar)
- toolbox.add_toolbar(_('Edit'), edit_toolbar)
+ button_factory('edit-delete', toolbox.toolbar,
+ self._reset_cb, tooltip=_('Reset'))
- button_factory('edit-delete', edit_toolbar,
- self._reset_cb, tooltip=_('Reset'))
+ separator_factory(toolbox.toolbar, False, True)
- self._label = label_factory(edit_toolbar, NAMES['suanpan'])
+ self._label = label_factory(NAMES['suanpan'], toolbox.toolbar)
- separator_factory(edit_toolbar, False, True)
+ separator_factory(toolbox.toolbar, True, False)
- toolbox.set_current_toolbar(1)
+ stop_button = StopButton(self)
+ stop_button.props.accelerator = _('<Ctrl>Q')
+ toolbox.toolbar.insert(stop_button, -1)
+ stop_button.show()
- # no sharing
- if hasattr(toolbox, 'share'):
- toolbox.share.hide()
- elif hasattr(toolbox, 'props'):
- toolbox.props.visible = False
+ self.set_toolbar_box(toolbox)
+ toolbox.show()
+ # TRANS: simple decimal abacus
self.decimal = radio_factory('decimal', abacus_toolbar,
self._radio_cb, cb_arg='decimal',
tooltip=NAMES['decimal'],
group=None)
+ # TRANS: http://en.wikipedia.org/wiki/Soroban (Japanese abacus)
self.japanese = radio_factory('soroban', abacus_toolbar,
self._radio_cb, cb_arg='soroban',
tooltip=_('Soroban'),
group=self.decimal)
+ # TRANS: http://en.wikipedia.org/wiki/Suanpan (Chinese abacus)
self.chinese = radio_factory('suanpan', abacus_toolbar,
self._radio_cb, cb_arg='suanpan',
tooltip=NAMES['suanpan'],
@@ -167,16 +131,19 @@ class AbacusActivity(activity.Activity):
separator_factory(abacus_toolbar)
+ # TRANS: http://en.wikipedia.org/wiki/Abacus#Native_American_abaci
self.mayan = radio_factory('nepohualtzintzin', abacus_toolbar,
self._radio_cb, cb_arg='nepohualtzintzin',
tooltip=NAMES['nepohualtzintzin'],
group=self.decimal)
+ # TRANS: hexidecimal abacus
self.hex = radio_factory('hexadecimal', abacus_toolbar,
self._radio_cb, cb_arg='hexadecimal',
tooltip=NAMES['hexadecimal'],
group=self.decimal)
+ # TRANS: binary abacus
self.binary = radio_factory('binary', abacus_toolbar,
self._radio_cb, cb_arg='binary',
tooltip=NAMES['binary'],
@@ -184,16 +151,19 @@ class AbacusActivity(activity.Activity):
separator_factory(abacus_toolbar)
+ # TRANS: http://en.wikipedia.org/wiki/Abacus#Russian_abacus
self.russian = radio_factory('schety', abacus_toolbar,
self._radio_cb, cb_arg='schety',
tooltip=NAMES['schety'],
group=self.decimal)
+ # TRANS: abacus for adding fractions
self.fraction = radio_factory('fraction', abacus_toolbar,
self._radio_cb, cb_arg='fraction',
tooltip=NAMES['fraction'],
group=self.decimal)
+ # TRANS: Abacus invented by teachers in Caacupé, Paraguay
self.caacupe = radio_factory('caacupe', abacus_toolbar,
self._radio_cb, cb_arg='caacupe',
tooltip=NAMES['caacupe'],
@@ -201,6 +171,7 @@ class AbacusActivity(activity.Activity):
separator_factory(abacus_toolbar)
+ # TRANS: Cuisenaire Rods
self.cuisenaire = radio_factory('cuisenaire', abacus_toolbar,
self._radio_cb,
cb_arg='cuisenaire',
@@ -212,62 +183,60 @@ class AbacusActivity(activity.Activity):
self.custom = radio_factory('custom', abacus_toolbar,
self._radio_cb,
cb_arg='custom',
- tooltip=_('Custom'), group=self.decimal)
+ tooltip=NAMES['custom'], group=self.decimal)
preferences_button = button_factory(
'preferences-system', custom_toolbar, self._preferences_palette_cb,
tooltip=_('Custom'))
self._palette = preferences_button.get_palette()
- button_box = gtk.VBox()
+ button_box = Gtk.VBox()
# TRANS: Number of rods on the abacus
- self._rods_spin = self._add_spinner_and_label(
+ self._rods_spin = add_spinner_and_label(
15, 1, MAX_RODS, _('Rods:'), self._rods_spin_cb, button_box)
# TRANS: Number of beads in the top section of the abacus
- self._top_spin = self._add_spinner_and_label(
+ self._top_spin = add_spinner_and_label(
2, 0, MAX_TOP, _('Top:'), self._top_spin_cb, button_box)
# TRANS: Number of beads in the bottom section of the abacus
- self._bottom_spin = self._add_spinner_and_label(
+ self._bottom_spin = add_spinner_and_label(
2, 0, MAX_BOT, _('Bottom:'), self._bottom_spin_cb, button_box)
# TRANS: Scale factor between bottom and top beads
- self._value_spin = self._add_spinner_and_label(
+ self._value_spin = add_spinner_and_label(
5, 1, MAX_BOT + 1, _('Factor:'), self._value_spin_cb, button_box)
# TRANS: Scale factor between rods
- self._base_spin = self._add_spinner_and_label(
+ self._base_spin = add_spinner_and_label(
10, 1, (MAX_TOP + 1) * MAX_BOT, _('Base:'), self._base_spin_cb,
button_box)
button_box.show_all()
self._palette.set_content(button_box)
+ separator_factory(custom_toolbar, False, False)
+
self.custom_maker = button_factory('new-abacus', custom_toolbar,
self._custom_cb,
tooltip=_('Custom'))
- copy = button_factory('edit-copy', edit_toolbar, self._copy_cb,
- tooltip=_('Copy'), accelerator='<Ctrl>c')
- paste = button_factory('edit-paste', edit_toolbar, self._paste_cb,
- tooltip=_('Paste'), accelerator='<Ctrl>v')
-
- self.toolbox.show()
+ button_factory('edit-copy', edit_toolbar, self._copy_cb,
+ tooltip=_('Copy'), accelerator='<Ctrl>c')
+ button_factory('edit-paste', edit_toolbar, self._paste_cb,
+ tooltip=_('Paste'), accelerator='<Ctrl>v')
- '''
- if HAS_TOOLBARBOX:
- # start with abacus toolbar expanded
- abacus_toolbar_button.set_expanded(True)
- '''
+ # start with abacus toolbar expanded
+ abacus_toolbar_button.set_expanded(True)
self.chinese.set_active(True)
# Create a canvas
- canvas = gtk.DrawingArea()
- canvas.set_size_request(gtk.gdk.screen_width(),
- gtk.gdk.screen_height())
+ canvas = Gtk.DrawingArea()
+ canvas.set_size_request(Gdk.Screen.width(),
+ Gdk.Screen.height())
self.set_canvas(canvas)
canvas.show()
self.show_all()
# Initialize the canvas
self.abacus = Abacus(canvas, self)
+
self._setting_up = False
# Read the current mode from the Journal
@@ -349,7 +318,7 @@ class AbacusActivity(activity.Activity):
alert.show()
def _select_abacus(self, abacus):
- ''' Display the selected abacus; hide the others '''
+ ''' Notify the user of an expected delay and then... '''
if not hasattr(self, 'abacus'):
return
if self._setting_up:
@@ -359,9 +328,10 @@ class AbacusActivity(activity.Activity):
self._notify_new_abacus(NAMES[abacus])
# Give the alert time to load
- gobject.timeout_add(100, self._switch_modes, abacus)
+ GObject.timeout_add(1000, self._switch_modes, abacus)
def _switch_modes(self, abacus):
+ ''' Display the selected abacus '''
# Save current value
value = int(float(self.abacus.mode.value()))
if abacus == 'custom':
@@ -405,16 +375,13 @@ class AbacusActivity(activity.Activity):
self.abacus.custom.create()
self.abacus.custom.draw_rods_and_beads()
self.abacus.custom.show()
-
- self._select_abacus('custom')
-
- # self.abacus.mode = self.abacus.custom
+ self.abacus.mode = self.abacus.custom
self.custom.set_active(True)
self._label.set_text(NAMES['custom'])
def _copy_cb(self, arg=None):
''' Copy a number to the clipboard from the active abacus. '''
- clipBoard = gtk.Clipboard()
+ clipBoard = Gtk.Clipboard()
text = self.abacus.generate_label(sum_only=True)
if text is not None:
clipBoard.set_text(text)
@@ -422,7 +389,7 @@ class AbacusActivity(activity.Activity):
def _paste_cb(self, arg=None):
''' Paste a number from the clipboard to the active abacus. '''
- clipBoard = gtk.Clipboard()
+ clipBoard = Gtk.Clipboard()
text = clipBoard.wait_for_text()
if text is not None:
try:
@@ -445,21 +412,21 @@ class AbacusActivity(activity.Activity):
self.metadata['factor'] = str(self._value_spin.get_value_as_int())
self.metadata['base'] = str(self._base_spin.get_value_as_int())
- def _add_spinner_and_label(self, default_value, min_value, max_value,
- tooltip, cb, box):
- ''' Add a spinner and a label to a box '''
- spinner_and_label = gtk.HBox()
- spinner, item = spin_factory(default_value, min_value, max_value,
- cb, None)
- label = gtk.Label(tooltip)
- label.set_justify(gtk.JUSTIFY_LEFT)
- label.set_line_wrap(True)
- label.show()
- spinner_and_label.pack_start(label)
- label = gtk.Label(' ')
- label.show()
- spinner_and_label.pack_start(label)
- spinner_and_label.pack_start(item)
- box.pack_start(spinner_and_label, False, False, padding=5)
- spinner_and_label.show()
- return spinner
+
+def add_spinner_and_label(default_value, min_value, max_value,
+ tooltip, cb, box):
+ ''' Add a spinner and a label to a box '''
+ spinner_and_label = Gtk.HBox()
+ spinner, item = spin_factory(default_value, min_value, max_value, cb, None)
+ label = Gtk.Label(label=tooltip)
+ label.set_justify(Gtk.Justification.LEFT)
+ label.set_line_wrap(True)
+ label.show()
+ spinner_and_label.pack_start(label, expand=False, fill=False, padding=0)
+ label = Gtk.Label(label=' ')
+ label.show()
+ spinner_and_label.pack_start(label, expand=True, fill=False, padding=0)
+ spinner_and_label.pack_start(item, expand=False, fill=False, padding=0)
+ box.pack_start(spinner_and_label, expand=False, fill=False, padding=5)
+ spinner_and_label.show()
+ return spinner
diff --git a/NEWS b/NEWS
index a888df1..174942a 100644
--- a/NEWS
+++ b/NEWS
@@ -1,58 +1,50 @@
-38
+39
-ENHANCEMENT
-* New translations
-
-36
-
-ENHANCEMENT
-* New translations
-* summary string in activity.info
+BUG_FIX
+* Use set_icon_name as per #3976
-30.7
-
-ENHANCEMENT
-* Fixed custom update bug??
-
-30.6
+37
ENHANCEMENT
* New translations
-* Updated icons
+* Added summary to activity.info
-30.5
+35
ENHANCEMENT
-* Move spinners to a palette so they fit on smaller screens
+* Using palette for spinners (#3563 and #3480)
-30.4
+BUG_FIX
+* Remove work-around label color-style problem (#3479) in light of
+ patch to fix #3558 and #3479
-ENHANCEMENT
-* New translations
-
-BUG_FIX:
-* Fix GNOME version to run with refactoring in 30.3
-
-30.3
+34
ENHANCEMENTS
* New translations
* Notify user of delay in loading new abacus
* Display abacus name on toolbar
-BUG_FIXES:
+BUG_FIXES
+* Work-around label color-style problem (#3479)
* Refactoring to fix problem with excessive memory usage
* Don't allow custom abacus larger than screen
+* Update GNOME version to use refactoring
-30.2
+33
ENHANCEMENT
* New translations
-30.1
+32
+
+BUG FIX
+* Update to latest sugar3-toolkit requirements (Gonzalo Oriard)
+
+31
ENHANCEMENT
-* New translations
+* GTK-3 conversion
30
@@ -119,7 +111,8 @@ ENHANCEMENTS
BUG FIX
-* Fixed regression with old-style toolbars that preventing activity launch (#2942)
+* Fixed regression with old-style toolbars that preventing activity
+ launch (#2942)
20
@@ -221,5 +214,3 @@ BUG FIXES
* Abacus activity
* Chinese (suanpan), Japanese (soroban), and Russian (schety) models
-
-
diff --git a/abacus.py b/abacus.py
index e99c96a..66b9859 100755
--- a/abacus.py
+++ b/abacus.py
@@ -13,7 +13,7 @@
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
-import gtk
+from gi.repository import Gtk, Gdk
from gettext import gettext as _
@@ -28,7 +28,7 @@ class AbacusMain:
self.r = 0
self.tw = None
- self.win = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ self.win = Gtk.Window(type=Gtk.WindowType.TOPLEVEL)
try:
data_file = open('.abacusrc', 'r')
except IOError:
@@ -46,7 +46,7 @@ class AbacusMain:
self.win.move(self.x, self.y)
self.win.maximize()
self.win.set_title(_('Abacus'))
- self.win.connect('delete_event', lambda w, e: gtk.main_quit())
+ self.win.connect('delete_event', lambda w, e: Gtk.main_quit())
ABACI = {
'c': _('Suanpan'),
@@ -61,42 +61,42 @@ class AbacusMain:
'R': _('Rods')
}
- menu = gtk.Menu()
+ menu = Gtk.Menu()
for k, v in ABACI.iteritems():
- menu_items = gtk.MenuItem(v)
+ menu_items = Gtk.MenuItem.new_with_label(v)
menu.append(menu_items)
menu_items.connect('activate', self._switch_abacus_cb, k)
- menu_items = gtk.MenuItem(_('Reset'))
+ menu_items = Gtk.MenuItem.new_with_label(_('Reset'))
menu.append(menu_items)
menu_items.connect('activate', self._reset)
- menu_items = gtk.MenuItem(_('Quit'))
+ menu_items = Gtk.MenuItem.new_with_label(_('Quit'))
menu.append(menu_items)
menu_items.connect('activate', self.destroy)
menu_items.show()
- root_menu = gtk.MenuItem('Tools')
+ root_menu = Gtk.MenuItem.new_with_label('Tools')
root_menu.show()
root_menu.set_submenu(menu)
- vbox = gtk.VBox(False, 0)
+ vbox = Gtk.VBox()
self.win.add(vbox)
vbox.show()
- menu_bar = gtk.MenuBar()
+ menu_bar = Gtk.MenuBar()
vbox.pack_start(menu_bar, False, False, 2)
menu_bar.show()
menu_bar.append(root_menu)
- sw = gtk.ScrolledWindow()
- sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ sw = Gtk.ScrolledWindow()
+ sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
sw.show()
- canvas = gtk.DrawingArea()
- width = gtk.gdk.screen_width()
- height = gtk.gdk.screen_height()
+ canvas = Gtk.DrawingArea()
+ width = Gdk.Screen.width()
+ height = Gdk.Screen.height()
canvas.set_size_request(width, height)
sw.add_with_viewport(canvas)
canvas.show()
- vbox.pack_end(sw, True, True)
+ vbox.pack_end(sw, True, True, 0)
self.win.show_all()
@@ -135,10 +135,10 @@ class AbacusMain:
def destroy(self, event, data=None):
''' Callback for destroy event. '''
- gtk.main_quit()
+ Gtk.main_quit()
def main():
- gtk.main()
+ Gtk.main()
return 0
if __name__ == '__main__':
diff --git a/abacus_window.py b/abacus_window.py
index 8265899..028b80f 100644
--- a/abacus_window.py
+++ b/abacus_window.py
@@ -12,9 +12,8 @@
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
-import pygtk
-pygtk.require('2.0')
-import gtk
+import gi
+from gi.repository import Gtk, Gdk, GdkPixbuf
from math import floor, ceil
import locale
@@ -24,7 +23,7 @@ import logging
_logger = logging.getLogger('abacus-activity')
try:
- from sugar.graphics import style
+ from sugar3.graphics import style
GRID_CELL_SIZE = style.GRID_CELL_SIZE
except ImportError:
GRID_CELL_SIZE = 0
@@ -102,7 +101,7 @@ readable-fractions/681534#681534
def _svg_str_to_pixbuf(svg_string):
''' Load pixbuf from SVG string '''
- pl = gtk.gdk.PixbufLoader('svg')
+ pl = GdkPixbuf.PixbufLoader.new_with_type('svg')
pl.write(svg_string)
pl.close()
pixbuf = pl.get_pixbuf()
@@ -725,19 +724,19 @@ class Abacus():
self.bead_colors = parent.bead_colors
parent.show_all()
- self.canvas.set_flags(gtk.CAN_FOCUS)
- self.canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK)
- self.canvas.add_events(gtk.gdk.BUTTON_RELEASE_MASK)
- self.canvas.add_events(gtk.gdk.POINTER_MOTION_MASK)
- self.canvas.connect('expose-event', self._expose_cb)
+ self.canvas.set_can_focus(True)
+ self.canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
+ self.canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK)
+ self.canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
+ self.canvas.connect('draw', self.__draw_cb)
self.canvas.connect('button-press-event', self._button_press_cb)
self.canvas.connect('button-release-event', self._button_release_cb)
self.canvas.connect('motion-notify-event', self._mouse_move_cb)
self.canvas.connect('key_press_event', self._keypress_cb)
- self.width = gtk.gdk.screen_width()
- self.height = gtk.gdk.screen_height() - GRID_CELL_SIZE
+ self.width = Gdk.Screen.width()
+ self.height = Gdk.Screen.height() - GRID_CELL_SIZE
self.sprites = Sprites(self.canvas)
- self.scale = 1.33 * gtk.gdk.screen_height() / 900.0
+ self.scale = 1.33 * Gdk.Screen.height() / 900.0
self.dragpos = 0
self.press = None
self.last = None
@@ -862,7 +861,7 @@ class Abacus():
def _keypress_cb(self, area, event):
''' Keypress: moving the slides with the arrow keys '''
- k = gtk.gdk.keyval_name(event.keyval)
+ k = Gdk.keyval_name(event.keyval)
if k in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'period',
'minus', 'Return', 'BackSpace', 'comma']:
if self.last == self.mode.label_bar:
@@ -917,23 +916,8 @@ class Abacus():
newnum = oldnum
sprite.set_label(newnum + CURSOR)
- def _expose_cb(self, win, event):
- ''' Callback to handle window expose events '''
- self.do_expose_event(event)
- return True
-
# Handle the expose-event by drawing
- def do_expose_event(self, event):
-
- # Create the cairo context
- cr = self.canvas.window.cairo_create()
-
- # Restrict Cairo to the exposed area; avoid extra work
- cr.rectangle(event.area.x, event.area.y,
- event.area.width, event.area.height)
- cr.clip()
-
- # Refresh sprite list
+ def __draw_cb(self, canvas, cr):
self.sprites.redraw_sprites(cr=cr)
def _destroy_cb(self, win, event):
diff --git a/activity/activity.info b/activity/activity.info
index 5e68791..d3333b8 100644
--- a/activity/activity.info
+++ b/activity/activity.info
@@ -1,10 +1,9 @@
[Activity]
name = Abacus
-activity_version = 38
+activity_version = 39
license = GPLv3
bundle_id = org.sugarlabs.AbacusActivity
exec = sugar-activity AbacusActivity.AbacusActivity
icon = activity-abacus
show_launcher = yes
summary = a tool for simple arithmetic calculations
-
diff --git a/setup.py b/setup.py
index bd1e319..bdeaed6 100755
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-from sugar.activity import bundlebuilder
+from sugar3.activity import bundlebuilder
if __name__ == "__main__":
bundlebuilder.start()
diff --git a/sprites.py b/sprites.py
index baae94f..e35f67b 100644
--- a/sprites.py
+++ b/sprites.py
@@ -24,7 +24,7 @@
'''
sprites.py is a simple sprites library for managing graphics objects,
-'sprites', on a gtk.DrawingArea. It manages multiple sprites with
+'sprites', on a Gtk.DrawingArea. It manages multiple sprites with
methods such as move, hide, set_layer, etc.
There are two classes:
@@ -68,7 +68,7 @@ Example usage:
# method for converting SVG to a gtk pixbuf
def svg_str_to_pixbuf(svg_string):
- pl = gtk.gdk.PixbufLoader('svg')
+ pl = GdkPixbuf.PixbufLoader('svg')
pl.write(svg_string)
pl.close()
pixbuf = pl.get_pixbuf()
@@ -76,12 +76,10 @@ def svg_str_to_pixbuf(svg_string):
'''
-import pygtk
-pygtk.require('2.0')
-import gtk
-import pango
-import pangocairo
-import cairo
+import gi
+from gi.repository import Gtk, GdkPixbuf, Gdk
+from gi.repository import Pango, PangoCairo
+
class Sprites:
''' A class for the list of sprites and everything they share in common '''
@@ -90,7 +88,6 @@ class Sprites:
''' Initialize an empty array of sprites '''
self.widget = widget
self.list = []
- self.cr = None
def set_cairo_context(self, cr):
''' Cairo context may be set or reset after __init__ '''
@@ -98,7 +95,7 @@ class Sprites:
def get_sprite(self, i):
''' Return a sprint from the array '''
- if i < 0 or i > len(self.list) - 1:
+ if i < 0 or i > len(self.list)-1:
return(None)
else:
return(self.list[i])
@@ -159,8 +156,7 @@ class Sprite:
def __init__(self, sprites, x, y, image):
''' Initialize an individual sprite '''
self._sprites = sprites
- self.save_xy = (x, y) # remember initial (x, y) position
- self.rect = gtk.gdk.Rectangle(int(x), int(y), 0, 0)
+ self.rect = [int(x), int(y), 0, 0]
self._scale = [12]
self._rescale = [True]
self._horiz_align = ["center"]
@@ -172,7 +168,7 @@ class Sprite:
self._margins = [0, 0, 0, 0]
self.layer = 100
self.labels = []
- self.cached_surfaces = []
+ self.images = []
self._dx = [] # image offsets
self._dy = []
self.type = None
@@ -181,54 +177,47 @@ class Sprite:
def set_image(self, image, i=0, dx=0, dy=0):
''' Add an image to the sprite. '''
- while len(self.cached_surfaces) < i + 1:
- self.cached_surfaces.append(None)
+ while len(self.images) < i + 1:
+ self.images.append(None)
self._dx.append(0)
self._dy.append(0)
+ self.images[i] = image
self._dx[i] = dx
self._dy[i] = dy
- if isinstance(image, gtk.gdk.Pixbuf):
- w = image.get_width()
- h = image.get_height()
+ if isinstance(self.images[i], GdkPixbuf.Pixbuf):
+ w = self.images[i].get_width()
+ h = self.images[i].get_height()
else:
- w, h = image.get_size()
+ w, h = self.images[i].get_size()
if i == 0: # Always reset width and height when base image changes.
- self.rect.width = w + dx
- self.rect.height = h + dy
+ self.rect[2] = w + dx
+ self.rect[3] = h + dy
else:
- if w + dx > self.rect.width:
- self.rect.width = w + dx
- if h + dy > self.rect.height:
- self.rect.height = h + dy
- surface = cairo.ImageSurface(
- cairo.FORMAT_ARGB32, self.rect.width, self.rect.height)
- context = cairo.Context(surface)
- context = gtk.gdk.CairoContext(context)
- context.set_source_pixbuf(image, 0, 0)
- context.rectangle(0, 0, self.rect.width, self.rect.height)
- context.fill()
- self.cached_surfaces[i] = surface
+ if w + dx > self.rect[2]:
+ self.rect[2] = w + dx
+ if h + dy > self.rect[3]:
+ self.rect[3] = h + dy
def move(self, pos):
''' Move to new (x, y) position '''
self.inval()
- self.rect.x, self.rect.y = int(pos[0]), int(pos[1])
+ self.rect[0], self.rect[1] = int(pos[0]), int(pos[1])
self.inval()
def move_relative(self, pos):
''' Move to new (x+dx, y+dy) position '''
self.inval()
- self.rect.x += int(pos[0])
- self.rect.y += int(pos[1])
+ self.rect[0] += int(pos[0])
+ self.rect[1] += int(pos[1])
self.inval()
def get_xy(self):
''' Return current (x, y) position '''
- return (self.rect.x, self.rect.y)
+ return (self.rect[0], self.rect[1])
def get_dimensions(self):
''' Return current size '''
- return (self.rect.width, self.rect.height)
+ return (self.rect[2], self.rect[3])
def get_layer(self):
''' Return current layer '''
@@ -240,11 +229,10 @@ class Sprite:
self.set_image(image, i)
self.inval()
- def set_layer(self, layer=None):
+ def set_layer(self, layer):
''' Set the layer for a sprite '''
self._sprites.remove_from_list(self)
- if layer is not None:
- self.layer = layer
+ self.layer = layer
for i in range(self._sprites.length_of_list()):
if layer < self._sprites.get_sprite(i).layer:
self._sprites.insert_in_list(self, i)
@@ -272,7 +260,7 @@ class Sprite:
if self._fd is None:
self.set_font('Sans')
if self._color is None:
- self._color = (0., 0., 0.)
+ self._color = (0.5, 0.5, 0.5)
while len(self.labels) < i + 1:
self.labels.append(" ")
self._scale.append(self._scale[0])
@@ -282,7 +270,7 @@ class Sprite:
def set_font(self, font):
''' Set the font for a label '''
- self._fd = pango.FontDescription(font)
+ self._fd = Pango.FontDescription(font)
def set_label_color(self, rgb):
''' Set the font color for a label '''
@@ -313,142 +301,136 @@ class Sprite:
self.inval()
self._sprites.remove_from_list(self)
- def restore(self):
- ''' Restore a hidden sprite '''
- self.set_layer()
-
def inval(self):
''' Invalidate a region for gtk '''
- self._sprites.widget.queue_draw_area(self.rect.x,
- self.rect.y,
- self.rect.width,
- self.rect.height)
+ # self._sprites.window.invalidate_rect(self.rect, False)
+ self._sprites.widget.queue_draw_area(self.rect[0],
+ self.rect[1],
+ self.rect[2],
+ self.rect[3])
def draw(self, cr=None):
''' Draw the sprite (and label) '''
if cr is None:
+ cr = self._sprites.cr
+ if cr is None:
print 'sprite.draw: no Cairo context.'
return
- for i, surface in enumerate(self.cached_surfaces):
- cr.set_source_surface(surface,
- self.rect.x + self._dx[i],
- self.rect.y + self._dy[i])
- cr.rectangle(self.rect.x + self._dx[i],
- self.rect.y + self._dy[i],
- self.rect.width,
- self.rect.height)
- cr.fill()
+ for i, img in enumerate(self.images):
+ if isinstance(img, GdkPixbuf.Pixbuf):
+ Gdk.cairo_set_source_pixbuf(cr, img,
+ self.rect[0] + self._dx[i],
+ self.rect[1] + self._dy[i])
+ cr.rectangle(self.rect[0] + self._dx[i],
+ self.rect[1] + self._dy[i],
+ self.rect[2],
+ self.rect[3])
+ cr.fill()
+ else:
+ print 'sprite.draw: source not a pixbuf (%s)' % (type(img))
if len(self.labels) > 0:
self.draw_label(cr)
def hit(self, pos):
''' Is (x, y) on top of the sprite? '''
x, y = pos
- if x < self.rect.x:
+ if x < self.rect[0]:
return False
- if x > self.rect.x + self.rect.width:
+ if x > self.rect[0] + self.rect[2]:
return False
- if y < self.rect.y:
+ if y < self.rect[1]:
return False
- if y > self.rect.y + self.rect.height:
+ if y > self.rect[1] + self.rect[3]:
return False
return True
def draw_label(self, cr):
''' Draw the label based on its attributes '''
- # Create a pangocairo context
- cr = pangocairo.CairoContext(cr)
- my_width = self.rect.width - self._margins[0] - self._margins[2]
+ my_width = self.rect[2] - self._margins[0] - self._margins[2]
if my_width < 0:
my_width = 0
- my_height = self.rect.height - self._margins[1] - self._margins[3]
+ my_height = self.rect[3] - self._margins[1] - self._margins[3]
for i in range(len(self.labels)):
- pl = cr.create_layout()
- pl.set_text(str(self.labels[i]))
- self._fd.set_size(int(self._scale[i] * pango.SCALE))
+ pl = PangoCairo.create_layout(cr)
+ pl.set_text(str(self.labels[i]), -1)
+ self._fd.set_size(int(self._scale[i] * Pango.SCALE))
pl.set_font_description(self._fd)
- w = pl.get_size()[0] / pango.SCALE
+ w = pl.get_size()[0] / Pango.SCALE
if w > my_width:
if self._rescale[i]:
self._fd.set_size(
- int(self._scale[i] * pango.SCALE * my_width / w))
+ int(self._scale[i] * Pango.SCALE * my_width / w))
pl.set_font_description(self._fd)
- w = pl.get_size()[0] / pango.SCALE
+ w = pl.get_size()[0] / Pango.SCALE
else:
j = len(self.labels[i]) - 1
while(w > my_width and j > 0):
pl.set_text(
- "…" + self.labels[i][len(self.labels[i]) - j:])
- self._fd.set_size(int(self._scale[i] * pango.SCALE))
+ "…" + self.labels[i][len(self.labels[i]) - j:], -1)
+ self._fd.set_size(int(self._scale[i] * Pango.SCALE))
pl.set_font_description(self._fd)
- w = pl.get_size()[0] / pango.SCALE
+ w = pl.get_size()[0] / Pango.SCALE
j -= 1
if self._horiz_align[i] == "center":
- x = int(self.rect.x + self._margins[0] + (my_width - w) / 2)
+ x = int(self.rect[0] + self._margins[0] + (my_width - w) / 2)
elif self._horiz_align[i] == 'left':
- x = int(self.rect.x + self._margins[0])
+ x = int(self.rect[0] + self._margins[0])
else: # right
- x = int(self.rect.x + self.rect.width - w - self._margins[2])
- h = pl.get_size()[1] / pango.SCALE
+ x = int(self.rect[0] + self.rect[2] - w - self._margins[2])
+ h = pl.get_size()[1] / Pango.SCALE
if self._vert_align[i] == "middle":
- y = int(self.rect.y + self._margins[1] + (my_height - h) / 2)
+ y = int(self.rect[1] + self._margins[1] + (my_height - h) / 2)
elif self._vert_align[i] == "top":
- y = int(self.rect.y + self._margins[1])
+ y = int(self.rect[1] + self._margins[1])
else: # bottom
- y = int(self.rect.y + self.rect.height - h - self._margins[3])
+ y = int(self.rect[1] + self.rect[3] - h - self._margins[3])
cr.save()
cr.translate(x, y)
cr.set_source_rgb(self._color[0], self._color[1], self._color[2])
- cr.update_layout(pl)
- cr.show_layout(pl)
+ PangoCairo.update_layout(cr, pl)
+ PangoCairo.show_layout(cr, pl)
cr.restore()
def label_width(self):
''' Calculate the width of a label '''
- cr = pangocairo.CairoContext(self._sprites.cr)
- if cr is not None:
- max = 0
- for i in range(len(self.labels)):
- pl = cr.create_layout()
- pl.set_text(self.labels[i])
- self._fd.set_size(int(self._scale[i] * pango.SCALE))
- pl.set_font_description(self._fd)
- w = pl.get_size()[0] / pango.SCALE
- if w > max:
- max = w
- return max
- else:
- return self.rect.width
+ max = 0
+ for i in range(len(self.labels)):
+ pl = self._sprites.canvas.create_pango_layout(self.labels[i])
+ self._fd.set_size(int(self._scale[i] * Pango.SCALE))
+ pl.set_font_description(self._fd)
+ w = pl.get_size()[0] / Pango.SCALE
+ if w > max:
+ max = w
+ return max
def label_safe_width(self):
''' Return maximum width for a label '''
- return self.rect.width - self._margins[0] - self._margins[2]
+ return self.rect[2] - self._margins[0] - self._margins[2]
def label_safe_height(self):
''' Return maximum height for a label '''
- return self.rect.height - self._margins[1] - self._margins[3]
+ return self.rect[3] - self._margins[1] - self._margins[3]
def label_left_top(self):
''' Return the upper-left corner of the label safe zone '''
return(self._margins[0], self._margins[1])
def get_pixel(self, pos, i=0):
- ''' Return the pixel at (x, y) '''
- x = int(pos[0] - self.rect.x)
- y = int(pos[1] - self.rect.y)
- if x < 0 or x > (self.rect.width - 1) or \
- y < 0 or y > (self.rect.height - 1):
+ ''' Return the pixl at (x, y) '''
+ x, y = pos
+ x = x - self.rect[0]
+ y = y - self.rect[1]
+ if y > self.images[i].get_height() - 1:
+ return(-1, -1, -1, -1)
+ try:
+ array = self.images[i].get_pixels()
+ if array is not None:
+ offset = (y * self.images[i].get_width() + x) * 4
+ r, g, b, a = ord(array[offset]), ord(array[offset + 1]),\
+ ord(array[offset + 2]), ord(array[offset + 3])
+ return(r, g, b, a)
+ else:
+ return(-1, -1, -1, -1)
+ except IndexError:
+ print "Index Error: %d %d" % (len(array), offset)
return(-1, -1, -1, -1)
-
- # create a new 1x1 cairo surface
- cs = cairo.ImageSurface(cairo.FORMAT_RGB24, 1, 1);
- cr = cairo.Context(cs)
- cr.set_source_surface(self.cached_surfaces[i], -x, -y)
- cr.rectangle(0,0,1,1)
- cr.set_operator(cairo.OPERATOR_SOURCE)
- cr.fill()
- cs.flush() # ensure all writing is done
- # Read the pixel
- pixels = cs.get_data()
- return (ord(pixels[2]), ord(pixels[1]), ord(pixels[0]), 0)
-
diff --git a/toolbar_utils.py b/toolbar_utils.py
index c9704b3..d8fa9b4 100644
--- a/toolbar_utils.py
+++ b/toolbar_utils.py
@@ -11,70 +11,25 @@
# Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
-import gtk
+from gi.repository import Gtk
-from sugar.graphics.radiotoolbutton import RadioToolButton
-from sugar.graphics.toolbutton import ToolButton
-from sugar.graphics.combobox import ComboBox
-from sugar.graphics.toolcombobox import ToolComboBox
-
-
-def combo_factory(combo_array, toolbar, callback, cb_arg=None,
- tooltip=None, default=None):
- '''Factory for making a toolbar combo box'''
- combo = ComboBox()
- if tooltip is not None and hasattr(combo, 'set_tooltip_text'):
- combo.set_tooltip_text(tooltip)
- if cb_arg is not None:
- combo.connect('changed', callback, cb_arg)
- else:
- combo.connect('changed', callback)
- for i, selection in enumerate(combo_array):
- combo.append_item(i, selection, None)
- combo.show()
- toolitem = gtk.ToolItem()
- toolitem.add(combo)
- if hasattr(toolbar, 'insert'): # the main toolbar
- toolbar.insert(toolitem, -1)
- else: # or a secondary toolbar
- toolbar.props.page.insert(toolitem, -1)
- toolitem.show()
- if default is not None:
- combo.set_active(combo_array.index(default))
- return combo
-
-
-def entry_factory(default_string, toolbar, tooltip=None, max=3):
- ''' Factory for adding a text box to a toolbar '''
- entry = gtk.Entry()
- entry.set_text(default_string)
- if tooltip is not None and hasattr(entry, 'set_tooltip_text'):
- entry.set_tooltip_text(tooltip)
- entry.set_width_chars(max)
- entry.show()
- toolitem = gtk.ToolItem()
- toolitem.add(entry)
- if hasattr(toolbar, 'insert'): # the main toolbar
- toolbar.insert(toolitem, -1)
- else: # or a secondary toolbar
- toolbar.props.page.insert(toolitem, -1)
- toolitem.show()
- return entry
+from sugar3.graphics.toolbutton import ToolButton
+from sugar3.graphics.radiotoolbutton import RadioToolButton
def button_factory(icon_name, toolbar, callback, cb_arg=None, tooltip=None,
- accelerator=None):
- '''Factory for making toolbar buttons'''
+ accelerator=None):
+ ''' Factory for making toolbar buttons '''
button = ToolButton(icon_name)
if tooltip is not None:
button.set_tooltip(tooltip)
button.props.sensitive = True
if accelerator is not None:
button.props.accelerator = accelerator
- if cb_arg is not None:
- button.connect('clicked', callback, cb_arg)
- else:
+ if cb_arg is None:
button.connect('clicked', callback)
+ else:
+ button.connect('clicked', cb_arg)
if hasattr(toolbar, 'insert'): # the main toolbar
toolbar.insert(button, -1)
else: # or a secondary toolbar
@@ -83,69 +38,46 @@ def button_factory(icon_name, toolbar, callback, cb_arg=None, tooltip=None,
return button
-def radio_factory(button_name, toolbar, callback, cb_arg=None, tooltip=None,
- group=None):
+def radio_factory(icon_name, toolbar, callback, cb_arg=None,
+ tooltip=None, group=None):
''' Add a radio button to a toolbar '''
button = RadioToolButton(group=group)
- button.set_named_icon(button_name)
- if callback is not None:
- if cb_arg is None:
- button.connect('clicked', callback)
- else:
- button.connect('clicked', callback, cb_arg)
- if hasattr(toolbar, 'insert'): # Add button to the main toolbar...
+ button.set_icon_name(icon_name)
+ if tooltip is not None:
+ button.set_tooltip(tooltip)
+ if cb_arg is None:
+ button.connect('clicked', callback)
+ else:
+ button.connect('clicked', callback, cb_arg)
+ if hasattr(toolbar, 'insert'): # the main toolbar
toolbar.insert(button, -1)
- else: # ...or a secondary toolbar.
+ else: # or a secondary toolbar
toolbar.props.page.insert(button, -1)
button.show()
- if tooltip is not None:
- button.set_tooltip(tooltip)
return button
-def label_factory(toolbar, label_text, width=None):
+def label_factory(label_text, toolbar):
''' Factory for adding a label to a toolbar '''
- label = gtk.Label(label_text)
+ label = Gtk.Label(label=label_text)
label.set_line_wrap(True)
- if width is not None:
- label.set_size_request(width, -1) # doesn't work on XOs
label.show()
- toolitem = gtk.ToolItem()
+ toolitem = Gtk.ToolItem()
toolitem.add(label)
toolbar.insert(toolitem, -1)
toolitem.show()
return label
-def separator_factory(toolbar, expand=False, visible=True):
- ''' add a separator to a toolbar '''
- separator = gtk.SeparatorToolItem()
- separator.props.draw = visible
- separator.set_expand(expand)
- toolbar.insert(separator, -1)
- separator.show()
-
-
-def image_factory(image, toolbar, tooltip=None):
- ''' Add an image to the toolbar '''
- img = gtk.Image()
- img.set_from_pixbuf(image)
- img_tool = gtk.ToolItem()
- img_tool.add(img)
- if tooltip is not None:
- img.set_tooltip_text(tooltip)
- toolbar.insert(img_tool, -1)
- img_tool.show()
- return img
-
-
-def spin_factory(default_value, min_value, max_value, callback, toolbar):
- spin_adj = gtk.Adjustment(default_value, min_value, max_value, 1, 32, 0)
- spin = gtk.SpinButton(spin_adj, 0, 0)
- spin_id = spin.connect('value-changed', callback)
+def spin_factory(default, min_value, max_value, callback, toolbar):
+ ''' Factory for making toolbar value spinners '''
+ spin_adj = Gtk.Adjustment(default, min_value, max_value, 1, 32, 0)
+ spin = Gtk.SpinButton()
+ spin.set_adjustment(spin_adj)
+ spin.connect('value-changed', callback)
spin.set_numeric(True)
spin.show()
- toolitem = gtk.ToolItem()
+ toolitem = Gtk.ToolItem()
toolitem.add(spin)
if toolbar is not None:
toolbar.insert(toolitem, -1)
@@ -154,3 +86,12 @@ def spin_factory(default_value, min_value, max_value, callback, toolbar):
else:
toolitem.show()
return spin, toolitem
+
+
+def separator_factory(toolbar, expand=False, visible=True):
+ ''' Add a separator to a toolbar '''
+ separator = Gtk.SeparatorToolItem()
+ separator.props.draw = visible
+ separator.set_expand(expand)
+ toolbar.insert(separator, -1)
+ separator.show()