Use pkg-config to find ncursesw
[centerim5.git] / src / PluginWindow.cpp
blob2a1a2c93e53a34ae02091d082dcc03a3da20545f
1 // Copyright (C) 2012-2015 Petr Pavlu <setup@dagobah.cz>
2 //
3 // This file is part of CenterIM.
4 //
5 // CenterIM 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 of the License, or
8 // (at your option) any later version.
9 //
10 // CenterIM 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.
15 // You should have received a copy of the GNU General Public License
16 // along with CenterIM. If not, see <http://www.gnu.org/licenses/>.
18 #include "PluginWindow.h"
20 #include "Log.h"
21 #include "Utils.h"
23 #include "gettext.h"
24 #include <cppconsui/Label.h>
26 // TODO
27 // - Improve handling of PURPLE_PREF_PATH.
28 // - Use these functions and use their results correctly:
29 // purple_plugin_pref_get_max_length(),
30 // purple_plugin_pref_get_masked(),
31 // purple_plugin_pref_get_format_type(),
32 // purple_plugin_pref_get_bounds().
34 PluginWindow::PluginWindow() : SplitDialog(0, 0, 80, 24, _("Plugins"))
36 setColorScheme(CenterIM::SCHEME_GENERALWINDOW);
38 treeview_ = new CppConsUI::TreeView(AUTOSIZE, AUTOSIZE);
39 setContainer(*treeview_);
41 // Show settings for all loaded plugins.
42 for (GList *iter = purple_plugins_get_loaded(); iter != nullptr;
43 iter = iter->next) {
44 PurplePlugin *plugin = reinterpret_cast<PurplePlugin *>(iter->data);
45 if (plugin->info->type == PURPLE_PLUGIN_STANDARD &&
46 !(plugin->info->flags & PURPLE_PLUGIN_FLAG_INVISIBLE))
47 populatePlugin(plugin);
50 buttons_->appendItem(_("Add"), sigc::mem_fun(this, &PluginWindow::addPlugin));
51 buttons_->appendSeparator();
52 buttons_->appendItem(
53 _("Done"), sigc::hide(sigc::mem_fun(this, &PluginWindow::close)));
55 onScreenResized();
58 PluginWindow::~PluginWindow()
60 // Destroy all allocated pref frames.
61 for (PluginEntries::value_type &entry : plugin_entries_)
62 if (entry.second.frame != nullptr)
63 purple_plugin_pref_frame_destroy(entry.second.frame);
66 void PluginWindow::onScreenResized()
68 moveResizeRect(CENTERIM->getScreenArea(CenterIM::CHAT_AREA));
71 PluginWindow::AddPluginWindow::AddPluginWindow()
72 : Window(0, 0, 80, 24, _("Add plugin"), TYPE_TOP)
74 auto treeview = new CppConsUI::TreeView(AUTOSIZE, AUTOSIZE);
75 addWidget(*treeview, 1, 1);
77 // Populate available plugins except the ones that are already enabled.
78 for (GList *iter = purple_plugins_get_all(); iter != nullptr;
79 iter = iter->next) {
80 PurplePlugin *plugin = reinterpret_cast<PurplePlugin *>(iter->data);
81 if (purple_plugin_is_loaded(plugin) ||
82 plugin->info->type != PURPLE_PLUGIN_STANDARD ||
83 (plugin->info->flags & PURPLE_PLUGIN_FLAG_INVISIBLE))
84 continue;
86 char *text = g_strdup_printf("%s, %s\n%s", purple_plugin_get_name(plugin),
87 purple_plugin_get_version(plugin), purple_plugin_get_summary(plugin));
88 auto button = new CppConsUI::Button(text);
89 g_free(text);
91 button->signal_activate.connect(sigc::bind(
92 sigc::mem_fun(this, &AddPluginWindow::onPluginButtonActivate), plugin));
93 treeview->appendNode(treeview->getRootNode(), *button);
96 onScreenResized();
99 void PluginWindow::AddPluginWindow::onScreenResized()
101 moveResizeRect(CENTERIM->getScreenAreaCentered(CenterIM::CHAT_AREA));
104 void PluginWindow::AddPluginWindow::onPluginButtonActivate(
105 CppConsUI::Button & /*activator*/, PurplePlugin *plugin)
107 signal_selection(*this, plugin);
108 close();
111 PluginWindow::BoolOption::BoolOption(const char *name, const char *pref)
112 : CheckBox(name)
114 g_assert(name != nullptr);
115 g_assert(pref != nullptr);
117 pref_ = g_strdup(pref);
118 setChecked(purple_prefs_get_bool(pref_));
119 signal_toggle.connect(sigc::mem_fun(this, &BoolOption::onToggle));
122 PluginWindow::BoolOption::~BoolOption()
124 g_free(pref_);
127 void PluginWindow::BoolOption::onToggle(
128 CheckBox & /*activator*/, bool new_state)
130 purple_prefs_set_bool(pref_, new_state);
133 PluginWindow::StringOption::StringOption(const char *name, const char *pref)
134 : Button(FLAG_VALUE, name)
136 g_assert(name != nullptr);
137 g_assert(pref != nullptr);
139 pref_ = g_strdup(pref);
140 updateValue();
141 signal_activate.connect(sigc::mem_fun(this, &StringOption::onActivate));
144 PluginWindow::StringOption::~StringOption()
146 g_free(pref_);
149 void PluginWindow::StringOption::updateValue()
151 setValue(purple_prefs_get_string(pref_));
154 void PluginWindow::StringOption::onActivate(Button & /*activator*/)
156 auto dialog = new CppConsUI::InputDialog(getText(), getValue());
157 dialog->signal_response.connect(
158 sigc::mem_fun(this, &StringOption::responseHandler));
159 dialog->show();
162 void PluginWindow::StringOption::responseHandler(
163 CppConsUI::InputDialog &activator, AbstractDialog::ResponseType response)
165 if (response != AbstractDialog::RESPONSE_OK)
166 return;
168 purple_prefs_set_string(pref_, activator.getText());
169 updateValue();
172 PluginWindow::IntegerOption::IntegerOption(const char *name, const char *pref)
173 : Button(FLAG_VALUE, name)
175 g_assert(name != nullptr);
176 g_assert(pref != nullptr);
178 pref_ = g_strdup(pref);
179 updateValue();
180 signal_activate.connect(sigc::mem_fun(this, &IntegerOption::onActivate));
183 PluginWindow::IntegerOption::~IntegerOption()
185 g_free(pref_);
188 void PluginWindow::IntegerOption::updateValue()
190 setValue(purple_prefs_get_int(pref_));
193 void PluginWindow::IntegerOption::onActivate(Button & /*activator*/)
195 auto dialog = new CppConsUI::InputDialog(getText(), getValue());
196 dialog->setFlags(CppConsUI::TextEntry::FLAG_NUMERIC);
197 dialog->signal_response.connect(
198 sigc::mem_fun(this, &IntegerOption::responseHandler));
199 dialog->show();
202 void PluginWindow::IntegerOption::responseHandler(
203 CppConsUI::InputDialog &activator,
204 CppConsUI::AbstractDialog::ResponseType response)
206 if (response != AbstractDialog::RESPONSE_OK)
207 return;
209 long num;
210 if (!Utils::stringToNumber(activator.getText(), INT_MIN, INT_MAX, &num))
211 return;
212 purple_prefs_set_int(pref_, num);
214 updateValue();
217 PluginWindow::PathOption::PathOption(const char *name, const char *pref)
218 : Button(FLAG_VALUE, name)
220 g_assert(name != nullptr);
221 g_assert(pref != nullptr);
223 pref_ = g_strdup(pref);
224 updateValue();
225 signal_activate.connect(sigc::mem_fun(this, &PathOption::onActivate));
228 PluginWindow::PathOption::~PathOption()
230 g_free(pref_);
233 void PluginWindow::PathOption::updateValue()
235 setValue(purple_prefs_get_path(pref_));
238 void PluginWindow::PathOption::onActivate(Button & /*activator*/)
240 auto dialog = new CppConsUI::InputDialog(getText(), getValue());
241 dialog->signal_response.connect(
242 sigc::mem_fun(this, &PathOption::responseHandler));
243 dialog->show();
246 void PluginWindow::PathOption::responseHandler(
247 CppConsUI::InputDialog &activator, AbstractDialog::ResponseType response)
249 if (response != AbstractDialog::RESPONSE_OK)
250 return;
252 purple_prefs_set_path(pref_, activator.getText());
253 updateValue();
256 void PluginWindow::clearPlugin(PurplePlugin *plugin)
258 PluginEntry *plugin_entry = &plugin_entries_[plugin];
259 treeview_->deleteNode(plugin_entry->parent_reference, false);
260 if (plugin_entry->frame != nullptr)
261 purple_plugin_pref_frame_destroy(plugin_entry->frame);
262 plugin_entries_.erase(plugin);
265 void PluginWindow::populatePlugin(PurplePlugin *plugin)
267 // Show settings for a single plugin.
268 CppConsUI::Button *button = new CppConsUI::TreeView::ToggleCollapseButton(
269 purple_plugin_get_name(plugin));
270 CppConsUI::TreeView::NodeReference parent_reference =
271 treeview_->appendNode(treeview_->getRootNode(), *button);
272 treeview_->setCollapsed(parent_reference, true);
274 // Record this plugin in plugin_entries_.
275 PluginEntry entry;
276 entry.parent = button;
277 entry.parent_reference = parent_reference;
278 entry.frame = nullptr;
279 plugin_entries_[plugin] = entry;
281 if (PURPLE_PLUGIN_HAS_PREF_FRAME(plugin)) {
282 PurplePluginUiInfo *ui_info = PURPLE_PLUGIN_UI_INFO(plugin);
283 PurplePluginPrefFrame *frame = ui_info->get_plugin_pref_frame(plugin);
285 // Note this pref frame.
286 plugin_entries_[plugin].frame = frame;
288 // Group node (set to an "invalid" value).
289 CppConsUI::TreeView::NodeReference group = treeview_->getRootNode();
291 for (GList *iter = purple_plugin_pref_frame_get_prefs(frame);
292 iter != nullptr; iter = iter->next) {
293 PurplePluginPref *pref = reinterpret_cast<PurplePluginPref *>(iter->data);
294 PurplePluginPrefType type = purple_plugin_pref_get_type(pref);
295 const char *label = purple_plugin_pref_get_label(pref);
296 const char *name = purple_plugin_pref_get_name(pref);
297 if (name == nullptr) {
298 if (label == nullptr)
299 continue;
301 if (type == PURPLE_PLUGIN_PREF_INFO) {
302 // No value label.
303 treeview_->appendNode(
304 parent_reference, *(new CppConsUI::Label(label)));
306 else {
307 // New group.
308 group = treeview_->appendNode(parent_reference,
309 *(new CppConsUI::TreeView::ToggleCollapseButton(label)));
311 continue;
314 PurplePrefType value_type = purple_prefs_get_type(name);
315 CppConsUI::Widget *pref_widget;
316 if (type == PURPLE_PLUGIN_PREF_CHOICE) {
317 auto combo = new CppConsUI::ComboBox(label);
318 pref_widget = combo;
320 // Add possible options.
321 for (GList *pref_iter = purple_plugin_pref_get_choices(pref);
322 pref_iter != nullptr && pref_iter->next != nullptr;
323 pref_iter = pref_iter->next->next)
324 combo->addOptionPtr(reinterpret_cast<const char *>(pref_iter->data),
325 pref_iter->next->data);
327 // Set default value.
328 intptr_t current_value;
329 if (value_type == PURPLE_PREF_BOOLEAN)
330 current_value = purple_prefs_get_bool(name);
331 else if (value_type == PURPLE_PREF_INT)
332 current_value = purple_prefs_get_int(name);
333 else if (value_type == PURPLE_PREF_STRING)
334 current_value =
335 reinterpret_cast<intptr_t>(purple_prefs_get_string(name));
336 else if (value_type == PURPLE_PREF_PATH)
337 current_value =
338 reinterpret_cast<intptr_t>(purple_prefs_get_path(name));
339 else {
340 LOG->error(_("Unhandled plugin preference type '%d'."), value_type);
341 continue;
343 combo->setSelectedByData(current_value);
345 else {
346 if (value_type == PURPLE_PREF_BOOLEAN)
347 pref_widget = new BoolOption(label, name);
348 else if (value_type == PURPLE_PREF_INT)
349 pref_widget = new IntegerOption(label, name);
350 else if (value_type == PURPLE_PREF_STRING)
351 pref_widget = new StringOption(label, name);
352 else if (value_type == PURPLE_PREF_PATH)
353 pref_widget = new PathOption(label, name);
354 else {
355 LOG->error(_("Unhandled plugin preference type '%d'."), value_type);
356 continue;
360 if (group == treeview_->getRootNode()) {
361 // No group has been created, so create one.
362 group = treeview_->appendNode(parent_reference,
363 *(new CppConsUI::TreeView::ToggleCollapseButton(_("Preferences"))));
365 treeview_->appendNode(group, *pref_widget);
369 // Disable plugin.
370 auto disable_button = new CppConsUI::Button(_("Disable plugin"));
371 disable_button->signal_activate.connect(
372 sigc::bind(sigc::mem_fun(this, &PluginWindow::disablePlugin), plugin));
373 treeview_->appendNode(parent_reference, *disable_button);
376 void PluginWindow::addPlugin(CppConsUI::Button & /*activator*/)
378 // Show a window for the user to select a plugin that he wants to add.
379 auto win = new AddPluginWindow;
380 win->signal_selection.connect(
381 sigc::mem_fun(this, &PluginWindow::onAddPluginSelection));
382 win->show();
385 void PluginWindow::onAddPluginSelection(
386 AddPluginWindow & /*activator*/, PurplePlugin *plugin)
388 // The user has selected a plugin that he wants to add, so do it.
389 if (!purple_plugin_load(plugin)) {
390 LOG->error(_("Error loading plugin '%s'."), purple_plugin_get_name(plugin));
391 return;
394 purple_plugins_save_loaded(CONF_PLUGINS_SAVE_PREF);
396 // Populate this plugin.
397 populatePlugin(plugin);
399 // Make the button for this plugin focused.
400 PluginEntries::iterator i = plugin_entries_.find(plugin);
401 g_assert(i != plugin_entries_.end());
402 g_assert(i->second.parent != nullptr);
403 i->second.parent->grabFocus();
406 void PluginWindow::disablePlugin(
407 CppConsUI::Button & /*activator*/, PurplePlugin *plugin)
409 auto dialog = new CppConsUI::MessageDialog(
410 _("Disable plugin"), _("Are you sure you want to disable this plugin?"));
411 dialog->signal_response.connect(sigc::bind(
412 sigc::mem_fun(this, &PluginWindow::disablePluginResponseHandler), plugin));
413 dialog->show();
416 void PluginWindow::disablePluginResponseHandler(
417 CppConsUI::MessageDialog & /*activator*/,
418 CppConsUI::AbstractDialog::ResponseType response, PurplePlugin *plugin)
420 if (response != AbstractDialog::RESPONSE_OK)
421 return;
423 if (!purple_plugin_unload(plugin)) {
424 LOG->error(_("Error unloading plugin '%s'. The plugin could not be "
425 "unloaded now, but will be disabled at the next startup."),
426 purple_plugin_get_name(plugin));
428 purple_plugin_disable(plugin);
430 else
431 clearPlugin(plugin);
432 purple_plugins_save_loaded(CONF_PLUGINS_SAVE_PREF);
435 // vim: set tabstop=2 shiftwidth=2 textwidth=80 expandtab: