container.py 22.3 KB
Newer Older
Cédric Bellegarde's avatar
Cédric Bellegarde committed
1
# Copyright (c) 2017 Cedric Bellegarde <cedric.bellegarde@adishatz.org>
Cédric Bellegarde's avatar
Cédric Bellegarde committed
2 3 4 5 6 7 8 9 10 11 12
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

13
from gi.repository import Gtk, WebKit2, GLib, Gdk
Cédric Bellegarde's avatar
Cédric Bellegarde committed
14

15
from urllib.parse import urlparse
16
from time import time
17

18
from eolie.view_web import WebView
19
from eolie.view import View
20
from eolie.popover_webview import WebViewPopover
21
from eolie.define import El, PanelMode, Indicator
Cédric Bellegarde's avatar
Cédric Bellegarde committed
22 23


24
class Container(Gtk.Overlay):
Cédric Bellegarde's avatar
Cédric Bellegarde committed
25 26 27 28
    """
        Main Eolie view
    """

Cédric Bellegarde's avatar
Cédric Bellegarde committed
29
    def __init__(self, window):
Cédric Bellegarde's avatar
Cédric Bellegarde committed
30
        """
31
            Ini.container
Cédric Bellegarde's avatar
Cédric Bellegarde committed
32
            @param window as Window
Cédric Bellegarde's avatar
Cédric Bellegarde committed
33
        """
34
        Gtk.Overlay.__init__(self)
35
        self.__window = window
36
        self.__history_queue = []
37
        self.__pages_overlay = None
38
        self.__popover = WebViewPopover(window)
39
        if El().sync_worker is not None:
40 41
            El().sync_worker.connect("sync-finished",
                                     self.__on_sync_finished)
Cédric Bellegarde's avatar
Cédric Bellegarde committed
42
        self.__stack = Gtk.Stack()
Cédric Bellegarde's avatar
Cédric Bellegarde committed
43 44 45 46
        self.__stack.set_hexpand(True)
        self.__stack.set_vexpand(True)
        self.__stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
        self.__stack.set_transition_duration(150)
Cédric Bellegarde's avatar
Cédric Bellegarde committed
47
        self.__stack.show()
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62

        self.__grid_stack = Gtk.Stack()
        self.__grid_stack.set_hexpand(True)
        self.__grid_stack.set_vexpand(True)
        self.__grid_stack.set_transition_type(
                                         Gtk.StackTransitionType.CROSSFADE)
        self.__grid_stack.set_transition_duration(150)
        self.__grid_stack.show()
        self.__grid = Gtk.Grid()
        # Attach at position 1 to let place to pages_manager
        self.__grid.attach(self.__stack, 1, 0, 1, 1)
        self.__grid.show()
        self.__pages_manager = None
        self.__grid_stack.add_named(self.__grid, "grid")
        self.add(self.__grid_stack)
63
        self.connect("unmap", self.__on_unmap)
Cédric Bellegarde's avatar
Cédric Bellegarde committed
64

Cédric Bellegarde's avatar
Cédric Bellegarde committed
65
    def add_webview(self, uri, window_type, ephemeral=False,
66
                    parent=None, state=None, load=True):
Cédric Bellegarde's avatar
Cédric Bellegarde committed
67
        """
68
            Add a web view t.container
Cédric Bellegarde's avatar
Cédric Bellegarde committed
69
            @param uri as str
70
            @param window_type as Gdk.WindowType
71
            @param parent as View
72
            @param ephemeral as bool
73
            @param state as WebViewSessionState
Cédric Bellegarde's avatar
Cédric Bellegarde committed
74
        """
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
        webview = View.get_new_webview(ephemeral, self.__window)
        if state is not None:
            webview.restore_session_state(state)
        self.add_view(webview, parent, window_type)
        if uri is not None:
            if load:
                panel_mode = El().settings.get_enum("panel-mode")
                # Do not load uri until we are on screen
                GLib.idle_add(webview.load_uri, uri)
                # Notify user about new window
                if window_type == Gdk.WindowType.OFFSCREEN and\
                        panel_mode == PanelMode.NONE:
                    GLib.idle_add(
                        self.__add_overlay_view, webview)
            else:
                webview.set_delayed_uri(uri)
                webview.emit("title-changed", uri)
