Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/SliderPuzzleUI.py
diff options
context:
space:
mode:
authorC. Neves cn@sueste.net <cn@goat.(none)>2007-05-29 10:06:16 (GMT)
committer C Neves <cn@goat.(none)>2007-05-29 10:06:16 (GMT)
commit507950fded36ace10787b7a091d3f7b59ebdb636 (patch)
treef478a0d99fdd0eb8178c021b25bcc28866b7166d /SliderPuzzleUI.py
parentc11c1df2fc903160dc8e07541576fe751a44e171 (diff)
Added Button keyboard shortcuts.
Added category button on the image selector widget. Added Language selector dropdown, with flag icon and language name, that changes the UI translations on the fly. Clicking on image thumbnail jumbles that image.
Diffstat (limited to 'SliderPuzzleUI.py')
-rw-r--r--SliderPuzzleUI.py361
1 files changed, 280 insertions, 81 deletions
diff --git a/SliderPuzzleUI.py b/SliderPuzzleUI.py
index 34d1941..bf120e5 100644
--- a/SliderPuzzleUI.py
+++ b/SliderPuzzleUI.py
@@ -28,13 +28,24 @@ pygtk.require('2.0')
import gtk, gobject, pango
from utils import load_image
+from i18n import list_available_translations
-from gettext import gettext as _
+import logging
+import gettext
+import locale
from glob import glob
from SliderPuzzleWidget import SliderPuzzleWidget
from time import time
import os
+try:
+ from sugar.graphics.combobox import ComboBox
+ from sugar.graphics import units
+ _inside_sugar = True
+except:
+ _inside_sugar = False
+
+
BORDER_LEFT = 1
BORDER_RIGHT = 2
BORDER_TOP = 4
@@ -43,6 +54,8 @@ BORDER_VERTICAL = BORDER_TOP | BORDER_BOTTOM
BORDER_HORIZONTAL = BORDER_LEFT | BORDER_RIGHT
BORDER_ALL = BORDER_VERTICAL | BORDER_HORIZONTAL
+# This is me trying to get to the translation message bundles:
+
class BorderFrame (gtk.EventBox):
def __init__ (self, border=BORDER_ALL, size=5, color="#0000FF"):
gtk.EventBox.__init__(self)
@@ -99,7 +112,8 @@ class TimerWidget (gtk.HBox):
hb = gtk.HBox()
self.counter.add(hb)
self.pack_start(spacer, False)
- self.pack_start(gtk.Label(_("Time: ")), False)
+ self.lbl_time = gtk.Label()
+ self.pack_start(self.lbl_time, False)
#eb = gtk.EventBox()
self.prepare_icons()
@@ -114,6 +128,9 @@ class TimerWidget (gtk.HBox):
self.timer_id = None
self.finished = False
+ def set_label (self, label):
+ self.lbl_time.set_label(label)
+
def prepare_icons (self):
self.icons = []
self.icons.append(load_image("icons/circle-x.svg"))
@@ -165,7 +182,73 @@ class TimerWidget (gtk.HBox):
self.time_label.set_text("%i:%0.2i" % (t/60, t%60))
return True
+class CategoryDirectory (object):
+ def __init__ (self, path, width=-1, height=-1):
+ self.path = path
+ if os.path.isdir(path):
+ self.images = glob(os.path.join(path, "image_*"))
+ else:
+ self.images = [path]
+ self.set_thumb_size(32, 32)
+ self.set_image_size(width, height)
+ self.filename = None
+
+ def set_image_size (self, w, h):
+ self.width = w
+ self.height = h
+
+ def set_thumb_size (self, w, h):
+ self.twidth = w
+ self.theight = h
+ self.thumb = self._get_category_thumb()
+
+ def get_next_image (self):
+ if not len(self.images):
+ return None
+ if self.filename is None or self.filename not in self.images:
+ pos = -1
+ else:
+ pos = self.images.index(self.filename)
+ pos += 1
+ if pos >= len(self.images):
+ pos = 0
+ self.filename = self.images[pos]
+ return load_image(self.images[pos], self.width, self.height)
+
+ def get_previous_image (self):
+ if not len(self.images):
+ return None
+ if self.filename is None or self.filename not in self.images:
+ pos = len(self.images)
+ else:
+ pos = self.images.index(self.filename)
+ pos -= 1
+ if pos < 0:
+ pos = len(self.images) - 1
+ self.filename = self.images[pos]
+ return load_image(self.images[pos], self.width, self.height)
+
+ def has_images (self):
+ return len(self.images) > 0
+
+ def has_image (self):
+ return self.filename is not None
+
+ def _get_category_thumb (self):
+ if os.path.isdir(self.path):
+ thumbs = glob(os.path.join(self.path, "thumb.*"))
+ thumbs.extend(glob(os.path.join("..", os.path.join(self.path, "default_thumb.*"))))
+ thumbs = filter(lambda x: os.path.exists(x), thumbs)
+ thumbs.append(None)
+ else:
+ thumbs = [self.path]
+ return load_image(thumbs[0], self.twidth, self.theight)
+
+
class ImageSelectorWidget (gtk.Table):
+ __gsignals__ = {'category_press' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ 'image_press' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),}
+
def __init__ (self, width=-1, height=-1):
gtk.Table.__init__(self, 2,4,False)
self.width = width
@@ -174,65 +257,62 @@ class ImageSelectorWidget (gtk.Table):
img_box = BorderFrame()
img_box.add(self.image)
img_box.set_border_width(5)
- self.attach(img_box, 0,4,0,1,0,0)
+ img_box.connect('button_press_event', self.emit_image_pressed)
+ self.attach(img_box, 0,5,0,1,0,0)
self.attach(gtk.Label(), 0,1,1,2)
bl = gtk.Button()
bl.add(gtk.Arrow(gtk.ARROW_LEFT, gtk.SHADOW_IN))
bl.connect('clicked', self.previous)
- self.attach(bl, 1,2,1,2)
+ self.attach(bl, 1,2,1,2,0,0)
+
+ cteb = gtk.EventBox()
+ self.cat_thumb = gtk.Image()
+ self.cat_thumb.set_size_request(32,32)
+ cteb.add(self.cat_thumb)
+ cteb.connect('button_press_event', self.emit_cat_pressed)
+ self.attach(cteb, 2,3,1,2,0,0)
+
br = gtk.Button()
br.add(gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_IN))
br.connect('clicked', self.next)
- self.attach(br, 2,3,1,2)
- self.attach(gtk.Label(),3,4,1,2)
+ self.attach(br, 3,4,1,2,0,0)
+ self.attach(gtk.Label(),4,5,1,2)
self.filename = None
self.show_all()
self.image.set_size_request(width, height)
+ def emit_cat_pressed (self, *args):
+ self.emit('category_press')
+ return True
+
+ def emit_image_pressed (self, *args):
+ self.emit('image_press')
+ return True
+
+ def has_image (self):
+ return self.category.has_image()
+
+ def get_filename (self):
+ return self.category.filename
+
def next (self, *args, **kwargs):
- if not len(self.images):
- return
- if self.filename is None or self.filename not in self.images:
- pos = -1
- else:
- pos = self.images.index(self.filename)
- pos += 1
- if pos >= len(self.images):
- pos = 0
- self.load_image(self.images[pos])
+ self.image.set_from_pixbuf(self.category.get_next_image())
def previous (self, *args, **kwargs):
- if not len(self.images):
- return
- if self.filename is None or self.filename not in self.images:
- pos = len(self.images)
- else:
- pos = self.images.index(self.filename)
- pos -= 1
- if pos < 0:
- pos = len(self.images) - 1
- self.load_image(self.images[pos])
+ self.image.set_from_pixbuf(self.category.get_previous_image())
def set_image_dir (self, directory):
- self.images = glob(os.path.join(directory, "image_*"))
- if len(self.images):
- self.load_image(self.images[0])
-# else:
-# self.load_image("activity/activity-sliderpuzzle.svg")
+ self.category = CategoryDirectory(directory, self.width, self.height)
+ self.cat_thumb.set_from_pixbuf(self.category.thumb)
+ if self.category.has_images():
+ self.next()
def load_image(self, filename, force_filename=False):
""" Loads an image from the file """
- pb = self.image.get_pixbuf()
- self.image.set_from_pixbuf(load_image(filename, self.width, self.height))
- if self.image.get_pixbuf() is not None:
- if (len(self.images) or force_filename):
- self.filename = filename
- if force_filename:
- self.images = []
- return True
- else:
- self.image.set_from_pixbuf(pb)
- return False
+ self.category = CategoryDirectory(filename, self.width, self.height)
+ self.next()
+ self.cat_thumb.set_from_pixbuf(self.category.thumb)
+ return self.image.get_pixbuf() is not None
class CategorySelector (gtk.ScrolledWindow):
__gsignals__ = {'selected' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,))}
@@ -245,7 +325,7 @@ class CategorySelector (gtk.ScrolledWindow):
self.thumbs = []
model = self.get_model(path)
- treeview = gtk.TreeView()
+ self.treeview = gtk.TreeView()
col = gtk.TreeViewColumn(title)
r1 = gtk.CellRendererPixbuf()
r2 = gtk.CellRendererText()
@@ -253,14 +333,18 @@ class CategorySelector (gtk.ScrolledWindow):
col.pack_start(r2, True)
col.set_cell_data_func(r1, self.cell_pb)
col.set_attributes(r2, text=1)
- treeview.append_column(col)
- treeview.set_model(model)
+ self.treeview.append_column(col)
+ self.treeview.set_model(model)
- treeview.connect("cursor-changed", self.do_select)
- self.add(treeview)
+ self.treeview.connect("cursor-changed", self.do_select)
+ self.add(self.treeview)
self.show_all()
+ def grab_focus (self):
+ self.treeview.grab_focus()
+
def cell_pb (self, tvcolumn, cell, model, it):
+ # Renders a pixbuf stored in the thumbs cache
cell.set_property('pixbuf', self.thumbs[model.get_value(it, 2)])
def get_pb (self, path):
@@ -286,37 +370,50 @@ class CategorySelector (gtk.ScrolledWindow):
class SliderPuzzleUI:
def __init__(self, parent):
+ # Add our own icons here, needed for the translation flags
+ theme = gtk.icon_theme_get_default()
+ theme.append_search_path(os.path.join(os.getcwd(), 'icons'))
+ logging.debug("GTK Theme path: %s" % (str(gtk.icon_theme_get_default().get_search_path())))
+ # We want the translatables to be detected but not yet translated
+ global _
+ _ = lambda x: x
+ self.labels_to_translate = []
+
# Basic window settings
self.window = parent
- #settings = self.window.get_settings()
- #settings.set_string_property("gtk-font-name", "sans bold 10", "SliderPuzzleUI")
- self.window.set_title(_("Slider Puzzle Activity"))
+ if _inside_sugar:
+ self.update_toolbar()
+ else:
+ gettext.bindtextdomain('org.worldwideworkshop.olpc.SliderPuzzle', 'locale')
+ gettext.textdomain('org.worldwideworkshop.olpc.SliderPuzzle')
bgcolor = gtk.gdk.color_parse("#DDDD40")
# The actual game widget
self.game = SliderPuzzleWidget(9, 480, 480)
self.game.connect("solved", self.do_solve)
self.window.connect("key_press_event",self.game.process_key)
+ self.window.connect("key_press_event",self.process_key)
# The image selector with thumbnail
self.thumb = ImageSelectorWidget(200, 200)
self.thumb.set_image_dir("images")
- self.thumb.connect("button_press_event", self.do_select_category)
+ self.thumb.connect("category_press", self.do_select_category)
+ self.thumb.connect("image_press", self.set_nr_pieces, None)
#self.thumb.load_image("images/image_XO.svg")
# Buttons for selecting the number of pieces
cutter = gtk.VBox()
- btn_9 = gtk.Button("9")
- btn_9.set_size_request(50,-1)
- btn_9.connect("clicked", self.set_nr_pieces, 9)
- cutter.add(btn_9)
- btn_12 = gtk.Button("12")
- btn_12.connect("clicked", self.set_nr_pieces, 12)
- cutter.add(btn_12)
- btn_16 = gtk.Button("16")
- btn_16.connect("clicked", self.set_nr_pieces, 16)
- cutter.add(btn_16)
+ self.btn_9 = gtk.Button("9")
+ self.btn_9.set_size_request(50,-1)
+ self.btn_9.connect("clicked", self.set_nr_pieces, 9)
+ cutter.add(self.btn_9)
+ self.btn_12 = gtk.Button("12")
+ self.btn_12.connect("clicked", self.set_nr_pieces, 12)
+ cutter.add(self.btn_12)
+ self.btn_16 = gtk.Button("16")
+ self.btn_16.connect("clicked", self.set_nr_pieces, 16)
+ cutter.add(self.btn_16)
# Thumb box has both the image selector and the number of pieces buttons
thumb_box = gtk.Table(1,2)
@@ -328,21 +425,25 @@ class SliderPuzzleUI:
buttons_box.modify_bg(gtk.STATE_NORMAL, bgcolor)
inner_buttons_box = gtk.VBox(False, 5)
inner_buttons_box.set_border_width(10)
- btn_add = gtk.Button(_("My Own Picture"))
- btn_add.connect("clicked", self.do_add_image)
- inner_buttons_box.add(btn_add)
- btn_solve = gtk.Button(_("Solve"))
- btn_solve.connect("clicked", self.do_solve)
- inner_buttons_box.add(btn_solve)
- btn_jumble = gtk.Button(_("Jumble"))
- btn_jumble.connect("clicked", self.do_jumble)
- inner_buttons_box.add(btn_jumble)
+ self.btn_add = gtk.Button()
+ self.labels_to_translate.append((self.btn_add, _("My Own Picture")))
+ self.btn_add.connect("clicked", self.do_add_image)
+ inner_buttons_box.add(self.btn_add)
+ self.btn_solve = gtk.Button()
+ self.labels_to_translate.append((self.btn_solve, _("Solve")))
+ self.btn_solve.connect("clicked", self.do_solve)
+ inner_buttons_box.add(self.btn_solve)
+ self.btn_jumble = gtk.Button()
+ self.labels_to_translate.append((self.btn_jumble, _("Jumble")))
+ self.btn_jumble.connect("clicked", self.do_jumble)
+ inner_buttons_box.add(self.btn_jumble)
buttons_box.add(inner_buttons_box)
# The timer widget
self.timer = TimerWidget()
self.timer.modify_bg(gtk.STATE_NORMAL, bgcolor)
self.timer.set_border_width(3)
+ self.labels_to_translate.append((self.timer, _("Time: ")))
# Everything on the left side of the game widget goes here
event_controls_box = gtk.EventBox()
@@ -368,34 +469,94 @@ class SliderPuzzleUI:
outter.attach(inner, 1,2,1,2,0,0)
outter.attach(gtk.Label(), 0,3,2,3)
- try:
- # This fails if testing outside Sugar
+ if _inside_sugar:
self.window.set_canvas(outter)
- except:
+ else:
self.window.add(outter)
self.window.show_all()
self.do_select_category(self)
+
+ # Push the gettext translator into the global namespace
+ del _
+ if _inside_sugar:
+ self.do_select_language(self.tb_lang_select)
+ else:
+ _ = gettext.gettext
+ self.refresh_labels()
#self.timer.start()
- def set_nr_pieces (self, btn, nr_pieces):
- if self.thumb.filename:
+
+ def update_toolbar (self):
+ """ This method lists all available translations in a ComboBox widget.
+ The first translation is English, for that is the 'no translation' language in the code."""
+ self.translations = list_available_translations()
+ logging.debug(self.translations)
+ self.tb_lang_select = ComboBox()
+ for i,x in enumerate(self.translations):
+ self.tb_lang_select.append_item(i+1,gettext.gettext(x.name), x.image)
+ self.tb_lang_select.connect('changed', self.do_select_language)
+ # Attach the ComboBox to Sugar activity default toolbar
+ toolbox = self.window.toolbox
+ toolbar = toolbox.get_activity_toolbar()
+ tool_item = gtk.ToolItem()
+ tool_item.set_expand(False)
+ tool_item.add(self.tb_lang_select)
+ self.tb_lang_select.show()
+ toolbar.insert(tool_item, 2)
+ tool_item.show()
+ self.tb_lang_select._icon_renderer.props.stock_size = 3
+
+ def do_select_language (self, combo, *args):
+ if combo.get_active() > -1:
+ self.translations[combo.get_active()].install()
+ else:
+ code, encoding = locale.getdefaultlocale()
+ # Try to find the exact translation
+ for i,t in enumerate(self.translations):
+ if t.matches(code):
+ combo.set_active(i)
+ break
+ if combo.get_active() < 0:
+ # Failed, try to get the translation based only in the country
+ for i,t in enumerate(self.translations):
+ if t.matches(code, False):
+ combo.set_active(i)
+ break
+ if combo.get_active() < 0:
+ # nothing found, select first translation
+ combo.set_active(0)
+ self.refresh_labels()
+
+ def refresh_labels (self):
+ logging.debug(str(_))
+ self.window.set_title(_("Slider Puzzle Activity"))
+ for lbl in self.labels_to_translate:
+ lbl[0].set_label(_(lbl[1]))
+ if not self.game.get_parent():
+ self.game_box.pop()
+ self.do_select_category(self)
+
+ def set_nr_pieces (self, btn, nr_pieces=None):
+ if nr_pieces is None:
+ nr_pieces = self.game.get_nr_pieces()
+ if self.thumb.has_image():
if not self.game.get_parent():
self.game_box.pop()
- self.game.load_image(self.thumb.filename)
+ self.game.load_image(self.thumb.get_filename())
self.game.set_nr_pieces(nr_pieces)
self.timer.reset()
def do_jumble (self, *args, **kwargs):
- if self.thumb.filename:
+ if self.thumb.has_image():
if not self.game.get_parent():
self.game_box.pop()
- self.game.load_image(self.thumb.filename)
+ self.game.load_image(self.thumb.get_filename())
self.game.randomize()
self.timer.reset()
def do_solve (self, btn):
- if self.thumb.filename:
+ if self.thumb.has_image():
if not self.game.get_parent():
self.game_box.pop()
self.game.show_image()
@@ -411,6 +572,7 @@ class SliderPuzzleUI:
s.connect("selected", self.do_select_category)
s.show()
self.game_box.push(s)
+ s.grab_focus()
else:
self.game_box.pop()
@@ -419,7 +581,7 @@ class SliderPuzzleUI:
imgfilter = gtk.FileFilter()
imgfilter.set_name(_("Image Files"))
imgfilter.add_mime_type('image/*')
- fd = gtk.FileChooserDialog(title=("Select Image File"), parent=self.window, action=gtk.FILE_CHOOSER_ACTION_OPEN,
+ fd = gtk.FileChooserDialog(title=_("Select Image File"), parent=self.window, action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
fd.set_current_folder(os.path.expanduser("~/"))
fd.set_modal(True)
@@ -428,7 +590,7 @@ class SliderPuzzleUI:
fd.show()
else:
if response == gtk.RESPONSE_ACCEPT:
- if self.thumb.load_image(widget.get_filename(), True):
+ if self.thumb.load_image(widget.get_filename()):
self.do_jumble()
else:
err = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
@@ -438,6 +600,43 @@ class SliderPuzzleUI:
return
widget.destroy()
+ def process_key (self, w, e):
+ """ The callback for key processing. The button shortcuts are all defined here. """
+ k = gtk.gdk.keyval_name(e.keyval)
+ if not isinstance(self.window.get_focus(), gtk.Editable):
+ if k == '1':
+ self.btn_9.clicked()
+ return True
+ if k == '2':
+ self.btn_12.clicked()
+ return True
+ if k == '3':
+ self.btn_16.clicked()
+ return True
+ if k == 'period':
+ self.thumb.next()
+ return True
+ if k == 'comma':
+ self.thumb.previous()
+ return True
+ if k == 'Return':
+ self.set_nr_pieces(None)
+ return True
+ if k == 'slash':
+ self.do_select_category(None)
+ return True
+ if k == 'question':
+ self.btn_add.clicked()
+ return True
+ if k == 'equal':
+ self.btn_solve.clicked()
+ return True
+ if k in ('Escape', 'q'):
+ gtk.main_quit()
+ return True
+ #logging.debug("%s %s %s" % (str(self.window.get_focus()), str(isinstance(self.window.get_focus(), gtk.Editable)), str(k)))
+ return False
+
def main():
win = gtk.Window(gtk.WINDOW_TOPLEVEL)
t = SliderPuzzleUI(win)