示例#1
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 = physics.main(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 _new_tube_cb(self, id, initiator, type, service, params, state):
        """ Create a new tube. """
        debug_output(
            'New tube: ID=%d initator=%d type=%d service=%s \
                     params=%r state=%d' %
            (id, initiator, type, service, params, state),
            self._tw.running_sugar)

        if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES]\
                    .AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            # Now that we have the tube, we can ask for the turtle dictionary.
            if self.waiting_for_turtles:  # A joiner must wait for turtles.
                debug_output('Sending a request for the turtle dictionary',
                             self._tw.running_sugar)
                # We need to send our own nick, colors, and turtle position
                colors = self._get_colors()
                event = data_to_string([self._get_nick(), colors])
                debug_output(event, self._tw.running_sugar)
                self.send_event("t", {"payload": event})
    def _setup_collab(self):
        ''' Setup the Collab Wrapper. '''
        self.initiating = None  # sharing (True) or joining (False)
        self._collab = CollabWrapper(self)
        self._collab.connect('message', self.__message_cb)

        owner = self._collab._leader
        self.owner = owner
        self._game.set_sharing(True)
        self._collab.setup()
示例#4
0
    def __init__(self, activity):
        Gtk.VBox.__init__(self)

        self.collab = CollabWrapper(activity)
        self.collab.connect('message', self.__message_cb)
        self.collab.connect('joined', self.__joined_cb)

        self.view = View()
        self.view.connect("insert-char", self.__insert_char_cb)
        self.view.connect("cursor-position-changed",
                          self.__cursor_position_changed_cb)
        self.view.connect("tag-applied", self.__tag_applied_cb)
        self.view.connect("tag-removed", self.__tag_removed_cb)
        self.pack_start(self.view, True, True, 0)
    def _new_tube_cb(self, id, initiator, type, service, params, state):
        """ Create a new tube. """
        debug_output(
            'New tube: ID=%d initator=%d type=%d service=%s \
                     params=%r state=%d' %
            (id, initiator, type, service, params, state), self._tw.running_sugar)

        if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES]\
                    .AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            # Now that we have the tube, we can ask for the turtle dictionary.
            if self.waiting_for_turtles:  # A joiner must wait for turtles.
                debug_output('Sending a request for the turtle dictionary',
                             self._tw.running_sugar)
                # We need to send our own nick, colors, and turtle position
                colors = self._get_colors()
                event = data_to_string([self._get_nick(), colors])
                debug_output(event, self._tw.running_sugar)
                self.send_event("t", {"payload": event})
示例#6
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_canvas = sugargame.canvas.PygameCanvas(self)
        self.game = physics.main(self)

        self.preview = None
        self._sample_window = None

        self._fixed = Gtk.Fixed()
        self._fixed.put(self.game_canvas, 0, 0)

        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._fixed)
        Gdk.Screen.get_default().connect("size-changed", self.__configure_cb)

        logging.debug(os.path.join(activity.get_activity_root(), "data", "data"))
        self.game_canvas.run_pygame(self.game.run)
        self.show_all()
        self._collab.setup()
示例#7
0
    def _new_tube_cb(self, id, initiator, type, service, params, state):
        """ Create a new tube. """
        print('New tube: ID=%d initator=%d type=%d service=%s params=%r \
state=%d' % (id, initiator, type, service, params, state))

        if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[ \
                              telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            # Let the sharer know joiner is waiting for a hand.
            if self.waiting_for_hand:
                self.send_event("j", json_dump([self.nick, self.colors]))
示例#8
0
    def __init__(self, file_path, url):
        GObject.GObject.__init__(self)
        logging.debug('websocket url %s', url)
        # base64 encode the file
        self._file = tempfile.TemporaryFile(mode='r+')
        base64.encode(open(file_path, 'r'), self._file)
        self._file.seek(0)

        self.buddies = {}

        self.collab = CollabWrapper(self)
        self.collab.message.connect(self._on_message)
        self.collab.setup()

        self._chunk = str(self._file.read(CHUNK_SIZE))

        self.start()
    def _new_tube_cb(self, id, initiator, type, service, params, state):
        ''' Create a new tube. '''
        _logger.debug(
            'Newtube: ID=%d initator=%d type=%d service=%s params=%r state=%d',
            id, initiator, type, service, params, state)

        if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[ \
                              telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            # Let the sharer know a new joiner has arrived.
            if self.waiting_for_fraction:
                self.send_event(
                    'j', {"data": (json_dump([self.nick, self._colors]))})
示例#10
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 == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(
                    id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            if self._waiting_for_reflections:
                self.send_event(JOIN_CMD, {})
                self._joined_alert = Alert()
                self._joined_alert.props.title = _('Please wait')
                self._joined_alert.props.msg = _('Requesting reflections...')
                self.add_alert(self._joined_alert)
示例#11
0
    def __init__(self, activity):
        Gtk.VBox.__init__(self)

        self.collab = CollabWrapper(activity)
        self.collab.connect('message', self.__message_cb)
        self.collab.connect('joined', self.__joined_cb)

        self.view = View()
        self.view.connect("insert-char", self.__insert_char_cb)
        self.view.connect("cursor-position-changed", self.__cursor_position_changed_cb)
        self.view.connect("tag-applied", self.__tag_applied_cb)
        self.view.connect("tag-removed", self.__tag_removed_cb)
        self.pack_start(self.view, True, True, 0)
示例#12
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_canvas = sugargame.canvas.PygameCanvas(self)
        self.game = physics.main(self)

        self.preview = None
        self._sample_window = None

        self._fixed = Gtk.Fixed()
        self._fixed.put(self.game_canvas, 0, 0)

        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._fixed)
        Gdk.Screen.get_default().connect('size-changed',
                                         self.__configure_cb)

        logging.debug(os.path.join(
                      activity.get_activity_root(), 'data', 'data'))
        self.game_canvas.run_pygame(self.game.run)
        self.show_all()
        self._collab.setup()
示例#13
0
    def __init__(self, file_path, url):
        GObject.GObject.__init__(self)
        logging.debug('websocket url %s', url)
        # base64 encode the file
        self._file = tempfile.TemporaryFile(mode='r+')
        base64.encode(open(file_path, 'r'), self._file)
        self._file.seek(0)

        self.buddies = {}

        self.collab = CollabWrapper(self)
        self.collab.message.connect(self._on_message)
        self.collab.setup()

        self._chunk = str(self._file.read(CHUNK_SIZE))

        self.start()
示例#14
0
    def _new_tube_cb(self, id, initiator, type, service, params, state):
        """ Create a new tube. """
        print('New tube: ID=%d initator=%d type=%d service=%s params=%r \
state=%d' % (id, initiator, type, service, params, state))

        if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[ \
                              telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            # Let the sharer know joiner is waiting for a hand.
            if self.waiting_for_hand:
                self.send_event("j", json_dump([self.nick, self.colors]))
示例#15
0
class Uploader(GObject.GObject):

    __gsignals__ = {
        'uploaded': (GObject.SignalFlags.RUN_FIRST, None, ([bool])),
        'transfer-state-changed':
        (GObject.SignalFlags.RUN_FIRST, None, ([str]))
    }

    def __init__(self, file_path, url):
        GObject.GObject.__init__(self)
        logging.debug('websocket url %s', url)
        # base64 encode the file
        self._file = tempfile.TemporaryFile(mode='r+')
        base64.encode(open(file_path, 'r'), self._file)
        self._file.seek(0)

        self.buddies = {}

        self.collab = CollabWrapper(self)
        self.collab.message.connect(self._on_message)
        self.collab.setup()

        self._chunk = str(self._file.read(CHUNK_SIZE))

        self.start()

    def start(self):
        self.collab.send(JOIN_CMD, {
            "nick": profile.get_nick_name(),
            "chunk": self._chunk
        })

    def _on_message(self, collab, buddy, msg):
        command = msg.get("cmd")

        if command == JOIN_CMD:
            self.buddies[msg.get("nick")] = msg.get("chunk")

        elif command == CLOSE_CMD:
            del self.buddies[msg.get("nick")]

    def _on_close(self, ws):
        self._file.close()
        self.send_event(CLOSE_CMD, {
            "nick": profile.get_nick_name(),
            "chunk": self._chunk
        })
        GObject.idle_add(self.emit, 'uploaded', True)

    def send_event(self, msg, payload={}):
        payload["cmd"] = msg
        self.collab.post(payload)
    def _new_tube_cb(self, id, initiator, type, service, params, state):
        ''' Create a new tube. '''
        _logger.debug(
            'Newtube: ID=%d initator=%d type=%d service=%s params=%r state=%d',
            id, initiator, type, service, params, state)

        if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[ \
                              telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            # Let the sharer know a new joiner has arrived.
            if self.waiting_for_fraction:
                self.send_event('j', {"data": (json_dump([self.nick,
                                                     self._colors]))})
示例#17
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 == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[
                    telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            if self._waiting_for_reflections:
                self.send_event(JOIN_CMD, {})
                self._joined_alert = Alert()
                self._joined_alert.props.title = _('Please wait')
                self._joined_alert.props.msg = _('Requesting reflections...')
                self.add_alert(self._joined_alert)
示例#18
0
class Uploader(GObject.GObject):

    __gsignals__ = {
        'uploaded': (GObject.SignalFlags.RUN_FIRST, None, ([bool])),
        'transfer-state-changed': (GObject.SignalFlags.RUN_FIRST, None,
                                   ([str]))
    }

    def __init__(self, file_path, url):
        GObject.GObject.__init__(self)
        logging.debug('websocket url %s', url)
        # base64 encode the file
        self._file = tempfile.TemporaryFile(mode='r+')
        base64.encode(open(file_path, 'r'), self._file)
        self._file.seek(0)

        self.buddies = {}

        self.collab = CollabWrapper(self)
        self.collab.message.connect(self._on_message)
        self.collab.setup()

        self._chunk = str(self._file.read(CHUNK_SIZE))

        self.start()

    def start(self):
        self.collab.send(JOIN_CMD, {"nick": profile.get_nick_name(), "chunk": self._chunk})

    def _on_message(self, collab, buddy, msg):
        command = msg.get("cmd")

        if command == JOIN_CMD:
            self.buddies[msg.get("nick")] = msg.get("chunk")

        elif command == CLOSE_CMD:
            del self.buddies[msg.get("nick")]

    def _on_close(self, ws):
        self._file.close()
        self.send_event(CLOSE_CMD, {"nick": profile.get_nick_name(), "chunk": self._chunk})
        GObject.idle_add(self.emit, 'uploaded', True)

    def send_event(self, msg, payload={}):
        payload["cmd"] = msg
        self.collab.post(payload)
示例#19
0
class PathsActivity(activity.Activity):
    """ Path puzzle game """

    def __init__(self, handle):
        """ Initialize the toolbars and the game board """
        super(PathsActivity, 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._setup_presence_service()

        # Restore game state from Journal or start new game
        if 'deck' in self.metadata:
            self._restore()
        else:
            self._game.new_game()

    def _setup_toolbars(self):
        """ Setup the toolbars. """

        self.max_participants = MAX_HANDS

        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.'))

        self.robot_button = button_factory(
            'robot-off', self.toolbar, self._robot_cb,
            tooltip= _('Play with the robot.'))

        self.player = image_factory(
            svg_str_to_pixbuf(generate_xo(scale=0.8,
                                          colors=['#303030', '#303030'])),
            self.toolbar, tooltip=self.nick)

        self.dialog_button = button_factory(
            'go-next', self.toolbar, self._dialog_cb,
            tooltip=_('Turn complete'))

        self.status = label_factory(self.toolbar, '')

        self.hint_button = button_factory(
            'help-toolbar', self.toolbar, self._hint_cb,
            tooltip=_('Help'))

        self.score = label_factory(self.toolbar, _('Score: ') + '0')

        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 _new_game_cb(self, button=None):
        ''' Start a new game. '''
        self._game.new_game()

    def _robot_cb(self, button=None):
        ''' Play with the computer (or not). '''
        if not self._game.playing_with_robot:
            self.set_robot_status(True, 'robot-on')
            self._game.new_game()
        else:
            self.set_robot_status(False, 'robot-off')
            self._game.new_game()

    def set_robot_status(self, status, icon):
        ''' Reset robot icon and status '''
        self._game.playing_with_robot = status
        self.robot_button.set_icon(icon)

    def _dialog_cb(self, button=None):
        ''' Send end of turn '''
        if self._game.placed_a_tile:
            self._game.took_my_turn()

    def _hint_cb(self, button=None):
        ''' Give a hint as to where to place a tile '''
        if not self._game.placed_a_tile:
            self._game.give_a_hint()

    def write_file(self, file_path):
        """ Write the grid status to the Journal """
        if not hasattr(self, '_game'):
            return
        for i in range(MAX_HANDS):
            if 'hand-' + str(i) in self.metadata:
                del self.metadata['hand-' + str(i)]
        if 'robot' in self.metadata:
            del self.metadata['robot']
        self.metadata['deck'] = self._game.deck.serialize()
        self.metadata['grid'] = self._game.grid.serialize()
        if self._game.we_are_sharing():
            for i, hand in enumerate(self._game.hands):
                self.metadata['hand-' + str(i)] = hand.serialize()
        else:
            self.metadata['hand-0'] = self._game.hands[0].serialize()
            if self._game.playing_with_robot:
                self.metadata['hand-1'] = self._game.hands[1].serialize()
                self.metadata['robot'] = 'True'

        self.metadata['score'] = str(self._game.score)
        self.metadata['index'] = str(self._game.deck.index)
        if self._game.last_spr_moved is not None and \
           self._game.grid.spr_to_grid(self._game.last_spr_moved) is not None:
            self.metadata['last'] = str(self._game.grid.grid[
                self._game.grid.spr_to_grid(self._game.last_spr_moved)].number)

    def _restore(self):
        """ Restore the game state from metadata """
        if 'robot' in self.metadata:
            self.set_robot_status(True, 'robot-on')
        if 'deck' in self.metadata:
            self._game.deck.restore(self.metadata['deck'])
        if 'grid' in self.metadata:
            self._game.grid.restore(self.metadata['grid'], self._game.deck)
        self._game.show_connected_tiles()

        for i in range(MAX_HANDS):
            if 'hand-' + str(i) in self.metadata:
                # hand-0 is already appended
                if i > 0:  # Add robot or shared hand?
                    self._game.hands.append(
                        Hand(self._game.tile_width, self._game.tile_height,
                             remote=True))
                self._game.hands[i].restore(self.metadata['hand-' + str(i)],
                                            self._game.deck)

        if 'index' in self.metadata:
            self._game.deck.index = int(self.metadata['index'])
        else:
            self._game.deck.index = ROW * COL - self._game.grid.tiles_in_grid()
            for hand in self._game.hands:
                self._game.deck.index += (COL - hand.tiles_in_hand())

        if 'score' in self.metadata:
            self._game.score = int(self.metadata['score'])
            self.score.set_label(_('Score: ') + str(self._game.score))

        self._game.last_spr_moved = None
        if 'last' in self.metadata:
            j = int(self.metadata['last'])
            for k in range(ROW * COL):
                if self._game.deck.tiles[k].number == j:
                    self._game.last_spr_moved = self._game.deck.tiles[k].spr
                    break

    # 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._game.buddies.append(self.nick)
        self._player_colors = [self.colors]
        self._player_pixbuf = [svg_str_to_pixbuf(
                generate_xo(scale=0.8, colors=self.colors))]
        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... """
        if self._shared_activity is None:
            print("Error: Failed to share or join activity ... \
                _shared_activity is null in _shared_cb()")
            return

        self.initiating = sharer
        self.waiting_for_hand = not sharer

        self.conn = self._shared_activity.telepathy_conn
        self.tubes_chan = self._shared_activity.telepathy_tubes_chan
        self.text_chan = self._shared_activity.telepathy_text_chan

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        if sharer:
            print('This is my activity: making a tube...')
            id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
                SERVICE, {})

            self._new_game_button.set_tooltip(
                _('Start a new game once everyone has joined.'))
        else:
            print('I am joining an activity: waiting for a tube...')
            self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
                reply_handler=self._list_tubes_reply_cb,
                error_handler=self._list_tubes_error_cb)

            self._new_game_button.set_icon('no-new-game')
            self._new_game_button.set_tooltip(
                _('Only the sharer can start a new game.'))

        self.robot_button.set_icon('no-robot')
        self.robot_button.set_tooltip(_('The robot is disabled when sharing.'))

        # display your XO on the toolbar
        self.player.set_from_pixbuf(self._player_pixbuf[0])
        self.toolbar.show_all()

    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. """
        print('Error: ListTubes() failed: %s', e)

    def _new_tube_cb(self, id, initiator, type, service, params, state):
        """ Create a new tube. """
        print('New tube: ID=%d initator=%d type=%d service=%s params=%r \
state=%d' % (id, initiator, type, service, params, state))

        if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[ \
                              telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            # Let the sharer know joiner is waiting for a hand.
            if self.waiting_for_hand:
                self.send_event("j", json_dump([self.nick, self.colors]))

    def _setup_dispatch_table(self):
        self._processing_methods = {
            'n': [self._new_game, 'new game'],
            'j': [self._new_joiner, 'new joiner'],
            'b': [self._buddy_list, 'buddy list'],
            'd': [self._sending_deck, 'sending deck'],
            'h': [self._sending_hand, 'sending hand'],
            'p': [self._play_a_piece, 'play a piece'],
            't': [self._take_a_turn, 'take a turn'],
            'g': [self._game_over, 'game over']
            }

    def event_received_cb(self, collab, buddy, msg):
        ''' Data from a tube has arrived. '''
        command = msg.get("command")
        if action is None:
            return

        payload = msg.get("payload")
        self._processing_methods[command][0](payload)

    def _new_joiner(self, payload):
        ''' Someone has joined; sharer adds them to the buddy list. '''
        [nick, colors] = json_load(payload)
        self.status.set_label(nick + ' ' + _('has joined.'))
        self._append_player(nick, colors)
        if self.initiating:
            payload = json_dump([self._game.buddies, self._player_colors])
            self.send_event("b", payload)

    def _append_player(self, nick, colors):
        ''' Keep a list of players, their colors, and an XO pixbuf '''
        if not nick in self._game.buddies:
            self._game.buddies.append(nick)
            self._player_colors.append(colors)
            self._player_pixbuf.append(svg_str_to_pixbuf(
                generate_xo(scale=0.8, colors=colors)))

    def _buddy_list(self, payload):
        ''' Sharer sent the updated buddy list. '''
        [buddies, colors] = json_load(payload)
        for i, nick in enumerate(buddies):
            self._append_player(nick, colors[i])

    def _new_game(self, payload):
        ''' Sharer can start a new game. '''
        if not self.initiating:
            self._game.new_game()

    def _game_over(self, payload):
        ''' Someone cannot plce a tile. '''
        if not self._game.saw_game_over:
            self._game.game_over()

    def _sending_deck(self, payload):
        ''' Sharer sends the deck. '''
        self._game.deck.restore(payload)
        for tile in self._game.deck.tiles:
            tile.reset()
            tile.hide()

    def _sending_hand(self, payload):
        ''' Sharer sends a hand. '''
        hand = json_load(payload)
        nick = hand[0]
        if nick == self.nick:
            self._game.hands[self._game.buddies.index(nick)].restore(
                payload, self._game.deck, buddy=True)

    def _play_a_piece(self, payload):
        ''' When a piece is played, everyone should move it into position. '''
        tile_number, orientation, grid_position = json_load(payload)
        for i in range(ROW * COL):  # find the tile with this number
            if self._game.deck.tiles[i].number == tile_number:
                tile_to_move = i
                break
        self._game.grid.add_tile_to_grid(tile_to_move, orientation,
                                         grid_position, self._game.deck)
        self._game.show_connected_tiles()

        if self.initiating:
            # First, remove the piece from whatever hand it was played.
            for i in range(COL):
                if self._game.hands[self._game.whos_turn].hand[i] is not None \
                   and \
                   self._game.hands[self._game.whos_turn].hand[i].number == \
                        tile_number:
                    self._game.hands[self._game.whos_turn].hand[i] = None
                    break

            # Then let the next player know it is their turn.
            self._game.whos_turn += 1
            if self._game.whos_turn == len(self._game.buddies):
                self._game.whos_turn = 0
            self.status.set_label(self.nick + ': ' + _('take a turn.'))
            self._take_a_turn(self._game.buddies[self._game.whos_turn])
            self.send_event("t", self._game.buddies[self._game.whos_turn])

    def _take_a_turn(self, nick):
        ''' If it is your turn, take it, otherwise, wait. '''
        if nick == self.nick:
            self._game.its_my_turn()
        else:
            self._game.its_their_turn(nick)

    def send_event(self, command, payload):
        """ Send event through the tube. """
        if hasattr(self, 'chattube') and self.collab is not None:
            self.collab.post(dict(
                command=command,
                payload=payload
            ))

    def set_player_on_toolbar(self, nick):
        self.player.set_from_pixbuf(self._player_pixbuf[
                self._game.buddies.index(nick)])
        self.player.set_tooltip_text(nick)
示例#20
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 = physics.main(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 __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'))
        self.game.run(True)

    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 = GObject.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 = GObject.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:
            GObject.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.loop = 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 not self.game.pygame_started:
            logging.debug('focus_event: pygame not yet initialized')
            return
        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)
                self.game.run(True)
            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):
        width = Gdk.Screen.width() / 4
        height = Gdk.Screen.height() / 4

        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)
            w = Gdk.Screen.width() / 2
            h = Gdk.Screen.height() / 2
            self._sample_window.set_size_request(w, h)
            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))
        GObject.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
    def __init__(self, handle):
        ''' Initialize the toolbars and the gnuchess '''
        try:
            super(GNUChessActivity, self).__init__(handle)
        except dbus.exceptions.DBusException as e:
            _logger.error(str(e))

        self.game_data = None
        self.playing_white = True
        self.playing_mode = 'easy'
        self.playing_robot = True
        self.showing_game_history = False
        self._restoring = True
        self.stopwatch_running = False
        self.time_interval = None
        self.timer_panel_visible = False

        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.buddy = None
        self.opponent_colors = None

        self.hardware = get_hardware()
        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.old_cursor = self.get_window().get_cursor()

        self._gnuchess = Gnuchess(canvas,
                                  parent=self,
                                  path=activity.get_bundle_path(),
                                  colors=self.colors)

        self.connect('shared', self._shared_cb)
        self.connect('joined', self._joined_cb)
        self._restoring = False

        self.collab = CollabWrapper(self)
        self.collab.connect('message', self._message_cb)
        self.collab.connect('joined', self._joined_cb)
        self.collab.setup()

        # Send the nick to our opponent
        if not self.collab.props.leader:
            self.send_nick()
            # And let the sharer know we've joined
            self.send_join()

        if self.game_data is not None:  # 'saved_game' in self.metadata:
            self._restore()
        else:
            self._gnuchess.new_game()
    def __init__(self, handle):
        activity.Activity.__init__(self, handle)
        self._object_id = handle.object_id
        self._collab = CollabWrapper(self)
        self._collab.incoming_file.connect(self.__incoming_file_cb)
        self._collab.buddy_joined.connect(self.__buddy_joined_cb)
        self._collab.joined.connect(self.__joined_cb)
        self._needs_file = False  # Set to true when we join

        # Status of temp file used for write_file:
        self._tempfile = None
        self._close_requested = False

        self._zoom_out_button = None
        self._zoom_in_button = None
        self.previous_image_button = None
        self.next_image_button = None

        self.scrolled_window = Gtk.ScrolledWindow()
        self.scrolled_window.set_policy(Gtk.PolicyType.ALWAYS,
                                        Gtk.PolicyType.ALWAYS)
        # disable sharing until a file is opened
        self.max_participants = 1

        # Don't use the default kinetic scrolling, let the view do the
        # drag-by-touch and pinch-to-zoom logic.
        self.scrolled_window.set_kinetic_scrolling(False)

        self.view = ImageView.ImageViewer()

        # Connect to the touch signal for performing drag-by-touch.
        self.view.add_events(Gdk.EventMask.TOUCH_MASK)
        self._touch_hid = self.view.connect('touch-event',
                                            self.__touch_event_cb)
        self.scrolled_window.add(self.view)
        self.view.show()

        self.connect('key-press-event', self.__key_press_cb)

        if GESTURES_AVAILABLE:
            # Connect to the zoom signals for performing
            # pinch-to-zoom.
            zoom_controller = SugarGestures.ZoomController()
            zoom_controller.attach(self,
                                   SugarGestures.EventControllerFlags.NONE)

            zoom_controller.connect('began', self.__zoomtouch_began_cb)
            zoom_controller.connect('scale-changed',
                                    self.__zoomtouch_changed_cb)
            zoom_controller.connect('ended', self.__zoomtouch_ended_cb)

        self._progress_alert = None

        toolbar_box = ToolbarBox()
        self._add_toolbar_buttons(toolbar_box)
        self.set_toolbar_box(toolbar_box)
        toolbar_box.show()

        if self._object_id is None or not self._jobject.file_path:
            empty_widgets = Gtk.EventBox()
            empty_widgets.modify_bg(Gtk.StateType.NORMAL,
                                    style.COLOR_WHITE.get_gdk_color())

            vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
            mvbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
            vbox.pack_start(mvbox, True, False, 0)

            image_icon = Icon(pixel_size=style.LARGE_ICON_SIZE,
                              icon_name='imageviewer',
                              stroke_color=style.COLOR_BUTTON_GREY.get_svg(),
                              fill_color=style.COLOR_TRANSPARENT.get_svg())
            mvbox.pack_start(image_icon, False, False, style.DEFAULT_PADDING)

            label = Gtk.Label('<span foreground="%s"><b>%s</b></span>' %
                              (style.COLOR_BUTTON_GREY.get_html(),
                               _('No image')))
            label.set_use_markup(True)
            mvbox.pack_start(label, False, False, style.DEFAULT_PADDING)

            hbox = Gtk.Box()
            open_image_btn = Gtk.Button()
            open_image_btn.connect('clicked', self._show_picker_cb)
            add_image = Gtk.Image.new_from_stock(Gtk.STOCK_ADD,
                                                 Gtk.IconSize.BUTTON)
            buttonbox = Gtk.Box()
            buttonbox.pack_start(add_image, False, True, 0)
            buttonbox.pack_end(Gtk.Label(_('Choose an image')), True, True, 5)
            open_image_btn.add(buttonbox)
            hbox.pack_start(open_image_btn, True, False, 0)
            mvbox.pack_start(hbox, False, False, style.DEFAULT_PADDING)

            empty_widgets.add(vbox)
            empty_widgets.show_all()
            self.set_canvas(empty_widgets)
        else:
            self.set_canvas(self.scrolled_window)
            self.scrolled_window.show()

        Gdk.Screen.get_default().connect('size-changed', self._configure_cb)
        self._collab.setup()
示例#23
0
    def __init__(self, handle):
        activity.Activity.__init__(self, handle)
        self._has_read_file = False
        self._collab = CollabWrapper(self)
        self._collab.message.connect(self.__message_cb)

        screen = Gdk.Screen.get_default()
        css_provider = Gtk.CssProvider.get_default()
        css_provider.load_from_path('style.css')
        context = Gtk.StyleContext()
        context.add_provider_for_screen(screen, css_provider,
                                        Gtk.STYLE_PROVIDER_PRIORITY_USER)

        toolbar_box = ToolbarBox()

        activity_button = ActivityToolbarButton(self)
        toolbar_box.toolbar.insert(activity_button, 0)
        activity_button.show()

        html = ToolButton('export-as-html')
        html.set_tooltip(_('Save as HTML'))
        html.connect('clicked', self.__export_as_html_cb)
        activity_button.props.page.insert(html, -1)
        html.show()

        abiword = ToolButton('export-as-abiword')
        abiword.set_tooltip(_('Save as a Write document'))
        abiword.connect('clicked', self.__export_as_abiword_cb)
        activity_button.props.page.insert(abiword, -1)
        abiword.show()

        add_button = AddToolButton(ALL_TYPE_NAMES)
        add_button.connect('add-type', self.__add_type_cb)
        toolbar_box.toolbar.insert(add_button, -1)
        add_button.show()
   
        separator = Gtk.SeparatorToolItem()
        separator.props.draw = False
        separator.set_expand(True)
        toolbar_box.toolbar.insert(separator, -1)
        separator.show()

        stop_button = StopButton(self)
        toolbar_box.toolbar.insert(stop_button, -1)
        stop_button.show()

        self.set_toolbar_box(toolbar_box)
        toolbar_box.show()

        self._main_sw = Gtk.ScrolledWindow()
        self._main_sw.set_policy(Gtk.PolicyType.NEVER,
                                 Gtk.PolicyType.ALWAYS)
        self._main_sw.connect('key-press-event',
                              self.__key_press_event_cb)

        self._main_list = MainList(self._main_sw, self._collab)
        self._main_list.connect('edit-row', self.__edit_row_cb)
        self._main_list.connect('deleted-row', self.__deleted_row_cb)
        self._main_sw.add(self._main_list)
        self._main_list.show()

        self._empty_message = EmptyMessage()

        self._overlay = Gtk.Overlay()
        self._overlay.add(self._empty_message)
        self._empty_message.show()
        self.set_canvas(self._overlay)
        self._overlay.show()

        self._collab.setup()
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()
        self._setup_dispatch_table()
        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)

        if self.shared_activity:
            # We're joining
            if not self.get_shared():
                xocolors = XoColor(profile.get_color().to_string())
                share_icon = Icon(icon_name='zoom-neighborhood',
                                  xo_color=xocolors)
                self._joined_alert = NotifyAlert()
                self._joined_alert.props.icon = share_icon
                self._joined_alert.props.title = _('Please wait')
                self._joined_alert.props.msg = _('Starting connection...')
                self._joined_alert.connect('response', self._alert_cancel_cb)
                self.add_alert(self._joined_alert)

                self._label.set_label(_('Wait for the sharer to start.'))

                # Wait for joined signal
                self.connect("joined", self._joined_cb)

        self._setup_sharing()

    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, state=palette.SECONDARY)
            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', {"data": (json_dump([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 _setup_sharing(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._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))
        ]
        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... '''
        if self.shared_activity is None:
            _logger.debug('Error: Failed to share or join activity ... \
                shared_activity is null in _shared_cb()')
            return

        self.initiating = sharer
        self.waiting_for_fraction = not sharer

        self.conn = self.shared_activity.telepathy_conn
        self.tubes_chan = self.shared_activity.telepathy_tubes_chan
        self.text_chan = self.shared_activity.telepathy_text_chan

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        if sharer:
            _logger.debug('This is my activity: making a tube...')
            id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
                SERVICE, {})

            self._label.set_label(_('Wait for others to join.'))
        else:
            _logger.debug('I am joining an activity: waiting for a tube...')
            self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
                reply_handler=self._list_tubes_reply_cb,
                error_handler=self._list_tubes_error_cb)

        # display your XO on the toolbar
        self._player.set_from_pixbuf(self._player_pixbufs[0])

    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.debug('Error: ListTubes() failed: %s', e)

    def _new_tube_cb(self, id, initiator, type, service, params, state):
        ''' Create a new tube. '''
        _logger.debug(
            'Newtube: ID=%d initator=%d type=%d service=%s params=%r state=%d',
            id, initiator, type, service, params, state)

        if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[ \
                              telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            # Let the sharer know a new joiner has arrived.
            if self.waiting_for_fraction:
                self.send_event(
                    'j', {"data": (json_dump([self.nick, self._colors]))})

    def _setup_dispatch_table(self):
        self._processing_methods = {
            'j': [self._new_joiner, 'new joiner'],
            'b': [self._buddy_list, 'buddy list'],
            'f': [self._receive_a_fraction, 'receive a fraction'],
            't': [self._take_a_turn, 'take a turn'],
            'l': [self._buddy_left, 'buddy left']
        }

    def event_received_cb(self, event_message):
        ''' Data from a tube has arrived. '''
        if len(event_message) == 0:
            return
        try:
            command, payload = event_message.split('|', 2)
        except ValueError:
            _logger.debug('Could not split event message %s', event_message)
            return
        _logger.debug('received an event %s|%s', command, payload)
        if self._playing:
            self._processing_methods[command][0](payload)

    def _buddy_left(self, payload):
        [nick] = json_load(payload)
        self._label.set_label(nick + ' ' + _('has left.'))
        if self.initiating:
            self._remove_player(nick)
            payload = json_dump(
                [self._bounce_window.buddies, self._player_colors])
            self.send_event('b', {"data": payload})
            # 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] = json_load(payload)
        self._label.set_label(nick + ' ' + _('has joined.'))
        if self.initiating:
            self._append_player(nick, colors)
            payload = json_dump(
                [self._bounce_window.buddies, self._player_colors])
            self.send_event('b', {"data": payload})
            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 not nick 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.initiating:
            [buddies, colors] = json_load(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. '''
        payload = json_dump(fraction)
        self.send_event('f', {"data": payload})

    def _receive_a_fraction(self, payload):
        ''' Receive a fraction from another player. '''
        fraction = json_load(payload)
        self._bounce_window.play_a_fraction(fraction)

    def _take_a_turn(self, nick):
        ''' If it is your turn, take it, otherwise, wait. '''
        if nick == self.nick:
            self._bounce_window.its_my_turn()
        else:
            self._bounce_window.its_their_turn(nick)

    def send_event(self, command, data):
        ''' Send event through the tube. '''
        _logger.debug('sending event: %s', command)
        if hasattr(self, 'collab') and self.collab is not None:
            data["command"] = command
            self.collab.post(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)
