def _make_collaboration(self): def on_activity_joined_cb(me): self._say('%.6f activity joined\n' % (time.time())) self.connect('joined', on_activity_joined_cb) def on_activity_shared_cb(me): self._say('%.6f activity shared\n' % (time.time())) self.connect('shared', on_activity_shared_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) def on_joined_cb(collab, msg): self._say('%.6f joined\n' % (time.time())) self._collab.connect('joined', on_joined_cb, 'joined') def on_buddy_joined_cb(collab, buddy, msg): self._say('%.6f buddy-joined %s@%s\n' % (time.time(), buddy.props.nick, buddy.props.ip4_address)) self._collab.connect('buddy_joined', on_buddy_joined_cb, 'buddy_joined') def on_buddy_left_cb(collab, buddy, msg): self._say('%.6f buddy-left %s@%s\n' % (time.time(), buddy.props.nick, buddy.props.ip4_address)) self._collab.connect('buddy_left', on_buddy_left_cb, 'buddy_left') self._collab.setup()
def __init__(self, handle): super(PhysicsActivity, self).__init__(handle) self._collab = CollabWrapper(self) self._collab.message.connect(self.__message_cb) self.metadata['mime_type'] = 'application/x-physics-activity' self.add_events(Gdk.EventMask.ALL_EVENTS_MASK | Gdk.EventMask.VISIBILITY_NOTIFY_MASK) self.connect('visibility-notify-event', self._focus_event) self.connect('window-state-event', self._window_event) self.game = PhysicsGame(self) self.game.canvas = sugargame.canvas.PygameCanvas( self, main=self.game.run, modules=[pygame.display, pygame.font]) self.preview = None self._sample_window = None self._notebook = Gtk.Notebook(show_tabs=False) self._notebook.add(self.game.canvas) w = Gdk.Screen.width() h = Gdk.Screen.height() - 2 * GRID_CELL_SIZE self.game.canvas.set_size_request(w, h) self._constructors = {} self.build_toolbar() self.set_canvas(self._notebook) Gdk.Screen.get_default().connect('size-changed', self.__configure_cb) self.show_all() self._collab.setup()
def __init__(self, handle): """ Initialize the toolbars and the game board """ super(FlipActivity, self).__init__(handle) self.nick = profile.get_nick_name() if profile.get_color() is not None: self.colors = profile.get_color().to_string().split(',') else: self.colors = ['#A0FFA0', '#FF8080'] self._setup_toolbars() self._setup_dispatch_table() # Create a canvas canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() self.show_all() self._game = Game(canvas, parent=self, colors=self.colors) self.connect('shared', self._shared_cb) self.connect('joined', self._joined_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) self._collab.connect('joined', self._joined_cb) self._collab.setup() if 'dotlist' in self.metadata: self._restore() else: self._game.new_game()
def _setup_collab(self): """ Setup the Presence Service. """ self.initiating = None # sharing (True) or joining (False) self.connect('shared', self._shared_cb) self.connect('joined', self._joined_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) self._collab.connect('joined', self._joined_cb) self._collab.setup()
def _new_tube_cb(self, id, initiator, type, service, params, state): ''' Create a new tube. ''' _logger.debug('New tube: ID=%d initator=%d type=%d service=%s' ' params=%r state=%d' % (id, initiator, type, service, params, state)) if (type == TelepathyGLib.TubeType.DBUS and service == SERVICE): if state == TelepathyGLib.TubeState.LOCAL_PENDING: self.tubes_chan[ TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].AcceptDBusTube(id) self.collab = CollabWrapper(self) self.collab.message.connect(self.event_received_cb) self.collab.setup()
def _make_collaboration(self): def on_activity_joined_cb(me): self._say('%.6f activity joined\n' % (time.time())) self.connect('joined', on_activity_joined_cb) def on_activity_shared_cb(me): self._say('%.6f activity shared\n' % (time.time())) self.connect('shared', on_activity_shared_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) def on_joined_cb(collab, msg): self._say('%.6f joined\n' % (time.time())) self._collab.connect('joined', on_joined_cb, 'joined') def on_buddy_joined_cb(collab, buddy, msg): self._say('%.6f buddy-joined %s@%s\n' % (time.time(), buddy.props.nick, buddy.props.ip4_address)) self._collab.connect('buddy_joined', on_buddy_joined_cb, 'buddy_joined') def on_buddy_left_cb(collab, buddy, msg): self._say('%.6f buddy-left %s@%s\n' % (time.time(), buddy.props.nick, buddy.props.ip4_address)) self._collab.connect('buddy_left', on_buddy_left_cb, 'buddy_left') def on_incoming_file_cb(collab, ft, desc): self._say('%.6f incoming-file %r\n' % (time.time(), desc)) def on_ready_cb(ft, stream): stream.close(None) gbytes = stream.steal_as_bytes() data = gbytes.get_data() self._say('%.6f data %r\n' % (time.time(), data)) ft.connect('ready', on_ready_cb) ft.accept_to_memory() self._collab.connect('incoming_file', on_incoming_file_cb) self._collab.setup() self._last_buddy = None
def __init__(self, handle): "The entry point to the Activity" activity.Activity.__init__(self, handle) self._timeout = None self.accelerometer = False try: open(ACCELEROMETER_DEVICE).close() self.accelerometer = True except: pass if not self.accelerometer and not self.shared_activity: return self._incompatible() self.buddies = {} canvas = MyCanvas(self) self.set_canvas(canvas) canvas.show() toolbar_box = ToolbarBox() self.set_toolbar_box(toolbar_box) toolbar_box.toolbar.insert(ActivityButton(self), 0) toolbar_box.toolbar.insert(TitleEntry(self), -1) toolbar_box.toolbar.insert(DescriptionItem(self), -1) toolbar_box.toolbar.insert(ShareButton(self), -1) separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) toolbar_box.toolbar.insert(StopButton(self), -1) toolbar_box.show_all() self._udp = Udp() self.hosts = {} self._collab = CollabWrapper(self) self._collab.message.connect(self.__message_cb) self._collab.buddy_joined.connect(self.__buddy_joined_cb) self._collab.buddy_left.connect(self.__buddy_left_cb) self._collab.setup() self._fuse = 1 self._timeout = GLib.timeout_add(100, self._timeout_cb, canvas)
def __init__(self, handle): Activity.__init__(self, handle) self._joining_hide = False self._game = ImplodeGame() self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) game_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) game_box.pack_start(self._game, True, True, 0) self._stuck_strip = _StuckStrip() self._configure_toolbars() self.set_canvas(game_box) # Show everything except the stuck strip. self.show_all() self._configure_cb() game_box.pack_end(self._stuck_strip, expand=False, fill=False, padding=0) self._game.connect('show-stuck', self._show_stuck_cb) self._game.connect('piece-selected', self._piece_selected_cb) self._game.connect('undo-key-pressed', self._undo_key_pressed_cb) self._game.connect('redo-key-pressed', self._redo_key_pressed_cb) self._game.connect('new-key-pressed', self._new_key_pressed_cb) self._stuck_strip.connect('undo-clicked', self._stuck_undo_cb) game_box.connect('key-press-event', self._key_press_event_cb) self._game.grab_focus() last_game_path = self._get_last_game_path() if os.path.exists(last_game_path): self.read_file(last_game_path) self._collab.setup() # Hide the canvas when joining a shared activity if self.shared_activity: if not self.get_shared(): self.get_canvas().hide() self.busy() self._joining_hide = True
def __init__(self, handle): """ Initialize the toolbars and the game board """ activity.Activity.__init__(self, handle) self.nick = profile.get_nick_name() if profile.get_color() is not None: self.colors = profile.get_color().to_string().split(',') else: self.colors = ['#A0FFA0', '#FF8080'] self.level = 0 self._correct = 0 self._playing = True self._game_over = False self._python_code = None self._setup_toolbars() self._setup_dispatch_table() # Create a canvas canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() self.show_all() self._game = Game(canvas, parent=self, colors=self.colors) self._sharing = False self._initiating = False self.connect('shared', self._shared_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) self._collab.connect('joined', self._joined_cb) self._collab.setup() if 'level' in self.metadata: self.level = int(self.metadata['level']) self.status.set_label(_('Resuming level %d') % (self.level + 1)) self._game.show_random() else: self._game.new_game()
def __init__(self, handle): ''' Initialize the Sugar activity ''' super(Dimensions, self).__init__(handle) self.ready_to_play = False self.initiating = False self._prompt = '' self._read_journal_data() self._sep = [] self._setup_toolbars() self._setup_canvas() self.connect('shared', self._shared_cb) if not hasattr(self, '_saved_state'): self._saved_state = None self.vmw.new_game(show_selector=True) else: self.vmw.new_game(saved_state=self._saved_state, deck_index=self._deck_index) self.ready_to_play = True if self._editing_word_list: self.vmw.editing_word_list = True self.vmw.edit_word_list() elif self._editing_custom_cards: self.vmw.editing_custom_cards = True self.vmw.edit_custom_card() Gdk.Screen.get_default().connect('size-changed', self._configure_cb) self._configure_cb(None) self._collab = CollabWrapper(self) self._collab.buddy_joined.connect(self._buddy_joined_cb) self._collab.buddy_left.connect(self._buddy_left_cb) self._collab.joined.connect(self._joined_cb) self._collab.message.connect(self._message_cb) self._collab.setup()
class Dimensions(activity.Activity): ''' Dimension matching game ''' def __init__(self, handle): ''' Initialize the Sugar activity ''' super(Dimensions, self).__init__(handle) self.ready_to_play = False self.initiating = False self._prompt = '' self._read_journal_data() self._sep = [] self._setup_toolbars() self._setup_canvas() self.connect('shared', self._shared_cb) if not hasattr(self, '_saved_state'): self._saved_state = None self.vmw.new_game(show_selector=True) else: self.vmw.new_game(saved_state=self._saved_state, deck_index=self._deck_index) self.ready_to_play = True if self._editing_word_list: self.vmw.editing_word_list = True self.vmw.edit_word_list() elif self._editing_custom_cards: self.vmw.editing_custom_cards = True self.vmw.edit_custom_card() Gdk.Screen.get_default().connect('size-changed', self._configure_cb) self._configure_cb(None) self._collab = CollabWrapper(self) self._collab.buddy_joined.connect(self._buddy_joined_cb) self._collab.buddy_left.connect(self._buddy_left_cb) self._collab.joined.connect(self._joined_cb) self._collab.message.connect(self._message_cb) self._collab.setup() def get_data(self): return None def set_data(self, data): pass def _select_game_cb(self, button, card_type): ''' Choose which game we are playing. ''' if self.vmw.joiner(): # joiner cannot change level return self._prompt = PROMPT_DICT[card_type] self._load_new_game(card_type) def _load_new_game(self, card_type=None, show_selector=True): if not self.ready_to_play: return # self._notify_new_game(self._prompt) GLib.idle_add(self._new_game, card_type, show_selector) def _new_game(self, card_type, show_selector=True): if card_type == 'custom' and self.vmw.custom_paths[0] is None: self.image_import_cb() else: self.tools_toolbar_button.set_expanded(False) self.vmw.new_game(show_selector=show_selector) def _robot_cb(self, button=None): ''' Toggle robot assist on/off ''' if self.vmw.robot: self.vmw.robot = False self.robot_button.set_tooltip(_('Play with the computer.')) self.robot_button.set_icon_name('robot-off') elif not self.vmw.editing_word_list: self.vmw.robot = True self.robot_button.set_tooltip(_('Stop playing with the computer.')) self.robot_button.set_icon_name('robot-on') def _level_cb(self, button, level): ''' Cycle between levels ''' if self.vmw.joiner(): # joiner cannot change level return self.vmw.level = level # self.level_label.set_text(self.calc_level_label(self.vmw.low_score, # self.vmw.level)) self._load_new_game(show_selector=False) def calc_level_label(self, low_score, play_level): ''' Show the score. ''' if low_score[play_level] == -1: return LEVEL_LABELS[play_level] else: return '%s (%d:%02d)' % \ (LEVEL_LABELS[play_level], int(low_score[play_level] / 60), int(low_score[play_level] % 60)) def image_import_cb(self, button=None): ''' Import custom cards from the Journal ''' self.vmw.editing_custom_cards = True self.vmw.editing_word_list = False self.vmw.edit_custom_card() if self.vmw.robot: self._robot_cb(button) def _number_card_O_cb(self, button, numberO): ''' Choose between O-card list for numbers game. ''' if self.vmw.joiner(): # joiner cannot change decks return self.vmw.numberO = numberO self.vmw.card_type = 'number' self._load_new_game() def _number_card_C_cb(self, button, numberC): ''' Choose between C-card list for numbers game. ''' if self.vmw.joiner(): # joiner cannot change decks return self.vmw.numberC = numberC self.vmw.card_type = 'number' self._load_new_game() def _edit_words_cb(self, button): ''' Edit the word list. ''' self.vmw.editing_word_list = True self.vmw.editing_custom_cards = False self.vmw.edit_word_list() if self.vmw.robot: self._robot_cb(button) def _read_metadata(self, keyword, default_value, json=False): ''' If the keyword is found, return stored value ''' if keyword in self.metadata: data = self.metadata[keyword] else: data = default_value if json: try: return jloads(data) except: pass return data def _read_journal_data(self): ''' There may be data from a previous instance. ''' self._play_level = int(self._read_metadata('play_level', 0)) self._robot_time = int( self._read_metadata('robot_time', ROBOT_TIMER_DEFAULT)) self._card_type = self._read_metadata('cardtype', MODE) self._low_score = [ int(self._read_metadata('low_score_beginner', -1)), int(self._read_metadata('low_score_intermediate', -1)), int(self._read_metadata('low_score_expert', -1)) ] self._all_scores = self._read_metadata( 'all_scores', '{"pattern": [], "word": [], "number": [], "custom": []}', True) self._numberO = int(self._read_metadata('numberO', PRODUCT)) self._numberC = int(self._read_metadata('numberC', HASH)) self._matches = int(self._read_metadata('matches', 0)) self._robot_matches = int(self._read_metadata('robot_matches', 0)) self._total_time = int(self._read_metadata('total_time', 0)) self._deck_index = int(self._read_metadata('deck_index', 0)) self._word_lists = [[ self._read_metadata('mouse', _('mouse')), self._read_metadata('cat', _('cat')), self._read_metadata('dog', _('dog')) ], [ self._read_metadata('cheese', _('cheese')), self._read_metadata('apple', _('apple')), self._read_metadata('bread', _('bread')) ], [ self._read_metadata('moon', _('moon')), self._read_metadata('sun', _('sun')), self._read_metadata('earth', _('earth')) ]] self._editing_word_list = bool( int(self._read_metadata('editing_word_list', 0))) self._editing_custom_cards = bool( int(self._read_metadata('editing_custom_cards', 0))) if self._card_type == 'custom': self._custom_object = self._read_metadata('custom_object', None) if self._custom_object is None: self._card_type = MODE self._custom_jobject = [] for i in range(9): self._custom_jobject.append( self._read_metadata('custom_' + str(i), None)) def _write_scores_to_clipboard(self, button=None): ''' SimpleGraph will plot the cululative results ''' jscores = '' c = 0 for key in list(self.vmw.all_scores.keys()): for data in self.vmw.all_scores[key]: jscores += '%s: %s\n' % (str(c + 1), data[1]) c += 1 clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) clipboard.set_text(jscores, -1) def _setup_toolbars(self): ''' Setup the toolbars.. ''' tools_toolbar = Gtk.Toolbar() numbers_toolbar = Gtk.Toolbar() toolbox = ToolbarBox() self.activity_toolbar_button = ActivityToolbarButton(self) toolbox.toolbar.insert(self.activity_toolbar_button, 0) self.activity_toolbar_button.show() self.numbers_toolbar_button = ToolbarButton(page=numbers_toolbar, icon_name='number-tools') if MODE == 'number': numbers_toolbar.show() toolbox.toolbar.insert(self.numbers_toolbar_button, -1) self.numbers_toolbar_button.show() self.tools_toolbar_button = ToolbarButton(page=tools_toolbar, icon_name='view-source') self.button_pattern = button_factory('new-pattern-game', toolbox.toolbar, self._select_game_cb, cb_arg='pattern', tooltip=_('New game')) self._set_extras(toolbox.toolbar) self._sep.append(separator_factory(toolbox.toolbar, True, False)) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() button_factory('score-copy', self.activity_toolbar_button, self._write_scores_to_clipboard, tooltip=_('Export scores to clipboard')) self.set_toolbar_box(toolbox) toolbox.show() if MODE == 'word': self.words_tool_button = button_factory( 'word-tools', tools_toolbar, self._edit_words_cb, tooltip=_('Edit word lists.')) self.import_button = button_factory('image-tools', tools_toolbar, self.image_import_cb, tooltip=_('Import custom cards')) self.button_custom = button_factory('new-custom-game', tools_toolbar, self._select_game_cb, cb_arg='custom', tooltip=_('New custom game')) self.button_custom.set_sensitive(False) if MODE == 'number': self._setup_number_buttons(numbers_toolbar) def _setup_number_buttons(self, numbers_toolbar): self.product_button = radio_factory('product', numbers_toolbar, self._number_card_O_cb, cb_arg=PRODUCT, tooltip=_('product'), group=None) NUMBER_O_BUTTONS[PRODUCT] = self.product_button self.roman_button = radio_factory('roman', numbers_toolbar, self._number_card_O_cb, cb_arg=ROMAN, tooltip=_('Roman numerals'), group=self.product_button) NUMBER_O_BUTTONS[ROMAN] = self.roman_button self.word_button = radio_factory('word', numbers_toolbar, self._number_card_O_cb, cb_arg=WORD, tooltip=_('word'), group=self.product_button) NUMBER_O_BUTTONS[WORD] = self.word_button self.chinese_button = radio_factory('chinese', numbers_toolbar, self._number_card_O_cb, cb_arg=CHINESE, tooltip=_('Chinese'), group=self.product_button) NUMBER_O_BUTTONS[CHINESE] = self.chinese_button self.mayan_button = radio_factory('mayan', numbers_toolbar, self._number_card_O_cb, cb_arg=MAYAN, tooltip=_('Mayan'), group=self.product_button) NUMBER_O_BUTTONS[MAYAN] = self.mayan_button self.incan_button = radio_factory('incan', numbers_toolbar, self._number_card_O_cb, cb_arg=INCAN, tooltip=_('Quipu'), group=self.product_button) NUMBER_O_BUTTONS[INCAN] = self.incan_button separator_factory(numbers_toolbar, False, True) self.hash_button = radio_factory('hash', numbers_toolbar, self._number_card_C_cb, cb_arg=HASH, tooltip=_('hash marks'), group=None) NUMBER_C_BUTTONS[HASH] = self.hash_button self.dots_button = radio_factory('dots', numbers_toolbar, self._number_card_C_cb, cb_arg=DOTS, tooltip=_('dots in a circle'), group=self.hash_button) NUMBER_C_BUTTONS[DOTS] = self.dots_button self.star_button = radio_factory('star', numbers_toolbar, self._number_card_C_cb, cb_arg=STAR, tooltip=_('points on a star'), group=self.hash_button) NUMBER_C_BUTTONS[STAR] = self.star_button self.dice_button = radio_factory('dice', numbers_toolbar, self._number_card_C_cb, cb_arg=DICE, tooltip=_('dice'), group=self.hash_button) NUMBER_C_BUTTONS[DICE] = self.dice_button self.lines_button = radio_factory('lines', numbers_toolbar, self._number_card_C_cb, cb_arg=LINES, tooltip=_('dots in a line'), group=self.hash_button) NUMBER_C_BUTTONS[LINES] = self.lines_button def _configure_cb(self, event): self._vbox.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self._vbox.show() if Gdk.Screen.width() < Gdk.Screen.height(): for sep in self._sep: sep.hide() else: for sep in self._sep: sep.show() def _robot_selection_cb(self, widget): if self._robot_palette: if not self._robot_palette.is_up(): self._robot_palette.popup(immediate=True) else: self._robot_palette.popdown(immediate=True) return def _setup_robot_palette(self): self._robot_palette = self._robot_time_button.get_palette() self._robot_timer_menu = {} for seconds in ROBOT_TIMER_VALUES: self._robot_timer_menu[seconds] = { 'menu_item': MenuItem(), 'label': ROBOT_TIMER_LABELS[seconds], 'icon': image_from_svg_file('timer-%d.svg' % (seconds)), 'icon-selected': image_from_svg_file('timer-%d-selected.svg' % (seconds)) } self._robot_timer_menu[seconds]['menu_item'].set_label( self._robot_timer_menu[seconds]['label']) if seconds == ROBOT_TIMER_DEFAULT: self._robot_timer_menu[seconds]['menu_item'].set_image( self._robot_timer_menu[seconds]['icon-selected']) else: self._robot_timer_menu[seconds]['menu_item'].set_image( self._robot_timer_menu[seconds]['icon']) self._robot_timer_menu[seconds]['menu_item'].connect( 'activate', self._robot_selected_cb, seconds) self._robot_palette.menu.append( self._robot_timer_menu[seconds]['menu_item']) self._robot_timer_menu[seconds]['menu_item'].show() def _robot_selected_cb(self, button, seconds): self.vmw.robot_time = seconds if hasattr(self, '_robot_time_button') and \ seconds in ROBOT_TIMER_VALUES: self._robot_time_button.set_icon_name('timer-%d' % seconds) for time in ROBOT_TIMER_VALUES: if time == seconds: self._robot_timer_menu[time]['menu_item'].set_image( self._robot_timer_menu[time]['icon-selected']) else: self._robot_timer_menu[time]['menu_item'].set_image( self._robot_timer_menu[time]['icon']) def _set_extras(self, toolbar): self.robot_button = button_factory('robot-off', toolbar, self._robot_cb, tooltip=_('Play with the computer')) self._robot_time_button = button_factory('timer-15', toolbar, self._robot_selection_cb, tooltip=_('robot pause time')) self._setup_robot_palette() self._sep.append(separator_factory(toolbar, False, True)) self.beginner_button = radio_factory('beginner', toolbar, self._level_cb, cb_arg=BEGINNER, tooltip=_('beginner'), group=None) LEVEL_BUTTONS[BEGINNER] = self.beginner_button self.intermediate_button = radio_factory('intermediate', toolbar, self._level_cb, cb_arg=INTERMEDIATE, tooltip=_('intermediate'), group=self.beginner_button) LEVEL_BUTTONS[INTERMEDIATE] = self.intermediate_button self.expert_button = radio_factory('expert', toolbar, self._level_cb, cb_arg=EXPERT, tooltip=_('expert'), group=self.beginner_button) LEVEL_BUTTONS[EXPERT] = self.expert_button def _fixed_resize_cb(self, widget=None, rect=None): ''' If a toolbar opens or closes, we need to resize the vbox holding out scrolling window. ''' self._vbox.set_size_request(rect.width, rect.height) def _setup_canvas(self): ''' Create a canvas in a Gtk.Fixed ''' self.fixed = Gtk.Fixed() self.fixed.connect('size-allocate', self._fixed_resize_cb) self.fixed.show() self.set_canvas(self.fixed) self._vbox = Gtk.VBox(False, 0) self._vbox.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.fixed.put(self._vbox, 0, 0) self._vbox.show() self._canvas = Gtk.DrawingArea() self._canvas.set_size_request(int(Gdk.Screen.width()), int(Gdk.Screen.height())) self._canvas.show() self.show_all() self._vbox.pack_end(self._canvas, True, True, 0) self._vbox.show() self.show_all() self.vmw = Game(self._canvas, parent=self, card_type=MODE) self.vmw.level = self._play_level LEVEL_BUTTONS[self._play_level].set_active(True) self.vmw.card_type = self._card_type self.vmw.robot = False self.vmw.robot_time = self._robot_time self.vmw.low_score = self._low_score self.vmw.all_scores = self._all_scores self.vmw.numberO = self._numberO self.vmw.numberC = self._numberC self.vmw.matches = self._matches self.vmw.robot_matches = self._robot_matches self.vmw.total_time = self._total_time self.vmw.buddies = [] self.vmw.word_lists = self._word_lists self.vmw.editing_word_list = self._editing_word_list if hasattr(self, '_custom_object') and self._custom_object is not None: self.vmw._find_custom_paths(datastore.get(self._custom_object)) for i in range(9): if hasattr(self, '_custom_jobject') and \ self._custom_jobject[i] is not None: self.vmw.custom_paths[i] = datastore.get( self._custom_jobject[i]) self.button_custom.set_sensitive(True) return self._canvas def write_file(self, file_path): ''' Write data to the Journal. ''' if hasattr(self, 'vmw'): self.metadata['play_level'] = self.vmw.level self.metadata['low_score_beginner'] = int(self.vmw.low_score[0]) self.metadata['low_score_intermediate'] = int( self.vmw.low_score[1]) self.metadata['low_score_expert'] = int(self.vmw.low_score[2]) self.metadata['all_scores'] = jdumps(self.vmw.all_scores) _logger.debug("{}".format(jdumps(self.vmw.all_scores))) self.metadata['robot_time'] = self.vmw.robot_time self.metadata['numberO'] = self.vmw.numberO self.metadata['numberC'] = self.vmw.numberC self.metadata['cardtype'] = self.vmw.card_type self.metadata['matches'] = self.vmw.matches self.metadata['robot_matches'] = self.vmw.robot_matches self.metadata['total_time'] = int(self.vmw.total_time) self.metadata['deck_index'] = self.vmw.deck.index self.metadata['mouse'] = self.vmw.word_lists[0][0] self.metadata['cat'] = self.vmw.word_lists[0][1] self.metadata['dog'] = self.vmw.word_lists[0][2] self.metadata['cheese'] = self.vmw.word_lists[1][0] self.metadata['apple'] = self.vmw.word_lists[1][1] self.metadata['bread'] = self.vmw.word_lists[1][2] self.metadata['moon'] = self.vmw.word_lists[2][0] self.metadata['sun'] = self.vmw.word_lists[2][1] self.metadata['earth'] = self.vmw.word_lists[2][2] self.metadata['editing_word_list'] = self.vmw.editing_word_list self.metadata['mime_type'] = 'application/x-visualmatch' f = open(file_path, 'w') f.write(self._dump()) f.close() else: _logger.debug('Deferring saving to %s' % file_path) def _dump(self): ''' Dump game data to the journal.''' data = [] for i in self.vmw.grid.grid: if i is None or self.vmw.editing_word_list: data.append(None) else: data.append(i.index) for i in self.vmw.clicked: if i.spr is None or self.vmw.deck.spr_to_card(i.spr) is None or \ self.vmw.editing_word_list: data.append(None) else: data.append(self.vmw.deck.spr_to_card(i.spr).index) for i in self.vmw.deck.cards: if i is None or self.vmw.editing_word_list: data.append(None) else: data.append(i.index) for i in self.vmw.match_list: if self.vmw.deck.spr_to_card(i) is not None: data.append(self.vmw.deck.spr_to_card(i).index) for i in self.vmw.word_lists: for j in i: data.append(j) return self._data_dumper(data) def _data_dumper(self, data): io = StringIO() jdump(data, io) return io.getvalue() def read_file(self, file_path): ''' Read data from the Journal. ''' f = open(file_path, 'r') self._load(f.read()) f.close() def _load(self, data): ''' Load game data from the journal. ''' saved_state = self._data_loader(data) if len(saved_state) > 0: self._saved_state = saved_state def _data_loader(self, data): json_data = jloads(data) return json_data def _notify_new_game(self, prompt): ''' Called from New Game button since loading a new game can be slooow!! ''' alert = NotifyAlert(3) alert.props.title = prompt alert.props.msg = _('A new game is loading.') def _notification_alert_response_cb(alert, response_id, self): self.remove_alert(alert) alert.connect('response', _notification_alert_response_cb, self) self.add_alert(alert) alert.show() def _new_help_box(self, name, button=None): help_box = Gtk.VBox() help_box.set_homogeneous(False) help_palettes[name] = help_box if button is not None: help_buttons[name] = button help_windows[name] = Gtk.ScrolledWindow() help_windows[name].set_size_request( int(Gdk.Screen.width() / 3), Gdk.Screen.height() - style.GRID_CELL_SIZE * 3) help_windows[name].set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) help_windows[name].add_with_viewport(help_palettes[name]) help_palettes[name].show() return help_box def _setup_toolbar_help(self): ''' Set up a help palette for the main toolbars ''' help_box = self._new_help_box('main-toolbar') add_section(help_box, _('Dimensions'), icon='activity-dimensions') add_paragraph(help_box, _('Tools'), icon='view-source') if MODE == 'pattern': add_paragraph(help_box, _('Game'), icon='new-pattern-game') elif MODE == 'number': add_paragraph(help_box, PROMPT_DICT['number'], icon='new-number-game') add_paragraph(help_box, _('Numbers'), icon='number-tools') elif MODE == 'word': add_paragraph(help_box, PROMPT_DICT['word'], icon='new-word-game') add_paragraph(help_box, _('Play with the computer'), icon='robot-off') add_paragraph(help_box, _('robot pause time'), icon='timer-60') add_paragraph(help_box, _('beginner'), icon='beginner') add_paragraph(help_box, _('intermediate'), icon='intermediate') add_paragraph(help_box, _('expert'), icon='expert') add_section(help_box, _('Dimensions'), icon='activity-dimensions') add_paragraph(help_box, _('Export scores to clipboard'), icon='score-copy') add_section(help_box, _('Tools'), icon='view-source') add_section(help_box, _('Import image cards'), icon='image-tools') add_paragraph(help_box, PROMPT_DICT['custom'], icon='new-custom-game') if MODE == 'word': add_section(help_box, _('Edit word lists.'), icon='word-tools') if MODE == 'number': add_section(help_box, _('Numbers'), icon='number-tools') add_paragraph(help_box, _('product'), icon='product') add_paragraph(help_box, _('Roman numerals'), icon='roman') add_paragraph(help_box, _('word'), icon='word') add_paragraph(help_box, _('Chinese'), icon='chinese') add_paragraph(help_box, _('Mayan'), icon='mayan') add_paragraph(help_box, _('Quipu'), icon='incan') add_paragraph(help_box, _('hash marks'), icon='hash') add_paragraph(help_box, _('dots in a circle'), icon='dots') add_paragraph(help_box, _('points on a star'), icon='star') add_paragraph(help_box, _('dice'), icon='dice') add_paragraph(help_box, _('dots in a line'), icon='lines') def _buddy_joined_cb(self, collab, buddy): if buddy.nick in self.vmw.buddies: return self.vmw.buddies.append(buddy.nick) def _buddy_left_cb(self, collab, buddy): if buddy.nick not in self.vmw.buddies: return del self.vmw.buddies[self.vmw.buddies.index(buddy.nick)] def _shared_cb(self, owner): if self.shared_activity is None: _logger.error('Failed to share or join activity...\ shared_activity is null in _shared_cb()') return _logger.debug('Sharing...') self.initiating = True self.waiting_for_deck = False def _joined_cb(self, sender): if self._collab.leader: return self.waiting_for_deck = True self.button_pattern.set_sensitive(False) self.robot_button.set_sensitive(False) self._robot_time_button.set_sensitive(False) self.beginner_button.set_sensitive(False) self.intermediate_button.set_sensitive(False) self.expert_button.set_sensitive(False) self._collab.post(dict(action='joined')) def _message_cb(self, collab, buddy, message): ''' Data is passed as dict(action='action') ''' action = message.get('action') if action == 'clicked_card': card_index = message.get('clicked_card') self.vmw.add_to_clicked( self.vmw.deck.index_to_card(int(card_index)).spr) elif action == 'unselect_cards': self.vmw.clean_up_match() elif action == 'return_card': self.vmw.clean_up_no_match(None) elif action == 'select_card': card = message.get('select_card') self.vmw.process_click(self.vmw.clicked[card].spr) self.vmw.process_selection(self.vmw.clicked[card].spr) elif action == 'joined': if self.initiating: # Only the sharer 'shares'. self._collab.post(dict(action='level', level=self.vmw.level)) self._collab.post( dict(action='index', index=self.vmw.deck.index)) self._collab.post( dict(action='matches', matches=self.vmw.matches)) self._collab.post( dict(action='card_type', card_type=self.vmw.card_type)) self._collab.post(dict(action='data', data=self._dump())) elif action == 'req_state': # Force a request for current state. self._collab.post(dict(action='joined')) self.waiting_for_deck = True elif action == 'choose_c_type': self.vmw.choose_card_type() elif action == 'card_type': card_type = message.get('card_type') self.vmw.card_type = card_type elif action == 'numberO': numberO = message.get('numberO') self.vmw.numberO = numberO elif action == 'numberC': numberC = message.get('numberC') self.vmw.numberC = numberC elif action == 'level': level = message.get('level') self.vmw.level = level # self.level_label.set_text(self.calc_level_label( # self.vmw.low_score, self.vmw.level)) LEVEL_BUTTONS[self.vmw.level].set_active(True) elif action == 'index': index = message.get('index') self.vmw.deck.index = index elif action == 'matches': matches = message.get('matches') self.vmw.matches = matches elif action == 'data': if self.waiting_for_deck: data = message.get('data') self._load(data) self.waiting_for_deck = False self.vmw.new_game(self._saved_state, self.vmw.deck.index)
def __init__(self, handle): ''' Initiate activity. ''' super(FractionBounceActivity, self).__init__(handle) self.nick = profile.get_nick_name() if profile.get_color() is not None: self._colors = profile.get_color().to_string().split(',') else: self._colors = ['#A0FFA0', '#FF8080'] self.max_participants = 4 # sharing self._playing = True self._setup_toolbars() canvas = self._setup_canvas() # Read any custom fractions from the project metadata if 'custom' in self.metadata: custom = self.metadata['custom'] else: custom = None self._current_ball = 'soccerball' self._toolbar_was_expanded = False # Initialize the canvas self._bounce_window = Bounce(canvas, activity.get_bundle_path(), self) Gdk.Screen.get_default().connect('size-changed', self._configure_cb) # Restore any custom fractions if custom is not None: fractions = custom.split(',') for f in fractions: self._bounce_window.add_fraction(f) self._bounce_window.buddies.append(self.nick) self._player_colors = [self._colors] self._player_pixbufs = [ svg_str_to_pixbuf(generate_xo_svg(scale=0.8, colors=self._colors)) ] def on_activity_joined_cb(me): logging.debug('activity joined') self._player.set_from_pixbuf(self._player_pixbufs[0]) self.connect('joined', on_activity_joined_cb) def on_activity_shared_cb(me): logging.debug('activity shared') self._player.set_from_pixbuf(self._player_pixbufs[0]) self._label.set_label(_('Wait for others to join.')) self.connect('shared', on_activity_shared_cb) self._collab = CollabWrapper(self) if self.shared_activity: # We're joining if not self.get_shared(): self._label.set_label(_('Wait for the sharer to start.')) actions = { 'j': self._new_joiner, 'b': self._buddy_list, 'f': self._receive_a_fraction, 't': self._take_a_turn, 'l': self._buddy_left, } def on_message_cb(collab, buddy, msg): logging.debug('on_message_cb buddy %r msg %r' % (buddy, msg)) if self._playing: actions[msg.get('action')](msg.get('data')) self._collab.connect('message', on_message_cb) def on_joined_cb(collab, msg): logging.debug('joined') self.send_event('j', [self.nick, self._colors]) self._collab.connect('joined', on_joined_cb, 'joined') def on_buddy_joined_cb(collab, buddy, msg): logging.debug('on_buddy_joined_cb buddy %r' % (buddy.props.nick)) self._collab.connect('buddy_joined', on_buddy_joined_cb, 'buddy_joined') def on_buddy_left_cb(collab, buddy, msg): logging.debug('on_buddy_left_cb buddy %r' % (buddy.props.nick)) self._collab.connect('buddy_left', on_buddy_left_cb, 'buddy_left') self._collab.setup()
class ImplodeActivity(Activity): def __init__(self, handle): Activity.__init__(self, handle) self._joining_hide = False self._game = ImplodeGame() self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) game_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) game_box.pack_start(self._game, True, True, 0) self._stuck_strip = _StuckStrip() self._configure_toolbars() self.set_canvas(game_box) # Show everything except the stuck strip. self.show_all() self._configure_cb() game_box.pack_end(self._stuck_strip, expand=False, fill=False, padding=0) self._game.connect('show-stuck', self._show_stuck_cb) self._game.connect('piece-selected', self._piece_selected_cb) self._game.connect('undo-key-pressed', self._undo_key_pressed_cb) self._game.connect('redo-key-pressed', self._redo_key_pressed_cb) self._game.connect('new-key-pressed', self._new_key_pressed_cb) self._stuck_strip.connect('undo-clicked', self._stuck_undo_cb) game_box.connect('key-press-event', self._key_press_event_cb) self._game.grab_focus() last_game_path = self._get_last_game_path() if os.path.exists(last_game_path): self.read_file(last_game_path) self._collab.setup() # Hide the canvas when joining a shared activity if self.shared_activity: if not self.get_shared(): self.get_canvas().hide() self.busy() self._joining_hide = True def _get_last_game_path(self): return os.path.join(self.get_activity_root(), 'data', 'last_game') def get_data(self): return self._game.get_game_state() def set_data(self, data): if not data['win_draw_flag']: self._game.set_game_state(data) # Ensure that the visual display matches the game state. self._levels_buttons[data['difficulty']].props.active = True # Release the cork if self._joining_hide: self.get_canvas().show() self.unbusy() def read_file(self, file_path): # Loads the game state from a file. f = open(file_path, 'r') file_data = json.loads(f.read()) f.close() (file_type, version, game_data) = file_data if file_type == 'Implode save game' and version <= [1, 0]: self.set_data(game_data) def write_file(self, file_path): # Writes the game state to a file. data = self.get_data() file_data = ['Implode save game', [1, 0], data] last_game_path = self._get_last_game_path() for path in (file_path, last_game_path): f = open(path, 'w') f.write(json.dumps(file_data)) f.close() def _show_stuck_cb(self, state, data=None): if self.shared_activity: return if self.metadata: share_scope = self.metadata.get('share-scope', SCOPE_PRIVATE) if share_scope != SCOPE_PRIVATE: return if data: self._stuck_strip.show_all() else: if self._stuck_strip.get_focus_child(): self._game.grab_focus() self._stuck_strip.hide() def _stuck_undo_cb(self, state, data=None): self._game.undo_to_solvable_state() def _key_press_event_cb(self, source, event): # Make the game navigable by keypad controls. action = KEY_MAP.get(event.keyval, None) if action is None: return False if not self._stuck_strip.get_state_flags() & Gtk.AccelFlags.VISIBLE: return True if self._game.get_focus_child(): if action == 'down': self._stuck_strip.button.grab_focus() return True elif self._stuck_strip.get_focus_child(): if action == 'up': self._game.grab_focus() elif action == 'select': self._stuck_strip.button.activate() return True return True def _configure_toolbars(self): """Create, set, and show a toolbar box with an activity button, game controls, difficulty selector, help button, and stop button. All callbacks are locally defined.""" self._seps = [] toolbar_box = ToolbarBox() toolbar = toolbar_box.toolbar activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(activity_button, 0) activity_button.show() self._add_separator(toolbar) def add_button(icon_name, tooltip, callback): button = ToolButton(icon_name) toolbar.add(button) button.connect('clicked', callback) button.set_tooltip(tooltip) return button add_button('new-game', _("New"), self._new_game_cb) add_button('replay-game', _("Replay"), self._replay_game_cb) self._add_separator(toolbar) add_button('edit-undo', _("Undo"), self._undo_cb) add_button('edit-redo', _("Redo"), self._redo_cb) self._add_separator(toolbar) self._levels_buttons = [] def add_level_button(icon_name, tooltip, numeric_level): if self._levels_buttons: button = RadioToolButton(icon_name=icon_name, group=self._levels_buttons[0]) else: button = RadioToolButton(icon_name=icon_name) self._levels_buttons.append(button) toolbar.add(button) def callback(source): if source.get_active(): self._collab.post({'action': icon_name}) self._game.set_level(numeric_level) self._game.new_game() button.connect('toggled', callback) button.set_tooltip(tooltip) add_level_button('easy-level', _("Easy"), 0) add_level_button('medium-level', _("Medium"), 1) add_level_button('hard-level', _("Hard"), 2) self._add_separator(toolbar) def _help_clicked_cb(button): help_window = _HelpWindow() help_window.set_transient_for(self.get_toplevel()) help_window.show_all() help_button = add_button('toolbar-help', _("Help"), _help_clicked_cb) def _help_disable_cb(collab, buddy): if help_button.props.sensitive: help_button.props.sensitive = False self._collab.connect('buddy-joined', _help_disable_cb) self._add_expander(toolbar) stop_button = StopButton(self) toolbar_box.toolbar.insert(stop_button, -1) stop_button.show() self.set_toolbar_box(toolbar_box) toolbar_box.show() Gdk.Screen.get_default().connect('size-changed', self._configure_cb) def _add_separator(self, toolbar): self._seps.append(Gtk.SeparatorToolItem()) toolbar.add(self._seps[-1]) self._seps[-1].show() def _add_expander(self, toolbar, expand=True): """Insert a toolbar item which will expand to fill the available space.""" self._seps.append(Gtk.SeparatorToolItem()) self._seps[-1].props.draw = False self._seps[-1].set_expand(expand) toolbar.insert(self._seps[-1], -1) self._seps[-1].show() def _configure_cb(self, event=None): if Gdk.Screen.width() < Gdk.Screen.height(): hide = True else: hide = False for sep in self._seps: if hide: sep.hide() else: sep.show() def _new_game_cb(self, button): self._game.reseed() self._collab.post({ 'action': 'new-game', 'seed': self._game.get_seed() }) self._game.new_game() def _replay_game_cb(self, button): self._collab.post({'action': 'replay-game'}) self._game.replay_game() def _undo_cb(self, button): self._collab.post({'action': 'edit-undo'}) self._game.undo() def _redo_cb(self, button): self._collab.post({'action': 'edit-redo'}) self._game.redo() def _message_cb(self, collab, buddy, msg): action = msg.get('action') if action == 'new-game': self._game.set_seed(msg.get('seed')) self._game.new_game() elif action == 'replay-game': self._game.replay_game() elif action == 'edit-undo': self._game.undo() elif action == 'edit-redo': self._game.redo() elif action == 'easy-level': self._game.set_level(0) self._game.new_game() elif action == 'medium-level': self._game.set_level(1) self._game.new_game() elif action == 'hard-level': self._game.set_level(2) self._game.new_game() elif action == 'piece-selected': x = msg.get('x') y = msg.get('y') self._game.piece_selected(x, y) def _piece_selected_cb(self, game, x, y): self._collab.post({'action': 'piece-selected', 'x': x, 'y': y}) def _undo_key_pressed_cb(self, game, dummy): self._collab.post({'action': 'edit-undo'}) def _redo_key_pressed_cb(self, game, dummy): self._collab.post({'action': 'edit-redo'}) def _new_key_pressed_cb(self, game, seed): self._collab.post({'action': 'new-game', 'seed': seed})
class PhysicsActivity(activity.Activity): def __init__(self, handle): super(PhysicsActivity, self).__init__(handle) self._collab = CollabWrapper(self) self._collab.message.connect(self.__message_cb) self.metadata['mime_type'] = 'application/x-physics-activity' self.add_events(Gdk.EventMask.ALL_EVENTS_MASK | Gdk.EventMask.VISIBILITY_NOTIFY_MASK) self.connect('visibility-notify-event', self._focus_event) self.connect('window-state-event', self._window_event) self.game = PhysicsGame(self) self.game.canvas = sugargame.canvas.PygameCanvas( self, main=self.game.run, modules=[pygame.display, pygame.font]) self.preview = None self._sample_window = None self._notebook = Gtk.Notebook(show_tabs=False) self._notebook.add(self.game.canvas) w = Gdk.Screen.width() h = Gdk.Screen.height() - 2 * GRID_CELL_SIZE self.game.canvas.set_size_request(w, h) self._constructors = {} self.build_toolbar() self.set_canvas(self._notebook) Gdk.Screen.get_default().connect('size-changed', self.__configure_cb) self.show_all() self._collab.setup() def get_data(self): """ FIXME: not implemented, thus objects created before sharing starts are not shared """ return dict() def set_data(self, data): pass def __configure_cb(self, event): ''' Screen size has changed ''' self.write_file(os.path.join( activity.get_activity_root(), 'data', 'data')) w = Gdk.Screen.width() h = Gdk.Screen.height() - 2 * GRID_CELL_SIZE pygame.display.set_mode((w, h), pygame.RESIZABLE) self.read_file(os.path.join( activity.get_activity_root(), 'data', 'data')) def read_file(self, file_path): self.game.read_file(file_path) def write_file(self, file_path): self.game.write_file(file_path) def get_preview(self): ''' Custom preview code to get image from pygame. ''' return self.game.canvas.get_preview() def build_toolbar(self): self.max_participants = 4 toolbar_box = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(activity_button, 0) activity_button.show() create_toolbar = ToolbarButton() create_toolbar.props.page = Gtk.Toolbar() create_toolbar.props.icon_name = 'magicpen' create_toolbar.props.label = _('Create') toolbar_box.toolbar.insert(create_toolbar, -1) self._insert_create_tools(create_toolbar) color = ColorToolButton('color') color.connect('notify::color', self.__color_notify_cb) toolbar_box.toolbar.insert(color, -1) color.show() random = ToggleToolButton('colorRandom') random.set_tooltip(_('Toggle random color')) toolbar_box.toolbar.insert(random, -1) random.set_active(True) random.connect('toggled', self.__random_toggled_cb) random.show() color.random = random random.color = color random.timeout_id = GLib.timeout_add(100, self.__timeout_cb, random) self._insert_stop_play_button(toolbar_box.toolbar) clear_trace = ToolButton('clear-trace') clear_trace.set_tooltip(_('Clear Trace Marks')) clear_trace.set_accelerator(_('<ctrl>x')) clear_trace.connect('clicked', self.clear_trace_cb) clear_trace.set_sensitive(False) toolbar_box.toolbar.insert(clear_trace, -1) clear_trace.show() self.clear_trace = clear_trace self._insert_clear_all_button(toolbar_box.toolbar) load_example = ToolButton('load-sample') load_example.set_tooltip(_('Show sample projects')) load_example.connect('clicked', self._create_store) toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) toolbar_box.toolbar.insert(load_example, -1) load_example.show() separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_size_request(0, -1) separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) separator.show() stop = StopButton(self) toolbar_box.toolbar.insert(stop, -1) stop.show() separator = Gtk.SeparatorToolItem() activity_button.props.page.insert(separator, -1) separator.show() export_json = ToolButton('save-as-json') export_json.set_tooltip(_('Export tracked objects to journal')) export_json.connect('clicked', self._export_json_cb) activity_button.props.page.insert(export_json, -1) export_json.show() load_project = ToolButton('load-project') load_project.set_tooltip(_('Load project from journal')) load_project.connect('clicked', self._load_project) activity_button.props.page.insert(load_project, -1) load_project.show() self.set_toolbar_box(toolbar_box) toolbar_box.show_all() create_toolbar.set_expanded(True) return toolbar_box def __color_notify_cb(self, button, pdesc): """ when a color is chosen; change world object add color, and change color of buttons. """ color = button.get_color() self._set_color(color) button.random.set_active(False) button.random.get_icon_widget().set_stroke_color(self._rgb8x(color)) def __random_toggled_cb(self, random): if random.props.active: self._random_on(random) else: self._random_off(random) def _random_on(self, random): """ when random is turned on; reset world object add color, and begin watching for changed world object add color. """ self.game.world.add.reset_color() if random.timeout_id is None: random.timeout_id = GLib.timeout_add(100, self.__timeout_cb, random) self.__timeout_cb(random) def _random_off(self, random): """ when random is turned off; change world object add color back to chosen color, change color of buttons, and stop watching for changed world object add color. """ color = random.color.get_color() self._set_color(color) random.get_icon_widget().set_stroke_color(self._rgb8x(color)) if random.timeout_id is not None: GLib.source_remove(random.timeout_id) random.timeout_id = None def __timeout_cb(self, random): """ copy the next color to the random button stroke color. """ if hasattr(self.game, "world"): color = self.game.world.add.next_color() random.get_icon_widget().set_stroke_color('#%.2X%.2X%.2X' % color) return True def _set_color(self, color): """ set world object add color. """ self.game.world.add.set_color(self._rgb8(color)) def _rgb8x(self, color): """ convert a Gdk.Color into hex triplet. """ return '#%.2X%.2X%.2X' % self._rgb8(color) def _rgb8(self, color): """ convert a Gdk.Color into an 8-bit RGB tuple. """ return (color.red / 256, color.green / 256, color.blue / 256) def can_close(self): self.preview = self.get_preview() self.game.running = False return True def _insert_stop_play_button(self, toolbar): self.stop_play_toolbar = ToolbarButton() st_toolbar = self.stop_play_toolbar st_toolbar.props.page = Gtk.Toolbar() st_toolbar.props.icon_name = 'media-playback-stop' self.stop_play_state = True self.stop_play = ToolButton('media-playback-stop') self.stop_play.set_tooltip(_('Stop')) self.stop_play.set_accelerator(_('<ctrl>space')) self.stop_play.connect('clicked', self.stop_play_cb) self._insert_item(st_toolbar, self.stop_play) self.stop_play.show() slowest_button = RadioToolButton(group=None) slowest_button.set_icon_name('slow-walk-milton-raposo') slowest_button.set_tooltip(_('Run slower')) slowest_button.connect('clicked', self._set_fps_cb, SLOWEST_FPS) self._insert_item(st_toolbar, slowest_button) slowest_button.show() slow_button = RadioToolButton(group=slowest_button) slow_button.set_icon_name('walking') slow_button.set_tooltip(_('Run slow')) slow_button.connect('clicked', self._set_fps_cb, SLOW_FPS) self._insert_item(st_toolbar, slow_button) slow_button.show() fast_button = RadioToolButton(group=slowest_button) fast_button.set_icon_name('running') fast_button.set_tooltip('Run fast') fast_button.connect('clicked', self._set_fps_cb, FAST_FPS) self._insert_item(st_toolbar, fast_button) fast_button.show() fast_button.set_active(True) toolbar.insert(self.stop_play_toolbar, -1) self.stop_play_toolbar.show_all() def _set_fps_cb(self, button, value): self.game.set_game_fps(value) def _insert_clear_all_button(self, toolbar): self.clear_all = ToolButton('clear_all') self.clear_all.set_tooltip(_('Erase All')) self.clear_all.set_accelerator(_('<ctrl>a')) self.clear_all.connect('clicked', self.clear_all_cb) toolbar.insert(self.clear_all, -1) self.clear_all.set_sensitive(False) self.clear_all.show() def _insert_item(self, toolbar, item, pos=-1): if hasattr(toolbar, 'insert'): toolbar.insert(item, pos) else: toolbar.props.page.insert(item, pos) def _insert_create_tools(self, create_toolbar): # Make + add the component buttons self.radioList = {} for i, c in enumerate(tools.allTools): if i == 0: button = RadioToolButton(group=None) firstbutton = button else: button = RadioToolButton(group=firstbutton) button.set_icon_name(c.icon) button.set_tooltip(c.toolTip) button.set_accelerator(c.toolAccelerator) button.connect('clicked', self.radioClicked) palette = self._build_palette(c) if palette is not None: palette.show() button.get_palette().set_content(palette) self._insert_item(create_toolbar, button, -1) button.show() self.radioList[button] = c.name if hasattr(c, 'constructor'): self._constructors[c.name] = \ self.game.toolList[c.name].constructor def __icon_path(self, name): activity_path = activity.get_bundle_path() icon_path = os.path.join(activity_path, 'icons', name + '.svg') return icon_path def _build_palette(self, tool): if tool.palette_enabled: if tool.palette_mode == tools.PALETTE_MODE_ICONS: grid = Gtk.Grid() for s, settings in enumerate(tool.palette_settings): self.game.toolList[tool.name].buttons.append([]) for i, icon_value in enumerate(settings['icon_values']): if i == 0: button = RadioToolButton(group=None) firstbutton = button else: button = RadioToolButton(group=firstbutton) button.set_icon_name(settings['icons'][i]) button.connect('clicked', self._palette_icon_clicked, tool.name, s, settings['name'], icon_value) grid.attach(button, i, s, 1, 1) self.game.toolList[tool.name].buttons[s].append(button) button.show() if settings['active'] == settings['icons'][i]: button.set_icon_name(settings['icons'][i] + '-selected') button.set_active(True) return grid else: return None def _palette_icon_clicked(self, widget, toolname, s, value_name, value): for tool in tools.allTools: if tool.name == toolname: # Radio buttons are not highlighting in the palette # so adding highlight by hand # See http://bugs.sugarlabs.org/ticket/305 setting = self.game.toolList[tool.name].palette_settings[s] for i, button in enumerate( self.game.toolList[tool.name].buttons[s]): icon_name = setting['icons'][i] if button == widget: button.set_icon_name(icon_name + '-selected') else: button.set_icon_name(icon_name) if hasattr(tool, 'palette_data_type'): tool.palette_data_type = value else: tool.palette_data[value_name] = value def clear_trace_alert_cb(self, alert, response): self.remove_alert(alert) if response is Gtk.ResponseType.OK: self.game.full_pos_list = [[] for _ in self.game.full_pos_list] self.game.tracked_bodies = 0 def clear_trace_cb(self, button): clear_trace_alert = ConfirmationAlert() clear_trace_alert.props.title = _('Are You Sure?') clear_trace_alert.props.msg = \ _('All trace points will be erased. This cannot be undone!') clear_trace_alert.connect('response', self.clear_trace_alert_cb) self.add_alert(clear_trace_alert) def stop_play_cb(self, button): pygame.event.post(pygame.event.Event(pygame.USEREVENT, action='stop_start_toggle')) self.stop_play_state = not self.stop_play_state if self.stop_play_state: self.stop_play.set_icon_name('media-playback-stop') self.stop_play.set_tooltip(_('Stop')) self.stop_play_toolbar.set_icon_name('media-playback-stop') else: self.stop_play.set_icon_name('media-playback-start') self.stop_play.set_tooltip(_('Start')) self.stop_play_toolbar.set_icon_name('media-playback-start') def clear_all_cb(self, button): def clear_all_alert_cb(alert, response_id): self.remove_alert(alert) if response_id is Gtk.ResponseType.OK: pygame.event.post(pygame.event.Event(pygame.USEREVENT, action='clear_all')) if len(self.game.world.world.bodies) > 2: clear_all_alert = ConfirmationAlert() clear_all_alert.props.title = _('Are You Sure?') clear_all_alert.props.msg = \ _('All your work will be discarded. This cannot be undone!') clear_all_alert.connect('response', clear_all_alert_cb) self.add_alert(clear_all_alert) def radioClicked(self, button): pygame.event.post(pygame.event.Event(pygame.USEREVENT, action=self.radioList[button])) def _focus_event(self, event, data=None): ''' Send focus events to pygame to allow it to idle when in background. ''' if data.state == Gdk.VisibilityState.FULLY_OBSCURED: pygame.event.post(pygame.event.Event(pygame.USEREVENT, action='focus_out')) else: self.game.show_fake_cursor = True pygame.event.post(pygame.event.Event(pygame.USEREVENT, action='focus_in')) def _export_json_cb(self, button): jobject = datastore.create() jobject.metadata['title'] = _('Physics export') jobject.metadata['mime_type'] = 'text/plain' tmp_dir = os.path.join(self.get_activity_root(), 'instance') fd, file_path = tempfile.mkstemp(dir=tmp_dir) os.close(fd) self.game.world.json_save(file_path) jobject.set_file_path(file_path) datastore.write(jobject) def _window_event(self, window, event): ''' Send focus out event to pygame when switching to a desktop view. ''' if event.changed_mask & Gdk.WindowState.ICONIFIED: pygame.event.post(pygame.event.Event(pygame.USEREVENT, action='focus_out')) def _restore_cursor(self): ''' No longer waiting, so restore standard cursor. ''' if not hasattr(self, 'get_window'): return self.get_window().set_cursor(self.old_cursor) def _waiting_cursor(self): ''' Waiting, so set watch cursor. ''' if not hasattr(self, 'get_window'): return self.old_cursor = self.get_window().get_cursor() self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH)) def __message_cb(self, collab, buddy, msg): ''' Data is passed as tuples: cmd:text ''' action = msg.get('action') if action != 'text': return text = msg['text'] dispatch_table = {'C': self._construct_shared_circle, 'B': self._construct_shared_box, 'T': self._construct_shared_triangle, 'P': self._construct_shared_polygon, 'M': self._construct_shared_magicpen, 'j': self._add_shared_joint, 'p': self._add_shared_pin, 'm': self._add_shared_motor, 't': self._add_shared_track, 'c': self._add_shared_chain, } logging.debug('<<< %s' % (text[0])) dispatch_table[text[0]](text[2:]) def _construct_shared_circle(self, data): circle_data = json.loads(data) pos = circle_data[0] radius = circle_data[1] density = circle_data[2] restitution = circle_data[3] friction = circle_data[4] self._constructors['Circle'](pos, radius, density, restitution, friction, share=False) def _construct_shared_box(self, data): box_data = json.loads(data) pos1 = box_data[0] pos2 = box_data[1] density = box_data[2] restitution = box_data[3] friction = box_data[4] self._constructors['Box'](pos1, pos2, density, restitution, friction, share=False) def _construct_shared_triangle(self, data): triangle_data = json.loads(data) pos1 = triangle_data[0] pos2 = triangle_data[1] density = triangle_data[2] restitution = triangle_data[3] friction = triangle_data[4] self._constructors['Triangle'](pos1, pos2, density, restitution, friction, share=False) def _construct_shared_polygon(self, data): polygon_data = json.loads(data) vertices = polygon_data[0] density = polygon_data[1] restitution = polygon_data[2] friction = polygon_data[3] self._constructors['Polygon'](vertices, density, restitution, friction, share=False) def _construct_shared_magicpen(self, data): magicpen_data = json.loads(data) vertices = magicpen_data[0] density = magicpen_data[1] restitution = magicpen_data[2] friction = magicpen_data[3] self._constructors['Magicpen'](vertices, density, restitution, friction, share=False) def _add_shared_joint(self, data): joint_data = json.loads(data) pos1 = joint_data[0] pos2 = joint_data[1] self._constructors['Joint'](pos1, pos2, share=False) def _add_shared_pin(self, data): joint_data = json.loads(data) pos = joint_data[0] self._constructors['Pin'](pos, share=False) def _add_shared_motor(self, data): joint_data = json.loads(data) pos = joint_data[0] speed = joint_data[1] self._constructors['Motor'](pos, speed, share=False) def _add_shared_track(self, data): joint_data = json.loads(data) pos = joint_data[0] color = joint_data[1] self._constructors['Track'](pos, color, share=False) def _add_shared_chain(self, data): joint_data = json.loads(data) vertices = joint_data[0] link_length = joint_data[1] radius = joint_data[2] self._constructors['Chain'](vertices, link_length, radius, share=False) def send_event(self, text): self._collab.post(dict(action='text', text=text)) def _load_project(self, button): chooser = ObjectChooser(parent=self) result = chooser.run() if result == Gtk.ResponseType.ACCEPT: dsobject = chooser.get_selected_object() file_path = dsobject.get_file_path() self.__load_game(file_path, True) chooser.destroy() def __load_game(self, file_path, journal=False): confirmation_alert = ConfirmationAlert() confirmation_alert.props.title = _('Are You Sure?') confirmation_alert.props.msg = \ _('All your work will be discarded. This cannot be undone!') def action(alert, response): self.remove_alert(alert) if response is not Gtk.ResponseType.OK: return try: f = open(file_path, 'r') # Test if the file is valid project. json.loads(f.read()) f.close() self.read_file(file_path) except: title = _('Load project from journal') if not journal: title = _('Load example') msg = _( 'Error: Cannot open Physics project from this file.') alert = NotifyAlert(5) alert.props.title = title alert.props.msg = msg alert.connect( 'response', lambda alert, response: self.remove_alert(alert)) self.add_alert(alert) confirmation_alert.connect('response', action) self.add_alert(confirmation_alert) def _create_store(self, widget=None): if self._sample_window is None: self._sample_box = Gtk.EventBox() vbox = Gtk.VBox() self._sample_window = Gtk.ScrolledWindow() self._sample_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self._sample_window.set_border_width(4) self._sample_window.set_size_request( Gdk.Screen.width() / 2, Gdk.Screen.height() / 2) self._sample_window.show() store = Gtk.ListStore(GdkPixbuf.Pixbuf, str) icon_view = Gtk.IconView() icon_view.set_model(store) icon_view.set_selection_mode(Gtk.SelectionMode.SINGLE) icon_view.connect('selection-changed', self._sample_selected, store) icon_view.set_pixbuf_column(0) icon_view.grab_focus() self._sample_window.add(icon_view) icon_view.show() self._fill_samples_list(store) title = Gtk.HBox() title_label = Gtk.Label(_("Select a sample...")) separator = Gtk.HSeparator() separator.props.expand = True separator.props.visible = False btn = Gtk.Button.new_from_stock(Gtk.STOCK_CANCEL) btn.connect('clicked', self._cancel_clicked_cb) title.pack_start(title_label, False, False, 5) title.pack_start(separator, False, False, 0) title.pack_end(btn, False, False, 0) vbox.pack_start(title, False, False, 5) vbox.pack_end(self._sample_window, True, True, 0) self._sample_box.add(vbox) self._sample_box.show_all() self._notebook.add(self._sample_box) self._notebook.set_current_page(1) def _cancel_clicked_cb(self, button=None): self._notebook.set_current_page(0) def _get_selected_path(self, widget, store): try: iter_ = store.get_iter(widget.get_selected_items()[0]) image_path = store.get(iter_, 1)[0] return image_path, iter_ except: return None def _sample_selected(self, widget, store): selected = self._get_selected_path(widget, store) if selected is None: self._selected_sample = None self._cancel_clicked_cb() return image_path, _iter = selected iter_ = store.get_iter(widget.get_selected_items()[0]) image_path = store.get(iter_, 1)[0] self._selected_sample = image_path self._cancel_clicked_cb() self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.WATCH)) GLib.idle_add(self._sample_loader) def _sample_loader(self): # Convert from thumbnail path to sample path basename = os.path.basename(self._selected_sample)[:-4] file_path = os.path.join(activity.get_bundle_path(), 'samples', basename + '.json') self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.LEFT_PTR)) self.__load_game(file_path) def _fill_samples_list(self, store): ''' Append images from the artwork_paths to the store. ''' for filepath in self._scan_for_samples(): pixbuf = None pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filepath, 100, 100) store.append([pixbuf, filepath]) def _scan_for_samples(self): samples = sorted( glob.glob( os.path.join( activity.get_bundle_path(), 'samples', 'thumbnails', '*.png'))) return samples
class FractionBounceActivity(activity.Activity): def __init__(self, handle): ''' Initiate activity. ''' super(FractionBounceActivity, self).__init__(handle) self.nick = profile.get_nick_name() if profile.get_color() is not None: self._colors = profile.get_color().to_string().split(',') else: self._colors = ['#A0FFA0', '#FF8080'] self.max_participants = 4 # sharing self._playing = True self._setup_toolbars() canvas = self._setup_canvas() # Read any custom fractions from the project metadata if 'custom' in self.metadata: custom = self.metadata['custom'] else: custom = None self._current_ball = 'soccerball' self._toolbar_was_expanded = False # Initialize the canvas self._bounce_window = Bounce(canvas, activity.get_bundle_path(), self) Gdk.Screen.get_default().connect('size-changed', self._configure_cb) # Restore any custom fractions if custom is not None: fractions = custom.split(',') for f in fractions: self._bounce_window.add_fraction(f) self._bounce_window.buddies.append(self.nick) self._player_colors = [self._colors] self._player_pixbufs = [ svg_str_to_pixbuf(generate_xo_svg(scale=0.8, colors=self._colors)) ] def on_activity_joined_cb(me): logging.debug('activity joined') self._player.set_from_pixbuf(self._player_pixbufs[0]) self.connect('joined', on_activity_joined_cb) def on_activity_shared_cb(me): logging.debug('activity shared') self._player.set_from_pixbuf(self._player_pixbufs[0]) self._label.set_label(_('Wait for others to join.')) self.connect('shared', on_activity_shared_cb) self._collab = CollabWrapper(self) if self.shared_activity: # We're joining if not self.get_shared(): self._label.set_label(_('Wait for the sharer to start.')) actions = { 'j': self._new_joiner, 'b': self._buddy_list, 'f': self._receive_a_fraction, 't': self._take_a_turn, 'l': self._buddy_left, } def on_message_cb(collab, buddy, msg): logging.debug('on_message_cb buddy %r msg %r' % (buddy, msg)) if self._playing: actions[msg.get('action')](msg.get('data')) self._collab.connect('message', on_message_cb) def on_joined_cb(collab, msg): logging.debug('joined') self.send_event('j', [self.nick, self._colors]) self._collab.connect('joined', on_joined_cb, 'joined') def on_buddy_joined_cb(collab, buddy, msg): logging.debug('on_buddy_joined_cb buddy %r' % (buddy.props.nick)) self._collab.connect('buddy_joined', on_buddy_joined_cb, 'buddy_joined') def on_buddy_left_cb(collab, buddy, msg): logging.debug('on_buddy_left_cb buddy %r' % (buddy.props.nick)) self._collab.connect('buddy_left', on_buddy_left_cb, 'buddy_left') self._collab.setup() def set_data(self, blob): pass def get_data(self): return None def close(self, **kwargs): aplay.close() activity.Activity.close(self, **kwargs) def _configure_cb(self, event): if Gdk.Screen.width() < 1024: self._label.set_size_request(275, -1) self._label.set_label('') self._separator.set_expand(False) else: self._label.set_size_request(500, -1) self._separator.set_expand(True) self._bounce_window.configure_cb(event) if self._toolbar_expanded(): self._bounce_window.bar.bump_bars('up') self._bounce_window.ball.ball.move_relative( (0, -style.GRID_CELL_SIZE)) def _toolbar_expanded(self): if self._activity_button.is_expanded(): return True elif self._custom_toolbar_button.is_expanded(): return True return False def _update_graphics(self, widget): # We need to catch opening and closing of toolbars and ignore # switching between open toolbars. if self._toolbar_expanded(): if not self._toolbar_was_expanded: self._bounce_window.bar.bump_bars('up') self._bounce_window.ball.ball.move_relative( (0, -style.GRID_CELL_SIZE)) self._toolbar_was_expanded = True else: if self._toolbar_was_expanded: self._bounce_window.bar.bump_bars('down') self._bounce_window.ball.ball.move_relative( (0, style.GRID_CELL_SIZE)) self._toolbar_was_expanded = False def _setup_toolbars(self): custom_toolbar = Gtk.Toolbar() toolbox = ToolbarBox() self._toolbar = toolbox.toolbar self._activity_button = ActivityToolbarButton(self) self._activity_button.connect('clicked', self._update_graphics) self._toolbar.insert(self._activity_button, 0) self._activity_button.show() self._custom_toolbar_button = ToolbarButton(label=_('Custom'), page=custom_toolbar, icon_name='view-source') self._custom_toolbar_button.connect('clicked', self._update_graphics) custom_toolbar.show() self._toolbar.insert(self._custom_toolbar_button, -1) self._custom_toolbar_button.show() self._load_standard_buttons(self._toolbar) self._separator = Gtk.SeparatorToolItem() self._separator.props.draw = False self._separator.set_expand(True) self._toolbar.insert(self._separator, -1) self._separator.show() stop_button = StopButton(self) stop_button.props.accelerator = _('<Ctrl>Q') self._toolbar.insert(stop_button, -1) stop_button.show() self.set_toolbar_box(toolbox) toolbox.show() self._load_custom_buttons(custom_toolbar) def _load_standard_buttons(self, toolbar): fraction_button = RadioToolButton(group=None) fraction_button.set_icon_name('fraction') fraction_button.set_tooltip(_('fractions')) fraction_button.connect('clicked', self._fraction_cb) toolbar.insert(fraction_button, -1) fraction_button.show() sector_button = RadioToolButton(group=fraction_button) sector_button.set_icon_name('sector') sector_button.set_tooltip(_('sectors')) sector_button.connect('clicked', self._sector_cb) toolbar.insert(sector_button, -1) sector_button.show() percent_button = RadioToolButton(group=fraction_button) percent_button.set_icon_name('percent') percent_button.set_tooltip(_('percents')) percent_button.connect('clicked', self._percent_cb) toolbar.insert(percent_button, -1) percent_button.show() self._player = Gtk.Image() self._player.set_from_pixbuf( svg_str_to_pixbuf( generate_xo_svg(scale=0.8, colors=['#282828', '#282828']))) self._player.set_tooltip_text(self.nick) toolitem = Gtk.ToolItem() toolitem.add(self._player) self._player.show() toolbar.insert(toolitem, -1) toolitem.show() self._label = Gtk.Label(_("Click the ball to start.")) self._label.set_line_wrap(True) if Gdk.Screen.width() < 1024: self._label.set_size_request(275, -1) else: self._label.set_size_request(500, -1) self.toolitem = Gtk.ToolItem() self.toolitem.add(self._label) self._label.show() toolbar.insert(self.toolitem, -1) self.toolitem.show() def _load_custom_buttons(self, toolbar): self.numerator = Gtk.Entry() self.numerator.set_text('') self.numerator.set_tooltip_text(_('numerator')) self.numerator.set_width_chars(3) toolitem = Gtk.ToolItem() toolitem.add(self.numerator) self.numerator.show() toolbar.insert(toolitem, -1) toolitem.show() label = Gtk.Label(' / ') toolitem = Gtk.ToolItem() toolitem.add(label) label.show() toolbar.insert(toolitem, -1) toolitem.show() self.denominator = Gtk.Entry() self.denominator.set_text('') self.denominator.set_tooltip_text(_('denominator')) self.denominator.set_width_chars(3) toolitem = Gtk.ToolItem() toolitem.add(self.denominator) self.denominator.show() toolbar.insert(toolitem, -1) toolitem.show() button = ToolButton('list-add') button.set_tooltip(_('add new fraction')) button.props.sensitive = True button.props.accelerator = 'Return' button.connect('clicked', self._add_fraction_cb) toolbar.insert(button, -1) button.show() separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(False) toolbar.insert(separator, -1) separator.show() button = ToolButton('soccerball') button.set_tooltip(_('choose a ball')) button.props.sensitive = True button.connect('clicked', self._button_palette_cb) toolbar.insert(button, -1) button.show() self._ball_palette = button.get_palette() button_grid = Gtk.Grid() row = 0 for ball in BALLDICT.keys(): if ball == 'custom': button = ToolButton('view-source') else: button = ToolButton(ball) button.connect('clicked', self._load_ball_cb, None, ball) eventbox = Gtk.EventBox() eventbox.connect('button_press_event', self._load_ball_cb, ball) label = Gtk.Label(BALLDICT[ball][0]) eventbox.add(label) label.show() button_grid.attach(button, 0, row, 1, 1) button.show() button_grid.attach(eventbox, 1, row, 1, 1) eventbox.show() row += 1 self._ball_palette.set_content(button_grid) button_grid.show() button = ToolButton('insert-picture') button.set_tooltip(_('choose a background')) button.props.sensitive = True button.connect('clicked', self._button_palette_cb) toolbar.insert(button, -1) button.show() self._bg_palette = button.get_palette() button_grid = Gtk.Grid() row = 0 for bg in BGDICT.keys(): if bg == 'custom': button = ToolButton('view-source') else: button = ToolButton(bg) button.connect('clicked', self._load_bg_cb, None, bg) eventbox = Gtk.EventBox() eventbox.connect('button_press_event', self._load_bg_cb, bg) label = Gtk.Label(BGDICT[bg][0]) eventbox.add(label) label.show() button_grid.attach(button, 0, row, 1, 1) button.show() button_grid.attach(eventbox, 1, row, 1, 1) eventbox.show() row += 1 self._bg_palette.set_content(button_grid) button_grid.show() def _button_palette_cb(self, button): palette = button.get_palette() if palette: if not palette.is_up(): palette.popup(immediate=True) else: palette.popdown(immediate=True) def can_close(self): # Let everyone know we are leaving... if hasattr(self, '_bounce_window') and \ self._bounce_window.we_are_sharing(): self._playing = False self.send_event('l', self.nick) return True def _setup_canvas(self): canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() return canvas def _load_bg_cb(self, widget, event, bg): if bg == 'custom': chooser(self, 'Image', self._new_background_from_journal) else: self._bounce_window.set_background(BGDICT[bg][1]) def _load_ball_cb(self, widget, event, ball): if ball == 'custom': chooser(self, 'Image', self._new_ball_from_journal) else: self._bounce_window.ball.new_ball( os.path.join(activity.get_bundle_path(), 'images', ball + '.svg')) self._bounce_window.set_background(BGDICT[BALLDICT[ball][1]][1]) self._current_ball = ball def _reset_ball(self): ''' If we switch back from sector mode, we need to restore the ball ''' if self._bounce_window.mode != 'sectors': return if self._current_ball == 'custom': # TODO: Reload custom ball self._current_ball = 'soccerball' self._bounce_window.ball.new_ball( os.path.join(activity.get_bundle_path(), 'images', self._current_ball + '.svg')) def _new_ball_from_journal(self, dsobject): ''' Load an image from the Journal. ''' self._bounce_window.ball.new_ball_from_image( dsobject.file_path, os.path.join(activity.get_activity_root(), 'tmp', 'custom.png')) def _new_background_from_journal(self, dsobject): ''' Load an image from the Journal. ''' self._bounce_window.new_background_from_image(None, dsobject=dsobject) def _fraction_cb(self, arg=None): ''' Set fraction mode ''' self._reset_ball() self._bounce_window.mode = 'fractions' def _percent_cb(self, arg=None): ''' Set percent mode ''' self._reset_ball() self._bounce_window.mode = 'percents' def _sector_cb(self, arg=None): ''' Set sector mode ''' self._bounce_window.mode = 'sectors' def _add_fraction_cb(self, arg=None): ''' Read entries and add a fraction to the list ''' try: numerator = int(self.numerator.get_text().strip()) except ValueError: self.numerator.set_text('NAN') numerator = 0 try: denominator = int(self.denominator.get_text().strip()) except ValueError: self.denominator.set_text('NAN') denominator = 1 if denominator == 0: self.denominator.set_text('ZDE') if numerator > denominator: numerator = 0 if numerator > 0 and denominator > 1: fraction = '%d/%d' % (numerator, denominator) self._bounce_window.add_fraction(fraction) if 'custom' in self.metadata: # Save to Journal self.metadata['custom'] = '%s,%s' % (self.metadata['custom'], fraction) else: self.metadata['custom'] = fraction self.alert( _('New fraction'), _('Your fraction, %s, has been added to the program' % (fraction))) def reset_label(self, label): ''' update the challenge label ''' self._label.set_label(label) def alert(self, title, text=None): alert = NotifyAlert(timeout=5) alert.props.title = title alert.props.msg = text self.add_alert(alert) alert.connect('response', self._alert_cancel_cb) alert.show() def _alert_cancel_cb(self, alert, response_id): self.remove_alert(alert) # Collaboration-related methods def _buddy_left(self, nick): self._label.set_label(nick + ' ' + _('has left.')) if self._collab.props.leader: self._remove_player(nick) self.send_event('b', [self._bounce_window.buddies, self._player_colors]) # Restart from sharer's turn self._bounce_window.its_my_turn() def _new_joiner(self, payload): ''' Someone has joined; sharer adds them to the buddy list. ''' [nick, colors] = payload self._label.set_label(nick + ' ' + _('has joined.')) if self._collab.props.leader: self._append_player(nick, colors) self.send_event('b', [self._bounce_window.buddies, self._player_colors]) if self._bounce_window.count == 0: # Haven't started yet... self._bounce_window.its_my_turn() def _remove_player(self, nick): if nick in self._bounce_window.buddies: i = self._bounce_window.buddies.index(nick) self._bounce_window.buddies.remove(nick) self._player_colors.remove(self._player_colors[i]) self._player_pixbufs.remove(self._player_pixbufs[i]) def _append_player(self, nick, colors): ''' Keep a list of players, their colors, and an XO pixbuf ''' if nick not in self._bounce_window.buddies: _logger.debug('appending %s to the buddy list', nick) self._bounce_window.buddies.append(nick) self._player_colors.append([str(colors[0]), str(colors[1])]) self._player_pixbufs.append( svg_str_to_pixbuf( generate_xo_svg(scale=0.8, colors=self._player_colors[-1]))) def _buddy_list(self, payload): '''Sharer sent the updated buddy list, so regenerate internal lists''' if not self._collab.props.leader: [buddies, colors] = payload self._bounce_window.buddies = buddies[:] self._player_colors = colors[:] self._player_pixbufs = [] for colors in self._player_colors: self._player_pixbufs.append( svg_str_to_pixbuf( generate_xo_svg( scale=0.8, colors=[str(colors[0]), str(colors[1])]))) def send_a_fraction(self, fraction): ''' Send a fraction to other players. ''' self.send_event('f', fraction) def _receive_a_fraction(self, payload): ''' Receive a fraction from another player. ''' self._bounce_window.play_a_fraction(payload) def _take_a_turn(self, nick): ''' If it is your turn, take it, otherwise, wait. ''' if nick == self.nick: # TODO: disambiguate self._bounce_window.its_my_turn() else: self._bounce_window.its_their_turn(nick) def send_event(self, action, data): ''' Send event through the tube. ''' _logger.debug('send_event action=%r data=%r' % (action, data)) self._collab.post({'action': action, 'data': data}) def set_player_on_toolbar(self, nick): ''' Display the XO icon of the player whose turn it is. ''' self._player.set_from_pixbuf( self._player_pixbufs[self._bounce_window.buddies.index(nick)]) self._player.set_tooltip_text(nick)
class CollabWrapperTestActivity(activity.Activity): def __init__(self, handle): activity.Activity.__init__(self, handle) self._make_toolbar_box() self._make_canvas() self._make_collaboration() self._make_automatic_restart() def set_data(self, data): if data is not 'no specific data': logging.error('get_data, set_data: unexpected data %r' % data) def get_data(self): return 'no specific data' def _make_toolbar_box(self): toolbar_box = ToolbarBox() def tool(callable, pos): widget = callable(self) toolbar_box.toolbar.insert(widget, pos) widget.show() return widget def bar(): separator = Gtk.SeparatorToolItem() toolbar_box.toolbar.insert(separator, -1) separator.show() def gap(): separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) separator.show() tool(ActivityButton, 0) tool(TitleEntry, -1) tool(DescriptionItem, -1) tool(ShareButton, -1) bar() button = tool(TestButton, -1) button.props.accelerator = '<ctrl>p' button.connect('clicked', self._test_clicked_cb) button = tool(SendButton, -1) button.connect('clicked', self._send_clicked_cb) button = tool(ClearButton, -1) button.connect('clicked', self._clear_clicked_cb) gap() tool(StopButton, -1) toolbar_box.show() self.set_toolbar_box(toolbar_box) def _test_clicked_cb(self, widget): now = time.time() self._collab.post({'action': 'echo-request', 'text': now}) self._say('%.6f send echo-request %r\n' % (now, now)) def _send_clicked_cb(self, widget): now = time.time() data = 'One Two Three' desc = 'Test Data' self._collab.send_file_memory(self._last_buddy, data, desc) self._say('%.6f send data %r\n' % (now, (data, desc))) def _clear_clicked_cb(self, widget): self._textbuffer.props.text = '' def _make_canvas(self): self._textview = Gtk.TextView() self._textview.props.editable = False self._textbuffer = self._textview.get_buffer() sw = Gtk.ScrolledWindow() sw.add(self._textview) entry = Gtk.Entry() entry.connect('activate', self._entry_activate_cb) def focus_timer_cb(): entry.grab_focus() return False GLib.timeout_add(1500, focus_timer_cb) box = Gtk.VBox() box.pack_start(sw, True, True, 10) box.pack_end(entry, False, False, 10) box.show_all() self.set_canvas(box) def _say(self, string): self._textbuffer.begin_user_action() self._textbuffer.insert(self._textbuffer.get_end_iter(), string, len(string)) self._textview.scroll_mark_onscreen( self._textbuffer.create_mark(None, self._textbuffer.get_end_iter())) self._textbuffer.end_user_action() def _entry_activate_cb(self, widget): text = widget.props.text widget.props.text = '' self._collab.post({'action': 'chat', 'text': text}) self._say('%.6f send chat %r\n' % (time.time(), text)) def _make_collaboration(self): def on_activity_joined_cb(me): self._say('%.6f activity joined\n' % (time.time())) self.connect('joined', on_activity_joined_cb) def on_activity_shared_cb(me): self._say('%.6f activity shared\n' % (time.time())) self.connect('shared', on_activity_shared_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) def on_joined_cb(collab, msg): self._say('%.6f joined\n' % (time.time())) self._collab.connect('joined', on_joined_cb, 'joined') def on_buddy_joined_cb(collab, buddy, msg): self._say('%.6f buddy-joined %s@%s\n' % (time.time(), buddy.props.nick, buddy.props.ip4_address)) self._collab.connect('buddy_joined', on_buddy_joined_cb, 'buddy_joined') def on_buddy_left_cb(collab, buddy, msg): self._say('%.6f buddy-left %s@%s\n' % (time.time(), buddy.props.nick, buddy.props.ip4_address)) self._collab.connect('buddy_left', on_buddy_left_cb, 'buddy_left') def on_incoming_file_cb(collab, ft, desc): self._say('%.6f incoming-file %r\n' % (time.time(), desc)) def on_ready_cb(ft, stream): stream.close(None) gbytes = stream.steal_as_bytes() data = gbytes.get_data() self._say('%.6f data %r\n' % (time.time(), data)) ft.connect('ready', on_ready_cb) ft.accept_to_memory() self._collab.connect('incoming_file', on_incoming_file_cb) self._collab.setup() self._last_buddy = None def _message_cb(self, collab, buddy, msg): def say(string): self._say('%.6f recv from %s@%s ' % (time.time(), buddy.props.nick, buddy.props.ip4_address)) self._say(string) self._last_buddy = buddy action = msg.get('action') if action == 'echo-reply': text = msg.get('text') latency = (time.time() - float(text)) * 1000.0 say('%s %r latency=%.3f ms\n' % (action, text, latency)) return if action == 'echo-request': text = msg.get('text') self._collab.post({'action': 'echo-reply', 'text': text}) say('%s %r\n' % (action, text)) return if action == 'chat': text = msg.get('text') say('%s %r\n' % (action, text)) return say('%s\n' % (action)) def _make_automatic_restart(self): ct = os.stat('activity.py').st_ctime def restarter(): if os.stat('activity.py').st_ctime != ct: GLib.timeout_add(233, self.close) logging.error('-- restart --') return False return True GLib.timeout_add(233, restarter)
class PhysicsActivity(activity.Activity): def __init__(self, handle): super(PhysicsActivity, self).__init__(handle) self._collab = CollabWrapper(self) self._collab.message.connect(self.__message_cb) self.metadata['mime_type'] = 'application/x-physics-activity' self.add_events(Gdk.EventMask.ALL_EVENTS_MASK | Gdk.EventMask.VISIBILITY_NOTIFY_MASK) self.connect('visibility-notify-event', self._focus_event) self.connect('window-state-event', self._window_event) self.game = PhysicsGame(self) self.game.canvas = sugargame.canvas.PygameCanvas( self, main=self.game.run, modules=[pygame.display, pygame.font]) self.preview = None self._sample_window = None self._notebook = Gtk.Notebook(show_tabs=False) self._notebook.add(self.game.canvas) w = Gdk.Screen.width() h = Gdk.Screen.height() - 2 * GRID_CELL_SIZE self.game.canvas.set_size_request(w, h) self._constructors = {} self.build_toolbar() self.set_canvas(self._notebook) Gdk.Screen.get_default().connect('size-changed', self.__configure_cb) self.show_all() self._collab.setup() def get_data(self): """ FIXME: not implemented, thus objects created before sharing starts are not shared """ return dict() def set_data(self, data): pass def __configure_cb(self, event): ''' Screen size has changed ''' self.write_file( os.path.join(activity.get_activity_root(), 'data', 'data')) w = Gdk.Screen.width() h = Gdk.Screen.height() - 2 * GRID_CELL_SIZE pygame.display.set_mode((w, h), pygame.RESIZABLE) self.read_file( os.path.join(activity.get_activity_root(), 'data', 'data')) def read_file(self, file_path): self.game.read_file(file_path) def write_file(self, file_path): self.game.write_file(file_path) def get_preview(self): ''' Custom preview code to get image from pygame. ''' return self.game.canvas.get_preview() def build_toolbar(self): self.max_participants = 4 toolbar_box = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(activity_button, 0) activity_button.show() create_toolbar = ToolbarButton() create_toolbar.props.page = Gtk.Toolbar() create_toolbar.props.icon_name = 'magicpen' create_toolbar.props.label = _('Create') toolbar_box.toolbar.insert(create_toolbar, -1) self._insert_create_tools(create_toolbar) color = ColorToolButton('color') color.connect('notify::color', self.__color_notify_cb) toolbar_box.toolbar.insert(color, -1) color.show() random = ToggleToolButton('colorRandom') random.set_tooltip(_('Toggle random color')) toolbar_box.toolbar.insert(random, -1) random.set_active(True) random.connect('toggled', self.__random_toggled_cb) random.show() color.random = random random.color = color random.timeout_id = GLib.timeout_add(100, self.__timeout_cb, random) self._insert_stop_play_button(toolbar_box.toolbar) clear_trace = ToolButton('clear-trace') clear_trace.set_tooltip(_('Clear Trace Marks')) clear_trace.set_accelerator(_('<ctrl>x')) clear_trace.connect('clicked', self.clear_trace_cb) clear_trace.set_sensitive(False) toolbar_box.toolbar.insert(clear_trace, -1) clear_trace.show() self.clear_trace = clear_trace self._insert_clear_all_button(toolbar_box.toolbar) load_example = ToolButton('load-sample') load_example.set_tooltip(_('Show sample projects')) load_example.connect('clicked', self._create_store) toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) toolbar_box.toolbar.insert(load_example, -1) load_example.show() separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_size_request(0, -1) separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) separator.show() stop = StopButton(self) toolbar_box.toolbar.insert(stop, -1) stop.show() separator = Gtk.SeparatorToolItem() activity_button.props.page.insert(separator, -1) separator.show() export_json = ToolButton('save-as-json') export_json.set_tooltip(_('Export tracked objects to journal')) export_json.connect('clicked', self._export_json_cb) activity_button.props.page.insert(export_json, -1) export_json.show() load_project = ToolButton('load-project') load_project.set_tooltip(_('Load project from journal')) load_project.connect('clicked', self._load_project) activity_button.props.page.insert(load_project, -1) load_project.show() self.set_toolbar_box(toolbar_box) toolbar_box.show_all() create_toolbar.set_expanded(True) return toolbar_box def __color_notify_cb(self, button, pdesc): """ when a color is chosen; change world object add color, and change color of buttons. """ color = button.get_color() self._set_color(color) button.random.set_active(False) button.random.get_icon_widget().set_stroke_color(self._rgb8x(color)) def __random_toggled_cb(self, random): if random.props.active: self._random_on(random) else: self._random_off(random) def _random_on(self, random): """ when random is turned on; reset world object add color, and begin watching for changed world object add color. """ self.game.world.add.reset_color() if random.timeout_id is None: random.timeout_id = GLib.timeout_add(100, self.__timeout_cb, random) self.__timeout_cb(random) def _random_off(self, random): """ when random is turned off; change world object add color back to chosen color, change color of buttons, and stop watching for changed world object add color. """ color = random.color.get_color() self._set_color(color) random.get_icon_widget().set_stroke_color(self._rgb8x(color)) if random.timeout_id is not None: GLib.source_remove(random.timeout_id) random.timeout_id = None def __timeout_cb(self, random): """ copy the next color to the random button stroke color. """ if hasattr(self.game, "world"): color = self.game.world.add.next_color() random.get_icon_widget().set_stroke_color('#%.2X%.2X%.2X' % color) return True def _set_color(self, color): """ set world object add color. """ self.game.world.add.set_color(self._rgb8(color)) def _rgb8x(self, color): """ convert a Gdk.Color into hex triplet. """ return '#%.2X%.2X%.2X' % self._rgb8(color) def _rgb8(self, color): """ convert a Gdk.Color into an 8-bit RGB tuple. """ return (color.red / 256, color.green / 256, color.blue / 256) def can_close(self): self.preview = self.get_preview() self.game.running = False return True def _insert_stop_play_button(self, toolbar): self.stop_play_toolbar = ToolbarButton() st_toolbar = self.stop_play_toolbar st_toolbar.props.page = Gtk.Toolbar() st_toolbar.props.icon_name = 'media-playback-stop' self.stop_play_state = True self.stop_play = ToolButton('media-playback-stop') self.stop_play.set_tooltip(_('Stop')) self.stop_play.set_accelerator(_('<ctrl>space')) self.stop_play.connect('clicked', self.stop_play_cb) self._insert_item(st_toolbar, self.stop_play) self.stop_play.show() slowest_button = RadioToolButton(group=None) slowest_button.set_icon_name('slow-walk-milton-raposo') slowest_button.set_tooltip(_('Run slower')) slowest_button.connect('clicked', self._set_fps_cb, SLOWEST_FPS) self._insert_item(st_toolbar, slowest_button) slowest_button.show() slow_button = RadioToolButton(group=slowest_button) slow_button.set_icon_name('walking') slow_button.set_tooltip(_('Run slow')) slow_button.connect('clicked', self._set_fps_cb, SLOW_FPS) self._insert_item(st_toolbar, slow_button) slow_button.show() fast_button = RadioToolButton(group=slowest_button) fast_button.set_icon_name('running') fast_button.set_tooltip('Run fast') fast_button.connect('clicked', self._set_fps_cb, FAST_FPS) self._insert_item(st_toolbar, fast_button) fast_button.show() fast_button.set_active(True) toolbar.insert(self.stop_play_toolbar, -1) self.stop_play_toolbar.show_all() def _set_fps_cb(self, button, value): self.game.set_game_fps(value) def _insert_clear_all_button(self, toolbar): self.clear_all = ToolButton('clear_all') self.clear_all.set_tooltip(_('Erase All')) self.clear_all.set_accelerator(_('<ctrl>a')) self.clear_all.connect('clicked', self.clear_all_cb) toolbar.insert(self.clear_all, -1) self.clear_all.set_sensitive(False) self.clear_all.show() def _insert_item(self, toolbar, item, pos=-1): if hasattr(toolbar, 'insert'): toolbar.insert(item, pos) else: toolbar.props.page.insert(item, pos) def _insert_create_tools(self, create_toolbar): # Make + add the component buttons self.radioList = {} for i, c in enumerate(tools.allTools): if i == 0: button = RadioToolButton(group=None) firstbutton = button else: button = RadioToolButton(group=firstbutton) button.set_icon_name(c.icon) button.set_tooltip(c.toolTip) button.set_accelerator(c.toolAccelerator) button.connect('clicked', self.radioClicked) palette = self._build_palette(c) if palette is not None: palette.show() button.get_palette().set_content(palette) self._insert_item(create_toolbar, button, -1) button.show() self.radioList[button] = c.name if hasattr(c, 'constructor'): self._constructors[c.name] = \ self.game.toolList[c.name].constructor def __icon_path(self, name): activity_path = activity.get_bundle_path() icon_path = os.path.join(activity_path, 'icons', name + '.svg') return icon_path def _build_palette(self, tool): if tool.palette_enabled: if tool.palette_mode == tools.PALETTE_MODE_ICONS: grid = Gtk.Grid() for s, settings in enumerate(tool.palette_settings): self.game.toolList[tool.name].buttons.append([]) for i, icon_value in enumerate(settings['icon_values']): if i == 0: button = RadioToolButton(group=None) firstbutton = button else: button = RadioToolButton(group=firstbutton) button.set_icon_name(settings['icons'][i]) button.connect('clicked', self._palette_icon_clicked, tool.name, s, settings['name'], icon_value) grid.attach(button, i, s, 1, 1) self.game.toolList[tool.name].buttons[s].append(button) button.show() if settings['active'] == settings['icons'][i]: button.set_icon_name(settings['icons'][i] + '-selected') button.set_active(True) return grid else: return None def _palette_icon_clicked(self, widget, toolname, s, value_name, value): for tool in tools.allTools: if tool.name == toolname: # Radio buttons are not highlighting in the palette # so adding highlight by hand # See http://bugs.sugarlabs.org/ticket/305 setting = self.game.toolList[tool.name].palette_settings[s] for i, button in enumerate( self.game.toolList[tool.name].buttons[s]): icon_name = setting['icons'][i] if button == widget: button.set_icon_name(icon_name + '-selected') else: button.set_icon_name(icon_name) if hasattr(tool, 'palette_data_type'): tool.palette_data_type = value else: tool.palette_data[value_name] = value def clear_trace_alert_cb(self, alert, response): self.remove_alert(alert) if response is Gtk.ResponseType.OK: self.game.full_pos_list = [[] for _ in self.game.full_pos_list] self.game.tracked_bodies = 0 def clear_trace_cb(self, button): clear_trace_alert = ConfirmationAlert() clear_trace_alert.props.title = _('Are You Sure?') clear_trace_alert.props.msg = \ _('All trace points will be erased. This cannot be undone!') clear_trace_alert.connect('response', self.clear_trace_alert_cb) self.add_alert(clear_trace_alert) def stop_play_cb(self, button): pygame.event.post( pygame.event.Event(pygame.USEREVENT, action='stop_start_toggle')) self.stop_play_state = not self.stop_play_state if self.stop_play_state: self.stop_play.set_icon_name('media-playback-stop') self.stop_play.set_tooltip(_('Stop')) self.stop_play_toolbar.set_icon_name('media-playback-stop') else: self.stop_play.set_icon_name('media-playback-start') self.stop_play.set_tooltip(_('Start')) self.stop_play_toolbar.set_icon_name('media-playback-start') def clear_all_cb(self, button): def clear_all_alert_cb(alert, response_id): self.remove_alert(alert) if response_id is Gtk.ResponseType.OK: pygame.event.post( pygame.event.Event(pygame.USEREVENT, action='clear_all')) if len(self.game.world.world.bodies) > 2: clear_all_alert = ConfirmationAlert() clear_all_alert.props.title = _('Are You Sure?') clear_all_alert.props.msg = \ _('All your work will be discarded. This cannot be undone!') clear_all_alert.connect('response', clear_all_alert_cb) self.add_alert(clear_all_alert) def radioClicked(self, button): pygame.event.post( pygame.event.Event(pygame.USEREVENT, action=self.radioList[button])) def _focus_event(self, event, data=None): ''' Send focus events to pygame to allow it to idle when in background. ''' if data.state == Gdk.VisibilityState.FULLY_OBSCURED: pygame.event.post( pygame.event.Event(pygame.USEREVENT, action='focus_out')) else: self.game.show_fake_cursor = True pygame.event.post( pygame.event.Event(pygame.USEREVENT, action='focus_in')) def _export_json_cb(self, button): jobject = datastore.create() jobject.metadata['title'] = _('Physics export') jobject.metadata['mime_type'] = 'text/plain' tmp_dir = os.path.join(self.get_activity_root(), 'instance') fd, file_path = tempfile.mkstemp(dir=tmp_dir) os.close(fd) self.game.world.json_save(file_path) jobject.set_file_path(file_path) datastore.write(jobject) def _window_event(self, window, event): ''' Send focus out event to pygame when switching to a desktop view. ''' if event.changed_mask & Gdk.WindowState.ICONIFIED: pygame.event.post( pygame.event.Event(pygame.USEREVENT, action='focus_out')) def _restore_cursor(self): ''' No longer waiting, so restore standard cursor. ''' if not hasattr(self, 'get_window'): return self.get_window().set_cursor(self.old_cursor) def _waiting_cursor(self): ''' Waiting, so set watch cursor. ''' if not hasattr(self, 'get_window'): return self.old_cursor = self.get_window().get_cursor() self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH)) def __message_cb(self, collab, buddy, msg): ''' Data is passed as tuples: cmd:text ''' action = msg.get('action') if action != 'text': return text = msg['text'] dispatch_table = { 'C': self._construct_shared_circle, 'B': self._construct_shared_box, 'T': self._construct_shared_triangle, 'P': self._construct_shared_polygon, 'M': self._construct_shared_magicpen, 'j': self._add_shared_joint, 'p': self._add_shared_pin, 'm': self._add_shared_motor, 't': self._add_shared_track, 'c': self._add_shared_chain, } logging.debug('<<< %s' % (text[0])) dispatch_table[text[0]](text[2:]) def _construct_shared_circle(self, data): circle_data = json.loads(data) pos = circle_data[0] radius = circle_data[1] density = circle_data[2] restitution = circle_data[3] friction = circle_data[4] self._constructors['Circle'](pos, radius, density, restitution, friction, share=False) def _construct_shared_box(self, data): box_data = json.loads(data) pos1 = box_data[0] pos2 = box_data[1] density = box_data[2] restitution = box_data[3] friction = box_data[4] self._constructors['Box'](pos1, pos2, density, restitution, friction, share=False) def _construct_shared_triangle(self, data): triangle_data = json.loads(data) pos1 = triangle_data[0] pos2 = triangle_data[1] density = triangle_data[2] restitution = triangle_data[3] friction = triangle_data[4] self._constructors['Triangle'](pos1, pos2, density, restitution, friction, share=False) def _construct_shared_polygon(self, data): polygon_data = json.loads(data) vertices = polygon_data[0] density = polygon_data[1] restitution = polygon_data[2] friction = polygon_data[3] self._constructors['Polygon'](vertices, density, restitution, friction, share=False) def _construct_shared_magicpen(self, data): magicpen_data = json.loads(data) vertices = magicpen_data[0] density = magicpen_data[1] restitution = magicpen_data[2] friction = magicpen_data[3] self._constructors['Magicpen'](vertices, density, restitution, friction, share=False) def _add_shared_joint(self, data): joint_data = json.loads(data) pos1 = joint_data[0] pos2 = joint_data[1] self._constructors['Joint'](pos1, pos2, share=False) def _add_shared_pin(self, data): joint_data = json.loads(data) pos = joint_data[0] self._constructors['Pin'](pos, share=False) def _add_shared_motor(self, data): joint_data = json.loads(data) pos = joint_data[0] speed = joint_data[1] self._constructors['Motor'](pos, speed, share=False) def _add_shared_track(self, data): joint_data = json.loads(data) pos = joint_data[0] color = joint_data[1] self._constructors['Track'](pos, color, share=False) def _add_shared_chain(self, data): joint_data = json.loads(data) vertices = joint_data[0] link_length = joint_data[1] radius = joint_data[2] self._constructors['Chain'](vertices, link_length, radius, share=False) def send_event(self, text): self._collab.post(dict(action='text', text=text)) def _load_project(self, button): chooser = ObjectChooser(parent=self) result = chooser.run() if result == Gtk.ResponseType.ACCEPT: dsobject = chooser.get_selected_object() file_path = dsobject.get_file_path() self.__load_game(file_path, True) chooser.destroy() def __load_game(self, file_path, journal=False): confirmation_alert = ConfirmationAlert() confirmation_alert.props.title = _('Are You Sure?') confirmation_alert.props.msg = \ _('All your work will be discarded. This cannot be undone!') def action(alert, response): self.remove_alert(alert) if response is not Gtk.ResponseType.OK: return try: f = open(file_path, 'r') # Test if the file is valid project. json.loads(f.read()) f.close() self.read_file(file_path) except: title = _('Load project from journal') if not journal: title = _('Load example') msg = _('Error: Cannot open Physics project from this file.') alert = NotifyAlert(5) alert.props.title = title alert.props.msg = msg alert.connect('response', lambda alert, response: self.remove_alert(alert)) self.add_alert(alert) confirmation_alert.connect('response', action) self.add_alert(confirmation_alert) def _create_store(self, widget=None): if self._sample_window is None: self._sample_box = Gtk.EventBox() vbox = Gtk.VBox() self._sample_window = Gtk.ScrolledWindow() self._sample_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self._sample_window.set_border_width(4) self._sample_window.set_size_request(Gdk.Screen.width() / 2, Gdk.Screen.height() / 2) self._sample_window.show() store = Gtk.ListStore(GdkPixbuf.Pixbuf, str) icon_view = Gtk.IconView() icon_view.set_model(store) icon_view.set_selection_mode(Gtk.SelectionMode.SINGLE) icon_view.connect('selection-changed', self._sample_selected, store) icon_view.set_pixbuf_column(0) icon_view.grab_focus() self._sample_window.add(icon_view) icon_view.show() self._fill_samples_list(store) title = Gtk.HBox() title_label = Gtk.Label(_("Select a sample...")) separator = Gtk.HSeparator() separator.props.expand = True separator.props.visible = False btn = Gtk.Button.new_from_stock(Gtk.STOCK_CANCEL) btn.connect('clicked', self._cancel_clicked_cb) title.pack_start(title_label, False, False, 5) title.pack_start(separator, False, False, 0) title.pack_end(btn, False, False, 0) vbox.pack_start(title, False, False, 5) vbox.pack_end(self._sample_window, True, True, 0) self._sample_box.add(vbox) self._sample_box.show_all() self._notebook.add(self._sample_box) self._notebook.set_current_page(1) def _cancel_clicked_cb(self, button=None): self._notebook.set_current_page(0) def _get_selected_path(self, widget, store): try: iter_ = store.get_iter(widget.get_selected_items()[0]) image_path = store.get(iter_, 1)[0] return image_path, iter_ except: return None def _sample_selected(self, widget, store): selected = self._get_selected_path(widget, store) if selected is None: self._selected_sample = None self._cancel_clicked_cb() return image_path, _iter = selected iter_ = store.get_iter(widget.get_selected_items()[0]) image_path = store.get(iter_, 1)[0] self._selected_sample = image_path self._cancel_clicked_cb() self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.WATCH)) GLib.idle_add(self._sample_loader) def _sample_loader(self): # Convert from thumbnail path to sample path basename = os.path.basename(self._selected_sample)[:-4] file_path = os.path.join(activity.get_bundle_path(), 'samples', basename + '.json') self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.LEFT_PTR)) self.__load_game(file_path) def _fill_samples_list(self, store): ''' Append images from the artwork_paths to the store. ''' for filepath in self._scan_for_samples(): pixbuf = None pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filepath, 100, 100) store.append([pixbuf, filepath]) def _scan_for_samples(self): samples = sorted( glob.glob( os.path.join(activity.get_bundle_path(), 'samples', 'thumbnails', '*.png'))) return samples
class YupanaActivity(activity.Activity): """ Yupana counting device """ def __init__(self, handle): """ Initialize the toolbars and the yupana """ activity.Activity.__init__(self, handle) self.nick = profile.get_nick_name() self._reload_custom = False if profile.get_color() is not None: self.colors = profile.get_color().to_string().split(',') else: self.colors = ['#A0FFA0', '#FF8080'] self._setup_toolbars() # Create a canvas canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), \ Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() self.show_all() self._yupana = Yupana(canvas, parent=self, colors=self.colors) self._setup_collab() if 'dotlist' in self.metadata: self._restore() else: self._yupana.new_yupana(mode='ten') self._make_custom_toolbar() if self._reload_custom: self._custom_cb() def _setup_toolbars(self): """ Setup the toolbars. """ self.max_participants = 4 yupana_toolbar = Gtk.Toolbar() self.custom_toolbar = Gtk.Toolbar() toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() yupana_toolbar_button = ToolbarButton(label=_("Mode"), page=yupana_toolbar, icon_name='preferences-system') yupana_toolbar.show() toolbox.toolbar.insert(yupana_toolbar_button, -1) yupana_toolbar_button.show() custom_toolbar_button = ToolbarButton(label=_("Custom"), page=self.custom_toolbar, icon_name='view-source') self.custom_toolbar.show() toolbox.toolbar.insert(custom_toolbar_button, -1) custom_toolbar_button.show() self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar self._new_yupana_button = button_factory( 'edit-delete', self.toolbar, self._new_yupana_cb, tooltip=_('Clear the yupana.')) separator_factory(yupana_toolbar, False, True) self.ten_button = radio_factory('ten', yupana_toolbar, self._ten_cb, tooltip=_('decimal mode'), group=None) self.twenty_button = radio_factory('twenty', yupana_toolbar, self._twenty_cb, tooltip=_('base-twenty mode'), group=self.ten_button) self.factor_button = radio_factory('factor', yupana_toolbar, self._factor_cb, tooltip=_('prime-factor mode'), group=self.ten_button) self.fibonacci_button = radio_factory('fibonacci', yupana_toolbar, self._fibonacci_cb, tooltip=_('Fibonacci mode'), group=self.ten_button) self.custom_button = radio_factory('view-source', yupana_toolbar, self._custom_cb, tooltip=_('custom mode'), group=self.ten_button) separator_factory(self.toolbar, False, False) self.status = label_factory(self.toolbar, '', width=200) self.status.set_label(_('decimal mode')) separator_factory(toolbox.toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() def _make_custom_toolbar(self): self._ones = entry_factory(str(self._yupana.custom[0]), self.custom_toolbar, tooltip=_('one row')) self._twos = entry_factory(str(self._yupana.custom[1]), self.custom_toolbar, tooltip=_('two row')) self._threes = entry_factory(str(self._yupana.custom[2]), self.custom_toolbar, tooltip=_('three row')) self._fives = entry_factory(str(self._yupana.custom[3]), self.custom_toolbar, tooltip=_('five row')) separator_factory(self.custom_toolbar, False, True) self._base = entry_factory(str(self._yupana.custom[4]), self.custom_toolbar, tooltip=_('base')) separator_factory(self.custom_toolbar, False, True) button_factory('view-refresh', self.custom_toolbar, self._custom_cb, tooltip=_('Reload custom values.')) def _new_yupana_cb(self, button=None): ''' Start a new yupana. ''' self._yupana.new_yupana() def _ten_cb(self, button=None): self._yupana.new_yupana(mode='ten') self.status.set_label(_('decimal mode')) def _twenty_cb(self, button=None): self._yupana.new_yupana(mode='twenty') self.status.set_label(_('base-twenty mode')) def _factor_cb(self, button=None): self._yupana.new_yupana(mode='factor') self.status.set_label(_('prime-factor mode')) def _fibonacci_cb(self, button=None): self._yupana.new_yupana(mode='fibonacci') self.status.set_label(_('Fibonacci mode')) def _custom_cb(self, button=None): if hasattr(self, '_ones'): self._yupana.custom[0] = int(self._ones.get_text()) self._yupana.custom[1] = int(self._twos.get_text()) self._yupana.custom[2] = int(self._threes.get_text()) self._yupana.custom[3] = int(self._fives.get_text()) self._yupana.custom[4] = int(self._base.get_text()) self._reload_custom = False else: self._reload_custom = True self._yupana.new_yupana(mode='custom') self.status.set_label(_('custom mode')) def write_file(self, file_path): """ Write the grid status to the Journal """ [mode, dot_list] = self._yupana.save_yupana() self.metadata['mode'] = mode self.metadata['custom'] = '' for i in range(5): self.metadata['custom'] += str(self._yupana.custom[i]) self.metadata['custom'] += ' ' self.metadata['dotlist'] = '' for dot in dot_list: self.metadata['dotlist'] += str(dot) if dot_list.index(dot) < len(dot_list) - 1: self.metadata['dotlist'] += ' ' self.metadata['label'] = self._yupana.get_label() def _restore(self): """ Restore the yupana state from metadata """ if 'custom' in self.metadata: values = self.metadata['custom'].split() for i in range(5): self._yupana.custom[i] = int(values[i]) if 'mode' in self.metadata: if self.metadata['mode'] == 'ten': self.ten_button.set_active(True) elif self.metadata['mode'] == 'twenty': self.twenty_button.set_active(True) elif self.metadata['mode'] == 'factor': self.factor_button.set_active(True) elif self.metadata['mode'] == 'fibonacci': self.fibonacci_button.set_active(True) else: self.custom_button.set_active(True) if 'dotlist' in self.metadata: dot_list = [] dots = self.metadata['dotlist'].split() for dot in dots: dot_list.append(int(dot)) self._yupana.restore_yupana(self.metadata['mode'], dot_list) self._yupana.set_label(self.metadata['label']) # Collaboration-related methods def _setup_collab(self): """ Setup the Presence Service. """ self.initiating = None # sharing (True) or joining (False) self.connect('shared', self._shared_cb) self.connect('joined', self._joined_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) self._collab.connect('joined', self._joined_cb) self._collab.setup() def set_data(self, data): pass def get_data(self): return None def _shared_cb(self, activity): """ Either set up initial share...""" self.after_share_join(True) def _joined_cb(self, activity): """ ...or join an exisiting share. """ self.after_share_join(False) def after_share_join(self, sharer): """ Joining and sharing are mostly the same... """ self.initiating = sharer self.waiting_for_hand = not sharer self._yupana.set_sharing(True) def _message_cb(self, collab, buddy, msg): command = msg.get('command') payload = msg.get('payload') if command == 'new_game': '''Get a new yupana grid''' self._receive_new_yupana(payload) elif command == 'played': '''Get a dot click''' self._receive_dot_click(payload) elif command == 'label': self._yupana.set_label(payload) def send_new_yupana(self): ''' Send a new orientation, grid to all players ''' self._collab.post( dict(command='new_game', payload=json_dump(self._yupana.save_yupana()))) def _receive_new_yupana(self, payload): ''' Sharer can start a new yupana. ''' mode, dot_list = json_load(payload) self._yupana.restore_yupana(mode, dot_list) def send_dot_click(self, dot, color): ''' Send a dot click to all the players ''' self._collab.post( dict(command='played', payload=json_dump([dot, color]))) def _receive_dot_click(self, payload): ''' When a dot is clicked, everyone should change its color. ''' (dot, color) = json_load(payload) self._yupana.remote_button_press(dot, color) def send_label(self, label): self._collab.post(dict(command='label', payload=label))
class WebActivity(activity.Activity): def __init__(self, handle): activity.Activity.__init__(self, handle) self._force_close = False if incompatible: return self._incompatible() self._collab = CollabWrapper(self) self._collab.message.connect(self.__message_cb) _logger.debug('Starting the web activity') # TODO PORT # session = WebKit2.get_default_session() # session.set_property('accept-language-auto', True) # session.set_property('ssl-use-system-ca-file', True) # session.set_property('ssl-strict', False) # But of a hack, but webkit doesn't let us change the cookie jar # contents, we we can just pre-seed it cookie_jar = SoupGNOME.CookieJarSqlite(filename=_cookies_db_path, read_only=False) _seed_xs_cookie(cookie_jar) del cookie_jar context = WebKit2.WebContext.get_default() cookie_manager = context.get_cookie_manager() cookie_manager.set_persistent_storage( _cookies_db_path, WebKit2.CookiePersistentStorage.SQLITE) # FIXME # downloadmanager.remove_old_parts() context.connect('download-started', self.__download_requested_cb) self._tabbed_view = TabbedView(self) self._tabbed_view.connect('focus-url-entry', self._on_focus_url_entry) self._tabbed_view.connect('switch-page', self.__switch_page_cb) self._titled_tray = TitledTray(_('Bookmarks')) self._tray = self._titled_tray.tray self.set_tray(self._titled_tray, Gtk.PositionType.BOTTOM) self._tray_links = {} self.model = Model() self.model.add_link_signal.connect(self._add_link_model_cb) self._primary_toolbar = PrimaryToolbar(self._tabbed_view, self) self._edit_toolbar = EditToolbar(self) self._view_toolbar = ViewToolbar(self) self._primary_toolbar.connect('add-link', self.__link_add_button_cb) self._primary_toolbar.connect('remove-link', self.__link_remove_button_cb) self._primary_toolbar.connect('go-home', self._go_home_button_cb) self._primary_toolbar.connect('go-library', self._go_library_button_cb) self._primary_toolbar.connect('set-home', self._set_home_button_cb) self._primary_toolbar.connect('reset-home', self._reset_home_button_cb) self._edit_toolbar_button = ToolbarButton( page=self._edit_toolbar, icon_name='toolbar-edit') self._primary_toolbar.toolbar.insert( self._edit_toolbar_button, 1) view_toolbar_button = ToolbarButton( page=self._view_toolbar, icon_name='toolbar-view') self._primary_toolbar.toolbar.insert( view_toolbar_button, 2) self._primary_toolbar.show_all() self.set_toolbar_box(self._primary_toolbar) self.set_canvas(self._tabbed_view) self._tabbed_view.show() self.connect('key-press-event', self._key_press_cb) if handle.uri: self._tabbed_view.current_browser.load_uri(handle.uri) elif not self._jobject.file_path: # TODO: we need this hack until we extend the activity API for # opening URIs and default docs. self._tabbed_view.load_homepage() # README: this is a workaround to remove old temp file # http://bugs.sugarlabs.org/ticket/3973 self._cleanup_temp_files() self._collab.setup() def __download_requested_cb(self, context, download): if hasattr(self, 'busy'): while self.unbusy() > 0: continue logging.debug('__download_requested_cb %r', download.get_request().get_uri()) downloadmanager.add_download(download, self) return True def fullscreen(self): activity.Activity.fullscreen(self) self._tabbed_view.set_show_tabs(False) def unfullscreen(self): activity.Activity.unfullscreen(self) # Always show tabs here, because they are automatically hidden # if it is like a video fullscreening. We ALWAYS need to make # sure these are visible. Don't make the user lost self._tabbed_view.set_show_tabs(True) def _cleanup_temp_files(self): """Removes temporary files generated by Download Manager that were cancelled by the user or failed for any reason. There is a bug in GLib that makes this to happen: https://bugzilla.gnome.org/show_bug.cgi?id=629301 """ try: uptime_proc = open('/proc/uptime', 'r').read() uptime = int(float(uptime_proc.split()[0])) except EnvironmentError: logging.warning('/proc/uptime could not be read') uptime = None temp_path = os.path.join(self.get_activity_root(), 'instance') now = int(time.time()) cutoff = now - 24 * 60 * 60 # yesterday if uptime is not None: boot_time = now - uptime cutoff = max(cutoff, boot_time) for f in os.listdir(temp_path): if f.startswith('.goutputstream-'): fpath = os.path.join(temp_path, f) mtime = int(os.path.getmtime(fpath)) if mtime < cutoff: logging.warning('Removing old temporary file: %s', fpath) try: os.remove(fpath) except EnvironmentError: logging.error('Temporary file could not be ' 'removed: %s', fpath) def _on_focus_url_entry(self, gobject): self._primary_toolbar.entry.grab_focus() def _get_data_from_file_path(self, file_path): fd = open(file_path, 'r') try: data = fd.read() finally: fd.close() return data def _get_save_as(self): if not hasattr(profile, 'get_save_as'): return False return profile.get_save_as() def read_file(self, file_path): if self.metadata['mime_type'] == 'text/plain': data = self._get_data_from_file_path(file_path) self.model.deserialize(data) for link in self.model.data['shared_links']: _logger.debug('read: url=%s title=%s d=%s' % (link['url'], link['title'], link['color'])) self._add_link_totray(link['url'], b64decode(link['thumb']), link['color'], link['title'], link['owner'], -1, link['hash'], link.get('notes')) logging.debug('########## reading %s', data) if 'session_state' in self.model.data: self._tabbed_view.set_session_state( self.model.data['session_state']) else: self._tabbed_view.set_legacy_history( self.model.data['history'], self.model.data['currents']) for number, tab in enumerate(self.model.data['currents']): tab_page = self._tabbed_view.get_nth_page(number) zoom_level = tab.get('zoom_level') if zoom_level is not None: tab_page.browser.set_zoom_level(zoom_level) tab_page.browser.grab_focus() self._tabbed_view.set_current_page(self.model.data['current_tab']) elif self.metadata['mime_type'] == 'text/uri-list': data = self._get_data_from_file_path(file_path) uris = mime.split_uri_list(data) if len(uris) == 1: self._tabbed_view.props.current_browser.load_uri(uris[0]) else: _logger.error('Open uri-list: Does not support' 'list of multiple uris by now.') else: file_uri = 'file://' + file_path self._tabbed_view.props.current_browser.load_uri(file_uri) self._tabbed_view.props.current_browser.grab_focus() def write_file(self, file_path): if not hasattr(self, '_tabbed_view'): _logger.debug('Called write_file before the tabbed_view was made') return if not self.metadata['mime_type']: self.metadata['mime_type'] = 'text/plain' if self.metadata['mime_type'] == 'text/plain': browser = self._tabbed_view.current_browser if not self._jobject.metadata['title_set_by_user'] == '1' and \ not self._get_save_as(): if browser.props.title is None: self.metadata['title'] = _('Untitled') else: self.metadata['title'] = browser.props.title self.model.data['history'] = self._tabbed_view.get_legacy_history() current_tab = self._tabbed_view.get_current_page() self.model.data['current_tab'] = current_tab self.model.data['currents'] = [] for n in range(0, self._tabbed_view.get_n_pages()): tab_page = self._tabbed_view.get_nth_page(n) n_browser = tab_page.browser if n_browser is not None: uri = n_browser.get_uri() history_index = n_browser.get_history_index() info = {'title': n_browser.props.title, 'url': uri, 'history_index': history_index, 'zoom_level': n_browser.get_zoom_level()} self.model.data['currents'].append(info) self.model.data['session_state'] = \ self._tabbed_view.get_state() f = open(file_path, 'w') try: logging.debug('########## writing %s', self.model.serialize()) f.write(self.model.serialize()) finally: f.close() def __link_add_button_cb(self, button): self._add_link() def __link_remove_button_cb(self, button): browser = self._tabbed_view.props.current_browser uri = browser.get_uri() self.__link_removed_cb(None, sha1(uri).hexdigest()) def _go_home_button_cb(self, button): self._tabbed_view.load_homepage() def _go_library_button_cb(self, button): self._tabbed_view.load_homepage(ignore_settings=True) def _set_home_button_cb(self, button): self._tabbed_view.set_homepage() self._alert(_('The initial page was configured')) def _reset_home_button_cb(self, button): self._tabbed_view.reset_homepage() self._alert(_('The default initial page was configured')) def _alert(self, title, text=None): alert = NotifyAlert(timeout=5) alert.props.title = title alert.props.msg = text self.add_alert(alert) alert.connect('response', self._alert_cancel_cb) alert.show() def _alert_cancel_cb(self, alert, response_id): self.remove_alert(alert) def _key_press_cb(self, widget, event): browser = self._tabbed_view.props.current_browser if event.get_state() & Gdk.ModifierType.CONTROL_MASK: if event.keyval == Gdk.KEY_f: self._edit_toolbar_button.set_expanded(True) self._edit_toolbar.search_entry.grab_focus() return True if event.keyval == Gdk.KEY_l: self._primary_toolbar.entry.grab_focus() return True if event.keyval == Gdk.KEY_equal: # On US keyboards, KEY_equal is KEY_plus without # SHIFT_MASK, so for convenience treat this as the # same as the zoom in accelerator configured in # WebKit2 browser.zoom_in() return True if event.keyval == Gdk.KEY_t: self._tabbed_view.add_tab() return True if event.keyval == Gdk.KEY_w: self._tabbed_view.close_tab() return True # FIXME: copy and paste is supposed to be handled by # Gtk.Entry, but does not work when we catch # key-press-event and return False. if self._primary_toolbar.entry.is_focus(): if event.keyval == Gdk.KEY_c: self._primary_toolbar.entry.copy_clipboard() return True if event.keyval == Gdk.KEY_v: self._primary_toolbar.entry.paste_clipboard() return True return False if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Down, Gdk.KEY_KP_Left, Gdk.KEY_KP_Right): scrolled_window = browser.get_parent() if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Down): adjustment = scrolled_window.get_vadjustment() elif event.keyval in (Gdk.KEY_KP_Left, Gdk.KEY_KP_Right): adjustment = scrolled_window.get_hadjustment() value = adjustment.get_value() step = adjustment.get_step_increment() if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Left): adjustment.set_value(value - step) else: adjustment.set_value(value + step) return True if event.keyval == Gdk.KEY_Escape: browser.stop_loading() return False # allow toolbar entry to handle escape too return False def _add_link(self): ''' take screenshot and add link info to the model ''' browser = self._tabbed_view.props.current_browser ui_uri = browser.get_uri() if self.model.has_link(ui_uri): return buf = b64encode(self._get_screenshot()) timestamp = time.time() args = (ui_uri, browser.props.title, buf, profile.get_nick_name(), profile.get_color().to_string(), timestamp) self.model.add_link(*args, by_me=True) self._collab.post({'type': 'add_link', 'args': args}) def __message_cb(self, collab, buddy, message): type_ = message.get('type') if type_ == 'add_link': self.model.add_link(*message['args']) elif type_ == 'add_link_from_info': self.model.add_link_from_info(message['dict']) elif type_ == 'remove_link': self.remove_link(message['hash']) def get_data(self): return self.model.data def set_data(self, data): for link in data['shared_links']: if link['hash'] not in self.model.get_links_ids(): self.model.add_link_from_info(link) # FIXME: Case where buddy has updated link desciption their_model = Model() their_model.data = data for link in self.model.data['shared_links']: if link['hash'] not in their_model.get_links_ids(): self._collab.post({'type': 'add_link_from_info', 'dict': link}) def _add_link_model_cb(self, model, index, by_me): ''' receive index of new link from the model ''' link = self.model.data['shared_links'][index] widget = self._add_link_totray( link['url'], b64decode(link['thumb']), link['color'], link['title'], link['owner'], index, link['hash'], link.get('notes')) if by_me: animator = Animator(1, widget=self) animator.add(AddLinkAnimation( self, self._tabbed_view.props.current_browser, widget)) animator.start() def _add_link_totray(self, url, buf, color, title, owner, index, hash, notes=None): ''' add a link to the tray ''' item = LinkButton(buf, color, title, owner, hash, notes) item.connect('clicked', self._link_clicked_cb, url) item.connect('remove_link', self.__link_removed_cb) item.notes_changed_signal.connect(self.__link_notes_changed) # use index to add to the tray self._tray_links[hash] = item self._tray.add_item(item, index) item.show() self._view_toolbar.traybutton.props.sensitive = True self._view_toolbar.traybutton.props.active = True self._view_toolbar.update_traybutton_tooltip() return item def __link_removed_cb(self, button, hash): self.remove_link(hash) self._collab.post({'type': 'remove_link', 'hash': hash}) def remove_link(self, hash): ''' remove a link from tray and delete it in the model ''' self._tray_links[hash].hide() self._tray_links[hash].destroy() del self._tray_links[hash] self.model.remove_link(hash) if len(self._tray.get_children()) == 0: self._view_toolbar.traybutton.props.sensitive = False self._view_toolbar.traybutton.props.active = False self._view_toolbar.update_traybutton_tooltip() def __link_notes_changed(self, button, hash, notes): self.model.change_link_notes(hash, notes) def _link_clicked_cb(self, button, url): ''' an item of the link tray has been clicked ''' browser = self._tabbed_view.add_tab() browser.load_uri(url) browser.grab_focus() def _get_screenshot(self): browser = self._tabbed_view.props.current_browser window = browser.get_window() width, height = window.get_width(), window.get_height() thumb_surface = Gdk.Window.create_similar_surface( window, cairo.CONTENT_COLOR, THUMB_WIDTH, THUMB_HEIGHT) cairo_context = cairo.Context(thumb_surface) thumb_scale_w = THUMB_WIDTH * 1.0 / width thumb_scale_h = THUMB_HEIGHT * 1.0 / height cairo_context.scale(thumb_scale_w, thumb_scale_h) Gdk.cairo_set_source_window(cairo_context, window, 0, 0) cairo_context.paint() thumb_str = StringIO.StringIO() thumb_surface.write_to_png(thumb_str) return thumb_str.getvalue() def can_close(self): if self._force_close: return True elif downloadmanager.can_quit(): return True else: alert = Alert() alert.props.title = ngettext('Download in progress', 'Downloads in progress', downloadmanager.num_downloads()) message = ngettext('Stopping now will erase your download', 'Stopping now will erase your downloads', downloadmanager.num_downloads()) alert.props.msg = message cancel_icon = Icon(icon_name='dialog-cancel') cancel_label = ngettext('Continue download', 'Continue downloads', downloadmanager.num_downloads()) alert.add_button(Gtk.ResponseType.CANCEL, cancel_label, cancel_icon) stop_icon = Icon(icon_name='dialog-ok') alert.add_button(Gtk.ResponseType.OK, _('Stop'), stop_icon) stop_icon.show() self.add_alert(alert) alert.connect('response', self.__inprogress_response_cb) alert.show() self.present() return False def __inprogress_response_cb(self, alert, response_id): self.remove_alert(alert) if response_id is Gtk.ResponseType.CANCEL: logging.debug('Keep on') elif response_id == Gtk.ResponseType.OK: logging.debug('Stop downloads and quit') self._force_close = True downloadmanager.remove_all_downloads() self.close() def __switch_page_cb(self, tabbed_view, page, page_num): if not hasattr(self, 'busy'): return browser = page._browser progress = browser.props.estimated_load_progress uri = browser.props.uri if progress < 1.0 and uri: self.busy() else: while self.unbusy() > 0: continue def get_document_path(self, async_cb, async_err_cb): browser = self._tabbed_view.props.current_browser browser.get_source(async_cb, async_err_cb) def get_canvas(self): return self._tabbed_view def _incompatible(self): ''' Display abbreviated activity user interface with alert ''' toolbox = ToolbarBox() stop = StopButton(self) toolbox.toolbar.add(stop) self.set_toolbar_box(toolbox) title = _('Activity not compatible with this system.') msg = _('Please downgrade activity and try again.') alert = Alert(title=title, msg=msg) alert.add_button(0, 'Stop', Icon(icon_name='activity-stop')) self.add_alert(alert) label = Gtk.Label(_('Uh oh, WebKit2 is too old. ' 'Browse-200 and later require WebKit2 API 4.0, ' 'sorry!')) self.set_canvas(label) ''' Workaround: start Terminal activity, then type sugar-erase-bundle org.laptop.WebActivity then in My Settings, choose Software Update, which will offer older Browse. ''' alert.connect('response', self.__incompatible_response_cb) stop.connect('clicked', self.__incompatible_stop_clicked_cb, alert) self.show_all() def __incompatible_stop_clicked_cb(self, button, alert): self.remove_alert(alert) def __incompatible_response_cb(self, alert, response): self.remove_alert(alert) self.close()
def __init__(self, handle): activity.Activity.__init__(self, handle) self._force_close = False if incompatible: return self._incompatible() self._collab = CollabWrapper(self) self._collab.message.connect(self.__message_cb) _logger.debug('Starting the web activity') # TODO PORT # session = WebKit2.get_default_session() # session.set_property('accept-language-auto', True) # session.set_property('ssl-use-system-ca-file', True) # session.set_property('ssl-strict', False) # But of a hack, but webkit doesn't let us change the cookie jar # contents, we we can just pre-seed it cookie_jar = SoupGNOME.CookieJarSqlite(filename=_cookies_db_path, read_only=False) _seed_xs_cookie(cookie_jar) del cookie_jar context = WebKit2.WebContext.get_default() cookie_manager = context.get_cookie_manager() cookie_manager.set_persistent_storage( _cookies_db_path, WebKit2.CookiePersistentStorage.SQLITE) # FIXME # downloadmanager.remove_old_parts() context.connect('download-started', self.__download_requested_cb) self._tabbed_view = TabbedView(self) self._tabbed_view.connect('focus-url-entry', self._on_focus_url_entry) self._tabbed_view.connect('switch-page', self.__switch_page_cb) self._titled_tray = TitledTray(_('Bookmarks')) self._tray = self._titled_tray.tray self.set_tray(self._titled_tray, Gtk.PositionType.BOTTOM) self._tray_links = {} self.model = Model() self.model.add_link_signal.connect(self._add_link_model_cb) self._primary_toolbar = PrimaryToolbar(self._tabbed_view, self) self._edit_toolbar = EditToolbar(self) self._view_toolbar = ViewToolbar(self) self._primary_toolbar.connect('add-link', self.__link_add_button_cb) self._primary_toolbar.connect('remove-link', self.__link_remove_button_cb) self._primary_toolbar.connect('go-home', self._go_home_button_cb) self._primary_toolbar.connect('go-library', self._go_library_button_cb) self._primary_toolbar.connect('set-home', self._set_home_button_cb) self._primary_toolbar.connect('reset-home', self._reset_home_button_cb) self._edit_toolbar_button = ToolbarButton(page=self._edit_toolbar, icon_name='toolbar-edit') self._primary_toolbar.toolbar.insert(self._edit_toolbar_button, 1) view_toolbar_button = ToolbarButton(page=self._view_toolbar, icon_name='toolbar-view') self._primary_toolbar.toolbar.insert(view_toolbar_button, 2) self._primary_toolbar.show_all() self.set_toolbar_box(self._primary_toolbar) self.set_canvas(self._tabbed_view) self._tabbed_view.show() self.connect('key-press-event', self._key_press_cb) if handle.uri: self._tabbed_view.current_browser.load_uri(handle.uri) elif not self._jobject.file_path: # TODO: we need this hack until we extend the activity API for # opening URIs and default docs. self._tabbed_view.load_homepage() # README: this is a workaround to remove old temp file # http://bugs.sugarlabs.org/ticket/3973 self._cleanup_temp_files() self._collab.setup()
class FlipActivity(activity.Activity): """ Flip puzzle game """ def __init__(self, handle): """ Initialize the toolbars and the game board """ super(FlipActivity, self).__init__(handle) self.nick = profile.get_nick_name() if profile.get_color() is not None: self.colors = profile.get_color().to_string().split(',') else: self.colors = ['#A0FFA0', '#FF8080'] self._setup_toolbars() self._setup_dispatch_table() # Create a canvas canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() self.show_all() self._game = Game(canvas, parent=self, colors=self.colors) self.connect('shared', self._shared_cb) self.connect('joined', self._joined_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) self._collab.connect('joined', self._joined_cb) self._collab.setup() if 'dotlist' in self.metadata: self._restore() else: self._game.new_game() def _setup_toolbars(self): """ Setup the toolbars. """ self.max_participants = 4 toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar self._new_game_button_h = button_factory('new-game', self.toolbar, self._new_game_cb, tooltip=_('Start a game.')) self.status = label_factory(self.toolbar, '') separator_factory(toolbox.toolbar, True, False) self.solver = button_factory('help-toolbar', self.toolbar, self._solve_cb, tooltip=_('Solve the puzzle')) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() def _new_game_cb(self, button=None): ''' Start a new game. ''' self._game.new_game() def _solve_cb(self, button=None): ''' Solve the puzzle ''' self._game.solve() def write_file(self, file_path): """ Write the grid status to the Journal """ (dot_list, move_list) = self._game.save_game() self.metadata['dotlist'] = '' for dot in dot_list: self.metadata['dotlist'] += str(dot) if dot_list.index(dot) < len(dot_list) - 1: self.metadata['dotlist'] += ' ' self.metadata['movelist'] = '' for move in move_list: self.metadata['movelist'] += str(move) if move_list.index(move) < len(move_list) - 1: self.metadata['movelist'] += ' ' _logger.debug(self.metadata['movelist']) def _restore(self): """ Restore the game state from metadata """ dot_list = [] dots = self.metadata['dotlist'].split() for dot in dots: dot_list.append(int(dot)) if 'movelist' in self.metadata: move_list = [] moves = self.metadata['movelist'].split() for move in moves: move_list.append(int(move)) else: move_list = None _logger.debug(move_list) self._game.restore_game(dot_list, move_list) # Collaboration-related methods def set_data(self, data): pass def get_data(self): return None def _shared_cb(self, activity): """ Either set up initial share...""" self.after_share_join(True) def _joined_cb(self, activity): """ ...or join an exisiting share. """ self.after_share_join(False) def after_share_join(self, sharer): self.waiting_for_hand = not sharer self._game.set_sharing(True) def _setup_dispatch_table(self): ''' Associate tokens with commands. ''' self._processing_methods = { 'n': [self._receive_new_game, 'get a new game grid'], 'p': [self._receive_dot_click, 'get a dot click'], } def _message_cb(self, collab, buddy, msg): ''' Data from a tube has arrived. ''' command = msg.get('command') payload = msg.get('payload') self._processing_methods[command][0](payload) def send_new_game(self): ''' Send a new grid to all players ''' self.send_event('n', self._game.save_game()) def _receive_new_game(self, payload): ''' Sharer can start a new game. ''' (dot_list, move_list) = payload self._game.restore_game(dot_list, move_list) def send_dot_click(self, dot): ''' Send a dot click to all the players ''' self.send_event('p', dot) def _receive_dot_click(self, payload): ''' When a dot is clicked, everyone should change its color. ''' dot = payload self._game.remote_button_press(dot) def send_event(self, command, payload): """ Send event through the tube. """ self._collab.post({'command': command, 'payload': payload})
class WebActivity(activity.Activity): def __init__(self, handle): activity.Activity.__init__(self, handle) self._force_close = False if incompatible: return self._incompatible() self._collab = CollabWrapper(self) self._collab.message.connect(self.__message_cb) _logger.debug('Starting the web activity') # TODO PORT # session = WebKit2.get_default_session() # session.set_property('accept-language-auto', True) # session.set_property('ssl-use-system-ca-file', True) # session.set_property('ssl-strict', False) # But of a hack, but webkit doesn't let us change the cookie jar # contents, we we can just pre-seed it cookie_jar = SoupGNOME.CookieJarSqlite(filename=_cookies_db_path, read_only=False) _seed_xs_cookie(cookie_jar) del cookie_jar context = WebKit2.WebContext.get_default() cookie_manager = context.get_cookie_manager() cookie_manager.set_persistent_storage( _cookies_db_path, WebKit2.CookiePersistentStorage.SQLITE) # FIXME # downloadmanager.remove_old_parts() context.connect('download-started', self.__download_requested_cb) self._tabbed_view = TabbedView(self) self._tabbed_view.connect('focus-url-entry', self._on_focus_url_entry) self._tabbed_view.connect('switch-page', self.__switch_page_cb) self._titled_tray = TitledTray(_('Bookmarks')) self._tray = self._titled_tray.tray self.set_tray(self._titled_tray, Gtk.PositionType.BOTTOM) self._tray_links = {} self.model = Model() self.model.add_link_signal.connect(self._add_link_model_cb) self._primary_toolbar = PrimaryToolbar(self._tabbed_view, self) self._edit_toolbar = EditToolbar(self) self._view_toolbar = ViewToolbar(self) self._primary_toolbar.connect('add-link', self.__link_add_button_cb) self._primary_toolbar.connect('remove-link', self.__link_remove_button_cb) self._primary_toolbar.connect('go-home', self._go_home_button_cb) self._primary_toolbar.connect('go-library', self._go_library_button_cb) self._primary_toolbar.connect('set-home', self._set_home_button_cb) self._primary_toolbar.connect('reset-home', self._reset_home_button_cb) self._edit_toolbar_button = ToolbarButton(page=self._edit_toolbar, icon_name='toolbar-edit') self._primary_toolbar.toolbar.insert(self._edit_toolbar_button, 1) view_toolbar_button = ToolbarButton(page=self._view_toolbar, icon_name='toolbar-view') self._primary_toolbar.toolbar.insert(view_toolbar_button, 2) self._primary_toolbar.show_all() self.set_toolbar_box(self._primary_toolbar) self.set_canvas(self._tabbed_view) self._tabbed_view.show() self.connect('key-press-event', self._key_press_cb) if handle.uri: self._tabbed_view.current_browser.load_uri(handle.uri) elif not self._jobject.file_path: # TODO: we need this hack until we extend the activity API for # opening URIs and default docs. self._tabbed_view.load_homepage() # README: this is a workaround to remove old temp file # http://bugs.sugarlabs.org/ticket/3973 self._cleanup_temp_files() self._collab.setup() def __download_requested_cb(self, context, download): if hasattr(self, 'busy'): while self.unbusy() > 0: continue logging.debug('__download_requested_cb %r', download.get_request().get_uri()) downloadmanager.add_download(download, self) return True def fullscreen(self): activity.Activity.fullscreen(self) self._tabbed_view.set_show_tabs(False) def unfullscreen(self): activity.Activity.unfullscreen(self) # Always show tabs here, because they are automatically hidden # if it is like a video fullscreening. We ALWAYS need to make # sure these are visible. Don't make the user lost self._tabbed_view.set_show_tabs(True) def _cleanup_temp_files(self): """Removes temporary files generated by Download Manager that were cancelled by the user or failed for any reason. There is a bug in GLib that makes this to happen: https://bugzilla.gnome.org/show_bug.cgi?id=629301 """ try: uptime_proc = open('/proc/uptime', 'r').read() uptime = int(float(uptime_proc.split()[0])) except EnvironmentError: logging.warning('/proc/uptime could not be read') uptime = None temp_path = os.path.join(self.get_activity_root(), 'instance') now = int(time.time()) cutoff = now - 24 * 60 * 60 # yesterday if uptime is not None: boot_time = now - uptime cutoff = max(cutoff, boot_time) for f in os.listdir(temp_path): if f.startswith('.goutputstream-'): fpath = os.path.join(temp_path, f) mtime = int(os.path.getmtime(fpath)) if mtime < cutoff: logging.warning('Removing old temporary file: %s', fpath) try: os.remove(fpath) except EnvironmentError: logging.error( 'Temporary file could not be ' 'removed: %s', fpath) def _on_focus_url_entry(self, gobject): self._primary_toolbar.entry.grab_focus() def _get_data_from_file_path(self, file_path): fd = open(file_path, 'r') try: data = fd.read() finally: fd.close() return data def _get_save_as(self): if not hasattr(profile, 'get_save_as'): return False return profile.get_save_as() def read_file(self, file_path): if self.metadata['mime_type'] == 'text/plain': data = self._get_data_from_file_path(file_path) self.model.deserialize(data) for link in self.model.data['shared_links']: _logger.debug('read: url=%s title=%s d=%s' % (link['url'], link['title'], link['color'])) self._add_link_totray(link['url'], b64decode(link['thumb']), link['color'], link['title'], link['owner'], -1, link['hash'], link.get('notes')) logging.debug('########## reading %s', data) if 'session_state' in self.model.data: self._tabbed_view.set_session_state( self.model.data['session_state']) else: self._tabbed_view.set_legacy_history( self.model.data['history'], self.model.data['currents']) for number, tab in enumerate(self.model.data['currents']): tab_page = self._tabbed_view.get_nth_page(number) zoom_level = tab.get('zoom_level') if zoom_level is not None: tab_page.browser.set_zoom_level(zoom_level) tab_page.browser.grab_focus() self._tabbed_view.set_current_page(self.model.data['current_tab']) elif self.metadata['mime_type'] == 'text/uri-list': data = self._get_data_from_file_path(file_path) uris = mime.split_uri_list(data) if len(uris) == 1: self._tabbed_view.props.current_browser.load_uri(uris[0]) else: _logger.error('Open uri-list: Does not support' 'list of multiple uris by now.') else: file_uri = 'file://' + file_path self._tabbed_view.props.current_browser.load_uri(file_uri) self._tabbed_view.props.current_browser.grab_focus() def write_file(self, file_path): if not hasattr(self, '_tabbed_view'): _logger.debug('Called write_file before the tabbed_view was made') return if not self.metadata['mime_type']: self.metadata['mime_type'] = 'text/plain' if self.metadata['mime_type'] == 'text/plain': browser = self._tabbed_view.current_browser if not self._jobject.metadata['title_set_by_user'] == '1' and \ not self._get_save_as(): if browser.props.title is None: self.metadata['title'] = _('Untitled') else: self.metadata['title'] = browser.props.title self.model.data['history'] = self._tabbed_view.get_legacy_history() current_tab = self._tabbed_view.get_current_page() self.model.data['current_tab'] = current_tab self.model.data['currents'] = [] for n in range(0, self._tabbed_view.get_n_pages()): tab_page = self._tabbed_view.get_nth_page(n) n_browser = tab_page.browser if n_browser is not None: uri = n_browser.get_uri() history_index = n_browser.get_history_index() info = { 'title': n_browser.props.title, 'url': uri, 'history_index': history_index, 'zoom_level': n_browser.get_zoom_level() } self.model.data['currents'].append(info) self.model.data['session_state'] = \ self._tabbed_view.get_state() f = open(file_path, 'w') try: logging.debug('########## writing %s', self.model.serialize()) f.write(self.model.serialize()) finally: f.close() def __link_add_button_cb(self, button): self._add_link() def __link_remove_button_cb(self, button): browser = self._tabbed_view.props.current_browser uri = browser.get_uri() self.__link_removed_cb(None, sha1(uri).hexdigest()) def _go_home_button_cb(self, button): self._tabbed_view.load_homepage() def _go_library_button_cb(self, button): self._tabbed_view.load_homepage(ignore_settings=True) def _set_home_button_cb(self, button): self._tabbed_view.set_homepage() self._alert(_('The initial page was configured')) def _reset_home_button_cb(self, button): self._tabbed_view.reset_homepage() self._alert(_('The default initial page was configured')) def _alert(self, title, text=None): alert = NotifyAlert(timeout=5) alert.props.title = title alert.props.msg = text self.add_alert(alert) alert.connect('response', self._alert_cancel_cb) alert.show() def _alert_cancel_cb(self, alert, response_id): self.remove_alert(alert) def _key_press_cb(self, widget, event): browser = self._tabbed_view.props.current_browser if event.get_state() & Gdk.ModifierType.CONTROL_MASK: if event.keyval == Gdk.KEY_f: self._edit_toolbar_button.set_expanded(True) self._edit_toolbar.search_entry.grab_focus() return True if event.keyval == Gdk.KEY_l: self._primary_toolbar.entry.grab_focus() return True if event.keyval == Gdk.KEY_equal: # On US keyboards, KEY_equal is KEY_plus without # SHIFT_MASK, so for convenience treat this as the # same as the zoom in accelerator configured in # WebKit2 browser.zoom_in() return True if event.keyval == Gdk.KEY_t: self._tabbed_view.add_tab() return True if event.keyval == Gdk.KEY_w: self._tabbed_view.close_tab() return True # FIXME: copy and paste is supposed to be handled by # Gtk.Entry, but does not work when we catch # key-press-event and return False. if self._primary_toolbar.entry.is_focus(): if event.keyval == Gdk.KEY_c: self._primary_toolbar.entry.copy_clipboard() return True if event.keyval == Gdk.KEY_v: self._primary_toolbar.entry.paste_clipboard() return True return False if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Down, Gdk.KEY_KP_Left, Gdk.KEY_KP_Right): scrolled_window = browser.get_parent() if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Down): adjustment = scrolled_window.get_vadjustment() elif event.keyval in (Gdk.KEY_KP_Left, Gdk.KEY_KP_Right): adjustment = scrolled_window.get_hadjustment() value = adjustment.get_value() step = adjustment.get_step_increment() if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Left): adjustment.set_value(value - step) else: adjustment.set_value(value + step) return True if event.keyval == Gdk.KEY_Escape: browser.stop_loading() return False # allow toolbar entry to handle escape too return False def _add_link(self): ''' take screenshot and add link info to the model ''' browser = self._tabbed_view.props.current_browser ui_uri = browser.get_uri() if self.model.has_link(ui_uri): return buf = b64encode(self._get_screenshot()).decode('ascii') timestamp = time.time() args = (ui_uri, browser.props.title, buf, profile.get_nick_name(), profile.get_color().to_string(), timestamp) self.model.add_link(*args, by_me=True) self._collab.post({'type': 'add_link', 'args': args}) def __message_cb(self, collab, buddy, message): type_ = message.get('type') if type_ == 'add_link': self.model.add_link(*message['args']) elif type_ == 'add_link_from_info': self.model.add_link_from_info(message['dict']) elif type_ == 'remove_link': self.remove_link(message['hash']) def get_data(self): return self.model.data def set_data(self, data): for link in data['shared_links']: if link['hash'] not in self.model.get_links_ids(): self.model.add_link_from_info(link) # FIXME: Case where buddy has updated link desciption their_model = Model() their_model.data = data for link in self.model.data['shared_links']: if link['hash'] not in their_model.get_links_ids(): self._collab.post({'type': 'add_link_from_info', 'dict': link}) def _add_link_model_cb(self, model, index, by_me): ''' receive index of new link from the model ''' link = self.model.data['shared_links'][index] widget = self._add_link_totray(link['url'], b64decode(link['thumb']), link['color'], link['title'], link['owner'], index, link['hash'], link.get('notes')) if by_me: animator = Animator(1, widget=self) animator.add( AddLinkAnimation(self, self._tabbed_view.props.current_browser, widget)) animator.start() def _add_link_totray(self, url, buf, color, title, owner, index, hash, notes=None): ''' add a link to the tray ''' item = LinkButton(buf, color, title, owner, hash, notes) item.connect('clicked', self._link_clicked_cb, url) item.connect('remove_link', self.__link_removed_cb) item.notes_changed_signal.connect(self.__link_notes_changed) # use index to add to the tray self._tray_links[hash] = item self._tray.add_item(item, index) item.show() self._view_toolbar.traybutton.props.sensitive = True self._view_toolbar.traybutton.props.active = True self._view_toolbar.update_traybutton_tooltip() return item def __link_removed_cb(self, button, hash): self.remove_link(hash) self._collab.post({'type': 'remove_link', 'hash': hash}) def remove_link(self, hash): ''' remove a link from tray and delete it in the model ''' self._tray_links[hash].hide() self._tray_links[hash].destroy() del self._tray_links[hash] self.model.remove_link(hash) if len(self._tray.get_children()) == 0: self._view_toolbar.traybutton.props.sensitive = False self._view_toolbar.traybutton.props.active = False self._view_toolbar.update_traybutton_tooltip() def __link_notes_changed(self, button, hash, notes): self.model.change_link_notes(hash, notes) def _link_clicked_cb(self, button, url): ''' an item of the link tray has been clicked ''' browser = self._tabbed_view.add_tab() browser.load_uri(url) browser.grab_focus() def _get_screenshot(self): browser = self._tabbed_view.props.current_browser window = browser.get_window() width, height = window.get_width(), window.get_height() thumb_surface = Gdk.Window.create_similar_surface( window, cairo.CONTENT_COLOR, THUMB_WIDTH, THUMB_HEIGHT) cairo_context = cairo.Context(thumb_surface) thumb_scale_w = THUMB_WIDTH * 1.0 / width thumb_scale_h = THUMB_HEIGHT * 1.0 / height cairo_context.scale(thumb_scale_w, thumb_scale_h) Gdk.cairo_set_source_window(cairo_context, window, 0, 0) cairo_context.paint() thumb_str = io.BytesIO() thumb_surface.write_to_png(thumb_str) return thumb_str.getvalue() def can_close(self): if self._force_close: return True elif downloadmanager.can_quit(): return True else: alert = Alert() alert.props.title = ngettext('Download in progress', 'Downloads in progress', downloadmanager.num_downloads()) message = ngettext('Stopping now will erase your download', 'Stopping now will erase your downloads', downloadmanager.num_downloads()) alert.props.msg = message cancel_icon = Icon(icon_name='dialog-cancel') cancel_label = ngettext('Continue download', 'Continue downloads', downloadmanager.num_downloads()) alert.add_button(Gtk.ResponseType.CANCEL, cancel_label, cancel_icon) stop_icon = Icon(icon_name='dialog-ok') alert.add_button(Gtk.ResponseType.OK, _('Stop'), stop_icon) stop_icon.show() self.add_alert(alert) alert.connect('response', self.__inprogress_response_cb) alert.show() self.present() return False def __inprogress_response_cb(self, alert, response_id): self.remove_alert(alert) if response_id is Gtk.ResponseType.CANCEL: logging.debug('Keep on') elif response_id == Gtk.ResponseType.OK: logging.debug('Stop downloads and quit') self._force_close = True downloadmanager.remove_all_downloads() self.close() def __switch_page_cb(self, tabbed_view, page, page_num): if not hasattr(self, 'busy'): return browser = page._browser progress = browser.props.estimated_load_progress uri = browser.props.uri if progress < 1.0 and uri: self.busy() else: while self.unbusy() > 0: continue def get_document_path(self, async_cb, async_err_cb): browser = self._tabbed_view.props.current_browser browser.get_source(async_cb, async_err_cb) def get_canvas(self): return self._tabbed_view def _incompatible(self): ''' Display abbreviated activity user interface with alert ''' toolbox = ToolbarBox() stop = StopButton(self) toolbox.toolbar.add(stop) self.set_toolbar_box(toolbox) title = _('Activity not compatible with this system.') msg = _('Please downgrade activity and try again.') alert = Alert(title=title, msg=msg) alert.add_button(0, 'Stop', Icon(icon_name='activity-stop')) self.add_alert(alert) label = Gtk.Label( _('Uh oh, WebKit2 is too old. ' 'Browse-200 and later require WebKit2 API 4.0, ' 'sorry!')) self.set_canvas(label) ''' Workaround: start Terminal activity, then type sugar-erase-bundle org.laptop.WebActivity then in My Settings, choose Software Update, which will offer older Browse. ''' alert.connect('response', self.__incompatible_response_cb) stop.connect('clicked', self.__incompatible_stop_clicked_cb, alert) self.show_all() def __incompatible_stop_clicked_cb(self, button, alert): self.remove_alert(alert) def __incompatible_response_cb(self, alert, response): self.remove_alert(alert) self.close()
def __init__(self, handle): Activity.__init__(self, handle) self.play_mode = None toolbar_box = ToolbarBox() self.set_toolbar_box(toolbar_box) self.activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(self.activity_button, -1) self._memorizeToolbarBuilder = \ memorizetoolbar.MemorizeToolbarBuilder(self) toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) self._edit_button = ToggleToolButton('view-source') self._edit_button.set_tooltip(_('Edit game')) self._edit_button.set_active(False) toolbar_box.toolbar.insert(self._edit_button, -1) self._createToolbarBuilder = \ createtoolbar.CreateToolbarBuilder(self) separator = Gtk.SeparatorToolItem() separator.set_expand(True) separator.set_draw(False) separator.set_size_request(0, -1) toolbar_box.toolbar.insert(separator, -1) toolbar_box.toolbar.insert(StopButton(self), -1) self.game = game.MemorizeGame() # Play game mode self.table = cardtable.CardTable() self.scoreboard = scoreboard.Scoreboard() self.cardlist = cardlist.CardList() self.createcardpanel = createcardpanel.CreateCardPanel(self.game) self.cardlist.connect('pair-selected', self.createcardpanel.pair_selected) self.cardlist.connect('update-create-toolbar', self._createToolbarBuilder.update_create_toolbar) self.createcardpanel.connect('add-pair', self.cardlist.add_pair) self.createcardpanel.connect('update-pair', self.cardlist.update_selected) self.createcardpanel.connect('change-font', self.cardlist.change_font) self.createcardpanel.connect('pair-closed', self.cardlist.rem_current_pair) self._createToolbarBuilder.connect('create_new_game', self.cardlist.clean_list) self._createToolbarBuilder.connect('create_new_game', self.createcardpanel.clean) self._createToolbarBuilder.connect('create_new_game', self._memorizeToolbarBuilder.reset) self._createToolbarBuilder.connect('create_equal_pairs', self.change_equal_pairs) self._edit_button.connect('toggled', self._change_mode_bt) self.connect('key-press-event', self.table.key_press_event) self.table.connect('card-flipped', self.game.card_flipped) self.table.connect('card-highlighted', self.game.card_highlighted) self.game.connect('set-border', self.table.set_border) self.game.connect('flop-card', self.table.flop_card) self.game.connect('flip-card', self.table.flip_card) self.game.connect('cement-card', self.table.cement_card) self.game.connect('highlight-card', self.table.highlight_card) self.game.connect('load_mode', self.table.load_msg) self.game.connect('msg_buddy', self.scoreboard.set_buddy_message) self.game.connect('add_buddy', self.scoreboard.add_buddy) self.game.connect('rem_buddy', self.scoreboard.rem_buddy) self.game.connect('increase-score', self.scoreboard.increase_score) self.game.connect('wait_mode_buddy', self.scoreboard.set_wait_mode) self.game.connect('change-turn', self.scoreboard.set_selected) self.game.connect('change_game', self.scoreboard.change_game) self.game.connect('reset_scoreboard', self.scoreboard.reset) self.game.connect('reset_table', self.table.reset) self.game.connect('load_game', self.table.load_game) self.game.connect('change_game', self.table.change_game) self.game.connect('load_game', self._memorizeToolbarBuilder.update_toolbar) self.game.connect('change_game', self._memorizeToolbarBuilder.update_toolbar) self.game.connect('change_game', self.createcardpanel.update_font_combos) self._memorizeToolbarBuilder.connect('game_changed', self.change_game) self.box = Gtk.HBox(orientation=Gtk.Orientation.VERTICAL, homogeneous=False) width = Gdk.Screen.width() height = Gdk.Screen.height() - style.GRID_CELL_SIZE self.table.resize(width, height - style.GRID_CELL_SIZE) self.scoreboard.set_size_request(-1, style.GRID_CELL_SIZE) self.set_canvas(self.box) # connect to the in/out events of the memorize activity self.connect('focus_in_event', self._focus_in) self.connect('focus_out_event', self._focus_out) self.connect('destroy', self._cleanup_cb) self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self.connect('motion_notify_event', lambda widget, event: face.look_at()) Gdk.Screen.get_default().connect('size-changed', self.__configure_cb) # start on the game toolbar, might change this # to the create toolbar later self._change_mode(_MODE_PLAY) def on_activity_joined_cb(me): logging.debug('activity joined') self.game.add_buddy(self._collab.props.owner) self.connect('joined', on_activity_joined_cb) def on_activity_shared_cb(me): logging.debug('activity shared') self.connect('shared', on_activity_shared_cb) self._collab = CollabWrapper(self) self.game.set_myself(self._collab.props.owner) def on_message_cb(collab, buddy, msg): logging.debug('on_message_cb buddy %r msg %r' % (buddy, msg)) action = msg.get('action') if action == 'flip': n = msg.get('n') self.game.card_flipped(None, n, True) elif action == 'change': self.get_canvas().hide() def momentary_blank_timeout_cb(): self.set_data(msg) self.get_canvas().show() GLib.timeout_add(100, momentary_blank_timeout_cb) self._collab.connect('message', on_message_cb) def on_joined_cb(collab, msg): logging.debug('joined') self._collab.connect('joined', on_joined_cb, 'joined') def on_buddy_joined_cb(collab, buddy, msg): logging.debug('on_buddy_joined_cb buddy %r msg %r' % (buddy, msg)) self.game.add_buddy(buddy) self._collab.connect('buddy_joined', on_buddy_joined_cb, 'buddy_joined') def on_buddy_left_cb(collab, buddy, msg): logging.debug('on_buddy_left_cb buddy %r msg %r' % (buddy, msg)) self.game.rem_buddy(buddy) self._collab.connect('buddy_left', on_buddy_left_cb, 'buddy_left') self._files = {} # local temporary copies of shared games self._collab.setup() def on_flip_card_cb(game, n): logging.debug('on_flip_card_cb n %r' % (n)) self._collab.post({'action': 'flip', 'n': n}) self.game.connect('flip-card-signal', on_flip_card_cb) def on_change_game_cb(sender, mode, grid, data, waiting_list, zip): logging.debug('on_change_game_cb') blob = self.get_data() blob['action'] = 'change' self._collab.post(blob) self.game.connect('change_game_signal', on_change_game_cb) if self._collab.props.leader: logging.debug('is leader') game_file = os.path.join(os.path.dirname(__file__), 'demos', 'addition.zip') self.game.load_game(game_file, 4, 'demo') self.game.add_buddy(self._collab.props.owner) self.show_all()
class LevelActivity(activity.Activity): def __init__(self, handle): "The entry point to the Activity" activity.Activity.__init__(self, handle) self._timeout = None self.accelerometer = False try: open(ACCELEROMETER_DEVICE).close() self.accelerometer = True except: pass if not self.accelerometer and not self.shared_activity: return self._incompatible() self.buddies = {} canvas = MyCanvas(self) self.set_canvas(canvas) canvas.show() toolbar_box = ToolbarBox() self.set_toolbar_box(toolbar_box) toolbar_box.toolbar.insert(ActivityButton(self), 0) toolbar_box.toolbar.insert(TitleEntry(self), -1) toolbar_box.toolbar.insert(DescriptionItem(self), -1) toolbar_box.toolbar.insert(ShareButton(self), -1) separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) toolbar_box.toolbar.insert(StopButton(self), -1) toolbar_box.show_all() self._udp = Udp() self.hosts = {} self._collab = CollabWrapper(self) self._collab.message.connect(self.__message_cb) self._collab.buddy_joined.connect(self.__buddy_joined_cb) self._collab.buddy_left.connect(self.__buddy_left_cb) self._collab.setup() self._fuse = 1 self._timeout = GLib.timeout_add(100, self._timeout_cb, canvas) def _timeout_cb(self, canvas): if self.accelerometer: fh = open(ACCELEROMETER_DEVICE) xyz = fh.read()[1:-2].split(',') fh.close() x = float(xyz[0]) / (64 * 18) y = float(xyz[1]) / (64 * 18) canvas.motion_cb(x, y) canvas.queue_draw() self._fuse -= 1 if self._fuse == 0: if self.accelerometer: self._collab.post({'action': '%d,%d' % (canvas.x, canvas.y)}) self._fuse = 10 if not self.shared_activity: return True if self.accelerometer: self._udp.put('%d,%d' % (canvas.x, canvas.y)) data = self._udp.get() while data: (data, ip4_address) = data if ip4_address in self.hosts: key = self.hosts[ip4_address] else: key = ip4_address self.hosts[key] = ip4_address # temporary (x, y) = data.split(',') self.buddies[key] = (int(x), int(y)) if not self.accelerometer: self.get_canvas().queue_draw() data = self._udp.get() return True def close(self): if self._timeout: GLib.source_remove(self._timeout) activity.Activity.close(self) def get_data(self): return None def set_data(self, data): pass def __message_cb(self, collab, buddy, msg): action = msg.get('action') if ',' in action: x, y = action.split(',') self.buddies[buddy.props.key] = (int(x), int(y)) if not self.accelerometer: self.get_canvas().queue_draw() def __buddy_joined_cb(self, collab, buddy): if buddy.props.ip4_address in self.hosts: del self.buddies[self.hosts[buddy.props.ip4_address]] # temporary self.hosts[buddy.props.ip4_address] = buddy.props.key def __buddy_left_cb(self, collab, buddy): if buddy.props.key in self.buddies: del self.buddies[buddy.props.key] if buddy.props.ip4_address in self.hosts: del self.hosts[buddy.props.ip4_address] def _incompatible(self): ''' Display abbreviated activity user interface with alert ''' toolbox = ToolbarBox() stop = StopButton(self) toolbox.toolbar.add(stop) self.set_toolbar_box(toolbox) title = _('Activity not compatible with this system.') msg = _('Please erase the activity.') alert = Alert(title=title, msg=msg) alert.add_button(0, 'Stop', Icon(icon_name='activity-stop')) self.add_alert(alert) label = Gtk.Label(_('You do not have an accelerometer.')) self.set_canvas(label) alert.connect('response', self.__incompatible_response_cb) stop.connect('clicked', self.__incompatible_stop_clicked_cb, alert) self.show_all() def __incompatible_stop_clicked_cb(self, button, alert): self.remove_alert(alert) def __incompatible_response_cb(self, alert, response): self.remove_alert(alert) self.close()
def __init__(self, handle): activity.Activity.__init__(self, handle) self._force_close = False if incompatible: return self._incompatible() self._collab = CollabWrapper(self) self._collab.message.connect(self.__message_cb) _logger.debug('Starting the web activity') # TODO PORT # session = WebKit2.get_default_session() # session.set_property('accept-language-auto', True) # session.set_property('ssl-use-system-ca-file', True) # session.set_property('ssl-strict', False) # But of a hack, but webkit doesn't let us change the cookie jar # contents, we we can just pre-seed it cookie_jar = SoupGNOME.CookieJarSqlite(filename=_cookies_db_path, read_only=False) _seed_xs_cookie(cookie_jar) del cookie_jar context = WebKit2.WebContext.get_default() cookie_manager = context.get_cookie_manager() cookie_manager.set_persistent_storage( _cookies_db_path, WebKit2.CookiePersistentStorage.SQLITE) # FIXME # downloadmanager.remove_old_parts() context.connect('download-started', self.__download_requested_cb) self._tabbed_view = TabbedView(self) self._tabbed_view.connect('focus-url-entry', self._on_focus_url_entry) self._tabbed_view.connect('switch-page', self.__switch_page_cb) self._titled_tray = TitledTray(_('Bookmarks')) self._tray = self._titled_tray.tray self.set_tray(self._titled_tray, Gtk.PositionType.BOTTOM) self._tray_links = {} self.model = Model() self.model.add_link_signal.connect(self._add_link_model_cb) self._primary_toolbar = PrimaryToolbar(self._tabbed_view, self) self._edit_toolbar = EditToolbar(self) self._view_toolbar = ViewToolbar(self) self._primary_toolbar.connect('add-link', self.__link_add_button_cb) self._primary_toolbar.connect('remove-link', self.__link_remove_button_cb) self._primary_toolbar.connect('go-home', self._go_home_button_cb) self._primary_toolbar.connect('go-library', self._go_library_button_cb) self._primary_toolbar.connect('set-home', self._set_home_button_cb) self._primary_toolbar.connect('reset-home', self._reset_home_button_cb) self._edit_toolbar_button = ToolbarButton( page=self._edit_toolbar, icon_name='toolbar-edit') self._primary_toolbar.toolbar.insert( self._edit_toolbar_button, 1) view_toolbar_button = ToolbarButton( page=self._view_toolbar, icon_name='toolbar-view') self._primary_toolbar.toolbar.insert( view_toolbar_button, 2) self._primary_toolbar.show_all() self.set_toolbar_box(self._primary_toolbar) self.set_canvas(self._tabbed_view) self._tabbed_view.show() self.connect('key-press-event', self._key_press_cb) if handle.uri: self._tabbed_view.current_browser.load_uri(handle.uri) elif not self._jobject.file_path: # TODO: we need this hack until we extend the activity API for # opening URIs and default docs. self._tabbed_view.load_homepage() # README: this is a workaround to remove old temp file # http://bugs.sugarlabs.org/ticket/3973 self._cleanup_temp_files() self._collab.setup()
class DeductoActivity(activity.Activity): """ Logic puzzle game """ def __init__(self, handle): """ Initialize the toolbars and the game board """ activity.Activity.__init__(self, handle) self.nick = profile.get_nick_name() if profile.get_color() is not None: self.colors = profile.get_color().to_string().split(',') else: self.colors = ['#A0FFA0', '#FF8080'] self.level = 0 self._correct = 0 self._playing = True self._game_over = False self._python_code = None self._setup_toolbars() self._setup_dispatch_table() # Create a canvas canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() self.show_all() self._game = Game(canvas, parent=self, colors=self.colors) self._sharing = False self._initiating = False self.connect('shared', self._shared_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) self._collab.connect('joined', self._joined_cb) self._collab.setup() if 'level' in self.metadata: self.level = int(self.metadata['level']) self.status.set_label(_('Resuming level %d') % (self.level + 1)) self._game.show_random() else: self._game.new_game() def _setup_toolbars(self): """ Setup the toolbars. """ self.max_participants = 4 toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar self._new_game_button = button_factory('new-game', self.toolbar, self._new_game_cb, tooltip=_('Start a new game.')) separator_factory(toolbox.toolbar, False, True) self._true_button = button_factory( 'true', self.toolbar, self._true_cb, tooltip=_('The pattern matches the rule.')) self._false_button = button_factory( 'false', self.toolbar, self._false_cb, tooltip=_('The pattern does not match the rule.')) separator_factory(toolbox.toolbar, False, True) self._example_button = button_factory( 'example', self.toolbar, self._example_cb, tooltip=_('Explore some examples.')) self.status = label_factory(self.toolbar, '', width=300) separator_factory(toolbox.toolbar, True, False) self._gear_button = button_factory('view-source', self.toolbar, self._gear_cb, tooltip=_('Load a custom level.')) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() def _new_game_cb(self, button=None): ''' Start a new game. ''' self._game_over = False self._correct = 0 self.level = 0 if not self._playing: self._example_cb() self._game.new_game() if self._initiating: _logger.debug('sending new game and new grid') self._send_new_game() self._send_new_grid() self.status.set_label(_('Playing level %d') % (self.level + 1)) def _test_for_game_over(self): ''' If we are at maximum levels, the game is over ''' if self.level == self._game.max_levels: self.level = 0 self._game_over = True self.status.set_label(_('Game over.')) else: self.status.set_label(_('Playing level %d') % (self.level + 1)) self._correct = 0 if (not self._sharing) or self._initiating: self._game.show_random() if self._initiating: self._send_new_grid() def _true_cb(self, button=None): ''' Declare pattern true or show an example of a true pattern. ''' if self._game_over: if (not self._sharing) or self._initiating: self.status.set_label(_('Click on new game button to begin.')) else: self.status.set_label( _('Wait for sharer to start ' + 'a new game.')) return if self._playing: if self._game.this_pattern: self._correct += 1 if self._correct == 5: self.level += 1 self._test_for_game_over() self.metadata['level'] = str(self.level) else: self.status.set_label( _('%d correct answers.') % (self._correct)) if (not self._sharing) or self._initiating: self._game.show_random() if self._initiating: self._send_new_grid() else: self.status.set_label(_('Pattern was false.')) self._correct = 0 if (button is not None) and self._sharing: self._send_true_button_click() else: self._game.show_true() def _false_cb(self, button=None): ''' Declare pattern false or show an example of a false pattern. ''' if self._game_over: if (not self._sharing) or self._initiating: self.status.set_label(_('Click on new game button to begin.')) else: self.status.set_label( _('Wait for sharer to start ' + 'a new game.')) return if self._playing: if not self._game.this_pattern: self._correct += 1 if self._correct == 5: self.level += 1 self._test_for_game_over() else: self.status.set_label( _('%d correct answers.') % (self._correct)) if (not self._sharing) or self._initiating: self._game.show_random() if self._initiating: self._send_new_grid() else: self.status.set_label(_('Pattern was true.')) self._correct = 0 if (button is not None) and self._sharing: self._send_false_button_click() else: self._game.show_false() def _example_cb(self, button=None): ''' Show examples or resume play of current level. ''' if self._playing: self._example_button.set_icon_name('resume-play') self._example_button.set_tooltip(_('Resume play')) self._true_button.set_tooltip( _('Show a pattern that matches the rule.')) self._false_button.set_tooltip( _('Show a pattern that does not match the rule.')) Button1 = '☑' Button2 = '☒' self.status.set_label( _("Explore patterns with the {} and {} buttons.".format( Button1, Button2))) self._playing = False else: self._example_button.set_icon_name('example') self._example_button.set_tooltip(_('Explore some examples.')) self._true_button.set_tooltip(_('The pattern matches the rule.')) self._false_button.set_tooltip( _('The pattern does not match the rule.')) self.status.set_label(_('Playing level %d') % (self.level + 1)) self._playing = True self._correct = 0 def _gear_cb(self, button=None): ''' Load a custom level. ''' self.status.set_text( _('Load a "True" pattern generator from the journal')) self._chooser('org.laptop.Pippy', self._load_python_code_from_journal) if self._python_code is None: return LEVELS_TRUE.append(self._python_code) self.status.set_text( _('Load a "False" pattern generator from the journal')) self._chooser('org.laptop.Pippy', self._load_python_code_from_journal) LEVELS_FALSE.append(self._python_code) if self._python_code is None: return self.status.set_text(_('New level added')) self._game.max_levels += 1 def _load_python_code_from_journal(self, dsobject): ''' Read the Python code from the Journal object ''' self._python_code = None try: _logger.debug("opening %s " % dsobject.file_path) file_handle = open(dsobject.file_path, "r") self._python_code = file_handle.read() file_handle.close() except IOError: _logger.debug("couldn't open %s" % dsobject.file_path) def _chooser(self, filter, action): ''' Choose an object from the datastore and take some action ''' chooser = None try: chooser = ObjectChooser(parent=self, what_filter=filter) except TypeError: chooser = ObjectChooser( None, self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT) if chooser is not None: try: result = chooser.run() if result == Gtk.ResponseType.ACCEPT: dsobject = chooser.get_selected_object() action(dsobject) dsobject.destroy() finally: chooser.destroy() del chooser # Collaboration-related methods # The sharer sends patterns and everyone shares whatever vote is # cast first among all the sharer and joiners. def set_data(self, data): pass def get_data(self): return None def _shared_cb(self, activity): ''' Either set up initial share...''' self.after_share_join(True) def _joined_cb(self, activity): ''' ...or join an exisiting share. ''' self.after_share_join(True) def after_share_join(self, sharer): self.waiting_for_hand = not sharer self._initiating = sharer self._sharing = True def _setup_dispatch_table(self): ''' Associate tokens with commands. ''' self._processing_methods = { 'new_game': [self._receive_new_game, 'new game'], 'new_grid': [self._receive_new_grid, 'get a new grid'], 'true': [self._receive_true_button_click, 'get a true button press'], 'false': [ self._receive_false_button_click, 'get a false ' + 'button press' ], } def _message_cb(self, collab, buddy, msg): command = msg.get('command') payload = msg.get('payload') self._processing_methods[command][0](payload) def _send_new_game(self): ''' Send a new game message to all players ''' self._send_event('new_game', ' ') def _receive_new_game(self, payload): ''' Receive a new game notification from the sharer. ''' self._game_over = False self._correct = 0 self.level = 0 if not self._playing: self._example_cb() self.status.set_label(_('Playing level %d') % (self.level + 1)) def _send_new_grid(self): ''' Send a new grid to all players ''' self._send_event('new_grid', self._game.save_grid()) def _receive_new_grid(self, payload): ''' Receive a grid from the sharer. ''' (dot_list, boolean, colors) = payload self._game.restore_grid(dot_list, boolean, colors) def _send_true_button_click(self): ''' Send a true click to all the players ''' self._send_event('true') def _receive_true_button_click(self, payload): ''' When a button is clicked, everyone should react. ''' self._playing = True self._true_cb() def _send_false_button_click(self): ''' Send a false click to all the players ''' self._send_event('false') def _receive_false_button_click(self, payload): ''' When a button is clicked, everyone should react. ''' self._playing = True self._false_cb() def _send_event(self, command, payload=None): self._collab.post({'command': command, 'payload': payload})
class MemorizeActivity(Activity): def __init__(self, handle): Activity.__init__(self, handle) self.play_mode = None toolbar_box = ToolbarBox() self.set_toolbar_box(toolbar_box) self.activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(self.activity_button, -1) self._memorizeToolbarBuilder = \ memorizetoolbar.MemorizeToolbarBuilder(self) toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) self._edit_button = ToggleToolButton('view-source') self._edit_button.set_tooltip(_('Edit game')) self._edit_button.set_active(False) toolbar_box.toolbar.insert(self._edit_button, -1) self._createToolbarBuilder = \ createtoolbar.CreateToolbarBuilder(self) separator = Gtk.SeparatorToolItem() separator.set_expand(True) separator.set_draw(False) separator.set_size_request(0, -1) toolbar_box.toolbar.insert(separator, -1) toolbar_box.toolbar.insert(StopButton(self), -1) self.game = game.MemorizeGame() # Play game mode self.table = cardtable.CardTable() self.scoreboard = scoreboard.Scoreboard() self.cardlist = cardlist.CardList() self.createcardpanel = createcardpanel.CreateCardPanel(self.game) self.cardlist.connect('pair-selected', self.createcardpanel.pair_selected) self.cardlist.connect('update-create-toolbar', self._createToolbarBuilder.update_create_toolbar) self.createcardpanel.connect('add-pair', self.cardlist.add_pair) self.createcardpanel.connect('update-pair', self.cardlist.update_selected) self.createcardpanel.connect('change-font', self.cardlist.change_font) self.createcardpanel.connect('pair-closed', self.cardlist.rem_current_pair) self._createToolbarBuilder.connect('create_new_game', self.cardlist.clean_list) self._createToolbarBuilder.connect('create_new_game', self.createcardpanel.clean) self._createToolbarBuilder.connect('create_new_game', self._memorizeToolbarBuilder.reset) self._createToolbarBuilder.connect('create_equal_pairs', self.change_equal_pairs) self._edit_button.connect('toggled', self._change_mode_bt) self.connect('key-press-event', self.table.key_press_event) self.table.connect('card-flipped', self.game.card_flipped) self.table.connect('card-highlighted', self.game.card_highlighted) self.game.connect('set-border', self.table.set_border) self.game.connect('flop-card', self.table.flop_card) self.game.connect('flip-card', self.table.flip_card) self.game.connect('cement-card', self.table.cement_card) self.game.connect('highlight-card', self.table.highlight_card) self.game.connect('load_mode', self.table.load_msg) self.game.connect('msg_buddy', self.scoreboard.set_buddy_message) self.game.connect('add_buddy', self.scoreboard.add_buddy) self.game.connect('rem_buddy', self.scoreboard.rem_buddy) self.game.connect('increase-score', self.scoreboard.increase_score) self.game.connect('wait_mode_buddy', self.scoreboard.set_wait_mode) self.game.connect('change-turn', self.scoreboard.set_selected) self.game.connect('change_game', self.scoreboard.change_game) self.game.connect('reset_scoreboard', self.scoreboard.reset) self.game.connect('reset_table', self.table.reset) self.game.connect('load_game', self.table.load_game) self.game.connect('change_game', self.table.change_game) self.game.connect('load_game', self._memorizeToolbarBuilder.update_toolbar) self.game.connect('change_game', self._memorizeToolbarBuilder.update_toolbar) self.game.connect('change_game', self.createcardpanel.update_font_combos) self._memorizeToolbarBuilder.connect('game_changed', self.change_game) self.box = Gtk.HBox(orientation=Gtk.Orientation.VERTICAL, homogeneous=False) width = Gdk.Screen.width() height = Gdk.Screen.height() - style.GRID_CELL_SIZE self.table.resize(width, height - style.GRID_CELL_SIZE) self.scoreboard.set_size_request(-1, style.GRID_CELL_SIZE) self.set_canvas(self.box) # connect to the in/out events of the memorize activity self.connect('focus_in_event', self._focus_in) self.connect('focus_out_event', self._focus_out) self.connect('destroy', self._cleanup_cb) self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self.connect('motion_notify_event', lambda widget, event: face.look_at()) Gdk.Screen.get_default().connect('size-changed', self.__configure_cb) # start on the game toolbar, might change this # to the create toolbar later self._change_mode(_MODE_PLAY) def on_activity_joined_cb(me): logging.debug('activity joined') self.game.add_buddy(self._collab.props.owner) self.connect('joined', on_activity_joined_cb) def on_activity_shared_cb(me): logging.debug('activity shared') self.connect('shared', on_activity_shared_cb) self._collab = CollabWrapper(self) self.game.set_myself(self._collab.props.owner) def on_message_cb(collab, buddy, msg): logging.debug('on_message_cb buddy %r msg %r' % (buddy, msg)) action = msg.get('action') if action == 'flip': n = msg.get('n') self.game.card_flipped(None, n, True) elif action == 'change': self.get_canvas().hide() def momentary_blank_timeout_cb(): self.set_data(msg) self.get_canvas().show() GLib.timeout_add(100, momentary_blank_timeout_cb) self._collab.connect('message', on_message_cb) def on_joined_cb(collab, msg): logging.debug('joined') self._collab.connect('joined', on_joined_cb, 'joined') def on_buddy_joined_cb(collab, buddy, msg): logging.debug('on_buddy_joined_cb buddy %r msg %r' % (buddy, msg)) self.game.add_buddy(buddy) self._collab.connect('buddy_joined', on_buddy_joined_cb, 'buddy_joined') def on_buddy_left_cb(collab, buddy, msg): logging.debug('on_buddy_left_cb buddy %r msg %r' % (buddy, msg)) self.game.rem_buddy(buddy) self._collab.connect('buddy_left', on_buddy_left_cb, 'buddy_left') self._files = {} # local temporary copies of shared games self._collab.setup() def on_flip_card_cb(game, n): logging.debug('on_flip_card_cb n %r' % (n)) self._collab.post({'action': 'flip', 'n': n}) self.game.connect('flip-card-signal', on_flip_card_cb) def on_change_game_cb(sender, mode, grid, data, waiting_list, zip): logging.debug('on_change_game_cb') blob = self.get_data() blob['action'] = 'change' self._collab.post(blob) self.game.connect('change_game_signal', on_change_game_cb) if self._collab.props.leader: logging.debug('is leader') game_file = os.path.join(os.path.dirname(__file__), 'demos', 'addition.zip') self.game.load_game(game_file, 4, 'demo') self.game.add_buddy(self._collab.props.owner) self.show_all() def __configure_cb(self, event): ''' Screen size has changed ''' width = Gdk.Screen.width() height = Gdk.Screen.height() - style.GRID_CELL_SIZE self.box.set_size_request(width, height) self.scoreboard.set_size_request(-1, style.GRID_CELL_SIZE) self.table.resize(width, height - style.GRID_CELL_SIZE) self.show_all() def _change_mode_bt(self, button): if button.get_active(): self._change_mode(_MODE_CREATE) button.set_icon_name('player_play') button.set_tooltip(_('Play game')) else: self._change_mode(_MODE_PLAY) button.set_icon_name('view-source') button.set_tooltip(_('Edit game')) def _change_game_receiver(self, mode, grid, data, path): if mode == 'demo': game_name = os.path.basename(data.get('game_file', 'debug-demo')) game_file = os.path.join(os.path.dirname(__file__), 'demos', game_name).encode('ascii') self.game.model.read(game_file) if mode == 'art4apps': game_file = data['game_file'] category = game_file[:game_file.find('_')] language = data['language'] self.game.model.is_demo = True self.game.model.read_art4apps(category, language) if mode == 'file': self.game.model.read(self._files[path]) if 'path' in self.game.model.data: data['path'] = self.game.model.data['path'] data['pathimg'] = self.game.model.data['pathimg'] data['pathsnd'] = self.game.model.data['pathsnd'] self.game.load_remote(grid, data, mode, True) def set_data(self, blob): logging.debug("set_data %r" % list(blob.keys())) grid = blob['grid'] data = blob['data'] current_player = blob['current'] path = blob['path'] if 'zip' in blob: tmp_root = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'instance') temp_dir = tempfile.mkdtemp(dir=tmp_root) os.chmod(temp_dir, 0o777) temp_file = os.path.join(temp_dir, 'game.zip') self._files[path] = temp_file f = open(temp_file, 'wb') f.write(base64.b64decode(blob['zip'])) f.close() self._change_game_receiver(data['mode'], grid, data, path) for i in range(len(self.game.players)): self.game.increase_point(self.game.players[i], int(data.get(str(i), '0'))) self.game.current_player = self.game.players[current_player] self.game.update_turn() def get_data(self): data = self.game.collect_data() path = data['game_file'] blob = { "grid": self.game.get_grid(), "data": data, "current": self.game.players.index(self.game.current_player), "path": path } if data['mode'] == 'file': blob['zip'] = base64.b64encode(open(path, 'rb').read()).decode() logging.debug("get_data %r" % list(blob.keys())) return blob def read_file(self, file_path): if 'icon-color' in self.metadata: color = self.metadata['icon-color'] else: color = profile.get_color().to_string() self.change_game(None, file_path, 4, 'file', self.metadata['title'], color) def write_file(self, file_path): logging.debug('WRITE_FILE is_demo %s', self.game.model.is_demo) if self.game.model.is_demo: # if is a demo game only want keep the metadata self._jobject.set_file_path(None) raise NotImplementedError return if self.cardlist.pair_list_modified: self.cardlist.update_model(self.game.model) temp_img_folder = self.game.model.data['pathimg'] temp_snd_folder = self.game.model.data['pathsnd'] self.game.model.create_temp_directories() game_zip = zipfile.ZipFile(file_path, 'w') save_image_and_sound = True if 'origin' in self.game.model.data: if self.game.model.data['origin'] == 'art4apps': # we don't need save images and audio files # for art4apps games save_image_and_sound = False if save_image_and_sound: for pair in self.game.model.pairs: # aimg aimg = self.game.model.pairs[pair].get_property('aimg') if aimg is not None: game_zip.write(os.path.join(temp_img_folder, aimg), os.path.join('images', aimg)) # bimg bimg = self.game.model.pairs[pair].get_property('bimg') if bimg is not None: game_zip.write(os.path.join(temp_img_folder, bimg), os.path.join('images', bimg)) # asnd asnd = self.game.model.pairs[pair].get_property('asnd') if asnd is not None: if os.path.exists(os.path.join(temp_snd_folder, asnd)): game_zip.write(os.path.join(temp_snd_folder, asnd), os.path.join('sounds', asnd)) # bsnd bsnd = self.game.model.pairs[pair].get_property('bsnd') if bsnd is not None: if os.path.exists(os.path.join(temp_snd_folder, bsnd)): game_zip.write(os.path.join(temp_snd_folder, bsnd), os.path.join('sounds', bsnd)) self.game.model.game_path = self.game.model.temp_folder self.game.model.data['name'] = str(self.get_title()) self.game.model.write() game_zip.write(os.path.join(self.game.model.temp_folder, 'game.xml'), 'game.xml') game_zip.close() self.metadata['mime_type'] = 'application/x-memorize-project' def _complete_close(self): self._remove_temp_files() Activity._complete_close(self) def _remove_temp_files(self): tmp_root = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'instance') for root, dirs, files in os.walk(tmp_root, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) def _change_mode(self, mode): logging.debug("Change mode %s" % mode) if mode == _MODE_CREATE: if self.play_mode: self.box.remove(self.scoreboard) self.box.remove(self.table) self.createcardpanel.update_orientation() self.box.pack_start(self.createcardpanel, True, True, 0) self.box.pack_start(self.cardlist, False, False, 0) self.cardlist.load_game(self.game) self.game.model.create_temp_directories() self.createcardpanel.set_temp_folder( self.game.model.temp_folder) self.play_mode = False else: if self.game.model.modified: self.cardlist.update_model(self.game.model) self.save() self.game.reset_game() self.table.change_game(None, self.game.model.data, self.game.model.grid) self.game.model.modified = False if not self.play_mode: self.box.remove(self.createcardpanel) self.box.remove(self.cardlist) if self.play_mode in (False, None): self.box.pack_start(self.table, True, True, 0) self.box.pack_start(self.scoreboard, False, False, 0) self.play_mode = True self._memorizeToolbarBuilder.update_controls(mode == _MODE_PLAY) self._createToolbarBuilder.update_controls(mode == _MODE_CREATE) def change_game(self, widget, game_name, size, mode, title=None, color=None): logging.debug('Change game %s', game_name) self.game.change_game(widget, game_name, size, mode, title, color) self.cardlist.game_loaded = False def change_equal_pairs(self, widget, state): self.cardlist.update_model(self.game.model) self.createcardpanel.change_equal_pairs(widget, state) def _focus_in(self, event, data=None): self.game.audio.play() def _focus_out(self, event, data=None): self.game.audio.pause() def _cleanup_cb(self, data=None): self.game.audio.stop()
class StoryActivity(activity.Activity): ''' Storytelling game ''' def __init__(self, handle): ''' Initialize the toolbars and the game board ''' try: super(StoryActivity, self).__init__(handle) except dbus.exceptions.DBusException as e: _logger.error(str(e)) self._path = activity.get_bundle_path() self.datapath = os.path.join(activity.get_activity_root(), 'instance') self._nick = profile.get_nick_name() if profile.get_color() is not None: self._colors = profile.get_color().to_string().split(',') else: self._colors = ['#A0FFA0', '#FF8080'] self._old_cursor = self.get_window().get_cursor() self.tablet_mode = _is_tablet_mode() self.recording = False self.audio_process = None self._arecord = None self._alert = None self._uid = None self._setup_toolbars() self._setup_dispatch_table() self._fixed = Gtk.Fixed() self._fixed.connect('size-allocate', self._fixed_resize_cb) self._fixed.show() self.set_canvas(self._fixed) self._vbox = Gtk.VBox(False, 0) self._vbox.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self._fixed.put(self._vbox, 0, 0) self._vbox.show() self._canvas = Gtk.DrawingArea() self._canvas.set_size_request(int(Gdk.Screen.width()), int(Gdk.Screen.height())) self._canvas.show() self._vbox.pack_end(self._canvas, True, True, 0) self._vbox.show() entry_width = Gdk.Screen.width() - 6 * style.GRID_CELL_SIZE entry_height = 3 * style.GRID_CELL_SIZE self._entry = Gtk.TextView() self._entry.set_wrap_mode(Gtk.WrapMode.WORD) self._entry.set_pixels_above_lines(0) self._entry.set_top_margin(10) self._entry.set_bottom_margin(10) self._entry.set_right_margin(10) self._entry.set_left_margin(10) self._entry.set_size_request(entry_width, entry_height) font_desc = Pango.font_description_from_string('14') self._entry.modify_font(font_desc) self.text_buffer = self._entry.get_buffer() self.text_buffer.set_text(PLACEHOLDER) self._entry.connect('focus-in-event', self._text_focus_in_cb) self._entry.connect('key-press-event', self._text_focus_in_cb) self._entry.connect('focus-out-event', self._text_focus_out_cb) self._entry.get_buffer().connect('changed', self._text_changed_cb) self._entry.show() self._scrolled_window = Gtk.ScrolledWindow() self._scrolled_window.set_shadow_type(Gtk.ShadowType.ETCHED_IN) self._scrolled_window.set_size_request( Gdk.Screen.width() - 6 * style.GRID_CELL_SIZE, style.GRID_CELL_SIZE * 3) self._scrolled_window.set_border_width(5) self._scrolled_window.add(self._entry) if self.tablet_mode: self._fixed.put(self._scrolled_window, 3 * style.GRID_CELL_SIZE, style.DEFAULT_SPACING) else: self._fixed.put( self._scrolled_window, 3 * style.GRID_CELL_SIZE, Gdk.Screen.height() - style.DEFAULT_SPACING - style.GRID_CELL_SIZE * 4) self._scrolled_window.show() self._fixed.show() self._game = Game(self._canvas, parent=self, path=self._path, root=activity.get_bundle_path(), colors=self._colors) self._setup_presence_service() if 'mode' in self.metadata: self._game.set_mode(self.metadata['mode']) if self.metadata['mode'] == 'array': self.array_button.set_active(True) self.autoplay_button.set_sensitive(False) else: self._linear_button.set_active(True) if 'uid' in self.metadata: self._uid = self.metadata['uid'] else: self._uid = generate_uid() self.metadata['uid'] = self._uid if 'dotlist' in self.metadata: self._restore() self.check_audio_status() self.check_text_status() else: self._game.new_game() Gdk.Screen.get_default().connect('size-changed', self._configure_cb) def close(self, **kwargs): aplay.close() activity.Activity.close(self, **kwargs) def _configure_cb(self, event): self._canvas.set_size_request(int(Gdk.Screen.width()), int(Gdk.Screen.height())) self._vbox.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) entry_width = Gdk.Screen.width() - 6 * style.GRID_CELL_SIZE entry_height = 3 * style.GRID_CELL_SIZE self._entry.set_size_request(entry_width, entry_height) self._scrolled_window.set_size_request( Gdk.Screen.width() - 6 * style.GRID_CELL_SIZE, style.GRID_CELL_SIZE * 3) if not self.tablet_mode: self._fixed.move( self._scrolled_window, 3 * style.GRID_CELL_SIZE, Gdk.Screen.height() - style.DEFAULT_SPACING - style.GRID_CELL_SIZE * 4) self._game.configure() def _restore_cursor(self): ''' No longer waiting, so restore standard cursor. ''' if not hasattr(self, 'get_window'): return self.get_window().set_cursor(self._old_cursor) def _waiting_cursor(self): ''' Waiting, so set watch cursor. ''' if not hasattr(self, 'get_window'): return self._old_cursor = self.get_window().get_cursor() self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH)) def _text_changed_cb(self, text_buffer): self._entry.place_cursor_onscreen() def _fixed_resize_cb(self, widget=None, rect=None): ''' If a toolbar opens or closes, we need to resize the vbox holding out scrolling window. ''' self._vbox.set_size_request(rect.width, rect.height) def _text_focus_in_cb(self, widget=None, event=None): bounds = self.text_buffer.get_bounds() text = self.text_buffer.get_text(bounds[0], bounds[1], True) if text in [PLACEHOLDER, PLACEHOLDER1, PLACEHOLDER2]: self.text_buffer.set_text('') self.metadata['dirty'] = 'True' self._game.set_speak_icon_state(True) if self._game.playing: self._game.stop() def _text_focus_out_cb(self, widget=None, event=None): self.speak_text_cb() def speak_text_cb(self, button=None): bounds = self.text_buffer.get_bounds() text = self.text_buffer.get_text(bounds[0], bounds[1], True) if self._game.get_mode() == 'array': if text != PLACEHOLDER: self.metadata['text'] = text self.metadata['dirty'] = 'True' self._game.set_speak_icon_state(True) else: self._game.set_speak_icon_state(False) else: if text not in [PLACEHOLDER, PLACEHOLDER1, PLACEHOLDER2]: key = 'text-%d' % self._game.current_image self.metadata[key] = text self.metadata['dirty'] = 'True' self._game.set_speak_icon_state(True) else: self._game.set_speak_icon_state(False) def check_text_status(self): self._game.set_speak_icon_state(False) if self._game.get_mode() == 'array': if 'text' in self.metadata: self.text_buffer.set_text(self.metadata['text']) if len(self.metadata['text']) > 0: self.metadata['dirty'] = 'True' self._game.set_speak_icon_state(True) else: self.text_buffer.set_text(PLACEHOLDER) else: self.text_buffer.set_text(PLACEHOLDER) else: key = 'text-%d' % self._game.current_image if key in self.metadata: self.text_buffer.set_text(self.metadata[key]) if len(self.metadata[key]) > 0: self.metadata['dirty'] = 'True' self._game.set_speak_icon_state(True) elif self._game.current_image == 0: self.text_buffer.set_text(PLACEHOLDER1) else: self.text_buffer.set_text(PLACEHOLDER2) elif self._game.current_image == 0: self.text_buffer.set_text(PLACEHOLDER1) else: self.text_buffer.set_text(PLACEHOLDER2) def _clear_text(self): if 'text' in self.metadata: self.metadata['text'] = '' for i in range(9): if 'text-%d' % i in self.metadata: self.metadata['text-%d' % i] = '' if 'dirty' in self.metadata: self.metadata['dirty'] = 'False' if self._game.get_mode() == 'array': self.text_buffer.set_text(PLACEHOLDER) elif self._game.current_image == 0: self.text_buffer.set_text(PLACEHOLDER1) else: self.text_buffer.set_text(PLACEHOLDER2) self._game.set_speak_icon_state(False) def _clear_audio_notes(self): dsobject = self._search_for_audio_note(self._uid, target=self._uid) if dsobject is not None: dsobject.metadata['tags'] = '' datastore.write(dsobject) dsobject.destroy() for i in range(9): target = '%s-%d' % (self._uid, i) dsobject = self._search_for_audio_note(self._uid, target=target) if dsobject is not None: dsobject.metadata['tags'] = '' datastore.write(dsobject) dsobject.destroy() self._game.set_play_icon_state(False) def check_audio_status(self): if self._search_for_audio_note(self._uid): self._game.set_play_icon_state(True) else: self._game.set_play_icon_state(False) def _setup_toolbars(self): ''' Setup the toolbars. ''' self.max_participants = 1 # collaboration is unfinished toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar self._new_game_button_h = button_factory('view-refresh', self.toolbar, self._new_game_cb, tooltip=_('Load new images')) self.array_button = radio_factory('array', self.toolbar, self._array_cb, tooltip=_('View images all at once'), group=None) self._linear_button = radio_factory( 'linear', self.toolbar, self._linear_cb, tooltip=_('View images one at a time'), group=self.array_button) self.autoplay_button = button_factory('media-playback-start', self.toolbar, self._do_autoplay_cb, tooltip=_('Play')) self.autoplay_button.set_sensitive(False) separator_factory(self.toolbar) self.save_as_image = button_factory('image-saveoff', self.toolbar, self._do_save_as_image_cb, tooltip=_('Save as image')) self.save_as_pdf = button_factory('save-as-pdf', self.toolbar, self._do_save_as_pdf_cb, tooltip=_('Save as PDF')) separator_factory(toolbox.toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() def _do_autoplay_cb(self, button=None): if self._game.playing: self._game.stop() else: self.autoplay_button.set_icon_name('media-playback-pause') self.autoplay_button.set_tooltip(_('Pause')) self.array_button.set_sensitive(False) self._game.autoplay() def _array_cb(self, button=None): self.speak_text_cb() self._game.set_mode('array') self.autoplay_button.set_sensitive(False) if self._uid is not None: self.check_audio_status() self.check_text_status() def _linear_cb(self, button=None): self.speak_text_cb() self._game.set_mode('linear') self.autoplay_button.set_sensitive(True) if self._uid is not None: self.check_audio_status() self.check_text_status() def _new_game_cb(self, button=None): ''' Start a new game. ''' if 'dirty' in self.metadata and self.metadata['dirty'] == 'True': if self._alert is not None: self.remove_alert(self._alert) self._alert = None self._alert = ConfirmationAlert() self._alert.props.title = \ _('Do you really want to load new images?') self._alert.props.msg = _('You have done work on this story.' ' Do you want to overwrite it?') self._alert.connect('response', self._confirmation_alert_cb) self.add_alert(self._alert) else: self.autoplay_button.set_sensitive(False) self._game.new_game() def _confirmation_alert_cb(self, alert, response_id): self.remove_alert(alert) if response_id is Gtk.ResponseType.OK: self.autoplay_button.set_sensitive(False) self._clear_text() self._clear_audio_notes() self._game.new_game() def write_file(self, file_path): ''' Write the grid status to the Journal ''' dot_list = self._game.save_game() self.metadata['dotlist'] = '' for dot in dot_list: self.metadata['dotlist'] += str(dot) if dot_list.index(dot) < len(dot_list) - 1: self.metadata['dotlist'] += ' ' self.metadata['mode'] = self._game.get_mode() self.speak_text_cb() def _restore(self): ''' Restore the game state from metadata ''' dot_list = [] dots = self.metadata['dotlist'].split() for dot in dots: dot_list.append(int(dot)) self._game.restore_game(dot_list) def _search_for_audio_note(self, obj_id, target=None): ''' Look to see if there is already a sound recorded for this dsobject: the object id is stored in a tag in the audio file. ''' dsobjects, nobjects = datastore.find({'mime_type': ['audio/ogg']}) # Look for tag that matches the target object id if target is None: if self._game.get_mode() == 'array': target = obj_id else: target = '%s-%d' % (obj_id, self._game.current_image) for dsobject in dsobjects: if 'tags' in dsobject.metadata and \ target in dsobject.metadata['tags']: _logger.debug('Found audio note') self.metadata['dirty'] = 'True' return dsobject return None def _do_save_as_pdf_cb(self, button=None): self._waiting_cursor() self._notify_successful_save(title=_('Save as PDF')) GObject.idle_add(self._save_as_pdf) def _save_as_pdf(self): self.speak_text_cb() file_path = os.path.join(self.datapath, 'output.pdf') if 'description' in self.metadata: save_pdf(self, file_path, self._nick, description=self.metadata['description']) else: save_pdf(self, file_path, self._nick) dsobject = datastore.create() dsobject.metadata['title'] = '%s %s' % \ (self.metadata['title'], _('PDF')) dsobject.metadata['icon-color'] = profile.get_color().to_string() dsobject.metadata['mime_type'] = 'application/pdf' dsobject.metadata['activity'] = 'org.laptop.sugar3.ReadActivity' dsobject.set_file_path(file_path) datastore.write(dsobject) dsobject.destroy() os.remove(file_path) GObject.timeout_add(1000, self._remove_alert) def _do_save_as_image_cb(self, button=None): self._waiting_cursor() self._notify_successful_save(title=_('Save as image')) GObject.idle_add(self._save_as_image) def _save_as_image(self): ''' Grab the current canvas and save it to the Journal. ''' if self._uid is None: self._uid = generate_uid() if self._game.get_mode() == 'array': target = self._uid else: target = '%s-%d' % (self._uid, self._game.current_image) file_path = os.path.join(self.datapath, 'story.png') png_surface = self._game.export() png_surface.write_to_png(file_path) dsobject = datastore.create() dsobject.metadata['title'] = '%s %s' % \ (self.metadata['title'], _('image')) dsobject.metadata['icon-color'] = profile.get_color().to_string() dsobject.metadata['mime_type'] = 'image/png' dsobject.metadata['tags'] = target dsobject.set_file_path(file_path) datastore.write(dsobject) dsobject.destroy() os.remove(file_path) GObject.timeout_add(1000, self._remove_alert) def record_cb(self, button=None, cb=None): ''' Start/stop audio recording ''' if self._arecord is None: self._arecord = Arecord(self) if self.recording: # Was recording, so stop and later save self._game.set_record_icon_state(False) self._arecord.stop_recording_audio() self.recording = False self.busy() GObject.timeout_add(100, self._is_record_complete_timeout, cb) else: # Wasn't recording, so start self._game.set_record_icon_state(True) self._arecord.record_audio() self.recording = True def _is_record_complete_timeout(self, cb=None): if not self._arecord.is_complete(): return True # call back later self._save_recording() self.unbusy() if cb is not None: cb() return False # do not call back def playback_recording_cb(self, button=None): ''' Play back current recording ''' if self.recording: # Stop recording if we happen to be recording self.record_cb(cb=self._playback_recording) else: self._playback_recording() def _playback_recording(self): path = os.path.join(self.datapath, 'output.ogg') if self._uid is not None: dsobject = self._search_for_audio_note(self._uid) if dsobject is not None: path = dsobject.file_path aplay.play(path) def _save_recording(self): self.metadata['dirty'] = 'True' # So we know that we've done work if os.path.exists(os.path.join(self.datapath, 'output.ogg')): _logger.debug('Saving recording to Journal...') if self._uid is None: self._uid = generate_uid() if self._game.get_mode() == 'array': target = self._uid else: target = '%s-%d' % (self._uid, self._game.current_image) dsobject = self._search_for_audio_note(target) if dsobject is None: dsobject = datastore.create() dsobject.metadata['title'] = \ _('audio note for %s') % (self.metadata['title']) dsobject.metadata['icon-color'] = profile.get_color().to_string() dsobject.metadata['mime_type'] = 'audio/ogg' if self._uid is not None: dsobject.metadata['tags'] = target dsobject.set_file_path(os.path.join(self.datapath, 'output.ogg')) datastore.write(dsobject) dsobject.destroy() # Enable playback after record is finished self._game.set_play_icon_state(True) self.metadata['dirty'] = 'True' # Always save an image with the recording. # self._do_save_as_image_cb() else: _logger.debug('Nothing to save...') return def _notify_successful_save(self, title='', msg=''): ''' Notify user when saves are completed ''' self._alert = Alert() self._alert.props.title = title self._alert.props.msg = msg self.add_alert(self._alert) self._alert.show() def _remove_alert(self): if self._alert is not None: self.remove_alert(self._alert) self._alert = None self._restore_cursor() # Collaboration-related methods def _setup_presence_service(self): ''' Setup the Presence Service. ''' self.pservice = presenceservice.get_instance() self.initiating = None # sharing (True) or joining (False) owner = self.pservice.get_owner() self.owner = owner self._share = '' self.connect('shared', self._shared_cb) self.connect('joined', self._joined_cb) def _shared_cb(self, activity): ''' Either set up initial share...''' self._new_tube_common(True) def _joined_cb(self, activity): ''' ...or join an exisiting share. ''' self._new_tube_common(False) def _new_tube_common(self, sharer): ''' Joining and sharing are mostly the same... ''' shared_activity = self.get_shared_activity() if shared_activity is None: _logger.error('Failed to share or join activity') return self.initiating = sharer self.waiting_for_hand = not sharer self.conn = shared_activity.telepathy_conn self.tubes_chan = shared_activity.telepathy_tubes_chan self.text_chan = shared_activity.telepathy_text_chan self.tubes_chan[ TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].connect_to_signal( 'NewTube', self._new_tube_cb) if sharer: _logger.debug('This is my activity: making a tube...') self.tubes_chan[ TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].OfferDBusTube( SERVICE, {}) else: _logger.debug('I am joining an activity: waiting for a tube...') self.tubes_chan[TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].ListTubes( reply_handler=self._list_tubes_reply_cb, error_handler=self._list_tubes_error_cb) self._game.set_sharing(True) def _list_tubes_reply_cb(self, tubes): ''' Reply to a list request. ''' for tube_info in tubes: self._new_tube_cb(*tube_info) def _list_tubes_error_cb(self, e): ''' Log errors. ''' _logger.error('Error: ListTubes() failed: %s' % (e)) def _new_tube_cb(self, id, initiator, type, service, params, state): ''' Create a new tube. ''' _logger.debug('New tube: ID=%d initator=%d type=%d service=%s' ' params=%r state=%d' % (id, initiator, type, service, params, state)) if (type == TelepathyGLib.TubeType.DBUS and service == SERVICE): if state == TelepathyGLib.TubeState.LOCAL_PENDING: self.tubes_chan[ TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].AcceptDBusTube(id) self.collab = CollabWrapper(self) self.collab.message.connect(self.event_received_cb) self.collab.setup() def _setup_dispatch_table(self): ''' Associate tokens with commands. ''' self._processing_methods = { 'n': [self._receive_new_images, 'get a new game grid'], 'p': [self._receive_dot_click, 'get a dot click'], } def event_received_cb(self, collab, buddy, msg): ''' Data from a tube has arrived. ''' command = msg.get("command") if command is None: return payload = msg.get("payload") self._processing_methods[command][0](payload) def send_new_images(self): ''' Send a new image grid to all players ''' self.send_event("n", json_dump(self._game.save_game())) def _receive_new_images(self, payload): ''' Sharer can start a new game. ''' dot_list = json_load(payload) self._game.restore_game(dot_list) def send_dot_click(self, dot, color): ''' Send a dot click to all the players ''' self.send_event("p", json_dump([dot, color])) def _receive_dot_click(self, payload): ''' When a dot is clicked, everyone should change its color. ''' (dot, color) = json_load(payload) self._game.remote_button_press(dot, color) def send_event(self, command, payload): ''' Send event through the tube. ''' if hasattr(self, 'chattube') and self.collab is not None: self.collab.SendText(dict( command=command, payload=payload, ))