├── .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 |
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 ('?a[^>]*> ',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 |
177 |
178 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
196 |
197 |
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 |
8 |
12 |
16 |
20 |
24 |
28 |
32 |
36 |
40 |
44 |
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 |
--------------------------------------------------------------------------------