Exemplo n.º 1
0
class WebActivity(activity.Activity):
    def __init__(self, handle):
        activity.Activity.__init__(self, handle)

        self._force_close = False
        if incompatible:
            return self._incompatible()

        self._collab = CollabWrapper(self)
        self._collab.message.connect(self.__message_cb)

        _logger.debug('Starting the web activity')

        # TODO PORT
        # session = WebKit2.get_default_session()
        # session.set_property('accept-language-auto', True)
        # session.set_property('ssl-use-system-ca-file', True)
        # session.set_property('ssl-strict', False)

        # But of a hack, but webkit doesn't let us change the cookie jar
        # contents, we we can just pre-seed it
        cookie_jar = SoupGNOME.CookieJarSqlite(filename=_cookies_db_path,
                                               read_only=False)
        _seed_xs_cookie(cookie_jar)
        del cookie_jar

        context = WebKit2.WebContext.get_default()
        cookie_manager = context.get_cookie_manager()
        cookie_manager.set_persistent_storage(
            _cookies_db_path, WebKit2.CookiePersistentStorage.SQLITE)

        # FIXME
        # downloadmanager.remove_old_parts()
        context.connect('download-started', self.__download_requested_cb)

        self._tabbed_view = TabbedView(self)
        self._tabbed_view.connect('focus-url-entry', self._on_focus_url_entry)
        self._tabbed_view.connect('switch-page', self.__switch_page_cb)

        self._titled_tray = TitledTray(_('Bookmarks'))
        self._tray = self._titled_tray.tray
        self.set_tray(self._titled_tray, Gtk.PositionType.BOTTOM)
        self._tray_links = {}

        self.model = Model()
        self.model.add_link_signal.connect(self._add_link_model_cb)

        self._primary_toolbar = PrimaryToolbar(self._tabbed_view, self)
        self._edit_toolbar = EditToolbar(self)
        self._view_toolbar = ViewToolbar(self)

        self._primary_toolbar.connect('add-link', self.__link_add_button_cb)
        self._primary_toolbar.connect('remove-link',
                                      self.__link_remove_button_cb)
        self._primary_toolbar.connect('go-home', self._go_home_button_cb)
        self._primary_toolbar.connect('go-library', self._go_library_button_cb)
        self._primary_toolbar.connect('set-home', self._set_home_button_cb)
        self._primary_toolbar.connect('reset-home', self._reset_home_button_cb)

        self._edit_toolbar_button = ToolbarButton(page=self._edit_toolbar,
                                                  icon_name='toolbar-edit')

        self._primary_toolbar.toolbar.insert(self._edit_toolbar_button, 1)

        view_toolbar_button = ToolbarButton(page=self._view_toolbar,
                                            icon_name='toolbar-view')
        self._primary_toolbar.toolbar.insert(view_toolbar_button, 2)

        self._primary_toolbar.show_all()
        self.set_toolbar_box(self._primary_toolbar)

        self.set_canvas(self._tabbed_view)
        self._tabbed_view.show()

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

        if handle.uri:
            self._tabbed_view.current_browser.load_uri(handle.uri)
        elif not self._jobject.file_path:
            # TODO: we need this hack until we extend the activity API for
            # opening URIs and default docs.
            self._tabbed_view.load_homepage()

        # README: this is a workaround to remove old temp file
        # http://bugs.sugarlabs.org/ticket/3973
        self._cleanup_temp_files()

        self._collab.setup()

    def __download_requested_cb(self, context, download):
        if hasattr(self, 'busy'):
            while self.unbusy() > 0:
                continue
        logging.debug('__download_requested_cb %r',
                      download.get_request().get_uri())
        downloadmanager.add_download(download, self)
        return True

    def fullscreen(self):
        activity.Activity.fullscreen(self)
        self._tabbed_view.set_show_tabs(False)

    def unfullscreen(self):
        activity.Activity.unfullscreen(self)

        # Always show tabs here, because they are automatically hidden
        # if it is like a video fullscreening.  We ALWAYS need to make
        # sure these are visible.  Don't make the user lost
        self._tabbed_view.set_show_tabs(True)

    def _cleanup_temp_files(self):
        """Removes temporary files generated by Download Manager that
        were cancelled by the user or failed for any reason.

        There is a bug in GLib that makes this to happen:
            https://bugzilla.gnome.org/show_bug.cgi?id=629301
        """

        try:
            uptime_proc = open('/proc/uptime', 'r').read()
            uptime = int(float(uptime_proc.split()[0]))
        except EnvironmentError:
            logging.warning('/proc/uptime could not be read')
            uptime = None

        temp_path = os.path.join(self.get_activity_root(), 'instance')
        now = int(time.time())
        cutoff = now - 24 * 60 * 60  # yesterday
        if uptime is not None:
            boot_time = now - uptime
            cutoff = max(cutoff, boot_time)

        for f in os.listdir(temp_path):
            if f.startswith('.goutputstream-'):
                fpath = os.path.join(temp_path, f)
                mtime = int(os.path.getmtime(fpath))
                if mtime < cutoff:
                    logging.warning('Removing old temporary file: %s', fpath)
                    try:
                        os.remove(fpath)
                    except EnvironmentError:
                        logging.error(
                            'Temporary file could not be '
                            'removed: %s', fpath)

    def _on_focus_url_entry(self, gobject):
        self._primary_toolbar.entry.grab_focus()

    def _get_data_from_file_path(self, file_path):
        fd = open(file_path, 'r')
        try:
            data = fd.read()
        finally:
            fd.close()
        return data

    def _get_save_as(self):
        if not hasattr(profile, 'get_save_as'):
            return False
        return profile.get_save_as()

    def read_file(self, file_path):
        if self.metadata['mime_type'] == 'text/plain':
            data = self._get_data_from_file_path(file_path)
            self.model.deserialize(data)

            for link in self.model.data['shared_links']:
                _logger.debug('read: url=%s title=%s d=%s' %
                              (link['url'], link['title'], link['color']))
                self._add_link_totray(link['url'], b64decode(link['thumb']),
                                      link['color'], link['title'],
                                      link['owner'], -1, link['hash'],
                                      link.get('notes'))
            logging.debug('########## reading %s', data)
            if 'session_state' in self.model.data:
                self._tabbed_view.set_session_state(
                    self.model.data['session_state'])
            else:
                self._tabbed_view.set_legacy_history(
                    self.model.data['history'], self.model.data['currents'])
                for number, tab in enumerate(self.model.data['currents']):
                    tab_page = self._tabbed_view.get_nth_page(number)
                    zoom_level = tab.get('zoom_level')
                    if zoom_level is not None:
                        tab_page.browser.set_zoom_level(zoom_level)
                    tab_page.browser.grab_focus()

            self._tabbed_view.set_current_page(self.model.data['current_tab'])

        elif self.metadata['mime_type'] == 'text/uri-list':
            data = self._get_data_from_file_path(file_path)
            uris = mime.split_uri_list(data)
            if len(uris) == 1:
                self._tabbed_view.props.current_browser.load_uri(uris[0])
            else:
                _logger.error('Open uri-list: Does not support'
                              'list of multiple uris by now.')
        else:
            file_uri = 'file://' + file_path
            self._tabbed_view.props.current_browser.load_uri(file_uri)
            self._tabbed_view.props.current_browser.grab_focus()

    def write_file(self, file_path):
        if not hasattr(self, '_tabbed_view'):
            _logger.debug('Called write_file before the tabbed_view was made')
            return

        if not self.metadata['mime_type']:
            self.metadata['mime_type'] = 'text/plain'

        if self.metadata['mime_type'] == 'text/plain':
            browser = self._tabbed_view.current_browser

            if not self._jobject.metadata['title_set_by_user'] == '1' and \
               not self._get_save_as():
                if browser.props.title is None:
                    self.metadata['title'] = _('Untitled')
                else:
                    self.metadata['title'] = browser.props.title

            self.model.data['history'] = self._tabbed_view.get_legacy_history()
            current_tab = self._tabbed_view.get_current_page()
            self.model.data['current_tab'] = current_tab

            self.model.data['currents'] = []
            for n in range(0, self._tabbed_view.get_n_pages()):
                tab_page = self._tabbed_view.get_nth_page(n)
                n_browser = tab_page.browser
                if n_browser is not None:
                    uri = n_browser.get_uri()
                    history_index = n_browser.get_history_index()
                    info = {
                        'title': n_browser.props.title,
                        'url': uri,
                        'history_index': history_index,
                        'zoom_level': n_browser.get_zoom_level()
                    }

                    self.model.data['currents'].append(info)

            self.model.data['session_state'] = \
                self._tabbed_view.get_state()

            f = open(file_path, 'w')
            try:
                logging.debug('########## writing %s', self.model.serialize())
                f.write(self.model.serialize())
            finally:
                f.close()

    def __link_add_button_cb(self, button):
        self._add_link()

    def __link_remove_button_cb(self, button):
        browser = self._tabbed_view.props.current_browser
        uri = browser.get_uri()
        self.__link_removed_cb(None, sha1(uri).hexdigest())

    def _go_home_button_cb(self, button):
        self._tabbed_view.load_homepage()

    def _go_library_button_cb(self, button):
        self._tabbed_view.load_homepage(ignore_settings=True)

    def _set_home_button_cb(self, button):
        self._tabbed_view.set_homepage()
        self._alert(_('The initial page was configured'))

    def _reset_home_button_cb(self, button):
        self._tabbed_view.reset_homepage()
        self._alert(_('The default initial page was configured'))

    def _alert(self, title, text=None):
        alert = NotifyAlert(timeout=5)
        alert.props.title = title
        alert.props.msg = text
        self.add_alert(alert)
        alert.connect('response', self._alert_cancel_cb)
        alert.show()

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

    def _key_press_cb(self, widget, event):
        browser = self._tabbed_view.props.current_browser

        if event.get_state() & Gdk.ModifierType.CONTROL_MASK:
            if event.keyval == Gdk.KEY_f:
                self._edit_toolbar_button.set_expanded(True)
                self._edit_toolbar.search_entry.grab_focus()
                return True
            if event.keyval == Gdk.KEY_l:
                self._primary_toolbar.entry.grab_focus()
                return True
            if event.keyval == Gdk.KEY_equal:
                # On US keyboards, KEY_equal is KEY_plus without
                # SHIFT_MASK, so for convenience treat this as the
                # same as the zoom in accelerator configured in
                # WebKit2
                browser.zoom_in()
                return True
            if event.keyval == Gdk.KEY_t:
                self._tabbed_view.add_tab()
                return True
            if event.keyval == Gdk.KEY_w:
                self._tabbed_view.close_tab()
                return True

            # FIXME: copy and paste is supposed to be handled by
            # Gtk.Entry, but does not work when we catch
            # key-press-event and return False.
            if self._primary_toolbar.entry.is_focus():
                if event.keyval == Gdk.KEY_c:
                    self._primary_toolbar.entry.copy_clipboard()
                    return True
                if event.keyval == Gdk.KEY_v:
                    self._primary_toolbar.entry.paste_clipboard()
                    return True

            return False

        if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Down, Gdk.KEY_KP_Left,
                            Gdk.KEY_KP_Right):
            scrolled_window = browser.get_parent()

            if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Down):
                adjustment = scrolled_window.get_vadjustment()
            elif event.keyval in (Gdk.KEY_KP_Left, Gdk.KEY_KP_Right):
                adjustment = scrolled_window.get_hadjustment()
            value = adjustment.get_value()
            step = adjustment.get_step_increment()

            if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Left):
                adjustment.set_value(value - step)
            else:
                adjustment.set_value(value + step)

            return True

        if event.keyval == Gdk.KEY_Escape:
            browser.stop_loading()
            return False  # allow toolbar entry to handle escape too

        return False

    def _add_link(self):
        ''' take screenshot and add link info to the model '''

        browser = self._tabbed_view.props.current_browser
        ui_uri = browser.get_uri()

        if self.model.has_link(ui_uri):
            return

        buf = b64encode(self._get_screenshot())
        timestamp = time.time()
        args = (ui_uri, browser.props.title, buf, profile.get_nick_name(),
                profile.get_color().to_string(), timestamp)
        self.model.add_link(*args, by_me=True)
        self._collab.post({'type': 'add_link', 'args': args})

    def __message_cb(self, collab, buddy, message):
        type_ = message.get('type')
        if type_ == 'add_link':
            self.model.add_link(*message['args'])
        elif type_ == 'add_link_from_info':
            self.model.add_link_from_info(message['dict'])
        elif type_ == 'remove_link':
            self.remove_link(message['hash'])

    def get_data(self):
        return self.model.data

    def set_data(self, data):
        for link in data['shared_links']:
            if link['hash'] not in self.model.get_links_ids():
                self.model.add_link_from_info(link)
            # FIXME: Case where buddy has updated link desciption

        their_model = Model()
        their_model.data = data
        for link in self.model.data['shared_links']:
            if link['hash'] not in their_model.get_links_ids():
                self._collab.post({'type': 'add_link_from_info', 'dict': link})

    def _add_link_model_cb(self, model, index, by_me):
        ''' receive index of new link from the model '''
        link = self.model.data['shared_links'][index]
        widget = self._add_link_totray(link['url'], b64decode(link['thumb']),
                                       link['color'], link['title'],
                                       link['owner'], index, link['hash'],
                                       link.get('notes'))

        if by_me:
            animator = Animator(1, widget=self)
            animator.add(
                AddLinkAnimation(self, self._tabbed_view.props.current_browser,
                                 widget))
            animator.start()

    def _add_link_totray(self,
                         url,
                         buf,
                         color,
                         title,
                         owner,
                         index,
                         hash,
                         notes=None):
        ''' add a link to the tray '''
        item = LinkButton(buf, color, title, owner, hash, notes)
        item.connect('clicked', self._link_clicked_cb, url)
        item.connect('remove_link', self.__link_removed_cb)
        item.notes_changed_signal.connect(self.__link_notes_changed)
        # use index to add to the tray
        self._tray_links[hash] = item
        self._tray.add_item(item, index)
        item.show()
        self._view_toolbar.traybutton.props.sensitive = True
        self._view_toolbar.traybutton.props.active = True
        self._view_toolbar.update_traybutton_tooltip()
        return item

    def __link_removed_cb(self, button, hash):
        self.remove_link(hash)
        self._collab.post({'type': 'remove_link', 'hash': hash})

    def remove_link(self, hash):
        ''' remove a link from tray and delete it in the model '''
        self._tray_links[hash].hide()
        self._tray_links[hash].destroy()
        del self._tray_links[hash]

        self.model.remove_link(hash)
        if len(self._tray.get_children()) == 0:
            self._view_toolbar.traybutton.props.sensitive = False
            self._view_toolbar.traybutton.props.active = False
            self._view_toolbar.update_traybutton_tooltip()

    def __link_notes_changed(self, button, hash, notes):
        self.model.change_link_notes(hash, notes)

    def _link_clicked_cb(self, button, url):
        ''' an item of the link tray has been clicked '''
        browser = self._tabbed_view.add_tab()
        browser.load_uri(url)
        browser.grab_focus()

    def _get_screenshot(self):
        browser = self._tabbed_view.props.current_browser
        window = browser.get_window()
        width, height = window.get_width(), window.get_height()

        thumb_surface = Gdk.Window.create_similar_surface(
            window, cairo.CONTENT_COLOR, THUMB_WIDTH, THUMB_HEIGHT)

        cairo_context = cairo.Context(thumb_surface)
        thumb_scale_w = THUMB_WIDTH * 1.0 / width
        thumb_scale_h = THUMB_HEIGHT * 1.0 / height
        cairo_context.scale(thumb_scale_w, thumb_scale_h)
        Gdk.cairo_set_source_window(cairo_context, window, 0, 0)
        cairo_context.paint()

        thumb_str = StringIO.StringIO()
        thumb_surface.write_to_png(thumb_str)
        return thumb_str.getvalue()

    def can_close(self):
        if self._force_close:
            return True
        elif downloadmanager.can_quit():
            return True
        else:
            alert = Alert()
            alert.props.title = ngettext('Download in progress',
                                         'Downloads in progress',
                                         downloadmanager.num_downloads())
            message = ngettext('Stopping now will erase your download',
                               'Stopping now will erase your downloads',
                               downloadmanager.num_downloads())
            alert.props.msg = message
            cancel_icon = Icon(icon_name='dialog-cancel')
            cancel_label = ngettext('Continue download', 'Continue downloads',
                                    downloadmanager.num_downloads())
            alert.add_button(Gtk.ResponseType.CANCEL, cancel_label,
                             cancel_icon)
            stop_icon = Icon(icon_name='dialog-ok')
            alert.add_button(Gtk.ResponseType.OK, _('Stop'), stop_icon)
            stop_icon.show()
            self.add_alert(alert)
            alert.connect('response', self.__inprogress_response_cb)
            alert.show()
            self.present()
            return False

    def __inprogress_response_cb(self, alert, response_id):
        self.remove_alert(alert)
        if response_id is Gtk.ResponseType.CANCEL:
            logging.debug('Keep on')
        elif response_id == Gtk.ResponseType.OK:
            logging.debug('Stop downloads and quit')
            self._force_close = True
            downloadmanager.remove_all_downloads()
            self.close()

    def __switch_page_cb(self, tabbed_view, page, page_num):
        if not hasattr(self, 'busy'):
            return

        browser = page._browser
        progress = browser.props.estimated_load_progress
        uri = browser.props.uri

        if progress < 1.0 and uri:
            self.busy()
        else:
            while self.unbusy() > 0:
                continue

    def get_document_path(self, async_cb, async_err_cb):
        browser = self._tabbed_view.props.current_browser
        browser.get_source(async_cb, async_err_cb)

    def get_canvas(self):
        return self._tabbed_view

    def _incompatible(self):
        ''' Display abbreviated activity user interface with alert '''
        toolbox = ToolbarBox()
        stop = StopButton(self)
        toolbox.toolbar.add(stop)
        self.set_toolbar_box(toolbox)

        title = _('Activity not compatible with this system.')
        msg = _('Please downgrade activity and try again.')
        alert = Alert(title=title, msg=msg)
        alert.add_button(0, 'Stop', Icon(icon_name='activity-stop'))
        self.add_alert(alert)

        label = Gtk.Label(
            _('Uh oh, WebKit2 is too old. '
              'Browse-200 and later require WebKit2 API 4.0, '
              'sorry!'))
        self.set_canvas(label)
        '''
        Workaround: start Terminal activity, then type

        sugar-erase-bundle org.laptop.WebActivity

        then in My Settings, choose Software Update, which will offer
        older Browse.
        '''

        alert.connect('response', self.__incompatible_response_cb)
        stop.connect('clicked', self.__incompatible_stop_clicked_cb, alert)

        self.show_all()

    def __incompatible_stop_clicked_cb(self, button, alert):
        self.remove_alert(alert)

    def __incompatible_response_cb(self, alert, response):
        self.remove_alert(alert)
        self.close()
