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