1
# Copyright 2007 World Wide Workshop Foundation
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17
# If you find this activity useful or end up using parts of it in one of your
18
# own creations we would love to hear from you at info@WorldWideWorkshop.org !
23
import gtk, gobject, pango
25
from mamamedia_modules import utils
26
from mamamedia_modules import NotebookReaderWidget
27
from mamamedia_modules import BorderFrame, BORDER_ALL_BUT_BOTTOM, BORDER_ALL_BUT_LEFT
28
from mamamedia_modules import LanguageComboBox
29
from mamamedia_modules import ImageSelectorWidget
30
from mamamedia_modules import TimerWidget
31
from mamamedia_modules import CategorySelector
32
from mamamedia_modules import BuddyPanel
34
from mamamedia_modules import GAME_IDLE, GAME_STARTED, GAME_FINISHED, GAME_QUIT
36
#from utils import load_image, SliderCreator, GAME_IDLE, GAME_STARTED, GAME_FINISHED, GAME_QUIT, trace
38
#from mamamedia_ui import NotebookReaderWidget, BorderFrame, BORDER_ALL_BUT_BOTTOM, BORDER_ALL_BUT_LEFT
41
#from toolbar import SliderToolbar
42
#from i18n import LanguageComboBox
47
from SliderPuzzleWidget import SliderPuzzleWidget
53
from sugar.activity import activity
54
from sugar.graphics import units
67
#MYOWNPIC_FOLDER = os.path.expanduser("~/.sugar/default/org.worldwideworkshop.olpc.SliderPuzzle.MyOwnPictures")
68
# Colors from Rich's UI design
70
COLOR_FRAME_OUTER = "#B7B7B7"
71
COLOR_FRAME_GAME = "#FF0099"
72
COLOR_FRAME_THUMB = COLOR_FRAME_GAME
73
COLOR_FRAME_CONTROLS = "#FFFF00"
74
COLOR_BG_CONTROLS = "#66CC00"
76
(gtk.STATE_NORMAL,"#CCFF99"),
77
(gtk.STATE_ACTIVE,"#CCFF99"),
78
(gtk.STATE_PRELIGHT,"#CCFF99"),
79
(gtk.STATE_SELECTED,"#CCFF99"),
80
(gtk.STATE_INSENSITIVE,"#CCFF99"),
83
(gtk.STATE_NORMAL,"#027F01"),
84
(gtk.STATE_ACTIVE,"#014D01"),
85
(gtk.STATE_PRELIGHT,"#016D01"),
86
(gtk.STATE_SELECTED,"#027F01"),
87
(gtk.STATE_INSENSITIVE,"#CCCCCC"),
90
def prepare_btn(btn, w=-1, h=-1):
91
for state, color in COLOR_BG_BUTTONS:
92
btn.modify_bg(state, gtk.gdk.color_parse(color))
95
for state, color in COLOR_FG_BUTTONS:
96
c.modify_fg(state, gtk.gdk.color_parse(color))
98
for state, color in COLOR_FG_BUTTONS:
99
btn.modify_fg(state, gtk.gdk.color_parse(color))
101
btn.set_size_request(w, h)
105
class SliderPuzzleUI (gtk.Table):
106
__gsignals__ = {'game-state-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (int,))}
108
def __init__(self, parent):
109
super(SliderPuzzleUI, self).__init__(3,3,False)
110
self._parent = parent
112
# We want the translatables to be detected but not yet translated
115
self.labels_to_translate = []
117
self._state = GAME_IDLE
119
inner_table = gtk.Table(2,2,False)
120
self.add(inner_table)
122
self.game = SliderPuzzleWidget(9, GAME_SIZE, GAME_SIZE)
123
self.game.connect("solved", self.do_solve)
124
self.game.connect("moved", self.slider_move_cb)
125
self._parent.connect("key_press_event",self.game.process_key)
126
self._parent.connect("key_press_event",self.process_key)
128
desktop = BorderFrame(border_color=COLOR_FRAME_CONTROLS)
130
desktop.add(self.game)
131
self.game_wrapper = gtk.VBox()
132
self.game_wrapper.show()
135
#BorderFrame(border=BORDER_ALL_BUT_BOTTOM,
136
# border_color=COLOR_FRAME_CONTROLS,
137
# bg_color=COLOR_BG_CONTROLS)
138
inner.pack_start(desktop, expand=True, fill=False)
139
self.game_wrapper.pack_start(inner, expand=True, fill=False)
141
# panel is a holder for everything on the left side down to (not inclusive) the language dropdown
145
img_logo = gtk.Image()
146
img_logo.set_from_file("icons/logo.png")
148
panel.pack_start(img_logo, expand=False, fill=False)
150
# Control panel has the image controls
151
control_panel = BorderFrame(border=BORDER_ALL_BUT_BOTTOM,
152
border_color=COLOR_FRAME_CONTROLS,
153
bg_color=COLOR_BG_CONTROLS)
154
control_panel_box = gtk.VBox()
155
control_panel.add(control_panel_box)
158
spacer.set_size_request(-1, 5)
159
control_panel_box.pack_start(spacer, expand=False, fill=False)
164
btn_box = gtk.Table(1,5,False)
165
btn_box.set_col_spacings(5)
166
btn_box.set_row_spacings(5)
167
btn_box.attach(gtk.Label(), 0,1,0,2)
169
#spacer = gtk.Label()
170
#spacer.set_size_request(-1, 15)
171
#control_panel_box.pack_start(spacer, expand=False, fill=False)
172
#cutter = gtk.HBox(False, 8)
173
#cutter.pack_start(gtk.Label(), True)
174
self.btn_9 = prepare_btn(gtk.ToggleButton("9"),SLICE_BTN_WIDTH)
175
self.btn_9.set_active(True)
176
self.btn_9.connect("clicked", self.set_nr_pieces, 9)
177
btn_box.attach(self.btn_9, 1,2,0,1,0,0)
178
#cutter.pack_start(self.btn_9, False, False)
179
self.btn_12 = prepare_btn(gtk.ToggleButton("12"), SLICE_BTN_WIDTH)
180
self.btn_12.connect("clicked", self.set_nr_pieces, 12)
181
btn_box.attach(self.btn_12, 2,3,0,1,0,0)
182
#cutter.pack_start(self.btn_12, False, False)
183
self.btn_16 = prepare_btn(gtk.ToggleButton("16"), SLICE_BTN_WIDTH)
184
self.btn_16.connect("clicked", self.set_nr_pieces, 16)
185
btn_box.attach(self.btn_16, 3,4,0,1,0,0)
186
#cutter.pack_start(self.btn_16, False, False)
187
#cutter.pack_start(gtk.Label(), True)
188
#control_panel_box.pack_start(cutter, True)
189
#spacer = gtk.Label()
190
#spacer.set_size_request(-1, 10)
191
#control_panel_box.pack_start(spacer, False)
192
btn_box.attach(gtk.Label(), 4,5,0,1)
193
control_panel_box.pack_start(btn_box, expand=False)
199
self.thumb = ImageSelectorWidget(frame_color=COLOR_FRAME_THUMB, prepare_btn_cb=prepare_btn, image_dir='images')
200
self.thumb.connect("category_press", self.do_select_category)
201
self.thumb.connect("image_press", self.set_nr_pieces)
202
control_panel_box.pack_start(self.thumb, False)
205
spacer.set_size_request(-1, 5)
206
control_panel_box.pack_start(spacer, expand=False, fill=False)
208
# The game control buttons
209
btn_box = gtk.Table(3,3,False)
210
btn_box.set_row_spacings(2)
211
btn_box.attach(gtk.Label(), 0,1,0,3)
212
btn_box.attach(gtk.Label(), 2,3,0,3)
213
self.btn_solve = prepare_btn(gtk.Button(" "), 200)
214
self.labels_to_translate.append([self.btn_solve, _("Solve")])
215
self.btn_solve.connect("clicked", self.do_solve)
216
btn_box.attach(self.btn_solve, 1,2,0,1,0,0)
217
self.btn_shuffle = prepare_btn(gtk.Button(" "), 200)
218
self.labels_to_translate.append([self.btn_shuffle, _("Shuffle")])
219
self.btn_shuffle.connect("clicked", self.do_shuffle)
220
btn_box.attach(self.btn_shuffle, 1,2,1,2,0,0)
221
self.btn_add = prepare_btn(gtk.Button(" "), 200)
222
self.labels_to_translate.append([self.btn_add, _("My Picture")])
223
self.btn_add.connect("clicked", self.do_add_image)
224
btn_box.attach(self.btn_add, 1,2,2,3,0,0)
225
control_panel_box.pack_start(btn_box, False)
228
panel.pack_start(control_panel, expand=True, fill=True)
230
inner_table.attach(panel, 0,1,0,1,0)
232
self.game_box = BorderFrame(border_color=COLOR_FRAME_GAME)
233
self.game_box.add(self.game_wrapper)
234
inner_table.attach(self.game_box, 1,2,0,1, gtk.FILL, gtk.FILL)
236
lang_combo = prepare_btn(LanguageComboBox('org.worldwideworkshop.olpc.SliderPuzzle'))
237
lang_combo.connect('changed', self.do_select_language)
238
# Push the gettext translator into the global namespace
241
lang_box = BorderFrame(bg_color=COLOR_BG_CONTROLS,
242
border_color=COLOR_FRAME_CONTROLS)
243
hbox = gtk.HBox(False)
244
vbox = gtk.VBox(False)
245
vbox.pack_start(lang_combo, padding=8)
246
hbox.pack_start(vbox, padding=8)
248
inner_table.attach(lang_box, 0,1,1,2,gtk.FILL, gtk.FILL)
250
timer_box = BorderFrame(border=BORDER_ALL_BUT_LEFT,
251
bg_color=COLOR_BG_CONTROLS,
252
border_color=COLOR_FRAME_CONTROLS)
253
timer_hbox = gtk.HBox(False)
254
self.timer = TimerWidget(bg_color=COLOR_BG_BUTTONS[0][1],
255
fg_color=COLOR_FG_BUTTONS[0][1],
256
lbl_color=COLOR_BG_BUTTONS[1][1])
257
self.timer.set_sensitive(False)
258
self.timer.set_border_width(3)
259
self.labels_to_translate.append((self.timer, _("Time: ")))
260
timer_hbox.pack_start(self.timer, False, padding=8)
261
self.timer.connect('timer_toggle', self.timer_toggle_cb)
263
self.msg_label = gtk.Label()
264
self.msg_label.show()
265
timer_hbox.pack_start(self.msg_label, True)
267
self.btn_lesson = prepare_btn(gtk.Button(" "))
268
self.labels_to_translate.append([self.btn_lesson, _("Lesson Plans")])
269
self.btn_lesson.connect("clicked", self.do_lesson_plan)
270
timer_hbox.pack_start(self.btn_lesson, False, padding=8)
271
vbox = gtk.VBox(False)
272
vbox.pack_start(timer_hbox, padding=8)
274
inner_table.attach(timer_box, 1,2,1,2,gtk.FILL|gtk.EXPAND, gtk.FILL)
275
#panel.pack_start(lang_box, expand=False, fill=False)
277
self.do_select_language(lang_combo)
279
self.buddy_panel = BuddyPanel()
280
self.buddy_panel.show()
282
if not parent._shared_activity:
283
self.do_select_category(self)
285
self.set_message(_("Waiting for remote game..."))
288
self.set_contest_mode(False)
290
self._on_lesson_plan = False
292
def set_message (self, msg, frommesh=False):
293
if frommesh and self.get_game_state() < GAME_STARTED:
295
self.msg_label.set_label(msg)
297
def is_initiator (self):
298
return self._parent.initiating
300
def set_readonly (self, ro=True):
301
self.thumb.set_readonly(ro)
302
for b in (self.btn_9, self.btn_12, self.btn_16):
303
if not b.get_active():
304
b.set_sensitive(False)
307
def timer_toggle_cb (self, evt, running):
308
logging.debug("Timer running: %s" % str(running))
309
if self._contest_mode and running:
310
self.set_game_state(GAME_STARTED)
311
self._send_status_update()
312
#if self._contest_mode:
314
# if self.game.filename and not self.game_wrapper.get_parent():
315
# self.game_box.pop()
317
# if not self.buddy_panel.get_parent():
318
# self.game_box.push(self.buddy_panel)
320
def _set_control_area (self, *args):
321
""" The controls area below the logo needs different actions when in contest mode,
322
and also if we are the contest initiators or not. """
323
if self._contest_mode:
324
if self.get_game_state() > GAME_IDLE:
327
if self.is_initiator():
328
if self.timer.is_reset():
329
self.set_message(_("Select image and press Start Game..."))
331
self.set_game_state(GAME_STARTED)
333
self.set_message(_("Waiting for Puzzle image to be chosen..."))
334
self.set_button_translation(self.btn_add, "Buddies")
335
self.btn_add.get_child().set_label(_("Buddies"))
337
def set_game_state (self, state, force=False):
338
if state[0] > self._state[0] or force:
340
self.emit('game-state-changed', state[0])
341
self._set_control_area()
342
if state == GAME_STARTED:
343
self.set_button_translation(self.btn_add, "Buddies")
344
self.btn_add.get_child().set_label(_("Buddies"))
345
self._send_status_update()
347
def get_game_state (self):
350
def set_button_translation (self, btn, translation):
351
for i in range(len(self.labels_to_translate)):
352
if self.labels_to_translate[i][0] == btn:
353
self.labels_to_translate[i][1] = translation
356
def set_contest_mode (self, mode):
357
if getattr(self, '_contest_mode', None) != mode:
358
self._contest_mode = bool(mode)
359
self._set_control_area()
360
if self._contest_mode:
361
self.set_button_translation(self.btn_solve, "Give Up")
362
self.btn_solve.get_child().set_label(_("Give Up"))
363
self.set_button_translation(self.btn_shuffle, "Start Game")
364
self.btn_shuffle.get_child().set_label(_("Start Game"))
366
def is_contest_mode (self):
367
return self._contest_mode# and self.game.filename
369
def do_select_language (self, combo, *args):
370
self.selected_lang_details = combo.translations[combo.get_active()]
371
self.refresh_labels()
373
def refresh_labels (self, first_time=False):
374
self._parent.set_title(_("Slider Puzzle Activity"))
375
for lbl in self.labels_to_translate:
376
if isinstance(lbl[0], gtk.Button):
377
lbl[0].get_child().set_label(_(lbl[1]))
379
lbl[0].set_label(_(lbl[1]))
380
if not self.game_wrapper.get_parent() and not first_time:
382
if isinstance(self.game_box.get_child(), NotebookReaderWidget):
383
m = self.do_lesson_plan
385
m = self.do_select_category
389
def set_nr_pieces (self, btn, nr_pieces=None):
390
#if isinstance(btn, gtk.ToggleButton) and not btn.get_active():
392
if self.is_contest_mode() and isinstance(btn, gtk.ToggleButton) and nr_pieces == self.game.get_nr_pieces():
394
if isinstance(btn, gtk.ToggleButton):
395
if not btn.get_active():
396
if nr_pieces == self.game.get_nr_pieces():
400
if nr_pieces is None:
401
nr_pieces = self.game.get_nr_pieces()
402
if btn is None: #not isinstance(btn, gtk.ToggleButton):
403
if self._contest_mode:
404
self.set_game_state(GAME_STARTED)
405
for n, b in ((9, self.btn_9),(12, self.btn_12),(16, self.btn_16)):
406
if n == nr_pieces and not b.get_active():
408
b.set_sensitive(True)
411
if self.thumb.has_image():
412
if not self.game_wrapper.get_parent():
414
self.game.load_image(self.thumb.get_image())
415
#self.thumb.set_game_widget(self.game)
416
self.game.set_nr_pieces(nr_pieces)
417
self.timer.reset(False)
418
if isinstance(btn, gtk.ToggleButton):
419
for n, b in ((9, self.btn_9),(12, self.btn_12),(16, self.btn_16)):
423
b.set_sensitive(not self._contest_mode)
425
def do_shuffle (self, *args, **kwargs):
426
if self._contest_mode:
427
if self.get_game_state() > GAME_IDLE:
429
self.set_game_state(GAME_STARTED, True)
430
self._parent.frozen.thaw()
431
self.timer.reset(True)
432
elif self.game.filename is not None and self.timer.is_reset():
435
elif self.thumb.has_image():
436
if not self.game_wrapper.get_parent():
438
self.game.load_image(self.thumb.get_image())
439
#self.thumb.set_game_widget(self.game)
440
self.game.randomize()
441
self.timer.reset(False)
443
def slider_move_cb (self, *args):
444
if not self.timer.is_running():
447
def do_solve (self, btn):
448
if self.game.filename is not None:
449
if not self.game_wrapper.get_parent():
451
self.game.show_image()
452
self.timer.stop(True)
453
if self._contest_mode and self.get_game_state() == GAME_STARTED:
454
if btn != self.btn_solve:
455
self.set_game_state(GAME_FINISHED)
456
self.set_message(_("Puzzle Solved!"))
458
self.set_game_state(GAME_QUIT)
459
self.set_message(_("Gave Up"))
460
self._set_control_area()
463
# def do_select_category(self, owner, *args, **kwargs):
464
# if isinstance(owner, CategorySelector):
465
# self.thumb.set_image_dir(args[0])
466
# #self.game_box.pop()
467
# if not self.thumb.category.has_images():
468
# self.do_add_image(None)
470
# if self.game_wrapper.get_parent():
471
# s = CategorySelector("images", _("Choose a Subject"), self.thumb.get_image_dir())
472
# s.connect("selected", self.do_select_category)
474
# self.game_box.push(s)
477
# self.game_box.pop()
479
def do_select_category (self, o, *args):
480
if isinstance(o, CategorySelector):
481
self.thumb.set_image_dir(args[0])
482
#if not self.thumb.category.has_images():
483
# self.do_add_image(None)
485
if self.game_wrapper.get_parent():
486
print ("Current cat dir=", self.thumb.get_image_dir())
487
s = CategorySelector(_("Choose a Subject"),
488
self.thumb.get_image_dir(),
490
#extra=('images/Sequencing Puzzles',))
491
s.connect("selected", self.do_select_category)
493
self.game_box.push(s)
499
def do_add_image (self, widget, *args):
500
""" Use to trigger and process the My Own Image selector.
501
Also used for showing the buddies panel on contest mode"""
502
if self._contest_mode and self.get_game_state() >= GAME_STARTED:
504
if not self.buddy_panel.get_parent():
506
self.game_box.push(self.buddy_panel)
509
elif self._contest_mode and not self.is_initiator():
513
self.thumb.add_image()
516
#if response is None:
518
# # My Own Image selector
519
# imgfilter = gtk.FileFilter()
520
# imgfilter.set_name(_("Image Files"))
521
# imgfilter.add_mime_type('image/*')
522
# fd = gtk.FileChooserDialog(title=_("Select Image File"), parent=self._parent,
523
# action=gtk.FILE_CHOOSER_ACTION_OPEN,
524
# buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
526
# fd.set_current_folder(os.path.expanduser("~/"))
528
# fd.add_filter(imgfilter)
529
# fd.connect("response", self.do_add_image)
533
# if response == gtk.RESPONSE_ACCEPT:
534
# if self.thumb.load_image(widget.get_filename()):
537
# err = gtk.MessageDialog(self._parent, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
538
# _("Not a valid image file"))
544
def do_lesson_plan (self, btn):
545
if self._on_lesson_plan:
548
self._on_lesson_plan = True
549
if self._contest_mode and self.get_game_state() < GAME_STARTED:
551
if isinstance(self.game_box.get_child(), NotebookReaderWidget):
552
if self.game_box.get_child().loaded:
555
s = NotebookReaderWidget('lessons', self.selected_lang_details)
556
s.connect('parent-set', self.do_lesson_plan_reparent)
558
self.game_box.push(s)
561
self._on_lesson_plan = False
563
def do_lesson_plan_reparent (self, widget, oldparent):
564
if widget.parent is None:
565
self.set_button_translation(self.btn_lesson, "Lesson Plans")
566
self.btn_lesson.get_child().set_label(_("Lesson Plans"))
568
self.set_button_translation(self.btn_lesson, "Close Lesson")
569
self.btn_lesson.get_child().set_label(_("Close Lesson"))
571
def process_key (self, w, e):
572
""" The callback for key processing. The button shortcuts are all defined here. """
573
k = gtk.gdk.keyval_name(e.keyval)
574
if not isinstance(self._parent.get_focus(), gtk.Editable):
579
self.btn_12.clicked()
582
self.btn_16.clicked()
588
self.thumb.previous()
591
self.set_nr_pieces(None)
594
self.do_select_category(None)
597
self.btn_add.clicked()
600
self.btn_solve.clicked()
602
if k in ('Escape', 'q'):
608
def _freeze (self, journal=True):
609
""" returns a json writable object representation capable of being used to restore our current status """
610
return (self.thumb._freeze(), self.game._freeze(journal=journal), self.game.get_nr_pieces(), self.timer._freeze())
612
def _thaw (self, obj):
613
""" retrieves a frozen status from a python object, as per _freeze """
614
#self.thumb._thaw(obj[0])
615
if not obj[1].has_key('image'):
616
self.game.load_image(self.thumb.get_image())
617
self.set_nr_pieces(None, obj[2])
619
wimg = obj[1].has_key('image')
620
self.game._thaw(obj[1])
622
print "Forcing thumb image from the one in game"
623
self.thumb.load_pb(self.game.image)
624
self.timer._thaw(obj[3])
628
def _send_status_update (self):
629
""" Send a status update signal """
630
if self._parent._shared_activity:
631
if self.get_game_state() == GAME_STARTED:
632
if self.thumb.has_image():
633
self.set_message(_("Game Started!"))
634
self._parent.game_tube.StatusUpdate(self._state[1], self.timer.is_running(), self.timer.ellapsed())
637
win = gtk.Window(gtk.WINDOW_TOPLEVEL)
638
t = SliderPuzzleUI(win)
642
if __name__ == "__main__":