Example #1
0
    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()
Example #2
0
    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()
Example #3
0
    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()
Example #4
0
    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()
Example #5
0
    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()
Example #6
0
    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
Example #7
0
    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):
        "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
Example #10
0
    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()
Example #11
0
    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()
Example #12
0
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})
Example #15
0
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)
Example #17
0
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)
Example #18
0
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
Example #19
0
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))
Example #20
0
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()
Example #21
0
    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()
Example #22
0
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})
Example #23
0
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()
Example #24
0
    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()
Example #25
0
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()
Example #26
0
    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()
Example #27
0
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})
Example #28
0
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()
Example #29
0
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,
            ))