diff options
author | C. 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) |
commit | 507950fded36ace10787b7a091d3f7b59ebdb636 (patch) | |
tree | f478a0d99fdd0eb8178c021b25bcc28866b7166d /SliderPuzzleUI.py | |
parent | c11c1df2fc903160dc8e07541576fe751a44e171 (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.py | 361 |
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) |