1# Copyright (c) 2017-2021 Cedric Bellegarde <cedric.bellegarde@adishatz.org> 2# This program is free software: you can redistribute it and/or modify 3# it under the terms of the GNU General Public License as published by 4# the Free Software Foundation, either version 3 of the License, or 5# (at your option) any later version. 6# This program is distributed in the hope that it will be useful, 7# but WITHOUT ANY WARRANTY; without even the implied warranty of 8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9# GNU General Public License for more details. 10# You should have received a copy of the GNU General Public License 11# along with this program. If not, see <http://www.gnu.org/licenses/>. 12 13from gi.repository import Gtk, Gdk, GLib, WebKit2 14 15from eolie.sites_manager_child import SitesManagerChild 16from eolie.define import App, LoadingType, MARGIN_SMALL 17from eolie.utils import get_safe_netloc, update_popover_internals 18 19 20class SitesManager(Gtk.Grid): 21 """ 22 Site manager (merged netloc of opened pages) 23 """ 24 25 def __init__(self, window): 26 """ 27 Init stack 28 @param window as Window 29 """ 30 Gtk.Grid.__init__(self) 31 self.set_orientation(Gtk.Orientation.VERTICAL) 32 self.__window = window 33 self.__initial_sort = [] 34 self.__show_labels = False 35 self.set_property("width-request", 50) 36 self.get_style_context().add_class("sidebar") 37 self.__scrolled = Gtk.ScrolledWindow() 38 self.__scrolled.set_policy(Gtk.PolicyType.NEVER, 39 Gtk.PolicyType.AUTOMATIC) 40 self.__scrolled.set_vexpand(True) 41 self.__scrolled.set_hexpand(True) 42 self.__scrolled.show() 43 viewport = Gtk.Viewport() 44 viewport.show() 45 self.__scrolled.add(viewport) 46 self.set_hexpand(False) 47 48 self.__box = Gtk.ListBox.new() 49 self.__box.set_activate_on_single_click(True) 50 self.__box.set_selection_mode(Gtk.SelectionMode.NONE) 51 self.__box.set_margin_start(2) 52 self.__box.set_margin_end(2) 53 self.__box.set_margin_top(2) 54 self.__box.set_margin_bottom(2) 55 self.__box.show() 56 self.__box.connect("row-activated", self.__on_row_activated) 57 58 viewport.set_property("valign", Gtk.Align.START) 59 viewport.add(self.__box) 60 61 menu_button = Gtk.Button.new_from_icon_name( 62 "view-more-horizontal-symbolic", Gtk.IconSize.BUTTON) 63 menu_button.show() 64 menu_button.get_style_context().add_class("overlay-button") 65 menu_button.set_property("margin", MARGIN_SMALL) 66 menu_button.connect("clicked", self.__on_menu_button_clicked) 67 68 self.add(self.__scrolled) 69 self.add(menu_button) 70 71 def add_webview(self, webview): 72 """ 73 Add a new web view to monitor 74 @param webview as WebView 75 """ 76 # Force update 77 if webview.uri: 78 self.__on_webview_load_changed(webview, 79 WebKit2.LoadEvent.COMMITTED) 80 webview.connect("load-changed", self.__on_webview_load_changed) 81 webview.connect("destroy", self.__on_webview_destroy) 82 83 def remove_webview(self, webview): 84 """ 85 Remove web view from pages manager 86 @param webview as WebView 87 """ 88 count = len(self.__box.get_children()) 89 for site in self.__box.get_children(): 90 site.remove_webview(webview) 91 if site.empty and count > 1: 92 site.destroy() 93 webview.disconnect_by_func(self.__on_webview_load_changed) 94 webview.disconnect_by_func(self.__on_webview_destroy) 95 96 def next(self): 97 """ 98 Show next site 99 """ 100 current = None 101 children = self.__box.get_children() 102 for child in children: 103 if child.get_state_flags() & Gtk.StateFlags.VISITED: 104 current = child 105 child.unset_state_flags(Gtk.StateFlags.VISITED) 106 index = current.get_index() 107 if index + 1 < len(children): 108 next_row = self.__box.get_row_at_index(index + 1) 109 else: 110 next_row = self.__box.get_row_at_index(0) 111 if next_row is not None: 112 next_row.set_state_flags(Gtk.StateFlags.VISITED, False) 113 self.__window.container.set_visible_webview(next_row.webviews[0]) 114 if len(next_row.webviews) == 1: 115 self.__window.container.set_expose(False) 116 else: 117 self.__window.container.pages_manager.set_filter( 118 next_row.netloc) 119 self.__window.container.set_expose(True) 120 121 def previous(self): 122 """ 123 Show previous site 124 """ 125 current = None 126 children = self.__box.get_children() 127 for child in children: 128 if child.get_state_flags() & Gtk.StateFlags.VISITED: 129 current = child 130 child.unset_state_flags(Gtk.StateFlags.VISITED) 131 index = current.get_index() 132 if index == 0: 133 next_row = self.__box.get_row_at_index(len(children) - 1) 134 else: 135 next_row = self.__box.get_row_at_index(index - 1) 136 if next_row is not None: 137 next_row.set_state_flags(Gtk.StateFlags.VISITED, False) 138 self.__window.container.set_visible_webview(next_row.webviews[0]) 139 if len(next_row.webviews) == 1: 140 self.__window.container.set_expose(False) 141 else: 142 self.__window.container.pages_manager.set_filter( 143 next_row.netloc) 144 self.__window.container.set_expose(True) 145 146 def update_visible_child(self): 147 """ 148 Mark current child as visible 149 Unmark all others 150 """ 151 current = self.__window.container.webview 152 for child in self.__box.get_children(): 153 if current in child.webviews: 154 child.set_state_flags(Gtk.StateFlags.VISITED, False) 155 child.update_favicon() 156 # Wait loop empty: will fails otherwise if child just created 157 GLib.idle_add(self.__scroll_to_child, child) 158 else: 159 child.unset_state_flags(Gtk.StateFlags.VISITED) 160 161 def set_initial_sort(self, sort): 162 """ 163 Set initial site sort 164 @param sort as [str] 165 """ 166 if sort: 167 self.__box.set_sort_func(self.__sort_func) 168 else: 169 self.__box.set_sort_func(None) 170 self.__initial_sort = sort 171 172 def update_shown_state(self, webview): 173 """ 174 Update shown state for webview 175 @param webview as WebView 176 """ 177 for child in self.__box.get_children(): 178 for _webview in child.webviews: 179 if _webview == webview: 180 child.indicator_label.mark(webview) 181 return 182 183 def show_labels(self, show): 184 """ 185 Show labels on children 186 @param show as bool 187 """ 188 self.__show_labels = show 189 for child in self.__box.get_children(): 190 child.show_label(show) 191 192 @property 193 def sort(self): 194 """ 195 Get current sort 196 @return [str] 197 """ 198 sort = [] 199 for child in self.__box.get_children(): 200 sort.append(child.netloc) 201 return sort 202 203####################### 204# PRIVATE # 205####################### 206 def __sort_func(self, row1, row2): 207 """ 208 Sort rows based on inital sort 209 @param row1 as Gtk.ListBoxRow 210 @param row2 as Gtk.ListBoxRow 211 """ 212 try: 213 index1 = self.__initial_sort.index(row1.netloc) 214 index2 = self.__initial_sort.index(row2.netloc) 215 return index1 > index2 216 except: 217 return False 218 219 def __get_index(self, netloc): 220 """ 221 Get child index 222 @param netloc as str 223 @return int 224 """ 225 # Search current index 226 children = self.__box.get_children() 227 index = 0 228 for child in children: 229 if child.netloc == netloc: 230 break 231 index += 1 232 return index 233 234 def __scroll_to_child(self, child): 235 """ 236 Scroll to child 237 @param child as SitesManagerChild 238 """ 239 adj = self.__scrolled.get_vadjustment() 240 if adj is None: 241 return 242 value = adj.get_value() 243 coordinates = child.translate_coordinates(self.__box, 0, 0) 244 if coordinates is None: 245 return 246 y = coordinates[1] 247 if y + child.get_allocated_height() >\ 248 self.__scrolled.get_allocated_height() + value or\ 249 y - child.get_allocated_height() < 0 + value: 250 self.__scrolled.get_vadjustment().set_value(y) 251 252 def __on_webview_destroy(self, webview): 253 """ 254 Clean children 255 @param webview as WebView 256 """ 257 self.remove_webview(webview) 258 259 def __on_webview_load_changed(self, webview, event): 260 """ 261 Update children 262 @param webview as WebView 263 @param event as WebKit2.LoadEvent 264 """ 265 if event != WebKit2.LoadEvent.COMMITTED: 266 return 267 netloc = get_safe_netloc(webview.uri) 268 child = None 269 empty_child = None 270 # Do not group by netloc 271 if webview.is_ephemeral: 272 for site in self.__box.get_children(): 273 if site.is_ephemeral: 274 child = site 275 break 276 else: 277 # Search for a child for wanted netloc 278 # Clean up any child matching webview, allowing us to reuse it 279 for site in self.__box.get_children(): 280 if site.netloc == netloc and site.is_ephemeral is False: 281 child = site 282 else: 283 site.remove_webview(webview) 284 if site.empty: 285 empty_child = site 286 287 if child is None: 288 # We need to create a new child 289 if empty_child is None: 290 child = SitesManagerChild(netloc, 291 self.__window, 292 webview.is_ephemeral) 293 child.show() 294 child.add_webview(webview) 295 child.show_label(self.__show_labels) 296 self.__box.add(child) 297 self.update_visible_child() 298 # Use empty child 299 else: 300 child = empty_child 301 child.reset(netloc) 302 child.add_webview(webview) 303 # We already have a child for this netloc 304 else: 305 # Webview previous child is empty, destroy it 306 if empty_child is not None: 307 empty_child.destroy() 308 child.add_webview(webview) 309 self.update_visible_child() 310 # Webview really loaded 311 if webview.get_uri() is not None: 312 child.on_webview_load_changed(webview, event) 313 314 def __on_row_activated(self, listbox, child): 315 """ 316 Show wanted expose 317 @param listbox as Gtk.ListBox 318 @param child as SitesManagerChild 319 """ 320 webviews = child.webviews 321 if len(webviews) == 1: 322 self.__window.container.set_visible_webview(webviews[0]) 323 else: 324 from eolie.pages_manager_list import PagesManagerList 325 widget = PagesManagerList(self.__window) 326 widget.show() 327 widget.populate(webviews) 328 popover = Gtk.Popover.new(child) 329 popover.set_modal(False) 330 self.__window.register(popover) 331 popover.get_style_context().add_class("box-shadow") 332 popover.set_position(Gtk.PositionType.RIGHT) 333 popover.add(widget) 334 popover.popup() 335 336 def __on_button_press(self, widget, event): 337 """ 338 Hide popover if visible 339 @param widget as Gtk.Widget 340 @param event as Gdk.EventButton 341 """ 342 if event.type == Gdk.EventType._2BUTTON_PRESS: 343 self.__window.container.add_webview(App().start_page, 344 LoadingType.FOREGROUND) 345 return self.__window.close_popovers() 346 347 def __on_menu_button_clicked(self, button): 348 """ 349 Show pages menu 350 @param button as Gtk.Button 351 """ 352 self.__window.close_popovers() 353 popover = Gtk.Popover.new_from_model(button, App().pages_menu) 354 popover.set_modal(False) 355 self.__window.register(popover) 356 popover.forall(update_popover_internals) 357 popover.popup() 358