1# Copyright (c) 2014-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, GLib, Handy
14
15from lollypop.define import App, ScanType, ArtSize
16from lollypop.container import Container
17from lollypop.toolbar import Toolbar
18from lollypop.utils import emit_signal
19from lollypop.helper_signals import SignalsHelper, signals_map
20from lollypop.logger import Logger
21
22
23class Window(Handy.ApplicationWindow, SignalsHelper):
24    """
25        Main window
26    """
27
28    @signals_map
29    def __init__(self):
30        """
31            Init window
32        """
33        Handy.ApplicationWindow.__init__(self,
34                                         application=App(),
35                                         title="Lollypop",
36                                         icon_name="org.gnome.Lollypop")
37        self.__miniplayer = None
38        self.__configure_timeout_id = None
39        self.set_auto_startup_notification(False)
40        self.connect("realize", self.__on_realize)
41        # Does not work with a Gtk.Gesture in GTK3
42        self.connect("button-release-event", self.__on_button_release_event)
43        self.connect("window-state-event", self.__on_window_state_event)
44        self.connect("configure-event", self.__on_configure_event)
45        self.connect("destroy", self.__on_destroy)
46        self.set_size_request(ArtSize.MINIPLAYER, ArtSize.MINIPLAYER)
47        return [
48            (App().player, "current-changed", "_on_current_changed")
49        ]
50
51    def setup(self):
52        """
53            Setup window content
54        """
55        self.__vgrid = Gtk.Grid()
56        self.__vgrid.set_orientation(Gtk.Orientation.VERTICAL)
57        self.__vgrid.show()
58        self.__container = Container()
59        self.__container.setup()
60        self.__container.show()
61        self.__toolbar = Toolbar(self)
62        self.__toolbar.show()
63        self.__vgrid.add(self.__toolbar)
64        self.__toolbar.set_show_close_button(True)
65        self.__vgrid.add(self.__container)
66        self.add(self.__vgrid)
67        self.__container.widget.connect("notify::folded",
68                                        self.__on_container_folded)
69
70    def show_miniplayer(self, show, reveal=False):
71        """
72            Show/hide subtoolbar
73            @param show as bool
74            @param reveal as bool
75        """
76        def show_buttons(show):
77            if show:
78                self.toolbar.end.show()
79                self.toolbar.playback.show()
80            else:
81                self.toolbar.end.hide()
82                self.toolbar.playback.hide()
83
84        def on_revealed(miniplayer, revealed):
85            miniplayer.set_vexpand(revealed)
86            show_buttons(not revealed)
87            if revealed:
88                self.__toolbar.hide()
89                self.__container.hide()
90                emit_signal(self.__container, "can-go-back-changed", False)
91            else:
92                self.__toolbar.show()
93                self.__container.show()
94                emit_signal(self.__container, "can-go-back-changed",
95                            self.__container.can_go_back)
96
97        if show and self.__miniplayer is None:
98            from lollypop.miniplayer import MiniPlayer
99            self.__miniplayer = MiniPlayer()
100            if App().player.current_track.id is not None:
101                self.__miniplayer.show()
102            self.__miniplayer.connect("revealed", on_revealed)
103            self.__vgrid.add(self.__miniplayer)
104            self.__miniplayer.set_vexpand(False)
105        elif not show and self.__miniplayer is not None:
106            if App().lookup_action("miniplayer").get_state():
107                App().lookup_action("miniplayer").change_state(
108                    GLib.Variant("b", False))
109            else:
110                self.__miniplayer.destroy()
111                self.__miniplayer = None
112                self.__container.show()
113                show_buttons(True)
114        if self.__miniplayer is not None:
115            if reveal:
116                self.__miniplayer.reveal(True)
117            else:
118                self.__miniplayer.update_artwork()
119
120    @property
121    def folded(self):
122        """
123            True if window is adaptive, ie widget folded
124        """
125        return App().window.container.widget.get_folded()
126
127    @property
128    def miniplayer(self):
129        """
130            @return MiniPlayer
131        """
132        return self.__miniplayer
133
134    @property
135    def toolbar(self):
136        """
137            @return Toolbar
138        """
139        return self.__toolbar
140
141    @property
142    def container(self):
143        """
144            @return Container
145        """
146        return self.__container
147
148##############
149# PROTECTED  #
150##############
151    def _on_current_changed(self, player):
152        """
153            Update toolbar
154            @param player as Player
155        """
156        if App().player.current_track.id is None:
157            self.set_title("Lollypop")
158        else:
159            artists = ", ".join(player.current_track.artists)
160            self.set_title("%s - %s" % (artists, player.current_track.name))
161            if self.__miniplayer is not None:
162                self.__miniplayer.show()
163
164    def _on_configure_event_timeout(self, width, height, x, y):
165        """
166            Setup content based on current size
167            @param width as int
168            @param height as int
169            @param x as int
170            @param y as int
171        """
172        self.__configure_timeout_id = None
173        if App().lookup_action("miniplayer").get_state():
174            return
175        if not self.is_maximized():
176            App().settings.set_value("window-size",
177                                     GLib.Variant("ai", [width, height]))
178        App().settings.set_value("window-position", GLib.Variant("ai", [x, y]))
179
180############
181# PRIVATE  #
182############
183    def __setup_size_and_position(self):
184        """
185            Setup window position and size, callbacks
186        """
187        try:
188            size = App().settings.get_value("window-size")
189            pos = App().settings.get_value("window-position")
190            self.resize(size[0], size[1])
191            self.move(pos[0], pos[1])
192            if App().settings.get_value("window-maximized"):
193                self.maximize()
194        except Exception as e:
195            Logger.error("Window::__setup_size_and_position(): %s", e)
196
197    def __on_realize(self, window):
198        """
199            Init window content
200            @param window as Gtk.Window
201        """
202        self.__setup_size_and_position()
203        if App().settings.get_value("auto-update") or App().tracks.is_empty():
204            App().scanner.update(ScanType.FULL)
205        # FIXME: remove this later
206        elif GLib.file_test("/app", GLib.FileTest.EXISTS) and\
207                not App().settings.get_value("flatpak-access-migration"):
208            App().scanner.update(ScanType.FULL)
209
210    def __on_button_release_event(self, window, event):
211        """
212            Handle special mouse buttons
213            @param window as Gtk.Window
214            @param event as Gdk.EventButton
215        """
216        if event.button == 8:
217            App().window.container.go_back()
218            return True
219
220    def __on_window_state_event(self, widget, event):
221        """
222            Save maximised state
223        """
224        if not App().lookup_action("miniplayer").get_state():
225            App().settings.set_boolean("window-maximized",
226                                       "GDK_WINDOW_STATE_MAXIMIZED" in
227                                       event.new_window_state.value_names)
228
229    def __on_container_folded(self, leaflet, folded):
230        """
231            show/hide miniplayer
232            @param leaflet as Handy.Leaflet
233            @param folded as Gparam
234        """
235        emit_signal(self.__container,
236                    "can-go-back-changed",
237                    self.__container.can_go_back)
238        self.show_miniplayer(App().window.folded)
239
240    def __on_destroy(self, widget):
241        """
242            Remove ref cycle, just to prevent output on DEBUG_LEAK
243            @param widget as Gtk.Widget
244        """
245        self.__toolbar = None
246
247    def __on_configure_event(self, window, event):
248        """
249            Delay event
250            @param window as Gtk.Window
251            @param event as Gdk.EventConfigure
252        """
253        if self.__configure_timeout_id:
254            GLib.source_remove(self.__configure_timeout_id)
255        (width, height) = window.get_size()
256        (x, y) = window.get_position()
257        self.__configure_timeout_id = GLib.idle_add(
258            self._on_configure_event_timeout,
259            width, height, x, y, priority=GLib.PRIORITY_LOW)
260