Cédric Bellegarde's avatar
Cédric Bellegarde committed
92

93
    def add_view(self, webview, parent, window_type):
94
        """
95
            Add view t.container
96 97
            @param webview as WebView
            @param parent as WebView
98 99
            @param window_type as Gdk.WindowType
        """
100 101
        view = self.__get_new_view(webview, parent)
        view.show()
102 103 104 105
        self.__pages_manager.add_child(view)
        # Force window type as current window is not visible
        if self.__grid_stack.get_visible_child_name() == "expose":
            window_type = Gdk.WindowType.OFFSCREEN
106 107 108 109
        if window_type == Gdk.WindowType.CHILD:
            self.__stack.add(view)
            self.__stack.set_visible_child(view)
        elif window_type == Gdk.WindowType.OFFSCREEN:
110
            panel_mode = El().settings.get_enum("panel-mode")
111 112 113
            # Little hack, we force webview to be shown (offscreen)
            # This allow getting snapshots from webkit
            window = Gtk.OffscreenWindow.new()
Cédric Bellegarde's avatar
Cédric Bellegarde committed
114
            if panel_mode == PanelMode.NONE:
115 116 117 118
                width = self.get_allocated_width()
            else:
                width = self.get_allocated_width() -\
                    self.__pages_manager.get_allocated_width()
119 120 121 122 123 124
            view.set_size_request(width, self.get_allocated_height())
            window.add(view)
            window.show()
            window.remove(view)
            view.set_size_request(-1, -1)
            self.__stack.add(view)
125
        self.__pages_manager.update_visible_child()
126
        # Do not count container views as destroy may be pending on somes
127
        count = str(len(self.__pages_manager.children))
128
        self.__window.toolbar.actions.count_label.set_text(count)
129

Cédric Bellegarde's avatar
Cédric Bellegarde committed
130 131 132 133 134
    def load_uri(self, uri):
        """
            Load uri in current view
            @param uri as str
        """
135
        if self.current is not None:
Cédric Bellegarde's avatar
Cédric Bellegarde committed
136
            self.current.webview.load_uri(uri)
137

138 139 140 141 142 143 144 145 146 147
    def get_view_for_webview(self, webview):
        """
            @param webview as WebView
            @return view as View
        """
        for child in self.__stack.get_children():
            if child.webview == webview:
                return child
        return None

148 149 150
    def set_visible_view(self, view):
        """
            Set visible view
Cédric Bellegarde's avatar
Cédric Bellegarde committed
151
            @param view as View
152
        """
153 154 155 156 157 158 159
        # Remove from offscreen window if needed
        # Will kill running get_snapshot :-/
        parent = view.get_parent()
        if parent is not None and isinstance(parent, Gtk.OffscreenWindow):
            parent.remove(view)
            view.set_size_request(-1, -1)
            self.__stack.add(view)
160
        self.__stack.set_visible_child(view)
161 162
        if self.__pages_overlay is not None:
            self.__pages_overlay.destroy_child(view)
163

164 165 166 167 168
    def stop(self):
        """
            Stop pending tasks
        """
        if El().sync_worker is not None:
169
            self.__on_sync_finished(El().sync_worker)
170

171
    def popup_webview(self, webview, destroy):
172 173 174
        """
            Show webview in popopver
            @param webview as WebView
175
            @param destroy webview when popover hidden
176
        """
177
        view = View(webview, None, self.__window)
178
        view.webview.connect("create", self.__on_create)
179
        view.show()
180
        self.__popover.add_view(view, destroy)
181 182 183 184 185 186
        if not self.__popover.is_visible():
            self.__popover.set_size_request(
                                 self.__window.get_allocated_width() / 3,
                                 self.__window.get_allocated_height() / 1.5)
            self.__popover.set_relative_to(self.__window.toolbar)
            self.__popover.set_position(Gtk.PositionType.BOTTOM)
