def add_page(self, ebook_reader_tab_instance): # Add the "content" for the section. tab_name = ebook_reader_tab_instance.get_tab_label() tab_widget = ebook_reader_tab_instance.get_widget_to_attach_notebook_tab() tab_toolbar = ebook_reader_tab_instance.get_tab_toolbar() if tab_toolbar is not None: tab_widget.pack_start(tab_toolbar, False, False, 0) tab_widget.reorder_child(tab_toolbar, 0) self._tab_toolbars_list.append(tab_toolbar) self.notebook.add_page(tab_name, tab_widget) # Add the "(secondary) toolbar" for the section. icon_name = \ ebook_reader_tab_instance.get_tab_toolbar_icon_name() toolbar_button = ToolbarButton(page=None, icon_name=icon_name + '-select') self._toolbar_buttons_list.append(toolbar_button) toolbar_button.connect('clicked', self.load_corresponding_page, ebook_reader_tab_instance.get_pagenum()) ebook_reader_tab_instance.show() toolbar_button.set_tooltip(tab_name) self._toolbar_box._toolbar.insert(toolbar_button, -1) toolbar_button.show() self._toolbar_box.show_all()
def add_page(self, ebook_reader_tab_instance): # Add the "content" for the section. tab_name = ebook_reader_tab_instance.get_tab_label() tab_widget = ebook_reader_tab_instance.get_widget_to_attach_notebook_tab( ) tab_toolbar = ebook_reader_tab_instance.get_tab_toolbar() if tab_toolbar is not None: tab_widget.pack_start(tab_toolbar, False, False, 0) tab_widget.reorder_child(tab_toolbar, 0) self._tab_toolbars_list.append(tab_toolbar) self.notebook.add_page(tab_name, tab_widget) # Add the "(secondary) toolbar" for the section. icon_name = \ ebook_reader_tab_instance.get_tab_toolbar_icon_name() toolbar_button = ToolbarButton(page=None, icon_name=icon_name + '-select') self._toolbar_buttons_list.append(toolbar_button) toolbar_button.connect('clicked', self.load_corresponding_page, ebook_reader_tab_instance.get_pagenum()) ebook_reader_tab_instance.show() toolbar_button.set_tooltip(tab_name) self._toolbar_box._toolbar.insert(toolbar_button, -1) toolbar_button.show() self._toolbar_box.show_all()
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|%s' % (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) tube_conn = TubeConnection(self.conn, self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES], id, \ group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP]) self.chattube = ChatTube(tube_conn, self.initiating, \ self.event_received_cb) # Let the sharer know a new joiner has arrived. if self.waiting_for_fraction: self.send_event('j|%s' % (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|%s' % (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|%s' % (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|%s' % (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, entry): ''' Send event through the tube. ''' _logger.debug('sending event: %s', entry) if hasattr(self, 'chattube') and self.chattube is not None: self.chattube.SendText(entry) 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 FractionBounceActivity(activity.Activity): def __init__(self, handle): ''' Initiate activity. ''' super(FractionBounceActivity, self).__init__(handle) self.nick = profile.get_nick_name() if profile.get_color() is not None: self._colors = profile.get_color().to_string().split(',') else: self._colors = ['#A0FFA0', '#FF8080'] self.max_participants = 4 # sharing self._playing = True self._setup_toolbars() canvas = self._setup_canvas() # Read any custom fractions from the project metadata if 'custom' in self.metadata: custom = self.metadata['custom'] else: custom = None self._current_ball = 'soccerball' self._toolbar_was_expanded = False # Initialize the canvas self._bounce_window = Bounce(canvas, activity.get_bundle_path(), self) Gdk.Screen.get_default().connect('size-changed', self._configure_cb) # Restore any custom fractions if custom is not None: fractions = custom.split(',') for f in fractions: self._bounce_window.add_fraction(f) self._bounce_window.buddies.append(self.nick) self._player_colors = [self._colors] self._player_pixbufs = [ svg_str_to_pixbuf(generate_xo_svg(scale=0.8, colors=self._colors)) ] def on_activity_joined_cb(me): logging.debug('activity joined') self._player.set_from_pixbuf(self._player_pixbufs[0]) self.connect('joined', on_activity_joined_cb) def on_activity_shared_cb(me): logging.debug('activity shared') self._player.set_from_pixbuf(self._player_pixbufs[0]) self._label.set_label(_('Wait for others to join.')) self.connect('shared', on_activity_shared_cb) self._collab = CollabWrapper(self) if self.shared_activity: # We're joining if not self.get_shared(): self._label.set_label(_('Wait for the sharer to start.')) actions = { 'j': self._new_joiner, 'b': self._buddy_list, 'f': self._receive_a_fraction, 't': self._take_a_turn, 'l': self._buddy_left, } def on_message_cb(collab, buddy, msg): logging.debug('on_message_cb buddy %r msg %r' % (buddy, msg)) if self._playing: actions[msg.get('action')](msg.get('data')) self._collab.connect('message', on_message_cb) def on_joined_cb(collab, msg): logging.debug('joined') self.send_event('j', [self.nick, self._colors]) self._collab.connect('joined', on_joined_cb, 'joined') def on_buddy_joined_cb(collab, buddy, msg): logging.debug('on_buddy_joined_cb buddy %r' % (buddy.props.nick)) self._collab.connect('buddy_joined', on_buddy_joined_cb, 'buddy_joined') def on_buddy_left_cb(collab, buddy, msg): logging.debug('on_buddy_left_cb buddy %r' % (buddy.props.nick)) self._collab.connect('buddy_left', on_buddy_left_cb, 'buddy_left') self._collab.setup() def set_data(self, blob): pass def get_data(self): return None def close(self, **kwargs): aplay.close() activity.Activity.close(self, **kwargs) def _configure_cb(self, event): if Gdk.Screen.width() < 1024: self._label.set_size_request(275, -1) self._label.set_label('') self._separator.set_expand(False) else: self._label.set_size_request(500, -1) self._separator.set_expand(True) self._bounce_window.configure_cb(event) if self._toolbar_expanded(): self._bounce_window.bar.bump_bars('up') self._bounce_window.ball.ball.move_relative( (0, -style.GRID_CELL_SIZE)) def _toolbar_expanded(self): if self._activity_button.is_expanded(): return True elif self._custom_toolbar_button.is_expanded(): return True return False def _update_graphics(self, widget): # We need to catch opening and closing of toolbars and ignore # switching between open toolbars. if self._toolbar_expanded(): if not self._toolbar_was_expanded: self._bounce_window.bar.bump_bars('up') self._bounce_window.ball.ball.move_relative( (0, -style.GRID_CELL_SIZE)) self._toolbar_was_expanded = True else: if self._toolbar_was_expanded: self._bounce_window.bar.bump_bars('down') self._bounce_window.ball.ball.move_relative( (0, style.GRID_CELL_SIZE)) self._toolbar_was_expanded = False def _setup_toolbars(self): custom_toolbar = Gtk.Toolbar() toolbox = ToolbarBox() self._toolbar = toolbox.toolbar self._activity_button = ActivityToolbarButton(self) self._activity_button.connect('clicked', self._update_graphics) self._toolbar.insert(self._activity_button, 0) self._activity_button.show() self._custom_toolbar_button = ToolbarButton(label=_('Custom'), page=custom_toolbar, icon_name='view-source') self._custom_toolbar_button.connect('clicked', self._update_graphics) custom_toolbar.show() self._toolbar.insert(self._custom_toolbar_button, -1) self._custom_toolbar_button.show() self._load_standard_buttons(self._toolbar) self._separator = Gtk.SeparatorToolItem() self._separator.props.draw = False self._separator.set_expand(True) self._toolbar.insert(self._separator, -1) self._separator.show() stop_button = StopButton(self) stop_button.props.accelerator = _('<Ctrl>Q') self._toolbar.insert(stop_button, -1) stop_button.show() self.set_toolbar_box(toolbox) toolbox.show() self._load_custom_buttons(custom_toolbar) def _load_standard_buttons(self, toolbar): fraction_button = RadioToolButton(group=None) fraction_button.set_icon_name('fraction') fraction_button.set_tooltip(_('fractions')) fraction_button.connect('clicked', self._fraction_cb) toolbar.insert(fraction_button, -1) fraction_button.show() sector_button = RadioToolButton(group=fraction_button) sector_button.set_icon_name('sector') sector_button.set_tooltip(_('sectors')) sector_button.connect('clicked', self._sector_cb) toolbar.insert(sector_button, -1) sector_button.show() percent_button = RadioToolButton(group=fraction_button) percent_button.set_icon_name('percent') percent_button.set_tooltip(_('percents')) percent_button.connect('clicked', self._percent_cb) toolbar.insert(percent_button, -1) percent_button.show() self._player = Gtk.Image() self._player.set_from_pixbuf( svg_str_to_pixbuf( generate_xo_svg(scale=0.8, colors=['#282828', '#282828']))) self._player.set_tooltip_text(self.nick) toolitem = Gtk.ToolItem() toolitem.add(self._player) self._player.show() toolbar.insert(toolitem, -1) toolitem.show() self._label = Gtk.Label(_("Click the ball to start.")) self._label.set_line_wrap(True) if Gdk.Screen.width() < 1024: self._label.set_size_request(275, -1) else: self._label.set_size_request(500, -1) self.toolitem = Gtk.ToolItem() self.toolitem.add(self._label) self._label.show() toolbar.insert(self.toolitem, -1) self.toolitem.show() def _load_custom_buttons(self, toolbar): self.numerator = Gtk.Entry() self.numerator.set_text('') self.numerator.set_tooltip_text(_('numerator')) self.numerator.set_width_chars(3) toolitem = Gtk.ToolItem() toolitem.add(self.numerator) self.numerator.show() toolbar.insert(toolitem, -1) toolitem.show() label = Gtk.Label(' / ') toolitem = Gtk.ToolItem() toolitem.add(label) label.show() toolbar.insert(toolitem, -1) toolitem.show() self.denominator = Gtk.Entry() self.denominator.set_text('') self.denominator.set_tooltip_text(_('denominator')) self.denominator.set_width_chars(3) toolitem = Gtk.ToolItem() toolitem.add(self.denominator) self.denominator.show() toolbar.insert(toolitem, -1) toolitem.show() button = ToolButton('list-add') button.set_tooltip(_('add new fraction')) button.props.sensitive = True button.props.accelerator = 'Return' button.connect('clicked', self._add_fraction_cb) toolbar.insert(button, -1) button.show() separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(False) toolbar.insert(separator, -1) separator.show() button = ToolButton('soccerball') button.set_tooltip(_('choose a ball')) button.props.sensitive = True button.connect('clicked', self._button_palette_cb) toolbar.insert(button, -1) button.show() self._ball_palette = button.get_palette() button_grid = Gtk.Grid() row = 0 for ball in BALLDICT.keys(): if ball == 'custom': button = ToolButton('view-source') else: button = ToolButton(ball) button.connect('clicked', self._load_ball_cb, None, ball) eventbox = Gtk.EventBox() eventbox.connect('button_press_event', self._load_ball_cb, ball) label = Gtk.Label(BALLDICT[ball][0]) eventbox.add(label) label.show() button_grid.attach(button, 0, row, 1, 1) button.show() button_grid.attach(eventbox, 1, row, 1, 1) eventbox.show() row += 1 self._ball_palette.set_content(button_grid) button_grid.show() button = ToolButton('insert-picture') button.set_tooltip(_('choose a background')) button.props.sensitive = True button.connect('clicked', self._button_palette_cb) toolbar.insert(button, -1) button.show() self._bg_palette = button.get_palette() button_grid = Gtk.Grid() row = 0 for bg in BGDICT.keys(): if bg == 'custom': button = ToolButton('view-source') else: button = ToolButton(bg) button.connect('clicked', self._load_bg_cb, None, bg) eventbox = Gtk.EventBox() eventbox.connect('button_press_event', self._load_bg_cb, bg) label = Gtk.Label(BGDICT[bg][0]) eventbox.add(label) label.show() button_grid.attach(button, 0, row, 1, 1) button.show() button_grid.attach(eventbox, 1, row, 1, 1) eventbox.show() row += 1 self._bg_palette.set_content(button_grid) button_grid.show() def _button_palette_cb(self, button): palette = button.get_palette() if palette: if not palette.is_up(): palette.popup(immediate=True) else: palette.popdown(immediate=True) def can_close(self): # Let everyone know we are leaving... if hasattr(self, '_bounce_window') and \ self._bounce_window.we_are_sharing(): self._playing = False self.send_event('l', self.nick) return True def _setup_canvas(self): canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() return canvas def _load_bg_cb(self, widget, event, bg): if bg == 'custom': chooser(self, 'Image', self._new_background_from_journal) else: self._bounce_window.set_background(BGDICT[bg][1]) def _load_ball_cb(self, widget, event, ball): if ball == 'custom': chooser(self, 'Image', self._new_ball_from_journal) else: self._bounce_window.ball.new_ball( os.path.join(activity.get_bundle_path(), 'images', ball + '.svg')) self._bounce_window.set_background(BGDICT[BALLDICT[ball][1]][1]) self._current_ball = ball def _reset_ball(self): ''' If we switch back from sector mode, we need to restore the ball ''' if self._bounce_window.mode != 'sectors': return if self._current_ball == 'custom': # TODO: Reload custom ball self._current_ball = 'soccerball' self._bounce_window.ball.new_ball( os.path.join(activity.get_bundle_path(), 'images', self._current_ball + '.svg')) def _new_ball_from_journal(self, dsobject): ''' Load an image from the Journal. ''' self._bounce_window.ball.new_ball_from_image( dsobject.file_path, os.path.join(activity.get_activity_root(), 'tmp', 'custom.png')) def _new_background_from_journal(self, dsobject): ''' Load an image from the Journal. ''' self._bounce_window.new_background_from_image(None, dsobject=dsobject) def _fraction_cb(self, arg=None): ''' Set fraction mode ''' self._reset_ball() self._bounce_window.mode = 'fractions' def _percent_cb(self, arg=None): ''' Set percent mode ''' self._reset_ball() self._bounce_window.mode = 'percents' def _sector_cb(self, arg=None): ''' Set sector mode ''' self._bounce_window.mode = 'sectors' def _add_fraction_cb(self, arg=None): ''' Read entries and add a fraction to the list ''' try: numerator = int(self.numerator.get_text().strip()) except ValueError: self.numerator.set_text('NAN') numerator = 0 try: denominator = int(self.denominator.get_text().strip()) except ValueError: self.denominator.set_text('NAN') denominator = 1 if denominator == 0: self.denominator.set_text('ZDE') if numerator > denominator: numerator = 0 if numerator > 0 and denominator > 1: fraction = '%d/%d' % (numerator, denominator) self._bounce_window.add_fraction(fraction) if 'custom' in self.metadata: # Save to Journal self.metadata['custom'] = '%s,%s' % (self.metadata['custom'], fraction) else: self.metadata['custom'] = fraction self.alert( _('New fraction'), _('Your fraction, %s, has been added to the program' % (fraction))) def reset_label(self, label): ''' update the challenge label ''' self._label.set_label(label) def alert(self, title, text=None): alert = NotifyAlert(timeout=5) alert.props.title = title alert.props.msg = text self.add_alert(alert) alert.connect('response', self._alert_cancel_cb) alert.show() def _alert_cancel_cb(self, alert, response_id): self.remove_alert(alert) # Collaboration-related methods def _buddy_left(self, nick): self._label.set_label(nick + ' ' + _('has left.')) if self._collab.props.leader: self._remove_player(nick) self.send_event('b', [self._bounce_window.buddies, self._player_colors]) # Restart from sharer's turn self._bounce_window.its_my_turn() def _new_joiner(self, payload): ''' Someone has joined; sharer adds them to the buddy list. ''' [nick, colors] = payload self._label.set_label(nick + ' ' + _('has joined.')) if self._collab.props.leader: self._append_player(nick, colors) self.send_event('b', [self._bounce_window.buddies, self._player_colors]) if self._bounce_window.count == 0: # Haven't started yet... self._bounce_window.its_my_turn() def _remove_player(self, nick): if nick in self._bounce_window.buddies: i = self._bounce_window.buddies.index(nick) self._bounce_window.buddies.remove(nick) self._player_colors.remove(self._player_colors[i]) self._player_pixbufs.remove(self._player_pixbufs[i]) def _append_player(self, nick, colors): ''' Keep a list of players, their colors, and an XO pixbuf ''' if nick not in self._bounce_window.buddies: _logger.debug('appending %s to the buddy list', nick) self._bounce_window.buddies.append(nick) self._player_colors.append([str(colors[0]), str(colors[1])]) self._player_pixbufs.append( svg_str_to_pixbuf( generate_xo_svg(scale=0.8, colors=self._player_colors[-1]))) def _buddy_list(self, payload): '''Sharer sent the updated buddy list, so regenerate internal lists''' if not self._collab.props.leader: [buddies, colors] = payload self._bounce_window.buddies = buddies[:] self._player_colors = colors[:] self._player_pixbufs = [] for colors in self._player_colors: self._player_pixbufs.append( svg_str_to_pixbuf( generate_xo_svg( scale=0.8, colors=[str(colors[0]), str(colors[1])]))) def send_a_fraction(self, fraction): ''' Send a fraction to other players. ''' self.send_event('f', fraction) def _receive_a_fraction(self, payload): ''' Receive a fraction from another player. ''' self._bounce_window.play_a_fraction(payload) def _take_a_turn(self, nick): ''' If it is your turn, take it, otherwise, wait. ''' if nick == self.nick: # TODO: disambiguate self._bounce_window.its_my_turn() else: self._bounce_window.its_their_turn(nick) def send_event(self, action, data): ''' Send event through the tube. ''' _logger.debug('send_event action=%r data=%r' % (action, data)) self._collab.post({'action': action, 'data': data}) def set_player_on_toolbar(self, nick): ''' Display the XO icon of the player whose turn it is. ''' self._player.set_from_pixbuf( self._player_pixbufs[self._bounce_window.buddies.index(nick)]) self._player.set_tooltip_text(nick)
class 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 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 OneSupportActivity(activity.Activity): ''' An activity for sending bug reports ''' def __init__(self, handle): ''' Initialize the toolbar ''' try: super(OneSupportActivity, self).__init__(handle) except dbus.exceptions.DBusException as e: _logger.error(str(e)) self.connect('realize', self.__realize_cb) if hasattr(self, 'metadata') and 'font_size' in self.metadata: self.font_size = int(self.metadata['font_size']) else: self.font_size = 8 self.zoom_level = self.font_size / float(len(FONT_SIZES)) _logger.debug('zoom level is %f' % self.zoom_level) # _check_gconf_settings() # For debugging purposes self._setup_toolbars() self.modify_bg(Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color()) self.bundle_path = activity.get_bundle_path() self._copy_entry = None self._paste_entry = None self._webkit = None self._clipboard_text = '' self._fixed = None self._notify_transfer_status = False get_power_manager().inhibit_suspend() self._launch_task_master() def can_close(self): get_power_manager().restore_suspend() return True 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 _launch_task_master(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 = 3 * style.GRID_CELL_SIZE dy2 = 2 * style.GRID_CELL_SIZE self._progress_area = Gtk.Alignment.new(0.5, 0, 0, 0) self._progress_area.set_size_request(Gdk.Screen.width(), -1) self._fixed.put(self._progress_area, 0, Gdk.Screen.height() - dy2) self._progress_area.show() self._button_area = Gtk.Alignment.new(0.5, 0, 0, 0) self._button_area.set_size_request(Gdk.Screen.width(), -1) self._fixed.put(self._button_area, 0, Gdk.Screen.height() - dy1) 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, 0) self._scrolled_window.show() self._task_master = TaskMaster(self) self._task_master.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._task_master.set_events(Gdk.EventMask.KEY_PRESS_MASK) self._task_master.connect('key_press_event', self._task_master.keypress_cb) self._task_master.set_can_focus(True) self._task_master.grab_focus() self.set_canvas(self._fixed) self._fixed.show() self._task_master.task_master() 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_progress_area(self, widget): self._progress_area.add(widget) def _load_intro_graphics(self, file_name='generic-problem.html', message=None): center_in_panel = Gtk.Alignment.new(0.5, 0, 0, 0) url = os.path.join(self.bundle_path, 'html-content', file_name) graphics = Graphics() if message is None: graphics.add_uri('file://' + url) else: graphics.add_uri('file://' + url + '?MSG=' + utils.get_safe_text(message)) graphics.set_zoom_level(0.667) center_in_panel.add(graphics) graphics.show() self.set_canvas(center_in_panel) center_in_panel.show() 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._task_master.reload_graphics() def _resize_canvas(self, widget, fullscreen=False): # When a toolbar is expanded or collapsed, resize the canvas # to ensure that the progress bar is still visible. if hasattr(self, '_task_master'): if self.toolbar_expanded(): dy1 = 4 * style.GRID_CELL_SIZE dy2 = 3 * style.GRID_CELL_SIZE else: dy1 = 3 * style.GRID_CELL_SIZE dy2 = 2 * 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() - dy1) self._fixed.move(self._progress_area, 0, Gdk.Screen.height() - dy2) self._fixed.move(self._button_area, 0, Gdk.Screen.height() - dy1) 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 write_file(self, file_path): self.metadata['font_size'] = str(self.font_size) def _setup_toolbars(self): ''' Setup the toolbars. ''' self.max_participants = 1 # No sharing 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) self._zoom_in = ToolButton('zoom-in') self._zoom_in.set_tooltip(_('Increase size')) view_toolbar.insert(self._zoom_in, -1) self._zoom_in.show() self._zoom_in.connect('clicked', self._zoom_in_cb) self._zoom_out = ToolButton('zoom-out') self._zoom_out.set_tooltip(_('Decrease size')) view_toolbar.insert(self._zoom_out, -1) self._zoom_out.show() self._zoom_out.connect('clicked', self._zoom_out_cb) self._zoom_eq = ToolButton('zoom-original') self._zoom_eq.set_tooltip(_('Restore original size')) view_toolbar.insert(self._zoom_eq, -1) self._zoom_eq.show() self._zoom_eq.connect('clicked', self._zoom_eq_cb) self._set_zoom_buttons_sensitivity() 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) 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 __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 _set_zoom_buttons_sensitivity(self): if self.font_size < len(FONT_SIZES) - 1: self._zoom_in.set_sensitive(True) else: self._zoom_in.set_sensitive(False) if self.font_size > 0: self._zoom_out.set_sensitive(True) else: self._zoom_out.set_sensitive(False) if hasattr(self, '_scrolled_window'): self._set_scroll_policy() def _set_scroll_policy(self): if Gdk.Screen.width() < Gdk.Screen.height() or self.zoom_level > 0.667: self._scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) else: self._scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) def _zoom_eq_cb(self, button): self.font_size = 8 self.zoom_level = 0.667 self._set_zoom_buttons_sensitivity() self._task_master.reload_graphics() def _zoom_in_cb(self, button): if self.font_size < len(FONT_SIZES) - 1: self.font_size += 1 self.zoom_level *= 1.1 self._set_zoom_buttons_sensitivity() self._task_master.reload_graphics() def _zoom_out_cb(self, button): if self.font_size > 0: self.font_size -= 1 self.zoom_level /= 1.1 self._set_zoom_buttons_sensitivity() self._task_master.reload_graphics() 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 _reboot_alert_cb(self, alert, response_id): self.remove_alert(alert) if response_id is Gtk.ResponseType.OK: try: utils.reboot() except Exception as e: _logger.error('Cannot reboot: %s' % e) def _mount_added_cb(self, volume_monitor, device): _logger.error('mount added') if self.check_volume_data(): _logger.debug('launching') self._launcher() def _mount_removed_cb(self, volume_monitor, device): _logger.error('mount removed') if self.check_volume_data(): _logger.debug('launching') self._launcher()
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 BulletinBoard(): def __init__(self, cactivity): self._activity = cactivity self.is_active = False self.left = self._create_left_panel() self.right = self._create_right_panel() self.text_channel = None self.fixed = Gtk.Fixed() #self.button = BulletinButton() #self.button.connect("clicked", self._toggle) self.button = ToolbarButton() self.button.connect("clicked", self._toggle) self.button.props.icon_name = 'computer-xo' self.toolbar = BulletinToolbar() self.button.props.page = self.toolbar self.toolbar.toolitems.entry.connect('activate', self.entry_activate_cb) #self.share_button = ShareButton(self._activity) #self.share_button.private.props.active = False pserv = presenceservice.get_instance() self.owner = pserv.get_owner() # If any shared activity exists if self._activity.shared_activity: self._activity.connect('joined', self._joined_cb) # joining an activity if self._activity.get_shared(): # already joined the activity self._joined_cb(self._activity) else: if not self._activity.metadata or (self._activity.metadata.get('share-scope', activity.SCOPE_PRIVATE) == activity.SCOPE_PRIVATE): self._alert(_('Off-line'), _('Share, or invite someone.')) self._activity.connect('shared', self._shared_cb) def add_text(self, buddy, text): if not buddy: buddy = self.owner if type(buddy) is dict: nick = buddy['nick'] color = buddy['color'] else: nick = buddy.props.nick color = buddy.props.color try: color_stroke_html, color_fill_html = color.split(',') except ValueError: color_stroke_html, color_fill_html = ('#000000', '#888888') """ select box fill and stroke color""" color_stroke = style.Color(color_stroke_html) color_fill = style.Color(color_fill_html) """ select text color based on fill color """ color_fill_rgba = style.Color(color_fill_html).get_rgba() color_fill_gray = (color_fill_rgba[0] + color_fill_rgba[1] + color_fill_rgba[2]) / 3 """ black or white text color based on the intensity """ if color_fill_gray < 0.5: text_color = style.COLOR_WHITE else: text_color = style.COLOR_BLACK """ Right To Left languages """ if Pango.find_base_dir(nick, -1) == Pango.Direction.RTL: lang_rtl = True else: lang_rtl = False """ Generate Round Box with textbox and nick label """ mb = MessageBox() # OUTER ROUND BOX mb.background_color = color_fill mb.border_color = color_stroke name = ColorLabel(text=nick + " : ", color=text_color) name_v = Gtk.VBox() # COLOR LABEL name_v.pack_start(name, False, False, style.zoom(10)) mb.pack_start(name_v, False, False, style.zoom(10)) msg = TextBox(text_color, color_fill, lang_rtl) # TEXT BOX msg.add_text(text) """ Gtk.Fixed() container for ensuring fixed horizontal width """ inner = Gtk.Fixed() inner.set_hexpand(False) inner.set_vexpand(True) inner.add(msg) if len(text) > int((Gdk.Screen.width() / 4) / 10): msg.set_size_request(int(Gdk.Screen.width() / 4), 30) else: msg.set_wrap_mode(Gtk.WrapMode.NONE) vb = Gtk.VBox() vb.pack_start(inner, False, False, style.zoom(10)) mb.pack_start(vb, True, False, style.zoom(10)) logging.debug('nick = ' + nick + 'text = ' + text) """ Place randomly on screen """ mb.panel_width = style.GRID_CELL_SIZE + style.LINE_WIDTH mb.x = random.randint(mb.panel_width + style.LINE_WIDTH, Gdk.Screen.width() - mb.panel_width) mb.y = random.randint(mb.panel_width + style.LINE_WIDTH, Gdk.Screen.height() - mb.panel_width) ev = Gtk.EventBox() ev.add(mb) mb.show() logging.debug("x =" + str(mb.x) + "y= " + str(mb.y)) self.fixed.put(ev, mb.x, mb.y) if self.is_active: self.fixed.show_all() def _setup(self): """ Setup Chat Client """ logging.debug(" Chat setting up ") self.text_channel = TextChannelWrapper( self._activity.shared_activity.telepathy_text_chan, # use text channel wrapper self._activity.shared_activity.telepathy_conn) self.text_channel.set_received_callback(self._received_cb) # callback for received messaged self._alert(_('On-line'), _('Connected')) self._activity.shared_activity.connect('buddy-joined', self._buddy_joined_cb) self._activity.shared_activity.connect('buddy-left', self._buddy_left_cb) self.button.props.sensitive = True def _received_cb(self, buddy, text): if buddy: if type(buddy) is dict: nick = buddy['nick'] else: nick = buddy.props.nick else: nick = "???" logging.debug("message received from - " + nick) self.add_text(buddy, text) def _shared_cb(self, sender): logging.debug("Activity Shared ! ") self._setup() def _joined_cb(self, sender): if not self._activity.shared_activity: # Joined shared activity return logging.debug("Joined the session ") for buddy in self._activity.shared_activity.get_joined_buddies(): self._buddy_already_exists(buddy) self._setup() def _buddy_already_exists(self, buddy): if buddy == self.owner: # the user himself return self._alert(buddy.props.nick + ' ' + _('is here')) def _buddy_joined_cb(self, sender, buddy): if buddy == self.owner: # display buddy who joined return self._alert(buddy.props.nick + ' ' + _('has joined')) def _buddy_left_cb(self, sender, buddy): if buddy == self.owner: # display buddy who joined return self._alert(buddy.props.nick + ' ' + _('left')) def _toggle(self, button): if self.is_active is False: self.is_active = True else: self.is_active = False if self.is_active: self.left.show() self.right.show() self.fixed.show_all() #self.share_button.show() #self.box_button.show() #self.is_active = True else: self.left.hide() self.right.hide() self.fixed.hide() #self.box_button.hide() #self.share_button.hide() #self.is_active = False def _create_left_panel(self): panel = self._create_panel(Gtk.PositionType.LEFT) tray = ClipboardTray() panel.append(tray) tray.show() return panel def _create_right_panel(self): panel = self._create_panel(Gtk.PositionType.RIGHT) tray = FriendsTray() panel.append(tray) tray.show() return panel def _create_panel(self, orientation): panel = FrameWindow(orientation) return panel def _alert(self, title, text=None): alert = NotifyAlert(timeout=5) alert.props.title = title alert.props.msg = text self._activity.add_alert(alert) alert.connect('response', self._alert_cancel_cb) alert.show() def _alert_cancel_cb(self, alert, response_id): self._activity.remove_alert(alert) def entry_activate_cb(self, entry): text = entry.props.text logging.debug('Entry: ' + text) if text: self.add_text(self.owner, text) entry.props.text = '' if self.text_channel: self.text_channel.send(text) else: logging.debug('Failed to send message')
class LetterMatch(activity.Activity): ''' Learning the alphabet. Level1: A letter card and six picture cards appear; the user listens to the name of letter and then selects the matching picture. Level2: A picture card and six letter cards appear; the user listens to the name of the picture and then selects the matching letter. Customization toolbar allows loading of new images and sounds. ''' def __init__(self, handle): ''' Initialize the toolbars and the reading board ''' super(LetterMatch, self).__init__(handle) self.datapath = get_path(activity, 'instance') self.image_id = None self.audio_id = None if 'LANG' in os.environ: language = os.environ['LANG'][0:2] elif 'LANGUAGE' in os.environ: language = os.environ['LANGUAGE'][0:2] else: language = 'es' # default to Spanish # FIXME: find some reasonable default situation language = 'es' self.letter = None self.activity_path = activity.get_bundle_path() self._lessons_path = os.path.join(self.activity_path, 'lessons', language) self._images_path = os.path.join(self.activity_path, 'images', language) self._sounds_path = os.path.join(self.activity_path, 'sounds', language) self.data_from_journal = {} if 'data_from_journal' in self.metadata: self.data_from_journal = json.loads( str(self.metadata['data_from_journal'])) self._setup_toolbars() self.canvas = Gtk.DrawingArea() self.canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.canvas.modify_bg(Gtk.StateType.NORMAL, Gdk.color_parse("#000000")) self.canvas.show() self.set_canvas(self.canvas) self.mode = 'letter' self._page = Page(self.canvas, self._lessons_path, self._images_path, self._sounds_path, parent=self) def _setup_toolbars(self): self.max_participants = 1 # no sharing toolbox = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() separator = Gtk.SeparatorToolItem() toolbox.toolbar.insert(separator, -1) self.set_toolbar_box(toolbox) toolbox.show() primary_toolbar = toolbox.toolbar custom_toolbar = ToolbarBox() self.custom_toolbar_button = ToolbarButton(icon_name='view-source', page=custom_toolbar) self.custom_toolbar_button.connect('clicked', self._customization_toolbar_cb) toolbox.toolbar.insert(self.custom_toolbar_button, -1) button = radio_factory('letter', primary_toolbar, self._letter_cb, tooltip=_('listen to the letter names')) radio_factory('picture', primary_toolbar, self._picture_cb, tooltip=_('listen to the letter names'), group=button) self.status = label_factory(primary_toolbar, '', width=300) self.letter_entry = None self.image_button = button_factory('load_image_from_journal', custom_toolbar.toolbar, self._choose_image_from_journal_cb, tooltip=_("Import Image")) self.sound_button = button_factory('load_audio_from_journal', custom_toolbar.toolbar, self._choose_audio_from_journal_cb, tooltip=_("Import Audio")) container = Gtk.ToolItem() self.letter_entry = Gtk.Entry() self.letter_entry.set_max_length(1) self.letter_entry.set_width_chars(3) # because 1 char looks funny self.letter_entry.connect('changed', self._set_letter) self.letter_entry.set_sensitive(False) self.letter_entry.show() container.add(self.letter_entry) container.show_all() custom_toolbar.toolbar.insert(container, -1) self.add_button = button_factory('list-add', custom_toolbar.toolbar, self._copy_to_journal, tooltip=_("Add")) self.add_button.set_sensitive(False) separator_factory(primary_toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() def _set_letter(self, event): ''' Process letter in text entry ''' text = self.letter_entry.get_text().strip() if text and len(text) > 0: if len(text) != 1: text = text[0].upper() text = text.upper() self.letter_entry.set_text(text) self.letter = text if self.letter in self.data_from_journal: self.data_from_journal[self.letter].append( (self.image_id, self.audio_id)) else: self.data_from_journal[self.letter] = \ [(self.image_id, self.audio_id)] self.add_button.set_sensitive(True) else: self.letter = None self.add_button.set_sensitive(False) def _copy_to_journal(self, event): ''' Callback from add button on customization toolbar ''' # Save data to journal and load it into the card database self.metadata['data_from_journal'] = json.dumps(self.data_from_journal) self._page.load_from_journal(self.data_from_journal) # Reinit the preview, et al. after add self.preview_image.hide() self._init_preview() self.image_id = None self.object_id = None self.letter_entry.set_text('') self.letter_entry.set_sensitive(False) self.add_button.set_sensitive(False) def _init_preview(self): ''' Set up customization toolbar, preview image ''' w = int(self._page._card_width) h = int(self._page._card_height) x = int(self._page._grid_x_offset + w + 12) y = int(self._page._grid_y_offset + 40) pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._images_path, '../drawing.png'), w, h) self.status.set_text( _('Please choose image and audio objects from the Journal.')) self._page._hide_cards() if not hasattr(self, 'preview_image'): self.preview_image = Sprite(self._page._sprites, 0, 0, pixbuf) else: self.preview_image.set_image(pixbuf) self.preview_image.move((x, y)) self.preview_image.set_layer(100) self._page._canvas.disconnect(self._page.button_press_event_id) self._page._canvas.disconnect(self._page.button_release_event_id) self._page.button_press_event_id = \ self._page._canvas.connect('button-press-event', self._preview_press_cb) self._page.button_release_event_id = \ self._page._canvas.connect('button-release-event', self._dummy_cb) def _customization_toolbar_cb(self, event): ''' Override toolbar button behavior ''' if self.custom_toolbar_button.is_expanded(): self._init_preview() else: if self.mode == 'letter': self._letter_cb() else: self._picture_cb() def _preview_press_cb(self, win, event): ''' Preview image was clicked ''' self._choose_image_from_journal_cb(None) def _dummy_cb(self, win, event): '''Does nothing''' return True def _choose_audio_from_journal_cb(self, event): ''' Create a chooser for audio objects ''' self.add_button.set_sensitive(False) self.letter_entry.set_sensitive(False) self.image_button.set_sensitive(False) self.sound_button.set_sensitive(False) self.audio_id = None chooser = ObjectChooser(what_filter=mime.GENERIC_TYPE_AUDIO) result = chooser.run() if result == Gtk.ResponseType.ACCEPT: jobject = chooser.get_selected_object() self.audio_id = str(jobject._object_id) self.image_button.set_sensitive(True) self.sound_button.set_sensitive(True) if self.image_id and self.audio_id: self.letter_entry.set_sensitive(True) self._page._canvas.disconnect(self._page.button_press_event_id) self._page.button_press_event_id = \ self._page._canvas.connect('button-press-event', self._play_audio_cb) def _play_audio_cb(self, win, event): ''' Preview audio ''' if self.audio_id: play_audio_from_file(datastore.get(self.audio_id).get_file_path()) def _choose_image_from_journal_cb(self, event): ''' Create a chooser for image objects ''' self.add_button.set_sensitive(False) self.letter_entry.set_sensitive(False) self.image_button.set_sensitive(False) self.sound_button.set_sensitive(False) self.image_id = None chooser = ObjectChooser(what_filter=mime.GENERIC_TYPE_IMAGE) result = chooser.run() if result == Gtk.ResponseType.ACCEPT: jobject = chooser.get_selected_object() self.image_id = str(jobject._object_id) x = self._page._grid_x_offset + self._page._card_width + 12 y = self._page._grid_y_offset + 40 w = self._page._card_width h = self._page._card_height pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( jobject.get_file_path(), w, h) self.preview_image.set_image(pixbuf) self.preview_image.move((x, y)) self.preview_image.set_layer(100) self.image_button.set_sensitive(True) self.sound_button.set_sensitive(True) if self.image_id and self.audio_id: self.letter_entry.set_sensitive(True) self._page._canvas.disconnect(self._page.button_press_event_id) self._page.button_press_event_id = \ self._page._canvas.connect('button-press-event', self._play_audio_cb) def _cleanup_preview(self): ''' No longer previewing, so hide image and clean up callbacks ''' if hasattr(self, 'preview_image'): self.preview_image.hide() self._page._canvas.disconnect(self._page.button_press_event_id) self._page._canvas.disconnect(self._page.button_release_event_id) self._page.button_press_event_id = \ self._canvas.connect("button-press-event", self._page._button_press_cb) self._page.button_release_event_id = \ self._canvas.connect("button-release-event", self._page._button_release_cb) def _letter_cb(self, event=None): ''' Click on card to hear the letter name ''' if self.custom_toolbar_button.is_expanded(): self.custom_toolbar_button.set_expanded(False) self._cleanup_preview() self.mode = 'letter' self.status.set_text( _('Click on the picture that matches the letter.')) if hasattr(self, '_page'): self._page.new_page() return def _picture_cb(self, event=None): ''' Click on card to hear the letter name ''' if self.custom_toolbar_button.is_expanded(): self.custom_toolbar_button.set_expanded(False) self._cleanup_preview() self.mode = 'picture' self.status.set_text( _('Click on the letter that matches the picture.')) if hasattr(self, '_page'): self._page.new_page() return def write_file(self, file_path): ''' Write status to the Journal ''' if not hasattr(self, '_page'): return self.metadata['page'] = str(self._page.current_card)
class TrainingActivity(activity.Activity): ''' A series of training exercises ''' transfer_started_signal = GObject.Signal('started', arg_types=([])) transfer_progressed_signal = GObject.Signal('progressed', arg_types=([])) transfer_completed_signal = GObject.Signal('completed', arg_types=([])) transfer_failed_signal = GObject.Signal('failed', arg_types=([])) def __init__(self, handle): ''' Initialize the toolbars and the game board ''' try: super(TrainingActivity, self).__init__(handle) except dbus.exceptions.DBusException as e: _logger.error(str(e)) self.connect('realize', self.__realize_cb) self.connect('started', self.__transfer_started_cb) self.connect('progressed', self.__transfer_progressed_cb) self.connect('completed', self.__transfer_completed_cb) self.connect('failed', self.__transfer_failed_cb) self.volume_monitor = Gio.VolumeMonitor.get() self.volume_monitor.connect('mount-added', self._mount_added_cb) self.volume_monitor.connect('mount-removed', self._mount_removed_cb) if hasattr(self, 'metadata') and 'font_size' in self.metadata: self.font_size = int(self.metadata['font_size']) else: self.font_size = 8 self.zoom_level = self.font_size / float(len(FONT_SIZES)) _logger.debug('zoom level is %f' % self.zoom_level) _check_gconf_settings() # For debugging purposes self._setup_toolbars() self.modify_bg(Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color()) self.bundle_path = activity.get_bundle_path() self.volume_data = [] self.help_palette = None self.help_panel_visible = False self._copy_entry = None self._paste_entry = None self._webkit = None self._clipboard_text = '' self._fixed = None self._notify_transfer_status = False if self._load_extension() and self.check_volume_data(): self._launcher() def _launcher(self): get_power_manager().inhibit_suspend() # We are resuming the activity or we are launching a new instance? # * Is there a data file to sync on the USB key? # * Do we create a new data file on the USB key? path = self._check_for_USB_data() if path is None: self._launch_task_master() elif self._sync_data_from_USB(path): self._copy_data_from_USB() # Flash a welcome back screen. self._load_intro_graphics(file_name='welcome-back.html') GObject.timeout_add(1500, self._launch_task_master) def can_close(self): get_power_manager().restore_suspend() return True def busy_cursor(self): self._old_cursor = self.get_window().get_cursor() self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH)) def reset_cursor(self): if hasattr(self, '_old_cursor'): self.get_window().set_cursor(self._old_cursor) def check_volume_data(self): # Before we begin (and before each task), # we need to find any and all USB keys # and any and all training-data files on them. _logger.debug(utils.get_volume_paths()) self.volume_data = [] for path in utils.get_volume_paths(): os.path.basename(path) self.volume_data.append( {'basename': os.path.basename(path), 'files': utils.look_for_training_data(path), 'sugar_path': os.path.join(self.get_activity_root(), 'data'), 'usb_path': path}) _logger.debug(self.volume_data[-1]) # (1) We require a USB key if len(self.volume_data) == 0: _logger.error('NO USB KEY INSERTED') alert = ConfirmationAlert() alert.props.title = _('USB key required') alert.props.msg = _('You must insert a USB key before launching ' 'this activity.') alert.connect('response', self._remove_alert_cb) self.add_alert(alert) self._load_intro_graphics(file_name='insert-usb.html') return False # (2) Only one USB key if len(self.volume_data) > 1: _logger.error('MULTIPLE USB KEYS INSERTED') alert = ConfirmationAlert() alert.props.title = _('Multiple USB keys found') alert.props.msg = _('Only one USB key must be inserted while ' 'running this program.\nPlease remove any ' 'additional USB keys before launching ' 'this activity.') alert.connect('response', self._remove_alert_cb) self.add_alert(alert) self._load_intro_graphics(message=alert.props.msg) return False volume = self.volume_data[0] # (3) At least 10MB of free space if utils.is_full(volume['usb_path'], required=_MINIMUM_SPACE): _logger.error('USB IS FULL') alert = ConfirmationAlert() alert.props.title = _('USB key is full') alert.props.msg = _('No room on USB') alert.connect('response', self._close_alert_cb) self.add_alert(alert) self._load_intro_graphics(message=alert.props.msg) return False # (4) File is read/write if not utils.is_writeable(volume['usb_path']): _logger.error('CANNOT WRITE TO USB') alert = ConfirmationAlert() alert.props.title = _('Cannot write to USB') alert.props.msg = _('USB key seems to be read-only.') alert.connect('response', self._close_alert_cb) self.add_alert(alert) self._load_intro_graphics(message=alert.props.msg) return False # (5) Only one set of training data per USB key # We expect UIDs to formated as XXXX-XXXX # We need to make sure we have proper UIDs associated with # the USBs and the files on them match the UID. # (a) If there are no files, we will assign the UID based on the # volume path; # (b) If there is one file with a valid UID, we use that UID; if len(volume['files']) == 0: volume['uid'] = 'training-data-%s' % \ utils.format_volume_name(volume['basename']) _logger.debug('No training data found. Using UID %s' % volume['uid']) return True elif len(volume['files']) == 1: volume['uid'] = 'training-data-%s' % volume['files'][0][-9:] _logger.debug('Training data found. Using UID %s' % volume['uid']) return True else: _logger.error('MULTIPLE TRAINING-DATA FILES FOUND') alert = ConfirmationAlert() alert.props.title = _('Multiple training-data files found.') alert.props.msg = _('There can only be one set of training ' 'data per USB key.') alert.connect('response', self._close_alert_cb) self.add_alert(alert) self._load_intro_graphics(message=alert.props.msg) return False def _check_for_USB_data(self): usb_path = os.path.join(self.volume_data[0]['usb_path'], self.volume_data[0]['uid']) if os.path.exists(usb_path): return usb_path else: return None def _sync_data_from_USB(self, usb_data_path=None): # We need to sync up file on USB with file on disk, # but only if the email addresses match. Otherwise, # raise an error. if usb_data_path is not None: usb_data = {} if os.path.exists(usb_data_path): fd = open(usb_data_path, 'r') json_data = fd.read() fd.close() if len(json_data) > 0: try: usb_data = json.loads(json_data) except ValueError as e: _logger.error('Cannot load USB data: %s' % e) else: _logger.error('Cannot find USB data: %s' % usb_data_path) sugar_data_path = os.path.join( self.volume_data[0]['sugar_path'], self.volume_data[0]['uid']) sugar_data = {} if os.path.exists(sugar_data_path): fd = open(sugar_data_path, 'r') json_data = fd.read() fd.close() if len(json_data) > 0: try: sugar_data = json.loads(json_data) except ValueError as e: _logger.error('Cannot load Sugar data: %s' % e) else: _logger.error('Cannot find Sugar data: %s' % sugar_data_path) # First, check to make sure email_address matches if EMAIL_UID in usb_data: usb_email = usb_data[EMAIL_UID] else: usb_email = None if EMAIL_UID in sugar_data: sugar_email = sugar_data[EMAIL_UID] else: sugar_email = None if usb_email != sugar_email: if usb_email is None and sugar_email is not None: _logger.warning('Using email address from Sugar: %s' % sugar_email) usb_data[EMAIL_UID] = sugar_email elif usb_email is not None and sugar_email is None: _logger.warning('Using email address from USB: %s' % usb_email) sugar_data[EMAIL_UID] = usb_email elif usb_email is None and sugar_email is None: _logger.warning('No email address found') else: # FIX ME: We need to resolve this, but for right now, punt. alert = ConfirmationAlert() alert.props.title = _('Data mismatch') alert.props.msg = _('Are you %(usb)s or %(sugar)s?' % {'usb': usb_email, 'sugar': sugar_email}) alert.connect('response', self._close_alert_cb) self.add_alert(alert) self._load_intro_graphics(message=alert.props.msg) return False def count_completed(data): count = 0 for key in data: if isinstance(data[key], dict) and \ 'completed' in data[key] and \ data[key]['completed']: count += 1 return count # The database with the most completed tasks takes precedence. if count_completed(usb_data) >= count_completed(sugar_data): _logger.debug('data sync: USB data takes precedence') data_one = usb_data data_two = sugar_data else: _logger.debug('data sync: Sugar data takes precedence') data_one = sugar_data data_two = usb_data # Copy completed tasks from one to two for key in data_one: if isinstance(data_one[key], dict) and \ 'completed' in data_one[key] and \ data_one[key]['completed']: data_two[key] = data_one[key] # Copy completed tasks from two to one for key in data_two: if isinstance(data_two[key], dict) and \ 'completed' in data_two[key] and \ data_two[key]['completed']: data_one[key] = data_two[key] # Copy incompleted tasks from one to two for key in data_one: if isinstance(data_one[key], dict) and \ (not 'completed' in data_one[key] or not data_one[key]['completed']): data_two[key] = data_one[key] # Copy incompleted tasks from two to one for key in data_two: if isinstance(data_two[key], dict) and \ (not 'completed' in data_two[key] or not data_two[key]['completed']): data_one[key] = data_two[key] # Copy name, email_address, current_task... for key in data_one: if not isinstance(data_one[key], dict): data_two[key] = data_one[key] for key in data_two: if not isinstance(data_two[key], dict): data_one[key] = data_two[key] # Finally, write to the USB and ... json_data = json.dumps(data_one) fd = open(usb_data_path, 'w') fd.write(json_data) fd.close() # ...save a shadow copy in Sugar fd = open(sugar_data_path, 'w') fd.write(json_data) fd.close() return True else: _logger.error('No data to sync on USB') return False def _copy_data_from_USB(self): usb_path = self._check_for_USB_data() if usb_path is not None: try: subprocess.call(['cp', usb_path, self.volume_data[0]['sugar_path']]) except OSError as e: _logger.error('Could not copy %s to %s: %s' % ( usb_path, self.volume_data[0]['sugar_path'], e)) else: _logger.error('No data found on USB') 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 elif hasattr(self, 'progress_toolbar_button') and \ self.progress_toolbar_button.is_expanded(): return True def _launch_task_master(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 = 3 * style.GRID_CELL_SIZE dy2 = 2 * style.GRID_CELL_SIZE self._progress_area = Gtk.Alignment.new(0.5, 0, 0, 0) self._progress_area.set_size_request(Gdk.Screen.width(), -1) self._fixed.put(self._progress_area, 0, Gdk.Screen.height() - dy2) self._progress_area.show() self._button_area = Gtk.Alignment.new(0.5, 0, 0, 0) self._button_area.set_size_request(Gdk.Screen.width(), -1) self._fixed.put(self._button_area, 0, Gdk.Screen.height() - dy1) 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, 0) self._scrolled_window.show() self._task_master = TaskMaster(self) self._task_master.show() # Now that we have the tasks, we can build the progress toolbar and # help panel. self._build_progress_toolbar() self._help_panel = HelpPanel(self._task_master) self.help_palette = self._help_button.get_palette() self.help_palette.set_content(self._help_panel) self._help_panel.show() self._help_button.set_sensitive(True) 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._task_master.set_events(Gdk.EventMask.KEY_PRESS_MASK) self._task_master.connect('key_press_event', self._task_master.keypress_cb) self._task_master.set_can_focus(True) self._task_master.grab_focus() self.set_canvas(self._fixed) self._fixed.show() self.completed = False self._update_completed_sections() self._check_connected_task_status() self._task_master.task_master() def load_graphics_area(self, widget): self._graphics_area.add(widget) def load_button_area(self, widget): self._button_area.add(widget) def load_progress_area(self, widget): self._progress_area.add(widget) def _load_intro_graphics(self, file_name='generic-problem.html', message=None): center_in_panel = Gtk.Alignment.new(0.5, 0, 0, 0) url = os.path.join(self.bundle_path, 'html-content', file_name) graphics = Graphics() if message is None: graphics.add_uri('file://' + url) else: graphics.add_uri('file://' + url + '?MSG=' + utils.get_safe_text(message)) graphics.set_zoom_level(0.667) center_in_panel.add(graphics) graphics.show() self.set_canvas(center_in_panel) center_in_panel.show() 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._task_master.reload_graphics() def _resize_canvas(self, widget, fullscreen=False): # When a toolbar is expanded or collapsed, resize the canvas # to ensure that the progress bar is still visible. if hasattr(self, '_task_master'): if self.toolbar_expanded(): dy1 = 4 * style.GRID_CELL_SIZE dy2 = 3 * style.GRID_CELL_SIZE else: dy1 = 3 * style.GRID_CELL_SIZE dy2 = 2 * 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() - dy1) self._fixed.move(self._progress_area, 0, Gdk.Screen.height() - dy2) self._fixed.move(self._button_area, 0, Gdk.Screen.height() - dy1) self.help_panel_visible = 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 write_file(self, file_path): # Only write if we have a valid USB/data file to work with. if len(self.volume_data) == 1 and \ len(self.volume_data[0]['files']) == 1: self.metadata[TRAINING_DATA_UID] = self.volume_data[0]['uid'] # We may have failed before getting to init of taskmaster if hasattr(self, '_task_master'): self._task_master.write_task_data( 'current_task', self._task_master.current_task) self.update_activity_title() email = self._task_master.read_task_data(EMAIL_UID) if email is None: email = '' self.metadata[TRAINING_DATA_EMAIL] = email name = self._task_master.read_task_data(NAME_UID) if name is None: name = '' self.metadata[TRAINING_DATA_FULLNAME] = name self.metadata['font_size'] = str(self.font_size) def update_activity_title(self): name = self._task_master.read_task_data(NAME_UID) if name is not None: bundle_name = activity.get_bundle_name() self.metadata['title'] = _('%(name)s %(bundle)s Activity') % \ {'name': name, 'bundle': bundle_name} def _setup_toolbars(self): ''' Setup the toolbars. ''' self.max_participants = 1 # No sharing 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) self._zoom_in = ToolButton('zoom-in') self._zoom_in.set_tooltip(_('Increase size')) view_toolbar.insert(self._zoom_in, -1) self._zoom_in.show() self._zoom_in.connect('clicked', self._zoom_in_cb) self._zoom_out = ToolButton('zoom-out') self._zoom_out.set_tooltip(_('Decrease size')) view_toolbar.insert(self._zoom_out, -1) self._zoom_out.show() self._zoom_out.connect('clicked', self._zoom_out_cb) self._zoom_eq = ToolButton('zoom-original') self._zoom_eq.set_tooltip(_('Restore original size')) view_toolbar.insert(self._zoom_eq, -1) self._zoom_eq.show() self._zoom_eq.connect('clicked', self._zoom_eq_cb) self._set_zoom_buttons_sensitivity() 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) self._progress_toolbar = Gtk.Toolbar() self.progress_toolbar_button = ToolbarButton( page=self._progress_toolbar, label=_('Check progress'), icon_name='check-progress') self.progress_toolbar_button.connect('clicked', self._resize_canvas) self._toolbox.toolbar.insert(self.progress_toolbar_button, -1) self._progress_toolbar.show() self.progress_toolbar_button.show() self._help_button = ToolButton('toolbar-help') self._help_button.set_tooltip(_('Help')) self._help_button.props.accelerator = '<Ctrl>H' self._toolbox.toolbar.insert(self._help_button, -1) self._help_button.show() self._help_button.connect('clicked', self._help_cb) self._help_button.set_sensitive(False) self._help_button.palette_invoker.props.lock_palette = True self.transfer_button = ToolButton('transfer') self.transfer_button.set_tooltip(_('Training data upload status')) self._toolbox.toolbar.insert(self.transfer_button, -1) self.transfer_button.connect('clicked', self._transfer_cb) self.transfer_button.hide() self.progress_label = Gtk.Label() self.progress_label.set_line_wrap(True) self.progress_label.set_size_request(300, -1) self.progress_label.set_use_markup(True) toolitem = Gtk.ToolItem() toolitem.add(self.progress_label) self.progress_label.show() self._toolbox.toolbar.insert(toolitem, -1) toolitem.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 _build_progress_toolbar(self): self._progress_buttons = [] progress = self._task_master.get_completed_sections() for section_index in range(self._task_master.get_number_of_sections()): icon = self._task_master.get_section_icon(section_index) if section_index in progress: icon = icon + '-white' else: icon = icon + '-grey' name = self._task_master.get_section_name(section_index) if section_index == 0: group = None else: group = self._progress_buttons[0] self._progress_buttons.append(RadioToolButton(group=group)) self._progress_buttons[-1].set_icon_name(icon) self._progress_buttons[-1].set_tooltip(name) self._progress_toolbar.insert(self._progress_buttons[-1], -1) self._progress_buttons[-1].show() self._progress_buttons[-1].connect( 'clicked', self._jump_to_section_cb, section_index) self._radio_buttons_live = False section_index, task_index = \ self._task_master.get_section_and_task_index() self._progress_buttons[section_index].set_active(True) self._radio_buttons_live = True def _check_connected_task_status(self): ''' We only want to turn on notifications if we expect connectivity ''' task = self._task_master.uid_to_task(GET_CONNECTED_TASK) self.set_notify_transfer_status(task.is_completed()) def _update_completed_sections(self): progress = self._task_master.get_completed_sections() for section in range(self._task_master.get_number_of_sections()): icon = self._task_master.get_section_icon(section) if section in progress: icon = icon + '-white' else: icon = icon + '-grey' self._progress_buttons[section].set_icon_name(icon) self._radio_buttons_live = False section_index, task_index = \ self._task_master.get_section_and_task_index() self._progress_buttons[section_index].set_active(True) self._radio_buttons_live = True def mark_section_as_complete(self, section): icon = self._task_master.get_section_icon(section) + '-white' self._progress_buttons[section].set_icon_name(icon) if section < self._task_master.get_number_of_sections() - 1: self._radio_buttons_live = False self._progress_buttons[section + 1].set_active(True) self._radio_buttons_live = True def set_notify_transfer_status(self, state): _logger.debug('Setting transfer status to %s' % (str(state))) self._notify_transfer_status = state def _update_transfer_button(self, icon_name, tooltip): self.transfer_button.set_icon_name(icon_name) self.transfer_button.set_tooltip(tooltip) if self._notify_transfer_status: self.transfer_button.show() else: self.transfer_button.hide() def _transfer_cb(self, button): ''' Hide the button to dismiss notification ''' self.transfer_button.set_tooltip(_('Training data upload status')) self.transfer_button.hide() def __transfer_started_cb(self, widget): self._update_transfer_button('transfer', _('Data transfer started')) def __transfer_progressed_cb(self, widget): self._update_transfer_button('transfer', _('Data transfer progressing')) def __transfer_completed_cb(self, widget): self._update_transfer_button('transfer-complete', _('Data transfer completed')) def __transfer_failed_cb(self, widget): self._update_transfer_button('transfer-failed', _('Data transfer failed')) 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 _set_zoom_buttons_sensitivity(self): if self.font_size < len(FONT_SIZES) - 1: self._zoom_in.set_sensitive(True) else: self._zoom_in.set_sensitive(False) if self.font_size > 0: self._zoom_out.set_sensitive(True) else: self._zoom_out.set_sensitive(False) if hasattr(self, '_scrolled_window'): self._set_scroll_policy() def _set_scroll_policy(self): if Gdk.Screen.width() < Gdk.Screen.height() or self.zoom_level > 0.667: self._scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) else: self._scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) def _zoom_eq_cb(self, button): self.font_size = 8 self.zoom_level = 0.667 self._set_zoom_buttons_sensitivity() self._task_master.reload_graphics() def _zoom_in_cb(self, button): if self.font_size < len(FONT_SIZES) - 1: self.font_size += 1 self.zoom_level *= 1.1 self._set_zoom_buttons_sensitivity() self._task_master.reload_graphics() def _zoom_out_cb(self, button): if self.font_size > 0: self.font_size -= 1 self.zoom_level /= 1.1 self._set_zoom_buttons_sensitivity() self._task_master.reload_graphics() def _jump_to_section_cb(self, button, section_index): if self._radio_buttons_live: if self._task_master.requirements_are_met(section_index, 0): uid = self._task_master.section_and_task_to_uid(section_index) self._task_master.current_task = \ self._task_master.uid_to_task_number(uid) self._task_master.reload_graphics() else: section_index, task_index = \ self._task_master.get_section_and_task_index() self._progress_buttons[section_index].set_active(True) def _help_cb(self, button): # title, help_file = self._task_master.get_help_info() # _logger.debug('%s: %s' % (title, help_file)) # if not hasattr(self, 'window_xid'): # self.window_xid = self.get_window().get_xid() # if title is not None and help_file is not None: # self.viewhelp = ViewHelp(title, help_file, self.window_xid) # self.viewhelp.show() try: self._help_panel.set_connected( utils.nm_status() == 'network-wireless-connected') except Exception as e: _logger.error('Could not read NM status: %s' % (e)) self._help_panel.set_connected(False) if self.help_palette: # FIXME: is_up() is always returning False, so we # "debounce" using help_panel_visible. if not self.help_palette.is_up() and not self.help_panel_visible: self.help_palette.popup( immediate=True, state=self.help_palette.SECONDARY) self.help_panel_visible = True else: self.help_palette.popdown(immediate=True) self.help_panel_visible = False self._help_button.set_expanded(False) def add_badge(self, msg, icon="training-trophy", name="One Academy"): sugar_icons = os.path.join(os.path.expanduser('~'), '.icons') if not os.path.exists(sugar_icons): try: subprocess.call(['mkdir', sugar_icons]) except OSError as e: _logger.error('Could not mkdir %s, %s' % (sugar_icons, e)) badge = { 'icon': icon, 'from': name, 'message': msg } # Use icons from html-content directory since default colors are # intended for white background. icon_dir = os.path.join(self.bundle_path, 'html-content', 'images') icon_path = os.path.join(icon_dir, icon + '.svg') try: subprocess.call(['cp', icon_path, sugar_icons]) except OSError as e: _logger.error('Could not copy %s to %s, %s' % (icon_path, sugar_icons, e)) if 'comments' in self.metadata: comments = json.loads(self.metadata['comments']) comments.append(badge) self.metadata['comments'] = json.dumps(comments) else: self.metadata['comments'] = json.dumps([badge]) def _load_extension(self): if not WEBSERVICES_AVAILABLE: _logger.error('Webservices not available on this version of Sugar') self._webservice_alert(_('Sugar upgrade required.')) return False extensions_path = os.path.join(os.path.expanduser('~'), '.sugar', 'default', 'extensions') webservice_path = os.path.join(extensions_path, 'webservice') sugarservices_path = os.path.join(self.bundle_path, 'sugarservices') init_path = os.path.join(self.bundle_path, 'sugarservices', '__init__.py') if not os.path.exists(extensions_path): try: subprocess.call(['mkdir', extensions_path]) except OSError as e: _logger.error('Could not mkdir %s, %s' % (extensions_path, e)) self._webservice_alert(_('System error.')) return False if not os.path.exists(webservice_path): try: subprocess.call(['mkdir', webservice_path]) except OSError as e: _logger.error('Could not mkdir %s, %s' % (webservice_path, e)) self._webservice_alert(_('System error.')) return False try: subprocess.call(['cp', init_path, webservice_path]) except OSError as e: _logger.error('Could not cp %s to %s, %s' % (init_path, webservice_path, e)) self._webservice_alert(_('System error.')) return False install = False if not os.path.exists(os.path.join(webservice_path, 'sugarservices')): _logger.error('SugarServices webservice not found. Installing...') install = True elif utils.get_sugarservices_version() < \ _REQUIRED_SUGARSERVICES_VERSION: _logger.error('Found old SugarServices version. Installing...') install = True if install: try: subprocess.call(['cp', '-r', sugarservices_path, webservice_path]) except OSError as e: _logger.error('Could not copy %s to %s, %s' % (sugarservices_path, webservice_path, e)) self._webservice_alert(_('System error.')) return False alert = ConfirmationAlert() alert.props.title = _('Restart required') alert.props.msg = _('We needed to install some software on your ' 'system.\nSugar must be restarted before ' 'sugarservices can commence.') alert.connect('response', self._reboot_alert_cb) self.add_alert(alert) self._load_intro_graphics(file_name='restart.html') return not install def _webservice_alert(self, message): alert = ConfirmationAlert() alert.props.title = message alert.props.msg = _('We are unable to install some software on your ' 'system.\nSugar must be upgraded before this ' 'activity can be run.') alert.connect('response', self._close_alert_cb) self.add_alert(alert) self._load_intro_graphics(message=message) 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 _reboot_alert_cb(self, alert, response_id): self.remove_alert(alert) if response_id is Gtk.ResponseType.OK: try: utils.reboot() except Exception as e: _logger.error('Cannot reboot: %s' % e) def _mount_added_cb(self, volume_monitor, device): _logger.error('mount added') if self.check_volume_data(): _logger.debug('launching') self._launcher() def _mount_removed_cb(self, volume_monitor, device): _logger.error('mount removed') if self.check_volume_data(): _logger.debug('launching') self._launcher()