class SitesManagerChild(Gtk.ListBoxRow): """ Child showing snapshot, title and favicon """ def __init__(self, netloc, window): """ Init child @param netloc as str @param window as Window """ Gtk.ListBoxRow.__init__(self) self.__window = window self.__netloc = netloc self.__views = [] self.__connected_ids = [] self.__scroll_timeout_id = None self.set_property("has-tooltip", True) self.connect("query-tooltip", self.__on_query_tooltip) builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Eolie/SitesManagerChild.ui") builder.connect_signals(self) widget = builder.get_object("widget") self.__close_button = builder.get_object("close_button") self.__indicator_label = LabelIndicator() self.__indicator_label.set_property("halign", Gtk.Align.CENTER) self.__indicator_label.show() builder.get_object("grid").attach(self.__indicator_label, 1, 0, 1, 1) self.__netloc_label = builder.get_object("netloc") self.__netloc_label.set_text(self.__netloc) self.__image = builder.get_object("image") self.__image.set_property("pixel-size", ArtSize.FAVICON) self.add(widget) def add_view(self, view): """ Add view @param view as View @param uri as str """ if not self.__views: self.__set_initial_artwork(self.__netloc, view.webview.ephemeral) if view not in self.__views: self.__views.append(view) self.update_indicator(view) self.update_label() def remove_view(self, view): """ Remove view and destroy self if no more view @param view as View """ if view in self.__views: self.__views.remove(view) self.update_indicator(view) self.update_label() def set_favicon(self, surface): """ Set favicon @param surface as cairo.Surface """ self.__image.set_from_surface(surface) def set_minimal(self, minimal): """ Make widget minimal @param minimal as bool """ if minimal: self.__netloc_label.hide() self.__close_button.hide() self.__image.set_property("halign", Gtk.Align.CENTER) self.__image.set_hexpand(True) else: self.__netloc_label.show() self.__close_button.show() self.__image.set_hexpand(False) self.__image.set_property("halign", Gtk.Align.START) def reset(self, netloc): """ Reset widget to new netloc @param netloc as str """ if netloc != self.__netloc: self.__netloc = netloc self.__netloc_label.set_text(self.__netloc) self.__set_initial_artwork(self.__netloc) def update_label(self): """ Update label: if one view, use title else use netloc @param view as View """ if len(self.__views) == 1: title = self.__views[0].webview.get_title() if title is None: self.__netloc_label.set_text(self.__netloc) else: self.__netloc_label.set_text(title) else: self.__netloc_label.set_text(self.__netloc) def update_indicator(self, view): """ Update indicator (count and color) @param view as View """ i = 0 unread = False for view in self.__views: if view.webview.access_time == 0: unread = True i += 1 if unread: self.__indicator_label.show_indicator(True) else: self.__indicator_label.show_indicator(False) # We force value to 1, Eolie is going to add a new view if i == 0: i = 1 self.__indicator_label.set_text(str(i)) @property def empty(self): """ True if no view associated @return bool """ return len(self.__views) == 0 @property def views(self): """ Get views @return [view] """ return self.__views @property def netloc(self): """ Get netloc @return str """ return self.__netloc ####################### # PROTECTED # ####################### def _on_close_button_clicked(self, button): """ Close site @param button as Gtk.Button """ for view in self.__views: self.__window.container.pages_manager.try_close_view(view) def _on_scroll_event(self, eventbox, event): """ Switch between children @param eventbox as Gtk.EventBox @param event as Gdk.Event """ if event.direction == Gdk.ScrollDirection.UP: self.__window.container.pages_manager.previous() self.__window.container.pages_manager.ctrl_released() elif event.direction == Gdk.ScrollDirection.DOWN: self.__window.container.pages_manager.next() self.__window.container.pages_manager.ctrl_released() def _on_button_press_event(self, eventbox, event): """ Hide popover or close view @param eventbox as Gtk.EventBox @param event as Gdk.Event """ if event.button == 2: for view in self.__views: self.__window.container.pages_manager.try_close_view(view) return True elif event.button == 3: from eolie.menu_sites import SitesMenu menu = SitesMenu(self.__views, self.__window) popover = Gtk.Popover.new_from_model(eventbox, menu) popover.set_position(Gtk.PositionType.RIGHT) popover.forall(self.__update_popover_internals) popover.show() return True ####################### # PRIVATE # ####################### def __update_popover_internals(self, widget): """ Little hack to manage Gtk.ModelButton text @param widget as Gtk.Widget """ if isinstance(widget, Gtk.Label): widget.set_ellipsize(Pango.EllipsizeMode.END) widget.set_max_width_chars(40) widget.set_tooltip_text(widget.get_text()) elif hasattr(widget, "forall"): GLib.idle_add(widget.forall, self.__update_popover_internals) def __set_initial_artwork(self, uri, ephemeral=False): """ Set initial artwork on widget @param uri as str @param ephemeral as bool """ artwork = El().art.get_icon_theme_artwork( uri, ephemeral) if artwork is not None: self.__image.set_from_icon_name(artwork, Gtk.IconSize.INVALID) else: self.__image.set_from_icon_name("applications-internet", Gtk.IconSize.INVALID) def __on_query_tooltip(self, widget, x, y, keyboard, tooltip): """ Show tooltip if needed @param widget as Gtk.Widget @param x as int @param y as int @param keyboard as bool @param tooltip as Gtk.Tooltip """ tooltip = "<b>%s</b>" % GLib.markup_escape_text(self.__netloc) for view in self.__views: title = view.webview.get_title() if not title: title = view.webview.get_uri() tooltip += "\n%s" % GLib.markup_escape_text(title) widget.set_tooltip_markup(tooltip)
class PagesManagerChild(Gtk.FlowBoxChild): """ Child showing snapshot, title and favicon """ def __init__(self, view, window): """ Init child @param view as View @param window as Window """ Gtk.FlowBoxChild.__init__(self) self.__view = view self.__window = window self.__connected_ids = [] self.__scroll_timeout_id = None builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Eolie/PagesManagerChild.ui") builder.connect_signals(self) self.__title = LabelIndicator() self.__title.set_hexpand(True) self.__title.set_margin_right(4) self.__title.set_property("halign", Gtk.Align.CENTER) self.__title.set_property("valign", Gtk.Align.CENTER) self.__title.set_ellipsize(Pango.EllipsizeMode.END) self.__title.show() builder.get_object("grid").attach(self.__title, 0, 0, 1, 1) self.__image = builder.get_object("image") self.__close_button = builder.get_object("close_button") self.__audio_indicator = builder.get_object("audio_indicator") if view.webview.is_playing_audio(): self.__audio_indicator.show() if view.webview.ephemeral: self.__close_button.get_image().set_from_icon_name( "window-close-symbolic", Gtk.IconSize.INVALID) else: self.__close_button.get_image().set_from_icon_name( "applications-internet", Gtk.IconSize.INVALID) self.__close_button.get_image().set_property("pixel-size", ArtSize.FAVICON) self.__spinner = builder.get_object("spinner") self.add(builder.get_object("widget")) self.get_style_context().add_class("sidebar-item") self.set_property("has-tooltip", True) self.set_property("halign", Gtk.Align.START) self.set_margin_start(20) self.set_margin_end(20) self.set_margin_top(20) self.set_margin_bottom(20) self.set_size_request( ArtSize.START_WIDTH + ArtSize.PREVIEW_WIDTH_MARGIN, ArtSize.START_HEIGHT + ArtSize.PREVIEW_WIDTH_MARGIN) if view.webview.access_time == 0: self.get_style_context().add_class("sidebar-item-unread") self.connect("query-tooltip", self.__on_query_tooltip) self.connect("destroy", self.__on_destroy) self.__view_destroy_id = view.connect("destroy", self.__on_view_destroy) self.__connected_ids.append( self.__view.webview.connect("notify::favicon", self.__on_notify_favicon)) self.__connected_ids.append( self.__view.webview.connect("notify::is-playing-audio", self.__on_notify_is_playing_audio)) self.__connected_ids.append( self.__view.webview.connect("uri-changed", self.__on_uri_changed)) self.__connected_ids.append( self.__view.webview.connect("title-changed", self.__on_title_changed)) self.__connected_ids.append( self.__view.webview.connect("scroll-event", self.__on_scroll_event)) self.__connected_ids.append( self.__view.webview.connect("load-changed", self.__on_load_changed)) def set_snapshot(self, uri): """ Set webpage preview @param uri as str """ if uri == self.__view.webview.get_uri(): if self.__view.webview.ephemeral: self.__image.set_from_icon_name("user-not-tracked-symbolic", Gtk.IconSize.DIALOG) else: self.__view.webview.get_snapshot( WebKit2.SnapshotRegion.VISIBLE, WebKit2.SnapshotOptions.NONE, None, self.__on_snapshot, uri) @property def label_indicator(self): """ Get label indicator @return LabelIndicator """ return self.__title @property def view(self): """ Get linked view @return View """ return self.__view ####################### # PROTECTED # ####################### def _on_button_press_event(self, eventbox, event): """ Hide popover or close view @param eventbox as Gtk.EventBox @param event as Gdk.Event """ if event.button == 2: self.__window.container.pages_manager.try_close_view(self.__view) return True def _on_button_release_event(self, eventbox, event): """ @param eventbox as Gtk.EventBox @param event as Gdk.Event """ pass def _on_close_button_clicked(self, button): """ Destroy self @param button as Gtk.Button """ self.__window.container.pages_manager.try_close_view(self.__view) return True def _on_enter_notify_event(self, eventbox, event): """ Show close button @param eventbox as Gtk.EventBox @param event as Gdk.Event """ self.__close_button.get_image().set_from_icon_name( "window-close-symbolic", Gtk.IconSize.INVALID) def _on_leave_notify_event(self, eventbox, event): """ Show close button @param eventbox as Gtk.EventBox @param event as Gdk.Event """ allocation = eventbox.get_allocation() if event.x <= 0 or\ event.x >= allocation.width or\ event.y <= 0 or\ event.y >= allocation.height: self.__set_favicon() ####################### # PRIVATE # ####################### def __set_favicon(self): """ Set favicon """ resized = None uri = self.__view.webview.get_uri() surface = self.__view.webview.get_favicon() artwork = El().art.get_icon_theme_artwork( uri, self.__view.webview.ephemeral) if artwork is not None: self.__close_button.get_image().set_from_icon_name( artwork, Gtk.IconSize.INVALID) elif surface is not None: resized = resize_favicon(surface) if not El().art.exists(uri, "favicon"): El().art.save_artwork(uri, resized, "favicon") self.__set_favicon_related(resized, uri, self.__view.webview.initial_uri) self.__close_button.get_image().set_from_surface(resized) else: self.__close_button.get_image().set_from_icon_name( "applications-internet", Gtk.IconSize.INVALID) if resized is not None: self.__window.container.sites_manager.set_favicon( self.__view, resized) del resized def __set_favicon_related(self, surface, uri, initial_uri): """ Set favicon for initial uri @param surface as cairo.surface @param uri as str @param initial_uri as str """ parsed = urlparse(uri) initial_parsed = urlparse(initial_uri) if parsed.netloc == initial_parsed.netloc and\ initial_uri != uri and\ not El().art.exists(initial_uri, "favicon"): El().art.save_artwork(initial_uri, surface, "favicon") def __on_scroll_timeout(self): """ Update snapshot """ uri = self.__view.webview.get_uri() self.__scroll_timeout_id = None self.set_snapshot(uri) def __on_query_tooltip(self, widget, x, y, keyboard, tooltip): """ Show tooltip if needed @param widget as Gtk.Widget @param x as int @param y as int @param keyboard as bool @param tooltip as Gtk.Tooltip """ text = "" label = self.__title.get_text() uri = self.__view.webview.get_uri() # GLib.markup_escape_text if uri is None: text = "<b>%s</b>" % GLib.markup_escape_text(label) else: text = "<b>%s</b>\n%s" % (GLib.markup_escape_text(label), GLib.markup_escape_text(uri)) widget.set_tooltip_markup(text) def __on_destroy(self, widget): """ Disconnect signals @param widget as Gtk.Widget """ self.__window.container.sites_manager.remove_view(self.__view) while self.__connected_ids: connected_id = self.__connected_ids.pop(0) self.__view.webview.disconnect(connected_id) if self.__view_destroy_id is not None: self.__view.disconnect(self.__view_destroy_id) def __on_view_destroy(self, view): """ Destroy self @param view as View """ self.__connected_ids = [] self.__view_destroy_id = None self.__window.container.sites_manager.remove_view(self.__view) GLib.idle_add(self.destroy) def __on_notify_favicon(self, webview, favicon): """ Set favicon @param webview as WebView @param favicon as Gparam """ if self.__view.webview == webview: self.__set_favicon() def __on_notify_is_playing_audio(self, webview, playing): """ Update status @param webview as WebView @param playing as bool """ if not webview.is_loading() and webview.is_playing_audio(): self.__audio_indicator.show() else: self.__audio_indicator.hide() def __on_scroll_event(self, webview, event): """ Update snapshot @param webview as WebView @param event as Gdk.EventScroll """ if self.__scroll_timeout_id is not None: GLib.source_remove(self.__scroll_timeout_id) self.__scroll_timeout_id = GLib.timeout_add(250, self.__on_scroll_timeout) def __on_snapshot(self, webview, result, uri): """ Set snapshot on main image @param webview as WebView @param result as Gio.AsyncResult @param uri as str """ ART_RATIO = 1.5 # ArtSize.START_WIDTH / ArtSize.START_HEIGHT try: snapshot = webview.get_snapshot_finish(result) # Set start image scale factor ratio = snapshot.get_width() / snapshot.get_height() if ratio > ART_RATIO: factor = ArtSize.START_HEIGHT / snapshot.get_height() else: factor = ArtSize.START_WIDTH / snapshot.get_width() surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, ArtSize.START_WIDTH, ArtSize.START_HEIGHT) context = cairo.Context(surface) context.scale(factor, factor) context.set_source_surface(snapshot, factor, 0) context.paint() self.__image.set_from_surface(surface) except Exception as e: print("PagesManagerChild::__on_snapshot():", e) def __on_uri_changed(self, webview, uri): """ Update uri @param webview as WebView @param uri as str """ # Js change, update snapshot if not webview.is_loading() and\ not webview.ephemeral: GLib.timeout_add(500, self.set_snapshot, uri) else: self.__window.container.sites_manager.add_view_for_uri( self.__view, uri) def __on_title_changed(self, webview, title): """ Update title @param webview as WebView @param title as str """ self.__title.set_text(title) def __on_load_changed(self, webview, event): """ Update widget content @param webview as WebView @param event as WebKit2.LoadEvent """ uri = webview.get_uri() if event == WebKit2.LoadEvent.STARTED: self.__image.clear() self.__audio_indicator.hide() self.__spinner.start() self.__title.set_text(uri) elif event == WebKit2.LoadEvent.COMMITTED: self.__title.set_text(uri) elif event == WebKit2.LoadEvent.FINISHED: self.__spinner.stop() if webview.is_playing_audio(): self.__audio_indicator.show() GLib.timeout_add(500, self.set_snapshot, uri)
class PagesManagerChild(Gtk.FlowBoxChild): """ Child showing snapshot, title and favicon """ def __init__(self, view, window): """ Init child @param view as View @param window as Window """ Gtk.FlowBoxChild.__init__(self) self.__view = view self.__window = window self.__favicon = None self.__connected_ids = [] self.__scroll_timeout_id = None builder = Gtk.Builder() builder.add_from_resource("/org/gnome/Eolie/PagesManagerChild.ui") builder.connect_signals(self) self.__indicator_label = LabelIndicator() self.__indicator_label.mark_unshown(view.webview) self.__indicator_label.set_hexpand(True) self.__indicator_label.set_margin_right(4) self.__indicator_label.set_property("halign", Gtk.Align.CENTER) self.__indicator_label.set_property("valign", Gtk.Align.CENTER) self.__indicator_label.set_ellipsize(Pango.EllipsizeMode.END) self.__indicator_label.show() if view.webview.title: self.__indicator_label.set_text(view.webview.title) builder.get_object("grid").attach(self.__indicator_label, 0, 0, 1, 1) self.__image = builder.get_object("image") self.__close_button = builder.get_object("close_button") self.__audio_indicator = builder.get_object("audio_indicator") if view.webview.is_playing_audio(): self.__audio_indicator.show() self.__close_button.get_image().set_from_icon_name( "window-close-symbolic", Gtk.IconSize.INVALID) self.__close_button.get_image().set_property("pixel-size", ArtSize.FAVICON) self.__spinner = builder.get_object("spinner") self.add(builder.get_object("widget")) self.get_style_context().add_class("sidebar-item") self.set_property("has-tooltip", True) self.set_property("halign", Gtk.Align.START) self.set_margin_start(20) self.set_margin_end(20) self.set_margin_top(20) self.set_margin_bottom(20) self.set_size_request(ArtSize.START_WIDTH + ArtSize.PREVIEW_WIDTH_MARGIN, ArtSize.START_HEIGHT + ArtSize.PREVIEW_WIDTH_MARGIN) self.connect("query-tooltip", self.__on_query_tooltip) view.connect("destroying", self.__on_view_destroying) self.__connected_signals = [] self.__connected_signals.append( self.__view.webview.connect("favicon-changed", self.__on_webview_favicon_changed)) self.__connected_signals.append( self.__view.webview.connect( "notify::is-playing-audio", self.__on_webview_notify_is_playing_audio)) self.__connected_signals.append( self.__view.webview.connect("uri-changed", self.__on_webview_uri_changed)) self.__connected_signals.append( self.__view.webview.connect("title-changed", self.__on_webview_title_changed)) self.__connected_signals.append( self.__view.webview.connect("scroll-event", self.__on_webview_scroll_event)) self.__connected_signals.append( self.__view.webview.connect("load-changed", self.__on_webview_load_changed)) self.__connected_signals.append( self.__view.webview.connect("shown", self.__on_webview_shown)) self.__set_favicon_artwork() if self.__view.webview.uri is not None: artwork_path = El().art.get_path(self.__view.webview.uri, "start") if artwork_path is not None and\ GLib.file_test(artwork_path, GLib.FileTest.IS_REGULAR): self.__image.set_from_file(artwork_path) def destroy(self): """ Disconnect signals and destroy self """ for signal_id in self.__connected_signals: self.__view.webview.disconnect(signal_id) Gtk.FlowBoxChild.destroy(self) @property def view(self): """ Get linked view @return View """ return self.__view ####################### # PROTECTED # ####################### def _on_button_press_event(self, eventbox, event): """ Hide popover or close view @param eventbox as Gtk.EventBox @param event as Gdk.Event """ if event.button == 2: self.__window.container.try_close_view(self.__view) return True elif event.button == 3: from eolie.menu_move_to import MoveToMenu moveto_menu = MoveToMenu([self.__view], self.__window, False) moveto_menu.show() popover = Gtk.PopoverMenu.new() popover.set_relative_to(eventbox) popover.set_position(Gtk.PositionType.BOTTOM) popover.add(moveto_menu) popover.forall(self.__update_popover_internals) popover.show() return True def _on_button_release_event(self, eventbox, event): """ @param eventbox as Gtk.EventBox @param event as Gdk.Event """ pass def _on_close_button_clicked(self, button): """ Destroy self @param button as Gtk.Button """ self.__window.container.try_close_view(self.__view) return True def _on_enter_notify_event(self, eventbox, event): """ Show close button @param eventbox as Gtk.EventBox @param event as Gdk.Event """ self.__close_button.get_image().set_from_icon_name( "window-close-symbolic", Gtk.IconSize.INVALID) def _on_leave_notify_event(self, eventbox, event): """ Show close button @param eventbox as Gtk.EventBox @param event as Gdk.Event """ allocation = eventbox.get_allocation() if event.x <= 0 or\ event.x >= allocation.width or\ event.y <= 0 or\ event.y >= allocation.height: self.__set_favicon_artwork() ####################### # PRIVATE # ####################### def __update_popover_internals(self, widget): """ Little hack to manage Gtk.ModelButton text @param widget as Gtk.Widget """ if isinstance(widget, Gtk.Label): widget.set_ellipsize(Pango.EllipsizeMode.END) widget.set_max_width_chars(40) widget.set_tooltip_text(widget.get_text()) elif hasattr(widget, "forall"): GLib.idle_add(widget.forall, self.__update_popover_internals) def __set_favicon_artwork(self): """ Set favicon artwork """ if self.__view.webview.ephemeral: return image = self.__close_button.get_image() if self.__favicon is not None: image.set_from_surface(self.__favicon) else: uri = self.__view.webview.uri artwork = El().art.get_icon_theme_artwork( uri, self.view.webview.ephemeral) if artwork is not None: image.set_from_icon_name(artwork, Gtk.IconSize.INVALID) else: favicon_path = El().art.get_favicon_path(uri) if favicon_path is not None: image.set_from_file(favicon_path) else: image.set_from_icon_name("applications-internet", Gtk.IconSize.INVALID) def __set_snapshot(self): """ Set webpage preview """ if self.__view.webview.ephemeral: self.__image.set_from_icon_name( "user-not-tracked-symbolic", Gtk.IconSize.DIALOG) else: self.__view.webview.get_snapshot( WebKit2.SnapshotRegion.VISIBLE, WebKit2.SnapshotOptions.NONE, None, get_snapshot, self.__on_snapshot) def __on_scroll_timeout(self): """ Update snapshot """ self.__scroll_timeout_id = None self.__set_snapshot() def __on_query_tooltip(self, widget, x, y, keyboard, tooltip): """ Show tooltip if needed @param widget as Gtk.Widget @param x as int @param y as int @param keyboard as bool @param tooltip as Gtk.Tooltip """ text = "" label = self.__indicator_label.get_text() uri = self.__view.webview.uri # GLib.markup_escape_text if uri is None: text = "<b>%s</b>" % GLib.markup_escape_text(label) else: text = "<b>%s</b>\n%s" % (GLib.markup_escape_text(label), GLib.markup_escape_text(uri)) widget.set_tooltip_markup(text) def __on_view_destroying(self, view): """ Destroy self @param view as View """ self.destroy() def __on_webview_favicon_changed(self, webview, favicon, icon_theme_artwork): """ Set favicon @param webview as WebView @param favicon as cairo.Surface @param icon_theme_artwork as str """ if favicon is not None: self.__favicon = favicon self.__close_button.get_image().set_from_surface(favicon) elif icon_theme_artwork is not None: self.__close_button.get_image().set_from_icon_name( icon_theme_artwork, Gtk.IconSize.INVALID) def __on_webview_notify_is_playing_audio(self, webview, playing): """ Update status @param webview as WebView @param playing as bool """ if not webview.is_loading() and webview.is_playing_audio(): self.__audio_indicator.show() else: self.__audio_indicator.hide() def __on_webview_scroll_event(self, webview, event): """ Update snapshot @param webview as WebView @param event as Gdk.EventScroll """ if self.__scroll_timeout_id is not None: GLib.source_remove(self.__scroll_timeout_id) self.__scroll_timeout_id = GLib.timeout_add(250, self.__on_scroll_timeout) def __on_snapshot(self, surface): """ Set snapshot @param surface as cairo.Surface """ self.__image.set_from_surface(surface) def __on_webview_uri_changed(self, webview, uri): """ Update uri @param webview as WebView @param uri as str """ # Js change, update snapshot if not webview.is_loading() and not webview.ephemeral: GLib.timeout_add(500, self.__set_snapshot) def __on_webview_title_changed(self, webview, title): """ Update title @param webview as WebView @param title as str """ self.__indicator_label.set_text(title) def __on_webview_load_changed(self, webview, event): """ Update widget content @param webview as WebView @param event as WebKit2.LoadEvent """ uri = webview.uri if event == WebKit2.LoadEvent.STARTED: self.__favicon = None self.__close_button.get_image().set_from_icon_name( "applications-internet", Gtk.IconSize.INVALID) self.__image.clear() self.__audio_indicator.hide() self.__spinner.start() self.__indicator_label.set_text(uri) elif event == WebKit2.LoadEvent.COMMITTED: self.__indicator_label.set_text(uri) elif event == WebKit2.LoadEvent.FINISHED: self.__spinner.stop() if webview.is_playing_audio(): self.__audio_indicator.show() GLib.timeout_add(500, self.__set_snapshot) def __on_webview_shown(self, webview): """ Remove indicator """ self.__indicator_label.mark_shown(webview)