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
62namespace {
63const 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
77K_PLUGIN_FACTORY(PatePluginFactory, registerPlugin<Pate::Plugin>();)
78K_EXPORT_PLUGIN(PatePluginFactory(getAboutData()))
79
80//BEGIN Pate::Plugin
81Pate::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
90Pate::Plugin::~Plugin()
91{
92 m_moduleConfigPages.clear();
93}
94
95Kate::PluginView* Pate::Plugin::createView(Kate::MainWindow* window)
96{
97 return new Pate::PluginView(window, this);
98}
99
100void 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
114void 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
130uint 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 */
141bool 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
174void 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
199Kate::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
236QString 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
251QString 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
266KIcon 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
281inline const Pate::Engine& Pate::Plugin::engine() const
282{
283 return m_engine;
284}
285
286inline Pate::Engine& Pate::Plugin::engine()
287{
288 return m_engine;
289}
290
291QString 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
302void 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
312Pate::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
342Pate::PluginView::~PluginView()
343{
344 mainWindow()->guiFactory()->removeClient(this);
345}
346
347void 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&nbsp;&nbsp;&nbsp;&nbsp;")));
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
386Pate::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
411Pate::ConfigPage::~ConfigPage()
412{
413}
414
415void Pate::ConfigPage::apply()
416{
417 // Retrieve the settings from the UI and reflect them in the plugin.
418}
419
420void Pate::ConfigPage::reset()
421{
422 // Retrieve the settings from the plugin and reflect them in the UI.
423}
424
425void 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
434Pate::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.