Warning: That file was not part of the compilation database. It may have many parsing errors.
1 | // This file is part of Pate, Kate' Python scripting plugin. |
---|---|
2 | // |
3 | // Copyright (C) 2006 Paul Giannaros <paul@giannaros.org> |
4 | // Copyright (C) 2012, 2013 Shaheed Haque <srhaque@theiet.org> |
5 | // Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com> |
6 | // |
7 | // This library 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) version 3, or any |
11 | // later version accepted by the membership of KDE e.V. (or its |
12 | // successor approved by the membership of KDE e.V.), which shall |
13 | // act as a proxy defined in Section 6 of version 3 of the license. |
14 | // |
15 | // This library is distributed in the hope that it will be useful, |
16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 | // Lesser General Public License for more details. |
19 | // |
20 | // You should have received a copy of the GNU Lesser General Public |
21 | // License along with this library. If not, see <http://www.gnu.org/licenses/>. |
22 | // |
23 | |
24 | #include "plugin.h" |
25 | #include "engine.h" |
26 | #include "utilities.h" |
27 | |
28 | #include <kate/application.h> |
29 | #include <kate/documentmanager.h> |
30 | #include <kate/mainwindow.h> |
31 | #include <kate/plugin.h> |
32 | |
33 | #include <ktexteditor/view.h> |
34 | #include <ktexteditor/document.h> |
35 | |
36 | #include <KAboutApplicationDialog> |
37 | #include <KAboutData> |
38 | #include <KAction> |
39 | #include <KActionCollection> |
40 | #include <KConfigBase> |
41 | #include <KConfigGroup> |
42 | #include <KDialog> |
43 | #include <KGenericFactory> |
44 | #include <KLocale> |
45 | #include <KPassivePopup> |
46 | #include <KTextEdit> |
47 | |
48 | #include <QCheckBox> |
49 | #include <QLabel> |
50 | #include <QPushButton> |
51 | #include <QSortFilterProxyModel> |
52 | #include <QStackedWidget> |
53 | #include <QTreeView> |
54 | #include <QVBoxLayout> |
55 | |
56 | #define CONFIG_SECTION "global" |
57 | |
58 | // |
59 | // The Pate plugin |
60 | // |
61 | |
62 | namespace { |
63 | const KAboutData& getAboutData() |
64 | { |
65 | static KAboutData about = KAboutData( |
66 | "katepateplugin" |
67 | , "pate" |
68 | , ki18n("Pâté host for Python plugins") |
69 | , "2.0" |
70 | , ki18n("Python interpreter settings") |
71 | , KAboutData::License_LGPL_V3 |
72 | ); |
73 | return about; |
74 | } |
75 | } // anonymous namespace |
76 | |
77 | K_PLUGIN_FACTORY(PatePluginFactory, registerPlugin<Pate::Plugin>();) |
78 | K_EXPORT_PLUGIN(PatePluginFactory(getAboutData())) |
79 | |
80 | //BEGIN Pate::Plugin |
81 | Pate::Plugin::Plugin(QObject* const app, const QList<QVariant>&) |
82 | : Kate::Plugin(static_cast<Kate::Application*>(app), "katepateplugin") |
83 | , m_engineFailureReason(m_engine.tryInitializeGetFailureReason()) |
84 | , m_autoReload(false) |
85 | { |
86 | // NOTE It is useless to show any popup here (in case of engine failure)... |
87 | // Need to wait untill some document's view changed... |
88 | } |
89 | |
90 | Pate::Plugin::~Plugin() |
91 | { |
92 | m_moduleConfigPages.clear(); |
93 | } |
94 | |
95 | Kate::PluginView* Pate::Plugin::createView(Kate::MainWindow* window) |
96 | { |
97 | return new Pate::PluginView(window, this); |
98 | } |
99 | |
100 | void Pate::Plugin::readSessionConfig(KConfigBase* const config, const QString& groupPrefix) |
101 | { |
102 | KConfigGroup group = config->group(groupPrefix + CONFIG_SECTION); |
103 | m_autoReload = group.readEntry("AutoReload", false); |
104 | if (m_engine) |
105 | { |
106 | m_engine.readGlobalPluginsConfiguration(); |
107 | kDebug() << "Reading session config from:"<< getSessionPrivateStorageFilename(config); |
108 | KConfig session_config(getSessionPrivateStorageFilename(config), KConfig::SimpleConfig); |
109 | m_engine.readSessionPluginsConfiguration(&session_config); |
110 | m_engine.setEnabledPlugins(group.readEntry("Enabled Plugins", QStringList())); |
111 | } |
112 | } |
113 | |
114 | void Pate::Plugin::writeSessionConfig(KConfigBase* const config, const QString& groupPrefix) |
115 | { |
116 | KConfigGroup group = config->group(groupPrefix + CONFIG_SECTION); |
117 | group.writeEntry("AutoReload", m_autoReload); |
118 | if (m_engine) |
119 | { |
120 | group.writeEntry("Enabled Plugins", m_engine.enabledPlugins()); |
121 | kDebug() << "Writing session config to:"<< getSessionPrivateStorageFilename(config); |
122 | m_engine.saveGlobalPluginsConfiguration(); |
123 | KConfig session_config(getSessionPrivateStorageFilename(config), KConfig::SimpleConfig); |
124 | m_engine.writeSessionPluginsConfiguration(&session_config); |
125 | session_config.sync(); |
126 | } |
127 | group.sync(); |
128 | } |
129 | |
130 | uint Pate::Plugin::configPages() const |
131 | { |
132 | const uint pages = 1; // The Manager page is always present. |
133 | reloadModuleConfigPages(); |
134 | return pages + m_moduleConfigPages.size(); |
135 | } |
136 | |
137 | /** |
138 | * The only purpose of this method is to reuse the same popup |
139 | * appeared (over and over) in case of broken engine... |
140 | */ |
141 | bool Pate::Plugin::checkEngineShowPopup() const |
142 | { |
143 | if (!m_engine) |
144 | { |
145 | KPassivePopup::message( |
146 | i18nc("@title:window", "Pate engine could not be initialised") |
147 | , m_engineFailureReason |
148 | , static_cast<QWidget*>(0) |
149 | ); |
150 | return false; |
151 | } |
152 | else |
153 | { |
154 | // Check if some modules are not available and show warning |
155 | unsigned broken_modules_count = 0; |
156 | Q_FOREACH(const Engine::PluginState& plugin, m_engine.plugins()) |
157 | broken_modules_count += unsigned(plugin.isEnabled() && plugin.isBroken()); |
158 | |
159 | if (broken_modules_count) |
160 | KPassivePopup::message( |
161 | i18nc("@title:window", "Warning") |
162 | , i18ncp( |
163 | "@info:tooltip %1 is a number of failed plugins" |
164 | , "%1 plugin module couldn't be loaded. Check the Python plugins config page for details." |
165 | , "%1 plugin modules couldn't be loaded. Check the Python plugins config page for details." |
166 | , broken_modules_count |
167 | ) |
168 | , static_cast<QWidget*>(0) |
169 | ); |
170 | } |
171 | return true; |
172 | } |
173 | |
174 | void Pate::Plugin::reloadModuleConfigPages() const |
175 | { |
176 | // Count the number of plugins which need their own custom page. |
177 | m_moduleConfigPages.clear(); |
178 | |
179 | Python py = Python(); |
180 | Q_FOREACH(const Engine::PluginState& plugin, m_engine.plugins()) |
181 | { |
182 | // Do not even try to load not enabled or broken plugins! |
183 | if (!plugin.isEnabled() || plugin.isBroken()) |
184 | continue; |
185 | |
186 | PyObject* configPages = py.moduleConfigPages(PQ(plugin.pythonModuleName())); |
187 | if (configPages) |
188 | { |
189 | for (Py_ssize_t k = 0, l = PyList_Size(configPages); k < l; ++k) |
190 | { |
191 | // Add an action for this plugin. |
192 | PyObject* tuple = PyList_GetItem(configPages, k); |
193 | m_moduleConfigPages.append(tuple); |
194 | } |
195 | } |
196 | } |
197 | } |
198 | |
199 | Kate::PluginConfigPage* Pate::Plugin::configPage( |
200 | uint number |
201 | , QWidget* const parent |
202 | , const char* const name |
203 | ) |
204 | { |
205 | Q_UNUSED(name); |
206 | |
207 | if (!number) |
208 | return new Pate::ConfigPage(parent, this); |
209 | |
210 | if (number > uint(m_moduleConfigPages.size())) |
211 | return 0; |
212 | |
213 | number--; |
214 | Python py = Python(); |
215 | PyObject* tuple = m_moduleConfigPages.at(number); |
216 | PyObject* func = PyTuple_GetItem(tuple, 1); |
217 | PyObject* w = py.objectWrap(parent, "PyQt4.QtGui.QWidget"); |
218 | PyObject* arguments = Py_BuildValue("(Oz)", w, name); |
219 | Py_DECREF(w); |
220 | Py_INCREF(func); |
221 | PyObject* result = PyObject_CallObject(func, arguments); |
222 | Py_DECREF(arguments); |
223 | if (!result) |
224 | { |
225 | // Return a page descrbing the error rather than crashing. |
226 | py.traceback("failed to call plugin page"); |
227 | return new Pate::ErrorConfigPage(parent, py.lastTraceback()); |
228 | } |
229 | Kate::PluginConfigPage* r = reinterpret_cast<Kate::PluginConfigPage*>(py.objectUnwrap(result)); |
230 | |
231 | /// \todo We leak this here reference. |
232 | //Py_DECREF(result); |
233 | return r; |
234 | } |
235 | |
236 | QString Pate::Plugin::configPageName(const uint number) const |
237 | { |
238 | if (!number) |
239 | return i18nc("@title:row", "Python Plugins"); |
240 | |
241 | if (number > uint(m_moduleConfigPages.size())) |
242 | return QString(); |
243 | |
244 | Python py = Python(); |
245 | PyObject* tuple = m_moduleConfigPages.at(number - 1); |
246 | PyObject* configPage = PyTuple_GetItem(tuple, 2); |
247 | PyObject* name = PyTuple_GetItem(configPage, 0); |
248 | return Python::unicode(name); |
249 | } |
250 | |
251 | QString Pate::Plugin::configPageFullName(const uint number) const |
252 | { |
253 | if (!number) |
254 | return i18nc("@title:tab", "Pâté host for Python plugins"); |
255 | |
256 | if (number > uint(m_moduleConfigPages.size())) |
257 | return QString(); |
258 | |
259 | Python py = Python(); |
260 | PyObject* tuple = m_moduleConfigPages.at(number - 1); |
261 | PyObject* configPage = PyTuple_GetItem(tuple, 2); |
262 | PyObject* fullName = PyTuple_GetItem(configPage, 1); |
263 | return Python::unicode(fullName); |
264 | } |
265 | |
266 | KIcon Pate::Plugin::configPageIcon(const uint number) const |
267 | { |
268 | if (!number) |
269 | return KIcon("preferences-plugin"); |
270 | |
271 | if (number > (uint)m_moduleConfigPages.size()) |
272 | return KIcon(); |
273 | |
274 | Python py = Python(); |
275 | PyObject* tuple = m_moduleConfigPages.at(number - 1); |
276 | PyObject* configPage = PyTuple_GetItem(tuple, 2); |
277 | PyObject* icon = PyTuple_GetItem(configPage, 2); |
278 | return *reinterpret_cast<KIcon*>(py.objectUnwrap(icon)); |
279 | } |
280 | |
281 | inline const Pate::Engine& Pate::Plugin::engine() const |
282 | { |
283 | return m_engine; |
284 | } |
285 | |
286 | inline Pate::Engine& Pate::Plugin::engine() |
287 | { |
288 | return m_engine; |
289 | } |
290 | |
291 | QString Pate::Plugin::getSessionPrivateStorageFilename(KConfigBase* const config) |
292 | { |
293 | KConfig* real_config = dynamic_cast<KConfig*>(config); |
294 | Q_ASSERT("WOW! KDE API now uses smth else than KConfig?"&& real_config); |
295 | /// \note In case of new or default session, a "global" config file |
296 | /// will be used, so switch to a separate (private) file then... |
297 | if (real_config->name() == "katerc") |
298 | return "katepaterc"; |
299 | return real_config->name().replace(".katesession", ".katepate"); |
300 | } |
301 | |
302 | void Pate::Plugin::setFailureReason(QString reason) |
303 | { |
304 | m_engineFailureReason.swap(reason); |
305 | } |
306 | |
307 | |
308 | // |
309 | // Plugin view, instances of which are created once for each session. |
310 | // |
311 | //BEGIN Pate::PluginView |
312 | Pate::PluginView::PluginView(Kate::MainWindow* const window, Plugin* const plugin) |
313 | : Kate::PluginView(window) |
314 | , Kate::XMLGUIClient(PatePluginFactory::componentData()) |
315 | , m_plugin(plugin) |
316 | { |
317 | KAction* about = actionCollection()->addAction("about_pate"); |
318 | about->setText(i18n("About Pate")); |
319 | about->setIcon(KIcon("python")); |
320 | connect(about, SIGNAL(triggered(bool)), this, SLOT(aboutPate())); |
321 | |
322 | // Try to import the `kate` module |
323 | Python py = Python(); |
324 | PyObject* katePackage = py.moduleImport("kate"); |
325 | if (katePackage) |
326 | { |
327 | // Ok, load others... |
328 | plugin->engine().tryLoadEnabledPlugins(); |
329 | py.functionCall("_pateLoaded"); |
330 | } |
331 | else |
332 | { |
333 | m_plugin->setFailureReason(i18nc("@info:tooltip ", "Cannot load <icode>kate</icode> module")); |
334 | m_plugin->engine().setBroken(); |
335 | } |
336 | m_plugin->checkEngineShowPopup(); |
337 | // Inject collected actions into GUI |
338 | mainWindow()->guiFactory()->addClient(this); |
339 | } |
340 | //END Pate::PluginView |
341 | |
342 | Pate::PluginView::~PluginView() |
343 | { |
344 | mainWindow()->guiFactory()->removeClient(this); |
345 | } |
346 | |
347 | void Pate::PluginView::aboutPate() |
348 | { |
349 | KAboutData about = getAboutData(); |
350 | // Set other text to show some info about Python used |
351 | // NOTE Separate scope around Python() instance |
352 | QStringList pythonPaths; |
353 | Python py = Python(); |
354 | if (PyObject* sysPath = py.itemString("path", "sys")) |
355 | { |
356 | Py_ssize_t len = PyList_Size(sysPath); |
357 | for (Py_ssize_t i = 0; i < len; i++) |
358 | { |
359 | PyObject* path = PyList_GetItem(sysPath, i); |
360 | pythonPaths += Python::unicode(path); |
361 | } |
362 | } |
363 | /// \todo Show info about loaded modules? Problems? |
364 | |
365 | /// \attention It seems about dialog is not customizable much... |
366 | /// Particularly it would be nice to add a custom tab... |
367 | /// So \b dirty hack is here... |
368 | about.setOtherText(ki18nc("Python variables, no translation needed", |
369 | "sys.version = %1<br/>sys.path = %2"). |
370 | subs(PY_VERSION).subs(pythonPaths.join(",\n "))); |
371 | |
372 | /// \todo Add logo, authors and everything... |
373 | about.setProgramIconName("python"); |
374 | about.addAuthor(ki18n("Paul Giannaros"), ki18n( "Out-of-tree original"), "paul@giannaros.org"); |
375 | about.addAuthor(ki18n("Shaheed Haque"), ki18n( "Rewritten and brought in-tree, V1.0"), "srhaque@theiet.org"); |
376 | about.addAuthor(ki18n("Alex Turbov"), ki18n( "Streamlined and updated, V2.0"), "i.zaufi@gmail.com"); |
377 | KAboutApplicationDialog ad(&about, KAboutApplicationDialog::HideKdeVersion); |
378 | ad.exec(); |
379 | } |
380 | |
381 | // |
382 | // Plugin configuration view. |
383 | // |
384 | |
385 | //BEGIN Pate::ConfigPage |
386 | Pate::ConfigPage::ConfigPage(QWidget* const parent, Plugin* const plugin) |
387 | : Kate::PluginConfigPage(parent) |
388 | , m_plugin(plugin) |
389 | { |
390 | if (!m_plugin->checkEngineShowPopup()) |
391 | { |
392 | /// \todo HIDE THE MANAGER AND SHOW ERROR INSTEAD! |
393 | //return; |
394 | } |
395 | |
396 | // Create a page with just the main manager tab. |
397 | m_manager.setupUi(this); |
398 | QSortFilterProxyModel* const proxy_model = new QSortFilterProxyModel(this); |
399 | proxy_model->setSourceModel(&m_plugin->engine()); |
400 | m_manager.pluginsList->setModel(proxy_model); |
401 | m_manager.pluginsList->resizeColumnToContents(0); |
402 | m_manager.pluginsList->sortByColumn(0, Qt::AscendingOrder); |
403 | reset(); |
404 | |
405 | const bool is_enabled = bool(m_plugin->engine()); |
406 | const bool is_visible = !is_enabled; |
407 | m_manager.errorLabel->setVisible(is_visible); |
408 | m_manager.pluginsList->setEnabled(is_enabled); |
409 | } |
410 | |
411 | Pate::ConfigPage::~ConfigPage() |
412 | { |
413 | } |
414 | |
415 | void Pate::ConfigPage::apply() |
416 | { |
417 | // Retrieve the settings from the UI and reflect them in the plugin. |
418 | } |
419 | |
420 | void Pate::ConfigPage::reset() |
421 | { |
422 | // Retrieve the settings from the plugin and reflect them in the UI. |
423 | } |
424 | |
425 | void Pate::ConfigPage::defaults() |
426 | { |
427 | // Set the UI to have default settings. |
428 | Q_EMIT(changed()); |
429 | } |
430 | //END Pate::ConfigPage |
431 | |
432 | |
433 | //BEGIN Pate::ErrorConfigPage |
434 | Pate::ErrorConfigPage::ErrorConfigPage(QWidget* const parent, const QString& traceback) |
435 | : Kate::PluginConfigPage(parent) |
436 | { |
437 | KTextEdit* const widget = new KTextEdit(parent); |
438 | widget->setText(traceback); |
439 | widget->setReadOnly(true); |
440 | widget->setEnabled(true); |
441 | QLayout* lo = parent->layout(); |
442 | lo->addWidget(widget); |
443 | } |
444 | //END Pate::ErrorConfigPage |
445 | |
446 | // kate: indent-width 4; |
447 |
Warning: That file was not part of the compilation database. It may have many parsing errors.