-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSPMainCore.py
1581 lines (1483 loc) · 70.2 KB
/
SPMainCore.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
# Copyright (c) 2006-2010 Stas Zykiewicz <[email protected]>
#
# SPMainCore.py
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 3 of the GNU General Public License
# as published by the Free Software Foundation. A copy of this license should
# be included in the file GPL-3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# The MainCoreCallbacks class, that's the thing that controls the events except
# activity stuff, is using ocempgui.
# The menu items are ocempgui buttons.
# Sprites are only used in the activities.
# The activity menu and the bottom menu bar are controlled by MainCoreGui
import sys
import gc
import atexit
import os
import pygame
import threading
import textwrap
import types
import configparser
import subprocess
import datetime
from pygame.constants import *
import time
#create logger, logger was configured in SPLogging
import logging
import importlib
module_logger = logging.getLogger("childsplay.SPMainCore")
import utils
###### for debugging memoryleaks ###########
#from guppy import hpy #
#h = hpy() #
############################################
from SPSpriteUtils import SPInit, MySprite, set_big_mouse_cursor, set_no_mouse_cursor, SPGroup
from SPGoodies import SPGoodies
# This is a kludge to execute the toplevel code in SPConstants as it is already
# imported in SPlogging before the loggers are set.
import SPConstants
importlib.reload(SPConstants)
from SPConstants import *
try:
import psyco #@UnresolvedImport
#psyco.log(PSYCOPATH, 'w')
psyco.profile()
except:
pass
else:
module_logger.debug("We got psyco, we should go faster, but report any weird crashes.")
import SPMenu
import SPDataManager as SPDataManager
import Version
import SPVideoPlayer
from SPWidgets import Init, Dialog, Label, MenuBar, VolumeAdjust, ExeCounter, \
Graph, TransImgButton
#from SPVirtualkeyboard import VirtualKeyboard
try:
from sqlalchemy import exceptions as sqlae
except ImportError:
from sqlalchemy import exc as sqlae
# Used to cleanup stuff when the Python vm ends
def cleanup():
module_logger.info("cleanup called.")
module_logger.info("flushing dbase to disk")
# flush dbase?
# remove any locks that been set
utils._remove_lock()
module_logger.info("logger stops.")
# add cleanup stuff in here or register it like the Activity class does
# index_off_php = os.path.join(WWWDIR, 'index_off.php')
# index_php = os.path.join(WWWDIR, 'index.php')
# if os.path.exists(index_php):
# os.remove(index_php)
# shutil.copy(index_off_php, index_php)
atexit.register(cleanup)
class MainEscapeKeyException(Exception):
"""Raised when the user decides to quit the maincore.
This is catched by schoolsplay.py to check if we quit or go back to the login"""
pass
class EscapeKeyException(Exception):
""" This is raised from the activity_loop when the user hits escape.
We basically using this exception as a signal"""
pass
class LevelsEndException(Exception):
""" This is raised from activity_loop when there are no more levels in the game."""
pass
class GDMEscapeKeyException(Exception):
""" This is raised from the activity_loop when the user hits escape.
We basically using this exception as a signal"""
pass
class MainCoreGui:
def __init__(self, resolution=(800, 600), dbmaker=None, options=None,
fullscr=None, mainscr=None, error=False,session_id=None,
start_splash=0, language='en'):
"""The main SP core.
The idea is that we have a menubar and a activity area.
The menu bar is the place for "our" gui widgets.
The activity doesn't draw in the menu bar and we don't come
into the activity area.
We call from the eventloop first some local checks like pressing escape etc.
Then we call the actives_group in which our activity sprites are
held.
Finally we call the the eventloop of the activity.
"""
self.logger = logging.getLogger("childsplay.SPMainCore.MainCoreGui")
self.logger.debug("Start")
self.myspritesactivated = None
self.menubuttons_backup = []
self.volume_level = 50
self.levelcount = 1
self.DTmode = False
self.DTactivity = None
self.DTlevelscounter = 1
self.DTlevelcount = 3
self.DTact_is_finished = None
self.COPmode = False
self.cmd_options = options
self.theme_rc = self.cmd_options.theme_rc
self.logger.debug("Found menu rc options %s" % self.theme_rc)
self.theme = self.cmd_options.theme
# language = self.cmd_options.lang
dbase = os.path.join(HOMEDIR, self.theme, 'quizcontent.db')
self.foreign_observers = []
# we setup the stuff we need for the stats table data.
# we collect evrything in a hash and when we done we put it into a
# dbase table.
self.statshash = {'session':session_id}
# get our base options from a rc file.
self.core_rc_path = os.path.join(ACTIVITYDATADIR, 'SPData', 'base', 'themes','core.rc')
self.logger.debug("Parsing core rc file %s" % self.core_rc_path)
d = {}
config = configparser.ConfigParser()
config.read(self.core_rc_path)
for k, v in dict(config.items(self.theme)).items():
d[k] = v
d['theme'] = self.theme
self.core_rc = d
self.logger.debug("found core rc options %s" % d)
if self.cmd_options.no_sound:
self.logger.info("Disabling sound support as requested by user")
pygame.mixer.quit()
# set mouse cursor ?
# TODO: use different cursors with an cmdline option
if self.cmd_options.bigcursor:
cursorfile = os.path.join(ACTIVITYDATADIR, 'SPData', 'base', 'icons', 'cursor_1.xbm')
maskfile = os.path.join(ACTIVITYDATADIR, 'SPData', 'base', 'icons', 'cursor_1-mask.xbm')
set_big_mouse_cursor(cursorfile, maskfile)
if self.cmd_options.nocursor:
set_no_mouse_cursor()
self.resolution = resolution
self.vtkb = None # will become the virtual keyboard object
## Set icon for the window manager ##
ICONPATH = os.path.join(ACTIVITYDATADIR, 'SPData', 'themes', self.theme)
DEFAULTICONPATH = os.path.join(ACTIVITYDATADIR, 'SPData', 'themes', 'childsplay')
# TODO:change icon files to the ones we would use
if sys.platform == 'darwin':
file = 'logo_cp_180x180.png'
elif sys.platform == 'win32':
file = 'logo_cp_64x64.png'
else:
file = 'logo_cp_32x32.xpm'
if not os.path.exists(os.path.join(ICONPATH, file)):
icon_path = os.path.join(DEFAULTICONPATH, file)
else:
icon_path = os.path.join(ICONPATH, file)
try:
surface = pygame.image.load(icon_path)
except pygame.error:
self.logger.info('Could not load image "%s"\n %s' % (file, pygame.get_error()))
else:
surface.set_colorkey(surface.get_at((0, 0)), RLEACCEL)
pygame.display.set_icon(surface)
# Thread lock, must be used by activities to be able to block everything.
self.lock = threading.Lock()
while start_splash + 3 > time.time():
time.sleep(0.1)
# setup main screen
if self.cmd_options.fullscreen:
if mainscr:
self.screen = mainscr
else:
self.screen = pygame.display.set_mode(self.resolution, FULLSCREEN)
else:
if mainscr:
self.screen = mainscr
else:
self.screen = pygame.display.set_mode(self.resolution)
if self.theme_rc['menubar_position'] == 'bottom':
y = 500
blitpos_y = 0
self.menu_rect = pygame.Rect(0, 500, 800, 100)
else:# we only have two options: top or bottom
y = 0
blitpos_y = 100
self.menu_rect = pygame.Rect(0, 0, 800, 100)
captxt = _("Childsplay_sp - educational activities, using theme:%s") % self.theme
if self.cmd_options.theme != 'childsplay':
captxt = captxt.replace('Childsplay_sp', self.cmd_options.theme)
pygame.display.set_caption(captxt)
self.backgroundimage = utils.load_image(os.path.join(ICONPATH, self.theme_rc['background']))
self.screen.blit(self.backgroundimage, (0, 0))
img = utils.load_image(os.path.join(ICONPATH, self.theme_rc['menubartop']))
self.backgroundimage.blit(img, (0, y))
self.screen.blit(self.backgroundimage, (0, 0))
self.backgr = self.screen.convert()# a real copy
# init SpriteUtils, mandatory when using SpriteUtils
self.actives_group = SPInit(self.screen, self.backgr, self.cmd_options.__dict__)
self.actives_group.set_onematch(True)
# Must be called before using SPWidgets
Init(self.theme)
# setup SPGoodies which we pass to the activities
# from here on we set various attributes for the spgoodies instance
self.screenclip = None
self.spgoodies = SPGoodies(self, self.screen, \
self.get_virtkb, \
language, \
self.lock, \
self.activity_pre_level_end, \
self.activity_level_end, \
self.activity_game_end, \
self.activity_info_dialog, \
self.display_execounter, \
self.set_framerate, \
self.enable_dice, \
self.set_dice_minimal_level, \
self.theme_rc, \
self.cmd_options.theme,\
self.disable_menubuttons, self.enable_menubuttons,\
self.disable_level_indicator,self.enable_level_indicator, \
self.disable_exit_button, self.enable_exit_button, \
self.disable_score_button, self.enable_score_button, \
self.hide_level_indicator, self.show_level_indicator, \
self.get_orm,self.register_foreign_observer)
# special goodies for the menu
self.spgoodies._menu_activity_userchoice = self._menu_activity_userchoice
self.spgoodies._cmd_options = self.cmd_options
self.spgoodies.stats_hash = self.statshash
# and one for the quizengine
self.spgoodies._unmute_quiz_voice = True
if error:
# there was an error in sp and the core is restarted
self.activity_info_dialog(_("An error occurred and BraintrainerPlus was restarted"))
# we get a datamanager which will start the SPgdm/BTP login screen, if available,
# and does some username and table checking.
try:
self.dm = SPDataManager.DataManager(self.spgoodies, dbmaker)
except sqlae.SQLAlchemyError:
self.logger.exception("Error while handling the dbase, try removing the existing dbase")
except utils.MyError as info:
self.logger.error("%s" % info)
raise utils.SPError
# # get locale setting from dbase and reset any locales already set to the
# # commandline language option
# if not self.cmd_options.lang:
# language = self.dm._get_language()
# self.spgoodies.localesetting = language
# else:
# language = (language, None)
# self.spgoodies.localesetting = language
# get session id, needed for the stats hash
# get user data
user_id = self.dm.get_user_id()
name = self.dm.get_username()
surf = utils.char2surf(name.replace('_', ' '), fcol=GREY ,fsize=12, bold=True)
self.backgroundimage.blit(surf,((800 - surf.get_width()) / 2 ,70))
orm, session = self.dm.get_orm('users', 'user')
row = session.query(orm).filter_by(user_id = user_id).first()
self.statshash['user_id'] = user_id
self.statshash['datetime'] = datetime.datetime.now()# we override it when something is loaded
# we now have user data so we check to see if we should set some custom stuff
# Set volume level
if not row:
self.logger.warning("Not found userdata for user id:%s" % user_id)
self.volume_level = 75
else:
self.volume_level = int(row.audio)
self.logger.debug("found volume setting %s for user id %s" % (self.volume_level, user_id))
try:
subprocess.Popen("amixer set Master %s" % self.volume_level + "%",shell=True)
cmd=subprocess.Popen("amixer get Master",shell=True, \
stdout=subprocess.PIPE, \
stderr=subprocess.PIPE)
output = cmd.communicate()[0]
except Exception as info:
module_logger.warning("program 'amixer' not found, unable to set volume levels: %s" % info)
self.volume_level = 75
else:
for line in output.split(b'\n'):
if b"%]" in line:
self.volume_level = int(line.split("%]")[0].split("[")[1])
# Check to see if today it's the user birthday
if row and row.birthdate:
name = "%s %s" % (row.title, row.last_name)
if not os.path.exists(os.path.join(TEMPDIR, 'birthday', name)):
date = row.birthdate
today = datetime.date.today()
if today.day == date.day and today.month == date.month:
age = int(today.year) - int(date.year)
try:
import birthday
except ImportError:
self.logger.warning("no birthday module found.")
else:
self.logger.debug("Start birthday show")
bd = birthday.birthday(self.screen, self.backgr,name, age)
bd.run()
f = open(os.path.join(TEMPDIR, 'birthday', name), 'w')
f.write('1')
f.close()
if row and row.levelup_dlg != 'true':
self.no_levelup_dlg = True
else:
self.no_levelup_dlg = False
if self.dm.are_we_cop():
self.COPmode = True
self.statshash['is_cop'] = True
# restore window title after the datamanager replaced it
pygame.display.set_caption(captxt)
if self.COPmode:
theme_dir = os.path.join('controlpanel_lgpl','lib', 'SPData')
xmlname = self.dm.are_we_cop()
self.logger.info("Using different xml for our menu, were in COP mode")
# we must rebuild the screen before we construct the menu
self.backgroundimage = utils.load_image\
(os.path.join(theme_dir, 'controlpanel_main.png'))
self.screen.blit(self.backgroundimage, (0, 0))
img = utils.load_image(os.path.join(theme_dir, 'controlpanel_top.png'))
self.backgroundimage.blit(img, (0, y))
self.screen.blit(self.backgroundimage, (0, 0))
self.backgr = self.screen.convert()# a real copy
# init SpriteUtils, mandatory when using SpriteUtils
self.actives_group = SPInit(self.screen, self.backgr)
self.actives_group.set_onematch(True)
# MustSPMainCore.py be called before using SPWidgets
Init(self.theme)
# and reset the screen used in spgoodies
self.spgoodies.screen = self.screen
else:
theme_dir = self.spgoodies.get_home_theme_path()
xmlname = 'SP_menu.xml'
session.close()
# We get the build menu activity here.
# check if we have local_quizdata, if not we remove the menu button
# orm, session = self.dm.get_orm('game_quizregional', 'content')
# row = session.query(orm).first()
# if row:
# showlocalquiz = True
# else:
# showlocalquiz = False
# session.close()
# orm, session = self.dm.get_orm('game_quizpersonal', 'content')
# row = session.query(orm).filter_by(user_id = user_id).first()
# if row:
# showpersonalquiz = True
# else:
# showpersonalquiz = False
# session.close()
showlocalquiz = False
showpersonalquiz = False
try:
self.activity = SPMenu.Activity(self.COPmode, showpersonalquiz, showlocalquiz)
except Exception as info:
self.logger.exception("Failed to setup the menu")
raise utils.MyError(info)
# First we parse the menu xml
self.activity._parse_menu(os.path.join(ACTIVITYDATADIR, 'SPData', 'themes', self.theme), xmlname)
# now we can build the menu buttons
if self.COPmode:
theme_dir = os.path.join('controlpanel_lgpl','lib', 'SPData')
else:
theme_dir = os.path.join(THEMESPATH, self.theme)
self.activity._build_menu(theme_dir, self.theme_rc, language[0])
r = pygame.Rect(0, blitpos_y, 800, 500)
# Add the rect to SPGoodies so that activities know where to blit.
self.spgoodies.screenrect = r
self.spgoodies.background = self.backgr
# now we can finish the menu object
self.activity._setup(self.spgoodies, self.dm, self.theme_rc['menubarbottom'])
# Setup the menubar
self.excludebuttons = self.theme_rc['exclude_buttons']
if self.theme_rc['level_indicator'] == 'star':
usestar = True
else:
usestar = False
if 'graph' in self.theme_rc['exclude_buttons']:
usegraph = False
else:
usegraph = True
self.menubar = MenuBar(self.menu_rect, \
self.actives_group, self.menubar_cbf,\
self.menubar_dice_cbf, self.lock, usestar, \
usegraph, volume_level=self.volume_level)
self.infobutton = self.menubar.get_infobutton()
if self.COPmode:
self.infobutton.moveto((CORE_BUTTONS_XCOORDS[7], self.menubar.get_buttons_posy()))
self.quitbutton = self.menubar.get_quitbutton()
self.scoredisplay = self.menubar.get_scoredisplay()
self.volumebutton = self.menubar.get_volumebutton()
self.menubarbuttons = [self.infobutton, self.quitbutton, self.volumebutton]
self.chartbutton = self.menubar.get_chartbutton()
self.dicebuttons = self.menubar.get_dicebuttons()
self.spgoodies.scoredisplay = self.scoredisplay
self.spgoodies.infobutton = self.infobutton
self.spgoodies.quitbutton = self.quitbutton
self.spgoodies.dm = self.dm
if not 'dice' in self.excludebuttons and not self.COPmode:
self.dicebuttons.UseDice = True
else:
self.dicebuttons.UseDice = False
if not 'graph' in self.excludebuttons and not self.COPmode:
self.menubarbuttons.append(self.chartbutton)
# disable the chartbutton
self.chartbutton.enable(False)
if not 'score' in self.excludebuttons and not self.COPmode:
self.menubarbuttons.append(self.scoredisplay)
self.actives_group.add(self.menubarbuttons)
# add the current user, if it exists
text = _("User: %s") % self.dm.get_username()
# TODO: Do we do an andmin mode ?
if self.cmd_options.adminmode:
text = text + " (Adminmode)"
if self.theme_rc['menubar_position'] == 'bottom':
y = 500
else:
y = 0
if not 'user' in self.excludebuttons:
self.usernamelabel = Label(text, (CORE_BUTTONS_XCOORDS[1], y+10))
else:
self.usernamelabel = None
if not 'activity' in self.excludebuttons:
text = _("Activity :")
self.activitylabel = Label(text, (CORE_BUTTONS_XCOORDS[1], y+50))
else:
self.activitylabel = None
# Show the mainscreen and menubar
self.clear_screen()
# setup misc stuff
self.clock = pygame.time.Clock()
self.run_activity_loop = True
self.run_event_loop = True
self.run_main_loop = True
self.framerate = 30
self.were_in_pre_level = False
self.store_data = True
if self.COPmode:
self.clear_screen()
self.disable_menubuttons()
self.scoredisplay.UseMe = False
self.enable_exit_button()
self.quitbutton.display_sprite()
def register_foreign_observer(self, obs):
"""You should register your observer that will take care of any cleanup
in case of an error"""
self.foreign_observers.append(obs)
def call_foreign_observers(self):
self.logger.warning("Calling foreign_observers to cleanup their own mess")
for obs in self.foreign_observers:
apply(obs)
def start(self):
# start menu
self.activity.start()
#For memoryleak debugging
#h.setrelheap()
self.start_main_loop()
def get_mainscreen(self):
return self.screen
def get_current_user(self):
return self.dm.get_username()
############################################################################
## Methods for internal use
def clear_screen(self):
self.logger.debug("clear_screen called")
# rebuild screen with the standard background
x, y = self.spgoodies.screenrect.left, self.spgoodies.screenrect.top
pygame.display.update(self.screen.blit(self.backgroundimage, (0, 0)))
self.backgr.blit(self.screen.convert(),(0, 0) )
# reset the screens in SPGoodies so that any activities gets a proper screen.
self.spgoodies.screen = self.screen
self.spgoodies.background = self.backgr
if self.COPmode:
self.quitbutton.display_sprite()
if self.activity.get_name() != 'menu':
self.enable_info_button()
self.infobutton.display_sprite()
else:
self.disable_info_button()
return
for b in self.menubarbuttons:
b.display_sprite()
if self.dicebuttons:
#self.dicebuttons.next_level(self.levelcount)
self.dicebuttons.show()
if self.usernamelabel:
self.usernamelabel.display_sprite()
if self.activitylabel:
self.activitylabel.display_sprite()
def load_activity(self, name):
# This will replace self.activity with the chosen activity
self.logger.debug("load_activity called with %s" % name)
self.set_framerate()
try:
if self.COPmode:
p = os.path.join('controlpanel_lgpl', 'lib')
else:
p = 'lib'
self.activity_module = utils.import_module(os.path.join(BASEDIR, p, name))
self.activity = self.activity_module.Activity(self.spgoodies)
if self.COPmode:
if self.activity.get_name() == 'menu':
self.disable_info_button()
else:
self.enable_info_button()
self.infobutton.display_sprite()
except (utils.MyError, ImportError, AttributeError) as info:
self.logger.exception("Error importing activity %s" % name)
dlg = Dialog(_("Error importing activity\n%s" % info),dialogwidth=500, \
buttons=[_("OK")], title=_('Error !'))
dlg.run()
self.activity = self.menu_saved
self.activity.refresh_sprites()
self.start_main_loop()
except (utils.MyError, Exception) as info:
self.logger.exception("Error constructing activity: %s" % info)
dlg = Dialog(_("Error constructing activity\n%s" % info), dialogwidth=500, \
buttons=[_("OK")], title=_('Error !'))
dlg.run()
self.activity = self.menu_saved
self.activity.refresh_sprites()
self.start_main_loop()
else:
self.logger.debug("Loaded %s activity succesfull" % self.activity)
text = _("Activity : %s") % self.activity.get_helptitle()
if self.activitylabel:
self.activitylabel.settext(text)
if self.activity.get_name() == 'dltr':
self.logger.debug("Got dltr, putting myself in DT mode")
self.DTmode = True
self.DTactivity = self.activity
mapper = self.dm.get_mapper(self.activity.get_name())
self.DTactivity._set_up(mapper)
self.hide_level_indicator()
self.enable_dice(False)
elif self.activity.get_name() == 'quiz_general':
self.logger.debug("Got the general, putting myself in DT mode")
self.DTmode = True
self.DTactivity = self.activity
mapper = self.dm.get_mapper(self.activity.get_name())
self.DTactivity._set_up(mapper)
self.DTlevelcount = 1
else:
self.enable_dice(True)
self.statshash['datetime'] = datetime.datetime.now()
self.statshash['activity_name'] = name
self.logger.debug("Resetting all sqla sessions")
self.dm.reset()
self.start_main_loop()
def start_start(self):
self.logger.debug("Calling activity start")
self.statshash['game_start_called'] = True
self.show_level_indicator()
self.levelcount = 1
self.store_data = True
try:
self.activity.start()
except Exception as info:
self.logger.exception("Error in %s start" % self.activity.get_name())
raise utils.MyError(info)
if 'graph' not in self.excludebuttons:
# as were datacollectors we also add a chart button to the menubar
self.chartbutton.enable(True)
def display_levelstart(self, level):
"""Displays a 'ready 3-2-1' screen prior to starting the level.
It also displays a blocking 'Start' button."""
self.logger.debug("Starting display_levelstart")
if self.cmd_options.no_text or self.cmd_options.nocountdown or self.DTmode:
return
org_screen = pygame.display.get_surface().convert()
org_backgr = self.backgr.convert()
self.screen.set_clip()
# disable the dice to prevent users clicking it
# dim the screen
sdim = utils.Dimmer(keepalive=0)
sdim.dim(darken_factor=200, color_filter=(51, 10, 10))
# display text
txt = _("Starting level %s") % level
txt_s0 = utils.char2surf(txt, TTFSIZE + 20, fcol=GREEN, ttf=TTF, bold=True)
self.screen.blit(txt_s0, ((780-txt_s0.get_width()) / 2, 100))
txt = self.activity.get_help()[1]
txt_s1 = utils.char2surf(txt, TTFSIZE + 2, fcol=GREEN, ttf=TTF, bold=True)
self.screen.blit(txt_s1, ((780-txt_s1.get_width()) / 2, 200))
txt = utils.txtfmt([_("Hit the 'space' key or a mousebutton to skip the countdown")], 60)
txtlist = []
for line in txt:
txtlist.append(utils.char2surf(line, TTFSIZE + 2, fcol=GREEN, ttf=TTF, bold=True))
y = 260
for s in txtlist:
self.screen.blit(s, (40, y))
y += s.get_height()
pygame.display.update()
# we have a animated sprite so we need to adjust the backgr screen
self.backgr.blit(self.screen, (0, 0))
# we re-init the sprite stuff as we use a different background
SPInit(self.screen, self.backgr)
# i is used as a counter. The loop runs at approx 30 times a second so
# when i == 30 we decrease the counter.
i = 0
counter = 4# used for 4-3-2-1
spr = None# will hold the sprite
# local eventloop
clock = pygame.time.Clock()
runloop = True
while runloop:
# runloop is set to False by the a event or when the counter == 0
pygame.event.pump()
events = pygame.event.get()
for event in events:
if event.type is KEYDOWN and event.key is K_SPACE or\
event.type is MOUSEBUTTONDOWN:
runloop = False
break
if i >= 29:
if spr:
spr.erase_sprite()
if counter == 0:
runloop = False
else:
num_s = utils.char2surf(str(counter), 72, fcol=RED, ttf=TTF, bold=True)
spr = MySprite(num_s)
spr.display_sprite((350, 300))
i = 0
counter -= 1
i += 1
clock.tick(30)
# restore the screens
# undim the screen
sdim.dim(darken_factor=255, color_filter=(0, 0, 0))
self.screen.blit(org_screen, (0, 0))
self.backgr.blit(org_backgr, (0, 0))
# reset the screens in SPGoodies so that any activities gets a proper screen.
SPInit(self.screen, self.backgr)
pygame.display.update()
def start_next_level(self, store_db=None, nextlevel=None, levelup=False, no_question=False):
"""store_db is used to signal normal ending and that we should store the data"""
self.logger.debug("start_next_level called with: %s, %s, %s,%s" % (store_db,nextlevel,levelup, no_question))
self.statshash['game_nextlevel_called'] = True
self.clear_screen()
self.screen.set_clip()
if nextlevel < 1:
nextlevel = 1
if nextlevel + (self.dicebuttons.get_minimal_level() - 1) > 6:
nextlevel = 6 - (self.dicebuttons.get_minimal_level() - 1)
self.logger.info("activity %s only has %s level, reset nextlevel to %s" % (self.activity.get_name(),\
self.dicebuttons.get_minimal_level() + 1,\
nextlevel))
if nextlevel and self.activity.get_name() != 'dltr':
if levelup and (self.levelcount + self.dicebuttons.get_minimal_level() -1) < 6:
if not no_question and not self.no_levelup_dlg:
dlg = Dialog(_("You play very good, do you want to try the next level ?"), \
buttons=[_("No"), _("Yes")], title=_('Question ?'), dialogwidth=300)
dlg.run()
c = dlg.get_result()[0]
else:
c = _('Yes')
if c == _('Yes'):
self.levelcount = nextlevel
else:
self.levelcount = nextlevel
if nextlevel and self.DTactivity and self.DTactivity.get_name() == 'dltr':
self.DTlevelcount = nextlevel
# are we in the data collecting bussiness ?
if self.activity.get_name() != 'menu':
# Here we fetch our data colletor object, we collect data per level,
# so it's the best place to start datacollecting
# get_mapper returns always an object, in case of anonymous mode the
# object is a fake.
self.mapper = self.dm.get_mapper(self.activity.get_name())
self.mapper.insert('level', self.levelcount)
self.mapper.insert('start_time', utils.current_time())
else:
self.mapper = self.dm.get_mapper(None)
try:
if self.activity.get_name() == 'menu':
self.activity.next_level(self.levelcount)
else:
if not self.menubuttons_backup:
if self.DTmode and self.DTactivity.get_name() == 'dltr':
level = self.DTlevelcount + (self.dicebuttons.get_minimal_level() -1)
else:
level = self.levelcount
if self.activity.get_name() not in ('dltr', 'quiz_general') and self.DTmode:
if self.DTlevelscounter > 0:
self.DTactivity.refresh_sprites()
self.logger.debug("Calling %s dailytraining_next_level, level:%s,counter:%s" % \
(self.activity.get_name(), self.DTlevelcount, self.DTlevelscounter))
result = self.activity.dailytraining_next_level(self.DTlevelcount, self.mapper)
self.DTlevelscounter -= 1
else:
self.DTact_is_finished = True
# we don't store the scores as it differs from normal act results
# the DT act stores the results
self.activity_game_end(store_db=False)
result = True
else:
self.logger.debug("Calling %s next_level, level:%s" % (self.activity.get_name(), self.levelcount))
result = self.activity.next_level(self.levelcount, self.mapper)
# we pass the db mapper so that the activity can store it's data
if not result:
if self.DTmode:
self.logger.debug("End the DT")
self.DTmode = False
self.DTactivity = None
self.logger.debug("start_activity_loop: Levels ended")
#self.activity_info_dialog(self.activity.get_helplevels())
self.activity_game_end(store_db=False)
else:
# We display a blocking ready-start screen
self.display_levelstart(self.levelcount)
# set the level indicator
self.dicebuttons.next_level(self.levelcount)
# and call the post_next_level
self.activity.post_next_level()
except MainEscapeKeyException:
self.activity.stop_timer()
raise MainEscapeKeyException
except utils.MyError as info:
self.logger.error("MyError in %s next_level" % self.activity.get_name())
self.activity.stop_timer()
raise utils.MyError(info)
except Exception as info:
self.logger.exception("Exception in %s next_level" % self.activity.get_name())
self.activity.stop_timer()
raise utils.MyError(info)
except Exception as info:
self.logger.exception("Exception in %s next_level" % self.activity.get_name())
self.activity.stop_timer()
raise utils.MyError(info)
def ask_exit(self):
self.logger.debug("ask_exit called")
if not self.cmd_options.noexitquestion:
dlg = Dialog(_("Do you really want to quit ?"), fsize=20, dialogwidth=300,\
buttons=[_("Cancel"), _("OK")], title=_('Quit ?'))
dlg.run()
c = dlg.get_result()[0]
self.activity.refresh_sprites()
else:
c = _("OK")
if c == _("OK"):
self.logger.info("User wants exit")
if self.activity.get_name() != 'menu':
try:
self.activity_game_end(store_db=False)
except:
pass
# any cleanup is done by atexit functions
# except this one :-)
self.dm._cleanup()
# dim the screen
sdim = utils.Dimmer(keepalive=0)
sdim.dim(darken_factor=200, color_filter=(51, 10, 10))
# display text
txt = utils.txtfmt([_("Stopping timers, please wait...")], 50)
y = 240
for line in txt:
s = utils.char2surf(line, TTFSIZE + 12, fcol=GREEN, ttf=TTF, bold=True)
self.screen.blit(s, (10, y))
y += s.get_height()
pygame.display.update()
self.store_stats()
#pygame.time.wait(1000)# just to show the text in case there are no timers
raise MainEscapeKeyException # let schoolsplay.py decide what next
def store_stats(self):
orm, session = self.spgoodies.get_orm('stats', 'user')
session.query(orm)
session.add(orm(**self.statshash))
session.commit()
session.close()
###########################################################################
# Main menubar button callbacks.
# These are called when the user hits the buttons in the lower menu bar of
# screen.
def menubar_cbf(self, sprite, event, data):
"""Callback function for the menubar buttons: quit, info, chart.
Here we decide which button has called us and we call the appropriate
method for further event handling."""
self.logger.debug("menubar_button_pressed: %s" % sprite.name)
if hasattr(self.activity, 'stop_sound'):
self.activity.stop_sound()
try:
if sprite.name == 'Info':
self.core_info_button_pressed()
sprite.mouse_hover_leave()
elif sprite.name == 'Quit':
sprite.mouse_hover_leave()
if self.DTmode and self.DTactivity.get_name() == 'dltr':
self.lock.acquire()
text = _("Are sure you want to quit the dailytraining ? All the results will be lost.")
dlg = Dialog(text, buttons=[_("No"), _("Yes")], title=_('Question ?'), dialogwidth=300)
dlg.run()
c = dlg.get_result()[0]
if c == _('No'):
self.lock.release()
return
raise utils.StopGameException
#self.core_quit_button_pressed()
elif sprite.name == 'Chart' and self.activity.get_name() != 'menu':
self.core_chart_button_pressed()
sprite.mouse_hover_leave()
elif sprite.name == 'Volume':
self.core_volume_button_pressed()
self.volumebutton.mouse_hover_leave()
except Exception as info:
self.logger.exception("System error: %s" % info)
self.lock.release()
self.clear_screen()
self.activity.refresh_sprites()
def menubar_dice_cbf(self, sprite, event, data):
"""Callback function for the menubar dicebutton."""
self.logger.debug("core_dice_button_pressed: %s" % data)
if hasattr(self.activity, 'stop_sound'):
self.activity.stop_sound()
self.myspritesactivated = True
if self.DTmode and self.DTactivity.get_name() != 'dltr':
self.DTactivity.act_hit_levelindicator(data)
self.DTlevelcount = data
if data:
if 1 > data > 6:
data = 1
if self.were_in_pre_level:
self.levelcount = data
else:
self.activity_level_end(nextlevel=data, score_audit=False)
else:
self.activity.refresh_sprites()
def core_info_button_pressed(self):
self.myspritesactivated = True
self.lock.acquire()
# Construct the help text from the activity's various get_help* methods
help = self.activity.get_help()
if self.activity.get_name() != 'menu' and not self.COPmode:
tip = self.activity.get_helptip()
if tip:
help.append(_("Tip:%s") % tip[0])
help.append(self.activity.get_helplevels())
#help.append(_("This activity belongs to the category: %s") % self.activity.get_helptype())
if hasattr(self.activity, 'get_extra_info'):
help.append("%s" % self.activity.get_extra_info())
buttons = [_("OK")]
if hasattr(self.activity, 'get_moviepath'):
if os.path.exists(self.activity.get_moviepath()):
buttons.append(_("Movie"))
# make sure all lines can fit the screen
newlines = utils.txtfmtlines(help, 80)
dlg = Dialog(newlines, dialogwidth=600, buttons=buttons, \
title= _("Information about %s") %
self.activity.get_helptitle())
dlg.run()
if dlg.result[0] == _("Movie"):
# play movie
self.logger.debug("Start videoplayer")
vpl = SPVideoPlayer.Player(os.path.join(THEMESPATH, self.theme,'btp_800x600.vlt'))
mv = self.activity.get_moviepath()
self.logger.debug("playing %s" % mv)
result = vpl.start(mv)
if not result[0]:
self.logger.error("Failed to start player, %s" % result[1])
self.activity.refresh_sprites()
self.lock.release()
return True
def core_volume_button_pressed(self):
self.myspritesactivated = True
self.lock.acquire()
dlg = Dialog('',dialogwidth=300, dialogheight=300, \
buttons=[_("Close")], title=_('Set volume level'))
r = dlg.get_action_area()['action_area']
x, y = r.topleft
x += 50
y += 30
va = VolumeAdjust((x, y), volume=int(self.volume_level), voice_unmute=self.spgoodies._unmute_quiz_voice)
va.set_use_current_background(True)# it will use the current screen as background as we blit over a dialog
va.display()
dlg.run(va.get_actives())
self.volume_level = va.get_volume()
self.spgoodies._unmute_quiz_voice = va.get_voice_state()
if hasattr(self.activity, 'quiz_voice_changed'):
self.activity.quiz_voice_changed(self.spgoodies._unmute_quiz_voice)
self.logger.debug("quiz voice unmute: %s" % self.spgoodies._unmute_quiz_voice)
pos = (CORE_BUTTONS_XCOORDS[7], self.menu_rect.top+8)
self.volumebutton.erase_sprite()
self.actives_group.remove(self.volumebutton)
if self.volume_level != 0:
bname = 'core_volume_button.png'
hbname = 'core_volume_button_ro.png'
p = os.path.join(ACTIVITYDATADIR, 'SPData', 'themes', self.theme, bname)
hp = os.path.join(ACTIVITYDATADIR, 'SPData', 'themes', self.theme, hbname)
self.volumebutton = TransImgButton(p, hp,pos, name='Volume')
else:
bname = 'core_volmute_button.png'
hbname = 'core_volmute_button_ro.png'
p = os.path.join(ACTIVITYDATADIR, 'SPData', 'themes', self.theme, bname)
hp = os.path.join(ACTIVITYDATADIR, 'SPData', 'themes', self.theme, hbname)
self.volumebutton = TransImgButton(p, hp,pos, name='Volume')
self.volumebutton.connect_callback(self.menubar_cbf, MOUSEBUTTONUP, 'Volume')
self.actives_group.add(self.volumebutton)
self.menubarbuttons[2] = self.volumebutton
self.logger.info("setting volume to %s" % self.volume_level)
orm, session = self.get_orm('users', 'user')
query = session.query(orm).filter_by(user_id = self.dm.get_user_id())
query.update({orm.audio:self.volume_level}, synchronize_session=False)
session.commit()
session.close()
self.lock.release()
return True
# Callback for the chart button in the menubar. Passed to SPCoreButtons.ChartButton
def core_chart_button_pressed(self):
if not hasattr(self, 'mapper'):
self.logger.warning("core_chart_button_pressed, no mapper found.")
return