Cédric Bellegarde's avatar
Cédric Bellegarde committed
187
            self.__popover.popup()
188

189
    def set_expose(self, expose, search=False):
190 191
        """
            Show current views
192 193
            @param expose as bool
            @param search as bool
194
        """
Cédric Bellegarde's avatar
Cédric Bellegarde committed
195
        # Show search bar
196 197
        child = self.__grid_stack.get_child_by_name("expose")
        if child is not None:
198
            GLib.timeout_add(500, child.set_filtered, search and expose)
Cédric Bellegarde's avatar
Cédric Bellegarde committed
199
        # Show expose mode
200 201
        if expose:
            self.__grid_stack.set_visible_child_name("expose")
202 203 204
            if self.__pages_overlay is not None:
                self.__pages_overlay.destroy()
                self.__pages_overlay = None
205 206 207
        else:
            self.__grid_stack.set_visible_child_name("grid")
            self.__window.toolbar.actions.view_button.set_active(False)
208
            GLib.idle_add(self.__pages_manager.move_first, self.current)
209 210 211 212 213 214 215 216

    def update_pages_manager(self, panel_mode):
        """
            Switch pages manager
            @param panel mode as int
        """
        views = []
        if self.__pages_manager is not None:
217 218
            for child in self.__pages_manager.children:
                views.append(child.view)
219
            self.__pages_manager.destroy()
220 221 222
        if self.__pages_overlay is not None:
            self.__pages_overlay.destroy()
            self.__pages_overlay = None
Cédric Bellegarde's avatar
Cédric Bellegarde committed
223
        if panel_mode == PanelMode.NONE:
Cédric Bellegarde's avatar
Cédric Bellegarde committed
224 225
            from eolie.pages_manager_flowbox import PagesManagerFlowBox
            self.__pages_manager = PagesManagerFlowBox(self.__window)
226 227
            self.__grid_stack.add_named(self.__pages_manager, "expose")
        else:
Cédric Bellegarde's avatar
Cédric Bellegarde committed
228 229
            from eolie.pages_manager_listbox import PagesManagerListBox
            self.__pages_manager = PagesManagerListBox(self.__window)
230 231 232
            self.__grid.attach(self.__pages_manager, 0, 0, 1, 1)
        self.__pages_manager.show()
        for view in views:
233
            self.__pages_manager.add_child(view)
234 235
        self.__pages_manager.update_visible_child()

236 237 238 239 240
    def on_view_map(self, webview):
        """
            Update window
            @param webview as WebView
        """
241 242 243 244 245 246 247 248
        if webview == self.__stack.get_visible_child().webview:
            uri = webview.delayed_uri
            if uri is None:
                uri = webview.get_uri()
            else:
                webview.load_uri(uri)
            title = webview.get_title()
            self.__window.toolbar.title.update_load_indicator(webview)
249 250 251 252
            if webview.popups:
                self.__window.toolbar.title.show_indicator(Indicator.POPUPS)
            else:
                self.__window.toolbar.title.show_indicator(Indicator.NONE)
253 254 255 256 257 258
            if uri is not None:
                self.__window.toolbar.title.set_uri(uri)
            if webview.is_loading():
                self.__window.toolbar.title.progress.show()
            else:
                self.__window.toolbar.title.progress.hide()
259 260
                self.__window.toolbar.title.show_readable_button(
                                                webview.readable_content != "")
261 262 263 264
            if title:
                self.__window.toolbar.title.set_title(title)
            elif uri:
                self.__window.toolbar.title.set_title(uri)
265
            self.__window.toolbar.actions.set_actions(webview)
266

267 268 269 270 271 272
    def set_panel_mode(self, panel_mode):
        """
            Set panel mode
            @param panel_mode as int
        """
        self.update_pages_manager(panel_mode)
273
        self.pages_manager.set_panel_mode()
274