Exemplo n.º 2
0
class EditActivity(activity.Activity):
    def checkts(self):
        '''Check the timestamp
        If someone's modified our file in an external editor,
        we should reload the contents
        '''

        mtime = self.metadata[mdnames.sugartimestamp_md]
        etime = self.metadata[mdnames.cloudtimestamp_md]
        return mtime > etime

    def __init__(self, handle):
        '''We want to set up the buffer et al. early on
        sure there's early_setup, but that's not early enough
        '''
        activity.Activity.__init__(self, handle)
        self._collab = CollabWrapper(self)

        self.buffer = GtkSource.Buffer()
        TextBufferCollaberizer(self.buffer, 'main', self._collab)
        self.refresh_buffer = False

        self.text_view = GtkSource.View.new_with_buffer(self.buffer)
        self.scrollwindow = Gtk.ScrolledWindow()
        self.scrollwindow.add(self.text_view)

        self.setup_toolbar()

        self.set_canvas(self.scrollwindow)
        self.scrollwindow.show_all()
        self._collab.setup()


    def fix_mimetype(self):
        '''We must have a mimetype. Sometimes, we don't (when we get launched
        newly.) This  fixes that.'''
        if self.metadata[mdnames.mimetype_md] == '':
            self.metadata[mdnames.mimetype_md] = "text/plain"
            #we MUST have a mimetype

    def setup_toolbar(self):
        '''Setup the top toolbar. Groupthink needs some work here.'''

        toolbox = ToolbarBox()

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

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

        self.edit_toolbar = EditToolbar()
        edit_toolbar_button = ToolbarButton(
            page=self.edit_toolbar,
            icon_name='toolbar-edit')
        self.edit_toolbar.show()
        toolbar.insert(edit_toolbar_button, -1)
        edit_toolbar_button.show()

        self.edit_toolbar.undo.connect('clicked', self.undobutton_cb)
        self.edit_toolbar.redo.connect('clicked', self.redobutton_cb)
        self.edit_toolbar.copy.connect('clicked', self.copybutton_cb)
        self.edit_toolbar.paste.connect('clicked', self.pastebutton_cb)

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

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

    def initialize_display(self):
        '''Set up GTK and friends'''
        self.fix_mimetype()

        lang_manager = GtkSource.LanguageManager.get_default()
        if hasattr(lang_manager, 'list_languages'):
            langs = lang_manager.list_languages()
        else:
            lang_ids = lang_manager.get_language_ids()
            langs = [lang_manager.get_language(lang_id) \
                         for lang_id in lang_ids]
            for lang in langs:
                for mtype in lang.get_mime_types():
                    if mtype == self.metadata[mdnames.mimetype_md]:
                        self.buffer.set_language(lang)
                        break

        self.text_view.set_editable(True)
        self.text_view.set_cursor_visible(True)

        if self.metadata[mdnames.mimetype_md] == "text/plain":
            self.text_view.set_show_line_numbers(False)
            self.text_view.set_wrap_mode(Gtk.WrapMode.WORD)
            font = Pango.FontDescription("Bitstream Vera Sans " +
                                         str(style.FONT_SIZE))
        else:
            if hasattr(self.buffer, 'set_highlight'):
                self.buffer.set_highlight(True)
            else:
                self.buffer.set_highlight_syntax(True)

            self.text_view.set_show_line_numbers(True)

            self.text_view.set_wrap_mode(Gtk.WrapMode.CHAR)
            self.text_view.set_insert_spaces_instead_of_tabs(True)
            self.text_view.set_tab_width(2)
            self.text_view.set_auto_indent(True)
            font = Pango.FontDescription("Monospace " +
                                         str(style.FONT_SIZE))

        self.text_view.modify_font(font)

        if self.refresh_buffer:
            #see load_from_journal()
            self.buffer.begin_not_undoable_action()
            self.buffer.set_text(self.refresh_buffer)
            self.buffer.end_not_undoable_action()

        self.text_view.show()

        #Return the main widget. our parents take care of GTK stuff
        return self.scrollwindow

    def write_file(self, filename):
        #Also write to file:
        fhandle = open(filename, "w")

        bounds = self.buffer.get_bounds()
        text = self.buffer.get_text(bounds[0], bounds[1], True)

        fhandle.write(text)
        fhandle.close()

        self.fix_mimetype()

        #We can do full-text search on all Edit documents, yay
        self.metadata[mdnames.contents_md] = text

    def read_file(self, filename):
        '''Load the file. Duh.'''
        text = open(filename, "r").read()  # yay hackish one-line read

        self.buffer.set_text(text)
        return None

    def undobutton_cb(self, button):
        if self.buffer.can_undo():
            self.buffer.undo()

    def redobutton_cb(self, button):
        global text_buffer
        if self.buffer.can_redo():
            self.buffer.redo()

    def copybutton_cb(self, button):
        self.buffer.copy_clipboard(Gtk.Clipboard())

    def pastebutton_cb(self, button):
        self.buffer.paste_clipboard(Gtk.Clipboard(), None, True)
