├── .gitignore ├── AlbumArtSearch ├── tmpl │ ├── main.css │ └── albumartsearch-tmpl.html ├── albumartsearch.plugin └── AlbumArtSearch.py ├── rb-open-folder ├── rb-open-folder.plugin └── rb-open-folder.py ├── equalizer ├── equalizer.plugin ├── equalizer.plugin3 ├── install.sh ├── equalizer.py ├── Conf.py ├── ConfDialog.py ├── equalizer-prefs.ui └── equalizer_rb3compat.py ├── README.md └── pygi-convert.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /AlbumArtSearch/tmpl/main.css: -------------------------------------------------------------------------------- 1 | body { font-size: 8pt; padding: 6px; line-height: 1.4em; } 2 | #heading {font-size: 10pt; text-align: center; background-color: #555; color: white;} 3 | .srchresult {margin-bottom: 2px; width: 125px; height: 125px} 4 | #searchtext {width:15em} 5 | #searchmode {width:15em} 6 | -------------------------------------------------------------------------------- /AlbumArtSearch/albumartsearch.plugin: -------------------------------------------------------------------------------- 1 | [Plugin] 2 | Loader=python 3 | Module=AlbumArtSearch 4 | IAge=2 5 | Name=Album Art Search Panel 6 | Description=Search album art using google image search. Adapted from context pane plugin by John Iacona 7 | Authors=Rupesh Kumar ,Luqman Aden 8 | Copyright=Copyright © 2010 Rupesh Kumar, 2011 Luqman Aden 9 | Website= 10 | -------------------------------------------------------------------------------- /rb-open-folder/rb-open-folder.plugin: -------------------------------------------------------------------------------- 1 | [Plugin] 2 | Loader=python 3 | Module=rb-open-folder 4 | IAge=1 5 | Name=Open Folder 6 | Description=Open the folder that contains the selected song. 7 | Authors=Adolfo González Blázquez , Ali Vakilzade 8 | Copyright=Copyright © 2007, 2008 Adolfo González Blázquez, 2011 Ali Vakilzade 9 | Website=http://www.infinicode.org/code/rb/ 10 | -------------------------------------------------------------------------------- /equalizer/equalizer.plugin: -------------------------------------------------------------------------------- 1 | [Plugin] 2 | Loader=python 3 | Module=equalizer 4 | IAge=2 5 | Depends=rb 6 | Name=Equalizer 7 | Description=10 Band Equalizer 8 | Authors=Teemu Kallio ;Floreal Morandat ;Luqman Aden 9 | Copyright=Copyright © 2008 Teemu Kallio, 2009-2010 Morandat, 2011 Luqman Aden, Icon Zdravko Nicolov 10 | Website=http://www.lirmm.fr/~morandat/index.php/Main/Tools 11 | -------------------------------------------------------------------------------- /equalizer/equalizer.plugin3: -------------------------------------------------------------------------------- 1 | [Plugin] 2 | Loader=python3 3 | Module=equalizer 4 | IAge=2 5 | Depends=rb 6 | Name=Equalizer 7 | Description=10 Band Equalizer 8 | Authors=Teemu Kallio ;Floreal Morandat ;Luqman Aden 9 | Copyright=Copyright © 2008 Teemu Kallio, 2009-2010 Morandat, 2011 Luqman Aden, Icon Zdravko Nicolov 10 | Website=http://www.lirmm.fr/~morandat/index.php/Main/Tools 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Plugins 2 | ======= 3 | 4 | AlbumArtSearch 5 | -------------- 6 | Do a google search for album-art and allow you to save in the rhythmbox cache/song folder or your Music folder 7 | 8 | 9 | 10 | Equalizer 11 | --------- 12 | Graphical equalizer 13 | 14 | Installation instructions: 15 | 16 |
17 | git clone https://github.com/luqmana/rhythmbox-plugins.git
18 | cd rhythmbox-plugins/equalizer
19 | 
20 | 21 | For debian and debian based systems such as Ubuntu and Linux Mint: 22 | 23 |
24 | sudo apt-get install gir1.2-gconf-2.0 python-lxml
25 | 
26 | 27 | 28 | For rhythmbox v2.96 - 2.98: 29 | 30 |
31 | ./install.sh
32 | 
33 | 34 | Enable the equalizer plugin in Edit - Plugins - Equalizer 35 | 36 | For rhythmbox v2.99: 37 | 38 |
39 | ./install.sh
40 | 
41 | 42 | Enable the equalizer plugin in the app menu - Plugins - Equalizer 43 | 44 | For rhythmbox v3.0 and later: 45 | 46 |
47 | ./install.sh --rb3
48 | 
49 | 50 | Enable the equalizer plugin in the app menu - Plugins - Equalizer 51 | 52 | 53 | Open Folder 54 | ----------- 55 | Right-click option to open the song folder where the selected folder is saved 56 | 57 | 58 | ----------- 59 | 60 | See the individual plugins for further information. 61 | -------------------------------------------------------------------------------- /equalizer/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ################################ USAGE ####################################### 4 | 5 | usage=$( 6 | cat < 3 | # 2009-2010 - Floreal Morandat 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see 17 | 18 | from ConfDialog import ConfDialog 19 | import Conf 20 | 21 | import os 22 | from gi.repository import GObject, Gst, Peas 23 | from gi.repository import RB 24 | 25 | class EqualizerPlugin(GObject.Object, Peas.Activatable): 26 | __gtype_name__ = 'EqualizerPlugin' 27 | object = GObject.property(type = GObject.Object) 28 | 29 | def __init__(self): 30 | GObject.Object.__init__(self) 31 | 32 | def do_activate(self): 33 | self.shell = self.object 34 | self.sp = self.shell.props.shell_player 35 | 36 | self.conf = Conf.Config() 37 | Gst.init(None) 38 | self.eq = Gst.ElementFactory.make('equalizer-10bands', None) 39 | self.conf.apply_settings(self.eq) 40 | 41 | self.glade_f = self.find_file("equalizer-prefs.ui") 42 | 43 | self.dialog = ConfDialog(self.glade_f, self.conf, self.eq, self) 44 | self.dialog.add_ui(self.shell) 45 | 46 | self.psc_id = self.sp.connect('playing-song-changed', 47 | self.playing_song_changed) 48 | 49 | try: 50 | 51 | if (self.sp.get_playing()): 52 | self.sp.stop() 53 | self.sp.props.player.add_filter(self.eq) 54 | self.sp.play() 55 | else: 56 | self.sp.props.player.add_filter(self.eq) 57 | 58 | except: 59 | pass 60 | 61 | def do_deactivate(self): 62 | 63 | self.sp.disconnect(self.psc_id) 64 | 65 | self.dialog.cleanup() 66 | 67 | try: 68 | 69 | if (self.sp.get_playing()): 70 | self.sp.stop() 71 | self.sp.props.player.remove_filter(self.eq) 72 | self.sp.play() 73 | else: 74 | self.sp.props.player.remove_filter(self.eq) 75 | 76 | except: 77 | pass 78 | 79 | del self.sp 80 | del self.glade_f 81 | del self.shell 82 | del self.conf 83 | del self.dialog 84 | del self.eq 85 | 86 | def playing_song_changed(self, sp, entry): 87 | if entry == None: 88 | return 89 | 90 | genre = entry.get_string(RB.RhythmDBPropType.GENRE) 91 | if self.conf.preset_exists(genre): 92 | self.conf.change_preset(genre, self.eq) 93 | 94 | def create_configure_dialog(self, dialog=None): 95 | dialog = self.dialog.get_dialog() 96 | dialog.present() 97 | return dialog 98 | 99 | def find_file(self, filename): 100 | info = self.plugin_info 101 | data_dir = info.get_data_dir() 102 | path = os.path.join(data_dir, filename) 103 | 104 | if os.path.exists(path): 105 | return path 106 | 107 | return RB.file(filename) 108 | -------------------------------------------------------------------------------- /rb-open-folder/rb-open-folder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | # 3 | # Copyright (C) 2007, 2008 Adolfo González Blázquez 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2, or (at your option) 8 | # any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | import rb 20 | from gi.repository import Gtk 21 | from gi.repository import GObject 22 | from gi.repository import RB 23 | from gi.repository import Peas 24 | from subprocess import Popen 25 | 26 | ui_str = """ 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | """ 53 | 54 | class OpenFolder(GObject.Object, Peas.Activatable): 55 | __gtype_name = 'OpenFolderPlugin' 56 | object = GObject.property (type = GObject.Object) 57 | 58 | def __init__(self): 59 | GObject.Object.__init__(self) 60 | 61 | def do_activate(self): 62 | shell = self.object 63 | self.action = Gtk.Action('OpenFolder', _('Open Containing Folder'), 64 | _('Open the folder that contains the selected song.'), 65 | 'rb-open-folder') 66 | self.activate_id = self.action.connect('activate', self.open_folder, shell) 67 | 68 | self.action_group = Gtk.ActionGroup('OpenFolderPluginActions') 69 | self.action_group.add_action(self.action) 70 | 71 | uim = shell.get_ui_manager () 72 | uim.insert_action_group(self.action_group, 0) 73 | self.ui_id = uim.add_ui_from_string(ui_str) 74 | uim.ensure_update() 75 | 76 | def open_folder(self, action, shell): 77 | source = shell.get_property("selected_page") 78 | entry = RB.Source.get_entry_view(source) 79 | selected = entry.get_selected_entries() 80 | if selected != []: 81 | uri = selected[0].get_playback_uri() 82 | dirpath = uri.rpartition('/')[0] 83 | if dirpath == "": dirpath = "/" 84 | Popen(["xdg-open", dirpath]) 85 | 86 | def do_deactivate(self): 87 | shell = self.object 88 | uim = shell.get_ui_manager() 89 | uim.remove_ui (self.ui_id) 90 | uim.remove_action_group (self.action_group) 91 | 92 | self.action_group = None 93 | self.action = None 94 | -------------------------------------------------------------------------------- /equalizer/Conf.py: -------------------------------------------------------------------------------- 1 | 2 | # Conf.py 3 | # 4 | # Copyright (C) 2008 - Teemu Kallio 5 | # 2009-2010 - Floreal Morandat 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see 19 | 20 | import gi 21 | gi.require_version('GConf', '2.0') 22 | from gi.repository import GConf, Gst 23 | 24 | EQUALIZER_GCONF_PREFIX = '/apps/rhythmbox/plugins/equalizer' 25 | EQUALIZER_PRESET = 'preset' 26 | 27 | class Config: 28 | def __init__(self): 29 | 30 | self.gconf_keys = [ 'band0', 31 | 'band1', 32 | 'band2', 33 | 'band3', 34 | 'band4', 35 | 'band5', 36 | 'band6', 37 | 'band7', 38 | 'band8', 39 | 'band9'] 40 | 41 | self.gconf = GConf.Client.get_default() 42 | self.config = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 43 | 44 | # Create default preset 45 | if not self.gconf.dir_exists(EQUALIZER_GCONF_PREFIX + '/default'): 46 | for i in range(0, 10): 47 | self.gconf.set_float(self.make_path(self.gconf_keys[i], 'default'), self.config[i]) 48 | 49 | if self.gconf.get_string(EQUALIZER_GCONF_PREFIX+'/'+EQUALIZER_PRESET): 50 | self.preset = self.gconf.get_string(EQUALIZER_GCONF_PREFIX+'/'+EQUALIZER_PRESET) 51 | else: 52 | self.preset = "default" 53 | 54 | self.read_settings(self.preset) 55 | 56 | def reset_all(self): 57 | self.gconf.recursive_unset(EQUALIZER_GCONF_PREFIX, 0) 58 | 59 | # Create default preset 60 | if not self.gconf.dir_exists(EQUALIZER_GCONF_PREFIX + '/default'): 61 | for i in range(0, 10): 62 | self.gconf.set_float(self.make_path(self.gconf_keys[i], 'default'), self.config[i]) 63 | 64 | self.preset = "default" 65 | self.read_settings(self.preset) 66 | 67 | def read_settings(self, preset): 68 | for i in range(0,10): 69 | if preset == "default": 70 | self.config[i] = self.read_value(preset, self.gconf_keys[i], 0.0) 71 | else: 72 | self.config[i] = self.read_value(preset, self.gconf_keys[i], self.config[i]) 73 | 74 | def apply_settings(self, eq): 75 | for i in range(0, 10): 76 | eq.set_property('band' + repr(i), self.config[i]) 77 | 78 | def write_settings(self): 79 | preset = self.preset 80 | self.gconf.set_string(EQUALIZER_GCONF_PREFIX+'/'+EQUALIZER_PRESET, preset) 81 | for i in range(0, 10): 82 | self.gconf.set_float(self.make_path(self.gconf_keys[i], preset), self.config[i]) 83 | 84 | def make_path(self, path, preset): 85 | return EQUALIZER_GCONF_PREFIX+'/' + preset + '/' + path 86 | 87 | def list_preset(self): 88 | return self.gconf.all_dirs(EQUALIZER_GCONF_PREFIX) 89 | 90 | def read_value(self, preset, value, default): 91 | gc = self.gconf 92 | path = self.make_path(value, preset) 93 | rv = gc.get_float(path) 94 | if(not rv): 95 | rv = default 96 | #gc.set_float(path, default) 97 | return rv 98 | 99 | def change_preset(self, new_preset, eq): 100 | if new_preset: 101 | m_preset = self.mangle(new_preset) 102 | else: 103 | return 104 | 105 | if self.preset_exists(m_preset): 106 | self.preset = self.mangle(m_preset) 107 | self.read_settings(self.preset) 108 | self.apply_settings(eq) 109 | else: 110 | Gst.Preset.load_preset(eq, new_preset) 111 | self.gconf.set_string(EQUALIZER_GCONF_PREFIX+'/'+EQUALIZER_PRESET, m_preset) 112 | self.config = list(eq.get_property('band' + str(i)) for i in range(0,10)) 113 | for i in range(0, 10): 114 | self.gconf.set_float(self.make_path(self.gconf_keys[i], m_preset), self.config[i]) 115 | self.preset = self.mangle(m_preset) 116 | 117 | def preset_exists(self, preset): 118 | return self.gconf.dir_exists(self.mangle(EQUALIZER_GCONF_PREFIX + '/' + preset)) 119 | 120 | def mangle(self, preset): 121 | #return preset 122 | return preset.replace(' ', '_') 123 | 124 | def demangle(self, preset): 125 | #return preset 126 | return preset.replace('_', ' ') 127 | -------------------------------------------------------------------------------- /equalizer/ConfDialog.py: -------------------------------------------------------------------------------- 1 | # ConfDialog.py 2 | # 3 | # Copyright (C) 2008 - Teemu Kallio 4 | # 2009-2010 - Floreal Morandat 5 | # 6 | # This program 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 | # This program 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 this program. If not, see 18 | 19 | from gi.repository import Gtk, Gio, Gdk, GdkPixbuf, Gst 20 | import Conf 21 | 22 | from equalizer_rb3compat import ActionGroup 23 | from equalizer_rb3compat import ApplicationShell 24 | 25 | ui_string=""" 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | """ 36 | 37 | class ConfDialog(object): 38 | def __init__(self, glade_file, conf, eq, plugin): 39 | self.plugin = plugin 40 | self.eq = eq 41 | self.conf = conf 42 | gladexml = Gtk.Builder() 43 | gladexml.add_from_file(glade_file) 44 | 45 | self.dialog = gladexml.get_object('preferences_dialog') 46 | self.dialog.connect("response", self.dialog_response) 47 | self.dialog.connect("destroy", self.on_destroy) 48 | self.dialog.connect("close", self.on_destroy) 49 | 50 | box = gladexml.get_object("presetchooser") 51 | self.box = box 52 | 53 | # workarounds 54 | # see https://bugzilla.gnome.org/show_bug.cgi?id=650369#c4 55 | self.box.set_entry_text_column(0) 56 | self.box.set_id_column(1) 57 | 58 | self.read_presets() 59 | box.connect("changed", self.preset_change) 60 | 61 | self.bands = [] 62 | for i in range(0,10): 63 | self.bands.append(gladexml.get_object("b" + repr(i))) 64 | self.bands[i].connect("value_changed", self.slider_changed) 65 | self.update_bands() 66 | conf.apply_settings(eq) 67 | 68 | def cleanup(self): 69 | self._appshell.cleanup() 70 | 71 | def on_destroy(self, dialog): 72 | dialog.hide() 73 | self.__init__(self.plugin.glade_f, self.conf, self.eq, self.plugin) 74 | return True 75 | 76 | def read_presets(self): 77 | box = self.box 78 | conf = self.conf 79 | if box: 80 | box.get_model().clear() 81 | 82 | current = conf.demangle(conf.preset) 83 | s_presets = sorted(Gst.Preset.get_preset_names(self.eq)) 84 | i = 1 85 | box.append_text("default") 86 | box.set_active(0) 87 | for preset in s_presets: 88 | box.append_text(preset) 89 | if (preset == current): 90 | box.set_active(i) 91 | i += 1 92 | 93 | def update_bands(self): 94 | for i in range(0, 10): 95 | self.bands[i].set_value(self.conf.config[i]) 96 | 97 | def get_dialog(self): 98 | return self.dialog 99 | 100 | def dialog_response(self, dialog, response): 101 | if (response == -4): 102 | self.conf.write_settings() 103 | dialog.hide() 104 | 105 | if (response == -6): 106 | self.conf.reset_all() 107 | self.box.set_active(0) 108 | 109 | if (response == -8 or response == -6): 110 | if self.box.get_active_text() == "default": 111 | self.conf.config = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 112 | else: 113 | Gst.Preset.load_preset(self.eq, self.box.get_active_text()) 114 | self.conf.config = list(self.eq.get_property('band' + str(i)) for i in range(0,10)) 115 | 116 | self.update_bands() 117 | 118 | def preset_change(self, entry): 119 | new_preset = entry.get_active_text() 120 | if new_preset != '': 121 | self.conf.change_preset(entry.get_active_text(), self.eq) 122 | self.update_bands() 123 | 124 | def slider_changed(self, hscale): 125 | eq = self.eq 126 | if eq == None: 127 | return 128 | i = self.bands.index(hscale) 129 | val = self.bands[i].get_value() 130 | self.conf.config[i] = val 131 | eq.set_property('band' + repr(i), val) 132 | self.conf.write_settings() 133 | 134 | def add_ui(self, shell): 135 | action_group = ActionGroup(shell, 'EqualizerActionGroup') 136 | action_group.add_action(func=self.show_ui, 137 | action_name='Equalize', label=_('_Equalizer'), 138 | action_type='app') 139 | 140 | self._appshell = ApplicationShell(shell) 141 | self._appshell.insert_action_group(action_group) 142 | self._appshell.add_app_menuitems(ui_string, 'EqualizerActionGroup') 143 | 144 | def show_ui(self, *args): 145 | self.read_presets() 146 | self.get_dialog().present() 147 | -------------------------------------------------------------------------------- /AlbumArtSearch/tmpl/albumartsearch-tmpl.html: -------------------------------------------------------------------------------- 1 | <%page args="artist, album, title, stylesheet" /> 2 | 3 | <%! 4 | import re 5 | remove_links = re.compile (']*> ',re.VERBOSE) 6 | 7 | def cleanup(text) : 8 | if text is None : 9 | return "No information available" 10 | text = remove_links.sub ('', text) 11 | text = text.replace('\n', '

