1 /* This file is part of the KDE project
2    Copyright (C) 2004 Lucijan Busch <lucijan@kde.org>
3    Copyright (C) 2004-2017 Jarosław Staniek <staniek@kde.org>
4 
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (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 GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public License
16    along with this program; see the file COPYING.  If not, write to
17    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19 */
20 
21 #include "kexiquerypart.h"
22 #include "kexiqueryview.h"
23 #include "kexiquerydesignerguieditor.h"
24 #include "kexiquerydesignersql.h"
25 #include <KexiMainWindowIface.h>
26 #include <KexiWindow.h>
27 #include <kexiproject.h>
28 #include <kexipartinfo.h>
29 #include <kexiutils/utils.h>
30 
31 #include <KDbConnection>
32 #include <KDbCursor>
33 #include <KDbParser>
34 #include <KDbQuerySchema>
35 
36 #include <KStandardGuiItem>
37 #include <KMessageBox>
38 
39 #include <QDebug>
40 
41 KEXI_PLUGIN_FACTORY(KexiQueryPart, "kexi_queryplugin.json")
42 
KexiQueryPart(QObject * parent,const QVariantList & l)43 KexiQueryPart::KexiQueryPart(QObject *parent, const QVariantList &l)
44   : KexiPart::Part(parent,
45         xi18nc("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). "
46               "Use '_' character instead of spaces. First character should be a..z character. "
47               "If you cannot use latin characters in your language, use english word.",
48               "query"),
49         xi18nc("tooltip", "Create new query"),
50         xi18nc("what's this", "Creates new query."),
51         l)
52 {
53     setInternalPropertyValue("textViewModeCaption", xi18n("SQL"));
54 }
55 
~KexiQueryPart()56 KexiQueryPart::~KexiQueryPart()
57 {
58 }
59 
createWindowData(KexiWindow * window)60 KexiWindowData* KexiQueryPart::createWindowData(KexiWindow* window)
61 {
62     return new KexiQueryPartTempData(window,
63                                      KexiMainWindowIface::global()->project()->dbConnection());
64 }
65 
createView(QWidget * parent,KexiWindow * window,KexiPart::Item * item,Kexi::ViewMode viewMode,QMap<QString,QVariant> *)66 KexiView* KexiQueryPart::createView(QWidget *parent, KexiWindow* window, KexiPart::Item *item,
67                                     Kexi::ViewMode viewMode, QMap<QString, QVariant>*)
68 {
69     Q_ASSERT(item);
70     Q_UNUSED(item);
71     Q_UNUSED(window);
72     //qDebug();
73 
74     KexiView* view = 0;
75     if (viewMode == Kexi::DataViewMode) {
76         view = new KexiQueryView(parent);
77         view->setObjectName("dataview");
78     }
79     else if (viewMode == Kexi::DesignViewMode) {
80         view = new KexiQueryDesignerGuiEditor(parent);
81         view->setObjectName("guieditor");
82         //needed for updating tables combo box:
83         KexiProject *prj = KexiMainWindowIface::global()->project();
84         connect(prj, SIGNAL(newItemStored(KexiPart::Item*)),
85                 view, SLOT(slotNewItemStored(KexiPart::Item*)));
86         connect(prj, SIGNAL(itemRemoved(KexiPart::Item)),
87                 view, SLOT(slotItemRemoved(KexiPart::Item)));
88         connect(prj, SIGNAL(itemRenamed(KexiPart::Item,QString)),
89                 view, SLOT(slotItemRenamed(KexiPart::Item,QString)));
90     }
91     else if (viewMode == Kexi::TextViewMode) {
92         view = new KexiQueryDesignerSqlView(parent);
93         view->setObjectName("sqldesigner");
94     }
95     return view;
96 }
97 
remove(KexiPart::Item * item)98 tristate KexiQueryPart::remove(KexiPart::Item *item)
99 {
100     if (!KexiMainWindowIface::global()->project()
101             || !KexiMainWindowIface::global()->project()->dbConnection())
102         return false;
103     KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection();
104     KDbQuerySchema *sch = conn->querySchema(item->identifier());
105     if (sch) {
106         const tristate res = KexiQueryPart::askForClosingObjectsUsingQuerySchema(
107             KexiMainWindowIface::global()->openedWindowFor(item->identifier()), conn, sch,
108             kxi18n("<para>You are about to delete query <resource>%1</resource> but it is used by "
109                    "following opened windows:</para>")
110                 .subs(sch->name()));
111         if (res != true) {
112             return res;
113         }
114         return conn->dropQuery(sch);
115     }
116     //last chance: just remove item
117     return conn->removeObject(item->identifier());
118 }
119 
initPartActions()120 void KexiQueryPart::initPartActions()
121 {
122 }
123 
initInstanceActions()124 void KexiQueryPart::initInstanceActions()
125 {
126 }
127 
loadSchemaObject(KexiWindow * window,const KDbObject & object,Kexi::ViewMode viewMode,bool * ownedByWindow)128 KDbObject* KexiQueryPart::loadSchemaObject(
129     KexiWindow *window, const KDbObject& object, Kexi::ViewMode viewMode,
130     bool *ownedByWindow)
131 {
132     Q_ASSERT(ownedByWindow);
133     *ownedByWindow = false;
134     KexiQueryPartTempData * temp = static_cast<KexiQueryPartTempData*>(window->data());
135     QString sql;
136     if (!loadDataBlock(window, &sql, "sql")) {
137         return 0;
138     }
139     KDbEscapedString sqlText(sql);
140     KDbParser *parser = KexiMainWindowIface::global()->project()->sqlParser();
141     KDbQuerySchema *query = 0;
142     if (parser->parse(sqlText)) {
143         query = parser->query();
144     }
145     //error?
146     if (!query) {
147         if (viewMode == Kexi::TextViewMode) {
148             //for SQL view, no parsing is initially needed:
149             //-just make a copy:
150             return KexiPart::Part::loadSchemaObject(window, object, viewMode, ownedByWindow);
151         }
152         /* Set this to true on data loading loadSchemaObject() to indicate that TextView mode
153          could be used instead of DataView or DesignView, because there are problems
154          with opening object. */
155         temp->proposeOpeningInTextViewModeBecauseOfProblems = true;
156         //! @todo
157         return 0;
158     }
159     qDebug() << KDbConnectionAndQuerySchema(
160         KexiMainWindowIface::global()->project()->dbConnection(), *query);
161     (KDbObject&)*query = object; //copy main attributes
162 
163     temp->registerTableSchemaChanges(query);
164     *ownedByWindow = true; // owned because it is created by the parser
165 
166     qDebug() << KDbConnectionAndQuerySchema(
167         KexiMainWindowIface::global()->project()->dbConnection(), *query);
168     return query;
169 }
170 
currentQuery(KexiView * view)171 KDbQuerySchema *KexiQueryPart::currentQuery(KexiView* view)
172 {
173     if (!view)
174         return 0;
175 
176     KexiQueryView *qvp = 0;
177     if (!(qvp = qobject_cast<KexiQueryView*>(view))) {
178         return 0;
179     }
180 
181     return static_cast<KexiQueryPartTempData*>(qvp->window()->data())->query();
182 }
183 
i18nMessage(const QString & englishMessage,KexiWindow * window) const184 KLocalizedString KexiQueryPart::i18nMessage(const QString& englishMessage, KexiWindow* window) const
185 {
186     if (englishMessage == "Design of object <resource>%1</resource> has been modified.")
187         return kxi18nc(I18NC_NOOP("@info", "Design of query <resource>%1</resource> has been modified."));
188     if (englishMessage == "Object <resource>%1</resource> already exists.")
189         return kxi18nc(I18NC_NOOP("@info", "Query <resource>%1</resource> already exists."));
190 
191     return Part::i18nMessage(englishMessage, window);
192 }
193 
rename(KexiPart::Item * item,const QString & newName)194 tristate KexiQueryPart::rename(KexiPart::Item *item, const QString& newName)
195 {
196     Q_ASSERT(item);
197     Q_UNUSED(newName);
198     if (!KexiMainWindowIface::global()->project()->dbConnection())
199         return false;
200     // Note: unlike in KexiTablePart::rename here we just mark the query obsolete here so no need
201     // to call KexiQueryPart::askForClosingObjectsUsingQuerySchema().
202     KexiMainWindowIface::global()->project()->dbConnection()
203         ->setQuerySchemaObsolete(item->name());
204     return true;
205 }
206 
207 // static
askForClosingObjectsUsingQuerySchema(KexiWindow * window,KDbConnection * conn,KDbQuerySchema * query,const KLocalizedString & msg)208 tristate KexiQueryPart::askForClosingObjectsUsingQuerySchema(KexiWindow *window,
209                                                              KDbConnection *conn,
210                                                              KDbQuerySchema *query,
211                                                              const KLocalizedString &msg)
212 {
213     Q_ASSERT(conn);
214     Q_ASSERT(query);
215     if (!window) {
216         return true;
217     }
218     QList<KDbTableSchemaChangeListener*> listeners
219             = KDbTableSchemaChangeListener::listeners(conn, query);
220     KexiQueryPartTempData *temp = static_cast<KexiQueryPartTempData*>(window->data());
221     // Special case: listener that is equal to window->data() will be silently closed
222     // without asking for confirmation. It is not counted when looking for objects that
223     // are "blocking" changes of the query.
224     const bool tempListenerExists = listeners.removeAll(temp) > 0;
225     // Immediate success if there's no temp-data's listener to close nor other listeners to close
226     if (!tempListenerExists && listeners.isEmpty()) {
227         return true;
228     }
229 
230     if (!listeners.isEmpty()) {
231         QString openedObjectsStr = "<p><ul>";
232         for(const KDbTableSchemaChangeListener* listener : listeners) {
233             openedObjectsStr += QString("<li>%1</li>").arg(listener->name());
234         }
235         openedObjectsStr += "</ul></p>";
236         QString message = "<html>"
237             + i18nc("@info/plain Sentence1 Sentence2 Sentence3", "%1%2%3",
238                     KexiUtils::localizedStringToHtmlSubstring(msg), openedObjectsStr,
239                     KexiUtils::localizedStringToHtmlSubstring(
240                         kxi18nc("@info", "<para>Do you want to close these windows and save the "
241                                          "design or cancel saving?</para>")))
242             + "</html>";
243         KGuiItem closeAndSaveItem(KStandardGuiItem::save());
244         closeAndSaveItem.setText(
245             xi18nc("@action:button Close all windows and save", "Close Windows and Save"));
246         closeAndSaveItem.setToolTip(xi18nc("@info:tooltip Close all windows and save design",
247                                            "Close all windows and save design"));
248         const int r = KMessageBox::questionYesNo(window, message, QString(), closeAndSaveItem,
249                                                  KStandardGuiItem::cancel(), QString(),
250                                                  KMessageBox::Notify | KMessageBox::Dangerous);
251         if (r != KMessageBox::Yes) {
252             return cancelled;
253         }
254     }
255     // try to close every window depending on the query (if present) and also the temp-data's
256     // listener (if present)
257     const tristate res = KDbTableSchemaChangeListener::closeListeners(conn, query, { temp });
258     if (res != true) { //do not expose closing errors twice; just cancel
259         return cancelled;
260     }
261     return true;
262 }
263 
264 //----------------
265 
KexiQueryPartTempData(KexiWindow * window,KDbConnection * conn)266 KexiQueryPartTempData::KexiQueryPartTempData(KexiWindow* window, KDbConnection *conn)
267         : KexiWindowData(window)
268         , KDbTableSchemaChangeListener()
269         , m_query(0)
270         , m_queryChangedInView(Kexi::NoViewMode)
271 {
272     this->conn = conn;
273     setName(KexiUtils::localizedStringToHtmlSubstring(
274         kxi18nc("@info", "Query <resource>%1</resource>").subs(window->partItem()->name())));
275 }
276 
~KexiQueryPartTempData()277 KexiQueryPartTempData::~KexiQueryPartTempData()
278 {
279     KDbTableSchemaChangeListener::unregisterForChanges(conn, this);
280 }
281 
clearQuery()282 void KexiQueryPartTempData::clearQuery()
283 {
284     if (!m_query)
285         return;
286     unregisterForTablesSchemaChanges();
287     m_query->clear();
288 }
289 
unregisterForTablesSchemaChanges()290 void KexiQueryPartTempData::unregisterForTablesSchemaChanges()
291 {
292     KDbTableSchemaChangeListener::unregisterForChanges(conn, this);
293 }
294 
registerTableSchemaChanges(KDbQuerySchema * q)295 void KexiQueryPartTempData::registerTableSchemaChanges(KDbQuerySchema *q)
296 {
297     if (!q)
298         return;
299     KDbTableSchemaChangeListener::registerForChanges(conn, this, q);
300 }
301 
closeListener()302 tristate KexiQueryPartTempData::closeListener()
303 {
304     KexiWindow* window = static_cast<KexiWindow*>(parent());
305     qDebug() << window->partItem()->name();
306     return KexiMainWindowIface::global()->closeWindow(window);
307 }
308 
takeQuery()309 KDbQuerySchema *KexiQueryPartTempData::takeQuery()
310 {
311     KDbQuerySchema *query = m_query;
312     m_query = 0;
313     return query;
314 }
315 
setQuery(KDbQuerySchema * query)316 void KexiQueryPartTempData::setQuery(KDbQuerySchema *query)
317 {
318     if (m_query && m_query == query)
319         return;
320     KexiWindow* window = static_cast<KexiWindow*>(parent());
321     if (m_query
322             /* query not owned by window */
323             && (static_cast<KexiWindow*>(parent())->schemaObject() != static_cast<KDbObject*>(m_query)))
324     {
325         KexiQueryView* dataView = qobject_cast<KexiQueryView*>(window->viewForMode(Kexi::DataViewMode));
326         if (dataView && dataView->query() == m_query) {
327             dataView->setQuery(nullptr); // unassign before deleting
328         }
329         delete m_query;
330     }
331     m_query = query;
332 }
333 
queryChangedInView() const334 Kexi::ViewMode KexiQueryPartTempData::queryChangedInView() const
335 {
336     return m_queryChangedInView;
337 }
338 
setQueryChangedInView(bool set)339 void KexiQueryPartTempData::setQueryChangedInView(bool set)
340 {
341     m_queryChangedInView = set ? qobject_cast<KexiWindow*>(parent())->currentViewMode()
342                                        : Kexi::NoViewMode;
343 }
344 
345 #include "kexiquerypart.moc"
346