1# Copyright 2006-2009 Scott Horowitz <stonecrest@gmail.com> 2# Copyright 2009-2014 Jonathan Ballet <jon@multani.info> 3# 4# This file is part of Sonata. 5# 6# Sonata is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation, either version 3 of the License, or 9# (at your option) any later version. 10# 11# Sonata is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with Sonata. If not, see <http://www.gnu.org/licenses/>. 18 19""" 20This module provides a user interface for changing configuration 21variables. 22 23Example usage: 24import preferences 25... 26prefs = preferences.Preferences() 27prefs.on_prefs_real(self.window, self.prefs_window_response, tab callbacks...) 28""" 29 30import gettext, hashlib 31 32from gi.repository import Gtk, Gdk, GdkPixbuf 33 34from sonata.config import Config 35from sonata.pluginsystem import pluginsystem 36from sonata import ui, misc, consts, formatting 37import os 38 39class Extras_cbs: 40 """Callbacks and data specific to the extras tab""" 41 popuptimes = [] 42 notif_toggled = None 43 crossfade_changed = None 44 crossfade_toggled = None 45 46class Display_cbs: 47 """Callbacks specific to the display tab""" 48 stylized_toggled = None 49 art_toggled = None 50 playback_toggled = None 51 progress_toggled = None 52 statusbar_toggled = None 53 lyrics_toggled = None 54 trayicon_available = None 55 56class Behavior_cbs: 57 """Callbacks and data specific to the behavior tab""" 58 trayicon_toggled = None 59 trayicon_in_use = None 60 sticky_toggled = None 61 ontop_toggled = None 62 decorated_toggled = None 63 infofile_changed = None 64 65class Format_cbs: 66 """Callbacks specific to the format tab""" 67 currentoptions_changed = None 68 libraryoptions_changed = None 69 titleoptions_changed = None 70 currsongoptions1_changed = None 71 currsongoptions2_changed = None 72 73class Preferences(): 74 """This class implements a preferences dialog for changing 75 configuration variables. 76 77 Many changes are applied instantly with respective 78 callbacks. Closing the dialog causes a response callback. 79 """ 80 def __init__(self, config, reconnect, renotify, reinfofile, 81 settings_save, populate_profiles_for_menu): 82 83 self.config = config 84 85 # These are callbacks to Main 86 self.reconnect = reconnect 87 self.renotify = renotify 88 self.reinfofile = reinfofile 89 self.settings_save = settings_save 90 self.populate_profiles_for_menu = populate_profiles_for_menu 91 92 # Temporary flag: 93 self.updating_nameentry = False 94 95 self.prev_host = None 96 self.prev_password = None 97 self.prev_port = None 98 99 self.window = None 100 self.last_tab = 0 101 self.display_trayicon = None 102 self.direntry = None 103 self.using_mpd_env_vars = False 104 105 def on_prefs_real(self): 106 """Display the preferences dialog""" 107 108 self.builder = ui.builder('preferences') 109 self.provider = ui.css_provider('preferences') 110 self.prefswindow = self.builder.get_object('preferences_dialog') 111 self.prefswindow.set_transient_for(self.window) 112 self.prefsnotebook = self.builder.get_object('preferences_notebook') 113 114 tabs = ('mpd', 'display', 'behavior', 'format', 'extras', 'plugins') 115 116 for name in tabs: 117 func = getattr(self, '%s_tab' % name) 118 cbs = globals().get('%s_cbs' % name.capitalize()) 119 func(cbs) 120 121 close_button = self.builder.get_object('preferences_closebutton') 122 self.prefswindow.show_all() 123 self.prefsnotebook.set_current_page(self.last_tab) 124 close_button.grab_focus() 125 self.prefswindow.connect('response', self._window_response) 126 # Save previous connection properties to determine if we should try to 127 # connect to MPD after prefs are closed: 128 self.prev_host = self.config.host[self.config.profile_num] 129 self.prev_port = self.config.port[self.config.profile_num] 130 self.prev_password = self.config.password[self.config.profile_num] 131 132 def mpd_tab(self, cbs=None): 133 """Construct and layout the MPD tab""" 134 #frame.set_shadow_type(Gtk.ShadowType.NONE) 135 controlbox = self.builder.get_object('connection_frame_label_widget') 136 profiles = self.builder.get_object('connection_profiles') 137 add_profile = self.builder.get_object('connection_add_profile') 138 remove_profile = self.builder.get_object('connection_remove_profile') 139 self._populate_profile_combo(profiles, self.config.profile_num, 140 remove_profile) 141 nameentry = self.builder.get_object('connection_name') 142 hostentry = self.builder.get_object('connection_host') 143 portentry = self.builder.get_object('connection_port') 144 direntry = self.builder.get_object('connection_dir') 145 self.direntry = direntry 146 direntry.connect('selection-changed', self._direntry_changed, 147 profiles) 148 passwordentry = self.builder.get_object('connection_password') 149 autoconnect = self.builder.get_object('connection_autoconnect') 150 autoconnect.set_active(self.config.autoconnect) 151 autoconnect.connect('toggled', self._config_widget_active, 152 'autoconnect') 153 # Fill in entries with current profile: 154 self._profile_chosen(profiles, nameentry, hostentry, 155 portentry, passwordentry, direntry) 156 # Update display if $MPD_HOST or $MPD_PORT is set: 157 host, port, password = misc.mpd_env_vars() 158 if host or port: 159 self.using_mpd_env_vars = True 160 if not host: 161 host = "" 162 if not port: 163 port = 0 164 if not password: 165 password = "" 166 hostentry.set_text(str(host)) 167 portentry.set_value(port) 168 passwordentry.set_text(str(password)) 169 nameentry.set_text(_("Using MPD_HOST/PORT")) 170 for widget in [hostentry, portentry, passwordentry, 171 nameentry, profiles, add_profile, 172 remove_profile]: 173 widget.set_sensitive(False) 174 else: 175 self.using_mpd_env_vars = False 176 nameentry.connect('changed', self._nameentry_changed, 177 profiles, remove_profile) 178 hostentry.connect('changed', self._hostentry_changed, 179 profiles) 180 portentry.connect('value-changed', 181 self._portentry_changed, profiles) 182 passwordentry.connect('changed', 183 self._passwordentry_changed, profiles) 184 profiles.connect('changed', 185 self._profile_chosen, nameentry, hostentry, 186 portentry, passwordentry, direntry) 187 add_profile.connect('clicked', self._add_profile, 188 nameentry, profiles, remove_profile) 189 remove_profile.connect('clicked', self._remove_profile, 190 profiles, remove_profile) 191 192 def extras_tab(self, cbs): 193 """Construct and layout the extras tab""" 194 if not self.scrobbler.imported(): 195 self.config.as_enabled = False 196 197 as_checkbox = self.builder.get_object('scrobbler_check') 198 as_checkbox.set_active(self.config.as_enabled) 199 as_user_label = self.builder.get_object('scrobbler_username_label') 200 as_user_entry = self.builder.get_object('scrobbler_username_entry') 201 as_user_entry.set_text(self.config.as_username) 202 as_user_entry.connect('changed', self._as_username_changed) 203 as_pass_label = self.builder.get_object('scrobbler_password_label') 204 as_pass_entry = self.builder.get_object('scrobbler_password_entry') 205 as_pass_entry.set_text(self.config.as_password_md5) 206 as_pass_entry.connect('changed', self._as_password_changed) 207 display_notification = self.builder.get_object('notification_check') 208 display_notification.set_active(self.config.show_notification) 209 210 time_names = ["%s %s" % 211 (i , ngettext('second', 'seconds', int(i))) 212 for i in cbs.popuptimes if i != _('Entire song')] 213 notification_options = self.builder.get_object('notification_time_combo') 214 for time in time_names: 215 notification_options.append_text(time) 216 notification_options.connect('changed', self._notiftime_changed) 217 notification_options.set_active(self.config.popup_option) 218 notification_locs = self.builder.get_object('notification_loc_combo') 219 notification_locs.set_active(self.config.traytips_notifications_location) 220 notification_locs.connect('changed', self._notiflocation_changed) 221 notifhbox = self.builder.get_object('notification_box') 222 display_notification.connect('toggled', cbs.notif_toggled, 223 notifhbox) 224 if not self.config.show_notification: 225 notifhbox.set_sensitive(False) 226 227 crossfadespin = self.builder.get_object('crossfade_time') 228 crossfadespin.set_value(self.config.xfade) 229 crossfadespin.connect('value-changed', cbs.crossfade_changed) 230 crossfadelabel2 = self.builder.get_object('crossfade_label') 231 crossfadelabel3 = self.builder.get_object('crossfade_extra_label') 232 crossfadecheck = self.builder.get_object('crossfade_check') 233 crossfadecheck.connect('toggled', 234 self._crossfadecheck_toggled, crossfadespin, 235 crossfadelabel2, crossfadelabel3) 236 crossfadecheck.connect('toggled', cbs.crossfade_toggled, 237 crossfadespin) 238 crossfadecheck.set_active(self.config.xfade_enabled) 239 crossfadecheck.toggled() # Force the toggled callback 240 241 as_checkbox.connect('toggled', self._as_enabled_toggled, 242 as_user_entry, as_pass_entry, as_user_label, 243 as_pass_label) 244 if not self.config.as_enabled or not self.scrobbler.imported(): 245 for widget in (as_user_entry, as_pass_entry, 246 as_user_label, as_pass_label): 247 widget.set_sensitive(False) 248 249 def display_tab(self, cbs): 250 """Construct and layout the display tab""" 251 252 art = self.builder.get_object('art_check') 253 art.set_active(self.config.show_covers) 254 stylized_combo = self.builder.get_object('art_style_combo') 255 stylized_combo.set_active(self.config.covers_type) 256 stylized_combo.connect('changed', cbs.stylized_toggled) 257 art_prefs = self.builder.get_object('art_preferences') 258 art_prefs.set_sensitive(self.config.show_covers) 259 art_combo = self.builder.get_object('art_search_combo') 260 art_combo.set_active(self.config.covers_pref) 261 art_combo.connect('changed', self._config_widget_active, 'covers_pref') 262 263 #FIXME move into preferences_display.ui? 264 art_location = self.builder.get_object('art_save_combo') 265 for item in ["%s/%s" % (_("SONG_DIR"), item) 266 for item in ("cover.jpg", "album.jpg", "folder.jpg", 267 self.config.art_location_custom_filename or _("custom"))]: 268 art_location.append_text(item) 269 art_location.set_active(self.config.art_location) 270 art_location.connect('changed', self._art_location_changed) 271 272 art.connect('toggled', cbs.art_toggled, art_prefs) 273 playback = self.builder.get_object('playback_buttons_check') 274 playback.set_active(self.config.show_playback) 275 playback.connect('toggled', cbs.playback_toggled) 276 progress = self.builder.get_object('progressbar_check') 277 progress.set_active(self.config.show_progress) 278 progress.connect('toggled', cbs.progress_toggled) 279 statusbar = self.builder.get_object('statusbar_check') 280 statusbar.set_active(self.config.show_statusbar) 281 statusbar.connect('toggled', cbs.statusbar_toggled) 282 lyrics = self.builder.get_object('lyrics_check') 283 lyrics.set_active(self.config.show_lyrics) 284 lyrics_location = self.builder.get_object('lyrics_save_combo') 285 lyrics_location.set_active(self.config.lyrics_location) 286 lyrics_location.connect('changed', self._lyrics_location_changed) 287 lyrics_location_hbox = self.builder.get_object('lyrics_preferences') 288 lyrics_location_hbox.set_sensitive(self.config.show_lyrics) 289 lyrics.connect('toggled', cbs.lyrics_toggled, lyrics_location_hbox) 290 trayicon = self.builder.get_object('tray_icon_check') 291 self.display_trayicon = trayicon 292 trayicon.set_active(self.config.show_trayicon) 293 trayicon.set_sensitive(cbs.trayicon_available) 294 295 def behavior_tab(self, cbs): 296 """Construct and layout the behavior tab""" 297 298 frame = self.builder.get_object('behavior_frame') 299 sticky = self.builder.get_object('behavior_sticky_check') 300 sticky.set_active(self.config.sticky) 301 sticky.connect('toggled', cbs.sticky_toggled) 302 ontop = self.builder.get_object('behavior_ontop_check') 303 ontop.set_active(self.config.ontop) 304 ontop.connect('toggled', cbs.ontop_toggled) 305 decor = self.builder.get_object('behavior_decor_check') 306 decor.set_active(not self.config.decorated) 307 decor.connect('toggled', cbs.decorated_toggled, self.prefswindow) 308 minimize = self.builder.get_object('behavior_minimize_check') 309 minimize.set_active(self.config.minimize_to_systray) 310 minimize.connect('toggled', self._config_widget_active, 311 'minimize_to_systray') 312 self.display_trayicon.connect('toggled', cbs.trayicon_toggled, 313 minimize) 314 minimize.set_sensitive(cbs.trayicon_in_use) 315 316 update_start = self.builder.get_object('misc_updatestart_check') 317 update_start.set_active(self.config.update_on_start) 318 update_start.connect('toggled', self._config_widget_active, 319 'update_on_start') 320 exit_stop = self.builder.get_object('misc_exit_stop_check') 321 exit_stop.set_active(self.config.stop_on_exit) 322 exit_stop.connect('toggled', self._config_widget_active, 323 'stop_on_exit') 324 infofile_usage = self.builder.get_object('misc_infofile_usage_check') 325 infofile_usage.set_active(self.config.use_infofile) 326 infopath_options = self.builder.get_object('misc_infofile_entry') 327 infopath_options.set_text(self.config.infofile_path) 328 infopath_options.connect('focus_out_event', 329 cbs.infofile_changed) 330 infopath_options.connect('activate', cbs.infofile_changed, None) 331 if not self.config.use_infofile: 332 infopath_options.set_sensitive(False) 333 infofile_usage.connect('toggled', self._infofile_toggled, 334 infopath_options) 335 336 def format_tab(self, cbs): 337 """Construct and layout the format tab""" 338 339 playlist_entry = self.builder.get_object('format_playlist_entry') 340 playlist_entry.set_text(self.config.currentformat) 341 library_entry = self.builder.get_object('format_library_entry') 342 library_entry.set_text(self.config.libraryformat) 343 window_entry = self.builder.get_object('format_window_entry') 344 window_entry.set_text(self.config.titleformat) 345 current1_entry = self.builder.get_object('format_current1_entry') 346 current1_entry.set_text(self.config.currsongformat1) 347 current2_entry = self.builder.get_object('format_current2_entry') 348 current2_entry.set_text(self.config.currsongformat2) 349 entries = [playlist_entry, library_entry, window_entry, current1_entry, 350 current2_entry] 351 352 entry_cbs = (cbs.currentoptions_changed, 353 cbs.libraryoptions_changed, 354 cbs.titleoptions_changed, 355 cbs.currsongoptions1_changed, 356 cbs.currsongoptions2_changed) 357 for entry, cb, next in zip(entries, entry_cbs, 358 entries[1:] + entries[:1]): 359 entry.connect('focus_out_event', cb) 360 entry.connect('activate', lambda _, n: n.grab_focus(), 361 next) 362 363 format_grid = self.builder.get_object('format_avail_descs') 364 codeset = formatting.formatcodes 365 codes = (codeset[:len(codeset) // 2], 366 codeset[len(codeset) // 2:]) 367 368 def make_label(content): 369 return Gtk.Label('<small>{}</small>'.format(content), 370 use_markup=True, xalign=0, yalign=0) 371 372 for column_base, codegroup in enumerate(codes): 373 column = column_base * 2 374 for row, code in enumerate(codegroup): 375 format_code = make_label("%{}".format(code.code)) 376 format_code.get_style_context().add_class('format_code') 377 format_desc = make_label(code.description) 378 format_grid.attach(format_code, column, row, 1, 1) 379 format_grid.attach(format_desc, column + 1, row, 1, 1) 380 381 additionalinfo = self.builder.get_object('format_additional_label') 382 # FIXME need to either separate markup from localized strings OR 383 # include markup in the strings and let the translators work around them 384 row = len(codes[0]) 385 enclosed_code = make_label('{ }') 386 enclosed_code.get_style_context().add_class('format_code') 387 enclosed_desc = make_label( 388 _('Info displayed only if all enclosed tags are defined')) 389 enclosed_desc.set_line_wrap(True) 390 enclosed_desc.set_max_width_chars(40) 391 column_code = make_label('|') 392 column_code.get_style_context().add_class('format_code') 393 column_desc = make_label(_('Creates columns in the current playlist')) 394 column_desc.set_line_wrap(True) 395 column_desc.set_max_width_chars(40) 396 397 for widget in (enclosed_code, enclosed_desc): 398 widget.get_style_context().add_class('additional_format') 399 format_grid.attach(enclosed_code, 0, row, 1, 1) 400 format_grid.attach(enclosed_desc, 1, row, 3, 1) 401 row += 1 402 format_grid.attach(column_code, 0, row, 1, 1) 403 format_grid.attach(column_desc, 1, row, 3, 1) 404 405 def plugins_tab(self, cbs=None): 406 """Construct and layout the plugins tab""" 407 408 self.plugin_UIManager = self.builder.get_object('plugins_ui_manager') 409 menu_handlers = { 410 "plugin_configure": self.plugin_configure, 411 "plugin_about": self.plugin_about 412 } 413 self.builder.connect_signals(menu_handlers) 414 415 self.pluginview = self.builder.get_object('plugins_treeview') 416 self.pluginselection = self.pluginview.get_selection() 417 plugindata = self.builder.get_object('plugins_store') 418 self.pluginview.connect('button-press-event', self.plugin_click) 419 420 plugincheckcell = self.builder.get_object('plugins_check_renderer') 421 plugincheckcell.connect('toggled', self.plugin_toggled, 422 (plugindata, 0)) 423 424 plugindata.clear() 425 for plugin in pluginsystem.get_info(): 426 pb = self.plugin_get_icon_pixbuf(plugin) 427 plugin_text = "<b>" + plugin.longname + "</b> " + plugin.version_string 428 plugin_text += "\n" + plugin.description 429 enabled = plugin.get_enabled() 430 plugindata.append((enabled, pb, plugin_text)) 431 432 def _window_response(self, window, response): 433 if response == Gtk.ResponseType.CLOSE: 434 self.last_tab = self.prefsnotebook.get_current_page() 435 #XXX: These two are probably never triggered 436 if self.config.show_lyrics and self.config.lyrics_location != consts.LYRICS_LOCATION_HOME: 437 if not os.path.isdir(self.config.current_musicdir): 438 ui.show_msg(self.window, _("To save lyrics to the music file's directory, you must specify a valid music directory."), _("Music Dir Verification"), 'musicdirVerificationError', Gtk.ButtonsType.CLOSE) 439 # Set music_dir entry focused: 440 self.prefsnotebook.set_current_page(0) 441 self.direntry.grab_focus() 442 return 443 if self.config.show_covers and self.config.art_location != consts.ART_LOCATION_HOMECOVERS: 444 if not os.path.isdir(self.config.current_musicdir): 445 ui.show_msg(self.window, _("To save artwork to the music file's directory, you must specify a valid music directory."), _("Music Dir Verification"), 'musicdirVerificationError', Gtk.ButtonsType.CLOSE) 446 # Set music_dir entry focused: 447 self.prefsnotebook.set_current_page(0) 448 self.direntry.grab_focus() 449 return 450 if not self.using_mpd_env_vars: 451 if self.prev_host != self.config.host[self.config.profile_num] or self.prev_port != self.config.port[self.config.profile_num] or self.prev_password != self.config.password[self.config.profile_num]: 452 # Try to connect if mpd connection info has been updated: 453 ui.change_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH)) 454 self.reconnect() 455 self.settings_save() 456 self.populate_profiles_for_menu() 457 ui.change_cursor(None) 458 window.destroy() 459 460 def _config_widget_active(self, widget, member): 461 """Sets a config attribute to the widget's active value""" 462 setattr(self.config, member, widget.get_active()) 463 464 def _as_enabled_toggled(self, checkbox, *widgets): 465 if checkbox.get_active(): 466 self.scrobbler.import_module(True) 467 if self.scrobbler.imported(): 468 self.config.as_enabled = checkbox.get_active() 469 self.scrobbler.init() 470 for widget in widgets: 471 widget.set_sensitive(self.config.as_enabled) 472 elif checkbox.get_active(): 473 checkbox.set_active(False) 474 475 def _as_username_changed(self, entry): 476 if self.scrobbler.imported(): 477 self.config.as_username = entry.get_text() 478 self.scrobbler.auth_changed() 479 480 def _as_password_changed(self, entry): 481 if self.scrobbler.imported(): 482 self.config.as_password_md5 = hashlib.md5( 483 entry.get_text().encode('utf-8')).hexdigest() 484 self.scrobbler.auth_changed() 485 486 def _nameentry_changed(self, entry, profile_combo, remove_profiles): 487 if not self.updating_nameentry: 488 profile_num = profile_combo.get_active() 489 self.config.profile_names[profile_num] = entry.get_text() 490 self._populate_profile_combo(profile_combo, profile_num, remove_profiles) 491 492 def _hostentry_changed(self, entry, profile_combo): 493 profile_num = profile_combo.get_active() 494 self.config.host[profile_num] = entry.get_text() 495 496 def _portentry_changed(self, entry, profile_combo): 497 profile_num = profile_combo.get_active() 498 self.config.port[profile_num] = entry.get_value_as_int() 499 500 def _passwordentry_changed(self, entry, profile_combo): 501 profile_num = profile_combo.get_active() 502 self.config.password[profile_num] = entry.get_text() 503 504 def _direntry_changed(self, entry, profile_combo): 505 profile_num = profile_combo.get_active() 506 self.config.musicdir[profile_num] = misc.sanitize_musicdir(entry.get_filename()) 507 508 def _add_profile(self, _button, nameentry, profile_combo, remove_profiles): 509 self.updating_nameentry = True 510 profile_num = profile_combo.get_active() 511 self.config.profile_names.append(_("New Profile")) 512 nameentry.set_text(self.config.profile_names[-1]) 513 self.updating_nameentry = False 514 self.config.host.append(self.config.host[profile_num]) 515 self.config.port.append(self.config.port[profile_num]) 516 self.config.password.append(self.config.password[profile_num]) 517 self.config.musicdir.append(self.config.musicdir[profile_num]) 518 self._populate_profile_combo(profile_combo, len(self.config.profile_names)-1, remove_profiles) 519 self.populate_profiles_for_menu() 520 521 def _remove_profile(self, _button, profile_combo, remove_profiles): 522 profile_num = profile_combo.get_active() 523 if profile_num == self.config.profile_num: 524 # Profile deleted, revert to first profile: 525 self.config.profile_num = 0 526 self.reconnect(None) 527 self.config.profile_names.pop(profile_num) 528 self.config.host.pop(profile_num) 529 self.config.port.pop(profile_num) 530 self.config.password.pop(profile_num) 531 self.config.musicdir.pop(profile_num) 532 self.populate_profiles_for_menu() 533 if profile_num > 0: 534 self._populate_profile_combo(profile_combo, profile_num-1, remove_profiles) 535 else: 536 self._populate_profile_combo(profile_combo, 0, remove_profiles) 537 538 def _profile_chosen(self, profile_combo, nameentry, hostentry, portentry, passwordentry, direntry): 539 profile_num = profile_combo.get_active() 540 self.updating_nameentry = True 541 nameentry.set_text(str(self.config.profile_names[profile_num])) 542 self.updating_nameentry = False 543 hostentry.set_text(str(self.config.host[profile_num])) 544 portentry.set_value(self.config.port[profile_num]) 545 passwordentry.set_text(str(self.config.password[profile_num])) 546 direntry.set_current_folder(misc.sanitize_musicdir(self.config.musicdir[profile_num])) 547 548 def _populate_profile_combo(self, profile_combo, active_index, remove_profiles): 549 new_model = Gtk.ListStore(str) 550 new_model.clear() 551 profile_combo.set_model(new_model) 552 for i, profile_name in enumerate(self.config.profile_names): 553 combo_text = "[%s] %s" % (i+1, profile_name[:15]) 554 if len(profile_name) > 15: 555 combo_text += "..." 556 profile_combo.append_text(combo_text) 557 profile_combo.set_active(active_index) 558 # Enable remove button if there is more than one profile 559 remove_profiles.set_sensitive(len(self.config.profile_names) > 1) 560 561 def _lyrics_location_changed(self, combobox): 562 self.config.lyrics_location = combobox.get_active() 563 564 def _art_location_changed(self, combobox): 565 if combobox.get_active() == consts.ART_LOCATION_CUSTOM: 566 dialog = self.builder.get_object('custom_art_dialog') 567 # Prompt user for playlist name: 568 entry = self.builder.get_object('custom_art_entry') 569 dialog.vbox.show_all() 570 response = dialog.run() 571 if response == Gtk.ResponseType.ACCEPT: 572 self.config.art_location_custom_filename = entry.get_text().replace("/", "") 573 iter = combobox.get_active_iter() 574 model = combobox.get_model() 575 model.set(iter, 0, "SONG_DIR/" + (self.config.art_location_custom_filename or _("custom"))) 576 else: 577 # Revert to non-custom item in combobox: 578 combobox.set_active(self.config.art_location) 579 dialog.hide() 580 self.config.art_location = combobox.get_active() 581 582 def _crossfadecheck_toggled(self, button, *widgets): 583 button_active = button.get_active() 584 for widget in widgets: 585 widget.set_sensitive(button_active) 586 587 def _notiflocation_changed(self, combobox): 588 self.config.traytips_notifications_location = combobox.get_active() 589 self.renotify() 590 591 def _notiftime_changed(self, combobox): 592 self.config.popup_option = combobox.get_active() 593 self.renotify() 594 595 def _infofile_toggled(self, button, infofileformatbox): 596 self.config.use_infofile = button.get_active() 597 infofileformatbox.set_sensitive(self.config.use_infofile) 598 if self.config.use_infofile: 599 self.reinfofile() 600 601 def plugin_click(self, _widget, event): 602 if event.button == 3: 603 self.plugin_UIManager.get_widget('/pluginmenu').popup(None, None, None, None, event.button, event.time) 604 605 def plugin_toggled(self, _renderer, path, user_data): 606 model, column = user_data 607 enabled = not model[path][column] 608 plugin = pluginsystem.get_info()[int(path)] 609 pluginsystem.set_enabled(plugin, enabled) 610 611 if enabled: 612 # test that the plugin loads or already was loaded 613 if not plugin.force_loaded(): 614 enabled = False 615 pluginsystem.set_enabled(plugin, enabled) 616 617 model[path][column] = enabled 618 619 def plugin_about(self, _widget): 620 plugin = self.plugin_get_selected() 621 iconpb = self.plugin_get_icon_pixbuf(plugin) 622 623 about_text = plugin.longname + "\n" + plugin.author + "\n" 624 if len(plugin.author_email) > 0: 625 about_text += "<" + plugin.author_email + ">" 626 627 self.about_dialog = Gtk.AboutDialog() 628 self.about_dialog.set_name(plugin.longname) 629 self.about_dialog.set_role('about') 630 self.about_dialog.set_version(plugin.version_string) 631 if len(plugin.description.strip()) > 0: 632 self.about_dialog.set_comments(plugin.description) 633 if len(plugin.author.strip()) > 0: 634 author = plugin.author 635 if len(plugin.author.strip()) > 0: 636 author += ' <' + plugin.author_email + '>' 637 self.about_dialog.set_authors([author]) 638 if len(plugin.url.strip()) > 0: 639 self.about_dialog.connect("activate-link", self.plugin_show_website) 640 self.about_dialog.set_website(plugin.url) 641 self.about_dialog.set_logo(iconpb) 642 643 self.about_dialog.connect('response', self.plugin_about_close) 644 self.about_dialog.connect('delete_event', self.plugin_about_close) 645 self.about_dialog.show_all() 646 647 def plugin_about_close(self, _event, _data=None): 648 self.about_dialog.hide() 649 return True 650 651 def plugin_show_website(self, _dialog, link): 652 return misc.browser_load(link, self.config.url_browser, \ 653 self.window) 654 655 def plugin_configure(self, _widget): 656 plugin = self.plugin_get_selected() 657 ui.show_msg(self.prefswindow, "Nothing yet implemented.", "Configure", "pluginConfigure", Gtk.ButtonsType.CLOSE) 658 659 def plugin_get_selected(self): 660 model, i = self.pluginselection.get_selected() 661 plugin_num = model.get_path(i).get_indices()[0] 662 return pluginsystem.get_info()[plugin_num] 663 664 def plugin_get_icon_pixbuf(self, plugin): 665 pb = plugin.iconurl 666 try: 667 pb = GdkPixbuf.Pixbuf.new_from_file(pb) 668 except: 669 pb = self.pluginview.render_icon(Gtk.STOCK_EXECUTE, Gtk.IconSize.LARGE_TOOLBAR) 670 return pb 671