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, Gio, WebKit2, GObject
14
15from gettext import gettext as _
16from urllib.parse import urlparse
17from time import time
18
19from eolie.define import App, LoadingState
20from eolie.utils import emit_signal
21from eolie.webview_signals_menu import WebViewMenuSignals
22from eolie.webview_signals_js import WebViewJsSignals
23
24
25class WebViewSignals(WebViewMenuSignals, WebViewJsSignals):
26    """
27        Handle webview signals
28    """
29
30    gsignals = {
31        "is-playing-audio": (GObject.SignalFlags.RUN_FIRST, None, (bool,)),
32        "readability-content": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
33        "readability-status": (GObject.SignalFlags.RUN_FIRST, None, (bool,)),
34        "title-changed": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
35        "uri-changed": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
36        "snapshot-changed": (GObject.SignalFlags.RUN_FIRST, None,
37                             (GObject.TYPE_PYOBJECT,)),
38    }
39
40    for signal in gsignals:
41        args = gsignals[signal]
42        GObject.signal_new(signal, WebKit2.WebView,
43                           args[0], args[1], args[2])
44
45    def __init__(self):
46        """
47            Init handler
48            @param webview as WebView
49        """
50        WebViewMenuSignals.__init__(self)
51        WebViewJsSignals.__init__(self)
52        self.reset_last_click_event()
53        self.__cancellable = Gio.Cancellable()
54        self.__uri = ""
55        self.__title = ""
56        self.connect("notify::title", self.__on_title_changed)
57        self.connect("notify::uri", self.__on_uri_changed)
58        self.connect("scroll-event", self.__on_scroll_event)
59        self.connect("run-file-chooser", self.__on_run_file_chooser)
60        self.connect("button-press-event", self.__on_button_press_event)
61
62    def reset_last_click_event(self):
63        """
64            Reset last click event
65        """
66        self._last_click_event_x = 0
67        self._last_click_event_y = 0
68        self._last_click_time = 0
69
70    def set_title(self, title):
71        """
72            Set title
73            @param title as str
74        """
75        if title:
76            self.__title = title
77            emit_signal(self, "title-changed", title)
78
79    def set_uri(self, uri):
80        """
81            Set delayed uri
82            @param uri as str
83        """
84        self.__uri = uri.rstrip("/")
85        emit_signal(self, "uri-changed", uri)
86
87    @property
88    def uri(self):
89        """
90            Get webview uri
91            @return str
92        """
93        return self.__uri
94
95    @property
96    def title(self):
97        """
98            Get webview title
99            @return str
100        """
101        return self.__title
102
103#######################
104# PRIVATE             #
105#######################
106    def __on_button_press_event(self, webview, event):
107        """
108            Hide Titlebar popover
109            @param webview as WebView
110            @param event as Gdk.Event
111        """
112        self._last_click_event_x = event.x
113        self._last_click_event_y = event.y
114        self._last_click_time = time()
115        if event.button == 8:
116            self.go_back()
117            return True
118        elif event.button == 9:
119            self.go_forward()
120            return True
121        elif self.get_ancestor(Gtk.Popover) is None:
122            return self.window.close_popovers()
123
124    def __on_run_file_chooser(self, webview, request):
125        """
126            Run own file chooser
127            @param webview as WebView
128            @param request as WebKit2.FileChooserRequest
129        """
130        dialog = Gtk.FileChooserNative.new(_("Select files to upload"),
131                                           self.window,
132                                           Gtk.FileChooserAction.OPEN,
133                                           _("Open"),
134                                           _("Cancel"))
135        dialog.set_select_multiple(request.get_select_multiple())
136        chooser_uri = App().websettings.get("chooser_uri", webview.uri)
137        if chooser_uri is not None:
138            dialog.set_current_folder_uri(chooser_uri)
139        response = dialog.run()
140        if response in [Gtk.ResponseType.DELETE_EVENT,
141                        Gtk.ResponseType.CANCEL]:
142            request.cancel()
143        else:
144            request.select_files(dialog.get_filenames())
145            App().websettings.set("chooser_uri",
146                                  webview.uri,
147                                  dialog.get_current_folder_uri())
148        return True
149
150    def __on_scroll_event(self, webview, event):
151        """
152            Adapt scroll speed to device
153            @param webview as WebView
154            @param event as Gdk.EventScroll
155        """
156        source = event.get_source_device().get_source()
157        if event.state & Gdk.ModifierType.CONTROL_MASK:
158            if source == Gdk.InputSource.MOUSE:
159                if event.delta_y < 0.5:
160                    webview.zoom_in()
161                elif event.delta_y > 0.5:
162                    webview.zoom_out()
163            else:
164                if event.delta_y > 0.5:
165                    webview.zoom_in()
166                elif event.delta_y < - 0.5:
167                    webview.zoom_out()
168            return True
169        elif source == Gdk.InputSource.MOUSE:
170            event.delta_x *= 2
171            event.delta_y *= 2
172
173    def __on_uri_changed(self, webview, param):
174        """
175            Handle JS updates
176            @param webview as WebKit2.WebView
177            @param param as GObject.ParamSpec
178        """
179        uri = webview.get_property(param.name).rstrip("/")
180        if not uri.startswith("javascript:"):
181            self.__uri = uri
182            emit_signal(self, "uri-changed", uri)
183            self._set_user_agent(uri)
184
185    def __on_title_changed(self, webview, param):
186        """
187            We launch Readability.js at page loading finished.
188            @param webview as WebKit2.WebView
189            @param param as GObject.ParamSpec
190        """
191        title = webview.get_property(param.name)
192        if title:
193            parsed = urlparse(webview.uri)
194            is_http = parsed.scheme in ["http", "https"]
195            if is_http:
196                self.__title = "%s: %s" % (parsed.netloc, title)
197                emit_signal(self, "title-changed", self.__title)
198            else:
199                self.__title = title
200                emit_signal(self, "title-changed", None)
201            if self.loading_state in [LoadingState.ERROR,
202                                      LoadingState.STOPPED] or\
203                    webview.is_ephemeral or\
204                    not is_http:
205                return
206            mtime = round(time(), 2)
207            history_id = App().history.add(self.__title, self.__uri, mtime)
208            App().history.set_page_state(self.__uri, mtime)
209            if App().sync_worker is not None:
210                App().sync_worker.push_history(history_id)
211