Beispiel #1
0
class DetailToolbox(ToolbarBox):
    __gsignals__ = {
        'volume-error': (GObject.SignalFlags.RUN_FIRST, None, ([str, str])),
    }

    def __init__(self, journalactivity):
        ToolbarBox.__init__(self)
        self._journalactivity = journalactivity
        self._metadata = None
        self._temp_file_path = None
        self._refresh = None

        self._resume = ToolButton('activity-start')
        self._resume.connect('clicked', self._resume_clicked_cb)
        self.toolbar.insert(self._resume, -1)
        self._resume.show()
        self._resume_menu = None

        color = profile.get_color()
        self._copy = ToolButton()
        icon = Icon(icon_name='edit-copy', xo_color=color)
        self._copy.set_icon_widget(icon)
        icon.show()
        self._copy.set_tooltip(_('Copy to'))
        self._copy.connect('clicked', self._copy_clicked_cb)
        self.toolbar.insert(self._copy, -1)
        self._copy.show()

        self._duplicate = ToolButton()
        icon = Icon(icon_name='edit-duplicate', xo_color=color)
        self._duplicate.set_icon_widget(icon)
        self._duplicate.set_tooltip(_('Duplicate'))
        self._duplicate.connect('clicked', self._duplicate_clicked_cb)
        self.toolbar.insert(self._duplicate, -1)

        if accountsmanager.has_configured_accounts():
            self._refresh = ToolButton('entry-refresh')
            self._refresh.set_tooltip(_('Refresh'))
            self._refresh.connect('clicked', self._refresh_clicked_cb)
            self.toolbar.insert(self._refresh, -1)
            self._refresh.show()

        separator = Gtk.SeparatorToolItem()
        self.toolbar.insert(separator, -1)
        separator.show()

        erase_button = ToolButton('list-remove')
        erase_button.set_tooltip(_('Erase'))
        erase_button.connect('clicked', self._erase_button_clicked_cb)
        self.toolbar.insert(erase_button, -1)
        erase_button.show()

    def set_metadata(self, metadata):
        self._metadata = metadata
        self._refresh_copy_palette()
        self._refresh_duplicate_palette()
        self._refresh_refresh_palette()
        self._refresh_resume_palette()

    def _resume_clicked_cb(self, button):
        if not misc.can_resume(self._metadata):
            palette = self._resume.get_palette()
            palette.popup(immediate=True)

        misc.resume(self._metadata,
                    alert_window=journalwindow.get_journal_window())

    def _copy_clicked_cb(self, button):
        button.palette.popup(immediate=True)

    def _refresh_clicked_cb(self, button):
        button.palette.popup(immediate=True)

    def _duplicate_clicked_cb(self, button):
        try:
            model.copy(self._metadata, '/')
        except IOError as e:
            logging.exception('Error while copying the entry.')
            self.emit('volume-error',
                      _('Error while copying the entry. %s') % (e.strerror, ),
                      _('Error'))

    def _erase_button_clicked_cb(self, button):
        alert = Alert()
        erase_string = _('Erase')
        alert.props.title = erase_string
        alert.props.msg = _('Do you want to permanently erase \"%s\"?') \
            % self._metadata['title']
        icon = Icon(icon_name='dialog-cancel')
        alert.add_button(Gtk.ResponseType.CANCEL, _('Cancel'), icon)
        icon.show()
        ok_icon = Icon(icon_name='dialog-ok')
        alert.add_button(Gtk.ResponseType.OK, erase_string, ok_icon)
        ok_icon.show()
        alert.connect('response', self.__erase_alert_response_cb)
        journalwindow.get_journal_window().add_alert(alert)
        alert.show()

    def __erase_alert_response_cb(self, alert, response_id):
        journalwindow.get_journal_window().remove_alert(alert)
        if response_id is Gtk.ResponseType.OK:
            registry = bundleregistry.get_registry()
            bundle = misc.get_bundle(self._metadata)
            if bundle is not None and registry.is_installed(bundle):
                registry.uninstall(bundle)
            model.delete(self._metadata['uid'])

    def _resume_menu_item_activate_cb(self, menu_item, service_name):
        misc.resume(self._metadata,
                    service_name,
                    alert_window=journalwindow.get_journal_window())

    def _refresh_copy_palette(self):
        palette = self._copy.get_palette()

        # Use the menu defined in CopyMenu
        for menu_item in palette.menu.get_children():
            palette.menu.remove(menu_item)
            menu_item.destroy()

        CopyMenuBuilder(self._journalactivity, self.__get_uid_list_cb,
                        self.__volume_error_cb, palette.menu)

    def __get_uid_list_cb(self):
        return [self._metadata['uid']]

    def _refresh_duplicate_palette(self):
        color = misc.get_icon_color(self._metadata)
        self._copy.get_icon_widget().props.xo_color = color
        if self._metadata['mountpoint'] == '/':
            self._duplicate.show()
            icon = self._duplicate.get_icon_widget()
            icon.props.xo_color = color
            icon.show()
        else:
            self._duplicate.hide()

    def _refresh_refresh_palette(self):
        if self._refresh is None:
            return

        color = misc.get_icon_color(self._metadata)
        self._refresh.get_icon_widget().props.xo_color = color

        palette = self._refresh.get_palette()
        for menu_item in palette.menu.get_children():
            palette.menu.remove(menu_item)

        for account in accountsmanager.get_configured_accounts():
            if hasattr(account, 'get_shared_journal_entry'):
                entry = account.get_shared_journal_entry()
                if hasattr(entry, 'get_refresh_menu'):
                    menu = entry.get_refresh_menu()
                    palette.menu.append(menu)
                    menu.set_metadata(self._metadata)

    def __volume_error_cb(self, menu_item, message, severity):
        self.emit('volume-error', message, severity)

    def _refresh_resume_palette(self):
        if self._metadata.get('activity_id', ''):
            # TRANS: Action label for resuming an activity.
            self._resume.set_tooltip(_('Resume'))
        else:
            # TRANS: Action label for starting an entry.
            self._resume.set_tooltip(_('Start'))

        palette = self._resume.get_palette()

        if self._resume_menu is not None:
            self._resume_menu.destroy()

        self._resume_menu = PaletteMenuBox()
        palette.set_content(self._resume_menu)
        self._resume_menu.show()

        for activity_info in misc.get_activities(self._metadata):
            menu_item = PaletteMenuItem(file_name=activity_info.get_icon(),
                                        text_label=activity_info.get_name())
            menu_item.connect('activate', self._resume_menu_item_activate_cb,
                              activity_info.get_bundle_id())
            self._resume_menu.append_item(menu_item)
            menu_item.show()

        if not misc.can_resume(self._metadata):
            self._resume.set_tooltip(_('No activity to start entry'))