275
    @property
276
    def pages_manager(self):
277
        """
278
            Get page manager
279
            @return PagesManager
280
        """
281
        return self.__pages_manager
282

Cédric Bellegarde's avatar
Cédric Bellegarde committed
283 284 285 286 287 288 289 290
    @property
    def views(self):
        """
            Get views
            @return views as [WebView]
        """
        return self.__stack.get_children()

291 292 293 294 295 296 297 298 299 300 301
    @property
    def current(self):
        """
            Current view
            @return WebView
        """
        return self.__stack.get_visible_child()

#######################
# PRIVATE             #
#######################
302
    def __get_new_view(self, webview, parent):
303
        """
304
            Get a new view
305
            @param parent as webview
306
            @param webview as WebView
307 308
            @return View
        """
309
        view = View(webview, parent, self.__window)
310
        view.webview.connect("map", self.on_view_map)
311 312 313 314
        view.webview.connect("notify::estimated-load-progress",
                             self.__on_estimated_load_progress)
        view.webview.connect("load-changed", self.__on_load_changed)
        view.webview.connect("button-press-event", self.__on_button_press)
315
        view.webview.connect("uri-changed", self.__on_uri_changed)
316
        view.webview.connect("title-changed", self.__on_title_changed)
317 318 319
        view.webview.connect("enter-fullscreen", self.__on_enter_fullscreen)
        view.webview.connect("leave-fullscreen", self.__on_leave_fullscreen)
        view.webview.connect("readable", self.__on_readable)
320
        view.webview.connect("new-page", self.__on_new_page)
321
        view.webview.connect("create", self.__on_create)
322
        view.webview.connect("close", self.__on_close)
323
        view.webview.connect("save-password", self.__on_save_password)
324
        view.webview.connect("script-dialog", self.__on_script_dialog)
325 326
        view.webview.connect("insecure-content-detected",
                             self.__on_insecure_content_detected)
327 328
        view.show()
        return view
329

330 331 332 333
    def __grab_focus_on_current(self):
        """
            Grab focus on current view
        """
334
        self.current.webview.grab_focus()
335

336 337 338 339 340
    def __add_overlay_view(self, webview):
        """
            Add an overlay view
            @param webview as WebView
        """
341
        from eolie.pages_overlay import PagesOverlay
342
        view = self.get_view_for_webview(webview)
343 344 345 346 347
        if self.__pages_overlay is None:
            self.__pages_overlay = PagesOverlay(self.__window)
            self.add_overlay(self.__pages_overlay)
        self.__pages_overlay.show()
        self.__pages_overlay.add_child(view)
348

349
    def __on_new_page(self, webview, uri, window_type):
350 351
        """
            Open a new page, switch to view if show is True
352
            @param webview as WebView
353
            @param uri as str
354
            @param window_type as Gdk.WindowType
355
        """
356
        if uri:
357
            if window_type == Gdk.WindowType.SUBSURFACE:
358
                if webview.ephemeral:
359
                    webview = WebView.new_ephemeral(self.__window)
360
                else:
361
                    webview = WebView.new(self.__window)
Cédric Bellegarde's avatar
Cédric Bellegarde committed
362
                self.popup_webview(webview, True)
363 364
                GLib.idle_add(webview.load_uri, uri)
            else:
365
                parent = self.get_view_for_webview(webview)
Cédric Bellegarde's avatar
Cédric Bellegarde committed
366
                self.add_webview(uri, window_type, webview.ephemeral, parent)
367

368
    def __on_create(self, related, navigation_action):
369 370
        """
            Create a new view for action
371
            @param related as WebView
372
            @param navigation_action as WebKit2.NavigationAction
373
            @param force as bool
374
        """
375
        webview = WebView.new_with_related_view(related, self.__window)
376 377 378
        webview.connect("ready-to-show",
                        self.__on_ready_to_show,
                        related,
379
                        navigation_action)
380 381 382 383 384
        return webview

    def __on_close(self, webview):
        """
            Close my self
385
            @param webview as WebView
386
        """