') 12 | return text 13 | %> 14 | 15 | 16 | 17 | 18 | 19 | Rhythmbox album art search 20 | 21 | 22 | 165 | 166 | 167 |

Album art search

168 |
169 |
170 |
171 | 172 |
173 |
174 |
175 | Search Options: [+] 176 | 198 |
199 |
200 |
Loading...
201 | 202 | 203 | -------------------------------------------------------------------------------- /AlbumArtSearch/AlbumArtSearch.py: -------------------------------------------------------------------------------- 1 | import rb, re, os, urllib2, glib, unicodedata 2 | from gi.repository import GObject, Gtk, Pango, Peas, RB, WebKit 3 | from mako.template import Template 4 | 5 | albumart_search_ui = """ 6 | 7 | 8 | 9 | 10 | 11 | """ 12 | 13 | class AlbumArtSearchPlugin(GObject.Object, Peas.Activatable): 14 | __gtype_name__ = 'AlbumArtSearchPlugin' 15 | object = GObject.property(type = GObject.Object) 16 | 17 | MODE_PICTURES = 1 18 | MODE_RHYTHM = 2 19 | MODE_FOLDER = 3 20 | 21 | def __init__(self): 22 | GObject.Object.__init__(self) 23 | 24 | def do_activate(self): 25 | 26 | self.shell = self.object 27 | self.sp = self.shell.props.shell_player 28 | self.db = self.shell.get_property('db') 29 | 30 | self.file = "" 31 | self.basepath = 'file://' + os.path.split(rb.find_plugin_file(self, "AlbumArtSearch.py"))[0] 32 | self.load_templates() 33 | 34 | self.current_artist = None 35 | self.current_album = None 36 | self.current_song = None 37 | self.current_location = None 38 | self.visible = False 39 | 40 | self.mode = self.MODE_FOLDER 41 | 42 | self.init_gui() 43 | self.connect_signals() 44 | 45 | self.action = ('ToggleAlbumArtSearch', Gtk.STOCK_DND_MULTIPLE, _('Album Art'), 46 | None, _('Change the visibility of album art search pane'), 47 | self.toggle_visibility, False) 48 | self.action_group = Gtk.ActionGroup('AlbumArtSearchActions') 49 | self.action_group.add_toggle_actions([self.action]) 50 | uim = self.shell.props.ui_manager 51 | uim.insert_action_group (self.action_group, -1) 52 | self.ui_id = uim.add_ui_from_string(albumart_search_ui) 53 | uim.ensure_update() 54 | 55 | def do_deactivate(self): 56 | 57 | self.sp.disconnect(self.player_cb_ids[0]) 58 | self.sp.disconnect(self.player_cb_ids[1]) 59 | 60 | self.shell.remove_widget (self.vbox, RB.ShellUILocation.RIGHT_SIDEBAR) 61 | 62 | self.shell.props.ui_manager.remove_action_group(self.action_group) 63 | self.shell.props.ui_manager.remove_ui(self.ui_id) 64 | 65 | del self.ui_id 66 | del self.action_group 67 | del self.sp 68 | del self.db 69 | del self.shell 70 | 71 | def connect_signals(self) : 72 | self.player_cb_ids = ( self.sp.connect ('playing-changed', self.playing_changed_cb), self.sp.connect ('playing-song-changed', self.playing_changed_cb)) 73 | self.albumartbutton.connect('clicked', self.set_album_art) 74 | 75 | def playing_changed_cb (self, playing, user_data) : 76 | playing_entry = None 77 | if self.sp: 78 | playing_entry = self.sp.get_playing_entry () 79 | if playing_entry is None : 80 | return 81 | 82 | playing_artist = playing_entry.get_string(RB.RhythmDBPropType.ARTIST) 83 | playing_album = playing_entry.get_string(RB.RhythmDBPropType.ALBUM) 84 | playing_title = playing_entry.get_string(RB.RhythmDBPropType.TITLE) 85 | playing_location = playing_entry.get_string(RB.RhythmDBPropType.LOCATION) 86 | self.current_location = playing_location 87 | 88 | if playing_album.upper() == "UNKNOWN": 89 | self.current_album = "" 90 | if playing_artist.upper() == "UNKNOWN": 91 | self.current_artist = "" 92 | if not(self.current_album == "" and self.current_artist == ""): 93 | self.current_artist = playing_artist.replace ('&', '&') 94 | self.current_album = playing_album.replace ('&', '&') 95 | self.current_title = playing_title.replace ('&', '&') 96 | self.render_album_art_search(self.current_artist, self.current_album, self.current_title) 97 | 98 | def set_album_art(self, button) : 99 | tburl = self.webview.get_main_frame().get_title() 100 | 101 | if not tburl: 102 | return 103 | print "Url: " + tburl 104 | filep = tburl.split('/') 105 | filepath = filep[len(filep)-1] 106 | (filen, filextn) = os.path.splitext(filepath) 107 | request = urllib2.Request(tburl) 108 | opener = urllib2.build_opener() 109 | image = None 110 | try: 111 | image = opener.open(request).read() 112 | except: 113 | print "Failed to download image" 114 | 115 | save_filename=self.current_artist + " - " + self.current_album + ".jpg" 116 | 117 | if(self.mode == self.MODE_PICTURES): 118 | covers_folder = glib.get_user_special_dir(glib.USER_DIRECTORY_PICTURES) 119 | filename = covers_folder + "/" + save_filename 120 | 121 | if(self.mode == self.MODE_RHYTHM): 122 | covers_folder=os.environ['HOME']+"/.cache/rhythmbox/covers/" 123 | filename = covers_folder + save_filename 124 | if (os.path.isdir(covers_folder) == False): 125 | os.mkdir(covers_folder) 126 | 127 | if(self.mode == self.MODE_FOLDER): 128 | location_path_improper = urllib2.url2pathname(self.current_location) 129 | location_path_arr = location_path_improper.split("//") 130 | location_path = location_path_arr[1] 131 | filename = location_path.rsplit("/",1)[0] + "/cover" #+ save_filename 132 | 133 | output = open(filename, 'w') 134 | output.write(image) 135 | output.close() 136 | 137 | def render_album_art_search(self, artistname, albumname, currenttitle): 138 | stripartistname=unicodedata.normalize('NFKD', unicode(artistname, errors='replace')).encode('ascii','ignore') 139 | stripalbumname=unicodedata.normalize('NFKD', unicode(albumname, errors='replace')).encode('ascii','ignore') 140 | stripcurrenttitle=unicodedata.normalize('NFKD', unicode(currenttitle, errors='replace')).encode('ascii','ignore') 141 | self.file = self.template.render (artist = stripartistname, album = stripalbumname, title = stripcurrenttitle, stylesheet = self.styles ) 142 | self.webview.load_string (self.file, 'text/html', 'utf-8', self.basepath) 143 | 144 | def toggle_visibility (self, action) : 145 | if not self.visible: 146 | self.shell.add_widget (self.vbox, RB.ShellUILocation.RIGHT_SIDEBAR, expand=True, fill=True)#, padding=4) 147 | self.visible = True 148 | else: 149 | self.shell.remove_widget (self.vbox, RB.ShellUILocation.RIGHT_SIDEBAR) 150 | self.visible = False 151 | 152 | def load_templates(self): 153 | self.path = rb.find_plugin_file(self, 'tmpl/albumartsearch-tmpl.html') 154 | self.template = Template (filename = self.path, module_directory = '/tmp/') 155 | self.styles = self.basepath + '/tmpl/main.css' 156 | 157 | def toggled_rhythm_radio(self, extra): 158 | if(self.rhythmlocradio.get_active()): 159 | self.mode = self.MODE_RHYTHM 160 | 161 | def toggled_folder_radio(self, extra): 162 | if(self.folderlocradio.get_active()): 163 | self.mode = self.MODE_FOLDER 164 | 165 | def toggled_pictures_radio(self, extra): 166 | if(self.pictureslocradio.get_active()): 167 | self.mode = self.MODE_PICTURES 168 | 169 | def init_gui(self) : 170 | self.vbox = Gtk.VBox() 171 | 172 | #---- set up webkit pane -----# 173 | self.webview = WebKit.WebView() 174 | self.scroll = Gtk.ScrolledWindow() 175 | self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) 176 | self.scroll.set_shadow_type(Gtk.ShadowType.IN) 177 | self.scroll.add( self.webview ) 178 | self.albumartbutton = Gtk.Button (_("Save Album Art")) 179 | 180 | self.selectlabel = Gtk.Label() 181 | self.selectlabel.set_markup("Choose save location") 182 | 183 | self.rhythmlocradio = Gtk.RadioButton.new_with_label_from_widget(None, "Rhythmbox Location") 184 | self.folderlocradio = Gtk.RadioButton.new_from_widget(self.rhythmlocradio) 185 | self.folderlocradio.set_label("Song Folder") 186 | self.pictureslocradio = Gtk.RadioButton.new_from_widget(self.folderlocradio) 187 | self.pictureslocradio.set_label("Pictures Folder") 188 | 189 | self.folderlocradio.set_active(True) 190 | 191 | self.rhythmlocradio.connect("toggled", self.toggled_rhythm_radio) 192 | self.folderlocradio.connect("toggled", self.toggled_folder_radio) 193 | self.pictureslocradio.connect("toggled", self.toggled_pictures_radio) 194 | 195 | self.hboxlabel = Gtk.HBox(); 196 | 197 | self.vbox.pack_start(self.scroll, expand = True, fill = True, padding = 0) 198 | self.vbox.pack_start(self.scroll, expand = True, fill = True, padding = 0) 199 | self.vbox.pack_start(self.hboxlabel, expand = False, fill = True, padding = 0) 200 | self.hboxlabel.pack_start(self.selectlabel, expand = False, fill = True, padding = 0) 201 | self.vbox.pack_start(self.rhythmlocradio, expand = False, fill = True, padding = 0) 202 | self.vbox.pack_start(self.folderlocradio, expand = False, fill = True, padding = 0) 203 | self.vbox.pack_start(self.pictureslocradio, expand = False, fill = True, padding = 0) 204 | self.vbox.pack_start(self.albumartbutton, expand = False, fill = True, padding = 0) 205 | 206 | #---- pack everything into side panel ----# 207 | self.vbox.show_all() 208 | self.vbox.set_size_request(200, -1) 209 | #self.shell.add_widget (self.vbox, RB.ShellUILocation.RIGHT_SIDEBAR, True, True) 210 | 211 | -------------------------------------------------------------------------------- /pygi-convert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -n "$1" ]; then 4 | FILES_TO_CONVERT="$@" 5 | else 6 | FILES_TO_CONVERT="$(find . -name '*.py')" 7 | fi 8 | 9 | for f in $FILES_TO_CONVERT; do 10 | perl -i -0 \ 11 | -pe "s/import gconf\n/from gi.repository import GConf\n/g;" \ 12 | -pe "s/gconf\./GConf\./g;" \ 13 | -pe "s/GConf\.client_get_default/GConf.Client.get_default/g;" \ 14 | -pe "s/GConf\.CLIENT_/GConf.ClientPreloadType./g;" \ 15 | -pe "s/GConf\.VALUE_/GConf.ValueType./g;" \ 16 | -pe "s/gconf_client.notify_add\('\/desktop\/sugar\/collaboration\/publish_gadget',/return;gconf_client.notify_add\('\/desktop\/sugar\/collaboration\/publish_gadget',/g;" \ 17 | \ 18 | -pe "s/import pygtk/import gi/g;" \ 19 | -pe "s/pygtk.require\('2.0'\)/gi.require_version\('Gtk', '3.0'\)/g;" \ 20 | -pe "s/import gtk\n/from gi.repository import Gtk\n/g;" \ 21 | -pe "s/(? 0/self._content.get_children\(\)/g;" \ 95 | -pe "s/len\(self.menu.get_children\(\)\) > 0/self.menu.get_children\(\)/g;" \ 96 | -pe "s/([^\.^ ]*)\.drag_dest_set\(/Gtk.drag_dest_set\(\1, /g;" \ 97 | -pe "s/import gobject\n/from gi.repository import GObject\n/g;" \ 98 | -pe "s/Gtk\..*\.__init__/gobject.GObject.__init__/g;" \ 99 | \ 100 | -pe "s/from gtk import gdk\n/from gi.repository import Gdk\n/g;" \ 101 | -pe "s/import gtk.gdk as gdk\n/from gi.repository import Gdk\n/g;" \ 102 | -pe "s/Gtk.gdk.x11_/GdkX11\./g;" \ 103 | -pe "s/Gtk.gdk\./Gdk\./g;" \ 104 | -pe "s/(? 2 | 3 | 4 | 5 | -12 6 | 12 7 | 8 | 9 | -12 10 | 12 11 | 12 | 13 | -12 14 | 12 15 | 16 | 17 | -12 18 | 12 19 | 20 | 21 | -12 22 | 12 23 | 24 | 25 | -12 26 | 12 27 | 28 | 29 | -12 30 | 12 31 | 32 | 33 | -12 34 | 12 35 | 36 | 37 | -12 38 | 12 39 | 40 | 41 | -12 42 | 12 43 | 44 | 45 | False 46 | True 47 | Equalizer Preferences 48 | False 49 | True 50 | dialog 51 | center 52 | 53 | 54 | True 55 | False 56 | True 57 | vertical 58 | 59 | 60 | True 61 | False 62 | 13 63 | 10 64 | True 65 | 66 | 67 | True 68 | True 69 | adjustment1 70 | True 71 | 72 | 73 | 1 74 | 13 75 | 76 | 77 | 78 | 79 | True 80 | True 81 | adjustment2 82 | True 83 | 84 | 85 | 1 86 | 2 87 | 1 88 | 13 89 | 90 | 91 | 92 | 93 | True 94 | True 95 | adjustment3 96 | True 97 | 98 | 99 | 2 100 | 3 101 | 1 102 | 13 103 | 104 | 105 | 106 | 107 | True 108 | True 109 | adjustment4 110 | True 111 | 112 | 113 | 3 114 | 4 115 | 1 116 | 13 117 | 118 | 119 | 120 | 121 | True 122 | True 123 | adjustment5 124 | True 125 | 126 | 127 | 4 128 | 5 129 | 1 130 | 13 131 | 132 | 133 | 134 | 135 | True 136 | True 137 | adjustment6 138 | True 139 | 140 | 141 | 5 142 | 6 143 | 1 144 | 13 145 | 146 | 147 | 148 | 149 | True 150 | True 151 | adjustment7 152 | True 153 | 154 | 155 | 6 156 | 7 157 | 1 158 | 13 159 | 160 | 161 | 162 | 163 | True 164 | True 165 | adjustment8 166 | True 167 | 168 | 169 | 7 170 | 8 171 | 1 172 | 13 173 | 174 | 175 | 176 | 177 | True 178 | True 179 | adjustment9 180 | True 181 | 182 | 183 | 8 184 | 9 185 | 1 186 | 13 187 | 188 | 189 | 190 | 191 | True 192 | True 193 | adjustment10 194 | True 195 | 196 | 197 | 9 198 | 10 199 | 1 200 | 13 201 | 202 | 203 | 204 | 205 | True 206 | False 207 | 30Hz 208 | center 209 | 210 | 211 | 212 | 213 | True 214 | False 215 | 60Hz 216 | 217 | 218 | 1 219 | 2 220 | 221 | 222 | 223 | 224 | True 225 | False 226 | 6 227 | 125Hz 228 | 229 | 230 | 2 231 | 3 232 | 233 | 234 | 235 | 236 | True 237 | False 238 | 250Hz 239 | 240 | 241 | 3 242 | 4 243 | 244 | 245 | 246 | 247 | True 248 | False 249 | 500Hz 250 | 251 | 252 | 4 253 | 5 254 | 255 | 256 | 257 | 258 | True 259 | False 260 | 1KHz 261 | 262 | 263 | 5 264 | 6 265 | 266 | 267 | 268 | 269 | True 270 | False 271 | 2KHz 272 | 273 | 274 | 6 275 | 7 276 | 277 | 278 | 279 | 280 | True 281 | False 282 | 4KHz 283 | 284 | 285 | 7 286 | 8 287 | 288 | 289 | 290 | 291 | True 292 | False 293 | 8KHz 294 | 295 | 296 | 8 297 | 9 298 | 299 | 300 | 301 | 302 | True 303 | False 304 | 16KHz 305 | 306 | 307 | 9 308 | 10 309 | 310 | 311 | 312 | 313 | False 314 | True 315 | 0 316 | 317 | 318 | 319 | 320 | True 321 | False 322 | end 323 | 324 | 325 | True 326 | False 327 | 0 328 | 1 329 | 330 | 331 | False 332 | True 333 | 0 334 | True 335 | 336 | 337 | 338 | 339 | Reset 340 | True 341 | True 342 | True 343 | False 344 | 345 | 346 | False 347 | True 348 | 1 349 | 350 | 351 | 352 | 353 | Reset All 354 | True 355 | True 356 | True 357 | False 358 | 359 | 360 | False 361 | True 362 | 2 363 | 364 | 365 | 366 | 367 | gtk-close 368 | True 369 | True 370 | True 371 | False 372 | True 373 | 374 | 375 | False 376 | True 377 | 3 378 | 379 | 380 | 381 | 382 | False 383 | True 384 | end 385 | 1 386 | 387 | 388 | 389 | 390 | 391 | resetbutton1 392 | resetbutton2 393 | closebutton1 394 | 395 | 396 | 397 | -------------------------------------------------------------------------------- /equalizer/equalizer_rb3compat.py: -------------------------------------------------------------------------------- 1 | # -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*- 2 | # 3 | # IMPORTANT - WHILST THIS MODULE IS USED BY SEVERAL OTHER PLUGINS 4 | # THE MASTER AND MOST UP-TO-DATE IS FOUND IN THE COVERART BROWSER 5 | # PLUGIN - https://github.com/fossfreedom/coverart-browser 6 | # PLEASE SUBMIT CHANGES BACK TO HELP EXPAND THIS API 7 | # 8 | # Copyright (C) 2012 - fossfreedom 9 | # Copyright (C) 2012 - Agustin Carrasco 10 | # 11 | # This program is free software; you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation; either version 2, or (at your option) 14 | # any later version. 15 | # 16 | # This program is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program; if not, write to the Free Software 23 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 24 | 25 | from gi.repository import Gtk 26 | from gi.repository import Gio 27 | from gi.repository import GLib 28 | from gi.repository import GObject 29 | from gi.repository import RB 30 | import sys 31 | import rb 32 | import xml.etree.ElementTree as ET 33 | 34 | def pygobject_version(): 35 | ''' 36 | returns float of the major and minor parts of a pygobject version 37 | e.g. version (3, 9, 5) return float(3.9) 38 | ''' 39 | to_number = lambda t: ".".join(str(v) for v in t) 40 | 41 | str_version = to_number(GObject.pygobject_version) 42 | 43 | return float(str_version.rsplit('.',1)[0]) 44 | 45 | def compare_pygobject_version(version): 46 | ''' 47 | return True if version is less than pygobject_version 48 | i.e. 3.9 < 3.11 49 | ''' 50 | to_number = lambda t: ".".join(str(v) for v in t) 51 | 52 | str_version = to_number(GObject.pygobject_version) 53 | 54 | split = str_version.rsplit('.',2) 55 | split_compare = version.rsplit('.',2) 56 | 57 | if int(split_compare[0])= 3: 68 | import urllib.request, urllib.parse, urllib.error 69 | else: 70 | import urllib 71 | from urlparse import urlparse as rb2urlparse 72 | 73 | if PYVER >= 3: 74 | import http.client 75 | else: 76 | import httplib 77 | 78 | def responses(): 79 | if PYVER >=3: 80 | return http.client.responses 81 | else: 82 | return httplib.responses 83 | 84 | def unicodestr(param, charset): 85 | if PYVER >=3: 86 | return param#str(param, charset) 87 | else: 88 | return unicode(param, charset) 89 | 90 | def unicodeencode(param, charset): 91 | if PYVER >=3: 92 | return param#str(param).encode(charset) 93 | else: 94 | return unicode(param).encode(charset) 95 | 96 | def unicodedecode(param, charset): 97 | if PYVER >=3: 98 | return param 99 | else: 100 | return param.decode(charset) 101 | 102 | def urlparse(uri): 103 | if PYVER >=3: 104 | return urllib.parse.urlparse(uri) 105 | else: 106 | return rb2urlparse(uri) 107 | 108 | def url2pathname(url): 109 | if PYVER >=3: 110 | return urllib.request.url2pathname(url) 111 | else: 112 | return urllib.url2pathname(url) 113 | 114 | def urlopen(filename): 115 | if PYVER >=3: 116 | return urllib.request.urlopen(filename) 117 | else: 118 | return urllib.urlopen(filename) 119 | 120 | def pathname2url(filename): 121 | if PYVER >=3: 122 | return urllib.request.pathname2url(filename) 123 | else: 124 | return urllib.pathname2url(filename) 125 | 126 | def unquote(uri): 127 | if PYVER >=3: 128 | return urllib.parse.unquote(uri) 129 | else: 130 | return urllib.unquote(uri) 131 | 132 | def quote(uri, safe=None): 133 | if PYVER >=3: 134 | if safe: 135 | return urllib.parse.quote(uri,safe=safe) 136 | else: 137 | return urllib.parse.quote(uri) 138 | else: 139 | if safe: 140 | return urllib.quote(uri, safe=safe) 141 | else: 142 | return urllib.quote(uri) 143 | 144 | def quote_plus(uri): 145 | if PYVER >=3: 146 | return urllib.parse.quote_plus(uri) 147 | else: 148 | return urllib.quote_plus(uri) 149 | 150 | def is_rb3(*args): 151 | if hasattr(RB.Shell.props, 'ui_manager'): 152 | return False 153 | else: 154 | return True 155 | 156 | class Menu(GObject.Object): 157 | ''' 158 | Menu object used to create window popup menus 159 | ''' 160 | __gsignals__ = { 161 | 'pre-popup': (GObject.SIGNAL_RUN_LAST, None, ()) 162 | } 163 | 164 | def __init__(self, plugin, shell): 165 | ''' 166 | Initializes the menu. 167 | ''' 168 | super(Menu, self).__init__() 169 | self.plugin = plugin 170 | self.shell = shell 171 | self._unique_num = 0 172 | 173 | self._rbmenu_items = {} 174 | self._rbmenu_objects = {} 175 | 176 | def add_menu_item(self, menubar, section_name, action): 177 | ''' 178 | add a new menu item to the popup 179 | :param menubar: `str` is the name GtkMenu (or ignored for RB2.99+) 180 | :param section_name: `str` is the name of the section to add the item to (RB2.99+) 181 | :param action: `Action` to associate with the menu item 182 | ''' 183 | return self.insert_menu_item(menubar, section_name, -1, action) 184 | 185 | def insert_menu_item(self, menubar, section_name, position, action): 186 | ''' 187 | add a new menu item to the popup 188 | :param menubar: `str` is the name GtkMenu (or ignored for RB2.99+) 189 | :param section_name: `str` is the name of the section to add the item to (RB2.99+) 190 | :param position: `int` position to add to GtkMenu (ignored for RB2.99+) 191 | :param action: `Action` to associate with the menu item 192 | ''' 193 | label = action.label 194 | 195 | if is_rb3(self.shell): 196 | app = self.shell.props.application 197 | item = Gio.MenuItem() 198 | action.associate_menuitem(item) 199 | item.set_label(label) 200 | 201 | if not section_name in self._rbmenu_items: 202 | self._rbmenu_items[section_name] = [] 203 | self._rbmenu_items[section_name].append(label) 204 | 205 | app.add_plugin_menu_item(section_name, label, item) 206 | else: 207 | item = Gtk.MenuItem(label=label) 208 | action.associate_menuitem(item) 209 | self._rbmenu_items[label] = item 210 | bar = self.get_menu_object(menubar) 211 | 212 | if position == -1: 213 | bar.append(item) 214 | else: 215 | bar.insert(item, position) 216 | bar.show_all() 217 | uim = self.shell.props.ui_manager 218 | uim.ensure_update() 219 | 220 | return item 221 | 222 | def insert_separator(self, menubar, at_position): 223 | ''' 224 | add a separator to the popup (only required for RB2.98 and earlier) 225 | :param menubar: `str` is the name GtkMenu (or ignored for RB2.99+) 226 | :param position: `int` position to add to GtkMenu (ignored for RB2.99+) 227 | ''' 228 | if not is_rb3(self.shell): 229 | menu_item = Gtk.SeparatorMenuItem().new() 230 | menu_item.set_visible(True) 231 | self._rbmenu_items['separator' + str(self._unique_num)] = menu_item 232 | self._unique_num = self._unique_num + 1 233 | bar = self.get_menu_object(menubar) 234 | bar.insert(menu_item, at_position) 235 | bar.show_all() 236 | uim = self.shell.props.ui_manager 237 | uim.ensure_update() 238 | 239 | def remove_menu_items(self, menubar, section_name): 240 | ''' 241 | utility function to remove all menuitems associated with the menu section 242 | :param menubar: `str` is the name of the GtkMenu containing the menu items (ignored for RB2.99+) 243 | :param section_name: `str` is the name of the section containing the menu items (for RB2.99+ only) 244 | ''' 245 | if is_rb3(self.shell): 246 | if not section_name in self._rbmenu_items: 247 | return 248 | 249 | app = self.shell.props.application 250 | 251 | for menu_item in self._rbmenu_items[section_name]: 252 | app.remove_plugin_menu_item(section_name, menu_item) 253 | 254 | if self._rbmenu_items[section_name]: 255 | del self._rbmenu_items[section_name][:] 256 | 257 | else: 258 | 259 | if not self._rbmenu_items: 260 | return 261 | 262 | uim = self.shell.props.ui_manager 263 | bar = self.get_menu_object(menubar) 264 | 265 | for menu_item in self._rbmenu_items: 266 | bar.remove(self._rbmenu_items[menu_item]) 267 | 268 | bar.show_all() 269 | uim.ensure_update() 270 | 271 | def load_from_file(self, rb2_ui_filename, rb3_ui_filename ): 272 | ''' 273 | utility function to load the menu structure 274 | :param rb2_ui_filename: `str` RB2.98 and below UI file 275 | :param rb3_ui_filename: `str` RB2.99 and higher UI file 276 | ''' 277 | self.builder = Gtk.Builder() 278 | try: 279 | from coverart_browser_prefs import CoverLocale 280 | cl = CoverLocale() 281 | 282 | self.builder.set_translation_domain(cl.Locale.LOCALE_DOMAIN) 283 | except: 284 | pass 285 | 286 | if is_rb3(self.shell): 287 | ui_filename = rb3_ui_filename 288 | else: 289 | ui_filename = rb2_ui_filename 290 | 291 | self.ui_filename = ui_filename 292 | 293 | self.builder.add_from_file(rb.find_plugin_file(self.plugin, 294 | ui_filename)) 295 | 296 | def _connect_rb3_signals(self, signals): 297 | def _menu_connect(action_name, func): 298 | action = Gio.SimpleAction(name=action_name) 299 | action.connect('activate', func) 300 | action.set_enabled(True) 301 | self.shell.props.window.add_action(action) 302 | 303 | for key,value in signals.items(): 304 | _menu_connect( key, value) 305 | 306 | def _connect_rb2_signals(self, signals): 307 | def _menu_connect(menu_item_name, func): 308 | menu_item = self.get_menu_object(menu_item_name) 309 | menu_item.connect('activate', func) 310 | 311 | for key,value in signals.items(): 312 | _menu_connect( key, value) 313 | 314 | def connect_signals(self, signals): 315 | ''' 316 | connect all signal handlers with their menuitem counterparts 317 | :param signals: `dict` key is the name of the menuitem 318 | and value is the function callback when the menu is activated 319 | ''' 320 | if is_rb3(self.shell): 321 | self._connect_rb3_signals(signals) 322 | else: 323 | self._connect_rb2_signals(signals) 324 | 325 | def get_gtkmenu(self, source, popup_name): 326 | ''' 327 | utility function to obtain the GtkMenu from the menu UI file 328 | :param popup_name: `str` is the name menu-id in the UI file 329 | ''' 330 | if popup_name in self._rbmenu_objects: 331 | return self._rbmenu_objects[popup_name] 332 | item = self.builder.get_object(popup_name) 333 | 334 | if is_rb3(self.shell): 335 | app = self.shell.props.application 336 | app.link_shared_menus(item) 337 | popup_menu = Gtk.Menu.new_from_model(item) 338 | popup_menu.attach_to_widget(source, None) 339 | else: 340 | popup_menu = item 341 | 342 | self._rbmenu_objects[popup_name] = popup_menu 343 | 344 | return popup_menu 345 | 346 | def get_menu_object(self, menu_name_or_link): 347 | ''' 348 | utility function returns the GtkMenuItem/Gio.MenuItem 349 | :param menu_name_or_link: `str` to search for in the UI file 350 | ''' 351 | if menu_name_or_link in self._rbmenu_objects: 352 | return self._rbmenu_objects[menu_name_or_link] 353 | item = self.builder.get_object(menu_name_or_link) 354 | if is_rb3(self.shell): 355 | if item: 356 | popup_menu = item 357 | else: 358 | app = self.shell.props.application 359 | popup_menu = app.get_plugin_menu(menu_name_or_link) 360 | else: 361 | popup_menu = item 362 | print (menu_name_or_link) 363 | self._rbmenu_objects[menu_name_or_link] = popup_menu 364 | 365 | return popup_menu 366 | 367 | def set_sensitive(self, menu_or_action_item, enable): 368 | ''' 369 | utility function to enable/disable a menu-item 370 | :param menu_or_action_item: `GtkMenuItem` or `Gio.SimpleAction` 371 | that is to be enabled/disabled 372 | :param enable: `bool` value to enable/disable 373 | ''' 374 | 375 | if is_rb3(self.shell): 376 | item = self.shell.props.window.lookup_action(menu_or_action_item) 377 | item.set_enabled(enable) 378 | else: 379 | item = self.get_menu_object(menu_or_action_item) 380 | item.set_sensitive(enable) 381 | 382 | def popup(self, source, menu_name, button, time): 383 | ''' 384 | utility function to show the popup menu 385 | ''' 386 | self.emit('pre-popup') 387 | menu = self.get_gtkmenu(source, menu_name) 388 | menu.popup(None, None, None, None, button, time) 389 | 390 | class ActionGroup(object): 391 | ''' 392 | container for all Actions used to associate with menu items 393 | ''' 394 | 395 | # action_state 396 | STANDARD=0 397 | TOGGLE=1 398 | 399 | def __init__(self, shell, group_name): 400 | ''' 401 | constructor 402 | :param shell: `RBShell` 403 | :param group_name: `str` unique name for the object to create 404 | ''' 405 | self.group_name = group_name 406 | self.shell = shell 407 | 408 | self._actions = {} 409 | 410 | if is_rb3(self.shell): 411 | self.actiongroup = Gio.SimpleActionGroup() 412 | else: 413 | self.actiongroup = Gtk.ActionGroup(group_name) 414 | uim = self.shell.props.ui_manager 415 | uim.insert_action_group(self.actiongroup) 416 | 417 | @property 418 | def name(self): 419 | return self.group_name 420 | 421 | def remove_actions(self): 422 | ''' 423 | utility function to remove all actions associated with the ActionGroup 424 | ''' 425 | for action in self.actiongroup.list_actions(): 426 | self.actiongroup.remove_action(action) 427 | 428 | def get_action(self, action_name): 429 | ''' 430 | utility function to obtain the Action from the ActionGroup 431 | 432 | :param action_name: `str` is the Action unique name 433 | ''' 434 | return self._actions[action_name] 435 | 436 | def add_action_with_accel(self, func, action_name, accel, **args): 437 | ''' 438 | Creates an Action with an accelerator and adds it to the ActionGroup 439 | 440 | :param func: function callback used when user activates the action 441 | :param action_name: `str` unique name to associate with an action 442 | :param accel: `str` accelerator 443 | :param args: dict of arguments - this is passed to the function callback 444 | 445 | Notes: 446 | see notes for add_action 447 | ''' 448 | args['accel'] = accel 449 | return self.add_action(func, action_name, **args) 450 | 451 | def add_action(self, func, action_name, **args ): 452 | ''' 453 | Creates an Action and adds it to the ActionGroup 454 | 455 | :param func: function callback used when user activates the action 456 | :param action_name: `str` unique name to associate with an action 457 | :param args: dict of arguments - this is passed to the function callback 458 | 459 | Notes: 460 | key value of "label" is the visual menu label to display 461 | key value of "action_type" is the RB2.99 Gio.Action type ("win" or "app") 462 | by default it assumes all actions are "win" type 463 | key value of "action_state" determines what action state to create 464 | ''' 465 | if 'label' in args: 466 | label = args['label'] 467 | else: 468 | label=action_name 469 | 470 | if 'accel' in args: 471 | accel = args['accel'] 472 | else: 473 | accel = None 474 | 475 | state = ActionGroup.STANDARD 476 | if 'action_state' in args: 477 | state = args['action_state'] 478 | 479 | if is_rb3(self.shell): 480 | if state == ActionGroup.TOGGLE: 481 | action = Gio.SimpleAction.new_stateful(action_name, None, 482 | GLib.Variant('b', False)) 483 | else: 484 | action = Gio.SimpleAction.new(action_name, None) 485 | 486 | action_type = 'win' 487 | if 'action_type' in args: 488 | if args['action_type'] == 'app': 489 | action_type = 'app' 490 | 491 | app = Gio.Application.get_default() 492 | 493 | if action_type == 'app': 494 | app.add_action(action) 495 | else: 496 | self.shell.props.window.add_action(action) 497 | self.actiongroup.add_action(action) 498 | 499 | if accel: 500 | app.add_accelerator(accel, action_type+"."+action_name, None) 501 | else: 502 | if 'stock_id' in args: 503 | stock_id = args['stock_id'] 504 | else: 505 | stock_id = Gtk.STOCK_CLEAR 506 | 507 | if state == ActionGroup.TOGGLE: 508 | action = Gtk.ToggleAction(label=label, 509 | name=action_name, 510 | tooltip='', stock_id=stock_id) 511 | else: 512 | action = Gtk.Action(label=label, 513 | name=action_name, 514 | tooltip='', stock_id=stock_id) 515 | 516 | if accel: 517 | self.actiongroup.add_action_with_accel(action, accel) 518 | else: 519 | self.actiongroup.add_action(action) 520 | 521 | act = Action(self.shell, action) 522 | act.connect('activate', func, args) 523 | 524 | act.label = label 525 | act.accel = accel 526 | 527 | self._actions[action_name] = act 528 | 529 | return act 530 | 531 | class ApplicationShell(object): 532 | ''' 533 | Unique class that mirrors RB.Application & RB.Shell menu functionality 534 | ''' 535 | # storage for the instance reference 536 | __instance = None 537 | 538 | class __impl: 539 | """ Implementation of the singleton interface """ 540 | def __init__(self, shell): 541 | self.shell = shell 542 | 543 | if is_rb3(self.shell): 544 | self._uids = {} 545 | else: 546 | self._uids = [] 547 | 548 | self._action_groups = {} 549 | 550 | def insert_action_group(self, action_group): 551 | ''' 552 | Adds an ActionGroup to the ApplicationShell 553 | 554 | :param action_group: `ActionGroup` to add 555 | ''' 556 | self._action_groups[action_group.name] = action_group 557 | 558 | def lookup_action(self, action_group_name, action_name, action_type='app'): 559 | ''' 560 | looks up (finds) an action created by another plugin. If found returns 561 | an Action or None if no matching Action. 562 | 563 | :param action_group_name: `str` is the Gtk.ActionGroup name (ignored for RB2.99+) 564 | :param action_name: `str` unique name for the action to look for 565 | :param action_type: `str` RB2.99+ action type ("win" or "app") 566 | ''' 567 | 568 | if is_rb3(self.shell): 569 | if action_type == "app": 570 | action = self.shell.props.application.lookup_action(action_name) 571 | else: 572 | action = self.shell.props.window.lookup_action(action_name) 573 | else: 574 | uim = self.shell.props.ui_manager 575 | ui_actiongroups = uim.get_action_groups() 576 | 577 | actiongroup = None 578 | for actiongroup in ui_actiongroups: 579 | if actiongroup.get_name() == action_group_name: 580 | break 581 | 582 | action = None 583 | if actiongroup: 584 | action = actiongroup.get_action(action_name) 585 | 586 | if action: 587 | return Action(self.shell, action) 588 | else: 589 | return None 590 | 591 | def add_app_menuitems(self, ui_string, group_name, menu='tools'): 592 | ''' 593 | utility function to add application menu items. 594 | 595 | For RB2.99 all application menu items are added to the "tools" section of the 596 | application menu. All Actions are assumed to be of action_type "app". 597 | 598 | For RB2.98 or less, it is added however the UI_MANAGER string 599 | is defined. 600 | 601 | :param ui_string: `str` is the Gtk UI definition. There is not an 602 | equivalent UI definition in RB2.99 but we can parse out menu items since 603 | this string is in XML format 604 | 605 | :param group_name: `str` unique name of the ActionGroup to add menu items to 606 | :param menu: `str` RB2.99 menu section to add to - nominally either 607 | 'tools' or 'view' 608 | ''' 609 | if is_rb3(self.shell): 610 | root = ET.fromstring(ui_string) 611 | for elem in root.findall(".//menuitem"): 612 | action_name = elem.attrib['action'] 613 | item_name = elem.attrib['name'] 614 | 615 | group = self._action_groups[group_name] 616 | act = group.get_action(action_name) 617 | 618 | item = Gio.MenuItem() 619 | item.set_detailed_action('app.' + action_name) 620 | item.set_label(act.label) 621 | item.set_attribute_value("accel", GLib.Variant("s", act.accel)) 622 | app = Gio.Application.get_default() 623 | index = menu+action_name 624 | app.add_plugin_menu_item(menu, 625 | index, item) 626 | self._uids[index] = menu 627 | else: 628 | uim = self.shell.props.ui_manager 629 | self._uids.append(uim.add_ui_from_string(ui_string)) 630 | uim.ensure_update() 631 | 632 | def add_browser_menuitems(self, ui_string, group_name): 633 | ''' 634 | utility function to add popup menu items to existing browser popups 635 | 636 | For RB2.99 all menu items are are assumed to be of action_type "win". 637 | 638 | For RB2.98 or less, it is added however the UI_MANAGER string 639 | is defined. 640 | 641 | :param ui_string: `str` is the Gtk UI definition. There is not an 642 | equivalent UI definition in RB2.99 but we can parse out menu items since 643 | this string is in XML format 644 | 645 | :param group_name: `str` unique name of the ActionGroup to add menu items to 646 | ''' 647 | if is_rb3(self.shell): 648 | root = ET.fromstring(ui_string) 649 | for elem in root.findall("./popup"): 650 | popup_name = elem.attrib['name'] 651 | 652 | menuelem = elem.find('.//menuitem') 653 | action_name = menuelem.attrib['action'] 654 | item_name = menuelem.attrib['name'] 655 | 656 | group = self._action_groups[group_name] 657 | act = group.get_action(action_name) 658 | 659 | item = Gio.MenuItem() 660 | item.set_detailed_action('win.' + action_name) 661 | item.set_label(act.label) 662 | app = Gio.Application.get_default() 663 | 664 | if popup_name == 'QueuePlaylistViewPopup': 665 | plugin_type = 'queue-popup' 666 | elif popup_name == 'BrowserSourceViewPopup': 667 | plugin_type = 'browser-popup' 668 | elif popup_name == 'PlaylistViewPopup': 669 | plugin_type = 'playlist-popup' 670 | elif popup_name == 'PodcastViewPopup': 671 | plugin_type = 'podcast-episode-popup' 672 | else: 673 | print ("unknown type %s" % plugin_type) 674 | 675 | index = plugin_type+action_name 676 | app.add_plugin_menu_item(plugin_type, index, item) 677 | self._uids[index]=plugin_type 678 | else: 679 | uim = self.shell.props.ui_manager 680 | self._uids.append(uim.add_ui_from_string(ui_string)) 681 | uim.ensure_update() 682 | 683 | def cleanup(self): 684 | ''' 685 | utility remove any menuitems created. 686 | ''' 687 | if is_rb3(self.shell): 688 | for uid in self._uids: 689 | 690 | Gio.Application.get_default().remove_plugin_menu_item(self._uids[uid], 691 | uid) 692 | else: 693 | uim = self.shell.props.ui_manager 694 | for uid in self._uids: 695 | uim.remove_ui(uid) 696 | uim.ensure_update(); 697 | 698 | def __init__(self, shell): 699 | """ Create singleton instance """ 700 | # Check whether we already have an instance 701 | if ApplicationShell.__instance is None: 702 | # Create and remember instance 703 | ApplicationShell.__instance = ApplicationShell.__impl(shell) 704 | 705 | # Store instance reference as the only member in the handle 706 | self.__dict__['_ApplicationShell__instance'] = ApplicationShell.__instance 707 | 708 | def __getattr__(self, attr): 709 | """ Delegate access to implementation """ 710 | return getattr(self.__instance, attr) 711 | 712 | def __setattr__(self, attr, value): 713 | """ Delegate access to implementation """ 714 | return setattr(self.__instance, attr, value) 715 | 716 | class Action(object): 717 | ''' 718 | class that wraps around either a Gio.Action or a Gtk.Action 719 | ''' 720 | 721 | def __init__(self, shell, action): 722 | ''' 723 | constructor. 724 | 725 | :param shell: `RBShell` 726 | :param action: `Gio.Action` or `Gtk.Action` 727 | ''' 728 | self.shell = shell 729 | self.action = action 730 | 731 | self._label = '' 732 | self._accel = '' 733 | self._current_state = False 734 | self._do_update_state = True 735 | 736 | def connect(self, address, func, args): 737 | self._connect_func = func 738 | self._connect_args = args 739 | 740 | if address == 'activate': 741 | func = self._activate 742 | 743 | if is_rb3(self.shell): 744 | self.action.connect(address, func, args) 745 | else: 746 | self.action.connect(address, func, None, args) 747 | 748 | def _activate(self, action, *args): 749 | if self._do_update_state: 750 | self._current_state = not self._current_state 751 | self.set_state(self._current_state) 752 | 753 | self._connect_func(action, None, self._connect_args) 754 | 755 | @property 756 | def label(self): 757 | ''' 758 | get the menu label associated with the Action 759 | 760 | for RB2.99+ actions dont have menu labels so this is managed 761 | manually 762 | ''' 763 | if not is_rb3(self.shell): 764 | return self.action.get_label() 765 | else: 766 | return self._label 767 | 768 | @label.setter 769 | def label(self, new_label): 770 | if not is_rb3(self.shell): 771 | self.action.set_label(new_label) 772 | 773 | self._label = new_label 774 | 775 | @property 776 | def accel(self): 777 | ''' 778 | get the accelerator associated with the Action 779 | ''' 780 | return self._accel 781 | 782 | @accel.setter 783 | def accel(self, new_accelerator): 784 | if new_accelerator: 785 | self._accel = new_accelerator 786 | else: 787 | self._accel = '' 788 | 789 | def get_sensitive(self): 790 | ''' 791 | get the sensitivity (enabled/disabled) state of the Action 792 | 793 | returns boolean 794 | ''' 795 | if is_rb3(self.shell): 796 | return self.action.get_enabled() 797 | else: 798 | return self.action.get_sensitive() 799 | 800 | def set_state(self, value): 801 | ''' 802 | set the state of a stateful action - this is applicable only 803 | to RB2.99+ 804 | ''' 805 | if is_rb3(self.shell) and self.action.props.state_type: 806 | self.action.change_state(GLib.Variant('b', value)) 807 | 808 | def activate(self): 809 | ''' 810 | invokes the activate signal for the action 811 | ''' 812 | if is_rb3(self.shell): 813 | self.action.activate(None) 814 | else: 815 | self.action.activate() 816 | 817 | def set_active(self, value): 818 | ''' 819 | activate or deactivate a stateful action signal 820 | For consistency with earlier RB versions, this will fire the 821 | activate signal for the action 822 | 823 | :param value: `boolean` state value 824 | ''' 825 | 826 | if is_rb3(self.shell): 827 | self.action.change_state(GLib.Variant('b', value)) 828 | self._current_state = value 829 | self._do_update_state = False 830 | self.activate() 831 | self._do_update_state = True 832 | else: 833 | self.action.set_active(value) 834 | 835 | def get_active(self): 836 | ''' 837 | get the state of the action 838 | 839 | returns `boolean` state value 840 | ''' 841 | if is_rb3(self.shell): 842 | returnval = self._current_state 843 | else: 844 | returnval = self.action.get_active() 845 | 846 | return returnval 847 | 848 | def associate_menuitem(self, menuitem): 849 | ''' 850 | links a menu with the action 851 | 852 | ''' 853 | if is_rb3(self.shell): 854 | menuitem.set_detailed_action('win.'+self.action.get_name()) 855 | else: 856 | menuitem.set_related_action(self.action) 857 | 858 | 859 | --------------------------------------------------------------------------------