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