Beispiel #2
0
class DetailToolbox(ToolbarBox):
    __gsignals__ = {
        'volume-error': (GObject.SignalFlags.RUN_FIRST, None,
                         ([str, str])),
    }

    def __init__(self, journalactivity):
        ToolbarBox.__init__(self)
        self._journalactivity = journalactivity
        self._metadata = None
        self._temp_file_path = None
        self._refresh = None

        self._resume = ToolButton('activity-start')
        self._resume.connect('clicked', self._resume_clicked_cb)
        self.toolbar.insert(self._resume, -1)
        self._resume.show()
        self._resume_menu = None

        color = profile.get_color()
        self._copy = ToolButton()
        icon = Icon(icon_name='edit-copy', xo_color=color)
        self._copy.set_icon_widget(icon)
        icon.show()
        self._copy.set_tooltip(_('Copy to'))
        self._copy.connect('clicked', self._copy_clicked_cb)
        self.toolbar.insert(self._copy, -1)
        self._copy.show()

        self._duplicate = ToolButton()
        icon = Icon(icon_name='edit-duplicate', xo_color=color)
        self._duplicate.set_icon_widget(icon)
        self._duplicate.set_tooltip(_('Duplicate'))
        self._duplicate.connect('clicked', self._duplicate_clicked_cb)
        self.toolbar.insert(self._duplicate, -1)

        if accountsmanager.has_configured_accounts():
            self._refresh = ToolButton('entry-refresh')
            self._refresh.set_tooltip(_('Refresh'))
            self._refresh.connect('clicked', self._refresh_clicked_cb)
            self.toolbar.insert(self._refresh, -1)
            self._refresh.show()

        separator = Gtk.SeparatorToolItem()
        self.toolbar.insert(separator, -1)
        separator.show()

        erase_button = ToolButton('list-remove')
        erase_button.set_tooltip(_('Erase'))
        erase_button.connect('clicked', self._erase_button_clicked_cb)
        self.toolbar.insert(erase_button, -1)
        erase_button.show()

    def set_metadata(self, metadata):
        self._metadata = metadata
        self._refresh_copy_palette()
        self._refresh_duplicate_palette()
        self._refresh_refresh_palette()
        self._refresh_resume_palette()

    def _resume_clicked_cb(self, button):
        if not misc.can_resume(self._metadata):
            palette = self._resume.get_palette()
            palette.popup(immediate=True)

        misc.resume(self._metadata,
                    alert_window=journalwindow.get_journal_window())

    def _copy_clicked_cb(self, button):
        button.palette.popup(immediate=True)

    def _refresh_clicked_cb(self, button):
        button.palette.popup(immediate=True)

    def _duplicate_clicked_cb(self, button):
        try:
            model.copy(self._metadata, '/')
        except IOError as e:
            logging.exception('Error while copying the entry.')
            self.emit('volume-error',
                      _('Error while copying the entry. %s') % (e.strerror, ),
                      _('Error'))

    def _erase_button_clicked_cb(self, button):
        alert = Alert()
        erase_string = _('Erase')
        alert.props.title = erase_string
        alert.props.msg = _('Do you want to permanently erase \"%s\"?') \
            % self._metadata['title']
        icon = Icon(icon_name='dialog-cancel')
        alert.add_button(Gtk.ResponseType.CANCEL, _('Cancel'), icon)
        icon.show()
        ok_icon = Icon(icon_name='dialog-ok')
        alert.add_button(Gtk.ResponseType.OK, erase_string, ok_icon)
        ok_icon.show()
        alert.connect('response', self.__erase_alert_response_cb)
        journalwindow.get_journal_window().add_alert(alert)
        alert.show()

    def __erase_alert_response_cb(self, alert, response_id):
        journalwindow.get_journal_window().remove_alert(alert)
        if response_id is Gtk.ResponseType.OK:
            registry = bundleregistry.get_registry()
            bundle = misc.get_bundle(self._metadata)
            if bundle is not None and registry.is_installed(bundle):
                registry.uninstall(bundle)
            model.delete(self._metadata['uid'])

    def _resume_menu_item_activate_cb(self, menu_item, service_name):
        misc.resume(self._metadata, service_name,
                    alert_window=journalwindow.get_journal_window())

    def _refresh_copy_palette(self):
        palette = self._copy.get_palette()

        # Use the menu defined in CopyMenu
        for menu_item in palette.menu.get_children():
            palette.menu.remove(menu_item)
            menu_item.destroy()

        CopyMenuBuilder(self._journalactivity, self.__get_uid_list_cb,
                        self.__volume_error_cb, palette.menu)

    def __get_uid_list_cb(self):
        return [self._metadata['uid']]

    def _refresh_duplicate_palette(self):
        color = misc.get_icon_color(self._metadata)
        self._copy.get_icon_widget().props.xo_color = color
        if self._metadata['mountpoint'] == '/':
            self._duplicate.show()
            icon = self._duplicate.get_icon_widget()
            icon.props.xo_color = color
            icon.show()
        else:
            self._duplicate.hide()

    def _refresh_refresh_palette(self):
        if self._refresh is None:
            return

        color = misc.get_icon_color(self._metadata)
        self._refresh.get_icon_widget().props.xo_color = color

        palette = self._refresh.get_palette()
        for menu_item in palette.menu.get_children():
            palette.menu.remove(menu_item)

        for account in accountsmanager.get_configured_accounts():
            if hasattr(account, 'get_shared_journal_entry'):
                entry = account.get_shared_journal_entry()
                if hasattr(entry, 'get_refresh_menu'):
                    menu = entry.get_refresh_menu()
                    palette.menu.append(menu)
                    menu.set_metadata(self._metadata)

    def __volume_error_cb(self, menu_item, message, severity):
        self.emit('volume-error', message, severity)

    def _refresh_resume_palette(self):
        if self._metadata.get('activity_id', ''):
            # TRANS: Action label for resuming an activity.
            self._resume.set_tooltip(_('Resume'))
        else:
            # TRANS: Action label for starting an entry.
            self._resume.set_tooltip(_('Start'))

        palette = self._resume.get_palette()

        if self._resume_menu is not None:
            self._resume_menu.destroy()

        self._resume_menu = PaletteMenuBox()
        palette.set_content(self._resume_menu)
        self._resume_menu.show()

        for activity_info in misc.get_activities(self._metadata):
            menu_item = PaletteMenuItem(file_name=activity_info.get_icon(),
                                        text_label=activity_info.get_name())
            menu_item.connect('activate', self._resume_menu_item_activate_cb,
                              activity_info.get_bundle_id())
            self._resume_menu.append_item(menu_item)
            menu_item.show()

        if not misc.can_resume(self._metadata):
            self._resume.set_tooltip(_('No activity to start entry'))
