forked from sqlitebrowser/sqlitebrowser
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEditDialog.cpp
More file actions
543 lines (445 loc) · 16.6 KB
/
EditDialog.cpp
File metadata and controls
543 lines (445 loc) · 16.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
#include "EditDialog.h"
#include "ui_EditDialog.h"
#include "sqlitedb.h"
#include "Settings.h"
#include "src/qhexedit.h"
#include "FileDialog.h"
#include <QMainWindow>
#include <QKeySequence>
#include <QShortcut>
#include <QImageReader>
#include <QBuffer>
//#include <QStringBuilder>
#include <QModelIndex>
EditDialog::EditDialog(QWidget* parent)
: QDialog(parent),
ui(new Ui::EditDialog),
currentIndex(QModelIndex()),
dataType(Null),
isReadOnly(true)
{
ui->setupUi(this);
// Add Ctrl-Enter (Cmd-Enter on OSX) as a shortcut for the Apply button
ui->buttonApply->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return));
QHBoxLayout* hexLayout = new QHBoxLayout(ui->editorBinary);
hexEdit = new QHexEdit(this);
hexLayout->addWidget(hexEdit);
hexEdit->setOverwriteMode(false);
QShortcut* ins = new QShortcut(QKeySequence(Qt::Key_Insert), this);
connect(ins, SIGNAL(activated()), this, SLOT(toggleOverwriteMode()));
// Set the font for the text and hex editors
QFont editorFont(Settings::getSettingsValue("databrowser", "font").toString());
editorFont.setPointSize(Settings::getSettingsValue("databrowser", "fontsize").toInt());
ui->editorText->setFont(editorFont);
hexEdit->setFont(editorFont);
connect(ui->editorText, SIGNAL(textChanged()), this, SLOT(updateApplyButton()));
connect(hexEdit, SIGNAL(dataChanged()), this, SLOT(updateApplyButton()));
}
EditDialog::~EditDialog()
{
delete ui;
}
void EditDialog::setCurrentIndex(const QModelIndex& idx)
{
currentIndex = QPersistentModelIndex(idx);
QByteArray data = idx.data(Qt::EditRole).toByteArray();
loadData(data);
updateCellInfo(data);
ui->buttonApply->setDisabled(true);
}
void EditDialog::showEvent(QShowEvent*)
{
// Whenever the dialog is shown, position it at the center of the parent dialog
QMainWindow* parentDialog = qobject_cast<QMainWindow*>(parent());
if(parentDialog)
{
move(parentDialog->x() + parentDialog->width() / 2 - width() / 2,
parentDialog->y() + parentDialog->height() / 2 - height() / 2);
}
}
void EditDialog::reject()
{
// We override this, to ensure the Escape key doesn't make the Edit Cell
// dock go away
return;
}
// Loads data from a cell into the Edit Cell window
void EditDialog::loadData(const QByteArray& data)
{
QImage img;
QString textData;
// Determine the data type, saving that info in the class variable
dataType = checkDataType(data);
// Get the current editor mode (eg text, hex, or image mode)
int editMode = ui->editorStack->currentIndex();
// Data type specific handling
switch (dataType) {
case Null:
switch (editMode) {
case TextEditor:
// Empty the text editor contents, then enable text editing
ui->editorText->clear();
ui->editorText->setEnabled(true);
// The text widget buffer is now the main data source
dataSource = TextBuffer;
break;
case HexEditor:
// Load the Null into the hex editor
hexEdit->setData(data);
// The hex widget buffer is now the main data source
dataSource = HexBuffer;
break;
case ImageViewer:
// Clear any image from the image viewing widget
ui->editorImage->setPixmap(QPixmap(0,0));
// Load the Null into the hex editor
hexEdit->setData(data);
// The hex widget buffer is now the main data source
dataSource = HexBuffer;
break;
}
break;
case Text:
switch (editMode) {
case TextEditor:
// Load the text into the text editor
textData = QString::fromUtf8(data.constData(), data.size());
ui->editorText->setPlainText(textData);
// Enable text editing
ui->editorText->setEnabled(true);
// Select all of the text by default
ui->editorText->selectAll();
// The text widget buffer is now the main data source
dataSource = TextBuffer;
break;
case HexEditor:
// Load the text into the hex editor
hexEdit->setData(data);
// The hex widget buffer is now the main data source
dataSource = HexBuffer;
break;
case ImageViewer:
// Clear any image from the image viewing widget
ui->editorImage->setPixmap(QPixmap(0,0));
// Load the text into the text editor
textData = QString::fromUtf8(data.constData(), data.size());
ui->editorText->setPlainText(textData);
// Enable text editing
ui->editorText->setEnabled(true);
// Select all of the text by default
ui->editorText->selectAll();
// The text widget buffer is now the main data source
dataSource = TextBuffer;
break;
}
break;
case Image:
// Image data is kept in the hex widget, mainly for safety. If we
// stored it in the editorImage widget instead, it would be a pixmap
// and there's no good way to restore that back to the original
// (pristine) image data. eg image metadata would be lost
hexEdit->setData(data);
// The hex widget buffer is now the main data source
dataSource = HexBuffer;
// Update the display if in text edit or image viewer mode
switch (editMode) {
case TextEditor:
// Disable text editing, and use a warning message as the contents
ui->editorText->setText(QString("<i>" %
tr("Image data can't be viewed with the text editor") %
"</i>"));
ui->editorText->setEnabled(false);
break;
case ImageViewer:
// Load the image into the image viewing widget
if (img.loadFromData(data)) {
ui->editorImage->setPixmap(QPixmap::fromImage(img));
}
break;
}
break;
default:
// The data seems to be general binary data, which is always loaded
// into the hex widget (the only safe place for it)
// Load the data into the hex editor
hexEdit->setData(data);
// The hex widget buffer is now the main data source
dataSource = HexBuffer;
switch (editMode) {
case TextEditor:
// Disable text editing, and use a warning message as the contents
ui->editorText->setText(QString("<i>" %
tr("Binary data can't be viewed with the text editor") %
"</i>"));
ui->editorText->setEnabled(false);
break;
case ImageViewer:
// Clear any image from the image viewing widget
ui->editorImage->setPixmap(QPixmap(0,0));
break;
}
}
}
void EditDialog::importData()
{
// Get list of supported image file formats to include them in the file dialog filter
QString image_formats;
QList<QByteArray> image_formats_list = QImageReader::supportedImageFormats();
for(int i=0;i<image_formats_list.size();++i)
image_formats.append(QString("*.%1 ").arg(QString::fromUtf8(image_formats_list.at(i))));
QString fileName = FileDialog::getOpenFileName(
this,
tr("Choose a file")
#ifndef Q_OS_MAC // Filters on OS X are buggy
, tr("Text files(*.txt);;Image files(%1);;All files(*)").arg(image_formats)
#endif
);
if(QFile::exists(fileName))
{
QFile file(fileName);
if(file.open(QIODevice::ReadOnly))
{
QByteArray d = file.readAll();
loadData(d);
file.close();
// Update the cell data info in the bottom left of the Edit Cell
updateCellInfo(d);
}
}
}
void EditDialog::exportData()
{
// Images get special treatment
QString fileExt;
if (dataType == Image) {
// Determine the likely filename extension
QByteArray cellData = hexEdit->data();
QBuffer imageBuffer(&cellData);
QImageReader imageReader(&imageBuffer);
QString imageFormat = imageReader.format();
fileExt = imageFormat.toUpper() % " " % tr("Image") % "(*." % imageFormat.toLower() % ");;All files(*)";
} else {
fileExt = tr("Text files(*.txt);;All files(*)");
}
QString fileName = FileDialog::getSaveFileName(
this,
tr("Choose a filename to export data"),
fileExt);
if(fileName.size() > 0)
{
QFile file(fileName);
if(file.open(QIODevice::WriteOnly))
{
if (dataSource == HexBuffer) {
// Data source is the hex buffer
file.write(hexEdit->data());
} else {
// Data source is the text buffer
file.write(ui->editorText->toPlainText().toUtf8());
}
file.close();
}
}
}
void EditDialog::setNull()
{
ui->editorText->clear();
ui->editorImage->clear();
hexEdit->setData(QByteArray());
dataType = Null;
// Check if in text editor mode
int editMode = ui->editorStack->currentIndex();
if (editMode == TextEditor) {
// Setting NULL in the text editor switches the data source to it
dataSource = TextBuffer;
// Ensure the text editor is enabled
ui->editorText->setEnabled(true);
// The text editor doesn't know the difference between an empty string
// and a NULL, so we need to record NULL outside of that
textNullSet = true;
}
// Update the cell data info in the bottom left of the Edit Cell
updateCellInfo(hexEdit->data());
ui->editorText->setFocus();
}
void EditDialog::updateApplyButton()
{
if (!isReadOnly)
ui->buttonApply->setEnabled(true);
}
void EditDialog::accept()
{
if(!currentIndex.isValid())
return;
if (dataSource == TextBuffer) {
// Check if a NULL is set in the text editor
if (textNullSet) {
emit recordTextUpdated(currentIndex, hexEdit->data(), true);
} else {
// It's not NULL, so proceed with normal text string checking
QString oldData = currentIndex.data(Qt::EditRole).toString();
QString newData = ui->editorText->toPlainText();
if (oldData != newData)
// The data is different, so commit it back to the database
emit recordTextUpdated(currentIndex, newData.toUtf8(), false);
}
} else {
// The data source is the hex widget buffer, thus binary data
QByteArray oldData = currentIndex.data(Qt::EditRole).toByteArray();
QByteArray newData = hexEdit->data();
if (newData != oldData)
emit recordTextUpdated(currentIndex, newData, true);
}
}
// Called when the user manually changes the "Mode" drop down combobox
void EditDialog::editModeChanged(int newMode)
{
// * If the dataSource is the text buffer, the data is always text *
if (dataSource == TextBuffer) {
switch (newMode) {
case TextEditor: // Switching to the text editor
// Nothing to do, as the text is already in the text buffer
break;
case HexEditor: // Switching to the hex editor
// Convert the text widget buffer for the hex widget
hexEdit->setData(ui->editorText->toPlainText().toUtf8());
// The hex widget buffer is now the main data source
dataSource = HexBuffer;
break;
case ImageViewer:
// Clear any image from the image viewing widget
ui->editorImage->setPixmap(QPixmap(0,0));
break;
}
// Switch to the selected editor
ui->editorStack->setCurrentIndex(newMode);
return;
}
// * If the dataSource is the hex buffer, the contents could be anything
// so we just pass it to our loadData() function to handle *
if (dataSource == HexBuffer) {
// Switch to the selected editor first, as loadData() relies on it
// being current
ui->editorStack->setCurrentIndex(newMode);
// Load the data into the appropriate widget, as done by loadData()
loadData(hexEdit->data());
}
}
// Called for every keystroke in the text editor (only)
void EditDialog::editTextChanged()
{
if (dataSource == TextBuffer) {
// Data has been changed in the text editor, so it can't be a NULL
// any more
textNullSet = false;
// Update the cell info in the bottom left manually. This is because
// updateCellInfo() only works with QByteArray's (for now)
int dataLength = ui->editorText->toPlainText().length();
ui->labelType->setText(tr("Type of data currently in cell: Text / Numeric"));
ui->labelSize->setText(tr("%n char(s)", "", dataLength));
}
}
// Determine the type of data in the cell
int EditDialog::checkDataType(const QByteArray& data)
{
QByteArray cellData = data;
// Check for NULL data type
if (cellData.isNull()) {
return Null;
}
// Check if it's an image
QBuffer imageBuffer(&cellData);
QImageReader readerBuffer(&imageBuffer);
bool someCheck = readerBuffer.canRead();
if (someCheck == true) {
return Image;
}
// Check if it's text only
if (QString(cellData).toUtf8() == cellData) { // Is there a better way to check this?
return Text;
}
// It's none of the above, so treat it as general binary data
return Binary;
}
void EditDialog::toggleOverwriteMode()
{
static bool currentMode = false;
currentMode = !currentMode;
hexEdit->setOverwriteMode(currentMode);
ui->editorText->setOverwriteMode(currentMode);
}
void EditDialog::setFocus()
{
QDialog::setFocus();
// Set the focus to the editor widget. The idea here is that setting focus
// to the dock itself doesn't make much sense as it's just a frame; you'd
// have to tab to the editor which is what you most likely want to use. So
// in order to save the user from doing this we explicitly set the focus
// to the editor.
ui->editorText->setFocus();
ui->editorText->selectAll();
}
// Enables or disables the Apply, Null, & Import buttons in the Edit Cell dock
void EditDialog::setReadOnly(bool ro)
{
isReadOnly = ro;
ui->buttonApply->setEnabled(!ro);
ui->buttonNull->setEnabled(!ro);
ui->buttonImport->setEnabled(!ro);
}
// Update the information labels in the bottom left corner of the dialog
void EditDialog::updateCellInfo(const QByteArray& data)
{
QByteArray cellData = data;
// Image data needs special treatment
if (dataType == Image) {
QBuffer imageBuffer(&cellData);
QImageReader imageReader(&imageBuffer);
// Display the image format
QString imageFormat = imageReader.format();
ui->labelType->setText(tr("Type of data currently in cell: %1 Image").arg(imageFormat.toUpper()));
// Display the image dimensions and size
QSize imageDimensions = imageReader.size();
int imageSize = cellData.size();
QString labelSizeText = tr("%1x%2 pixel(s)").arg(imageDimensions.width()).arg(imageDimensions.height()) + ", " + humanReadableSize(imageSize);
ui->labelSize->setText(labelSizeText);
return;
}
// Determine the length of the cell data
int dataLength = cellData.length();
// Use a switch statement for the other data types to keep things neat :)
switch (dataType) {
case Null:
// NULL data type
ui->labelType->setText(tr("Type of data currently in cell: NULL"));
ui->labelSize->setText(tr("%n byte(s)", "", dataLength));
break;
case Text:
// Text only
ui->labelType->setText(tr("Type of data currently in cell: Text / Numeric"));
ui->labelSize->setText(tr("%n char(s)", "", dataLength));
break;
default:
// If none of the above data types, consider it general binary data
ui->labelType->setText(tr("Type of data currently in cell: Binary"));
ui->labelSize->setText(tr("%n byte(s)", "", dataLength));
break;
}
}
QString EditDialog::humanReadableSize(double byteCount)
{
QList<QString> units;
units<<""<<"Ki"<<"Mi"<<"Gi"<<"Ti"<<"Pi"<<"Ei"<<"Zi";
foreach (QString unit, units)
{
if (fabs(byteCount) < 1024.0)
{
QString size = QString::number(byteCount, 'f', 2);
return size + " " + unit + "B";
}
byteCount /= 1024.0;
}
QString yiUnit = "Yi";
QString size = QString::number(byteCount, 'f', 2);
return size + " " + yiUnit + "B";
}