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, GObject, GLib, Pango
14
15from gettext import gettext as _
16from datetime import datetime
17
18from eolie.define import App, Type, LoadingType, MARGIN_SMALL
19from eolie.utils import wanted_loading_type, emit_signal
20
21
22class Row(Gtk.ListBoxRow):
23    """
24        A row
25    """
26    __gsignals__ = {
27        'edited': (GObject.SignalFlags.RUN_FIRST, None, ()),
28        'moved': (GObject.SignalFlags.RUN_FIRST, None, (GLib.Variant,))
29    }
30
31    def __init__(self, item, window):
32        """
33            Init row
34            @param item as Item
35            @param window as Window
36        """
37        self.__item = item
38        self.__window = window
39        self.__search = ""
40        eventbox = None
41        favicon = None
42        Gtk.ListBoxRow.__init__(self)
43        item_id = item.get_property("id")
44        item_type = item.get_property("type")
45        uri = item.get_property("uri")
46        title = item.get_property("title")
47        grid = Gtk.Grid()
48        grid.set_property("margin", MARGIN_SMALL)
49        grid.set_column_spacing(10)
50        grid.set_hexpand(True)
51        grid.set_property("valign", Gtk.Align.CENTER)
52        if item_type in [Type.BOOKMARK, Type.SEARCH, Type.HISTORY]:
53            favicon = Gtk.Image()
54            self.__set_favicon(favicon)
55        elif item_type == Type.SUGGESTION:
56            favicon = Gtk.Image.new_from_icon_name("system-search-symbolic",
57                                                   Gtk.IconSize.MENU)
58            favicon.show()
59        elif item_type == Type.WEBVIEW:
60            favicon = Gtk.Image.new_from_icon_name("view-paged-symbolic",
61                                                   Gtk.IconSize.MENU)
62            favicon.show()
63        if favicon is not None:
64            grid.attach(favicon, 0, 0, 1, 2)
65
66        uri = item.get_property("uri")
67        self.__title = Gtk.Label.new()
68        self.__title.set_ellipsize(Pango.EllipsizeMode.END)
69        self.__title.set_property("halign", Gtk.Align.START)
70        self.__title.set_hexpand(True)
71        self.__title.set_property('has-tooltip', True)
72        self.__title.connect('query-tooltip', self.__on_query_tooltip)
73        self.__title.show()
74        if uri:
75            self.__title.set_markup("%s\n<span alpha='40000'>%s</span>" %
76                                    (GLib.markup_escape_text(title),
77                                     GLib.markup_escape_text(uri)))
78        else:
79            self.__title.set_text(title)
80        grid.attach(self.__title, 1, 0, 1, 2)
81
82        if item_type == Type.HISTORY:
83            dt = datetime.fromtimestamp(item.get_property("atime"))
84            hour = str(dt.hour).rjust(2, "0")
85            minute = str(dt.minute).rjust(2, "0")
86            atime = Gtk.Label.new("%s:%s" % (hour, minute))
87            atime.set_property("valign", Gtk.Align.CENTER)
88            atime.get_style_context().add_class("dim-label")
89            atime.set_margin_end(2)
90            atime.show()
91            grid.attach(atime, 2, 0, 1, 2)
92
93        if item_type == Type.HISTORY:
94            delete_button = Gtk.Button.new_from_icon_name(
95                "user-trash-symbolic",
96                Gtk.IconSize.MENU)
97            delete_button.get_image().set_opacity(0.5)
98            delete_button.set_margin_end(5)
99            delete_button.set_property("valign", Gtk.Align.CENTER)
100            delete_button.connect("clicked", self.__on_delete_clicked)
101            delete_button.get_style_context().add_class("overlay-button")
102            delete_button.set_tooltip_text(_("Delete page from history"))
103            delete_button.show()
104            grid.attach(delete_button, 3, 0, 1, 2)
105        elif item_type == Type.BOOKMARK:
106            edit_button = Gtk.Button.new_from_icon_name(
107                "document-edit-symbolic",
108                Gtk.IconSize.MENU)
109            edit_button.get_image().set_opacity(0.5)
110            edit_button.set_margin_end(5)
111            edit_button.connect("clicked", self.__on_edit_clicked)
112            edit_button.get_style_context().add_class("overlay-button")
113            edit_button.set_property("valign", Gtk.Align.CENTER)
114            edit_button.set_tooltip_text(_("Edit bookmark"))
115            edit_button.show()
116            grid.attach(edit_button, 2, 0, 1, 2)
117        elif item_type == Type.TAG:
118            if item_id == Type.NONE:
119                icon_name = "folder-visiting-symbolic"
120            elif item_id == Type.POPULARS:
121                icon_name = "emote-love-symbolic"
122            elif item_id == Type.RECENTS:
123                icon_name = "document-open-recent-symbolic"
124            else:
125                icon_name = "folder-symbolic"
126            open_button = Gtk.Button.new_from_icon_name(
127                icon_name,
128                Gtk.IconSize.MENU)
129            open_button.connect("clicked", self.__on_open_clicked)
130            open_button.get_style_context().add_class("overlay-button-alt")
131            open_button.set_tooltip_text(_("Open all pages with this tag"))
132            open_button.show()
133            grid.attach(open_button, 0, 0, 1, 1)
134        grid.show()
135        style_context = self.get_style_context()
136        style_context.add_class("row")
137        eventbox = Gtk.EventBox()
138        eventbox.add(grid)
139        eventbox.set_size_request(-1, 30)
140        eventbox.connect("button-release-event",
141                         self.__on_button_release_event)
142        eventbox.show()
143        self.add(eventbox)
144
145    def set_title(self, title):
146        """
147            Set row title
148            @param title as str
149        """
150        self.__item.set_property("title", title)
151        self.__title.set_text(title)
152
153    @property
154    def item(self):
155        """
156            Get item
157            @return Item
158        """
159        return self.__item
160
161#######################
162# PRIVATE             #
163#######################
164    def __set_favicon(self, favicon):
165        """
166            Try to get a favicon for current URI
167            @param favicon as Gtk.Image
168            @param uri as str
169        """
170        uri = self.__item.get_property("uri")
171        favicon_path = App().art.get_favicon_path(uri)
172        if favicon_path is not None:
173            favicon.set_from_file(favicon_path)
174        else:
175            favicon.set_from_icon_name("web-browser-symbolic",
176                                       Gtk.IconSize.LARGE_TOOLBAR)
177        favicon.show()
178
179    def __on_query_tooltip(self, widget, x, y, keyboard, tooltip):
180        """
181            Show tooltip if needed
182            @param widget as Gtk.Widget
183            @param x as int
184            @param y as int
185            @param keyboard as bool
186            @param tooltip as Gtk.Tooltip
187        """
188        text = ''
189        layout = widget.get_layout()
190        label = widget.get_text()
191        if layout.is_ellipsized():
192            text = "%s" % (GLib.markup_escape_text(label))
193        widget.set_tooltip_markup(text)
194
195    def __on_button_release_event(self, eventbox, event):
196        """
197            Handle button press in popover
198            @param eventbox as Gtk.EventBox
199            @param event as Gdk.Event
200        """
201        # Lets user select item
202        if event.state & Gdk.ModifierType.CONTROL_MASK or\
203                event.state & Gdk.ModifierType.SHIFT_MASK:
204            return False
205        # Event is for internal button
206        if eventbox.get_window() != event.window:
207            return True
208        uri = self.__item.get_property("uri")
209        type_id = self.__item.get_property("type")
210        if type_id in [Type.HISTORY, Type.SUGGESTION,
211                       Type.SEARCH, Type.BOOKMARK]:
212            if event.button == 1:
213                self.__window.container.webview.load_uri(uri)
214                self.__window.container.set_expose(False)
215                self.__window.close_popovers()
216            else:
217                self.__window.container.add_webview_for_uri(
218                    uri, LoadingType.FOREGROUND)
219                if event.button == 2:
220                    self.__window.close_popovers()
221        elif type_id == Type.WEBVIEW:
222            title = self.__item.get_property("title")
223            for webview in self.__window.container.webviews:
224                if webview.uri == uri and webview.title == title:
225                    self.__window.container.set_visible_webview(webview)
226                    self.__window.close_popovers()
227                    break
228        else:
229            emit_signal(self, "activate")
230            # We force focus to stay on title entry
231            GLib.idle_add(self.__window.toolbar.title.entry.focus)
232
233    def __on_edit_clicked(self, button):
234        """
235            Edit self
236            @param button as Gtk.Button
237        """
238        emit_signal(self, "edited")
239
240    def __on_open_clicked(self, button):
241        """
242            Open all bookmarks
243            @param button as Gtk.Button
244        """
245        self.__window.close_popovers()
246        tag_id = self.__item.get_property("id")
247        if tag_id == Type.POPULARS:
248            items = App().bookmarks.get_populars(50)
249        elif tag_id == Type.RECENTS:
250            items = App().bookmarks.get_recents()
251        elif tag_id == Type.UNCLASSIFIED:
252            items = App().bookmarks.get_unclassified()
253        else:
254            items = App().bookmarks.get_bookmarks(tag_id)
255        i = 0
256        for (bid, uri, title) in items:
257            loading_type = wanted_loading_type(i)
258            self.__window.container.add_webview_for_uri(uri, loading_type)
259            i += 1
260
261    def __on_delete_clicked(self, button):
262        """
263            Delete self
264            @param button as Gtk.Button
265        """
266        history_id = self.__item.get_property("id")
267        guid = App().history.get_guid(history_id)
268        if App().sync_worker is not None:
269            App().sync_worker.remove_from_history(guid)
270        App().history.remove(history_id)
271        GLib.idle_add(self.destroy)
272