Beispiel #3
0
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()
Beispiel #4
0
    def __init__(self, activity, top_level_toolbox):
        self._activity = activity
        self.toolbox = top_level_toolbox

        Terminal.__init__(self, self)

        #set up tool box/menu buttons
        activity_toolbar = self.toolbox.toolbar

        separator = Gtk.SeparatorToolItem()
        separator.set_draw(True)
        separator.show()
        activity_toolbar.insert(separator, 0)

        activity_go = ToolButton(stock_id=Gtk.STOCK_MEDIA_FORWARD)
        activity_go.set_icon_widget(None)
        activity_go.set_tooltip(_('Start Debugging'))
        activity_go.connect('clicked', self.project_run_cb)
        activity_go.add_accelerator('clicked', self.get_accelerator(),
                                    ord('O'), Gdk.ModifierType.CONTROL_MASK,
                                    Gtk.AccelFlags.VISIBLE)
        activity_go.show()
        activity_toolbar.insert(activity_go, 0)

        activity_copy_tb = ToolButton('edit-copy')
        activity_copy_tb.set_tooltip(_('Copy'))
        activity_copy_tb.connect('clicked', self._copy_cb)
        activity_toolbar.insert(activity_copy_tb, 3)
        activity_copy_tb.show()

        activity_paste_tb = ToolButton('edit-paste')
        activity_paste_tb.set_tooltip(_('Paste'))
        activity_paste_tb.connect('clicked', self._paste_cb)
        activity_paste_tb.add_accelerator('clicked', self.get_accelerator(),
                                          ord('V'),
                                          Gdk.ModifierType.CONTROL_MASK,
                                          Gtk.AccelFlags.VISIBLE)
        activity_toolbar.insert(activity_paste_tb, 4)
        activity_paste_tb.show()

        activity_tab_tb = ToolButton('list-add')
        activity_tab_tb.set_tooltip(_("Open New Tab"))
        activity_tab_tb.add_accelerator('clicked', self.get_accelerator(),
                                        ord('T'),
                                        Gdk.ModifierType.CONTROL_MASK,
                                        Gtk.AccelFlags.VISIBLE)
        activity_tab_tb.show()
        activity_tab_tb.connect('clicked', self._open_tab_cb)
        activity_toolbar.insert(activity_tab_tb, 5)

        activity_tab_delete_tv = ToolButton('list-remove')
        activity_tab_delete_tv.set_tooltip(_("Close Tab"))
        activity_tab_delete_tv.show()
        activity_tab_delete_tv.connect('clicked', self._close_tab_cb)
        activity_toolbar.insert(activity_tab_delete_tv, 6)

        activity_fullscreen_tb = ToolButton('view-fullscreen')
        activity_fullscreen_tb.set_tooltip(_("Fullscreen"))
        activity_fullscreen_tb.connect('clicked',
                                       self._activity._fullscreen_cb)
        activity_toolbar.insert(activity_fullscreen_tb, 7)
        activity_fullscreen_tb.hide()