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()
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 __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 _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 __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]))})
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 __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()
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]))})
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)
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)
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)
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()
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)
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)
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()
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(',')
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)
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)
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)
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)
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)
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)
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)
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