1 #include "SqlExecutionArea.h"
2 #include "ui_SqlExecutionArea.h"
3 #include "sqltextedit.h"
4 #include "sqlitetablemodel.h"
5 #include "sqlitedb.h"
6 #include "Settings.h"
7 #include "ExportDataDialog.h"
8 #include "FilterTableHeader.h"
9
10 #include <QInputDialog>
11 #include <QMessageBox>
12 #include <QShortcut>
13 #include <QFile>
14
SqlExecutionArea(DBBrowserDB & _db,QWidget * parent)15 SqlExecutionArea::SqlExecutionArea(DBBrowserDB& _db, QWidget* parent) :
16 QWidget(parent),
17 db(_db),
18 ui(new Ui::SqlExecutionArea),
19 m_columnsResized(false),
20 error_state(false)
21 {
22 // Create UI
23 ui->setupUi(this);
24
25 // Create model
26 model = new SqliteTableModel(db, this);
27 ui->tableResult->setModel(model);
28 connect(model, &SqliteTableModel::finishedFetch, this, &SqlExecutionArea::fetchedData);
29 connect(ui->tableResult->filterHeader(), &FilterTableHeader::sectionPressed, ui->tableResult, &QTableView::selectColumn);
30
31 ui->findFrame->hide();
32
33 QShortcut* shortcutHideFind = new QShortcut(QKeySequence("ESC"), ui->findLineEdit);
34 connect(shortcutHideFind, &QShortcut::activated, this, &SqlExecutionArea::hideFindFrame);
35
36 connect(ui->findLineEdit, &QLineEdit::textChanged, this, &SqlExecutionArea::findLineEdit_textChanged);
37 connect(ui->previousToolButton, &QToolButton::clicked, this, &SqlExecutionArea::findPrevious);
38 connect(ui->nextToolButton, &QToolButton::clicked, this, &SqlExecutionArea::findNext);
39 connect(ui->findLineEdit, &QLineEdit::returnPressed, this, &SqlExecutionArea::findNext);
40 connect(ui->hideFindButton, &QToolButton::clicked, this, &SqlExecutionArea::hideFindFrame);
41
42 connect(&fileSystemWatch, &QFileSystemWatcher::fileChanged, this, &SqlExecutionArea::fileChanged);
43
44 // Save to settings when sppliter is moved, but only to memory.
45 connect(ui->splitter, &QSplitter::splitterMoved, this, [this]() {
46 Settings::setValue("editor", "splitter1_sizes", ui->splitter->saveState(), /* dont_save_to_disk */ true);
47 });
48 connect(ui->splitter_2, &QSplitter::splitterMoved, this, [this]() {
49 Settings::setValue("editor", "splitter2_sizes", ui->splitter_2->saveState(), /* dont_save_to_disk */ true);
50 });
51
52 // Set collapsible the editErrors panel
53 ui->splitter_2->setCollapsible(1, true);
54
55 // Load settings
56 reloadSettings();
57 }
58
~SqlExecutionArea()59 SqlExecutionArea::~SqlExecutionArea()
60 {
61 delete ui;
62 }
63
getSql() const64 QString SqlExecutionArea::getSql() const
65 {
66 return ui->editEditor->text();
67 }
68
getSelectedSql() const69 QString SqlExecutionArea::getSelectedSql() const
70 {
71 return ui->editEditor->selectedText().trimmed().replace(QChar(0x2029), '\n');
72 }
73
setSql(const QString & sql)74 void SqlExecutionArea::setSql(const QString& sql)
75 {
76 ui->editEditor->setText(sql);
77 }
78
finishExecution(const QString & result,const bool ok)79 void SqlExecutionArea::finishExecution(const QString& result, const bool ok)
80 {
81 error_state = !ok;
82 m_columnsResized = false;
83 ui->editErrors->setPlainText(result);
84 // Set reddish background when not ok
85 if (showErrorIndicators)
86 {
87 if (ok)
88 ui->editErrors->setStyleSheet("");
89 else
90 ui->editErrors->setStyleSheet("QTextEdit {color: white; background-color: rgb(255, 102, 102)}");
91 }
92
93 }
94
fetchedData()95 void SqlExecutionArea::fetchedData()
96 {
97 // Don't resize the columns more than once to fit their contents. This is necessary because the finishedFetch signal of the model
98 // is emitted for each loaded prefetch block and we want to avoid resizes while scrolling down.
99 if(m_columnsResized)
100 return;
101 m_columnsResized = true;
102
103 // Set column widths according to their contents but make sure they don't exceed a certain size
104 ui->tableResult->resizeColumnsToContents();
105 for(int i = 0; i < model->columnCount(); i++)
106 {
107 if(ui->tableResult->columnWidth(i) > 300)
108 ui->tableResult->setColumnWidth(i, 300);
109 }
110 }
111
getEditor()112 SqlTextEdit *SqlExecutionArea::getEditor()
113 {
114 return ui->editEditor;
115 }
116
getTableResult()117 ExtendedTableWidget *SqlExecutionArea::getTableResult()
118 {
119 return ui->tableResult;
120 }
121
getStatusEdit()122 QTextEdit* SqlExecutionArea::getStatusEdit()
123 {
124 return ui->editErrors;
125 }
126
saveAsCsv()127 void SqlExecutionArea::saveAsCsv()
128 {
129 ExportDataDialog dialog(db, ExportDataDialog::ExportFormatCsv, this, model->query());
130 dialog.exec();
131 }
132
reloadSettings()133 void SqlExecutionArea::reloadSettings()
134 {
135 // Reload editor and table settings
136 ui->editEditor->reloadSettings();
137 ui->tableResult->reloadSettings();
138
139 // Set font
140 QFont logfont(Settings::getValue("editor", "font").toString());
141 logfont.setStyleHint(QFont::TypeWriter);
142 logfont.setPointSize(Settings::getValue("log", "fontsize").toInt());
143 ui->editErrors->setFont(logfont);
144
145 // Apply horizontal/vertical tiling option
146 if(Settings::getValue("editor", "horizontal_tiling").toBool())
147 ui->splitter->setOrientation(Qt::Horizontal);
148 else
149 ui->splitter->setOrientation(Qt::Vertical);
150
151 ui->splitter->restoreState(Settings::getValue("editor", "splitter1_sizes").toByteArray());
152 ui->splitter_2->restoreState(Settings::getValue("editor", "splitter2_sizes").toByteArray());
153
154 // Reload model settings
155 model->reloadSettings();
156
157 // Check if error indicators are enabled for the not-ok background clue
158 showErrorIndicators = Settings::getValue("editor", "error_indicators").toBool();
159 if (!showErrorIndicators)
160 ui->editErrors->setStyleSheet("");
161
162 }
163
find(QString expr,bool forward)164 void SqlExecutionArea::find(QString expr, bool forward)
165 {
166
167 bool found = ui->editEditor->findText
168 (expr,
169 ui->regexpCheckBox->isChecked(),
170 ui->caseCheckBox->isChecked(),
171 ui->wholeWordsCheckBox->isChecked(),
172 /* wrap */ true,
173 forward);
174
175 // Set reddish background when not found
176 if (found || expr.isEmpty())
177 ui->findLineEdit->setStyleSheet("");
178 else
179 ui->findLineEdit->setStyleSheet("QLineEdit {color: white; background-color: rgb(255, 102, 102)}");
180
181 }
182
findPrevious()183 void SqlExecutionArea::findPrevious()
184 {
185 find(ui->findLineEdit->text(), false);
186 }
187
findNext()188 void SqlExecutionArea::findNext()
189 {
190 find(ui->findLineEdit->text(), true);
191 }
192
findLineEdit_textChanged(const QString &)193 void SqlExecutionArea::findLineEdit_textChanged(const QString &)
194 {
195 // When the text changes, perform an incremental search from cursor
196 // position, or from begin of the selection position.
197
198 // For incremental search while typing we need to start from the
199 // begining of the current selection, otherwise we'd jump to the
200 // next occurrence
201 if (ui->editEditor->hasSelectedText()) {
202 int lineFrom;
203 int indexFrom;
204 int lineTo;
205 int indexTo;
206 ui->editEditor->getSelection(&lineFrom, &indexFrom, &lineTo, &indexTo);
207 ui->editEditor->setCursorPosition(lineFrom, indexFrom);
208 }
209
210 find(ui->findLineEdit->text(), true);
211 }
212
hideFindFrame()213 void SqlExecutionArea::hideFindFrame()
214 {
215 ui->editEditor->setFocus();
216 ui->findFrame->hide();
217 emit findFrameVisibilityChanged(false);
218 }
219
setFindFrameVisibility(bool show)220 void SqlExecutionArea::setFindFrameVisibility(bool show)
221 {
222 if (show) {
223 ui->findFrame->show();
224 ui->findLineEdit->setFocus();
225 ui->findLineEdit->selectAll();
226 emit findFrameVisibilityChanged(true);
227 } else {
228 hideFindFrame();
229 }
230 }
231
openFile(const QString & filename)232 void SqlExecutionArea::openFile(const QString& filename)
233 {
234 // Open file for reading
235 QFile f(filename);
236 f.open(QIODevice::ReadOnly);
237 if(!f.isOpen())
238 {
239 QMessageBox::warning(this, qApp->applicationName(), tr("Couldn't read file: %1.").arg(f.errorString()));
240 return;
241 }
242
243 // Read in the entire file
244 ui->editEditor->setText(f.readAll());
245
246 // No modifications yet
247 ui->editEditor->setModified(false);
248
249 // Remember file name
250 sqlFileName = filename;
251
252 // Start watching this file for changes and unwatch the previously watched file, if any
253 if(!fileSystemWatch.files().empty())
254 fileSystemWatch.removePaths(fileSystemWatch.files());
255 fileSystemWatch.addPath(filename);
256 }
257
saveFile(const QString & filename)258 void SqlExecutionArea::saveFile(const QString& filename)
259 {
260 // Unwatch all files now. By unwathing them before the actual saving, we are not notified of our own changes
261 if(!fileSystemWatch.files().empty())
262 fileSystemWatch.removePaths(fileSystemWatch.files());
263
264 // Open file for writing
265 QFile f(filename);
266 f.open(QIODevice::WriteOnly);
267 if(!f.isOpen())
268 {
269 QMessageBox::warning(this, qApp->applicationName(), tr("Couldn't save file: %1.").arg(f.errorString()));
270 return;
271 }
272
273 // Write to the file
274 if(f.write(getSql().toUtf8()) != -1)
275 {
276 // Close file now. If we let the destructor close it, we can get change notifications.
277 f.close();
278 // Set modified to false so we can get control of unsaved changes when closing.
279 ui->editEditor->setModified(false);
280
281 // Remember file name
282 sqlFileName = filename;
283
284 // Start watching this file
285 fileSystemWatch.addPath(filename);
286 } else {
287 QMessageBox::warning(this, qApp->applicationName(), tr("Couldn't save file: %1.").arg(f.errorString()));
288 return;
289 }
290 }
291
fileChanged(const QString & filename)292 void SqlExecutionArea::fileChanged(const QString& filename)
293 {
294 // Check if there are unsaved changes in the file
295 QString changes;
296 if(ui->editEditor->isModified())
297 changes = QString(" ") + tr("Your changes will be lost when reloading it!");
298
299 // Ask user whether to realod the modified file
300 if(QMessageBox::question(
301 this,
302 qApp->applicationName(),
303 tr("The file \"%1\" was modified by another program. Do you want to reload it?%2").arg(filename, changes),
304 QMessageBox::Yes | QMessageBox::Ignore) == QMessageBox::Yes)
305 {
306 // Read in the file
307 openFile(filename);
308 } else {
309 // The file does not match the file on the disk anymore. So set the modified flag
310 ui->editEditor->setModified(true);
311 }
312 }
313
saveState()314 void SqlExecutionArea::saveState() {
315
316 // Save to disk last stored splitter sizes
317 Settings::setValue("editor", "splitter1_sizes", Settings::getValue("editor", "splitter1_sizes"));
318 Settings::setValue("editor", "splitter2_sizes", Settings::getValue("editor", "splitter2_sizes"));
319 }
320