Exemplo n.º 3
0
class EditActivity(activity.Activity):
    def checkts(self):
        '''Check the timestamp
        If someone's modified our file in an external editor,
        we should reload the contents
        '''

        mtime = self.metadata[mdnames.sugartimestamp_md]
        etime = self.metadata[mdnames.cloudtimestamp_md]
        return mtime > etime

    def __init__(self, handle):
        '''We want to set up the buffer et al. early on
        sure there's early_setup, but that's not early enough
        '''
        activity.Activity.__init__(self, handle)
        self._collab = CollabWrapper(self)

        self.buffer = GtkSource.Buffer()
        TextBufferCollaberizer(self.buffer, 'main', self._collab)
        self.refresh_buffer = False

        self.text_view = GtkSource.View.new_with_buffer(self.buffer)
        self.scrollwindow = Gtk.ScrolledWindow()
        self.scrollwindow.add(self.text_view)

        self.setup_toolbar()

        self.set_canvas(self.scrollwindow)
        self.scrollwindow.show_all()
        self._collab.setup()

    def fix_mimetype(self):
        '''We must have a mimetype. Sometimes, we don't (when we get launched
        newly.) This  fixes that.'''
        if self.metadata[mdnames.mimetype_md] == '':
            self.metadata[mdnames.mimetype_md] = "text/plain"
            #we MUST have a mimetype

    def setup_toolbar(self):
        '''Setup the top toolbar. Groupthink needs some work here.'''

        toolbox = ToolbarBox()

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

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

        self.edit_toolbar = EditToolbar()
        edit_toolbar_button = ToolbarButton(page=self.edit_toolbar,
                                            icon_name='toolbar-edit')
        self.edit_toolbar.show()
        toolbar.insert(edit_toolbar_button, -1)
        edit_toolbar_button.show()

        self.edit_toolbar.undo.connect('clicked', self.undobutton_cb)
        self.edit_toolbar.redo.connect('clicked', self.redobutton_cb)
        self.edit_toolbar.copy.connect('clicked', self.copybutton_cb)
        self.edit_toolbar.paste.connect('clicked', self.pastebutton_cb)

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

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

    def initialize_display(self):
        '''Set up GTK and friends'''
        self.fix_mimetype()

        lang_manager = GtkSource.LanguageManager.get_default()
        if hasattr(lang_manager, 'list_languages'):
            langs = lang_manager.list_languages()
        else:
            lang_ids = lang_manager.get_language_ids()
            langs = [lang_manager.get_language(lang_id) \
                         for lang_id in lang_ids]
            for lang in langs:
                for mtype in lang.get_mime_types():
                    if mtype == self.metadata[mdnames.mimetype_md]:
                        self.buffer.set_language(lang)
                        break

        self.text_view.set_editable(True)
        self.text_view.set_cursor_visible(True)

        if self.metadata[mdnames.mimetype_md] == "text/plain":
            self.text_view.set_show_line_numbers(False)
            self.text_view.set_wrap_mode(Gtk.WrapMode.WORD)
            font = Pango.FontDescription("Bitstream Vera Sans " +
                                         str(style.FONT_SIZE))
        else:
            if hasattr(self.buffer, 'set_highlight'):
                self.buffer.set_highlight(True)
            else:
                self.buffer.set_highlight_syntax(True)

            self.text_view.set_show_line_numbers(True)

            self.text_view.set_wrap_mode(Gtk.WrapMode.CHAR)
            self.text_view.set_insert_spaces_instead_of_tabs(True)
            self.text_view.set_tab_width(2)
            self.text_view.set_auto_indent(True)
            font = Pango.FontDescription("Monospace " + str(style.FONT_SIZE))

        self.text_view.modify_font(font)

        if self.refresh_buffer:
            #see load_from_journal()
            self.buffer.begin_not_undoable_action()
            self.buffer.set_text(self.refresh_buffer)
            self.buffer.end_not_undoable_action()

        self.text_view.show()

        #Return the main widget. our parents take care of GTK stuff
        return self.scrollwindow

    def write_file(self, filename):
        #Also write to file:
        fhandle = open(filename, "w")

        bounds = self.buffer.get_bounds()
        text = self.buffer.get_text(bounds[0], bounds[1], True)

        fhandle.write(text)
        fhandle.close()

        self.fix_mimetype()

        #We can do full-text search on all Edit documents, yay
        self.metadata[mdnames.contents_md] = text

    def read_file(self, filename):
        '''Load the file. Duh.'''
        text = open(filename, "r").read()  # yay hackish one-line read

        self.buffer.set_text(text)
        return None

    def undobutton_cb(self, button):
        if self.buffer.can_undo():
            self.buffer.undo()

    def redobutton_cb(self, button):
        global text_buffer
        if self.buffer.can_redo():
            self.buffer.redo()

    def copybutton_cb(self, button):
        self.buffer.copy_clipboard(Gtk.Clipboard())

    def pastebutton_cb(self, button):
        self.buffer.paste_clipboard(Gtk.Clipboard(), None, True)