387
        view = self.get_view_for_webview(webview)
388
        if view is not None:
Cédric Bellegarde's avatar
Cédric Bellegarde committed
389
            self.__pages_manager.close_view(view)
390

391
    def __on_ready_to_show(self, webview, related, navigation_action):
392
        """
393
            Add a new webview with related
394
            @param webview as WebView
395 396
            @param related as WebView
            @param navigation_action as WebKit2.NavigationAction
397
        """
398 399 400
        properties = webview.get_window_properties()
        if properties.get_locationbar_visible() and\
                properties.get_toolbar_visible():
401
            self.add_view(webview, None, Gdk.WindowType.CHILD)
402 403 404 405 406 407
        else:
            elapsed = time() - related.last_click_time
            # Block popups, see WebView::set_popup_exception() for details
            popup_block = El().settings.get_value("popupblock")
            parsed_related = urlparse(related.get_uri())
            exception = \
408
                related.js_load or\
409 410 411 412 413 414 415 416 417 418 419
                El().popup_exceptions.find(parsed_related.netloc) or\
                El().popup_exceptions.find(parsed_related.netloc +
                                           parsed_related.path) or\
                (not related.is_loading() and elapsed < 0.5)
            if not exception and popup_block and\
                    navigation_action.get_navigation_type() in [
                                   WebKit2.NavigationType.OTHER,
                                   WebKit2.NavigationType.RELOAD,
                                   WebKit2.NavigationType.BACK_FORWARD]:
                related.add_popup(webview)
                if related == self.current.webview:
420 421
                    self.__window.toolbar.title.show_indicator(
                                                            Indicator.POPUPS)
422
                return
423
            self.popup_webview(webview, True)
424

425
    def __on_readable(self, webview):
426 427
        """
            Show readable button in titlebar
428
            @param webview as WebView
429
        """
430 431
        if webview == self.current.webview:
            self.__window.toolbar.title.show_readable_button(True)
432

433 434
    def __on_save_password(self, webview, username, userform,
                           password, passform, uri):
435 436 437 438
        """
            Ask user to save password
            @param webview as WebView
            @param username as str
439
            @param userform as str
440
            @param password as str
441
            @param passform as str
442
            @param uri as str
443
        """
444 445 446
        self.__window.toolbar.title.show_password(username, userform,
                                                  password, passform,
                                                  uri)
447

448 449 450 451 452 453
    def __on_script_dialog(self, webview, dialog):
        """
            Show message to user
            @param webview as WebView
            @param dialog as WebKit2.ScriptDialog
        """
454
        if not dialog.get_message().startswith("@&$%ù²"):
455 456
            self.__window.toolbar.title.show_javascript(dialog)
            return True
457

458
    def __on_button_press(self, webview, event):
459 460
        """
            Hide Titlebar popover
461
            @param webview as WebView
462
            @param event as Gdk.Event
463
        """
464
        return self.__window.close_popovers()
465

466
    def __on_estimated_load_progress(self, webview, value):
Cédric Bellegarde's avatar
Cédric Bellegarde committed
467 468
        """
            Update progress bar
469
            @param webview as WebView
470
            @param value GparamFloat
Cédric Bellegarde's avatar
Cédric Bellegarde committed
471
        """
472 473
        if webview == self.current.webview:
            value = webview.get_estimated_load_progress()
474
            self.__window.toolbar.title.progress.set_fraction(value)
Cédric Bellegarde's avatar
Cédric Bellegarde committed
475

476
    def __on_uri_changed(self, webview, uri):
477 478
        """
            Update uri
479
            @param webview as WebView
480
            @param uri as GParamString (Do not use)
481
        """
482
        if webview == self.current.webview:
483
            if uri:
Cédric Bellegarde's avatar
Cédric Bellegarde committed
484
                self.__window.toolbar.actions.set_actions(webview)
485
                self.__window.toolbar.title.set_uri(uri)
