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 Gdk, GLib, Gtk, Pango, GdkPixbuf
14
15from math import pi
16import unicodedata
17import string
18import cairo
19from threading import current_thread
20from urllib.parse import urlparse
21from random import choice
22from base64 import b64encode
23
24from eolie.logger import Logger
25from eolie.define import ArtSize, LoadingType
26
27
28def get_char_surface(char):
29    """
30        Draw a char with a random color
31        @param char as str
32        @return cairo surface
33    """
34    colors = [[0.102, 0.737, 0.612],                 # Turquoise
35              [0.204, 0.596, 0.859],                 # Peterriver
36              [0.608, 0.349, 0.714],                 # Amethyst
37              [0.204, 0.286, 0.369],                 # Wetasphalt
38              [0.086, 0.627, 0.522],                 # Greensea
39              [0.153, 0.682, 0.376],                 # Nephritis
40              [0.161, 0.502, 0.725],                 # Belizehole
41              [0.557, 0.267, 0.678],                 # Wisteria
42              [0.173, 0.243, 0.314],                 # Midnightblue
43              [0.827, 0.329, 0.0],                   # Pumpkin
44              [0.753, 0.224, 0.169],                 # Pomegranate
45              [0.498, 0.549, 0.553]                  # Asbestos
46              ]
47    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
48                                 ArtSize.FAVICON,
49                                 ArtSize.FAVICON)
50    context = cairo.Context(surface)
51    try:
52        index = string.ascii_lowercase.index(char)
53    except:
54        index = 0
55    colors_count = len(colors)
56    while int(index) >= colors_count:
57        index /= 2
58    color = colors[int(index)]
59    context.set_source_rgb(color[0], color[1], color[2])
60    context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL,
61                             cairo.FONT_WEIGHT_BOLD)
62    context.set_font_size(ArtSize.FAVICON)
63    (xbearing, ybearing,
64     width, height,
65     xadvance, yadvance) = context.text_extents(char)
66    context.move_to(ArtSize.FAVICON / 2 - (xadvance + xbearing) / 2,
67                    ArtSize.FAVICON / 2 - ybearing - height / 2)
68    context.show_text(char)
69    context.stroke()
70    return surface
71
72
73def get_baseuri(uri):
74    """
75        Get Base URI
76        @param uri as str
77        @return str
78    """
79    parsed = urlparse(uri)
80    return "%s://%s" % (parsed.scheme, parsed.netloc)
81
82
83def get_safe_netloc(uri):
84    """
85        Get netloc (scheme if empty)
86        @param uri as str
87    """
88    parsed = urlparse(uri)
89    netloc = parsed.netloc
90    if not netloc:
91        netloc = "%s://" % urlparse(uri).scheme
92    else:
93        split = netloc.split(".")
94        if len(split) > 2:
95            netloc = ".".join(split[-2:])
96    return netloc
97
98
99def wanted_loading_type(index):
100    """
101        Return window type based on current index
102    """
103    if index == 0:
104        return LoadingType.FOREGROUND
105    elif index == 1:
106        return LoadingType.BACKGROUND
107    else:
108        return LoadingType.OFFLOAD
109
110
111def emit_signal(obj, signal, *args):
112    """
113        Emit signal
114        @param obj as GObject.Object
115        @param signal as str
116        @thread safe
117    """
118    if current_thread().getName() == "MainThread":
119        obj.emit(signal, *args)
120    else:
121        GLib.idle_add(obj.emit, signal, *args)
122
123
124def get_round_surface(image, scale_factor, radius):
125    """
126        Get rounded surface from pixbuf
127        @param image as GdkPixbuf.Pixbuf/cairo.Surface
128        @return surface as cairo.Surface
129        @param scale_factor as int
130        @param radius as int
131        @warning not thread safe!
132    """
133    width = image.get_width()
134    height = image.get_height()
135    is_pixbuf = isinstance(image, GdkPixbuf.Pixbuf)
136    if is_pixbuf:
137        width = width // scale_factor
138        height = height // scale_factor
139        radius = radius // scale_factor
140    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
141    ctx = cairo.Context(surface)
142    degrees = pi / 180
143    ctx.arc(width - radius, radius, radius, -90 * degrees, 0 * degrees)
144    ctx.arc(width - radius, height - radius,
145            radius, 0 * degrees, 90 * degrees)
146    ctx.arc(radius, height - radius, radius, 90 * degrees, 180 * degrees)
147    ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees)
148    ctx.close_path()
149    ctx.set_line_width(10)
150    if is_pixbuf:
151        image = Gdk.cairo_surface_create_from_pixbuf(image, scale_factor, None)
152    ctx.set_source_surface(image, 0, 0)
153    ctx.clip()
154    ctx.paint()
155    return surface
156
157
158def is_unity():
159    """
160        Return True if desktop is Gnome
161    """
162    return GLib.getenv("XDG_CURRENT_DESKTOP") == "ubuntu:GNOME"
163
164
165def on_query_tooltip(label, x, y, keyboard, tooltip):
166    """
167        Show label tooltip if needed
168        @param label as Gtk.Label
169        @param x as int
170        @param y as int
171        @param keyboard as bool
172        @param tooltip as Gtk.Tooltip
173    """
174    layout = label.get_layout()
175    if layout.is_ellipsized():
176        tooltip.set_markup(label.get_label())
177        return True
178
179
180def resize_favicon(favicon):
181    """
182        Resize surface to match favicon size
183        @param favicon as cairo.surface
184        @return cairo.surface
185    """
186    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
187                                 ArtSize.FAVICON,
188                                 ArtSize.FAVICON)
189    factor = ArtSize.FAVICON / favicon.get_width()
190    context = cairo.Context(surface)
191    context.scale(factor, factor)
192    context.set_source_surface(favicon, 0, 0)
193    context.paint()
194    return surface
195
196
197# TODO Use Lollypop menu builder
198def update_popover_internals(widget):
199    """
200        Little hack to force Gtk.ModelButton to show image
201        @param widget as Gtk.Widget
202    """
203    if isinstance(widget, Gtk.Image):
204        widget.show()
205    elif isinstance(widget, Gtk.Label):
206        widget.set_ellipsize(Pango.EllipsizeMode.END)
207        widget.set_max_width_chars(40)
208        widget.set_tooltip_text(widget.get_text())
209    elif hasattr(widget, "forall"):
210        GLib.idle_add(widget.forall, update_popover_internals)
211
212
213def get_snapshot(webview, result, callback, *args):
214    """
215        Set snapshot on main image
216        @param webview as WebKit2.WebView
217        @param result as Gio.AsyncResult
218        @return cairo.Surface
219    """
220    ART_RATIO = 1.5  # ArtSize.START_WIDTH / ArtSize.START_HEIGHT
221    try:
222        snapshot = webview.get_snapshot_finish(result)
223        # Set start image scale factor
224        ratio = snapshot.get_width() / snapshot.get_height()
225        if ratio > ART_RATIO:
226            factor = ArtSize.START_HEIGHT / snapshot.get_height()
227        else:
228            factor = ArtSize.START_WIDTH / snapshot.get_width()
229        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
230                                     ArtSize.START_WIDTH,
231                                     ArtSize.START_HEIGHT)
232        context = cairo.Context(surface)
233        context.scale(factor, factor)
234        context.set_source_surface(snapshot, factor, 0)
235        context.paint()
236        callback(surface, *args)
237    except Exception as e:
238        Logger.error("get_snapshot(): %s", e)
239        callback(None, *args)
240
241
242def get_random_string(size):
243    """
244        Get a rand string at size
245        @param size as int
246        return str
247    """
248    s = ''.join(choice(string.printable) for c in range(size))
249    return b64encode(s.encode("utf-8"))[:size].decode("utf-8")
250
251
252def get_current_monitor_model(window):
253    """
254        Return monitor model as string
255        @param window as Gtk.Window
256        @return str
257    """
258    screen = Gdk.Screen.get_default()
259    display = screen.get_display()
260    monitor = display.get_monitor_at_window(window.get_window())
261    width_mm = monitor.get_width_mm()
262    height_mm = monitor.get_height_mm()
263    geometry = monitor.get_geometry()
264    return "%sx%s/%sx%s" % (width_mm, height_mm,
265                            geometry.width, geometry.height)
266
267
268def noaccents(string):
269    """
270        Return string without accents
271        @param string as str
272        @return str
273    """
274    nfkd_form = unicodedata.normalize('NFKD', string)
275    return u"".join([c for c in nfkd_form if not unicodedata.combining(c)])
276
277
278def get_ftp_cmd():
279    """
280        Try to guess best ftp app
281        @return app cmd as str
282    """
283    for app in ["filezilla", "nautilus", "thunar", "nemo", "true"]:
284        cmd = GLib.find_program_in_path(app)
285        if cmd is not None:
286            return cmd
287