Exemplo n.º 4
0
class WebActivity(activity.Activity):
    def __init__(self, handle):
        activity.Activity.__init__(self, handle)
        self._collab = CollabWrapper(self)
        self._collab.message.connect(self.__message_cb)

        _logger.debug('Starting the web activity')

        # TODO PORT
        # session = WebKit2.get_default_session()
        # session.set_property('accept-language-auto', True)
        # session.set_property('ssl-use-system-ca-file', True)
        # session.set_property('ssl-strict', False)

        # But of a hack, but webkit doesn't let us change the cookie jar
        # contents, we we can just pre-seed it
        cookie_jar = SoupGNOME.CookieJarSqlite(filename=_cookies_db_path,
                                               read_only=False)
        _seed_xs_cookie(cookie_jar)
        del cookie_jar

        context = WebKit2.WebContext.get_default()
        cookie_manager = context.get_cookie_manager()
        cookie_manager.set_persistent_storage(
            _cookies_db_path, WebKit2.CookiePersistentStorage.SQLITE)

        # FIXME
        # downloadmanager.remove_old_parts()
        context.connect('download-started', self.__download_requested_cb)

        self._force_close = False
        self._tabbed_view = TabbedView(self)
        self._tabbed_view.connect('focus-url-entry', self._on_focus_url_entry)
        self._tabbed_view.connect('switch-page', self.__switch_page_cb)

        self._titled_tray = TitledTray(_('Bookmarks'))
        self._tray = self._titled_tray.tray
        self.set_tray(self._titled_tray, Gtk.PositionType.BOTTOM)
        self._tray_links = {}

        self.model = Model()
        self.model.add_link_signal.connect(self._add_link_model_cb)

        self._primary_toolbar = PrimaryToolbar(self._tabbed_view, self)
        self._edit_toolbar = EditToolbar(self)
        self._view_toolbar = ViewToolbar(self)

        self._primary_toolbar.connect('add-link', self.__link_add_button_cb)
        self._primary_toolbar.connect('remove-link',
                                      self.__link_remove_button_cb)
        self._primary_toolbar.connect('go-home', self._go_home_button_cb)
        self._primary_toolbar.connect('go-library', self._go_library_button_cb)
        self._primary_toolbar.connect('set-home', self._set_home_button_cb)
        self._primary_toolbar.connect('reset-home', self._reset_home_button_cb)

        self._edit_toolbar_button = ToolbarButton(
            page=self._edit_toolbar, icon_name='toolbar-edit')

        self._primary_toolbar.toolbar.insert(
            self._edit_toolbar_button, 1)

        view_toolbar_button = ToolbarButton(
            page=self._view_toolbar, icon_name='toolbar-view')
        self._primary_toolbar.toolbar.insert(
            view_toolbar_button, 2)

        self._primary_toolbar.show_all()
        self.set_toolbar_box(self._primary_toolbar)

        self.set_canvas(self._tabbed_view)
        self._tabbed_view.show()

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

        if handle.uri:
            self._tabbed_view.current_browser.load_uri(handle.uri)
        elif not self._jobject.file_path:
            # TODO: we need this hack until we extend the activity API for
            # opening URIs and default docs.
            self._tabbed_view.load_homepage()

        # README: this is a workaround to remove old temp file
        # http://bugs.sugarlabs.org/ticket/3973
        self._cleanup_temp_files()

        self._collab.setup()

    def __download_requested_cb(self, context, download):
        if hasattr(self, 'busy'):
            while self.unbusy() > 0:
                continue
        logging.debug('__download_requested_cb %r',
                      download.get_request().get_uri())
        downloadmanager.add_download(download, self)
        return True

    def unfullscreen(self):
        activity.Activity.unfullscreen(self)

        # Always show tabs here, because they are automatically hidden
        # if it is like a video fullscreening.  We ALWAYS need to make
        # sure these are visible.  Don't make the user lost
        self._tabbed_view.props.show_tabs = True

    def _cleanup_temp_files(self):
        """Removes temporary files generated by Download Manager that
        were cancelled by the user or failed for any reason.

        There is a bug in GLib that makes this to happen:
            https://bugzilla.gnome.org/show_bug.cgi?id=629301
        """

        try:
            uptime_proc = open('/proc/uptime', 'r').read()
            uptime = int(float(uptime_proc.split()[0]))
        except EnvironmentError:
            logging.warning('/proc/uptime could not be read')
            uptime = None

        temp_path = os.path.join(self.get_activity_root(), 'instance')
        now = int(time.time())
        cutoff = now - 24 * 60 * 60  # yesterday
        if uptime is not None:
            boot_time = now - uptime
            cutoff = max(cutoff, boot_time)

        for f in os.listdir(temp_path):
            if f.startswith('.goutputstream-'):
                fpath = os.path.join(temp_path, f)
                mtime = int(os.path.getmtime(fpath))
                if mtime < cutoff:
                    logging.warning('Removing old temporary file: %s', fpath)
                    try:
                        os.remove(fpath)
                    except EnvironmentError:
                        logging.error('Temporary file could not be '
                                      'removed: %s', fpath)

    def _on_focus_url_entry(self, gobject):
        self._primary_toolbar.entry.grab_focus()

    def _get_data_from_file_path(self, file_path):
        fd = open(file_path, 'r')
        try:
            data = fd.read()
        finally:
            fd.close()
        return data

    def _get_save_as(self):
        if not hasattr(profile, 'get_save_as'):
            return False
        return profile.get_save_as()

    def read_file(self, file_path):
        if self.metadata['mime_type'] == 'text/plain':
            data = self._get_data_from_file_path(file_path)
            self.model.deserialize(data)

            for link in self.model.data['shared_links']:
                _logger.debug('read: url=%s title=%s d=%s' % (link['url'],
                                                              link['title'],
                                                              link['color']))
                self._add_link_totray(link['url'],
                                      b64decode(link['thumb']),
                                      link['color'], link['title'],
                                      link['owner'], -1, link['hash'],
                                      link.get('notes'))
            logging.debug('########## reading %s', data)
            if 'session_state' in self.model.data:
                self._tabbed_view.set_session_state(
                    self.model.data['session_state'])
            else:
                self._tabbed_view.set_legacy_history(
                    self.model.data['history'],
                    self.model.data['currents'])
                for number, tab in enumerate(self.model.data['currents']):
                    tab_page = self._tabbed_view.get_nth_page(number)
                    zoom_level = tab.get('zoom_level')
                    if zoom_level is not None:
                        tab_page.browser.set_zoom_level(zoom_level)
                    tab_page.browser.grab_focus()

            self._tabbed_view.set_current_page(self.model.data['current_tab'])

        elif self.metadata['mime_type'] == 'text/uri-list':
            data = self._get_data_from_file_path(file_path)
            uris = mime.split_uri_list(data)
            if len(uris) == 1:
                self._tabbed_view.props.current_browser.load_uri(uris[0])
            else:
                _logger.error('Open uri-list: Does not support'
                              'list of multiple uris by now.')
        else:
            file_uri = 'file://' + file_path
            self._tabbed_view.props.current_browser.load_uri(file_uri)
            self._tabbed_view.props.current_browser.grab_focus()

    def write_file(self, file_path):
        if not hasattr(self, '_tabbed_view'):
            _logger.error('Called write_file before the tabbed_view was made')
            return

        if not self.metadata['mime_type']:
            self.metadata['mime_type'] = 'text/plain'

        if self.metadata['mime_type'] == 'text/plain':
            browser = self._tabbed_view.current_browser

            if not self._jobject.metadata['title_set_by_user'] == '1' and \
                not self._get_save_as():
                if browser.props.title is None:
                    self.metadata['title'] = _('Untitled')
                else:
                    self.metadata['title'] = browser.props.title

            self.model.data['history'] = self._tabbed_view.get_legacy_history()
            current_tab = self._tabbed_view.get_current_page()
            self.model.data['current_tab'] = current_tab

            self.model.data['currents'] = []
            for n in range(0, self._tabbed_view.get_n_pages()):
                tab_page = self._tabbed_view.get_nth_page(n)
                n_browser = tab_page.browser
                if n_browser is not None:
                    uri = n_browser.get_uri()
                    history_index = n_browser.get_history_index()
                    info = {'title': n_browser.props.title, 'url': uri,
                            'history_index': history_index,
                            'zoom_level': n_browser.get_zoom_level()}

                    self.model.data['currents'].append(info)

            self.model.data['session_state'] = \
                self._tabbed_view.get_state()

            f = open(file_path, 'w')
            try:
                logging.debug('########## writing %s', self.model.serialize())
                f.write(self.model.serialize())
            finally:
                f.close()

    def __link_add_button_cb(self, button):
        self._add_link()

    def __link_remove_button_cb(self, button):
        browser = self._tabbed_view.props.current_browser
        uri = browser.get_uri()
        self.__link_removed_cb(None, sha1(uri).hexdigest())

    def _go_home_button_cb(self, button):
        self._tabbed_view.load_homepage()

    def _go_library_button_cb(self, button):
        self._tabbed_view.load_homepage(ignore_gconf=True)

    def _set_home_button_cb(self, button):
        self._tabbed_view.set_homepage()
        self._alert(_('The initial page was configured'))

    def _reset_home_button_cb(self, button):
        self._tabbed_view.reset_homepage()
        self._alert(_('The default initial page was configured'))

    def _alert(self, title, text=None):
        alert = NotifyAlert(timeout=5)
        alert.props.title = title
        alert.props.msg = text
        self.add_alert(alert)
        alert.connect('response', self._alert_cancel_cb)
        alert.show()

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

    def _key_press_cb(self, widget, event):
        browser = self._tabbed_view.props.current_browser

        if event.get_state() & Gdk.ModifierType.CONTROL_MASK:
            if event.keyval == Gdk.KEY_f:
                self._edit_toolbar_button.set_expanded(True)
                self._edit_toolbar.search_entry.grab_focus()
                return True
            if event.keyval == Gdk.KEY_l:
                self._primary_toolbar.entry.grab_focus()
                return True
            if event.keyval == Gdk.KEY_equal:
                # On US keyboards, KEY_equal is KEY_plus without
                # SHIFT_MASK, so for convenience treat this as the
                # same as the zoom in accelerator configured in
                # WebKit2
                browser.zoom_in()
                return True
            if event.keyval == Gdk.KEY_t:
                self._tabbed_view.add_tab()
                return True
            if event.keyval == Gdk.KEY_w:
                self._tabbed_view.close_tab()
                return True

            # FIXME: copy and paste is supposed to be handled by
            # Gtk.Entry, but does not work when we catch
            # key-press-event and return False.
            if self._primary_toolbar.entry.is_focus():
                if event.keyval == Gdk.KEY_c:
                    self._primary_toolbar.entry.copy_clipboard()
                    return True
                if event.keyval == Gdk.KEY_v:
                    self._primary_toolbar.entry.paste_clipboard()
                    return True

            return False

        if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Down,
                            Gdk.KEY_KP_Left, Gdk.KEY_KP_Right):
            scrolled_window = browser.get_parent()

            if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Down):
                adjustment = scrolled_window.get_vadjustment()
            elif event.keyval in (Gdk.KEY_KP_Left, Gdk.KEY_KP_Right):
                adjustment = scrolled_window.get_hadjustment()
            value = adjustment.get_value()
            step = adjustment.get_step_increment()

            if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Left):
                adjustment.set_value(value - step)
            else:
                adjustment.set_value(value + step)

            return True

        if event.keyval == Gdk.KEY_Escape:
            browser.stop_loading()
            return False  # allow toolbar entry to handle escape too

        return False

    def _add_link(self):
        ''' take screenshot and add link info to the model '''

        browser = self._tabbed_view.props.current_browser
        ui_uri = browser.get_uri()

        if self.model.has_link(ui_uri):
            return

        buf = b64encode(self._get_screenshot())
        timestamp = time.time()
        args = (ui_uri, browser.props.title, buf,
                profile.get_nick_name(),
                profile.get_color().to_string(), timestamp)
        self.model.add_link(*args, by_me=True)
        self._collab.post({'type': 'add_link', 'args': args})

    def __message_cb(self, collab, buddy, message):
        type_ = message.get('type')
        if type_ == 'add_link':
            self.model.add_link(*message['args'])
        elif type_ == 'add_link_from_info':
            self.model.add_link_from_info(message['dict'])
        elif type_ == 'remove_link':
            self.remove_link(message['hash'])

    def get_data(self):
        return self.model.data

    def set_data(self, data):
        for link in data['shared_links']:
            if link['hash'] not in self.model.get_links_ids():
                self.model.add_link_from_info(link)
            # FIXME: Case where buddy has updated link desciption

        their_model = Model()
        their_model.data = data
        for link in self.model.data['shared_links']:
            if link['hash'] not in their_model.get_links_ids():
                self._collab.post({'type': 'add_link_from_info',
                                   'dict': link})

    def _add_link_model_cb(self, model, index, by_me):
        ''' receive index of new link from the model '''
        link = self.model.data['shared_links'][index]
        widget = self._add_link_totray(
            link['url'], b64decode(link['thumb']),
            link['color'], link['title'],
            link['owner'], index, link['hash'],
            link.get('notes'))

        if by_me:
            animator = Animator(1, widget=self)
            animator.add(AddLinkAnimation(
                self, self._tabbed_view.props.current_browser, widget))
            animator.start()

    def _add_link_totray(self, url, buf, color, title, owner, index, hash,
                         notes=None):
        ''' add a link to the tray '''
        item = LinkButton(buf, color, title, owner, hash, notes)
        item.connect('clicked', self._link_clicked_cb, url)
        item.connect('remove_link', self.__link_removed_cb)
        item.notes_changed_signal.connect(self.__link_notes_changed)
        # use index to add to the tray
        self._tray_links[hash] = item
        self._tray.add_item(item, index)
        item.show()
        self._view_toolbar.traybutton.props.sensitive = True
        self._view_toolbar.traybutton.props.active = True
        self._view_toolbar.update_traybutton_tooltip()
        return item

    def __link_removed_cb(self, button, hash):
        self.remove_link(hash)
        self._collab.post({'type': 'remove_link', 'hash': hash})

    def remove_link(self, hash):
        ''' remove a link from tray and delete it in the model '''
        self._tray_links[hash].hide()
        self._tray_links[hash].destroy()
        del self._tray_links[hash]

        self.model.remove_link(hash)
        if len(self._tray.get_children()) == 0:
            self._view_toolbar.traybutton.props.sensitive = False
            self._view_toolbar.traybutton.props.active = False
            self._view_toolbar.update_traybutton_tooltip()

    def __link_notes_changed(self, button, hash, notes):
        self.model.change_link_notes(hash, notes)

    def _link_clicked_cb(self, button, url):
        ''' an item of the link tray has been clicked '''
        browser = self._tabbed_view.add_tab()
        browser.load_uri(url)
        browser.grab_focus()

    def _get_screenshot(self):
        browser = self._tabbed_view.props.current_browser
        window = browser.get_window()
        width, height = window.get_width(), window.get_height()

        thumb_surface = Gdk.Window.create_similar_surface(
            window, cairo.CONTENT_COLOR, THUMB_WIDTH, THUMB_HEIGHT)

        cairo_context = cairo.Context(thumb_surface)
        thumb_scale_w = THUMB_WIDTH * 1.0 / width
        thumb_scale_h = THUMB_HEIGHT * 1.0 / height
        cairo_context.scale(thumb_scale_w, thumb_scale_h)
        Gdk.cairo_set_source_window(cairo_context, window, 0, 0)
        cairo_context.paint()

        thumb_str = StringIO.StringIO()
        thumb_surface.write_to_png(thumb_str)
        return thumb_str.getvalue()

    def can_close(self):
        if self._force_close:
            return True
        elif downloadmanager.can_quit():
            return True
        else:
            alert = Alert()
            alert.props.title = ngettext('Download in progress',
                                         'Downloads in progress',
                                         downloadmanager.num_downloads())
            message = ngettext('Stopping now will erase your download',
                               'Stopping now will erase your downloads',
                               downloadmanager.num_downloads())
            alert.props.msg = message
            cancel_icon = Icon(icon_name='dialog-cancel')
            cancel_label = ngettext('Continue download', 'Continue downloads',
                                    downloadmanager.num_downloads())
            alert.add_button(Gtk.ResponseType.CANCEL, cancel_label,
                             cancel_icon)
            stop_icon = Icon(icon_name='dialog-ok')
            alert.add_button(Gtk.ResponseType.OK, _('Stop'), stop_icon)
            stop_icon.show()
            self.add_alert(alert)
            alert.connect('response', self.__inprogress_response_cb)
            alert.show()
            self.present()
            return False

    def __inprogress_response_cb(self, alert, response_id):
        self.remove_alert(alert)
        if response_id is Gtk.ResponseType.CANCEL:
            logging.debug('Keep on')
        elif response_id == Gtk.ResponseType.OK:
            logging.debug('Stop downloads and quit')
            self._force_close = True
            downloadmanager.remove_all_downloads()
            self.close()

    def __switch_page_cb(self, tabbed_view, page, page_num):
        if not hasattr(self, 'busy'):
            return

        browser = page._browser
        progress = browser.props.estimated_load_progress
        uri = browser.props.uri

        if progress < 1.0 and uri:
            self.busy()
        else:
            while self.unbusy() > 0:
                continue

    def get_document_path(self, async_cb, async_err_cb):
        browser = self._tabbed_view.props.current_browser
        browser.get_source(async_cb, async_err_cb)

    def get_canvas(self):
        return self._tabbed_view