1
# PiTiVi , Non-linear video editor
3
# pitivi/ui/pluginmanagerdialog.py
5
# Copyright (c) 2007, Luca Della Santina <dellasantina@farm.unipi.it>
7
# This program is free software; you can redistribute it and/or
8
# modify it under the terms of the GNU Lesser General Public
9
# License as published by the Free Software Foundation; either
10
# version 2.1 of the License, or (at your option) any later version.
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 GNU
15
# Lesser General Public License for more details.
17
# You should have received a copy of the GNU Lesser General Public
18
# License along with this program; if not, write to the
19
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20
# Boston, MA 02111-1307, USA.
27
from gettext import gettext as _
29
from pitivi.configure import LIBDIR
30
import pitivi.plugincore as plugincore
31
import pitivi.pluginmanager as pluginmanager
33
(COL_ENABLED, COL_INFO, COL_CATEGORY, COL_PLUGIN) = range(4)
34
(RESPONSE_ABOUT, RESPONSE_CONFIGURE, RESPONSE_DELETE) = range(3)
36
class PluginManagerDialog(object):
37
""" This dialog is the main way user can interact with the plugin manager.
38
It allows to install,remove,update,configure and enable plugins. """
40
def __init__(self, plugin_manager):
41
self.pm = plugin_manager
43
# load user interface items
44
if 'pitivi.exe' in __file__.lower():
47
glade_dir = os.path.dirname(os.path.abspath(__file__))
48
self.wTree = gtk.glade.XML(os.path.join(glade_dir, 'pluginmanagerdialog.glade'))
49
self.window = self.wTree.get_widget('pluginmanager_dlg')
50
self.search_entry = self.wTree.get_widget('search_entry')
51
self.category_cmb = self.wTree.get_widget('category_cmb')
52
self.about_btn = self.wTree.get_widget('about_btn')
53
self.configure_btn = self.wTree.get_widget('configure_btn')
54
self.delete_btn = self.wTree.get_widget('delete_btn')
55
self.plugin_tree = self.wTree.get_widget('plugin_tree')
56
self.search_entry = self.wTree.get_widget('search_entry')
59
self.wTree.signal_autoconnect(self)
61
# intialize plugin list
62
self._initialize_plugin_tree(self.plugin_tree)
63
self._initialize_category_cmb(self.category_cmb)
64
self.refresh_category()
68
self.search_entry.grab_focus()
71
def _initialize_plugin_tree(self, tree):
72
""" Perform treeview initialization """
74
self.model = gtk.ListStore(gobject.TYPE_BOOLEAN,
80
tree.set_model(self.model)
81
tree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
84
tree.enable_model_drag_dest([("text/uri-list", 0, 1)], \
85
gtk.gdk.ACTION_DEFAULT)
86
tree.connect("drag-data-received", self.drag_data_received_cb)
88
# plugin enabled status
89
cell = gtk.CellRendererToggle()
90
cell.set_property('activatable', True)
91
cell.connect('toggled', self.plugin_enabled_cb, self.model)
93
column = gtk.TreeViewColumn(_("Enabled"))
94
tree.append_column(column)
95
column.pack_start(cell, True)
96
column.add_attribute(cell, 'active', COL_ENABLED)
97
column.set_sort_column_id(COL_ENABLED)
100
cell = gtk.CellRendererText()
101
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
103
column = gtk.TreeViewColumn(_("Plugin"))
104
tree.append_column(column)
105
column.pack_start(cell, True)
106
column.set_min_width(300)
107
column.add_attribute(cell, "markup", COL_INFO)
108
column.set_sort_column_id(COL_INFO)
111
cell = gtk.CellRendererText()
113
column = gtk.TreeViewColumn(_("Category"))
114
tree.append_column(column)
115
column.pack_start(cell, False)
116
column.add_attribute(cell, 'text', COL_CATEGORY)
117
column.set_sort_column_id(COL_CATEGORY)
119
def _initialize_category_cmb(self, combo):
120
""" perform category combobox initialization """
122
self.model_category = gtk.ListStore(gobject.TYPE_STRING)
123
combo.set_model(self.model_category)
125
def search_entry_changed_cb(self, widget):
126
""" filter the plugin list according to searched text """
128
self.refresh_tree(self.search_entry.get_text())
130
if self.search_entry.get_text():
131
self.search_entry.modify_base(gtk.STATE_NORMAL,\
132
gtk.gdk.color_parse("#FBFAD6"))
134
self.search_entry.modify_base(gtk.STATE_NORMAL, None)
136
def category_cmb_changed_cb(self, widget):
137
""" Catch changes in category combobox triggered by the user """
139
self.refresh_tree(self.search_entry.get_text())
141
def refresh_tree(self, filter_text = None):
143
Refresh the list of plugins according to filter_text
145
@param filter_text: plugin name must have this substring (case insensitive)
148
def _get_active_category():
149
""" return the active category the chosen from the combobox """
150
if self.category_cmb.get_active() > 0:
151
return self.model_category[self.category_cmb.get_active()][0]
157
for plugin in self.pm.getPlugins(category=_get_active_category()):
158
if filter_text and (plugin.name.lower().find(filter_text.lower()) < 0):
161
rowiter = self.model.append()
162
self.model.set_value(rowiter, COL_ENABLED, plugin.enabled)
163
self.model.set_value(rowiter, COL_INFO, "<b>%s</b>\n%s"\
164
%(plugin.name, plugin.description))
165
self.model.set_value(rowiter, COL_CATEGORY, plugin.category)
166
self.model.set_value(rowiter, COL_PLUGIN, plugin)
168
# refresh available operations according to the new visualized list
169
self.plugin_tree_button_release_cb(self.plugin_tree, None)
171
def refresh_category(self):
172
""" Refresh the list of plugin categories """
173
self._initialize_category_cmb(self.category_cmb)
175
# The first entry is always "All categories"
176
rowiter = self.model_category.append()
177
self.model_category.set_value(rowiter, 0, _("All categories"))
180
# populate categories
181
for plugin in self.pm.getPlugins():
182
if not plugin.category in categories:
183
categories.append(plugin.category)
185
#populate combo model with categories
186
for category in categories:
187
rowiter = self.model_category.append()
188
self.model_category.set_value(rowiter, 0, category)
190
self.category_cmb.set_active(0)
192
def response_cb(self, widget, response):
193
""" Catch signal emitted by user-pressed buttons in the main bar """
194
if response == gtk.RESPONSE_DELETE_EVENT:
195
self.window.destroy()
196
elif response == gtk.RESPONSE_CLOSE:
197
self.window.destroy()
198
elif response == RESPONSE_ABOUT:
199
self.show_plugin_info()
200
elif response == RESPONSE_CONFIGURE:
201
for plugin in self._get_selected_plugins():
203
elif response == RESPONSE_DELETE:
204
self.uninstall_selected_plugins()
206
def plugin_enabled_cb(self, cell, path, model):
207
""" Toggle loaded status for selected plugin"""
209
model[path][COL_ENABLED] = model[path][COL_PLUGIN].enabled = not model[path][COL_PLUGIN].enabled
211
def plugin_tree_button_release_cb(self, widget, event):
212
""" Select plugins from the list """
214
selection = widget.get_selection()
218
if selection.count_selected_rows() == 1:
219
self.about_btn.set_sensitive(True)
221
(model, pathlist) = selection.get_selected_rows()
222
row = model.get_iter(pathlist[0])
223
plugin = model[row][COL_PLUGIN]
224
self.configure_btn.set_sensitive(plugincore.IConfigurable.providedBy(plugin))
225
self.delete_btn.set_sensitive(self.pm.canUninstall(plugin))
226
elif selection.count_selected_rows() > 1:
227
self.about_btn.set_sensitive(False)
228
self.configure_btn.set_sensitive(False)
229
self.delete_btn.set_sensitive(True)
231
self.about_btn.set_sensitive(False)
232
self.configure_btn.set_sensitive(False)
233
self.delete_btn.set_sensitive(False)
235
def _get_selected_plugins(self):
237
Retrieve from treeview widget those plugins selected by the use
239
@return: the list of plugins selected by the user
242
selection = self.plugin_tree.get_selection()
246
(model, pathlist) = selection.get_selected_rows()
248
for path in pathlist:
249
row = model.get_iter(path)
250
sel_plugins.append(model[row][COL_PLUGIN])
254
def show_plugin_info(self):
255
""" Show the about dialog for selected plugins """
257
for plugin in self._get_selected_plugins():
258
dialog = gtk.AboutDialog()
259
dialog.connect("response", lambda x, y: dialog.destroy())
260
dialog.set_name(plugin.name)
261
dialog.set_version(plugin.version)
262
dialog.set_authors(plugin.authors.split(","))
263
dialog.set_comments(plugin.description)
266
def uninstall_selected_plugins(self):
267
""" Uninstall plugins selected by the user """
269
# ensure the user really wants this operation to be performed
270
dialog = gtk.MessageDialog(
271
parent = self.window,
272
flags = gtk.DIALOG_MODAL,
273
type = gtk.MESSAGE_WARNING,
274
buttons = gtk.BUTTONS_OK_CANCEL,
275
message_format = _("Are you sure you want to remove the selected plugins?"))
276
dialog.set_title(_("Confirm remove operation"))
278
if dialog.run() == gtk.RESPONSE_CANCEL:
283
# remove selected plugins
284
for plugin in self._get_selected_plugins():
286
self.pm.uninstall(plugin)
288
error_dialog = gtk.MessageDialog(
289
parent = self.window,
290
flags = gtk.DIALOG_MODAL,
291
type = gtk.MESSAGE_ERROR,
292
buttons = gtk.BUTTONS_CLOSE,
293
message_format = _("Cannot remove %s") % (plugin.name))
295
error_dialog.destroy()
297
# refresh the plugin list
300
self.refresh_category()
302
def drag_data_received_cb(self, widget, context, x, y, selection,
304
""" handle drag&drop of new plugins into the list by installing them"""
306
uri_list = selection.data.strip().split()
310
# ensure a file is dragged
311
if not (uri.startswith("file://") and os.path.isfile(uri[7:])):
316
self.pm.install(filename, self.pm.local_plugin_path)
318
except plugincore.DuplicatePluginError, e:
319
# Plugin already exists, ask the user if he wants to update
320
dialog = gtk.MessageDialog(
321
parent = self.window,
322
flags = gtk.DIALOG_MODAL,
323
type = gtk.MESSAGE_WARNING,
324
buttons = gtk.BUTTONS_OK_CANCEL,
325
message_format = _("Update the existing plugin?"))
327
dialog.format_secondary_text(
328
_("This plugin is already installed in your system.\nIf you agree, version %(v1)s will be replaced with version %(v2)s")\
329
%{'v1': e.old_plugin.version, 'v2': e.new_plugin.version})
331
dialog.set_title(_("Duplicate plugin found"))
333
if dialog.run() == gtk.RESPONSE_OK:
334
self.pm.update(filename, self.pm.local_plugin_path)
338
except plugincore.InvalidPluginError, e:
339
# The file user is trying to install is not a valid plugin
340
error_dialog = gtk.MessageDialog(
341
parent = self.window,
342
flags = gtk.DIALOG_MODAL,
343
type = gtk.MESSAGE_ERROR,
344
buttons = gtk.BUTTONS_CLOSE,
345
message_format = _("Cannot install %s\nThe file is not a valid plugin") % e.filename)
347
error_dialog.destroy()
350
# refresh plugin list if the operation succedded
353
self.refresh_category()
354
# Tell the drag source that operation succedded
355
context.finish(success=True, del_=False, time=time)
357
if __name__ == "__main__":
358
pm = pluginmanager.PluginManager("./plugins", "./plugins-settings")
359
PluginManagerDialog(pm)