示例#25
0
class RecallActivity(activity.Activity):
    """ A memory game """
    def __init__(self, handle):
        """ Initialize the toolbars and the game board """
        super(RecallActivity, self).__init__(handle)

        self.path = activity.get_bundle_path()

        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._restoring = False
        self._setup_toolbars(True)

        # 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,
                          path=self.path,
                          colors=self.colors)
        self._setup_collab()
        if 'dotlist' in self.metadata:
            self._restore()
        else:
            self._game.new_game()

    def _setup_toolbars(self, have_toolbox):
        """ 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.radio = []
        self.radio.append(
            radio_factory('game-1',
                          self.toolbar,
                          self._new_game_cb,
                          cb_arg=0,
                          tooltip=_('Play attention game (repeated symbol).'),
                          group=None))
        self.radio.append(
            radio_factory('game-2',
                          self.toolbar,
                          self._new_game_cb,
                          cb_arg=1,
                          tooltip=_('Play attention game (missing symbol).'),
                          group=self.radio[0]))
        self.radio.append(
            radio_factory('game-4',
                          self.toolbar,
                          self._new_game_cb,
                          cb_arg=2,
                          tooltip=_('Play n-back game.'),
                          group=self.radio[0]))
        """
        # Game mode disabled
        self.radio.append(radio_factory(
            'game-3', self.toolbar, self._new_game_cb,
            cb_arg=3, tooltip=_('Play attention game (color symbols).'),
            group=self.radio[0]))
        """

        self.status = label_factory(self.toolbar, '')

        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 _new_game_cb(self, button=None, game=0):
        ''' Reload a new level. '''
        self._game.new_game(game=game, restart=(not self._restoring))

    def write_file(self, file_path):
        """ Write the grid status to the Journal """
        dot_list, correct, level, game = 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['correct'] = str(correct)
        self.metadata['level'] = str(level)
        self.metadata['game'] = str(game)

    def _restore(self):
        """ Restore the game state from metadata """
        self._restoring = True
        dot_list = []
        dots = self.metadata['dotlist'].split()
        for dot in dots:
            dot_list.append(int(dot))
        if 'correct' in self.metadata:
            correct = int(self.metadata['correct'])
        else:
            correct = 0
        if 'level' in self.metadata:
            level = int(self.metadata['level'])
        else:
            level = 0
        if 'game' in self.metadata:
            game = int(self.metadata['game'])
            self.radio[game].set_active(True)
        else:
            game = 0
        self._game.restore_game(dot_list, correct, level, game)
        self._restoring = False

    # Collaboration-related methods

    def _setup_collab(self):
        """ Setup the Collab Wrapper. """
        self.initiating = None  # sharing (True) or joining (False)
        self._collab = CollabWrapper(self)
        self._collab.connect('message', self.__message_cb)

        owner = self._collab._leader
        self.owner = owner

        self._game.set_sharing(True)

    def __message_cb(self, collab, buddy, message):
        action = message.get('action')
        payload = message.get('payload')
        if action == 'n':
            '''Get a new game grid'''
            self._receive_new_game(payload)
        elif action == 'p':
            '''Get a dot click'''
            self._receive_dot_click(payload)

    def send_new_game(self):
        ''' Send a new grid to all players '''
        self._collab.post(
            dict(action='n', payload=json_dump(self._game.save_game())))

    def _receive_new_game(self, payload):
        ''' Sharer can start a new game. '''
        dot_list, correct, level, game = json_load(payload)
        self._game.restore_game(dot_list, correct, level, game)

    def send_dot_click(self, dot, color):
        ''' Send a dot click to all the players '''
        self._collab.post(dict(action='p', 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._game.remote_button_press(dot, color)
示例#26
0
    def __init__(self, handle):
        activity.Activity.__init__(self, handle)
        self._has_read_file = False
        self._collab = CollabWrapper(self)
        self._collab.message.connect(self.__message_cb)

        screen = Gdk.Screen.get_default()
        css_provider = Gtk.CssProvider.get_default()
        css_provider.load_from_path('style.css')
        context = Gtk.StyleContext()
        context.add_provider_for_screen(screen, css_provider,
                                        Gtk.STYLE_PROVIDER_PRIORITY_USER)

        toolbar_box = ToolbarBox()

        activity_button = ActivityToolbarButton(self)
        toolbar_box.toolbar.insert(activity_button, 0)
        activity_button.show()

        html = ToolButton('export-as-html')
        html.set_tooltip(_('Save as HTML'))
        html.connect('clicked', self.__export_as_html_cb)
        activity_button.props.page.insert(html, -1)
        html.show()

        abiword = ToolButton('export-as-abiword')
        abiword.set_tooltip(_('Save as a Write document'))
        abiword.connect('clicked', self.__export_as_abiword_cb)
        activity_button.props.page.insert(abiword, -1)
        abiword.show()

        add_button = AddToolButton(ALL_TYPE_NAMES)
        add_button.connect('add-type', self.__add_type_cb)
        toolbar_box.toolbar.insert(add_button, -1)
        add_button.show()

        browse = ToolButton('import-browse')
        browse.set_tooltip(_('Add Web Pages from Browse Entry'))
        browse.connect('clicked', self.__import_from_browse_cb)
        toolbar_box.toolbar.insert(browse, -1)
        browse.show()

        separator = Gtk.SeparatorToolItem()
        separator.props.draw = False
        separator.set_expand(True)
        toolbar_box.toolbar.insert(separator, -1)
        separator.show()

        stop_button = StopButton(self)
        toolbar_box.toolbar.insert(stop_button, -1)
        stop_button.show()

        self.set_toolbar_box(toolbar_box)
        toolbar_box.show()

        self._main_sw = Gtk.ScrolledWindow()
        self._main_sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS)
        self._main_sw.connect('key-press-event', self.__key_press_event_cb)

        self._main_list = MainList(self._main_sw, self._collab)
        self._main_list.connect('edit-row', self.__edit_row_cb)
        self._main_list.connect('deleted-row', self.__deleted_row_cb)
        self._main_sw.add(self._main_list)
        self._main_list.show()

        self._empty_message = EmptyMessage()

        self.set_canvas(self._empty_message)
        self._empty_message.show()

        self._collab.setup()
示例#27
0
class BibliographyActivity(activity.Activity):
    def __init__(self, handle):
        activity.Activity.__init__(self, handle)
        self._has_read_file = False
        self._collab = CollabWrapper(self)
        self._collab.message.connect(self.__message_cb)

        screen = Gdk.Screen.get_default()
        css_provider = Gtk.CssProvider.get_default()
        css_provider.load_from_path('style.css')
        context = Gtk.StyleContext()
        context.add_provider_for_screen(screen, css_provider,
                                        Gtk.STYLE_PROVIDER_PRIORITY_USER)

        toolbar_box = ToolbarBox()

        activity_button = ActivityToolbarButton(self)
        toolbar_box.toolbar.insert(activity_button, 0)
        activity_button.show()

        html = ToolButton('export-as-html')
        html.set_tooltip(_('Save as HTML'))
        html.connect('clicked', self.__export_as_html_cb)
        activity_button.props.page.insert(html, -1)
        html.show()

        abiword = ToolButton('export-as-abiword')
        abiword.set_tooltip(_('Save as a Write document'))
        abiword.connect('clicked', self.__export_as_abiword_cb)
        activity_button.props.page.insert(abiword, -1)
        abiword.show()

        add_button = AddToolButton(ALL_TYPE_NAMES)
        add_button.connect('add-type', self.__add_type_cb)
        toolbar_box.toolbar.insert(add_button, -1)
        add_button.show()

        browse = ToolButton('import-browse')
        browse.set_tooltip(_('Add Web Pages from Browse Entry'))
        browse.connect('clicked', self.__import_from_browse_cb)
        toolbar_box.toolbar.insert(browse, -1)
        browse.show()

        separator = Gtk.SeparatorToolItem()
        separator.props.draw = False
        separator.set_expand(True)
        toolbar_box.toolbar.insert(separator, -1)
        separator.show()

        stop_button = StopButton(self)
        toolbar_box.toolbar.insert(stop_button, -1)
        stop_button.show()

        self.set_toolbar_box(toolbar_box)
        toolbar_box.show()

        self._main_sw = Gtk.ScrolledWindow()
        self._main_sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS)
        self._main_sw.connect('key-press-event', self.__key_press_event_cb)

        self._main_list = MainList(self._main_sw, self._collab)
        self._main_list.connect('edit-row', self.__edit_row_cb)
        self._main_list.connect('deleted-row', self.__deleted_row_cb)
        self._main_sw.add(self._main_list)
        self._main_list.show()

        self._empty_message = EmptyMessage()

        self.set_canvas(self._empty_message)
        self._empty_message.show()

        self._collab.setup()

    def add_item(self, text, type_, data):
        self._empty_message.hide()
        self.set_canvas(self._main_sw)
        self._main_sw.show()
        self._main_list.show()
        self._main_list.add(text, type_, data)

    def __message_cb(self, collab, buddy, msg):
        action = msg.get('action')
        if action is None:
            return

        args = msg.get('args')
        if action == 'add_item':
            self.add_item(*args)
        elif action == 'delete_row':
            self._main_list.delete(args)
        elif action == 'edit_item':
            self._main_list.edited_via_collab(msg.get('path'), args)
        else:
            logging.error('Got message that is weird %r', msg)

    def __add_type_cb(self, add_button, type_):
        window = EntryWindow(ALL_TYPES[type_], self)
        window.connect('save-item', self.__save_item_cb)
        window.show()

    def __save_item_cb(self, window, *args):
        self.add_item(*args)
        window.hide()
        self._collab.post(dict(action='add_item', args=args))
        window.destroy()

    def __import_from_browse_cb(self, button):
        chooser = ObjectChooser(parent=self,
                                what_filter='org.laptop.WebActivity',
                                filter_type=FILTER_TYPE_ACTIVITY)
        result = chooser.run()

        if result == Gtk.ResponseType.ACCEPT:
            logging.debug('ObjectChooser: %r' % chooser.get_selected_object())
            jobject = chooser.get_selected_object()
            self._load_browse(jobject)

        chooser.destroy()
        del chooser

    def __try_again_cb(self, window, jobject):
        window.hide()
        window.destroy()
        self._load_browse(jobject)

    def _load_browse(self, jobject):
        if jobject and jobject.file_path:
            with open(jobject.file_path) as f:
                data = json.load(f)
            window = BrowseImportWindow(data, self, jobject)
            window.connect('save-item', self.__save_item_importer_cb)
            window.connect('try-again', self.__try_again_cb)
            window.show()

    def __save_item_importer_cb(self, window, *args):
        self.add_item(*args)
        self._collab.post(dict(action='add_item', args=args))

    def __edit_row_cb(self, tree_view, type_, json_string):
        previous_values = json.loads(json_string)
        window = EntryWindow(ALL_TYPES[type_], self, previous_values)
        window.connect('save-item', tree_view.edited_row_cb)
        window.show()

    def __deleted_row_cb(self, tree_view, *row):
        if len(tree_view.get_model()) == 0:
            self._main_list.hide()
            self.set_canvas(self._empty_message)
            self._empty_message.show()

    def __key_press_event_cb(self, scrolled_window, event):
        keyname = Gdk.keyval_name(event.keyval)

        vadjustment = scrolled_window.props.vadjustment
        if keyname == 'Up':
            if vadjustment.props.value > vadjustment.props.lower:
                vadjustment.props.value -= vadjustment.props.step_increment
        elif keyname == 'Down':
            max_value = vadjustment.props.upper - vadjustment.props.page_size
            if vadjustment.props.value < max_value:
                vadjustment.props.value = min(
                    vadjustment.props.value + vadjustment.props.step_increment,
                    max_value)
        else:
            return False

    def __export_as_html_cb(self, button):
        jobject = datastore.create()
        jobject.metadata['title'] = \
            _('{} as HTML').format(self.metadata['title'])
        jobject.metadata['mime_type'] = 'text/html'
        preview = self.get_preview()
        if preview is not None:
            jobject.metadata['preview'] = dbus.ByteArray(preview)

        # write out the document contents in the requested format
        path = os.path.join(self.get_activity_root(), 'instance',
                            str(time.time()))
        with open(path, 'w') as f:
            f.write('''<html>
                         <head>
                           <title>{title}</title>
                         </head>

                         <body>
                           <h1>{title}</h1>
                    '''.format(title=jobject.metadata['title']))
            for item in self._main_list.all():
                f.write('<p>{}</p>'.format(item[self._main_list.COLUMN_TEXT]))
            f.write('''
                         </body>
                       </html>
                    ''')
        jobject.file_path = path

        datastore.write(jobject, transfer_ownership=True)
        self._journal_alert(
            jobject.object_id, _('Success'),
            _('Your'
              ' Bibliography was saved to the journal as HTML'))
        jobject.destroy()
        del jobject

    def __export_as_abiword_cb(self, button):
        jobject = datastore.create()
        jobject.metadata['title'] = \
            _('{} as Write document').format(self.metadata['title'])
        jobject.metadata['mime_type'] = 'application/x-abiword'
        preview = self.get_preview()
        if preview is not None:
            jobject.metadata['preview'] = dbus.ByteArray(preview)

        path = os.path.join(self.get_activity_root(), 'instance',
                            str(time.time()))
        with open(path, 'w') as f:
            f.write('<?xml version="1.0" encoding="UTF-8"?>\n<abiword>\n'
                    '<section>')
            entries = []
            for item in self._main_list.all():
                markup = item[self._main_list.COLUMN_TEXT]
                abiword = '<p><c>{}</c></p>'.format(markup) \
                    .replace('<b>', '<c props="font-weight:bold">') \
                    .replace('<i>', '<c props="font-style:italic;'
                             ' font-weight:normal">') \
                    .replace('</b>', '</c>').replace('</i>', '</c>')
                entries.append(abiword)
            f.write('\n<p><c></c></p>\n'.join(entries))
            f.write('</section>\n</abiword>')
        jobject.file_path = path

        datastore.write(jobject, transfer_ownership=True)
        self._journal_alert(
            jobject.object_id, _('Success'),
            _('Your'
              ' Bibliography was saved to the journal as a Write'
              ' document'))
        jobject.destroy()
        del jobject

    def _journal_alert(self, object_id, title, msg):
        alert = Alert()
        alert.props.title = title
        alert.props.msg = msg

        bundle = get_bundle(object_id=object_id)
        if bundle is not None:
            alert.add_button(Gtk.ResponseType.ACCEPT,
                             _('Open with {}').format(bundle.get_name()),
                             Icon(file=bundle.get_icon()))
        else:
            alert.add_button(Gtk.ResponseType.APPLY, _('Show in Journal'),
                             Icon(icon_name='zoom-activity'))
        alert.add_button(Gtk.ResponseType.OK, _('Ok'),
                         Icon(icon_name='dialog-ok'))

        # Remove other alerts
        for alert in self._alerts:
            self.remove_alert(alert)

        self.add_alert(alert)
        alert.connect('response', self.__alert_response_cb, object_id)
        alert.show_all()

    def __alert_response_cb(self, alert, response_id, object_id):
        if response_id is Gtk.ResponseType.ACCEPT:
            launch_bundle(object_id=object_id)
        if response_id is Gtk.ResponseType.APPLY:
            activity.show_object_in_journal(object_id)
        self.remove_alert(alert)

    def write_file(self, file_path):
        if self._main_list is None:
            return  # WhataTerribleFailure
        data = self._main_list.all()
        with open(file_path, 'w') as f:
            json.dump(data, f)

        self.metadata['mime_type'] == 'application/json+bib'

    def get_data(self):
        return self._main_list.all()

    def read_file(self, file_path):
        # FIXME: Why does sugar call read_file so many times?
        if self._has_read_file:
            return
        self._has_read_file = True

        with open(file_path) as f:
            l = json.load(f)
        self.set_data(l)

    def set_data(self, l):
        if len(l) > 0:
            self._main_list.load_json(l)
            self._empty_message.hide()
            self.set_canvas(self._main_sw)
            self._main_sw.show()
            self._main_list.show()
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()
        self._setup_dispatch_table()
        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)

        if self.shared_activity:
            # We're joining
            if not self.get_shared():
                xocolors = XoColor(profile.get_color().to_string())
                share_icon = Icon(icon_name='zoom-neighborhood',
                                  xo_color=xocolors)
                self._joined_alert = NotifyAlert()
                self._joined_alert.props.icon = share_icon
                self._joined_alert.props.title = _('Please wait')
                self._joined_alert.props.msg = _('Starting connection...')
                self._joined_alert.connect('response', self._alert_cancel_cb)
                self.add_alert(self._joined_alert)

                self._label.set_label(_('Wait for the sharer to start.'))

                # Wait for joined signal
                self.connect("joined", self._joined_cb)

        self._setup_sharing()

    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, state=palette.SECONDARY)
            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', {"data": (json_dump([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 _setup_sharing(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._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))
        ]
        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... '''
        if self.shared_activity is None:
            _logger.debug('Error: Failed to share or join activity ... \
                shared_activity is null in _shared_cb()')
            return

        self.initiating = sharer
        self.waiting_for_fraction = not sharer

        self.conn = self.shared_activity.telepathy_conn
        self.tubes_chan = self.shared_activity.telepathy_tubes_chan
        self.text_chan = self.shared_activity.telepathy_text_chan

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        if sharer:
            _logger.debug('This is my activity: making a tube...')
            id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
                SERVICE, {})

            self._label.set_label(_('Wait for others to join.'))
        else:
            _logger.debug('I am joining an activity: waiting for a tube...')
            self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
                reply_handler=self._list_tubes_reply_cb,
                error_handler=self._list_tubes_error_cb)

        # display your XO on the toolbar
        self._player.set_from_pixbuf(self._player_pixbufs[0])

    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.debug('Error: ListTubes() failed: %s', e)

    def _new_tube_cb(self, id, initiator, type, service, params, state):
        ''' Create a new tube. '''
        _logger.debug(
            'Newtube: ID=%d initator=%d type=%d service=%s params=%r state=%d',
            id, initiator, type, service, params, state)

        if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[ \
                              telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            # Let the sharer know a new joiner has arrived.
            if self.waiting_for_fraction:
                self.send_event('j', {"data": (json_dump([self.nick,
                                                     self._colors]))})

    def _setup_dispatch_table(self):
        self._processing_methods = {
            'j': [self._new_joiner, 'new joiner'],
            'b': [self._buddy_list, 'buddy list'],
            'f': [self._receive_a_fraction, 'receive a fraction'],
            't': [self._take_a_turn, 'take a turn'],
            'l': [self._buddy_left, 'buddy left']
            }

    def event_received_cb(self, event_message):
        ''' Data from a tube has arrived. '''
        if len(event_message) == 0:
            return
        try:
            command, payload = event_message.split('|', 2)
        except ValueError:
            _logger.debug('Could not split event message %s', event_message)
            return
        _logger.debug('received an event %s|%s', command, payload)
        if self._playing:
            self._processing_methods[command][0](payload)

    def _buddy_left(self, payload):
        [nick] = json_load(payload)
        self._label.set_label(nick + ' ' + _('has left.'))
        if self.initiating:
            self._remove_player(nick)
            payload = json_dump([self._bounce_window.buddies,
                                 self._player_colors])
            self.send_event('b', {"data": payload})
            # 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] = json_load(payload)
        self._label.set_label(nick + ' ' + _('has joined.'))
        if self.initiating:
            self._append_player(nick, colors)
            payload = json_dump([self._bounce_window.buddies,
                                 self._player_colors])
            self.send_event('b', {"data": payload})
            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 not nick 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.initiating:
            [buddies, colors] = json_load(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. '''
        payload = json_dump(fraction)
        self.send_event('f', {"data": payload})

    def _receive_a_fraction(self, payload):
        ''' Receive a fraction from another player. '''
        fraction = json_load(payload)
        self._bounce_window.play_a_fraction(fraction)

    def _take_a_turn(self, nick):
        ''' If it is your turn, take it, otherwise, wait. '''
        if nick == self.nick:
            self._bounce_window.its_my_turn()
        else:
            self._bounce_window.its_their_turn(nick)

    def send_event(self, command, data):
        ''' Send event through the tube. '''
        _logger.debug('sending event: %s', command)
        if hasattr(self, 'collab') and self.collab is not None:
            data["command"] = command
            self.collab.post(data)

    def set_player_on_toolbar(self, nick):
        ''' Display the XO icon of the player whose turn it is. '''
        self._player.set_from_pixbuf(self._player_pixbufs[
                self._bounce_window.buddies.index(nick)])
        self._player.set_tooltip_text(nick)
class Collaboration():
    def __init__(self, tw, activity):
        """ A simplistic sharing model: the sharer is the master """
        self._tw = tw
        self._tw.send_event = self.send_event
        self._tw.remote_turtle_dictionary = {}
        self._activity = activity
        self._setup_dispatch_table()

    def setup(self):
        # TODO: hand off role of master if sharer leaves
        self.pservice = presenceservice.get_instance()
        self.initiating = None  # sharing (True) or joining (False)

        # Add my buddy object to the list
        owner = self.pservice.get_owner()
        self.owner = owner
        self._tw.buddies.append(self.owner)
        self._share = ''
        self._activity.connect('shared', self._shared_cb)
        self._activity.connect('joined', self._joined_cb)

    def _setup_dispatch_table(self):
        self._processing_methods = {
            't': self._turtle_request,
            'T': self._receive_turtle_dict,
            'R': self._reskin_turtle,
            'f': self._move_forward,
            'a': self._move_in_arc,
            'r': self._rotate_turtle,
            'x': self._set_xy,
            'W': self._draw_text,
            'c': self._set_pen_color,
            'g': self._set_pen_gray_level,
            's': self._set_pen_shade,
            'w': self._set_pen_width,
            'p': self._set_pen_state,
            'F': self._fill_polygon,
            'P': self._draw_pixbuf,
            'B': self._paste,
            'S': self._speak
        }

    def _shared_cb(self, activity):
        self._shared_activity = self._activity.get_shared_activity()
        if self._shared_activity is None:
            debug_output(
                'Failed to share or join activity ... \
                _shared_activity is null in _shared_cb()',
                self._tw.running_sugar)
            return

        self._tw.set_sharing(True)

        self.initiating = True
        self.waiting_for_turtles = False
        self._tw.remote_turtle_dictionary = self._get_dictionary()

        debug_output('I am sharing...', self._tw.running_sugar)

        self.conn = self._shared_activity.telepathy_conn
        self.tubes_chan = self._shared_activity.telepathy_tubes_chan
        self.text_chan = self._shared_activity.telepathy_text_chan

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        debug_output('This is my activity: making a tube...',
                     self._tw.running_sugar)

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
            SERVICE, {})
        self._enable_share_button()

    def _joined_cb(self, activity):
        self._shared_activity = self._activity.get_shared_activity()
        if self._shared_activity is None:
            debug_output(
                'Failed to share or join activity ... \
                _shared_activity is null in _shared_cb()',
                self._tw.running_sugar)
            return

        self._tw.set_sharing(True)

        self.initiating = False
        self.conn = self._shared_activity.telepathy_conn
        self.tubes_chan = self._shared_activity.telepathy_tubes_chan
        self.text_chan = self._shared_activity.telepathy_text_chan

        # call back for "NewTube" signal
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        debug_output('I am joining an activity: waiting for a tube...',
                     self._tw.running_sugar)
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
            reply_handler=self._list_tubes_reply_cb,
            error_handler=self._list_tubes_error_cb)

        # Joiner should request current state from sharer.
        self.waiting_for_turtles = True
        self._enable_share_button()

    def _enable_share_button(self):
        self._activity.share_button.set_icon_name('shareon')
        self._activity.share_button.set_tooltip(_('Share selected blocks'))

    def _list_tubes_reply_cb(self, tubes):
        for tube_info in tubes:
            self._new_tube_cb(*tube_info)

    def _list_tubes_error_cb(self, e):
        error_output('ListTubes() failed: %s' % (e), self._tw.running_sugar)

    def _new_tube_cb(self, id, initiator, type, service, params, state):
        """ Create a new tube. """
        debug_output(
            'New tube: ID=%d initator=%d type=%d service=%s \
                     params=%r state=%d' %
            (id, initiator, type, service, params, state),
            self._tw.running_sugar)

        if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES]\
                    .AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            # Now that we have the tube, we can ask for the turtle dictionary.
            if self.waiting_for_turtles:  # A joiner must wait for turtles.
                debug_output('Sending a request for the turtle dictionary',
                             self._tw.running_sugar)
                # We need to send our own nick, colors, and turtle position
                colors = self._get_colors()
                event = data_to_string([self._get_nick(), colors])
                debug_output(event, self._tw.running_sugar)
                self.send_event("t", {"payload": event})

    def event_received_cb(self, colab, buddy, msg):
        """
        Events are sent as a tuple, nick|cmd, where nick is a turle name
        and cmd is a turtle event. Everyone gets the turtle dictionary from
        the sharer and watches for 't' events, which indicate that a new
        turtle has joined.
        """
        if len(event_message) == 0:
            return

    # Save active Turtle
        save_active_turtle = self._tw.turtles.get_active_turtle()

        command = msg.get("msg")
        payload = msg.get("payload")
        self._processing_methods[command](payload)

        # Restore active Turtle
        self._tw.turtles.set_turtle(
            self._tw.turtles.get_turtle_key(save_active_turtle))

    def send_event(self, message, payload):
        """ Send event through the tube. """
        if hasattr(self, 'chattube') and self.chattube is not None:
            payload["msg"] = message
            self.chattube.post(payload)

    def _turtle_request(self, payload):
        ''' incoming turtle from a joiner '''
        if payload > 0:
            [nick, colors] = data_from_string(payload)
            if nick != self._tw.nick:  # It is not me.
                # There may not be a turtle dictionary.
                if hasattr(self._tw, 'remote_turtle_dictionary'):
                    # Make sure it is not a "rejoin".
                    if nick not in self._tw.remote_turtle_dictionary:
                        # Add new turtle for the joiner.
                        self._tw.turtles.set_turtle(nick, colors)
                        self._tw.label_remote_turtle(nick, colors)
                    self._tw.remote_turtle_dictionary[nick] = colors
                else:
                    self._tw.remote_turtle_dictionary = self._get_dictionary()
                    # Add new turtle for the joiner.
                    self._tw.turtles.set_turtle(nick, colors)
                    self._tw.label_remote_turtle(nick, colors)

        # Sharer should send the updated remote turtle dictionary to everyone.
        if self.initiating:
            if self._tw.nick not in self._tw.remote_turtle_dictionary:
                self._tw.remote_turtle_dictionary[self._tw.nick] = \
                    self._get_colors()
            event_payload = data_to_string(self._tw.remote_turtle_dictionary)
            self.send_event("T", {"payload": event_payload})
            self.send_my_xy()  # And the sender should report her xy position.

    def _receive_turtle_dict(self, payload):
        ''' Any time there is a new joiner, an updated turtle dictionary is
        circulated. Everyone must report their turtle positions so that we
        are in sync. '''
        if self.waiting_for_turtles:
            if len(payload) > 0:
                # Grab the new remote turtles dictionary.
                remote_turtle_dictionary = data_from_string(payload)
                # Add see what is new.
                for nick in remote_turtle_dictionary:
                    if nick == self._tw.nick:
                        debug_output('skipping my nick %s' % (nick),
                                     self._tw.running_sugar)
                    elif nick != self._tw.remote_turtle_dictionary:
                        # Add new the turtle.
                        colors = remote_turtle_dictionary[nick]
                        self._tw.remote_turtle_dictionary[nick] = colors
                        self._tw.turtles.set_turtle(nick, colors)
                        # Label the remote turtle.
                        self._tw.label_remote_turtle(nick, colors)
                        debug_output(
                            'adding %s to remote turtle dictionary' % (nick),
                            self._tw.running_sugar)
                    else:
                        debug_output(
                            '%s already in remote turtle dictionary' % (nick),
                            self._tw.running_sugar)
            self.waiting_for_turtles = False
        self.send_my_xy()

    def send_my_xy(self):
        ''' Set xy location so joiner can sync turtle positions. Should be
        used to sync positions after turtle drag. '''
        self._tw.turtles.set_turtle(self._get_nick())
        if self._tw.turtles.get_active_turtle().get_pen_state():
            self.send_event(
                "p", {"payload": data_to_string([self._get_nick(), False])})
            put_pen_back_down = True
        else:
            put_pen_back_down = False
        self.send_event(
            "x", {
                "payload":
                data_to_string([
                    self._get_nick(),
                    [
                        int(self._tw.turtles.get_active_turtle().get_xy()[0]),
                        int(self._tw.turtles.get_active_turtle().get_xy()[1])
                    ]
                ])
            })
        if put_pen_back_down:
            self.send_event(
                "p", {"payload": data_to_string([self._get_nick(), True])})
        self.send_event(
            "r", {
                "payload":
                data_to_string([
                    self._get_nick(),
                    int(self._tw.turtles.get_active_turtle().get_heading())
                ])
            })

    def _reskin_turtle(self, payload):
        if len(payload) > 0:
            [nick, [width, height, data]] = data_from_string(payload)
            if nick != self._tw.nick:
                if self._tw.running_sugar:
                    tmp_path = get_path(self._tw.activity, 'instance')
                else:
                    tmp_path = '/tmp'
                file_name = base64_to_image(data, tmp_path)
                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
                    file_name, width, height)
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_shapes([pixbuf])

    def _draw_pixbuf(self, payload):
        if len(payload) > 0:
            [nick, [a, b, x, y, w, h, width, height, data]] =\
                data_from_string(payload)
            if nick != self._tw.nick:
                if self._tw.running_sugar:
                    tmp_path = get_path(self._tw.activity, 'instance')
                else:
                    tmp_path = '/tmp'
                file_name = base64_to_image(data, tmp_path)
                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
                    file_name, width, height)
                pos = self._tw.turtles.turtle_to_screen_coordinates((x, y))
                self._tw.turtles.get_active_turtle().draw_pixbuf(
                    pixbuf, a, b, pos[0], pos[1], w, h, file_name, False)

    def _move_forward(self, payload):
        if len(payload) > 0:
            [nick, x] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().forward(x, False)

    def _move_in_arc(self, payload):
        if len(payload) > 0:
            [nick, [a, r]] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().arc(a, r, False)

    def _rotate_turtle(self, payload):
        if len(payload) > 0:
            [nick, h] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_heading(h, False)

    def _set_xy(self, payload):
        if len(payload) > 0:
            [nick, [x, y]] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_xy(x, y, share=False)

    def _draw_text(self, payload):
        if len(payload) > 0:
            [nick, [label, x, y, size, w]] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().draw_text(
                    label, x, y, size, w, False)

    def _set_pen_color(self, payload):
        if len(payload) > 0:
            [nick, x] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_color(x, False)

    def _set_pen_gray_level(self, payload):
        if len(payload) > 0:
            [nick, x] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_gray(x, False)

    def _set_pen_shade(self, payload):
        if len(payload) > 0:
            [nick, x] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_shade(x, False)

    def _set_pen_width(self, payload):
        if len(payload) > 0:
            [nick, x] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_pen_size(x, False)

    def _set_pen_state(self, payload):
        if len(payload) > 0:
            [nick, x] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_pen_state(x, False)

    def _fill_polygon(self, payload):
        if len(payload) > 0:
            [nick, poly_points] = data_from_string(payload)
            shared_poly_points = []
            for i in range(len(poly_points)):
                x, y = self._tw.turtles.screen_to_turtle_coordinates(
                    (poly_points[i][1], poly_points[i][2]))
                if poly_points[i][0] in ['move', 'line']:
                    shared_poly_points.append((poly_points[i][0], x, y))
                elif poly_points[i][0] in ['rarc', 'larc']:
                    shared_poly_points.append(
                        (poly_points[i][0], x, y, poly_points[i][3],
                         poly_points[i][4], poly_points[i][5]))
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_poly_points(
                    shared_poly_points)
                self._tw.turtles.get_active_turtle().stop_fill(False)

    def _speak(self, payload):
        if len(payload) > 0:
            [nick, language_option, text] = data_from_string(payload)
            if language_option == 'None':
                language_option = ''
            if text is not None:
                os.system('espeak %s "%s" --stdout | aplay' %
                          (language_option, str(text)))

    def _paste(self, payload):
        if len(payload) > 0:
            [nick, text] = data_from_string(payload)
            if text is not None:
                self._tw.process_data(data_from_string(text),
                                      self._tw.paste_offset)
                self._tw.paste_offset += 20

    def _get_dictionary(self):
        return {self._get_nick(): self._get_colors()}

    def _get_nick(self):
        return self._tw.nick

    def _get_colors(self):
        colors = None
        if self._tw.running_sugar:
            if profile.get_color() is not None:
                colors = profile.get_color().to_string()
        else:
            colors = self._activity.get_colors()
        if colors is None:
            colors = '%s,%s' % (DEFAULT_TURTLE_COLORS[0],
                                DEFAULT_TURTLE_COLORS[1])
        return colors.split(',')
示例#30
0
class ReflectActivity(activity.Activity):
    ''' An activity for reflecting on one's work '''
    def __init__(self, handle):
        ''' Initialize the toolbar '''
        try:
            super(ReflectActivity, self).__init__(handle)
        except dbus.exceptions.DBusException as e:
            _logger.error(str(e))

        logging.error('setting reflection data to []')
        self.reflection_data = []

        self.connect('realize', self.__realize_cb)

        self.font_size = 8

        self.max_participants = 4
        self._setup_toolbars()

        color = profile.get_color()
        color_stroke = color.get_stroke_color()
        color_fill = color.get_fill_color()

        lighter = utils.lighter_color([color_stroke, color_fill])
        darker = 1 - lighter

        if lighter == 0:
            self.bg_color = style.Color(color_stroke)
            self.fg_color = style.Color(color_fill)
        else:
            self.bg_color = style.Color(color_fill)
            self.fg_color = style.Color(color_stroke)

        self.modify_bg(Gtk.StateType.NORMAL, self.bg_color.get_gdk_color())

        self.bundle_path = activity.get_bundle_path()
        self.tmp_path = os.path.join(activity.get_activity_root(), 'instance')

        self.sharing = False
        self._copy_entry = None
        self._paste_entry = None
        self._webkit = None
        self._clipboard_text = ''
        self._fixed = None

        self.initiating = True
        if self.shared_activity:
            # We're joining
            if not self.get_shared():
                self.initiating = False

                self.busy_cursor()
                share_icon = Icon(icon_name='zoom-neighborhood')
                self._joined_alert = Alert()
                self._joined_alert.props.icon = share_icon
                self._joined_alert.props.title = _('Please wait')
                self._joined_alert.props.msg = _('Starting connection...')
                self.add_alert(self._joined_alert)

                # Wait for joined signal
                self.connect("joined", self._joined_cb)

        self._open_reflect_windows()

        self._setup_presence_service()

        # Joiners wait to receive data from sharer
        # Otherwise, load reflections from local store
        if not self.shared_activity:
            self.busy_cursor()
            GObject.idle_add(self._load_reflections)

    def read_file(self, file_path):
        fd = open(file_path, 'r')
        data = fd.read()
        fd.close()
        self.reflection_data = json.loads(data)

    def write_file(self, file_path):
        data = json.dumps(self.reflection_data)
        fd = open(file_path, 'w')
        fd.write(data)
        fd.close()

        self.metadata['font_size'] = str(self.font_size)

    def _load_reflections(self):
        self._find_starred()
        self._reflect_window.load(self.reflection_data)
        self.reset_cursor()

    def _found_obj_id(self, obj_id):
        for item in self.reflection_data:
            if 'obj_id' in item and item['obj_id'] == obj_id:
                return True
        return False

    def reload_data(self, data):
        ''' Reload data after sorting or searching '''
        self._reflection_data = data[:]
        self._reflect_window.reload(self._reflection_data)
        self.reset_scrolled_window_adjustments()

    def _find_starred(self):
        ''' Find all the _stars in the Journal. '''
        self.dsobjects, self._nobjects = datastore.find({'keep': '1'})
        for dsobj in self.dsobjects:
            if self._found_obj_id(dsobj.object_id):
                continue  # Already have this object -- TODO: update it
            self._add_new_from_journal(dsobj)

    def _add_new_from_journal(self, dsobj):
        self.reflection_data.append({
            'title': _('Untitled'),
            'obj_id': dsobj.object_id
        })
        if hasattr(dsobj, 'metadata'):
            if 'creation_time' in dsobj.metadata:
                self.reflection_data[-1]['creation_time'] = \
                    dsobj.metadata['creation_time']
            else:
                self.reflection_data[-1]['creation_time'] = \
                    int(time.time())
            if 'timestamp' in dsobj.metadata:
                self.reflection_data[-1]['modification_time'] = \
                    dsobj.metadata['timestamp']
            else:
                self.reflection_data[-1]['modification_time'] = \
                    self.reflection_data[-1]['creation_time']
            if 'activity' in dsobj.metadata:
                self.reflection_data[-1]['activities'] = \
                    [utils.bundle_id_to_icon(dsobj.metadata['activity'])]
            if 'title' in dsobj.metadata:
                self.reflection_data[-1]['title'] = \
                    dsobj.metadata['title']
            if 'description' in dsobj.metadata:
                self.reflection_data[-1]['content'] = \
                    [{'text': dsobj.metadata['description']}]
            else:
                self.reflection_data[-1]['content'] = []
            if 'tags' in dsobj.metadata:
                self.reflection_data[-1]['tags'] = []
                tags = dsobj.metadata['tags'].split()
                for tag in tags:
                    if tag[0] != '#':
                        self.reflection_data[-1]['tags'].append('#' + tag)
                    else:
                        self.reflection_data[-1]['tags'].append(tag)
            if 'comments' in dsobj.metadata:
                try:
                    comments = json.loads(dsobj.metadata['comments'])
                except BaseException:
                    comments = []
                self.reflection_data[-1]['comments'] = []
                for comment in comments:
                    try:
                        data = {
                            'nick': comment['from'],
                            'comment': comment['message']
                        }
                        if 'icon-color' in comment:
                            colors = comment['icon-color'].split(',')
                            darker = 1 - utils.lighter_color(colors)
                            data['color'] = colors[darker]
                        else:
                            data['color'] = '#000000'
                        self.reflection_data[-1]['comments'].append(data)
                    except BaseException:
                        _logger.debug('could not parse comment %s' % comment)
            if 'mime_type' in dsobj.metadata and \
               dsobj.metadata['mime_type'][0:5] == 'image':
                new_path = os.path.join(self.tmp_path, dsobj.object_id)
                try:
                    shutil.copy(dsobj.file_path, new_path)
                except Exception as e:
                    logging.error("Couldn't copy %s to %s: %s" %
                                  (dsobj.file_path, new_path, e))
                self.reflection_data[-1]['content'].append({'image': new_path})
            elif 'preview' in dsobj.metadata:
                pixbuf = utils.get_pixbuf_from_journal(dsobj, 300, 225)
                if pixbuf is not None:
                    path = os.path.join(self.tmp_path,
                                        dsobj.object_id + '.png')
                    utils.save_pixbuf_to_file(pixbuf, path)
                    self.reflection_data[-1]['content'].append({'image': path})
            self.reflection_data[-1]['stars'] = 0

    def delete_item(self, obj_id):
        for i, obj in enumerate(self.reflection_data):
            if obj['obj_id'] == obj_id:
                self.reflection_data.remove(self.reflection_data[i])
                return

    def busy_cursor(self):
        self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))

    def reset_cursor(self):
        self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR))

    def _open_reflect_windows(self):
        # Most things need only be done once
        if self._fixed is None:
            self._fixed = Gtk.Fixed()
            self._fixed.set_size_request(Gdk.Screen.width(),
                                         Gdk.Screen.height())

            # Offsets from the bottom of the screen
            dy1 = 2 * style.GRID_CELL_SIZE
            dy2 = 1 * style.GRID_CELL_SIZE

            self._button_area = Gtk.Alignment.new(0.5, 0, 0, 0)
            self._button_area.set_size_request(Gdk.Screen.width(),
                                               style.GRID_CELL_SIZE)
            self._fixed.put(self._button_area, 0, 0)
            self._button_area.show()

            self._scrolled_window = Gtk.ScrolledWindow()
            self._scrolled_window.set_size_request(Gdk.Screen.width(),
                                                   Gdk.Screen.height() - dy1)
            self._set_scroll_policy()
            self._graphics_area = Gtk.Alignment.new(0.5, 0, 0, 0)
            self._scrolled_window.add_with_viewport(self._graphics_area)
            self._graphics_area.show()
            self._fixed.put(self._scrolled_window, 0, dy2)
            self._scrolled_window.show()

            self._overlay_window = Gtk.ScrolledWindow()
            self._overlay_window.set_size_request(style.GRID_CELL_SIZE * 10,
                                                  style.GRID_CELL_SIZE * 6)
            self._overlay_window.modify_bg(Gtk.StateType.NORMAL,
                                           style.COLOR_WHITE.get_gdk_color())
            self._overlay_window.set_policy(Gtk.PolicyType.NEVER,
                                            Gtk.PolicyType.AUTOMATIC)
            self._overlay_area = Gtk.Alignment.new(0.5, 0, 0, 0)
            self._overlay_window.add_with_viewport(self._overlay_area)
            self._overlay_area.show()
            x = int((Gdk.Screen.width() - style.GRID_CELL_SIZE * 10) / 2)
            self._fixed.put(self._overlay_window, 0, Gdk.Screen.height())
            self._overlay_window.show()
            self._old_overlay_widget = None

            self._reflect_window = ReflectWindow(self)
            self._reflect_window.show()

            Gdk.Screen.get_default().connect('size-changed',
                                             self._configure_cb)
            self._toolbox.connect('hide', self._resize_hide_cb)
            self._toolbox.connect('show', self._resize_show_cb)

            self._reflect_window.set_events(Gdk.EventMask.KEY_PRESS_MASK)
            self._reflect_window.connect('key_press_event',
                                         self._reflect_window.keypress_cb)
            self._reflect_window.set_can_focus(True)
            self._reflect_window.grab_focus()

        self.set_canvas(self._fixed)
        self._fixed.show()

    def reset_scrolled_window_adjustments(self):
        adj = self._scrolled_window.get_hadjustment()
        if adj is not None:
            adj.set_value(0)
        adj = self._scrolled_window.get_vadjustment()
        if adj is not None:
            adj.set_value(0)

    def load_graphics_area(self, widget):
        self._graphics_area.add(widget)

    def load_button_area(self, widget):
        self._button_area.add(widget)

    def load_overlay_area(self, widget):
        if self._old_overlay_widget is not None:
            self._overlay_area.remove(self._old_overlay_widget)
        self._overlay_area.add(widget)
        self._old_overlay_widget = widget

    def show_overlay_area(self):
        x = int((Gdk.Screen.width() - style.GRID_CELL_SIZE * 10) / 2)
        self._fixed.move(self._overlay_window, x, style.GRID_CELL_SIZE)

    def hide_overlay_area(self):
        self._fixed.move(self._overlay_window, 0, Gdk.Screen.height())

    def collapse_overlay_area(self, button, event):
        self._fixed.move(self._overlay_window, 0, Gdk.Screen.height())

    def _resize_hide_cb(self, widget):
        self._resize_canvas(widget, True)

    def _resize_show_cb(self, widget):
        self._resize_canvas(widget, False)

    def _configure_cb(self, event):
        self._fixed.set_size_request(Gdk.Screen.width(), Gdk.Screen.height())
        self._set_scroll_policy()
        self._resize_canvas(None)
        self._reflect_window.reload_graphics()

    def _resize_canvas(self, widget, fullscreen=False):
        # When a toolbar is expanded or collapsed, resize the canvas
        if hasattr(self, '_reflect_window'):
            if self.toolbar_expanded():
                dy1 = 3 * style.GRID_CELL_SIZE
                dy2 = 2 * style.GRID_CELL_SIZE
            else:
                dy1 = 2 * style.GRID_CELL_SIZE
                dy2 = 1 * style.GRID_CELL_SIZE

            if fullscreen:
                dy1 -= 2 * style.GRID_CELL_SIZE
                dy2 -= 2 * style.GRID_CELL_SIZE

            self._scrolled_window.set_size_request(Gdk.Screen.width(),
                                                   Gdk.Screen.height() - dy2)
            self._fixed.move(self._button_area, 0, 0)

        self._about_panel_visible = False

    def toolbar_expanded(self):
        if self.activity_button.is_expanded():
            return True
        elif self.edit_toolbar_button.is_expanded():
            return True
        elif self.view_toolbar_button.is_expanded():
            return True
        return False

    def get_activity_version(self):
        info_path = os.path.join(self.bundle_path, 'activity', 'activity.info')
        try:
            info_file = open(info_path, 'r')
        except Exception as e:
            _logger.error('Could not open %s: %s' % (info_path, e))
            return 'unknown'

        cp = ConfigParser()
        cp.readfp(info_file)

        section = 'Activity'

        if cp.has_option(section, 'activity_version'):
            activity_version = cp.get(section, 'activity_version')
        else:
            activity_version = 'unknown'
        return activity_version

    def get_uid(self):
        if len(self.volume_data) == 1:
            return self.volume_data[0]['uid']
        else:
            return 'unknown'

    def _setup_toolbars(self):
        ''' Setup the toolbars. '''
        self._toolbox = ToolbarBox()

        self.activity_button = ActivityToolbarButton(self)
        self.activity_button.connect('clicked', self._resize_canvas)
        self._toolbox.toolbar.insert(self.activity_button, 0)
        self.activity_button.show()

        self.set_toolbar_box(self._toolbox)
        self._toolbox.show()
        self.toolbar = self._toolbox.toolbar

        view_toolbar = Gtk.Toolbar()
        self.view_toolbar_button = ToolbarButton(page=view_toolbar,
                                                 label=_('View'),
                                                 icon_name='toolbar-view')
        self.view_toolbar_button.connect('clicked', self._resize_canvas)
        self._toolbox.toolbar.insert(self.view_toolbar_button, 1)
        view_toolbar.show()
        self.view_toolbar_button.show()

        button = ToolButton('view-fullscreen')
        button.set_tooltip(_('Fullscreen'))
        button.props.accelerator = '<Alt>Return'
        view_toolbar.insert(button, -1)
        button.show()
        button.connect('clicked', self._fullscreen_cb)

        edit_toolbar = Gtk.Toolbar()
        self.edit_toolbar_button = ToolbarButton(page=edit_toolbar,
                                                 label=_('Edit'),
                                                 icon_name='toolbar-edit')
        self.edit_toolbar_button.connect('clicked', self._resize_canvas)
        self._toolbox.toolbar.insert(self.edit_toolbar_button, 1)
        edit_toolbar.show()
        self.edit_toolbar_button.show()

        self._copy_button = ToolButton('edit-copy')
        self._copy_button.set_tooltip(_('Copy'))
        self._copy_button.props.accelerator = '<Ctrl>C'
        edit_toolbar.insert(self._copy_button, -1)
        self._copy_button.show()
        self._copy_button.connect('clicked', self._copy_cb)
        self._copy_button.set_sensitive(False)

        self._paste_button = ToolButton('edit-paste')
        self._paste_button.set_tooltip(_('Paste'))
        self._paste_button.props.accelerator = '<Ctrl>V'
        edit_toolbar.insert(self._paste_button, -1)
        self._paste_button.show()
        self._paste_button.connect('clicked', self._paste_cb)
        self._paste_button.set_sensitive(False)

        button = ToolButton('list-add')
        button.set_tooltip(_('Add Item'))
        button.props.accelerator = '<Ctrl>+'
        self._toolbox.toolbar.insert(button, -1)
        button.show()
        button.connect('clicked', self.__add_item_cb)

        self._date_button = RadioToolButton('date-sort', group=None)
        self._date_button.set_tooltip(_('Sort by Date'))
        self._date_button.connect('clicked', self._date_button_cb)
        self._toolbox.toolbar.insert(self._date_button, -1)
        self._date_button.show()

        self._title_button = RadioToolButton('title-sort',
                                             group=self._date_button)
        self._title_button.set_tooltip(_('Sort by Title'))
        self._title_button.connect('clicked', self._title_button_cb)
        self._toolbox.toolbar.insert(self._title_button, -1)
        self._title_button.show()

        self._stars_button = RadioToolButton('stars-sort',
                                             group=self._date_button)
        self._stars_button.set_tooltip(_('Sort by Favourite'))
        self._stars_button.connect('clicked', self._stars_button_cb)
        self._toolbox.toolbar.insert(self._stars_button, -1)
        self._stars_button.show()

        # setup the search options
        self._search_entry = iconentry.IconEntry()
        self._search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
                                              'system-search')
        self._search_entry.connect('activate', self._search_entry_activated_cb)
        self._search_entry.connect('changed', self._search_entry_changed_cb)
        self._search_entry.add_clear_button()

        tool_item = Gtk.ToolItem()
        tool_item.set_expand(True)
        tool_item.add(self._search_entry)
        self._search_entry.show()
        self._toolbox.toolbar.insert(tool_item, -1)
        tool_item.show()

        self._search_button = ToolButton('dialog-ok')
        self._search_button.set_tooltip(_('Search by Tags'))
        self._search_button.connect('clicked', self._search_button_cb)
        self._toolbox.toolbar.insert(self._search_button, -1)
        self._search_button.show()

        separator = Gtk.SeparatorToolItem()
        separator.props.draw = False
        separator.set_expand(True)
        self._toolbox.toolbar.insert(separator, -1)
        separator.show()

        stop_button = StopButton(self)
        stop_button.props.accelerator = '<Ctrl>q'
        self._toolbox.toolbar.insert(stop_button, -1)
        stop_button.show()

    def _search_button_cb(self, button):
        self.busy_cursor()
        self._do_search()

    def _search_entry_activated_cb(self, entry):
        self.busy_cursor()
        self._do_search()

    def _do_search(self):
        logging.debug('_search_entry_activated_cb')
        if self._search_entry.props.text == '':
            logging.debug('clearing search')
            for item in self.reflection_data:
                item['hidden'] = False
        else:
            tags = self._search_entry.props.text.split()
            for i, tag in enumerate(tags):
                if not tag[0] == '#':
                    tags[i] = '#%s' % tag
            logging.error(tags)
            for item in self.reflection_data:
                hidden = True
                if 'tags' in item:
                    for tag in tags:
                        if tag in item['tags']:
                            hidden = False
                item['hidden'] = hidden
        self.reload_data(self.reflection_data)
        self.reset_cursor()

    def _search_entry_changed_cb(self, entry):
        logging.debug('_search_entry_changed_cb search for \'%s\'',
                      self._search_entry.props.text)
        self.busy_cursor()
        self._do_search_changed()

    def _do_search_changed(self):
        if self._search_entry.props.text == '':
            logging.debug('clearing search')
            for item in self.reflection_data:
                item['hidden'] = False
            self.reload_data(self.reflection_data)
        self.reset_cursor()

    def _title_button_cb(self, button):
        ''' sort by title '''
        self.busy_cursor()
        GObject.idle_add(self._title_sort)

    def _title_sort(self):
        sorted_data = sorted(self.reflection_data,
                             key=lambda item: item['title'].lower())
        self.reload_data(sorted_data)
        self.reset_cursor()

    def _date_button_cb(self, button):
        ''' sort by modification date '''
        self.busy_cursor()
        GObject.idle_add(self._date_sort)

    def _date_sort(self):
        sorted_data = sorted(self.reflection_data,
                             key=lambda item: int(item['modification_time']),
                             reverse=True)
        self.reload_data(sorted_data)
        self.reset_cursor()

    def _stars_button_cb(self, button):
        ''' sort by number of stars '''
        self.busy_cursor()
        GObject.idle_add(self._stars_sort)

    def _stars_sort(self):
        sorted_data = sorted(self.reflection_data,
                             key=lambda item: item['stars'],
                             reverse=True)
        self.reload_data(sorted_data)
        self.reset_cursor()

    def __realize_cb(self, window):
        self.window_xid = window.get_window().get_xid()

    def set_copy_widget(self, webkit=None, text_entry=None):
        # Each task is responsible for setting a widget for copy
        if webkit is not None:
            self._webkit = webkit
        else:
            self._webkit = None
        if text_entry is not None:
            self._copy_entry = text_entry
        else:
            self._copy_entry = None

        self._copy_button.set_sensitive(webkit is not None
                                        or text_entry is not None)

    def _copy_cb(self, button):
        if self._copy_entry is not None:
            self._copy_entry.copy_clipboard()
        elif self._webkit is not None:
            self._webkit.copy_clipboard()
        else:
            _logger.debug('No widget set for copy.')

    def set_paste_widget(self, text_entry=None):
        # Each task is responsible for setting a widget for paste
        if text_entry is not None:
            self._paste_entry = text_entry
        self._paste_button.set_sensitive(text_entry is not None)

    def _paste_cb(self, button):
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        self.clipboard_text = clipboard.wait_for_text()
        if self._paste_entry is not None:
            self._paste_entry.paste_clipboard()
        else:
            _logger.debug('No widget set for paste (%s).' %
                          self.clipboard_text)

    def _fullscreen_cb(self, button):
        ''' Hide the Sugar toolbars. '''
        self.fullscreen()

    def __add_item_cb(self, button):
        try:
            chooser = ObjectChooser(parent=self, what_filter=None)
        except TypeError:
            chooser = ObjectChooser(
                None, self._reflection.activity,
                Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT)

        try:
            result = chooser.run()
            if result == Gtk.ResponseType.ACCEPT:
                jobject = chooser.get_selected_object()
                if jobject:
                    self._add_new_from_journal(jobject)
                    self.reload_data(self.reflection_data)
        finally:
            chooser.destroy()
            del chooser

    def _set_scroll_policy(self):
        if Gdk.Screen.width() < Gdk.Screen.height():
            self._scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC,
                                             Gtk.PolicyType.AUTOMATIC)
        else:
            self._scrolled_window.set_policy(Gtk.PolicyType.NEVER,
                                             Gtk.PolicyType.AUTOMATIC)

    def _remove_alert_cb(self, alert, response_id):
        self.remove_alert(alert)

    def _close_alert_cb(self, alert, response_id):
        self.remove_alert(alert)
        if response_id is Gtk.ResponseType.OK:
            self.close()

    def _setup_presence_service(self):
        ''' Setup the Presence Service. '''
        self.pservice = presenceservice.get_instance()

        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...'''
        if self.shared_activity is None:
            _logger.error('Failed to share or join activity ... \
                shared_activity is null in _shared_cb()')
            return

        self.initiating = True
        self._waiting_for_reflections = False
        _logger.debug('I am sharing...')

        self.conn = self.shared_activity.telepathy_conn
        self.tubes_chan = self.shared_activity.telepathy_tubes_chan
        self.text_chan = self.shared_activity.telepathy_text_chan

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        _logger.debug('This is my activity: making a tube...')
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
            SERVICE, {})

        self.sharing = True

    def _joined_cb(self, activity):
        ''' ...or join an exisiting share. '''
        if self.shared_activity is None:
            _logger.error('Failed to share or join activity ... \
                shared_activity is null in _shared_cb()')
            return

        if self._joined_alert is not None:
            self.remove_alert(self._joined_alert)
            self._joined_alert = None

        self.initiating = False
        self._waiting_for_reflections = True
        _logger.debug('I joined a shared activity.')

        self.conn = self.shared_activity.telepathy_conn
        self.tubes_chan = self.shared_activity.telepathy_tubes_chan
        self.text_chan = self.shared_activity.telepathy_text_chan

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        _logger.debug('I am joining an activity: waiting for a tube...')
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
            reply_handler=self._list_tubes_reply_cb,
            error_handler=self._list_tubes_error_cb)

        self.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('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 == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(
                    id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            if self._waiting_for_reflections:
                self.send_event(JOIN_CMD, {})
                self._joined_alert = Alert()
                self._joined_alert.props.title = _('Please wait')
                self._joined_alert.props.msg = _('Requesting reflections...')
                self.add_alert(self._joined_alert)

    def event_received_cb(self, collab, buddy, msg):
        ''' Data is passed as tuples: cmd:text '''
        command = msg.get("command")
        payload = msg.get("payload")
        logging.debug(command)

        if command == JOIN_CMD:
            # Sharer needs to send reflections database to joiners.
            if self.initiating:
                # Send pictures first.
                for item in self.reflection_data:
                    if 'content' in item:
                        for content in item['content']:
                            if 'image' in content:
                                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
                                    content['image'], 120, 90)
                                if pixbuf is not None:
                                    data = utils.pixbuf_to_base64(pixbuf)
                                self.send_event(
                                    PICTURE_CMD, {
                                        "image": os.path.basename(
                                            content['image']),
                                        "data": data
                                    })
                data = json.dumps(self.reflection_data)
                self.send_event(SHARE_CMD, {"data": data})
        elif command == NEW_REFLECTION_CMD:
            self._reflect_window.add_new_reflection(payload)
        elif command == TITLE_CMD:
            obj_id = payload.get("obj_id")
            title = payload.get("title")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    self._reflect_window.update_title(obj_id, title)
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == TAG_CMD:
            obj_id = payload.get("obj_id")
            data = payload.get("data")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    self._reflect_window.update_tags(obj_id, data)
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == ACTIVITY_CMD:
            obj_id = payload.get("obj_id")
            bundle_id = payload.get("bundle_id")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    self._reflect_window.insert_activity(obj_id, bundle_id)
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == STAR_CMD:
            obj_id = payload.get("obj_id")
            stars = payload.get("stars")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    self._reflect_window.update_stars(obj_id, int(stars))
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == COMMENT_CMD:
            found_the_object = False
            # Receive a comment and associated reflection ID
            obj_id = payload.get("obj_id")
            nick = payload.get("nick")
            color = payload.get("color")
            comment = payload.get("comment")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    if 'comments' not in item:
                        item['comments'] = []
                    data = {'nick': nick, 'comment': comment, 'color': color}
                    item['comments'].append(data)
                    self._reflect_window.insert_comment(obj_id, data)
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == REFLECTION_CMD:
            found_the_object = False
            # Receive a reflection and associated reflection ID
            obj_id = payload.get("obj_id")
            reflection = payload.get("reflection")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    if '' not in item:
                        item['content'] = []
                    item['content'].append({'text': reflection})
                    self._reflect_window.insert_reflection(obj_id, reflection)
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == IMAGE_REFLECTION_CMD:
            found_the_object = False
            # Receive a picture reflection and associated reflection ID
            obj_id = payload.get("obj_id")
            basename = payload.get("basename")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    if '' not in item:
                        item['content'] = []
                    item['content'].append(
                        {'image': os.path.join(self.tmp_path, basename)})
                    self._reflect_window.insert_picture(
                        obj_id, os.path.join(self.tmp_path, basename))
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == PICTURE_CMD:
            # Receive a picture (MAYBE DISPLAY IT AS IT ARRIVES?)
            basename = payload.get("basename")
            data = payload.get("data")
            utils.base64_to_file(data, os.path.join(self.tmp_path, basename))
        elif command == SHARE_CMD:
            # Joiner needs to load reflection database.
            if not self.initiating:
                # Note that pictures should be received.
                self.reflection_data = payload
                self._reflect_window.load(self.reflection_data)
                self._waiting_for_reflections = False
                self.reset_cursor()
                if self._joined_alert is not None:
                    self.remove_alert(self._joined_alert)
                    self._joined_alert = None

    def send_event(self, command, data):
        ''' Send event through the tube. '''
        if hasattr(self, 'collab') and self.collab is not None:
            data["command"] = command
            self.collab.post(data)
示例#31
0
class ReflectionActivity(activity.Activity):
    ''' Reflection puzzle game '''
    def __init__(self, handle):
        ''' Initialize the toolbars and the game board '''
        try:
            super(ReflectionActivity, self).__init__(handle)
        except dbus.exceptions.DBusException as e:
            _logger.error(str(e))

        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()

        # 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._setup_collab()

        if 'dotlist' in self.metadata:
            self._restore()
        else:
            self._game.new_game('horizontal')

    def _setup_toolbars(self):
        ''' Setup the toolbars. '''

        self.max_participants = 4

        toolbox = ToolbarBox()

        activity_button = ActivityToolbarButton(self)

        toolbox.toolbar.insert(activity_button, 0)
        activity_button.show()

        self.set_toolbar_box(toolbox)
        toolbox.show()
        self.toolbar = toolbox.toolbar

        my_colors = radio_factory('my-colors',
                                  self.toolbar,
                                  self._my_colors_cb,
                                  group=None)

        radio_factory('toolbar-colors',
                      self.toolbar,
                      self._roygbiv_colors_cb,
                      group=my_colors)

        self._new_game_button_h = button_factory(
            'new-game-horizontal',
            self.toolbar,
            self._new_game_cb,
            cb_arg='horizontal',
            tooltip=_('Start a new horizontal-reflection game.'))

        self._new_game_button_v = button_factory(
            'new-game-vertical',
            self.toolbar,
            self._new_game_cb,
            cb_arg='vertical',
            tooltip=_('Start a new vertical-reflection game.'))

        self._new_game_button_b = button_factory(
            'new-game-bilateral',
            self.toolbar,
            self._new_game_cb,
            cb_arg='bilateral',
            tooltip=_('Start a new bilateral-reflection game.'))

        self.status = label_factory(self.toolbar, '')

        separator_factory(toolbox.toolbar, False, True)

        self.robot_button = button_factory('robot-off',
                                           self.toolbar,
                                           self._robot_cb,
                                           tooltip=_('Play with the robot.'))

        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 _my_colors_cb(self, button=None):
        if hasattr(self, '_game'):
            self._game.roygbiv = False
            self._game.new_game()

    def _roygbiv_colors_cb(self, button=None):
        if hasattr(self, '_game'):
            self._game.roygbiv = True
            self._game.new_game()

    def _new_game_cb(self, button=None, orientation='horizontal'):
        ''' Start a new game. '''
        self._game.new_game(orientation)

    def _robot_cb(self, button=None):
        ''' Play with the computer (or not). '''
        if not self._game.playing_with_robot:
            self.set_robot_status(True, 'robot-on')
        else:
            self.set_robot_status(False, 'robot-off')

    def set_robot_status(self, status, icon):
        ''' Reset robot icon and status '''
        self._game.playing_with_robot = status
        self.robot_button.set_icon_name(icon)

    def write_file(self, file_path):
        ''' Write the grid status to the Journal '''
        [dot_list, orientation] = self._game.save_game()
        self.metadata['orientation'] = orientation
        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'] += ' '

    def _restore(self):
        ''' Restore the game state from metadata '''
        if 'orientation' in self.metadata:
            orientation = self.metadata['orientation']
        else:
            orientation = 'horizontal'

        dot_list = []
        dots = self.metadata['dotlist'].split()
        for dot in dots:
            dot_list.append(int(dot))
        self._game.restore_game(dot_list, orientation)

    # Collaboration-related methods

    def _setup_collab(self):
        ''' Setup the Collab Wrapper. '''
        self.initiating = None  # sharing (True) or joining (False)
        self._collab = CollabWrapper(self)
        self._collab.connect('message', self.__message_cb)

        owner = self._collab._leader
        self.owner = owner
        self._game.set_sharing(True)
        self._collab.setup()

    def __message_cb(self, collab, buddy, message):
        action = message.get('action')
        payload = message.get('payload')
        if action == 'n':
            '''Get a new game grid'''
            self._receive_new_game(payload)
        elif action == 'p':
            '''Get a dot click'''
            self._receive_dot_click(payload)

    def send_new_game(self):
        ''' Send a new orientation, grid to all players '''
        self._collab.post(
            dict(action='n', payload=json_dump(self._game.save_game())))

    def _receive_new_game(self, payload):
        ''' Sharer can start a new game. '''
        [dot_list, orientation] = json_load(payload)
        self._game.restore_game(dot_list, orientation)

    def send_dot_click(self, dot, color):
        ''' Send a dot click to all the players '''
        self._collab.post(dict(action='p', 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._game.remote_button_press(dot, color)
示例#32
0
class CollabEdit(Gtk.VBox):

    __gsignals__ = {
        "insert-char": (GObject.SIGNAL_RUN_FIRST, None, [int, int]),
        "cursor-position-changed": (GObject.SIGNAL_RUN_FIRST, None, [int])
    }

    def __init__(self, activity):
        Gtk.VBox.__init__(self)

        self.collab = CollabWrapper(activity)
        self.collab.connect('message', self.__message_cb)
        self.collab.connect('joined', self.__joined_cb)

        self.view = View()
        self.view.connect("insert-char", self.__insert_char_cb)
        self.view.connect("cursor-position-changed",
                          self.__cursor_position_changed_cb)
        self.view.connect("tag-applied", self.__tag_applied_cb)
        self.view.connect("tag-removed", self.__tag_removed_cb)
        self.pack_start(self.view, True, True, 0)

    def __message_cb(self, collab, buddy, msg):
        action = msg.get("action")
        if action is None:
            return

        elif action == "insert":
            self.view.insert_at_position(msg.get("key"), msg.get("position"))

        elif action == "curosr_moved":
            self.view.draw_other_cursor(msg.get("id"), msg.get("position"))

        elif action == "insert_tag":
            self.view.insert_tag(msg.get("tag"),
                                 msg.get("start"),
                                 msg.get("end"))

        elif action == "remove_tag":
            self.view.remove_tag(msg.get("tag"),
                                 msg.get("start"),
                                 msg.get("end"))

    def __joined_cb(self, sender):
        if self.collab._leader:
            return

        self.collab.post(dict(action='init_request',
                              res_id=10))  # How get an unique id?

    def __insert_char_cb(self, view, key, position):
        self.collab.post(dict(action="insert",
                              key=Gdk.keyval_name(key),
                              position=position))

    def __cursor_position_changed_cb(self, view, position):
        self.collab.post(dict(action="cursor_moved",
                              id=10,  # How get an unique id?
                              position=position))

        self.emit("cursor-position-changed", position)

    def __tag_applied_cb(self, buffer, tag, start, end):
        self.collab.post(dict(action="insert_tag",
                              tag=tag,
                              start=start,
                              end=end))

    def __tag_removed_cb(self, buffer, tag, start, end):
        self.collab.post(dict(action="remove_tag",
                              tag=tag,
                              start=start,
                              end=end))

    def toggle_bold(self):
        self.view.toggle_bold()

    def toggle_italic(self):
        self.view.toggle_italic()

    def toggle_underline(self):
        self.view.toggle_underline()

    def check_tag_at_offset(self, tag, position):
        return self.view.check_tag_at_offset(tag, position)
示例#33
0
class PathsActivity(activity.Activity):
    """ Path puzzle game """
    def __init__(self, handle):
        """ Initialize the toolbars and the game board """
        super(PathsActivity, 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._setup_presence_service()

        # Restore game state from Journal or start new game
        if 'deck' in self.metadata:
            self._restore()
        else:
            self._game.new_game()

    def _setup_toolbars(self):
        """ Setup the toolbars. """

        self.max_participants = MAX_HANDS

        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.'))

        self.robot_button = button_factory('robot-off',
                                           self.toolbar,
                                           self._robot_cb,
                                           tooltip=_('Play with the robot.'))

        self.player = image_factory(svg_str_to_pixbuf(
            generate_xo(scale=0.8, colors=['#303030', '#303030'])),
                                    self.toolbar,
                                    tooltip=self.nick)

        self.dialog_button = button_factory('go-next',
                                            self.toolbar,
                                            self._dialog_cb,
                                            tooltip=_('Turn complete'))

        self.status = label_factory(self.toolbar, '')

        self.hint_button = button_factory('help-toolbar',
                                          self.toolbar,
                                          self._hint_cb,
                                          tooltip=_('Help'))

        self.score = label_factory(self.toolbar, _('Score: ') + '0')

        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 _new_game_cb(self, button=None):
        ''' Start a new game. '''
        self._game.new_game()

    def _robot_cb(self, button=None):
        ''' Play with the computer (or not). '''
        if not self._game.playing_with_robot:
            self.set_robot_status(True, 'robot-on')
            self._game.new_game()
        else:
            self.set_robot_status(False, 'robot-off')
            self._game.new_game()

    def set_robot_status(self, status, icon):
        ''' Reset robot icon and status '''
        self._game.playing_with_robot = status
        self.robot_button.set_icon(icon)

    def _dialog_cb(self, button=None):
        ''' Send end of turn '''
        if self._game.placed_a_tile:
            self._game.took_my_turn()

    def _hint_cb(self, button=None):
        ''' Give a hint as to where to place a tile '''
        if not self._game.placed_a_tile:
            self._game.give_a_hint()

    def write_file(self, file_path):
        """ Write the grid status to the Journal """
        if not hasattr(self, '_game'):
            return
        for i in range(MAX_HANDS):
            if 'hand-' + str(i) in self.metadata:
                del self.metadata['hand-' + str(i)]
        if 'robot' in self.metadata:
            del self.metadata['robot']
        self.metadata['deck'] = self._game.deck.serialize()
        self.metadata['grid'] = self._game.grid.serialize()
        if self._game.we_are_sharing():
            for i, hand in enumerate(self._game.hands):
                self.metadata['hand-' + str(i)] = hand.serialize()
        else:
            self.metadata['hand-0'] = self._game.hands[0].serialize()
            if self._game.playing_with_robot:
                self.metadata['hand-1'] = self._game.hands[1].serialize()
                self.metadata['robot'] = 'True'

        self.metadata['score'] = str(self._game.score)
        self.metadata['index'] = str(self._game.deck.index)
        if self._game.last_spr_moved is not None and \
           self._game.grid.spr_to_grid(self._game.last_spr_moved) is not None:
            self.metadata['last'] = str(
                self._game.grid.grid[self._game.grid.spr_to_grid(
                    self._game.last_spr_moved)].number)

    def _restore(self):
        """ Restore the game state from metadata """
        if 'robot' in self.metadata:
            self.set_robot_status(True, 'robot-on')
        if 'deck' in self.metadata:
            self._game.deck.restore(self.metadata['deck'])
        if 'grid' in self.metadata:
            self._game.grid.restore(self.metadata['grid'], self._game.deck)
        self._game.show_connected_tiles()

        for i in range(MAX_HANDS):
            if 'hand-' + str(i) in self.metadata:
                # hand-0 is already appended
                if i > 0:  # Add robot or shared hand?
                    self._game.hands.append(
                        Hand(self._game.tile_width,
                             self._game.tile_height,
                             remote=True))
                self._game.hands[i].restore(self.metadata['hand-' + str(i)],
                                            self._game.deck)

        if 'index' in self.metadata:
            self._game.deck.index = int(self.metadata['index'])
        else:
            self._game.deck.index = ROW * COL - self._game.grid.tiles_in_grid()
            for hand in self._game.hands:
                self._game.deck.index += (COL - hand.tiles_in_hand())

        if 'score' in self.metadata:
            self._game.score = int(self.metadata['score'])
            self.score.set_label(_('Score: ') + str(self._game.score))

        self._game.last_spr_moved = None
        if 'last' in self.metadata:
            j = int(self.metadata['last'])
            for k in range(ROW * COL):
                if self._game.deck.tiles[k].number == j:
                    self._game.last_spr_moved = self._game.deck.tiles[k].spr
                    break

    # 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._game.buddies.append(self.nick)
        self._player_colors = [self.colors]
        self._player_pixbuf = [
            svg_str_to_pixbuf(generate_xo(scale=0.8, colors=self.colors))
        ]
        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... """
        if self._shared_activity is None:
            print("Error: Failed to share or join activity ... \
                _shared_activity is null in _shared_cb()")
            return

        self.initiating = sharer
        self.waiting_for_hand = not sharer

        self.conn = self._shared_activity.telepathy_conn
        self.tubes_chan = self._shared_activity.telepathy_tubes_chan
        self.text_chan = self._shared_activity.telepathy_text_chan

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        if sharer:
            print('This is my activity: making a tube...')
            id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
                SERVICE, {})

            self._new_game_button.set_tooltip(
                _('Start a new game once everyone has joined.'))
        else:
            print('I am joining an activity: waiting for a tube...')
            self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
                reply_handler=self._list_tubes_reply_cb,
                error_handler=self._list_tubes_error_cb)

            self._new_game_button.set_icon('no-new-game')
            self._new_game_button.set_tooltip(
                _('Only the sharer can start a new game.'))

        self.robot_button.set_icon('no-robot')
        self.robot_button.set_tooltip(_('The robot is disabled when sharing.'))

        # display your XO on the toolbar
        self.player.set_from_pixbuf(self._player_pixbuf[0])
        self.toolbar.show_all()

    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. """
        print('Error: ListTubes() failed: %s', e)

    def _new_tube_cb(self, id, initiator, type, service, params, state):
        """ Create a new tube. """
        print('New tube: ID=%d initator=%d type=%d service=%s params=%r \
state=%d' % (id, initiator, type, service, params, state))

        if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[ \
                              telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            # Let the sharer know joiner is waiting for a hand.
            if self.waiting_for_hand:
                self.send_event("j", json_dump([self.nick, self.colors]))

    def _setup_dispatch_table(self):
        self._processing_methods = {
            'n': [self._new_game, 'new game'],
            'j': [self._new_joiner, 'new joiner'],
            'b': [self._buddy_list, 'buddy list'],
            'd': [self._sending_deck, 'sending deck'],
            'h': [self._sending_hand, 'sending hand'],
            'p': [self._play_a_piece, 'play a piece'],
            't': [self._take_a_turn, 'take a turn'],
            'g': [self._game_over, 'game over']
        }

    def event_received_cb(self, collab, buddy, msg):
        ''' Data from a tube has arrived. '''
        command = msg.get("command")
        if action is None:
            return

        payload = msg.get("payload")
        self._processing_methods[command][0](payload)

    def _new_joiner(self, payload):
        ''' Someone has joined; sharer adds them to the buddy list. '''
        [nick, colors] = json_load(payload)
        self.status.set_label(nick + ' ' + _('has joined.'))
        self._append_player(nick, colors)
        if self.initiating:
            payload = json_dump([self._game.buddies, self._player_colors])
            self.send_event("b", payload)

    def _append_player(self, nick, colors):
        ''' Keep a list of players, their colors, and an XO pixbuf '''
        if not nick in self._game.buddies:
            self._game.buddies.append(nick)
            self._player_colors.append(colors)
            self._player_pixbuf.append(
                svg_str_to_pixbuf(generate_xo(scale=0.8, colors=colors)))

    def _buddy_list(self, payload):
        ''' Sharer sent the updated buddy list. '''
        [buddies, colors] = json_load(payload)
        for i, nick in enumerate(buddies):
            self._append_player(nick, colors[i])

    def _new_game(self, payload):
        ''' Sharer can start a new game. '''
        if not self.initiating:
            self._game.new_game()

    def _game_over(self, payload):
        ''' Someone cannot plce a tile. '''
        if not self._game.saw_game_over:
            self._game.game_over()

    def _sending_deck(self, payload):
        ''' Sharer sends the deck. '''
        self._game.deck.restore(payload)
        for tile in self._game.deck.tiles:
            tile.reset()
            tile.hide()

    def _sending_hand(self, payload):
        ''' Sharer sends a hand. '''
        hand = json_load(payload)
        nick = hand[0]
        if nick == self.nick:
            self._game.hands[self._game.buddies.index(nick)].restore(
                payload, self._game.deck, buddy=True)

    def _play_a_piece(self, payload):
        ''' When a piece is played, everyone should move it into position. '''
        tile_number, orientation, grid_position = json_load(payload)
        for i in range(ROW * COL):  # find the tile with this number
            if self._game.deck.tiles[i].number == tile_number:
                tile_to_move = i
                break
        self._game.grid.add_tile_to_grid(tile_to_move, orientation,
                                         grid_position, self._game.deck)
        self._game.show_connected_tiles()

        if self.initiating:
            # First, remove the piece from whatever hand it was played.
            for i in range(COL):
                if self._game.hands[self._game.whos_turn].hand[i] is not None \
                   and \
                   self._game.hands[self._game.whos_turn].hand[i].number == \
                        tile_number:
                    self._game.hands[self._game.whos_turn].hand[i] = None
                    break

            # Then let the next player know it is their turn.
            self._game.whos_turn += 1
            if self._game.whos_turn == len(self._game.buddies):
                self._game.whos_turn = 0
            self.status.set_label(self.nick + ': ' + _('take a turn.'))
            self._take_a_turn(self._game.buddies[self._game.whos_turn])
            self.send_event("t", self._game.buddies[self._game.whos_turn])

    def _take_a_turn(self, nick):
        ''' If it is your turn, take it, otherwise, wait. '''
        if nick == self.nick:
            self._game.its_my_turn()
        else:
            self._game.its_their_turn(nick)

    def send_event(self, command, payload):
        """ Send event through the tube. """
        if hasattr(self, 'chattube') and self.collab is not None:
            self.collab.post(dict(command=command, payload=payload))

    def set_player_on_toolbar(self, nick):
        self.player.set_from_pixbuf(
            self._player_pixbuf[self._game.buddies.index(nick)])
        self.player.set_tooltip_text(nick)
示例#34
0
class CollabEdit(Gtk.VBox):

    __gsignals__ = {
        "insert-char": (GObject.SIGNAL_RUN_FIRST, None, [int, int]),
        "cursor-position-changed": (GObject.SIGNAL_RUN_FIRST, None, [int])
    }

    def __init__(self, activity):
        Gtk.VBox.__init__(self)

        self.collab = CollabWrapper(activity)
        self.collab.connect('message', self.__message_cb)
        self.collab.connect('joined', self.__joined_cb)

        self.view = View()
        self.view.connect("insert-char", self.__insert_char_cb)
        self.view.connect("cursor-position-changed", self.__cursor_position_changed_cb)
        self.view.connect("tag-applied", self.__tag_applied_cb)
        self.view.connect("tag-removed", self.__tag_removed_cb)
        self.pack_start(self.view, True, True, 0)

    def __message_cb(self, collab, buddy, msg):
        action = msg.get("action")
        if action is None:
            return

        elif action == "insert":
            self.view.insert_at_position(msg.get("key"), msg.get("position"))

        elif action == "curosr_moved":
            self.view.draw_other_cursor(msg.get("id"), msg.get("position"))

        elif action == "insert_tag":
            self.view.insert_tag(msg.get("tag"), msg.get("start"), msg.get("end"))

        elif action == "remove_tag":
            self.view.remove_tag(msg.get("tag"), msg.get("start"), msg.get("end"))

    def __joined_cb(self, sender):
        if self.collab._leader:
            return

        self.collab.post(dict(action='init_request',
                              res_id=10))  # How get an unique id?

    def __insert_char_cb(self, view, key, position):
        if key in utils.LETTERS_KEYS:
            self.collab.post(dict(action="insert",
                                  key=Gdk.keyval_name(key),
                                  position=position))

    def __cursor_position_changed_cb(self, view, position):
        self.collab.post(dict(action="cursor_moved",
                              id=10,  # How get an unique id?
                              position=position))

        self.emit("cursor-position-changed", position)

    def __tag_applied_cb(self, buffer, tag, start, end):
        self.collab.post(dict(action="insert_tag",
                              tag=tag,
                              start=start,
                              end=end))

    def __tag_removed_cb(self, buffer, tag, start, end):
        self.collab.post(dict(action="remove_tag",
                              tag=tag,
                              start=start,
                              end=end))

    def toggle_bold(self):
        self.view.toggle_bold()

    def toggle_italic(self):
        self.view.toggle_italic()

    def toggle_underline(self):
        self.view.toggle_underline()

    def check_tag_at_offset(self, tag, position):
        return self.view.check_tag_at_offset(tag, position)
示例#35
0
class BibliographyActivity(activity.Activity):

    def __init__(self, handle):
        activity.Activity.__init__(self, handle)
        self._has_read_file = False
        self._collab = CollabWrapper(self)
        self._collab.message.connect(self.__message_cb)

        screen = Gdk.Screen.get_default()
        css_provider = Gtk.CssProvider.get_default()
        css_provider.load_from_path('style.css')
        context = Gtk.StyleContext()
        context.add_provider_for_screen(screen, css_provider,
                                        Gtk.STYLE_PROVIDER_PRIORITY_USER)

        toolbar_box = ToolbarBox()

        activity_button = ActivityToolbarButton(self)
        toolbar_box.toolbar.insert(activity_button, 0)
        activity_button.show()

        html = ToolButton('export-as-html')
        html.set_tooltip(_('Save as HTML'))
        html.connect('clicked', self.__export_as_html_cb)
        activity_button.props.page.insert(html, -1)
        html.show()

        abiword = ToolButton('export-as-abiword')
        abiword.set_tooltip(_('Save as a Write document'))
        abiword.connect('clicked', self.__export_as_abiword_cb)
        activity_button.props.page.insert(abiword, -1)
        abiword.show()

        add_button = AddToolButton(ALL_TYPE_NAMES)
        add_button.connect('add-type', self.__add_type_cb)
        toolbar_box.toolbar.insert(add_button, -1)
        add_button.show()
   
        separator = Gtk.SeparatorToolItem()
        separator.props.draw = False
        separator.set_expand(True)
        toolbar_box.toolbar.insert(separator, -1)
        separator.show()

        stop_button = StopButton(self)
        toolbar_box.toolbar.insert(stop_button, -1)
        stop_button.show()

        self.set_toolbar_box(toolbar_box)
        toolbar_box.show()

        self._main_sw = Gtk.ScrolledWindow()
        self._main_sw.set_policy(Gtk.PolicyType.NEVER,
                                 Gtk.PolicyType.ALWAYS)
        self._main_sw.connect('key-press-event',
                              self.__key_press_event_cb)

        self._main_list = MainList(self._main_sw, self._collab)
        self._main_list.connect('edit-row', self.__edit_row_cb)
        self._main_list.connect('deleted-row', self.__deleted_row_cb)
        self._main_sw.add(self._main_list)
        self._main_list.show()

        self._empty_message = EmptyMessage()

        self._overlay = Gtk.Overlay()
        self._overlay.add(self._empty_message)
        self._empty_message.show()
        self.set_canvas(self._overlay)
        self._overlay.show()

        self._collab.setup()

    def add_item(self, text, type_, data):
        self._empty_message.hide()
        self._overlay.remove(self._overlay.get_child())
        self._overlay.add(self._main_sw)
        self._main_sw.show()
        self._main_list.show()
        self._main_list.add(text, type_, data)

    def __message_cb(self, collab, buddy, msg):
        action = msg.get('action')
        if action is None:
            return

        args = msg.get('args')
        if action == 'add_item':
            self.add_item(*args)
        elif action == 'delete_row':
            self._main_list.delete(args)
        elif action == 'edit_item':
            self._main_list.edited_via_collab(msg.get('path'), args)
        else:
            logging.error('Got message that is weird %r', msg)

    def __add_type_cb(self, add_button, type_):
        window = EntryWindow(ALL_TYPES[type_], self)
        window.connect('save-item', self.__save_item_cb)
        self._overlay.add_overlay(window)
        window.show()
    
    def __save_item_cb(self, window, *args):
        self.add_item(*args)
        window.hide()
        self._overlay.remove(window)
        self._collab.post(dict(
            action='add_item',
            args=args
        ))

    def __edit_row_cb(self, tree_view, type_, json_string):
        previous_values = json.loads(json_string)
        window = EntryWindow(ALL_TYPES[type_], self, previous_values)
        window.connect('save-item', tree_view.edited_row_cb)
        self._overlay.add_overlay(window)
        window.show()

    def __deleted_row_cb(self, tree_view, *row):
        if len(tree_view.get_model()) == 0:
            self._main_list.hide()
            self._overlay.remove(self._overlay.get_child())
            self._overlay.add(self._empty_message)
            self._empty_message.show()

    def __key_press_event_cb(self, scrolled_window, event):
        keyname = Gdk.keyval_name(event.keyval)

        vadjustment = scrolled_window.props.vadjustment
        if keyname == 'Up':
            if vadjustment.props.value > vadjustment.props.lower:
                vadjustment.props.value -= vadjustment.props.step_increment
        elif keyname == 'Down':
            max_value = vadjustment.props.upper - vadjustment.props.page_size
            if vadjustment.props.value < max_value:
                vadjustment.props.value = min(
                    vadjustment.props.value + vadjustment.props.step_increment,
                    max_value)
        else:
            return False

    def __export_as_html_cb(self, button):
        jobject = datastore.create()
        jobject.metadata['title'] = \
            _('{} as HTML').format(self.metadata['title'])
        jobject.metadata['mime_type'] = 'text/html'
        preview = self.get_preview()
        if preview is not None:
            jobject.metadata['preview'] = dbus.ByteArray(preview)

        # write out the document contents in the requested format
        path = os.path.join(self.get_activity_root(),
                            'instance', str(time.time()))
        with open(path, 'w') as f:
            f.write('''<html>
                         <head>
                           <title>{title}</title>
                         </head>

                         <body>
                           <h1>{title}</h1>
                    '''.format(title=jobject.metadata['title']))
            for item in self._main_list.all():
                f.write('<p>{}</p>'.format(
                    item[self._main_list.COLUMN_TEXT]))
            f.write('''
                         </body>
                       </html>
                    ''')
        jobject.file_path = path

        datastore.write(jobject, transfer_ownership=True)
        self._journal_alert(jobject.object_id, _('Success'), _('Your'
                            ' Bibliography was saved to the journal as HTML'))
        jobject.destroy()
        del jobject

    def __export_as_abiword_cb(self, button):
        jobject = datastore.create()
        jobject.metadata['title'] = \
            _('{} as Write document').format(self.metadata['title'])
        jobject.metadata['mime_type'] = 'application/x-abiword'
        preview = self.get_preview()
        if preview is not None:
            jobject.metadata['preview'] = dbus.ByteArray(preview)

        path = os.path.join(self.get_activity_root(),
                            'instance', str(time.time()))
        with open(path, 'w') as f:
            f.write('<?xml version="1.0" encoding="UTF-8"?>\n<abiword>\n'
                    '<section>')
            entries = []
            for item in self._main_list.all():
                markup = item[self._main_list.COLUMN_TEXT]
                abiword = '<p><c>{}</c></p>'.format(markup) \
                    .replace('<b>', '<c props="font-weight:bold">') \
                    .replace('<i>', '<c props="font-style:italic;'
                             ' font-weight:normal">') \
                    .replace('</b>', '</c>').replace('</i>', '</c>')
                entries.append(abiword)
            f.write('\n<p><c></c></p>\n'.join(entries))
            f.write('</section>\n</abiword>')
        jobject.file_path = path

        datastore.write(jobject, transfer_ownership=True)
        self._journal_alert(jobject.object_id, _('Success'), _('Your'
                            ' Bibliography was saved to the journal as a Write'
                            ' document'))
        jobject.destroy()
        del jobject

    def _journal_alert(self, object_id, title, msg):
        alert = Alert()
        alert.props.title = title
        alert.props.msg = msg

        bundle = get_bundle(object_id=object_id)
        if bundle is not None:
            alert.add_button(Gtk.ResponseType.ACCEPT,
                             _('Open with {}').format(bundle.get_name()),
                             Icon(file=bundle.get_icon()))
        else:
            alert.add_button(Gtk.ResponseType.APPLY,
                             _('Show in Journal'),
                             Icon(icon_name='zoom-activity'))
        alert.add_button(Gtk.ResponseType.OK, _('Ok'),
                         Icon(icon_name='dialog-ok'))

        # Remove other alerts
        for alert in self._alerts:
            self.remove_alert(alert)

        self.add_alert(alert)
        alert.connect('response', self.__alert_response_cb, object_id)
        alert.show_all()

    def __alert_response_cb(self, alert, response_id, object_id):
        if response_id is Gtk.ResponseType.ACCEPT:
            launch_bundle(object_id=object_id)
        if response_id is Gtk.ResponseType.APPLY:
            activity.show_object_in_journal(object_id)
        self.remove_alert(alert)

    def write_file(self, file_path):
        if self._main_list is None:
            return  # WhataTerribleFailure
        data = self._main_list.all()
        with open(file_path, 'w') as f:
            json.dump(data, f)

        self.metadata['mime_type'] == 'application/json+bib'

    def get_data(self):
        return self._main_list.all()

    def read_file(self, file_path):
        # FIXME: Why does sugar call read_file so many times?
        if self._has_read_file:
            return
        self._has_read_file = True

        with open(file_path) as f:
            l = json.load(f)
        self.set_data(l)

    def set_data(self, l):
        if len(l) > 0:
            self._main_list.load_json(l)
            self._empty_message.hide()
            self._overlay.remove(self._empty_message)
            self._overlay.add(self._main_sw)
            self._main_sw.show()
            self._main_list.show()
class Collaboration():

    def __init__(self, tw, activity):
        """ A simplistic sharing model: the sharer is the master """
        self._tw = tw
        self._tw.send_event = self.send_event
        self._tw.remote_turtle_dictionary = {}
        self._activity = activity
        self._setup_dispatch_table()

    def setup(self):
        # TODO: hand off role of master if sharer leaves
        self.pservice = presenceservice.get_instance()
        self.initiating = None  # sharing (True) or joining (False)

        # Add my buddy object to the list
        owner = self.pservice.get_owner()
        self.owner = owner
        self._tw.buddies.append(self.owner)
        self._share = ''
        self._activity.connect('shared', self._shared_cb)
        self._activity.connect('joined', self._joined_cb)

    def _setup_dispatch_table(self):
        self._processing_methods = {
            't': self._turtle_request,
            'T': self._receive_turtle_dict,
            'R': self._reskin_turtle,
            'f': self._move_forward,
            'a': self._move_in_arc,
            'r': self._rotate_turtle,
            'x': self._set_xy,
            'W': self._draw_text,
            'c': self._set_pen_color,
            'g': self._set_pen_gray_level,
            's': self._set_pen_shade,
            'w': self._set_pen_width,
            'p': self._set_pen_state,
            'F': self._fill_polygon,
            'P': self._draw_pixbuf,
            'B': self._paste,
            'S': self._speak
        }

    def _shared_cb(self, activity):
        self._shared_activity = self._activity.get_shared_activity()
        if self._shared_activity is None:
            debug_output('Failed to share or join activity ... \
                _shared_activity is null in _shared_cb()',
                         self._tw.running_sugar)
            return

        self._tw.set_sharing(True)

        self.initiating = True
        self.waiting_for_turtles = False
        self._tw.remote_turtle_dictionary = self._get_dictionary()

        debug_output('I am sharing...', self._tw.running_sugar)

        self.conn = self._shared_activity.telepathy_conn
        self.tubes_chan = self._shared_activity.telepathy_tubes_chan
        self.text_chan = self._shared_activity.telepathy_text_chan

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        debug_output('This is my activity: making a tube...',
                     self._tw.running_sugar)

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
            SERVICE, {})
        self._enable_share_button()

    def _joined_cb(self, activity):
        self._shared_activity = self._activity.get_shared_activity()
        if self._shared_activity is None:
            debug_output('Failed to share or join activity ... \
                _shared_activity is null in _shared_cb()',
                         self._tw.running_sugar)
            return

        self._tw.set_sharing(True)

        self.initiating = False
        self.conn = self._shared_activity.telepathy_conn
        self.tubes_chan = self._shared_activity.telepathy_tubes_chan
        self.text_chan = self._shared_activity.telepathy_text_chan

        # call back for "NewTube" signal
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        debug_output('I am joining an activity: waiting for a tube...',
                     self._tw.running_sugar)
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
            reply_handler=self._list_tubes_reply_cb,
            error_handler=self._list_tubes_error_cb)

        # Joiner should request current state from sharer.
        self.waiting_for_turtles = True
        self._enable_share_button()

    def _enable_share_button(self):
        self._activity.share_button.set_icon_name('shareon')
        self._activity.share_button.set_tooltip(_('Share selected blocks'))

    def _list_tubes_reply_cb(self, tubes):
        for tube_info in tubes:
            self._new_tube_cb(*tube_info)

    def _list_tubes_error_cb(self, e):
        error_output('ListTubes() failed: %s' % (e), self._tw.running_sugar)

    def _new_tube_cb(self, id, initiator, type, service, params, state):
        """ Create a new tube. """
        debug_output(
            'New tube: ID=%d initator=%d type=%d service=%s \
                     params=%r state=%d' %
            (id, initiator, type, service, params, state), self._tw.running_sugar)

        if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES]\
                    .AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            # Now that we have the tube, we can ask for the turtle dictionary.
            if self.waiting_for_turtles:  # A joiner must wait for turtles.
                debug_output('Sending a request for the turtle dictionary',
                             self._tw.running_sugar)
                # We need to send our own nick, colors, and turtle position
                colors = self._get_colors()
                event = data_to_string([self._get_nick(), colors])
                debug_output(event, self._tw.running_sugar)
                self.send_event("t", {"payload": event})

    def event_received_cb(self, colab, buddy, msg):
        """
        Events are sent as a tuple, nick|cmd, where nick is a turle name
        and cmd is a turtle event. Everyone gets the turtle dictionary from
        the sharer and watches for 't' events, which indicate that a new
        turtle has joined.
        """
        if len(event_message) == 0:
            return

       # Save active Turtle
        save_active_turtle = self._tw.turtles.get_active_turtle()

        command = msg.get("msg")
        payload = msg.get("payload")
        self._processing_methods[command](payload)

        # Restore active Turtle
        self._tw.turtles.set_turtle(
            self._tw.turtles.get_turtle_key(save_active_turtle))

    def send_event(self, message, payload):
        """ Send event through the tube. """
        if hasattr(self, 'chattube') and self.chattube is not None:
            payload["msg"] = message
            self.chattube.post(payload)

    def _turtle_request(self, payload):
        ''' incoming turtle from a joiner '''
        if payload > 0:
            [nick, colors] = data_from_string(payload)
            if nick != self._tw.nick:  # It is not me.
                # There may not be a turtle dictionary.
                if hasattr(self._tw, 'remote_turtle_dictionary'):
                    # Make sure it is not a "rejoin".
                    if nick not in self._tw.remote_turtle_dictionary:
                        # Add new turtle for the joiner.
                        self._tw.turtles.set_turtle(nick, colors)
                        self._tw.label_remote_turtle(nick, colors)
                    self._tw.remote_turtle_dictionary[nick] = colors
                else:
                    self._tw.remote_turtle_dictionary = self._get_dictionary()
                    # Add new turtle for the joiner.
                    self._tw.turtles.set_turtle(nick, colors)
                    self._tw.label_remote_turtle(nick, colors)

        # Sharer should send the updated remote turtle dictionary to everyone.
        if self.initiating:
            if self._tw.nick not in self._tw.remote_turtle_dictionary:
                self._tw.remote_turtle_dictionary[self._tw.nick] = \
                    self._get_colors()
            event_payload = data_to_string(self._tw.remote_turtle_dictionary)
            self.send_event("T", {"payload": event_payload})
            self.send_my_xy()  # And the sender should report her xy position.

    def _receive_turtle_dict(self, payload):
        ''' Any time there is a new joiner, an updated turtle dictionary is
        circulated. Everyone must report their turtle positions so that we
        are in sync. '''
        if self.waiting_for_turtles:
            if len(payload) > 0:
                # Grab the new remote turtles dictionary.
                remote_turtle_dictionary = data_from_string(payload)
                # Add see what is new.
                for nick in remote_turtle_dictionary:
                    if nick == self._tw.nick:
                        debug_output('skipping my nick %s' %
                                     (nick), self._tw.running_sugar)
                    elif nick != self._tw.remote_turtle_dictionary:
                        # Add new the turtle.
                        colors = remote_turtle_dictionary[nick]
                        self._tw.remote_turtle_dictionary[nick] = colors
                        self._tw.turtles.set_turtle(nick, colors)
                        # Label the remote turtle.
                        self._tw.label_remote_turtle(nick, colors)
                        debug_output('adding %s to remote turtle dictionary' %
                                     (nick), self._tw.running_sugar)
                    else:
                        debug_output('%s already in remote turtle dictionary' %
                                     (nick), self._tw.running_sugar)
            self.waiting_for_turtles = False
        self.send_my_xy()

    def send_my_xy(self):
        ''' Set xy location so joiner can sync turtle positions. Should be
        used to sync positions after turtle drag. '''
        self._tw.turtles.set_turtle(self._get_nick())
        if self._tw.turtles.get_active_turtle().get_pen_state():
            self.send_event("p", {"payload": data_to_string([self._get_nick(),
                                                             False])})
            put_pen_back_down = True
        else:
            put_pen_back_down = False
        self.send_event("x",
                        {"payload": data_to_string([self._get_nick(),
                                                    [int(self._tw.turtles.get_active_turtle().get_xy()[0]),
                                                     int(self._tw.turtles.get_active_turtle().get_xy()[1])]])})
        if put_pen_back_down:
            self.send_event("p", {"payload": data_to_string([self._get_nick(),
                                                             True])})
        self.send_event("r", {"payload": data_to_string(
            [self._get_nick(),
             int(self._tw.turtles.get_active_turtle().get_heading())])})

    def _reskin_turtle(self, payload):
        if len(payload) > 0:
            [nick, [width, height, data]] = data_from_string(payload)
            if nick != self._tw.nick:
                if self._tw.running_sugar:
                    tmp_path = get_path(self._tw.activity, 'instance')
                else:
                    tmp_path = '/tmp'
                file_name = base64_to_image(data, tmp_path)
                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(file_name,
                                                                width, height)
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_shapes([pixbuf])

    def _draw_pixbuf(self, payload):
        if len(payload) > 0:
            [nick, [a, b, x, y, w, h, width, height, data]] =\
                data_from_string(payload)
            if nick != self._tw.nick:
                if self._tw.running_sugar:
                    tmp_path = get_path(self._tw.activity, 'instance')
                else:
                    tmp_path = '/tmp'
                file_name = base64_to_image(data, tmp_path)
                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(file_name,
                                                                width, height)
                pos = self._tw.turtles.turtle_to_screen_coordinates((x, y))
                self._tw.turtles.get_active_turtle().draw_pixbuf(
                    pixbuf, a, b, pos[0], pos[1], w, h, file_name, False)

    def _move_forward(self, payload):
        if len(payload) > 0:
            [nick, x] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().forward(x, False)

    def _move_in_arc(self, payload):
        if len(payload) > 0:
            [nick, [a, r]] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().arc(a, r, False)

    def _rotate_turtle(self, payload):
        if len(payload) > 0:
            [nick, h] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_heading(h, False)

    def _set_xy(self, payload):
        if len(payload) > 0:
            [nick, [x, y]] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_xy(x, y, share=False)

    def _draw_text(self, payload):
        if len(payload) > 0:
            [nick, [label, x, y, size, w]] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().draw_text(
                    label, x, y, size, w, False)

    def _set_pen_color(self, payload):
        if len(payload) > 0:
            [nick, x] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_color(x, False)

    def _set_pen_gray_level(self, payload):
        if len(payload) > 0:
            [nick, x] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_gray(x, False)

    def _set_pen_shade(self, payload):
        if len(payload) > 0:
            [nick, x] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_shade(x, False)

    def _set_pen_width(self, payload):
        if len(payload) > 0:
            [nick, x] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_pen_size(x, False)

    def _set_pen_state(self, payload):
        if len(payload) > 0:
            [nick, x] = data_from_string(payload)
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_pen_state(x, False)

    def _fill_polygon(self, payload):
        if len(payload) > 0:
            [nick, poly_points] = data_from_string(payload)
            shared_poly_points = []
            for i in range(len(poly_points)):
                x, y = self._tw.turtles.screen_to_turtle_coordinates(
                    (poly_points[i][1], poly_points[i][2]))
                if poly_points[i][0] in ['move', 'line']:
                    shared_poly_points.append((poly_points[i][0], x, y))
                elif poly_points[i][0] in ['rarc', 'larc']:
                    shared_poly_points.append(
                        (poly_points[i][0],
                         x,
                         y,
                         poly_points[i][3],
                            poly_points[i][4],
                            poly_points[i][5]))
            if nick != self._tw.nick:
                self._tw.turtles.set_turtle(nick)
                self._tw.turtles.get_active_turtle().set_poly_points(
                    shared_poly_points)
                self._tw.turtles.get_active_turtle().stop_fill(False)

    def _speak(self, payload):
        if len(payload) > 0:
            [nick, language_option, text] = data_from_string(payload)
            if language_option == 'None':
                language_option = ''
            if text is not None:
                os.system('espeak %s "%s" --stdout | aplay' %
                          (language_option, str(text)))

    def _paste(self, payload):
        if len(payload) > 0:
            [nick, text] = data_from_string(payload)
            if text is not None:
                self._tw.process_data(data_from_string(text),
                                      self._tw.paste_offset)
                self._tw.paste_offset += 20

    def _get_dictionary(self):
        return {self._get_nick(): self._get_colors()}

    def _get_nick(self):
        return self._tw.nick

    def _get_colors(self):
        colors = None
        if self._tw.running_sugar:
            if profile.get_color() is not None:
                colors = profile.get_color().to_string()
        else:
            colors = self._activity.get_colors()
        if colors is None:
            colors = '%s,%s' % (DEFAULT_TURTLE_COLORS[0],
                                DEFAULT_TURTLE_COLORS[1])
        return colors.split(',')
class ImageViewerActivity(activity.Activity):

    def __init__(self, handle):
        activity.Activity.__init__(self, handle)
        self._object_id = handle.object_id
        self._collab = CollabWrapper(self)
        self._collab.incoming_file.connect(self.__incoming_file_cb)
        self._collab.buddy_joined.connect(self.__buddy_joined_cb)
        self._collab.joined.connect(self.__joined_cb)
        self._needs_file = False  # Set to true when we join

        # Status of temp file used for write_file:
        self._tempfile = None
        self._close_requested = False

        self._zoom_out_button = None
        self._zoom_in_button = None
        self.previous_image_button = None
        self.next_image_button = None

        self.scrolled_window = Gtk.ScrolledWindow()
        self.scrolled_window.set_policy(Gtk.PolicyType.ALWAYS,
                                        Gtk.PolicyType.ALWAYS)
        # disable sharing until a file is opened
        self.max_participants = 1

        # Don't use the default kinetic scrolling, let the view do the
        # drag-by-touch and pinch-to-zoom logic.
        self.scrolled_window.set_kinetic_scrolling(False)

        self.view = ImageView.ImageViewer()

        # Connect to the touch signal for performing drag-by-touch.
        self.view.add_events(Gdk.EventMask.TOUCH_MASK)
        self._touch_hid = self.view.connect('touch-event',
                                            self.__touch_event_cb)
        self.scrolled_window.add(self.view)
        self.view.show()

        self.connect('key-press-event', self.__key_press_cb)

        if GESTURES_AVAILABLE:
            # Connect to the zoom signals for performing
            # pinch-to-zoom.
            zoom_controller = SugarGestures.ZoomController()
            zoom_controller.attach(self,
                                   SugarGestures.EventControllerFlags.NONE)

            zoom_controller.connect('began', self.__zoomtouch_began_cb)
            zoom_controller.connect('scale-changed',
                                    self.__zoomtouch_changed_cb)
            zoom_controller.connect('ended', self.__zoomtouch_ended_cb)

        self._progress_alert = None

        toolbar_box = ToolbarBox()
        self._add_toolbar_buttons(toolbar_box)
        self.set_toolbar_box(toolbar_box)
        toolbar_box.show()

        if self._object_id is None or not self._jobject.file_path:
            empty_widgets = Gtk.EventBox()
            empty_widgets.modify_bg(Gtk.StateType.NORMAL,
                                    style.COLOR_WHITE.get_gdk_color())

            vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
            mvbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
            vbox.pack_start(mvbox, True, False, 0)

            image_icon = Icon(pixel_size=style.LARGE_ICON_SIZE,
                              icon_name='imageviewer',
                              stroke_color=style.COLOR_BUTTON_GREY.get_svg(),
                              fill_color=style.COLOR_TRANSPARENT.get_svg())
            mvbox.pack_start(image_icon, False, False, style.DEFAULT_PADDING)

            label = Gtk.Label('<span foreground="%s"><b>%s</b></span>' %
                              (style.COLOR_BUTTON_GREY.get_html(),
                               _('No image')))
            label.set_use_markup(True)
            mvbox.pack_start(label, False, False, style.DEFAULT_PADDING)

            hbox = Gtk.Box()
            open_image_btn = Gtk.Button()
            open_image_btn.connect('clicked', self._show_picker_cb)
            add_image = Gtk.Image.new_from_stock(Gtk.STOCK_ADD,
                                                 Gtk.IconSize.BUTTON)
            buttonbox = Gtk.Box()
            buttonbox.pack_start(add_image, False, True, 0)
            buttonbox.pack_end(Gtk.Label(_('Choose an image')), True, True, 5)
            open_image_btn.add(buttonbox)
            hbox.pack_start(open_image_btn, True, False, 0)
            mvbox.pack_start(hbox, False, False, style.DEFAULT_PADDING)

            empty_widgets.add(vbox)
            empty_widgets.show_all()
            self.set_canvas(empty_widgets)
        else:
            self.set_canvas(self.scrolled_window)
            self.scrolled_window.show()

        Gdk.Screen.get_default().connect('size-changed', self._configure_cb)
        self._collab.setup()

    def __touch_event_cb(self, widget, event):
        coords = event.get_coords()
        if event.type == Gdk.EventType.TOUCH_BEGIN:
            self.view.start_dragtouch(coords)
        elif event.type == Gdk.EventType.TOUCH_UPDATE:
            self.view.update_dragtouch(coords)
        elif event.type == Gdk.EventType.TOUCH_END:
            self.view.finish_dragtouch(coords)

    def __zoomtouch_began_cb(self, controller):
        self.view.start_zoomtouch(controller.get_center())

        # Don't listen to touch signals until pinch-to-zoom ends.
        self.view.disconnect(self._touch_hid)

    def __zoomtouch_changed_cb(self, controller, scale):
        self.view.update_zoomtouch(controller.get_center(), scale)

    def __zoomtouch_ended_cb(self, controller):
        self.view.finish_zoomtouch()
        self._touch_hid = self.view.connect('touch-event',
                                            self.__touch_event_cb)

    def __key_press_cb(self, widget, event):
        key_name = Gdk.keyval_name(event.keyval)
        if key_name == "Left":
            self._change_image(-1)
        elif key_name == "Right":
            self._change_image(1)
        elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
            if key_name == "q":
                self.close()
        return True

    def _get_image_list(self):
        value = mime.GENERIC_TYPE_IMAGE
        mime_types = mime.get_generic_type(value).mime_types
        (self.image_list, self.image_count) = datastore.find({'mime_type':
                                                             mime_types})

    def _add_toolbar_buttons(self, toolbar_box):
        self._seps = []

        self.activity_button = ActivityToolbarButton(self)
        toolbar_box.toolbar.insert(self.activity_button, 0)
        self.activity_button.show()

        self._zoom_out_button = ToolButton('zoom-out')
        self._zoom_out_button.set_tooltip(_('Zoom out'))
        self._zoom_out_button.connect('clicked', self.__zoom_out_cb)
        toolbar_box.toolbar.insert(self._zoom_out_button, -1)
        self._zoom_out_button.show()

        self._zoom_in_button = ToolButton('zoom-in')
        self._zoom_in_button.set_tooltip(_('Zoom in'))
        self._zoom_in_button.connect('clicked', self.__zoom_in_cb)
        toolbar_box.toolbar.insert(self._zoom_in_button, -1)
        self._zoom_in_button.show()

        zoom_tofit_button = ToolButton('zoom-best-fit')
        zoom_tofit_button.set_tooltip(_('Fit to window'))
        zoom_tofit_button.connect('clicked', self.__zoom_tofit_cb)
        toolbar_box.toolbar.insert(zoom_tofit_button, -1)
        zoom_tofit_button.show()

        zoom_original_button = ToolButton('zoom-original')
        zoom_original_button.set_tooltip(_('Original size'))
        zoom_original_button.connect('clicked', self.__zoom_original_cb)
        toolbar_box.toolbar.insert(zoom_original_button, -1)
        zoom_original_button.show()

        fullscreen_button = ToolButton('view-fullscreen')
        fullscreen_button.set_tooltip(_('Fullscreen'))
        fullscreen_button.connect('clicked', self.__fullscreen_cb)
        toolbar_box.toolbar.insert(fullscreen_button, -1)
        fullscreen_button.show()

        self._seps.append(Gtk.SeparatorToolItem())
        toolbar_box.toolbar.insert(self._seps[-1], -1)
        self._seps[-1].show()

        rotate_anticlockwise_button = ToolButton('rotate_anticlockwise')
        rotate_anticlockwise_button.set_tooltip(_('Rotate anticlockwise'))
        rotate_anticlockwise_button.connect('clicked',
                                            self.__rotate_anticlockwise_cb)
        toolbar_box.toolbar.insert(rotate_anticlockwise_button, -1)
        rotate_anticlockwise_button.show()

        rotate_clockwise_button = ToolButton('rotate_clockwise')
        rotate_clockwise_button.set_tooltip(_('Rotate clockwise'))
        rotate_clockwise_button.connect('clicked', self.__rotate_clockwise_cb)
        toolbar_box.toolbar.insert(rotate_clockwise_button, -1)
        rotate_clockwise_button.show()

        if self._object_id is None:
            self._seps.append(Gtk.SeparatorToolItem())
            toolbar_box.toolbar.insert(self._seps[-1], -1)
            self._seps[-1].show()

            self.previous_image_button = ToolButton('go-previous-paired')
            self.previous_image_button.set_tooltip(_('Previous Image'))
            self.previous_image_button.props.sensitive = False
            self.previous_image_button.connect('clicked',
                                               self.__previous_image_cb)
            toolbar_box.toolbar.insert(self.previous_image_button, -1)
            self.previous_image_button.show()

            self.next_image_button = ToolButton('go-next-paired')
            self.next_image_button.set_tooltip(_('Next Image'))
            self.next_image_button.props.sensitive = False
            self.next_image_button.connect('clicked', self.__next_image_cb)
            toolbar_box.toolbar.insert(self.next_image_button, -1)
            self.next_image_button.show()

            GObject.idle_add(self._get_image_list)

        separator = Gtk.SeparatorToolItem()
        separator.props.draw = False
        separator.set_expand(True)
        toolbar_box.toolbar.insert(separator, -1)
        separator.show()

        stop_button = StopButton(self)
        toolbar_box.toolbar.insert(stop_button, -1)
        stop_button.show()

    def _configure_cb(self, event=None):
        if Gdk.Screen.width() <= style.GRID_CELL_SIZE * 12:
            for sep in self._seps:
                sep.hide()
        else:
            for sep in self._seps:
                sep.show()

    def _update_zoom_buttons(self):
        self._zoom_in_button.set_sensitive(self.view.can_zoom_in())
        self._zoom_out_button.set_sensitive(self.view.can_zoom_out())

    def _change_image(self, delta):
        # boundary conditions
        if self.current_image_index == 0 and delta == -1:
            return
        if self.current_image_index == self.image_count - 1 and delta == 1:
            return

        self.current_image_index += delta
        self.make_button_sensitive()
        jobject = self.image_list[self.current_image_index]
        self._object_id = jobject.object_id
        if os.path.exists(self._tempfile):
            os.remove(self._tempfile)
        self.read_file(jobject.file_path)

    def __previous_image_cb(self, button):
        if self.current_image_index > 0:
            self._change_image(-1)

    def __next_image_cb(self, button):
        if self.current_image_index < self.image_count:
            self._change_image(1)

    def __zoom_in_cb(self, button):
        self.view.zoom_in()
        self._update_zoom_buttons()

    def __zoom_out_cb(self, button):
        self.view.zoom_out()
        self._update_zoom_buttons()

    def __zoom_tofit_cb(self, button):
        self.view.zoom_to_fit()
        self._update_zoom_buttons()

    def __zoom_original_cb(self, button):
        self.view.zoom_original()
        self._update_zoom_buttons()

    def __rotate_anticlockwise_cb(self, button):
        self.view.rotate_anticlockwise()

    def __rotate_clockwise_cb(self, button):
        self.view.rotate_clockwise()

    def __fullscreen_cb(self, button):
        self.fullscreen()

    def get_current_image_index(self):
        for image in self.image_list:
            if image.object_id == self._object_id:
                jobject = image
                break

        self.current_image_index = self.image_list.index(jobject)

    def make_button_sensitive(self):
        if self.image_count <= 1:
            return

        if self.current_image_index == 0:
            self.next_image_button.props.sensitive = True
            self.previous_image_button.props.sensitive = False
        elif self.current_image_index == self.image_count - 1:
            self.previous_image_button.props.sensitive = True
            self.next_image_button.props.sensitive = False
        else:
            self.next_image_button.props.sensitive = True
            self.previous_image_button.props.sensitive = True

    def _show_picker_cb(self, button):
        if not self._want_document:
            return

        try:
            chooser = ObjectChooser(self, what_filter='Image',
                                    filter_type=FILTER_TYPE_GENERIC_MIME,
                                    show_preview=True)
        except:
            # for compatibility with older versions
            chooser = ObjectChooser(self._activity, what_filter='Image')

        try:
            result = chooser.run()
            if result == Gtk.ResponseType.ACCEPT:
                jobject = chooser.get_selected_object()
                if jobject and jobject.file_path:
                    self._object_id = jobject.object_id
                    self.read_file(jobject.file_path)
                    self.set_canvas(self.scrolled_window)
                    self.scrolled_window.show()
        finally:
            self.get_current_image_index()
            self.make_button_sensitive()
            chooser.destroy()
            del chooser

    def read_file(self, file_path):
        if self._object_id is None or self.shared_activity:
            # read_file is call because the canvas is visible
            # but we need check if is not the case of empty file
            return

        self._want_document = False
        # enable collaboration
        self.activity_button.page.share.props.sensitive = True

        tempfile = os.path.join(self.get_activity_root(), 'instance',
                                'tmp%i' % time.time())

        os.link(file_path, tempfile)
        self._tempfile = tempfile

        self.view.set_file_location(tempfile)

        zoom = self.metadata.get('zoom', None)
        if zoom is not None:
            self.view.set_zoom(float(zoom))

    def write_file(self, file_path):
        if self._tempfile:
            self.metadata['activity'] = self.get_bundle_id()
            self.metadata['zoom'] = str(self.view.get_zoom())
            if self._close_requested:
                os.link(self._tempfile, file_path)
                os.unlink(self._tempfile)
                self._tempfile = None
        else:
            raise NotImplementedError

    def can_close(self):
        self._close_requested = True
        return True

    def __incoming_file_cb(self, collab, file, desc):
        logging.debug('__incoming_file_cb with need %r', self._needs_file)
        if not self._needs_file:
            return

        self._progress_alert = ProgressAlert()
        self._progress_alert.props.title = _('Receiving image...')
        self.add_alert(self._progress_alert)

        self._needs_file = False
        file_path = os.path.join(self.get_activity_root(), 'instance',
                                 '%i' % time.time())
        file.connect('notify::state', self.__file_notify_state_cb)
        file.connect('notify::transfered_bytes',
                     self.__file_transfered_bytes_cb)
        file.accept_to_file(file_path)

    def __file_notify_state_cb(self, file, pspec):
        logging.debug('__file_notify_state %r', file.props.state)
        if file.props.state != FT_STATE_COMPLETED:
            return

        file_path = file.props.output
        logging.debug("Saving file %s to datastore...", file_path)
        self._jobject.file_path = file_path
        datastore.write(self._jobject, transfer_ownership=True)

        if self._progress_alert is not None:
            self.remove_alert(self._progress_alert)
            self._progress_alert = None

        GObject.idle_add(self.__set_file_idle_cb, self._jobject.object_id)

    def __set_file_idle_cb(self, object_id):
        dsobj = datastore.get(object_id)
        self._tempfile = dsobj.file_path
        """ This method is used when join a collaboration session """
        self.view.set_file_location(self._tempfile)
        try:
            zoom = int(self.metadata.get('zoom', '0'))
            if zoom > 0:
                self.view.set_zoom(zoom)
        except Exception:
            pass
        self.set_canvas(self.scrolled_window)
        self.scrolled_window.show_all()
        return False

    def __file_transfered_bytes_cb(self, file, pspec):
        total = file.file_size
        bytes_downloaded = file.props.transfered_bytes
        fraction = bytes_downloaded / total
        self._progress_alert.set_fraction(fraction)

    def __buddy_joined_cb(self, collab, buddy):
        logging.debug('__buddy_joined_cb %r', buddy)
        self._collab.send_file_file(buddy, self._tempfile, None)

    def __joined_cb(self, collab):
        logging.debug('I joined!')
        # Somebody will send us a file, just wait
        self._needs_file = True

    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)
示例#38
0
class ReflectActivity(activity.Activity):
    ''' An activity for reflecting on one's work '''

    def __init__(self, handle):
        ''' Initialize the toolbar '''
        try:
            super(ReflectActivity, self).__init__(handle)
        except dbus.exceptions.DBusException as e:
            _logger.error(str(e))

        logging.error('setting reflection data to []')
        self.reflection_data = []

        self.connect('realize', self.__realize_cb)

        self.font_size = 8

        self.max_participants = 4
        self._setup_toolbars()

        color = profile.get_color()
        color_stroke = color.get_stroke_color()
        color_fill = color.get_fill_color()

        lighter = utils.lighter_color([color_stroke, color_fill])
        darker = 1 - lighter

        if lighter == 0:
            self.bg_color = style.Color(color_stroke)
            self.fg_color = style.Color(color_fill)
        else:
            self.bg_color = style.Color(color_fill)
            self.fg_color = style.Color(color_stroke)

        self.modify_bg(Gtk.StateType.NORMAL, self.bg_color.get_gdk_color())

        self.bundle_path = activity.get_bundle_path()
        self.tmp_path = os.path.join(activity.get_activity_root(), 'instance')

        self.sharing = False
        self._copy_entry = None
        self._paste_entry = None
        self._webkit = None
        self._clipboard_text = ''
        self._fixed = None

        self.initiating = True
        if self.shared_activity:
            # We're joining
            if not self.get_shared():
                self.initiating = False

                self.busy_cursor()
                share_icon = Icon(icon_name='zoom-neighborhood')
                self._joined_alert = Alert()
                self._joined_alert.props.icon = share_icon
                self._joined_alert.props.title = _('Please wait')
                self._joined_alert.props.msg = _('Starting connection...')
                self.add_alert(self._joined_alert)

                # Wait for joined signal
                self.connect("joined", self._joined_cb)

        self._open_reflect_windows()

        self._setup_presence_service()

        # Joiners wait to receive data from sharer
        # Otherwise, load reflections from local store
        if not self.shared_activity:
            self.busy_cursor()
            GObject.idle_add(self._load_reflections)

    def read_file(self, file_path):
        fd = open(file_path, 'r')
        data = fd.read()
        fd.close()
        self.reflection_data = json.loads(data)

    def write_file(self, file_path):
        data = json.dumps(self.reflection_data)
        fd = open(file_path, 'w')
        fd.write(data)
        fd.close()

        self.metadata['font_size'] = str(self.font_size)

    def _load_reflections(self):
        self._find_starred()
        self._reflect_window.load(self.reflection_data)
        self.reset_cursor()

    def _found_obj_id(self, obj_id):
        for item in self.reflection_data:
            if 'obj_id' in item and item['obj_id'] == obj_id:
                return True
        return False

    def reload_data(self, data):
        ''' Reload data after sorting or searching '''
        self._reflection_data = data[:]
        self._reflect_window.reload(self._reflection_data)
        self.reset_scrolled_window_adjustments()

    def _find_starred(self):
        ''' Find all the _stars in the Journal. '''
        self.dsobjects, self._nobjects = datastore.find({'keep': '1'})
        for dsobj in self.dsobjects:
            if self._found_obj_id(dsobj.object_id):
                continue  # Already have this object -- TODO: update it
            self._add_new_from_journal(dsobj)

    def _add_new_from_journal(self, dsobj):
        self.reflection_data.append({
            'title': _('Untitled'), 'obj_id': dsobj.object_id})
        if hasattr(dsobj, 'metadata'):
            if 'creation_time' in dsobj.metadata:
                self.reflection_data[-1]['creation_time'] = \
                    dsobj.metadata['creation_time']
            else:
                self.reflection_data[-1]['creation_time'] = \
                    int(time.time())
            if 'timestamp' in dsobj.metadata:
                self.reflection_data[-1]['modification_time'] = \
                    dsobj.metadata['timestamp']
            else:
                self.reflection_data[-1]['modification_time'] = \
                    self.reflection_data[-1]['creation_time']
            if 'activity' in dsobj.metadata:
                self.reflection_data[-1]['activities'] = \
                    [utils.bundle_id_to_icon(dsobj.metadata['activity'])]
            if 'title' in dsobj.metadata:
                self.reflection_data[-1]['title'] = \
                    dsobj.metadata['title']
            if 'description' in dsobj.metadata:
                self.reflection_data[-1]['content'] = \
                    [{'text': dsobj.metadata['description']}]
            else:
                self.reflection_data[-1]['content'] = []
            if 'tags' in dsobj.metadata:
                self.reflection_data[-1]['tags'] = []
                tags = dsobj.metadata['tags'].split()
                for tag in tags:
                    if tag[0] != '#':
                        self.reflection_data[-1]['tags'].append('#' + tag)
                    else:
                        self.reflection_data[-1]['tags'].append(tag)
            if 'comments' in dsobj.metadata:
                try:
                    comments = json.loads(dsobj.metadata['comments'])
                except:
                    comments = []
                self.reflection_data[-1]['comments'] = []
                for comment in comments:
                    try:
                        data = {'nick': comment['from'],
                                'comment': comment['message']}
                        if 'icon-color' in comment:
                            colors = comment['icon-color'].split(',')
                            darker = 1 - utils.lighter_color(colors)
                            data['color'] = colors[darker]
                        else:
                            data['color'] = '#000000'
                        self.reflection_data[-1]['comments'].append(data)
                    except:
                        _logger.debug('could not parse comment %s'
                                      % comment)
            if 'mime_type' in dsobj.metadata and \
               dsobj.metadata['mime_type'][0:5] == 'image':
                new_path = os.path.join(self.tmp_path,
                                        dsobj.object_id)
                try:
                    shutil.copy(dsobj.file_path, new_path)
                except Exception as e:
                    logging.error("Couldn't copy %s to %s: %s" %
                                  (dsobj.file_path, new_path, e))
                self.reflection_data[-1]['content'].append(
                    {'image': new_path})
            elif 'preview' in dsobj.metadata:
                pixbuf = utils.get_pixbuf_from_journal(dsobj, 300, 225)
                if pixbuf is not None:
                    path = os.path.join(self.tmp_path,
                                        dsobj.object_id + '.png')
                    utils.save_pixbuf_to_file(pixbuf, path)
                    self.reflection_data[-1]['content'].append(
                        {'image': path})
            self.reflection_data[-1]['stars'] = 0

    def delete_item(self, obj_id):
        for i, obj in enumerate(self.reflection_data):
            if obj['obj_id'] == obj_id:
                self.reflection_data.remove(self.reflection_data[i])
                return

    def busy_cursor(self):
        self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))

    def reset_cursor(self):
        self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR))

    def _open_reflect_windows(self):
        # Most things need only be done once
        if self._fixed is None:
            self._fixed = Gtk.Fixed()
            self._fixed.set_size_request(Gdk.Screen.width(),
                                         Gdk.Screen.height())

            # Offsets from the bottom of the screen
            dy1 = 2 * style.GRID_CELL_SIZE
            dy2 = 1 * style.GRID_CELL_SIZE

            self._button_area = Gtk.Alignment.new(0.5, 0, 0, 0)
            self._button_area.set_size_request(Gdk.Screen.width(),
                                               style.GRID_CELL_SIZE)
            self._fixed.put(self._button_area, 0, 0)
            self._button_area.show()

            self._scrolled_window = Gtk.ScrolledWindow()
            self._scrolled_window.set_size_request(
                Gdk.Screen.width(), Gdk.Screen.height() - dy1)
            self._set_scroll_policy()
            self._graphics_area = Gtk.Alignment.new(0.5, 0, 0, 0)
            self._scrolled_window.add_with_viewport(self._graphics_area)
            self._graphics_area.show()
            self._fixed.put(self._scrolled_window, 0, dy2)
            self._scrolled_window.show()

            self._overlay_window = Gtk.ScrolledWindow()
            self._overlay_window.set_size_request(
                style.GRID_CELL_SIZE * 10,
                style.GRID_CELL_SIZE * 6)
            self._overlay_window.modify_bg(
                Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color())
            self._overlay_window.set_policy(
                 Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
            self._overlay_area = Gtk.Alignment.new(0.5, 0, 0, 0)
            self._overlay_window.add_with_viewport(self._overlay_area)
            self._overlay_area.show()
            x = int((Gdk.Screen.width() - style.GRID_CELL_SIZE * 10) / 2)
            self._fixed.put(self._overlay_window, 0, Gdk.Screen.height())
            self._overlay_window.show()
            self._old_overlay_widget = None

            self._reflect_window = ReflectWindow(self)
            self._reflect_window.show()

            Gdk.Screen.get_default().connect('size-changed',
                                             self._configure_cb)
            self._toolbox.connect('hide', self._resize_hide_cb)
            self._toolbox.connect('show', self._resize_show_cb)

            self._reflect_window.set_events(Gdk.EventMask.KEY_PRESS_MASK)
            self._reflect_window.connect('key_press_event',
                                      self._reflect_window.keypress_cb)
            self._reflect_window.set_can_focus(True)
            self._reflect_window.grab_focus()

        self.set_canvas(self._fixed)
        self._fixed.show()

    def reset_scrolled_window_adjustments(self):
        adj = self._scrolled_window.get_hadjustment()
        if adj is not None:
            adj.set_value(0)
        adj = self._scrolled_window.get_vadjustment()
        if adj is not None:
            adj.set_value(0)

    def load_graphics_area(self, widget):
        self._graphics_area.add(widget)

    def load_button_area(self, widget):
        self._button_area.add(widget)

    def load_overlay_area(self, widget):
        if self._old_overlay_widget is not None:
            self._overlay_area.remove(self._old_overlay_widget)
        self._overlay_area.add(widget)
        self._old_overlay_widget = widget

    def show_overlay_area(self):
        x = int((Gdk.Screen.width() - style.GRID_CELL_SIZE * 10) / 2)
        self._fixed.move(self._overlay_window, x, style.GRID_CELL_SIZE)

    def hide_overlay_area(self):
        self._fixed.move(
            self._overlay_window, 0, Gdk.Screen.height())

    def _resize_hide_cb(self, widget):
        self._resize_canvas(widget, True)

    def _resize_show_cb(self, widget):
        self._resize_canvas(widget, False)

    def _configure_cb(self, event):
        self._fixed.set_size_request(Gdk.Screen.width(), Gdk.Screen.height())
        self._set_scroll_policy()
        self._resize_canvas(None)
        self._reflect_window.reload_graphics()

    def _resize_canvas(self, widget, fullscreen=False):
        # When a toolbar is expanded or collapsed, resize the canvas
        if hasattr(self, '_reflect_window'):
            if self.toolbar_expanded():
                dy1 = 3 * style.GRID_CELL_SIZE
                dy2 = 2 * style.GRID_CELL_SIZE
            else:
                dy1 = 2 * style.GRID_CELL_SIZE
                dy2 = 1 * style.GRID_CELL_SIZE

            if fullscreen:
                dy1 -= 2 * style.GRID_CELL_SIZE
                dy2 -= 2 * style.GRID_CELL_SIZE

            self._scrolled_window.set_size_request(
                Gdk.Screen.width(), Gdk.Screen.height() - dy2)
            self._fixed.move(self._button_area, 0, 0)

        self._about_panel_visible = False

    def toolbar_expanded(self):
        if self.activity_button.is_expanded():
            return True
        elif self.edit_toolbar_button.is_expanded():
            return True
        elif self.view_toolbar_button.is_expanded():
            return True
        return False

    def get_activity_version(self):
        info_path = os.path.join(self.bundle_path, 'activity', 'activity.info')
        try:
            info_file = open(info_path, 'r')
        except Exception as e:
            _logger.error('Could not open %s: %s' % (info_path, e))
            return 'unknown'

        cp = ConfigParser()
        cp.readfp(info_file)

        section = 'Activity'

        if cp.has_option(section, 'activity_version'):
            activity_version = cp.get(section, 'activity_version')
        else:
            activity_version = 'unknown'
        return activity_version

    def get_uid(self):
        if len(self.volume_data) == 1:
            return self.volume_data[0]['uid']
        else:
            return 'unknown'

    def _setup_toolbars(self):
        ''' Setup the toolbars. '''
        self._toolbox = ToolbarBox()

        self.activity_button = ActivityToolbarButton(self)
        self.activity_button.connect('clicked', self._resize_canvas)
        self._toolbox.toolbar.insert(self.activity_button, 0)
        self.activity_button.show()

        self.set_toolbar_box(self._toolbox)
        self._toolbox.show()
        self.toolbar = self._toolbox.toolbar

        view_toolbar = Gtk.Toolbar()
        self.view_toolbar_button = ToolbarButton(
            page=view_toolbar,
            label=_('View'),
            icon_name='toolbar-view')
        self.view_toolbar_button.connect('clicked', self._resize_canvas)
        self._toolbox.toolbar.insert(self.view_toolbar_button, 1)
        view_toolbar.show()
        self.view_toolbar_button.show()

        button = ToolButton('view-fullscreen')
        button.set_tooltip(_('Fullscreen'))
        button.props.accelerator = '<Alt>Return'
        view_toolbar.insert(button, -1)
        button.show()
        button.connect('clicked', self._fullscreen_cb)

        edit_toolbar = Gtk.Toolbar()
        self.edit_toolbar_button = ToolbarButton(
            page=edit_toolbar,
            label=_('Edit'),
            icon_name='toolbar-edit')
        self.edit_toolbar_button.connect('clicked', self._resize_canvas)
        self._toolbox.toolbar.insert(self.edit_toolbar_button, 1)
        edit_toolbar.show()
        self.edit_toolbar_button.show()

        self._copy_button = ToolButton('edit-copy')
        self._copy_button.set_tooltip(_('Copy'))
        self._copy_button.props.accelerator = '<Ctrl>C'
        edit_toolbar.insert(self._copy_button, -1)
        self._copy_button.show()
        self._copy_button.connect('clicked', self._copy_cb)
        self._copy_button.set_sensitive(False)

        self._paste_button = ToolButton('edit-paste')
        self._paste_button.set_tooltip(_('Paste'))
        self._paste_button.props.accelerator = '<Ctrl>V'
        edit_toolbar.insert(self._paste_button, -1)
        self._paste_button.show()
        self._paste_button.connect('clicked', self._paste_cb)
        self._paste_button.set_sensitive(False)

        button = ToolButton('list-add')
        button.set_tooltip(_('Add Item'))
        button.props.accelerator = '<Ctrl>+'
        self._toolbox.toolbar.insert(button, -1)
        button.show()
        button.connect('clicked', self.__add_item_cb)

        self._date_button = RadioToolButton('date-sort', group=None)
        self._date_button.set_tooltip(_('Sort by Date'))
        self._date_button.connect('clicked', self._date_button_cb)
        self._toolbox.toolbar.insert(self._date_button, -1)
        self._date_button.show()

        self._title_button = RadioToolButton('title-sort',
                                             group=self._date_button)
        self._title_button.set_tooltip(_('Sort by Title'))
        self._title_button.connect('clicked', self._title_button_cb)
        self._toolbox.toolbar.insert(self._title_button, -1)
        self._title_button.show()

        self._stars_button = RadioToolButton('stars-sort',
                                             group=self._date_button)
        self._stars_button.set_tooltip(_('Sort by Favourite'))
        self._stars_button.connect('clicked', self._stars_button_cb)
        self._toolbox.toolbar.insert(self._stars_button, -1)
        self._stars_button.show()

        # setup the search options
        self._search_entry = iconentry.IconEntry()
        self._search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
                                              'system-search')
        self._search_entry.connect('activate', self._search_entry_activated_cb)
        self._search_entry.connect('changed', self._search_entry_changed_cb)
        self._search_entry.add_clear_button()

        tool_item = Gtk.ToolItem()
        tool_item.set_expand(True)
        tool_item.add(self._search_entry)
        self._search_entry.show()
        self._toolbox.toolbar.insert(tool_item, -1)
        tool_item.show()

        self._search_button = ToolButton('dialog-ok')
        self._search_button.set_tooltip(_('Search by Tags'))
        self._search_button.connect('clicked', self._search_button_cb)
        self._toolbox.toolbar.insert(self._search_button, -1)
        self._search_button.show()

        separator = Gtk.SeparatorToolItem()
        separator.props.draw = False
        separator.set_expand(True)
        self._toolbox.toolbar.insert(separator, -1)
        separator.show()

        stop_button = StopButton(self)
        stop_button.props.accelerator = '<Ctrl>q'
        self._toolbox.toolbar.insert(stop_button, -1)
        stop_button.show()

    def _search_button_cb(self, button):
        self.busy_cursor()
        self._do_search()

    def _search_entry_activated_cb(self, entry):
        self.busy_cursor()
        self._do_search()

    def _do_search(self):
        logging.debug('_search_entry_activated_cb')
        if self._search_entry.props.text == '':
            logging.debug('clearing search')
            for item in self.reflection_data:
                item['hidden'] = False
        else:
            tags = self._search_entry.props.text.split()
            for i, tag in enumerate(tags):
                if not tag[0] == '#':
                    tags[i] = '#%s' % tag
            logging.error(tags)
            for item in self.reflection_data:
                hidden = True
                if 'tags' in item:
                    for tag in tags:
                        if tag in item['tags']:
                            hidden = False
                item['hidden'] = hidden
        self.reload_data(self.reflection_data)
        self.reset_cursor()

    def _search_entry_changed_cb(self, entry):
        logging.debug('_search_entry_changed_cb search for \'%s\'',
                     self._search_entry.props.text)
        self.busy_cursor()
        self._do_search_changed()

    def _do_search_changed(self):
        if self._search_entry.props.text == '':
            logging.debug('clearing search')
            for item in self.reflection_data:
                item['hidden'] = False
            self.reload_data(self.reflection_data)
        self.reset_cursor()

    def _title_button_cb(self, button):
        ''' sort by title '''
        self.busy_cursor()
        GObject.idle_add(self._title_sort)

    def _title_sort(self):
        sorted_data = sorted(self.reflection_data,
                             key=lambda item: item['title'].lower())
        self.reload_data(sorted_data)
        self.reset_cursor()

    def _date_button_cb(self, button):
        ''' sort by modification date '''
        self.busy_cursor()
        GObject.idle_add(self._date_sort)

    def _date_sort(self):
        sorted_data = sorted(self.reflection_data,
                             key=lambda item: int(item['modification_time']),
                             reverse=True)
        self.reload_data(sorted_data)
        self.reset_cursor()

    def _stars_button_cb(self, button):
        ''' sort by number of stars '''
        self.busy_cursor()
        GObject.idle_add(self._stars_sort)

    def _stars_sort(self):
        sorted_data = sorted(self.reflection_data,
                             key=lambda item: item['stars'], reverse=True)
        self.reload_data(sorted_data)
        self.reset_cursor()

    def __realize_cb(self, window):
        self.window_xid = window.get_window().get_xid()

    def set_copy_widget(self, webkit=None, text_entry=None):
        # Each task is responsible for setting a widget for copy
        if webkit is not None:
            self._webkit = webkit
        else:
            self._webkit = None
        if text_entry is not None:
            self._copy_entry = text_entry
        else:
            self._copy_entry = None

        self._copy_button.set_sensitive(webkit is not None or
                                        text_entry is not None)

    def _copy_cb(self, button):
        if self._copy_entry is not None:
            self._copy_entry.copy_clipboard()
        elif self._webkit is not None:
            self._webkit.copy_clipboard()
        else:
            _logger.debug('No widget set for copy.')

    def set_paste_widget(self, text_entry=None):
        # Each task is responsible for setting a widget for paste
        if text_entry is not None:
            self._paste_entry = text_entry
        self._paste_button.set_sensitive(text_entry is not None)

    def _paste_cb(self, button):
        clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        self.clipboard_text = clipboard.wait_for_text()
        if self._paste_entry is not None:
            self._paste_entry.paste_clipboard()
        else:
            _logger.debug('No widget set for paste (%s).' %
                          self.clipboard_text)

    def _fullscreen_cb(self, button):
        ''' Hide the Sugar toolbars. '''
        self.fullscreen()

    def __add_item_cb(self, button):
        try:
            chooser = ObjectChooser(parent=self, what_filter=None)
        except TypeError:
            chooser = ObjectChooser(
                None, self._reflection.activity,
                Gtk.DialogFlags.MODAL |
                Gtk.DialogFlags.DESTROY_WITH_PARENT)

        try:
            result = chooser.run()
            if result == Gtk.ResponseType.ACCEPT:
                jobject = chooser.get_selected_object()
                if jobject:
                    self._add_new_from_journal(jobject)
                    self.reload_data(self.reflection_data)
        finally:
            chooser.destroy()
            del chooser

    def _set_scroll_policy(self):
        if Gdk.Screen.width() < Gdk.Screen.height():
            self._scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC,
                                             Gtk.PolicyType.AUTOMATIC)
        else:
            self._scrolled_window.set_policy(Gtk.PolicyType.NEVER,
                                             Gtk.PolicyType.AUTOMATIC)

    def _remove_alert_cb(self, alert, response_id):
        self.remove_alert(alert)

    def _close_alert_cb(self, alert, response_id):
        self.remove_alert(alert)
        if response_id is Gtk.ResponseType.OK:
            self.close()

    def _setup_presence_service(self):
        ''' Setup the Presence Service. '''
        self.pservice = presenceservice.get_instance()

        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...'''
        if self.shared_activity is None:
            _logger.error('Failed to share or join activity ... \
                shared_activity is null in _shared_cb()')
            return

        self.initiating = True
        self._waiting_for_reflections = False
        _logger.debug('I am sharing...')

        self.conn = self.shared_activity.telepathy_conn
        self.tubes_chan = self.shared_activity.telepathy_tubes_chan
        self.text_chan = self.shared_activity.telepathy_text_chan

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        _logger.debug('This is my activity: making a tube...')
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
            SERVICE, {})

        self.sharing = True

    def _joined_cb(self, activity):
        ''' ...or join an exisiting share. '''
        if self.shared_activity is None:
            _logger.error('Failed to share or join activity ... \
                shared_activity is null in _shared_cb()')
            return

        if self._joined_alert is not None:
            self.remove_alert(self._joined_alert)
            self._joined_alert = None

        self.initiating = False
        self._waiting_for_reflections = True
        _logger.debug('I joined a shared activity.')

        self.conn = self.shared_activity.telepathy_conn
        self.tubes_chan = self.shared_activity.telepathy_tubes_chan
        self.text_chan = self.shared_activity.telepathy_text_chan

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal(
            'NewTube', self._new_tube_cb)

        _logger.debug('I am joining an activity: waiting for a tube...')
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
            reply_handler=self._list_tubes_reply_cb,
            error_handler=self._list_tubes_error_cb)

        self.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('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 == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[
                    telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)

            self.collab = CollabWrapper(self)
            self.collab.message.connect(self.event_received_cb)
            self.collab.setup()

            if self._waiting_for_reflections:
                self.send_event(JOIN_CMD, {})
                self._joined_alert = Alert()
                self._joined_alert.props.title = _('Please wait')
                self._joined_alert.props.msg = _('Requesting reflections...')
                self.add_alert(self._joined_alert)

    def event_received_cb(self, collab, buddy, msg):
        ''' Data is passed as tuples: cmd:text '''
        command = msg.get("command")
        payload = msg.get("payload")
        logging.debug(command)

        if command == JOIN_CMD:
            # Sharer needs to send reflections database to joiners.
            if self.initiating:
                # Send pictures first.
                for item in self.reflection_data:
                    if 'content' in item:
                        for content in item['content']:
                            if 'image' in content:
                                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
                                    content['image'], 120, 90)
                                if pixbuf is not None:
                                    data = utils.pixbuf_to_base64(pixbuf)
                                self.send_event(PICTURE_CMD,
                                    {"image": os.path.basename(content['image']),
                                     "data": data})
                data = json.dumps(self.reflection_data)
                self.send_event(SHARE_CMD, {"data": data})
        elif command == NEW_REFLECTION_CMD:
            self._reflect_window.add_new_reflection(payload)
        elif command == TITLE_CMD:
            obj_id = payload.get("obj_id")
            title = payload.get("title")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    self._reflect_window.update_title(obj_id, title)
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == TAG_CMD:
            obj_id = payload.get("obj_id")
            data = payload.get("data")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    self._reflect_window.update_tags(obj_id, data)
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == ACTIVITY_CMD:
            obj_id = payload.get("obj_id")
            bundle_id = payload.get("bundle_id")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    self._reflect_window.insert_activity(obj_id, bundle_id)
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == STAR_CMD:
            obj_id = payload.get("obj_id")
            stars = payload.get("stars")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    self._reflect_window.update_stars(obj_id, int(stars))
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == COMMENT_CMD:
            found_the_object = False
            # Receive a comment and associated reflection ID
            obj_id = payload.get("obj_id")
            nick = payload.get("nick")
            color = payload.get("color")
            comment = payload.get("comment")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    if not 'comments' in item:
                        item['comments'] = []
                    data = {'nick': nick, 'comment': comment, 'color': color}
                    item['comments'].append(data)
                    self._reflect_window.insert_comment(obj_id, data)
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == REFLECTION_CMD:
            found_the_object = False
            # Receive a reflection and associated reflection ID
            obj_id = payload.get("obj_id")
            reflection = payload.get("reflection")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    if not '' in item:
                        item['content'] = []
                    item['content'].append({'text': reflection})
                    self._reflect_window.insert_reflection(obj_id, reflection)
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == IMAGE_REFLECTION_CMD:
            found_the_object = False
            # Receive a picture reflection and associated reflection ID
            obj_id = payload.get("obj_id")
            basename = payload.get("basename")
            for item in self.reflection_data:
                if item['obj_id'] == obj_id:
                    found_the_object = True
                    if not '' in item:
                        item['content'] = []
                    item['content'].append(
                        {'image': os.path.join(self.tmp_path, basename)})
                    self._reflect_window.insert_picture(
                        obj_id, os.path.join(self.tmp_path, basename))
                    break
            if not found_the_object:
                logging.error('Could not find obj_id %s' % obj_id)
        elif command == PICTURE_CMD:
            # Receive a picture (MAYBE DISPLAY IT AS IT ARRIVES?)
            basename = payload.get("basename")
            data = payload.get("data")
            utils.base64_to_file(data, os.path.join(self.tmp_path, basename))
        elif command == SHARE_CMD:
            # Joiner needs to load reflection database.
            if not self.initiating:
                # Note that pictures should be received.
                self.reflection_data = payload
                self._reflect_window.load(self.reflection_data)
                self._waiting_for_reflections = False
                self.reset_cursor()
                if self._joined_alert is not None:
                    self.remove_alert(self._joined_alert)
                    self._joined_alert = None

    def send_event(self, command, data):
        ''' Send event through the tube. '''
        if hasattr(self, 'collab') and self.collab is not None:
            data["command"] = command
            self.collab.post(data)
示例#39
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_canvas = sugargame.canvas.PygameCanvas(self)
        self.game = physics.main(self)

        self.preview = None
        self._sample_window = None

        self._fixed = Gtk.Fixed()
        self._fixed.put(self.game_canvas, 0, 0)

        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._fixed)
        Gdk.Screen.get_default().connect("size-changed", self.__configure_cb)

        logging.debug(os.path.join(activity.get_activity_root(), "data", "data"))
        self.game_canvas.run_pygame(self.game.run)
        self.show_all()
        self._collab.setup()

    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"))
        self.game.run(True)

    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 Button")
        color.props.icon_name = "color"
        color.connect("notify::color", self.returnChosenColor)
        toolbar_box.toolbar.insert(color, -1)
        color.show()

        self.randomColor = ToggleToolButton("Random Color")
        self.randomColor.set_tooltip(_("Toggle random color"))
        self.randomColor.props.icon_name = "colorRandom"
        self.randomColor.connect("toggled", self.resetColors)
        toolbar_box.toolbar.insert(self.randomColor, -1)
        self.randomColor.set_active(True)
        self.randomColor.show()

        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 returnChosenColor(self, widget, pspec):
        """
        input: used as a connect function for Gtk Color widget The
        conversion that follows is required because Gtk Color is
        stored in RGB with the highest value being 65535, whereas this
        program stores color with the highest value being 255.
        """
        color = widget.get_color()
        red = (color.red / 65535.0) * 255
        green = (color.green / 65535.0) * 255
        blue = (color.blue / 65535.0) * 255
        objectColor = ((red), (green), (blue))
        self.randomColor.set_active(False)

        self.game.world.add.setColor(objectColor)

    def resetColors(self, button):
        self.game.world.add.resetColor()

    def can_close(self):
        self.preview = self.get_preview()
        self.game.loop = 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 not self.game.pygame_started:
            logging.debug("focus_event: pygame not yet initialized")
            return
        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)
                self.game.run(True)
            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):
        width = Gdk.Screen.width() / 4
        height = Gdk.Screen.height() / 4

        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)
            w = Gdk.Screen.width() / 2
            h = Gdk.Screen.height() / 2
            self._sample_window.set_size_request(w, h)
            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", lambda button: self._sample_box.hide())

            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._fixed.put(self._sample_box, width, height)

        if self._sample_window:
            # Remove and add again. Maybe its on portrait mode.
            self._fixed.remove(self._sample_box)
            self._fixed.put(self._sample_box, width, height)

        self._sample_box.show_all()

    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._sample_box.hide()
            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._sample_box.hide()

        self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.WATCH))
        GObject.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 GNUChessActivity(activity.Activity):
    ''' Gnuchess interface from Sugar '''
    def __init__(self, handle):
        ''' Initialize the toolbars and the gnuchess '''
        try:
            super(GNUChessActivity, self).__init__(handle)
        except dbus.exceptions.DBusException as e:
            _logger.error(str(e))

        self.game_data = None
        self.playing_white = True
        self.playing_mode = 'easy'
        self.playing_robot = True
        self.showing_game_history = False
        self._restoring = True
        self.stopwatch_running = False
        self.time_interval = None
        self.timer_panel_visible = False

        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.buddy = None
        self.opponent_colors = None

        self.hardware = get_hardware()
        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.old_cursor = self.get_window().get_cursor()

        self._gnuchess = Gnuchess(canvas,
                                  parent=self,
                                  path=activity.get_bundle_path(),
                                  colors=self.colors)

        self.connect('shared', self._shared_cb)
        self.connect('joined', self._joined_cb)
        self._restoring = False

        self.collab = CollabWrapper(self)
        self.collab.connect('message', self._message_cb)
        self.collab.connect('joined', self._joined_cb)
        self.collab.setup()

        # Send the nick to our opponent
        if not self.collab.props.leader:
            self.send_nick()
            # And let the sharer know we've joined
            self.send_join()

        if self.game_data is not None:  # 'saved_game' in self.metadata:
            self._restore()
        else:
            self._gnuchess.new_game()

    def set_data(self, data):
        pass

    def get_data(self):
        return None

    def _alert_cancel_cb(self, alert, response_id):
        self.remove_alert(alert)

    def restore_cursor(self):
        ''' No longer thinking, so restore standard cursor. '''
        self.get_window().set_cursor(self.old_cursor)

    def set_thinking_cursor(self):
        ''' Thinking, so set watch cursor. '''
        self.old_cursor = self.get_window().get_cursor()
        Watch = Gdk.Cursor(Gdk.CursorType.WATCH)
        self.get_window().set_cursor(Watch)

    def _setup_toolbars(self):
        ''' Setup the toolbars. '''
        self.max_participants = 2

        self.edit_toolbar = Gtk.Toolbar()
        self.view_toolbar = Gtk.Toolbar()
        self.adjust_toolbar = Gtk.Toolbar()
        self.custom_toolbar = Gtk.Toolbar()

        toolbox = ToolbarBox()

        activity_button = ActivityToolbarButton(self)
        toolbox.toolbar.insert(activity_button, 0)
        activity_button.show()

        edit_toolbar_button = ToolbarButton(label=_("Edit"),
                                            page=self.edit_toolbar,
                                            icon_name='toolbar-edit')
        self.edit_toolbar.show()
        toolbox.toolbar.insert(edit_toolbar_button, -1)
        edit_toolbar_button.show()

        view_toolbar_button = ToolbarButton(label=_("View"),
                                            page=self.view_toolbar,
                                            icon_name='toolbar-view')
        self.view_toolbar.show()
        toolbox.toolbar.insert(view_toolbar_button, -1)
        view_toolbar_button.show()

        adjust_toolbar_button = ToolbarButton(label=_('Adjust'),
                                              page=self.adjust_toolbar,
                                              icon_name='preferences-system')
        self.adjust_toolbar.show()
        toolbox.toolbar.insert(adjust_toolbar_button, -1)
        adjust_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

        adjust_toolbar_button.set_expanded(True)

        button_factory('edit-copy',
                       self.edit_toolbar,
                       self._copy_cb,
                       tooltip=_('Copy'),
                       accelerator='<Ctrl>c')

        button_factory('edit-paste',
                       self.edit_toolbar,
                       self._paste_cb,
                       tooltip=_('Paste'),
                       accelerator='<Ctrl>v')

        button_factory('view-fullscreen',
                       self.view_toolbar,
                       self.do_fullscreen_cb,
                       tooltip=_('Fullscreen'),
                       accelerator='<Alt>Return')

        button_factory('media-playback-start',
                       self.view_toolbar,
                       self._play_history_cb,
                       tooltip=_('Play game history'))

        self.history_button = button_factory('list-numbered',
                                             self.view_toolbar,
                                             self._show_history_cb,
                                             tooltip=_('Show game history'))

        separator_factory(self.view_toolbar, False, True)

        label_factory(self.view_toolbar, _('White: '))
        self.white_entry = entry_factory('',
                                         self.view_toolbar,
                                         tooltip=_("White's move"))

        separator_factory(self.view_toolbar, False, False)

        label_factory(self.view_toolbar, _('Black: '))
        self.black_entry = entry_factory('',
                                         self.view_toolbar,
                                         tooltip=_("Black's move"))

        separator_factory(self.view_toolbar, False, True)

        skin_button1 = radio_factory('white-knight',
                                     self.view_toolbar,
                                     self.do_default_skin_cb,
                                     tooltip=_('Default pieces'),
                                     group=None)

        skin_button2 = radio_factory('white-knight-sugar',
                                     self.view_toolbar,
                                     self.do_sugar_skin_cb,
                                     tooltip=_('Sugar-style pieces'),
                                     group=skin_button1)
        xocolors = XoColor(','.join(self.colors))
        icon = Icon(icon_name='white-knight-sugar', xo_color=xocolors)
        icon.show()
        skin_button2.set_icon_widget(icon)

        self.skin_button3 = radio_factory('white-knight-custom',
                                          self.view_toolbar,
                                          self.do_custom_skin_cb,
                                          tooltip=_('Custom pieces'),
                                          group=skin_button1)
        skin_button1.set_active(True)

        self.play_white_button = radio_factory('white-rook',
                                               self.adjust_toolbar,
                                               self._play_white_cb,
                                               group=None,
                                               tooltip=_('Play White'))

        self.play_black_button = radio_factory('black-rook',
                                               self.adjust_toolbar,
                                               self._play_black_cb,
                                               group=self.play_white_button,
                                               tooltip=_('Play Black'))

        self.play_white_button.set_active(True)

        separator_factory(self.adjust_toolbar, False, True)

        self.easy_button = radio_factory('beginner',
                                         self.adjust_toolbar,
                                         self._easy_cb,
                                         group=None,
                                         tooltip=_('Beginner'))

        self.hard_button = radio_factory('expert',
                                         self.adjust_toolbar,
                                         self._hard_cb,
                                         group=self.easy_button,
                                         tooltip=_('Expert'))

        self.easy_button.set_active(True)

        separator_factory(self.adjust_toolbar, False, True)

        self.robot_button = radio_factory(
            'robot',
            self.adjust_toolbar,
            self._robot_cb,
            group=None,
            tooltip=_('Play against the computer'))

        self.human_button = radio_factory('human',
                                          self.adjust_toolbar,
                                          self._human_cb,
                                          group=self.robot_button,
                                          tooltip=_('Play against a person'))

        separator_factory(self.adjust_toolbar, False, False)

        self.opponent = label_factory(self.adjust_toolbar, '')

        separator_factory(self.adjust_toolbar, False, True)

        self.timer_button = ToolButton('timer-0')
        self.timer_button.set_tooltip(_('Timer'))
        self.timer_button.connect('clicked', self._timer_button_cb)
        self.toolbar.insert(self.timer_button, -1)
        self._setup_timer_palette()
        self.timer_button.show()
        self.timer_button.set_sensitive(True)

        self.robot_button.set_active(True)

        button_factory('new-game',
                       self.toolbar,
                       self._new_gnuchess_cb,
                       tooltip=_('New game'))

        button_factory('edit-undo',
                       self.toolbar,
                       self._undo_cb,
                       tooltip=_('Undo'))

        button_factory('hint', self.toolbar, self._hint_cb, tooltip=_('Hint'))

        separator_factory(self.toolbar, False, False)
        self.status = label_factory(self.toolbar, '', width=150)
        self.status.set_label(_("It is White's move."))

        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()

        for piece in list(PIECES.keys()):
            for color in ['white', 'black']:
                button_factory('%s-%s' % (color, piece),
                               self.custom_toolbar,
                               self._reskin_cb,
                               cb_arg='%s_%s' % (color, piece),
                               tooltip=PIECES[piece][color])

    def do_default_skin_cb(self, button=None):
        for piece in list(PIECES.keys()):
            for color in ['white', 'black']:
                self._gnuchess.reskin_from_file(
                    '%s_%s' % (color, piece), '%s/icons/%s-%s.svg' %
                    (activity.get_bundle_path(), color, piece))

    def _black_pieces(self, colors):
        for piece in list(PIECES.keys()):
            self._gnuchess.reskin_from_svg('black_%s' % piece,
                                           colors,
                                           bw='#000000')

    def _white_pieces(self, colors):
        for piece in list(PIECES.keys()):
            self._gnuchess.reskin_from_svg('white_%s' % piece,
                                           colors,
                                           bw='#ffffff')

    def do_sugar_skin_cb(self, button=None):
        colors = self.colors
        if not self._gnuchess.we_are_sharing:
            self._black_pieces(colors)
            self._white_pieces(colors)
        else:
            if self.playing_white:
                self._white_pieces(colors)
                if self.opponent_colors is not None:
                    colors = self.opponent_colors
                self._black_pieces(colors)
            else:
                self._black_pieces(colors)
                if self.opponent_colors is not None:
                    colors = self.opponent_colors
                self._white_pieces(colors)

    def do_custom_skin_cb(self, button=None):
        for piece in list(PIECES.keys()):
            for color in ['white', 'black']:
                name = '%s_%s' % (color, piece)
                if name in self.metadata:
                    id = self.metadata[name]
                    jobject = datastore.get(id)
                    if jobject is not None and jobject.file_path is not None:
                        self._do_reskin(name, jobject.file_path)

    def _do_reskin(self, name, file_path):
        ''' If we are sharing, only reskin pieces of your color '''
        if self._gnuchess.we_are_sharing and self.buddy is not None:
            if 'white' in name and self.playing_white:
                pixbuf = self._gnuchess.reskin_from_file(name,
                                                         file_path,
                                                         return_pixbuf=True)
                self.send_piece(name, pixbuf)
            elif 'black' in name and not self.playing_white:
                pixbuf = self._gnuchess.reskin_from_file(name,
                                                         file_path,
                                                         return_pixbuf=True)
                self.send_piece(name, pixbuf)
        else:
            self._gnuchess.reskin_from_file(name, file_path)
        return

    def _timer_button_cb(self, button):
        if not self.timer_palette.is_up() and not self.timer_panel_visible:
            self.timer_palette.popup(immediate=True)
            self.timer_panel_visible = True
        else:
            self.timer_palette.popdown(immediate=True)
            self.timer_panel_visible = False

    def _setup_timer_palette(self):
        self.timer_values = [None, 30, 180, 600]
        self.timer_tooltips = [
            '', _('30 seconds'),
            _('3 minutes'),
            _('10 minutes')
        ]
        self.timer_labels = [
            _('Disabled'),
            # TRANS: Lightning chess 30 seconds between moves
            _('Lightning: %d seconds') % (30),
            # TRANS: Blitz chess 3 minutes between moves
            _('Blitz: %d minutes') % (3),
            # TRANS: Tournament chess 10 minutes between moves
            _('Tournament: %d minutes') % (10)
        ]
        self.timer_palette = self.timer_button.get_palette()

        for i, label in enumerate(self.timer_labels):
            menu_item = MenuItem(icon_name='timer-%d' % (i), text_label=label)
            menu_item.connect('activate', self._timer_selected_cb, i)
            self.timer_palette.menu.append(menu_item)
            menu_item.show()

    def _timer_selected_cb(self, button, index):
        game_already_started = 0
        if self.time_interval is not None:
            game_already_started = 1

        self.time_interval = self.timer_values[index]
        if self.time_interval is None:
            self.timer_button.set_tooltip(_('Timer off'))
        else:
            self.timer_button.set_tooltip(
                _('Timer') + ' (' + self.timer_tooltips[index] + ')')
            if game_already_started:
                self.alert_reset(self.timer_labels[index])
                if self.time_interval and self.time_interval is not None:
                    self.stopwatch(self.time_interval, self.alert_time)
                else:
                    GLib.source_remove(self.stopwatch_timer)
            else:
                self._gnuchess.new_game()

    def _reskin_cb(self, button, piece):
        object_id, file_path = self._choose_skin()
        if file_path is not None:
            self._do_reskin(piece, file_path)
            self.metadata[piece] = str(object_id)

    def do_fullscreen_cb(self, button):
        ''' Hide the Sugar toolbars. '''
        self.fullscreen()

    def _play_history_cb(self, button):
        self._gnuchess.play_game_history()
        return

    def _show_history_cb(self, button):
        self._gnuchess.show_game_history(self.tag_pairs())
        if self.showing_game_history:
            self.history_button.set_icon_name('checkerboard')
            self.history_button.set_tooltip(_('Show game board'))
        else:
            self.history_button.set_icon_name('list-numbered')
            self.history_button.set_tooltip(_('Show game history'))
        return

    def _copy_cb(self, *args):
        clipboard = Gtk.Clipboard()
        clipboard.set_text(self.tag_pairs() + self._gnuchess.copy_game())

    def _paste_cb(self, *args):
        ''' Pasting '''
        clipboard = Gtk.Clipboard()
        move_list = self._parse_move_list(clipboard.wait_for_text())
        if move_list is not None:
            self._gnuchess.restore_game(move_list)

    def _parse_move_list(self, text):
        ''' Take a standard game description and return a move list '''
        # Assuming of form ... 1. e4 e6 2. ...
        move_list = []
        found_one = False
        comment = False
        for move in text.split():
            if move[0] == '{':
                comment = True
            elif move[-1] == '}':
                comment = False
            if not comment:
                if move == '1.':
                    found_one = True
                    number = True
                    white = False
                elif found_one:
                    if not number:
                        number = True
                    elif not white:
                        move_list.append(move)
                        white = True
                    else:
                        move_list.append(move)
                        number = False
                        white = False
        return move_list

    def _undo_cb(self, *args):
        # No undo while sharing
        if self.collab.props.leader is None:
            self._gnuchess.undo()

    def _hint_cb(self, *args):
        self._gnuchess.hint()

    def _play_white_cb(self, *args):
        if not self.play_white_button.get_active():
            return
        if not self._restoring:
            self._new_game_alert('white')
        return True

    def _play_black_cb(self, *args):
        if not self.play_black_button.get_active():
            return
        if not self._restoring:
            self._new_game_alert('black')
        return True

    def _easy_cb(self, *args):
        if not self.easy_button.get_active():
            return
        if not self._restoring:
            self._new_game_alert('easy')
        return True

    def _hard_cb(self, *args):
        if not self.hard_button.get_active():
            return
        if not self._restoring:
            self._new_game_alert('hard')
        return True

    def _robot_cb(self, *args):
        if not self.robot_button.get_active():
            return
        if not self._restoring:
            self._new_game_alert('robot')
        return True

    def _human_cb(self, *args):
        if not self.human_button.get_active():
            return
        if not self._restoring:
            self._new_game_alert('human')
        return True

    def _new_gnuchess_cb(self, button=None):
        ''' Start a new gnuchess. '''
        self._new_game_alert('new')

    def tag_pairs(self):
        ''' Tag paris must be ascii '''
        if type(self.nick) is str:
            nick = self.nick.encode('ascii', 'replace')
        else:
            nick = self.nick
        if self.buddy is not None and type(self.buddy) is str:
            buddy = self.buddy.encode('ascii', 'replace')
        else:
            buddy = self.buddy
        if self.playing_white:
            white = nick
            if self.playing_robot:
                black = 'gnuchess (%s)' % (self.playing_mode)
            elif self._gnuchess.we_are_sharing and buddy is not None:
                black = buddy
            else:
                black = '?'
        else:
            black = nick
            if self.playing_robot:
                white = 'gnuchess (%s)' % (self.playing_mode)
            elif self._gnuchess.we_are_sharing and buddy is not None:
                white = buddy
            else:
                white = '?'
        return '[White "%s"]\n[Black "%s"]\n\n' % (white, black)

    def write_file(self, file_path):
        ''' Write the grid status to the Journal '''
        fd = open(file_path, 'w')
        fd.write(self.tag_pairs())
        fd.write(self._gnuchess.copy_game())
        fd.close()
        # self.metadata['saved_game'] = json_dump(self._gnuchess.save_game())
        if self.playing_white:
            self.metadata['playing_white'] = 'True'
        else:
            self.metadata['playing_white'] = 'False'
        self.metadata['playing_mode'] = self.playing_mode
        if self.playing_robot:
            self.metadata['playing_robot'] = 'True'
        else:
            self.metadata['playing_robot'] = 'False'
        '''
        self.metadata['timer_mode'] = self.timer.get_active_text()
        '''

    def read_file(self, file_path):
        ''' Read project file on relaunch '''
        fd = open(file_path, 'r')
        self.game_data = fd.read()
        fd.close()
        _logger.debug(self.game_data)

    def _restore(self):
        ''' Restore the gnuchess state from metadata '''
        if 'playing_white' in self.metadata:
            if self.metadata['playing_white'] == 'False':
                self.playing_white = False
                self.play_black_button.set_active(True)
        if 'playing_mode' in self.metadata:
            self.playing_mode = self.metadata['playing_mode']
            if self.playing_mode == 'hard':
                self.hard_button.set_active(True)
        if 'playing_robot' in self.metadata:
            if self.metadata['playing_robot'] == 'False':
                self.playing_robot = False
                self.human_button.set_active(True)
        '''
        if 'timer_mode' in self.metadata:
            self.timer_intervale.set_active(self.timer_list.index(
                                            self.metadata['timer_mode']))
        '''

        self._gnuchess.restore_game(self._parse_move_list(self.game_data))
        self.do_custom_skin_cb()

    def _choose_skin(self):
        ''' Select a skin from the Journal '''
        chooser = None
        name = None
        if hasattr(mime, 'GENERIC_TYPE_IMAGE'):
            if 'image/svg+xml' not in \
                    mime.get_generic_type(mime.GENERIC_TYPE_IMAGE).mime_types:
                mime.get_generic_type(
                    mime.GENERIC_TYPE_IMAGE).mime_types.append('image/svg+xml')
            chooser = ObjectChooser(parent=self,
                                    what_filter=mime.GENERIC_TYPE_IMAGE)
        else:
            try:
                chooser = ObjectChooser(parent=self, what_filter=None)
            except TypeError:
                chooser = ObjectChooser(
                    None, activity,
                    Gtk.DialogType.MODAL | Gtk.DialogType.DESTROY_WITH_PARENT)
        if chooser is not None:
            try:
                result = chooser.run()
                if result == Gtk.ResponseType.ACCEPT:
                    jobject = chooser.get_selected_object()
                    if jobject and jobject.file_path:
                        name = jobject.metadata['title']
            finally:
                jobject.destroy()
                chooser.destroy()
                del chooser
            if name is not None:
                return jobject.object_id, jobject.file_path
        else:
            return None, None

    def _take_button_action(self, button):
        if button == 'black':
            self.playing_white = False
        elif button == 'white':
            self.playing_white = True
        elif button == 'easy':
            self.playing_mode = 'easy'
        elif button == 'hard':
            self.playing_mode = 'hard'
        elif button == 'robot':
            self.playing_robot = True
        elif button == 'human':
            self.playing_robot = False
        self._gnuchess.new_game()

    def _no_action(self, button):
        if button == 'black':
            self.play_white_button.set_active(True)
            self.playing_white = True
        elif button == 'white':
            self.play_black_button.set_active(True)
            self.playing_white = False
        elif button == 'easy':
            self.hard_button.set_active(True)
            self.playing_mode = 'hard'
        elif button == 'hard':
            self.easy_button.set_active(True)
            self.playing_mode = 'easy'
        elif button == 'robot':
            self.human_button.set_active(True)
            self.playing_robot = False
        elif button == 'human':
            self.robot_button.set_active(True)
            self.playing_robot = True

    def _new_game_alert(self, button):
        ''' We warn the user if the game is in progress before loading
        a new game. '''
        if self.collab.props.leader is not None and not self.collab.props.leader:
            # joiner cannot push buttons
            self._restoring = True
            self._no_action(button)
            self._restoring = False
            return

        if len(self._gnuchess.move_list) == 0:
            self._take_button_action(button)
            return

        self._restoring = True
        alert = ConfirmationAlert()
        alert.props.title = _('Game in progress.')
        alert.props.msg = _('Do you want to start a new game?')

        def _new_game_alert_response_cb(alert, response_id, self, button):
            if response_id is Gtk.ResponseType.OK:
                self._take_button_action(button)
            elif response_id is Gtk.ResponseType.CANCEL:
                self._no_action(button)
            self._restoring = False
            self.remove_alert(alert)

        alert.connect('response', _new_game_alert_response_cb, self, button)
        self.add_alert(alert)
        alert.show()

    # Collaboration-related methods

    def _shared_cb(self, activity):
        ''' Either set up initial share...'''
        _logger.debug('shared')
        self.after_share_join(True)

    def _joined_cb(self, activity):
        ''' ...or join an exisiting share. '''
        _logger.debug('joined')
        self.after_share_join(False)
        self.send_nick()
        # And let the sharer know we've joined
        self.send_join()

    def after_share_join(self, sharer):
        self._gnuchess.set_sharing(True)
        self.restoring = True
        self.playing_robot = False
        self.human_button.set_active(True)
        self.robot_button.set_active(False)
        self.restoring = False

        self.easy_button.set_sensitive(False)
        self.hard_button.set_sensitive(False)
        self.robot_button.set_sensitive(False)

    def _setup_dispatch_table(self):
        ''' Associate tokens with commands. '''
        self._processing_methods = {
            'n': [self._receive_new_game, 'start a new game'],
            'm': [self._receive_move, 'make a move'],
            'r': [self._receive_restore, 'restore game state'],
            'N': [self._receive_nick, 'receive nick from opponent'],
            'C': [self._receive_colors, 'receive colors from opponent'],
            'j': [self._receive_join, 'receive new joiner'],
            'p': [self._receive_piece, 'receive new piece'],
        }

    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 game to joiner. '''
        if not self.collab.props.leader:
            return
        self.send_nick()
        if self.playing_white:
            _logger.debug('send_new_game: B')
            self.send_event("n", "B")
        else:
            _logger.debug('send_new_game: W')
            self.send_event("n", "W")

    def send_restore(self):
        ''' Send a new game to joiner. '''
        if not self.collab.props.leader:
            return
        _logger.debug('send_restore')
        self.send_event("r", self._gnuchess.copy_game())

    def send_join(self):
        _logger.debug('send_join')
        self.send_event("j", self.nick)

    def send_nick(self):
        _logger.debug('send_nick')
        self.send_event("N", self.nick)
        self.send_event("C", "%s,%s" % (self.colors[0], self.colors[1]))

    def alert_time(self):
        def _alert_response_cb(alert, response_id):
            self.remove_alert(alert)

        alert = NotifyAlert()
        alert.props.title = _('Time Up!')
        alert.props.msg = _('Your time is up.')
        alert.connect('response', _alert_response_cb)
        alert.show()
        self.add_alert(alert)

    def alert_reset(self, mode):
        def _alert_response_cb(alert, response_id):
            self.remove_alert(alert)

        alert = NotifyAlert()
        alert.props.title = _('Time Reset')
        alert.props.msg = _('The timer mode was reset to %s' % mode)
        alert.connect('response', _alert_response_cb)
        alert.show()
        self.add_alert(alert)

    def stopwatch(self, time, alert_callback):
        if self.stopwatch_running:
            GLib.source_remove(self.stopwatch_timer)
            time = self.time_interval
        self.stopwatch_timer = GLib.timeout_add(time * 1000, alert_callback)
        self.stopwatch_running = True

    def _receive_join(self, payload):
        _logger.debug('received_join %s' % (payload))
        if self.collab.props.leader:
            self.send_new_game()
            _logger.debug(self.game_data)
            if self.game_data is not None:
                self.send_restore()

    def _receive_nick(self, payload):
        _logger.debug('received_nick %s' % (payload))
        self.buddy = payload
        self.opponent.set_label(self.buddy)
        if self.collab.props.leader:
            self.send_nick()

    def _receive_colors(self, payload):
        _logger.debug('received_colors %s' % (payload))
        self.opponent_colors = payload.split(',')
        xocolors = XoColor(payload)
        icon = Icon(icon_name='human', xo_color=xocolors)
        icon.show()
        self.human_button.set_icon_widget(icon)
        self.human_button.show()

    def _receive_restore(self, payload):
        ''' Get game state from sharer. '''
        if self.collab.props.leader:
            return
        _logger.debug('received_restore %s' % (payload))
        self._gnuchess.restore_game(self._parse_move_list(payload))

    def _receive_move(self, payload):
        ''' Get a move from opponent. '''
        _logger.debug('received_move %s' % (payload))
        self._gnuchess.remote_move(payload)

    def _receive_new_game(self, payload):
        ''' Sharer can start a new gnuchess. '''
        _logger.debug('receive_new_game %s' % (payload))
        # The leader cannot receive new game
        if self.collab.props.leader:
            return
        self.send_nick()
        if payload == 'W':
            if not self.playing_white:
                self.restoring = True
                self.play_black_button.set_active(False)
                self.play_white_button.set_active(True)
                self.playing_white = True
        else:
            if self.playing_white:
                self.restoring = True
                self.play_white_button.set_active(False)
                self.play_black_button.set_active(True)
                self.playing_white = False
        self.robot_button.set_active(False)
        self.human_button.set_active(True)
        self.playing_robot = False
        self.restoring = False
        self._gnuchess.set_sharing(True)
        self._gnuchess.new_game()

    def send_event(self, command, payload):
        ''' Send event through the tube. '''
        if hasattr(self, 'collab') and self.collab is not None:
            self.collab.post(dict(command=command, payload=payload))

    # sharing pieces

    def send_piece(self, piece, pixbuf):
        _logger.debug('send_piece %s' % (piece))
        GLib.idle_add(self.send_event, ("p", self._dump(piece, pixbuf)))

    def _receive_piece(self, payload):
        piece, pixbuf = self._load(payload)
        _logger.debug('received_piece %s' % (piece))
        self._gnuchess.reskin(piece, pixbuf)

    def _dump(self, piece, pixbuf):
        ''' Dump data for sharing.'''
        _logger.debug('dumping %s' % (piece))
        data = [piece, pixbuf_to_base64(activity, pixbuf)]
        return json_dump(data)

    def _load(self, data):
        ''' Load game data from the journal. '''
        piece, pixbuf_data = json_load(data)
        pixbuf = base64_to_pixbuf(activity,
                                  pixbuf_data,
                                  width=self._gnuchess.scale,
                                  height=self._gnuchess.scale)
        return piece, pixbuf