486 487 488
                if not webview.is_loading():
                    self.__window.toolbar.title.show_readable_button(
                                                webview.readable_content != "")
489

490
    def __on_title_changed(self, webview, title):
491 492
        """
            Update title
493
            @param webview as WebView
494 495
            @param title as str
        """
496
        if webview == self.current.webview:
497
            self.__window.toolbar.title.set_title(title)
498
        # Update history
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
        if webview.error is None:
            uri = webview.get_uri()
            parsed = urlparse(uri)
            if parsed.scheme in ["http", "https"] and\
                    not webview.ephemeral:
                mtime = round(time(), 2)
                El().history.thread_lock.acquire()
                history_id = El().history.add(title, uri, mtime)
                El().history.set_page_state(uri, mtime)
                El().history.thread_lock.release()
                if El().sync_worker is not None:
                    if El().sync_worker.syncing:
                        self.__history_queue.append(history_id)
                    else:
                        El().sync_worker.push_history([history_id])
514

515
    def __on_enter_fullscreen(self, webview):
516 517
        """
            Hide sidebar (conflict with fs)
518
            @param webview as WebView
519
        """
520
        self.__pages_manager.hide()
521

522
    def __on_leave_fullscreen(self, webview):
523 524
        """
            Show sidebar (conflict with fs)
525
            @param webview as WebView
526
        """
527
        self.__pages_manager.show()
528

529
    def __on_insecure_content_detected(self, webview, event):
530
        """
531
            @param webview as WebView
532
            @param event as WebKit2.InsecureContentEvent
533
        """
534
        self.__window.toolbar.title.set_insecure_content()
535

536
    def __on_load_changed(self, webview, event):
537 538
        """
            Update sidebar/urlbar
539
            @param webview as WebView
540
            @param event as WebKit2.LoadEvent
541
        """
542 543
        if webview != self.current.webview:
            return
544
        self.__window.toolbar.title.update_load_indicator(webview)
545 546
        uri = webview.get_uri()
        parsed = urlparse(uri)
547
        focus_in_view = parsed.scheme in ["http", "https", "file"]
548
        if event == WebKit2.LoadEvent.STARTED:
549
            self.__window.toolbar.title.show_spinner(True)
550
            self.__window.toolbar.title.set_title(uri)
551 552
            # Give focus to url bar
            if not focus_in_view:
553
                self.__window.toolbar.title.start_search()
554
            self.__window.toolbar.title.show_indicator(Indicator.NONE)
Cédric Bellegarde's avatar
Cédric Bellegarde committed
555 556 557
            # Turn off reading mode if needed
            if self.current.reading:
                self.current.switch_read_mode()
558
            self.__window.toolbar.title.progress.show()
Cédric Bellegarde's avatar
Cédric Bellegarde committed
559
        elif event == WebKit2.LoadEvent.COMMITTED:
560
            self.__window.toolbar.title.set_title(uri)
561
            self.__window.toolbar.actions.set_actions(webview)
562
        elif event == WebKit2.LoadEvent.FINISHED:
563
            self.__window.toolbar.title.show_spinner(False)
564 565 566
            title = webview.get_title()
            if title is not None:
                self.__window.toolbar.title.set_title(title)
567
            # Give focus to webview
568
            if focus_in_view:
569
                GLib.idle_add(self.__grab_focus_on_current)
570 571
            # Hide progress
            GLib.timeout_add(500, self.__window.toolbar.title.progress.hide)
572

573 574 575 576 577 578 579 580 581
    def __on_unmap(self, widget):
        """
            Disconnect sync signal
            @param widget as Gtk.Widget
        """
        if El().sync_worker is not None:
            El().sync_worker.disconnect_by_func(self.__on_sync_finished)

    def __on_sync_finished(self, worker):
582
        """
583
            Commit queue to sync
584
            @param worker as SyncWorker
585 586
        """
        if self.__history_queue:
587 588
            history_id = self.__history_queue.pop(0)
            worker.push_history([history_id])
589
            GLib.idle_add(self.__on_sync_finished, worker)