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