1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2### BEGIN LICENSE
3# Copyright (c) 2012, Peter Levi <peterlevi@peterlevi.com>
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU General Public License version 3, as published
6# by the Free Software Foundation.
7#
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE.  See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License along
14# with this program.  If not, see <http://www.gnu.org/licenses/>.
15### END LICENSE
16
17"""Code to add AppIndicator."""
18
19import logging
20import os
21
22from gi.repository import Gtk  # pylint: disable=E0611
23
24from variety.Util import Util, _
25from variety_lib import varietyconfig
26
27THEME_ICON_NAME = "variety-indicator"
28THEME_ICON_NAME_DARK = "variety-indicator-dark"
29THEME_ICON_NAME_NUM = "variety-indicator-num{}"
30
31try:
32    import gi
33
34    try:
35        gi.require_version("AyatanaAppIndicator3", "0.1")
36        from gi.repository import AyatanaAppIndicator3 as AppIndicator3  # pylint: disable=E0611
37
38        _indicator_backend = "AyatanaAppIndicator3"  # Just a dummy value we use for logging
39    except (ValueError, ImportError):
40        gi.require_version("AppIndicator3", "0.1")
41        from gi.repository import AppIndicator3  # pylint: disable=E0611
42
43        _indicator_backend = "AppIndicator3"
44    use_appindicator = True
45except (ValueError, ImportError):
46    _indicator_backend = "fallback tray"
47    use_appindicator = False
48
49
50logger = logging.getLogger("variety")
51
52
53class Indicator:
54    def __init__(self, window):
55        self.parent = window
56        self.create_menu(window)
57        self.create_indicator(window)
58
59    def create_menu(self, window):
60        self.menu = Gtk.Menu()
61
62        if not Util.is_unity():
63            self.next_main = Gtk.MenuItem(_("_Next"))
64            self.next_main.set_use_underline(True)
65            self.next_main.connect("activate", window.next_wallpaper)
66            self.menu.append(self.next_main)
67
68            self.prev_main = Gtk.MenuItem(_("_Previous"))
69            self.prev_main.set_use_underline(True)
70            self.prev_main.connect("activate", window.prev_wallpaper)
71            self.menu.append(self.prev_main)
72
73            self.menu.append(Gtk.SeparatorMenuItem.new())
74
75        self.file_label = Gtk.MenuItem(_("Current desktop wallpaper"))
76        self.file_label.connect("activate", window.open_file)
77        self.menu.append(self.file_label)
78
79        self.show_origin = Gtk.MenuItem(_("Show origin"))
80        self.show_origin.connect("activate", window.on_show_origin)
81        self.show_origin.set_sensitive(False)
82        self.menu.append(self.show_origin)
83
84        self.show_author = Gtk.MenuItem("Show author")
85        self.show_author.connect("activate", window.on_show_author)
86        self.show_author.set_sensitive(False)
87        self.show_author.set_visible(False)
88        self.menu.append(self.show_author)
89
90        self.copy_to_favorites = Gtk.MenuItem(_("Copy to _Favorites"))
91        self.copy_to_favorites.set_use_underline(True)
92        self.copy_to_favorites.connect("activate", window.copy_to_favorites)
93        self.menu.append(self.copy_to_favorites)
94
95        self.move_to_favorites = Gtk.MenuItem(_("Move to Favorites"))
96        self.move_to_favorites.set_use_underline(True)
97        self.move_to_favorites.connect("activate", window.move_to_favorites)
98        self.move_to_favorites.set_visible(False)
99        self.menu.append(self.move_to_favorites)
100
101        self.trash = Gtk.MenuItem(_("Delete to _Trash"))
102        self.trash.set_use_underline(True)
103        self.trash.connect("activate", window.move_to_trash)
104        self.menu.append(self.trash)
105
106        self.menu.append(Gtk.SeparatorMenuItem.new())
107
108        self.image_menu = Gtk.Menu()
109
110        self.next = Gtk.MenuItem(_("_Next"))
111        self.next.set_use_underline(True)
112        self.next.connect("activate", window.next_wallpaper)
113        self.image_menu.append(self.next)
114
115        self.prev = Gtk.MenuItem(_("_Previous"))
116        self.prev.set_use_underline(True)
117        self.prev.connect("activate", window.prev_wallpaper)
118        self.image_menu.append(self.prev)
119
120        self.fast_forward = Gtk.MenuItem(_("_Next, skipping forward history"))
121        self.fast_forward.set_use_underline(True)
122
123        def _fast_forward(widget):
124            window.next_wallpaper(widget, bypass_history=True)
125
126        self.fast_forward.connect("activate", _fast_forward)
127        self.image_menu.append(self.fast_forward)
128
129        self.image_menu.append(Gtk.SeparatorMenuItem.new())
130        self.scroll_tip = Gtk.MenuItem(_("Tip: Scroll wheel over icon\nfor Next and Previous"))
131        self.scroll_tip.set_sensitive(False)
132        self.image_menu.append(self.scroll_tip)
133
134        self.image_menu.append(Gtk.SeparatorMenuItem.new())
135
136        self.pause_resume = Gtk.MenuItem(_("Pause on current"))
137        self.pause_resume.connect("activate", window.on_pause_resume)
138        self.image_menu.append(self.pause_resume)
139
140        self.image_item = Gtk.MenuItem(_("_Image"))
141        self.image_item.set_use_underline(True)
142        self.image_item.set_submenu(self.image_menu)
143        self.menu.append(self.image_item)
144
145        self.image_menu.append(Gtk.SeparatorMenuItem.new())
146
147        self.focus = Gtk.MenuItem(_("Where is it from?"))
148        self.focus.connect("activate", window.focus_in_preferences)
149        self.image_menu.append(self.focus)
150
151        self.no_effects = Gtk.CheckMenuItem(_("Show without effects"))
152        self.no_effects.set_active(False)
153        self.no_effects.set_use_underline(True)
154
155        def _toggle_no_effects(widget=None):
156            window.toggle_no_effects(self.no_effects.get_active())
157
158        self.no_effects_handler_id = self.no_effects.connect("toggled", _toggle_no_effects)
159        self.image_menu.append(self.no_effects)
160
161        self.google_image = Gtk.MenuItem(_("Google Image Search"))
162        self.google_image.connect("activate", window.google_image_search)
163        self.image_menu.append(self.google_image)
164
165        self.rating_separator = Gtk.SeparatorMenuItem.new()
166        self.image_menu.append(self.rating_separator)
167
168        self.rating = Gtk.MenuItem(_("Set EXIF Rating"))
169        self.image_menu.append(self.rating)
170
171        # self.image_item = Gtk.MenuItem(_("_Image"))
172        # self.image_item.set_use_underline(True)
173        # self.image_item.set_submenu(self.image_menu)
174        # self.menu.append(self.image_item)
175        #
176
177        self.quotes_menu = Gtk.Menu()
178
179        self.next_quote = Gtk.MenuItem(_("_Next"))
180        self.next_quote.set_use_underline(True)
181        self.next_quote.connect("activate", window.next_quote)
182        self.quotes_menu.append(self.next_quote)
183
184        self.prev_quote = Gtk.MenuItem(_("_Previous"))
185        self.prev_quote.set_use_underline(True)
186        self.prev_quote.connect("activate", window.prev_quote)
187        self.quotes_menu.append(self.prev_quote)
188
189        self.fast_forward_quote = Gtk.MenuItem(_("_Next, skipping forward history"))
190        self.fast_forward_quote.set_use_underline(True)
191
192        def _fast_forward_quote(widget):
193            window.next_quote(widget, bypass_history=True)
194
195        self.fast_forward_quote.connect("activate", _fast_forward_quote)
196        self.quotes_menu.append(self.fast_forward_quote)
197
198        self.quotes_menu.append(Gtk.SeparatorMenuItem.new())
199
200        self.quotes_pause_resume = Gtk.MenuItem(_("Pause on current"))
201        self.quotes_pause_resume.connect("activate", window.on_quotes_pause_resume)
202        self.quotes_menu.append(self.quotes_pause_resume)
203
204        self.quotes_menu.append(Gtk.SeparatorMenuItem.new())
205
206        self.quote_favorite = Gtk.MenuItem(_("Save to Favorites"))
207        self.quote_favorite.set_use_underline(True)
208        self.quote_favorite.connect("activate", window.quote_save_to_favorites)
209        self.quotes_menu.append(self.quote_favorite)
210
211        self.quote_view_favs = Gtk.MenuItem(_("View Favorites..."))
212        self.quote_view_favs.set_use_underline(True)
213        self.quote_view_favs.connect("activate", window.quote_view_favorites)
214        self.quotes_menu.append(self.quote_view_favs)
215
216        self.quotes_menu.append(Gtk.SeparatorMenuItem.new())
217
218        self.quote_clipboard = Gtk.MenuItem(_("Copy to Clipboard"))
219        self.quote_clipboard.set_use_underline(True)
220        self.quote_clipboard.connect("activate", window.quote_copy_to_clipboard)
221        self.quotes_menu.append(self.quote_clipboard)
222
223        self.view_quote = Gtk.MenuItem()
224        self.view_quote.set_use_underline(True)
225        self.view_quote.connect("activate", window.view_quote)
226        self.quotes_menu.append(self.view_quote)
227
228        self.google_quote_text = Gtk.MenuItem(_("Google Quote"))
229        self.google_quote_text.set_use_underline(True)
230        self.google_quote_text.connect("activate", window.google_quote_text)
231        self.quotes_menu.append(self.google_quote_text)
232
233        self.google_quote_author = Gtk.MenuItem(_("Google Author"))
234        self.google_quote_author.set_use_underline(True)
235        self.google_quote_author.connect("activate", window.google_quote_author)
236        self.quotes_menu.append(self.google_quote_author)
237
238        self.quotes_menu.append(Gtk.SeparatorMenuItem.new())
239
240        self.quotes_preferences = Gtk.MenuItem(_("Preferences..."))
241        self.quotes_preferences.set_use_underline(True)
242
243        def _quotes_prefs(widget=None):
244            window.preferences_dialog.ui.notebook.set_current_page(1)
245            window.on_mnu_preferences_activate()
246
247        self.quotes_preferences.connect("activate", _quotes_prefs)
248        self.quotes_menu.append(self.quotes_preferences)
249
250        self.quotes_disable = Gtk.MenuItem(_("Turn off"))
251        self.quotes_disable.set_use_underline(True)
252        self.quotes_disable.connect("activate", window.disable_quotes)
253        self.quotes_menu.append(self.quotes_disable)
254
255        self.quotes = Gtk.MenuItem(_("_Quote"))
256        self.quotes.set_use_underline(True)
257        self.quotes.set_submenu(self.quotes_menu)
258        self.menu.append(self.quotes)
259
260        self.menu.append(Gtk.SeparatorMenuItem.new())
261
262        self.history = Gtk.CheckMenuItem(_("_History"))
263        self.history.set_active(False)
264        self.history.set_use_underline(True)
265        self.history_handler_id = self.history.connect("toggled", window.show_hide_history)
266        self.menu.append(self.history)
267
268        self.selector = Gtk.CheckMenuItem(_("_Wallpaper Selector"))
269        self.selector.set_active(False)
270        self.selector.set_use_underline(True)
271        self.selector_handler_id = self.selector.connect(
272            "toggled", window.show_hide_wallpaper_selector
273        )
274        self.menu.append(self.selector)
275
276        self.downloads = Gtk.CheckMenuItem(_("Recent _Downloads"))
277        self.downloads.set_active(False)
278        self.downloads.set_use_underline(True)
279        self.downloads_handler_id = self.downloads.connect("toggled", window.show_hide_downloads)
280        self.menu.append(self.downloads)
281
282        if Util.check_variety_slideshow_present():
283            self.menu.append(Gtk.SeparatorMenuItem.new())
284            self.slideshow = Gtk.MenuItem(_("Start Slideshow"))
285            self.slideshow.connect("activate", window.on_start_slideshow)
286            self.menu.append(self.slideshow)
287        else:
288            logger.warning(
289                "Variety Slideshow is not installed. This is an optional extension "
290                "adding pan-and-zoom slideshows to Variety: see "
291                "https://github.com/peterlevi/variety-slideshow for details"
292            )
293
294        self.menu.append(Gtk.SeparatorMenuItem.new())
295
296        self.preferences = Gtk.MenuItem(_("Preferences..."))
297        self.preferences.connect("activate", window.on_mnu_preferences_activate)
298        self.menu.append(self.preferences)
299
300        self.about = Gtk.MenuItem(_("About"))
301        self.about.connect("activate", window.on_mnu_about_activate)
302        self.menu.append(self.about)
303
304        self.donate = Gtk.MenuItem(_("Donate"))
305        self.donate.connect("activate", window.on_mnu_donate_activate)
306        self.menu.append(self.donate)
307
308        self.quit = Gtk.MenuItem(_("Quit"))
309        self.quit.connect("activate", window.on_quit)
310        self.menu.append(self.quit)
311
312        self.menu.show_all()
313
314    def create_indicator(self, window):
315        logger.info("indicator backend: %s", _indicator_backend)
316        self.indicator = None
317        self.status_icon = None
318        self.visible = True
319
320        def right_click_event(icon, button, time):
321            self.menu.popup(None, None, Gtk.StatusIcon.position_menu, self.status_icon, 0, time)
322
323        def left_click_event(data):
324            self.menu.popup(
325                None,
326                None,
327                Gtk.StatusIcon.position_menu,
328                self.status_icon,
329                0,
330                Gtk.get_current_event_time(),
331            )
332
333        def on_indicator_scroll_status_icon(status_icon, event):
334            window.on_indicator_scroll(None, 1, event.direction)
335
336        icon_path = varietyconfig.get_data_file("media", "variety-indicator.png")
337        if use_appindicator:
338            self.indicator = AppIndicator3.Indicator.new(
339                "variety", "", AppIndicator3.IndicatorCategory.APPLICATION_STATUS
340            )
341            self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
342            self.indicator.set_icon(icon_path)
343            self.indicator.connect("scroll-event", window.on_indicator_scroll)
344            self.indicator.set_menu(self.menu)
345        else:
346            self.status_icon = Gtk.StatusIcon.new_from_file(icon_path)
347            self.status_icon.set_visible(True)
348            self.status_icon.connect("activate", left_click_event)
349            self.status_icon.connect("popup-menu", right_click_event)
350            self.status_icon.connect("scroll-event", on_indicator_scroll_status_icon)
351
352    def set_visible(self, visible):
353        self.visible = visible
354        if visible:
355            if self.indicator:
356                logger.info(lambda: "Showing indicator icon")
357                self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
358            if self.status_icon:
359                logger.info(lambda: "Showing status icon")
360                self.status_icon.set_visible(True)
361        else:
362            if self.indicator:
363                logger.info(lambda: "Hiding indicator icon")
364                self.indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE)
365            if self.status_icon:
366                logger.info(lambda: "Hiding status icon")
367                self.status_icon.set_visible(False)
368
369    def set_icon(self, icon):
370        def set_from_theme_icon(name):
371            if self.indicator:
372                logger.info(lambda: "Showing indicator icon %s from GTK theme" % name)
373                self.indicator.set_icon(name)
374            if self.status_icon:
375                logger.info(lambda: "Showing status icon %s from GTK theme" % name)
376                self.status_icon.set_from_icon_name(name)
377
378        if icon == "Light":
379            if Gtk.IconTheme.get_default().has_icon(THEME_ICON_NAME):
380                set_from_theme_icon(THEME_ICON_NAME)
381                return
382            else:
383                icon_path = varietyconfig.get_data_file("media", "variety-indicator.png")
384        elif icon == "Dark":
385            if Gtk.IconTheme.get_default().has_icon(THEME_ICON_NAME_DARK):
386                set_from_theme_icon(THEME_ICON_NAME_DARK)
387                return
388            else:
389                icon_path = varietyconfig.get_data_file("media", "variety-indicator-dark.png")
390        elif icon in ["1", "2", "3", "4"]:
391            if Gtk.IconTheme.get_default().has_icon(THEME_ICON_NAME_NUM.format(icon)):
392                set_from_theme_icon(THEME_ICON_NAME_NUM.format(icon))
393                return
394            else:
395                icon_path = varietyconfig.get_data_file(
396                    "media", "variety-indicator-num{}.png".format(icon)
397                )
398        elif icon and os.access(icon, os.R_OK) and Util.is_image(icon):
399            icon_path = icon
400        else:
401            icon_path = varietyconfig.get_data_file("media", "variety-indicator.png")
402
403        if self.indicator:
404            logger.info(lambda: "Showing indicator icon image: " + icon_path)
405            self.indicator.set_icon(icon_path)
406        if self.status_icon:
407            logger.info(lambda: "Showing status icon image: " + icon_path)
408            self.status_icon.set_from_file(icon_path)
409
410    def get_visible(self):
411        return self.visible
412
413
414def new_application_indicator(window):
415    ind = Indicator(window)
416    return ind, ind.indicator, ind.status_icon
417