mirror of
https://github.com/sqlitebrowser/sqlitebrowser.git
synced 2026-05-13 07:19:48 -05:00
Merge branch 'master' of https://github.com/sqlitebrowser/sqlitebrowser
This commit is contained in:
@@ -144,6 +144,8 @@ set(SQLB_MOC_HDR
|
||||
src/FileExtensionManager.h
|
||||
src/CipherSettings.h
|
||||
src/DotenvFormat.h
|
||||
src/Palette.h
|
||||
src/CondFormat.h
|
||||
)
|
||||
|
||||
set(SQLB_SRC
|
||||
@@ -191,6 +193,8 @@ set(SQLB_SRC
|
||||
src/Data.cpp
|
||||
src/CipherSettings.cpp
|
||||
src/DotenvFormat.cpp
|
||||
src/Palette.cpp
|
||||
src/CondFormat.cpp
|
||||
)
|
||||
|
||||
set(SQLB_FORMS
|
||||
|
||||
@@ -90,6 +90,7 @@ Application::Application(int& argc, char** argv) :
|
||||
qWarning() << qPrintable(tr(" -s, --sql [file]\tExecute this SQL file after opening the DB"));
|
||||
qWarning() << qPrintable(tr(" -t, --table [table]\tBrowse this table after opening the DB"));
|
||||
qWarning() << qPrintable(tr(" -R, --read-only\tOpen database in read-only mode"));
|
||||
qWarning() << qPrintable(tr(" -o, --option [group/setting=value]\tRun application with this setting temporarily set to value"));
|
||||
qWarning() << qPrintable(tr(" -v, --version\t\tDisplay the current version"));
|
||||
qWarning() << qPrintable(tr(" [file]\t\tOpen this SQLite database"));
|
||||
m_dontShowMainWindow = true;
|
||||
@@ -113,6 +114,22 @@ Application::Application(int& argc, char** argv) :
|
||||
m_dontShowMainWindow = true;
|
||||
} else if(arguments().at(i) == "-R" || arguments().at(i) == "--read-only") {
|
||||
readOnly = true;
|
||||
} else if(arguments().at(i) == "-o" || arguments().at(i) == "--option") {
|
||||
const QString optionWarning = tr("The -o/--option option requires an argument in the form group/setting=value");
|
||||
if(++i >= arguments().size())
|
||||
qWarning() << qPrintable(optionWarning);
|
||||
else {
|
||||
QStringList option = arguments().at(i).split("=");
|
||||
if (option.size() != 2)
|
||||
qWarning() << qPrintable(optionWarning);
|
||||
else {
|
||||
QStringList setting = option.at(0).split("/");
|
||||
if (setting.size() != 2)
|
||||
qWarning() << qPrintable(optionWarning);
|
||||
else
|
||||
Settings::setValue(setting.at(0), setting.at(1), option.at(1), /* dont_save_to_disk */ true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Other: Check if it's a valid file name
|
||||
if(QFile::exists(arguments().at(i)))
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
#include "CondFormat.h"
|
||||
#include "Settings.h"
|
||||
#include "Data.h"
|
||||
|
||||
CondFormat::CondFormat(const QString& filter, const QColor& color, const QString& encoding)
|
||||
: m_filter(filter),
|
||||
m_color(color)
|
||||
{
|
||||
m_sqlCondition = filterToSqlCondition(filter, encoding);
|
||||
}
|
||||
|
||||
QString CondFormat::filterToSqlCondition(const QString& value, const QString& encoding)
|
||||
{
|
||||
// Check for any special comparison operators at the beginning of the value string. If there are none default to LIKE.
|
||||
QString op = "LIKE";
|
||||
QString val, val2;
|
||||
QString escape;
|
||||
bool numeric = false, ok = false;
|
||||
|
||||
// range/BETWEEN operator
|
||||
if (value.contains("~")) {
|
||||
int sepIdx = value.indexOf('~');
|
||||
val = value.mid(0, sepIdx);
|
||||
val2 = value.mid(sepIdx+1);
|
||||
val.toFloat(&ok);
|
||||
if (ok) {
|
||||
val2.toFloat(&ok);
|
||||
ok = ok && (val.toFloat() < val2.toFloat());
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
op = "BETWEEN";
|
||||
numeric = true;
|
||||
} else {
|
||||
val.clear();
|
||||
val2.clear();
|
||||
if(value.left(2) == ">=" || value.left(2) == "<=" || value.left(2) == "<>")
|
||||
{
|
||||
// Check if we're filtering for '<> NULL'. In this case we need a special comparison operator.
|
||||
if(value.left(2) == "<>" && value.mid(2) == "NULL")
|
||||
{
|
||||
// We are filtering for '<>NULL'. Override the comparison operator to search for NULL values in this column. Also treat search value (NULL) as number,
|
||||
// in order to avoid putting quotes around it.
|
||||
op = "IS NOT";
|
||||
numeric = true;
|
||||
val = "NULL";
|
||||
} else if(value.left(2) == "<>" && value.mid(2) == "''") {
|
||||
// We are filtering for "<>''", i.e. for everything which is not an empty string
|
||||
op = "<>";
|
||||
numeric = true;
|
||||
val = "''";
|
||||
} else {
|
||||
value.mid(2).toFloat(&numeric);
|
||||
op = value.left(2);
|
||||
val = value.mid(2);
|
||||
}
|
||||
} else if(value.left(1) == ">" || value.left(1) == "<") {
|
||||
value.mid(1).toFloat(&numeric);
|
||||
op = value.left(1);
|
||||
val = value.mid(1);
|
||||
} else if(value.left(1) == "=") {
|
||||
val = value.mid(1);
|
||||
|
||||
// Check if value to compare with is 'NULL'
|
||||
if(val != "NULL")
|
||||
{
|
||||
// It's not, so just compare normally to the value, whatever it is.
|
||||
op = "=";
|
||||
} else {
|
||||
// It is NULL. Override the comparison operator to search for NULL values in this column. Also treat search value (NULL) as number,
|
||||
// in order to avoid putting quotes around it.
|
||||
op = "IS";
|
||||
numeric = true;
|
||||
}
|
||||
} else {
|
||||
// Keep the default LIKE operator
|
||||
|
||||
// Set the escape character if one has been specified in the settings dialog
|
||||
QString escape_character = Settings::getValue("databrowser", "filter_escape").toString();
|
||||
if(escape_character == "'") escape_character = "''";
|
||||
if(escape_character.length())
|
||||
escape = QString("ESCAPE '%1'").arg(escape_character);
|
||||
|
||||
// Add % wildcards at the start and at the beginning of the filter query, but only if there weren't set any
|
||||
// wildcards manually. The idea is to assume that a user who's just typing characters expects the wildcards to
|
||||
// be added but a user who adds them herself knows what she's doing and doesn't want us to mess up her query.
|
||||
if(!value.contains("%"))
|
||||
{
|
||||
val = value;
|
||||
val.prepend('%');
|
||||
val.append('%');
|
||||
}
|
||||
}
|
||||
}
|
||||
if(val.isEmpty())
|
||||
val = value;
|
||||
|
||||
if(val == "" || val == "%" || val == "%%")
|
||||
return QString();
|
||||
else {
|
||||
// Quote and escape value, but only if it's not numeric and not the empty string sequence
|
||||
if(!numeric && val != "''")
|
||||
val = QString("'%1'").arg(val.replace("'", "''"));
|
||||
|
||||
QString whereClause(op + " " + QString(encodeString(val.toUtf8(), encoding)));
|
||||
if (!val2.isEmpty())
|
||||
whereClause += " AND " + QString(encodeString(val2.toUtf8(), encoding));
|
||||
whereClause += " " + escape;
|
||||
return whereClause;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef CONDFORMAT_H
|
||||
#define CONDFORMAT_H
|
||||
|
||||
#include <QString>
|
||||
#include <QColor>
|
||||
|
||||
// Conditional formatting for given format to table cells based on a specified condition.
|
||||
class CondFormat
|
||||
{
|
||||
public:
|
||||
CondFormat() {};
|
||||
explicit CondFormat(const QString& filter, const QColor& color, const QString& encoding = QString());
|
||||
|
||||
static QString filterToSqlCondition(const QString& value, const QString& encoding = QString());
|
||||
|
||||
private:
|
||||
QString m_sqlCondition;
|
||||
QString m_filter;
|
||||
QColor m_color;
|
||||
|
||||
public:
|
||||
QString sqlCondition() const { return m_sqlCondition; };
|
||||
QString filter() const { return m_filter; };
|
||||
QColor color() const { return m_color; };
|
||||
|
||||
};
|
||||
|
||||
#endif // CONDFORMAT_H
|
||||
+17
-2
@@ -21,8 +21,7 @@ bool isTextOnly(QByteArray data, const QString& encoding, bool quickTest)
|
||||
data = data.left(512);
|
||||
|
||||
// Convert to Unicode if necessary
|
||||
if(!encoding.isEmpty())
|
||||
data = QTextCodec::codecForName(encoding.toUtf8())->toUnicode(data).toUtf8();
|
||||
data = decodeString(data, encoding);
|
||||
|
||||
// Perform check
|
||||
return QString(data).toUtf8() == data;
|
||||
@@ -65,3 +64,19 @@ QStringList toStringList(const QList<QByteArray> list) {
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
QByteArray encodeString(const QByteArray& str, const QString& encoding)
|
||||
{
|
||||
if(encoding.isEmpty())
|
||||
return str;
|
||||
else
|
||||
return QTextCodec::codecForName(encoding.toUtf8())->fromUnicode(str);
|
||||
}
|
||||
|
||||
QByteArray decodeString(const QByteArray& str, const QString& encoding)
|
||||
{
|
||||
if(encoding.isEmpty())
|
||||
return str;
|
||||
else
|
||||
return QTextCodec::codecForName(encoding.toUtf8())->toUnicode(str).toUtf8();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define DATA_H
|
||||
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
|
||||
// This returns false if the data in the data parameter contains binary data. If it is text only, the function returns
|
||||
// true. If the second parameter is specified, it will be used to convert the data from the given encoding to Unicode
|
||||
@@ -19,4 +20,8 @@ QByteArray removeBom(QByteArray& data);
|
||||
|
||||
QStringList toStringList(const QList<QByteArray> list);
|
||||
|
||||
QByteArray encodeString(const QByteArray& str, const QString& encoding);
|
||||
|
||||
QByteArray decodeString(const QByteArray& str, const QString& encoding);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -339,6 +339,7 @@ void EditDialog::importData()
|
||||
break;
|
||||
}
|
||||
QString fileName = FileDialog::getOpenFileName(
|
||||
OpenDataFile,
|
||||
this,
|
||||
tr("Choose a file to import")
|
||||
#ifndef Q_OS_MAC // Filters on OS X are buggy
|
||||
@@ -403,6 +404,7 @@ void EditDialog::exportData()
|
||||
|
||||
QString selectedFilter = filters.first();
|
||||
QString fileName = FileDialog::getSaveFileName(
|
||||
CreateDataFile,
|
||||
this,
|
||||
tr("Choose a filename to export data"),
|
||||
filters.join(";;"),
|
||||
|
||||
@@ -307,6 +307,7 @@ void ExportDataDialog::accept()
|
||||
{
|
||||
// called from sqlexecute query tab
|
||||
QString sFilename = FileDialog::getSaveFileName(
|
||||
CreateDataFile,
|
||||
this,
|
||||
tr("Choose a filename to export data"),
|
||||
file_dialog_filter);
|
||||
@@ -333,6 +334,7 @@ void ExportDataDialog::accept()
|
||||
if(selectedItems.size() == 1)
|
||||
{
|
||||
QString fileName = FileDialog::getSaveFileName(
|
||||
CreateDataFile,
|
||||
this,
|
||||
tr("Choose a filename to export data"),
|
||||
file_dialog_filter,
|
||||
@@ -347,6 +349,7 @@ void ExportDataDialog::accept()
|
||||
} else {
|
||||
// ask for folder
|
||||
QString exportfolder = FileDialog::getExistingDirectory(
|
||||
CreateDataFile,
|
||||
this,
|
||||
tr("Choose a directory"),
|
||||
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
||||
|
||||
@@ -83,6 +83,7 @@ void ExportSqlDialog::accept()
|
||||
defaultFileName = pdb->currentFile() + ".sql";;
|
||||
|
||||
QString fileName = FileDialog::getSaveFileName(
|
||||
CreateSQLFile,
|
||||
this,
|
||||
tr("Choose a filename to export"),
|
||||
tr("Text files(*.sql *.txt)"),
|
||||
|
||||
+24
-18
@@ -1,52 +1,55 @@
|
||||
#include "FileDialog.h"
|
||||
#include "Settings.h"
|
||||
|
||||
QString FileDialog::getOpenFileName(QWidget* parent, const QString& caption, const QString &filter, QString *selectedFilter, Options options)
|
||||
QString FileDialog::getOpenFileName(const FileDialogTypes dialogType, QWidget* parent, const QString& caption, const QString &filter, QString *selectedFilter, Options options)
|
||||
{
|
||||
QString result = QFileDialog::getOpenFileName(parent, caption, getFileDialogPath(), filter, selectedFilter, options);
|
||||
QString result = QFileDialog::getOpenFileName(parent, caption, getFileDialogPath(dialogType), filter, selectedFilter, options);
|
||||
if(!result.isEmpty())
|
||||
setFileDialogPath(result);
|
||||
setFileDialogPath(dialogType, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList FileDialog::getOpenFileNames(QWidget *parent, const QString &caption, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
|
||||
QStringList FileDialog::getOpenFileNames(const FileDialogTypes dialogType, QWidget *parent, const QString &caption, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
|
||||
{
|
||||
QStringList result = QFileDialog::getOpenFileNames(parent, caption, getFileDialogPath(), filter, selectedFilter, options);
|
||||
QStringList result = QFileDialog::getOpenFileNames(parent, caption, getFileDialogPath(dialogType), filter, selectedFilter, options);
|
||||
if(!result.isEmpty())
|
||||
{
|
||||
QFileInfo path = QFileInfo(result.first());
|
||||
setFileDialogPath(path.absolutePath());
|
||||
setFileDialogPath(dialogType, path.absolutePath());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString FileDialog::getSaveFileName(QWidget* parent, const QString& caption, const QString& filter, const QString& defaultFileName, QString* selectedFilter, Options options)
|
||||
QString FileDialog::getSaveFileName(const FileDialogTypes dialogType, QWidget* parent, const QString& caption, const QString& filter, const QString& defaultFileName, QString* selectedFilter, Options options)
|
||||
{
|
||||
QString dir = getFileDialogPath();
|
||||
QString dir = getFileDialogPath(dialogType);
|
||||
if(!defaultFileName.isEmpty())
|
||||
dir += "/" + defaultFileName;
|
||||
|
||||
QString result = QFileDialog::getSaveFileName(parent, caption, dir, filter, selectedFilter, options);
|
||||
if(!result.isEmpty())
|
||||
setFileDialogPath(result);
|
||||
setFileDialogPath(dialogType, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
QString FileDialog::getExistingDirectory(QWidget* parent, const QString& caption, Options options)
|
||||
QString FileDialog::getExistingDirectory(const FileDialogTypes dialogType, QWidget* parent, const QString& caption, Options options)
|
||||
{
|
||||
QString result = QFileDialog::getExistingDirectory(parent, caption, getFileDialogPath(), options);
|
||||
QString result = QFileDialog::getExistingDirectory(parent, caption, getFileDialogPath(dialogType), options);
|
||||
if(!result.isEmpty())
|
||||
setFileDialogPath(result);
|
||||
setFileDialogPath(dialogType, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
QString FileDialog::getFileDialogPath()
|
||||
QString FileDialog::getFileDialogPath(const FileDialogTypes dialogType)
|
||||
{
|
||||
switch(Settings::getValue("db", "savedefaultlocation").toInt())
|
||||
{
|
||||
case 0: // Remember last location
|
||||
case 2: // Remember last location for current session only
|
||||
return Settings::getValue("db", "lastlocation").toString();
|
||||
case 2: { // Remember last location for current session only
|
||||
QHash<QString, QVariant> lastLocations = Settings::getValue("db", "lastlocations").toHash();
|
||||
|
||||
return lastLocations[QString(dialogType)].toString();
|
||||
}
|
||||
case 1: // Always use this locations
|
||||
return Settings::getValue("db", "defaultlocation").toString();
|
||||
default:
|
||||
@@ -54,17 +57,20 @@ QString FileDialog::getFileDialogPath()
|
||||
}
|
||||
}
|
||||
|
||||
void FileDialog::setFileDialogPath(const QString& new_path)
|
||||
void FileDialog::setFileDialogPath(const FileDialogTypes dialogType, const QString& new_path)
|
||||
{
|
||||
QString dir = QFileInfo(new_path).absolutePath();
|
||||
QHash<QString, QVariant> lastLocations = Settings::getValue("db", "lastlocations").toHash();
|
||||
|
||||
lastLocations[QString(dialogType)] = dir;
|
||||
|
||||
switch(Settings::getValue("db", "savedefaultlocation").toInt())
|
||||
{
|
||||
case 0: // Remember last location
|
||||
Settings::setValue("db", "lastlocation", dir);
|
||||
Settings::setValue("db", "lastlocations", lastLocations);
|
||||
break;
|
||||
case 2: // Remember last location for current session only
|
||||
Settings::setValue("db", "lastlocation", dir, true);
|
||||
Settings::setValue("db", "lastlocations", lastLocations, true);
|
||||
break;
|
||||
case 1: // Always use this locations
|
||||
break; // Do nothing
|
||||
|
||||
+30
-6
@@ -3,28 +3,52 @@
|
||||
|
||||
#include <QFileDialog>
|
||||
|
||||
enum FileDialogTypes {
|
||||
NoSpecificType,
|
||||
|
||||
CreateProjectFile,
|
||||
OpenProjectFile,
|
||||
|
||||
CreateDatabaseFile,
|
||||
OpenDatabaseFile,
|
||||
|
||||
CreateSQLFile,
|
||||
OpenSQLFile,
|
||||
|
||||
OpenCSVFile,
|
||||
|
||||
CreateDataFile,
|
||||
OpenDataFile,
|
||||
|
||||
OpenExtensionFile,
|
||||
OpenCertificateFile,
|
||||
|
||||
// ImportTable,
|
||||
// ExportTable,
|
||||
};
|
||||
|
||||
class FileDialog : public QFileDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static QString getOpenFileName(QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
static QString getOpenFileName(const FileDialogTypes dialogType, QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
const QString& filter = QString(), QString* selectedFilter = nullptr,
|
||||
Options options = 0);
|
||||
static QStringList getOpenFileNames(QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
static QStringList getOpenFileNames(const FileDialogTypes dialogType, QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
const QString& filter = QString(), QString* selectedFilter = nullptr,
|
||||
Options options = 0);
|
||||
static QString getSaveFileName(QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
static QString getSaveFileName(const FileDialogTypes dialogType, QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
const QString& filter = QString(), const QString& defaultFileName = QString(), QString* selectedFilter = nullptr,
|
||||
Options options = 0);
|
||||
static QString getExistingDirectory(QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
static QString getExistingDirectory(const FileDialogTypes dialogType, QWidget* parent = nullptr, const QString& caption = QString(),
|
||||
Options options = 0);
|
||||
|
||||
static QString getSqlDatabaseFileFilter();
|
||||
|
||||
private:
|
||||
static QString getFileDialogPath();
|
||||
static void setFileDialogPath(const QString& new_path);
|
||||
static QString getFileDialogPath(const FileDialogTypes dialogType);
|
||||
static void setFileDialogPath(const FileDialogTypes dialogType, const QString& new_path);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -107,6 +107,15 @@ void FilterLineEdit::showContextMenu(const QPoint &pos)
|
||||
// This has to be created here, otherwise the set of enabled options would not update accordingly.
|
||||
QMenu* editContextMenu = createStandardContextMenu();
|
||||
editContextMenu->addSeparator();
|
||||
QString conditionalFormatLabel = text().isEmpty() ? tr("Clear All Conditional Formats") : tr("Use for Conditional Format");
|
||||
QAction* conditionalFormatAction = new QAction(conditionalFormatLabel, editContextMenu);
|
||||
connect(conditionalFormatAction, &QAction::triggered, [&]() {
|
||||
if (text().isEmpty())
|
||||
emit clearAllCondFormats();
|
||||
else
|
||||
emit addFilterAsCondFormat(text());
|
||||
});
|
||||
editContextMenu->addSeparator();
|
||||
|
||||
QMenu* filterMenu = editContextMenu->addMenu(tr("Set Filter Expression"));
|
||||
|
||||
@@ -164,6 +173,8 @@ void FilterLineEdit::showContextMenu(const QPoint &pos)
|
||||
setFilterHelper(QString ("?~"));
|
||||
});
|
||||
|
||||
editContextMenu->addAction(conditionalFormatAction);
|
||||
|
||||
filterMenu->addAction(whatsThisAction);
|
||||
filterMenu->addSeparator();
|
||||
filterMenu->addAction(isNullAction);
|
||||
|
||||
@@ -23,6 +23,8 @@ private slots:
|
||||
|
||||
signals:
|
||||
void delayedTextChanged(QString text);
|
||||
void addFilterAsCondFormat(QString text);
|
||||
void clearAllCondFormats();
|
||||
|
||||
protected:
|
||||
void keyReleaseEvent(QKeyEvent* event) override;
|
||||
|
||||
@@ -36,6 +36,8 @@ void FilterTableHeader::generateFilters(int number, bool showFirst)
|
||||
else
|
||||
l->setVisible(true);
|
||||
connect(l, SIGNAL(delayedTextChanged(QString)), this, SLOT(inputChanged(QString)));
|
||||
connect(l, SIGNAL(addFilterAsCondFormat(QString)), this, SLOT(addFilterAsCondFormat(QString)));
|
||||
connect(l, SIGNAL(clearAllCondFormats()), this, SLOT(clearAllCondFormats()));
|
||||
filterWidgets.push_back(l);
|
||||
}
|
||||
|
||||
@@ -86,6 +88,18 @@ void FilterTableHeader::inputChanged(const QString& new_value)
|
||||
emit filterChanged(sender()->property("column").toInt(), new_value);
|
||||
}
|
||||
|
||||
void FilterTableHeader::addFilterAsCondFormat(const QString& filter)
|
||||
{
|
||||
// Just get the column number and the new value and send them to anybody interested in new conditional formatting
|
||||
emit addCondFormat(sender()->property("column").toInt(), filter);
|
||||
}
|
||||
|
||||
void FilterTableHeader::clearAllCondFormats()
|
||||
{
|
||||
// Just get the column number and send it to anybody responsible or interested in clearing conditional formatting
|
||||
emit clearAllCondFormats(sender()->property("column").toInt());
|
||||
}
|
||||
|
||||
void FilterTableHeader::clearFilters()
|
||||
{
|
||||
for(FilterLineEdit* filterLineEdit : filterWidgets)
|
||||
|
||||
@@ -25,12 +25,16 @@ public slots:
|
||||
|
||||
signals:
|
||||
void filterChanged(int column, QString value);
|
||||
void addCondFormat(int column, QString filter);
|
||||
void clearAllCondFormats(int column);
|
||||
|
||||
protected:
|
||||
void updateGeometries() override;
|
||||
|
||||
private slots:
|
||||
void inputChanged(const QString& new_value);
|
||||
void addFilterAsCondFormat(const QString& filter);
|
||||
void clearAllCondFormats();
|
||||
|
||||
private:
|
||||
QList<FilterLineEdit*> filterWidgets;
|
||||
|
||||
+28
-6
@@ -100,10 +100,15 @@ namespace {
|
||||
void rollback(
|
||||
ImportCsvDialog* dialog,
|
||||
DBBrowserDB* pdb,
|
||||
DBBrowserDB::db_pointer_type* db_ptr,
|
||||
const QString& savepointName,
|
||||
size_t nRecord,
|
||||
const QString& message)
|
||||
{
|
||||
// Release DB handle. This needs to be done before calling revertToSavepoint as that function needs to be able to acquire its own handle.
|
||||
if(db_ptr)
|
||||
*db_ptr = nullptr;
|
||||
|
||||
QApplication::restoreOverrideCursor(); // restore original cursor
|
||||
if(!message.isEmpty())
|
||||
{
|
||||
@@ -533,7 +538,7 @@ bool ImportCsvDialog::importCsv(const QString& fileName, const QString& name)
|
||||
QString restorepointName = pdb->generateSavepointName("csvimport");
|
||||
if(!pdb->setSavepoint(restorepointName))
|
||||
{
|
||||
rollback(this, pdb, restorepointName, 0, tr("Creating restore point failed: %1").arg(pdb->lastError()));
|
||||
rollback(this, pdb, nullptr, restorepointName, 0, tr("Creating restore point failed: %1").arg(pdb->lastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -546,7 +551,7 @@ bool ImportCsvDialog::importCsv(const QString& fileName, const QString& name)
|
||||
{
|
||||
if(!pdb->createTable(sqlb::ObjectIdentifier("main", tableName), fieldList))
|
||||
{
|
||||
rollback(this, pdb, restorepointName, 0, tr("Creating the table failed: %1").arg(pdb->lastError()));
|
||||
rollback(this, pdb, nullptr, restorepointName, 0, tr("Creating the table failed: %1").arg(pdb->lastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -601,7 +606,7 @@ bool ImportCsvDialog::importCsv(const QString& fileName, const QString& name)
|
||||
}
|
||||
|
||||
// Prepare the INSERT statement. The prepared statement can then be reused for each row to insert
|
||||
QString sQuery = QString("INSERT INTO %1 VALUES(").arg(sqlb::escapeIdentifier(tableName));
|
||||
QString sQuery = QString("INSERT %1 INTO %2 VALUES(").arg(currentOnConflictStrategy()).arg(sqlb::escapeIdentifier(tableName));
|
||||
for(size_t i=1;i<=fieldList.size();i++)
|
||||
sQuery.append(QString("?%1,").arg(i));
|
||||
sQuery.chop(1); // Remove last comma
|
||||
@@ -672,13 +677,15 @@ bool ImportCsvDialog::importCsv(const QString& fileName, const QString& name)
|
||||
// Some error occurred or the user cancelled the action
|
||||
|
||||
// Rollback the entire import. If the action was cancelled, don't show an error message. If it errored, show an error message.
|
||||
sqlite3_finalize(stmt);
|
||||
if(result == CSVParser::ParserResult::ParserResultCancelled)
|
||||
{
|
||||
rollback(this, pdb, restorepointName, 0, QString());
|
||||
sqlite3_finalize(stmt);
|
||||
rollback(this, pdb, &pDb, restorepointName, 0, QString());
|
||||
return false;
|
||||
} else {
|
||||
rollback(this, pdb, restorepointName, lastRowNum, tr("Inserting row failed: %1").arg(pdb->lastError()));
|
||||
QString error(sqlite3_errmsg(pDb.get()));
|
||||
sqlite3_finalize(stmt);
|
||||
rollback(this, pdb, &pDb, restorepointName, lastRowNum, tr("Inserting row failed: %1").arg(error));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -773,6 +780,19 @@ QString ImportCsvDialog::currentEncoding() const
|
||||
return ui->comboEncoding->currentText();
|
||||
}
|
||||
|
||||
QString ImportCsvDialog::currentOnConflictStrategy() const
|
||||
{
|
||||
switch(ui->comboOnConflictStrategy->currentIndex())
|
||||
{
|
||||
case 1:
|
||||
return "OR IGNORE";
|
||||
case 2:
|
||||
return "OR REPLACE";
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
void ImportCsvDialog::toggleAdvancedSection(bool show)
|
||||
{
|
||||
ui->labelNoTypeDetection->setVisible(show);
|
||||
@@ -781,4 +801,6 @@ void ImportCsvDialog::toggleAdvancedSection(bool show)
|
||||
ui->checkFailOnMissing->setVisible(show);
|
||||
ui->labelIgnoreDefaults->setVisible(show);
|
||||
ui->checkIgnoreDefaults->setVisible(show);
|
||||
ui->labelOnConflictStrategy->setVisible(show);
|
||||
ui->comboOnConflictStrategy->setVisible(show);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ private:
|
||||
|
||||
void setEncoding(const QString& sEnc);
|
||||
QString currentEncoding() const;
|
||||
|
||||
QString currentOnConflictStrategy() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+67
-30
@@ -292,14 +292,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<item row="11" column="1">
|
||||
<widget class="QCheckBox" name="checkFailOnMissing">
|
||||
<property name="toolTip">
|
||||
<string>Activate this option to stop the import when trying to import an empty value into a NOT NULL column without a default value.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="labelFailOnMissing">
|
||||
<property name="text">
|
||||
<string>Fail on missing values </string>
|
||||
@@ -314,6 +314,9 @@
|
||||
<property name="text">
|
||||
<string>Disable data type detection</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>checkNoTypeDetection</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
@@ -323,6 +326,38 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<widget class="QComboBox" name="comboOnConflictStrategy">
|
||||
<property name="toolTip">
|
||||
<string>When importing into an existing table with a primary key, unique constraints or a unique index there is a chance for a conflict. This option allows you to select a strategy for that case: By default the import is aborted and rolled back but you can also choose to ignore and not import conflicting rows or to replace the existing row in the table.</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Abort import</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Ignore row</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Replace existing row</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="labelOnConflictStrategy">
|
||||
<property name="text">
|
||||
<string>Conflict strategy</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>comboOnConflictStrategy</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
@@ -432,7 +467,9 @@
|
||||
<tabstop>checkBoxSeparateTables</tabstop>
|
||||
<tabstop>buttonAdvanced</tabstop>
|
||||
<tabstop>checkIgnoreDefaults</tabstop>
|
||||
<tabstop>checkNoTypeDetection</tabstop>
|
||||
<tabstop>checkFailOnMissing</tabstop>
|
||||
<tabstop>comboOnConflictStrategy</tabstop>
|
||||
<tabstop>filePicker</tabstop>
|
||||
<tabstop>toggleSelected</tabstop>
|
||||
<tabstop>matchSimilar</tabstop>
|
||||
@@ -449,8 +486,8 @@
|
||||
<slot>updatePreview()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>232</x>
|
||||
<y>99</y>
|
||||
<x>245</x>
|
||||
<y>92</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>445</x>
|
||||
@@ -481,8 +518,8 @@
|
||||
<slot>updatePreview()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>478</x>
|
||||
<y>99</y>
|
||||
<x>511</x>
|
||||
<y>92</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>577</x>
|
||||
@@ -497,8 +534,8 @@
|
||||
<slot>updatePreview()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>478</x>
|
||||
<y>132</y>
|
||||
<x>511</x>
|
||||
<y>126</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>530</x>
|
||||
@@ -513,8 +550,8 @@
|
||||
<slot>updatePreview()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>495</x>
|
||||
<y>165</y>
|
||||
<x>524</x>
|
||||
<y>160</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>540</x>
|
||||
@@ -529,8 +566,8 @@
|
||||
<slot>updatePreview()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>184</x>
|
||||
<y>191</y>
|
||||
<x>192</x>
|
||||
<y>182</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>368</x>
|
||||
@@ -545,8 +582,8 @@
|
||||
<slot>updatePreview()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>263</x>
|
||||
<y>183</y>
|
||||
<x>271</x>
|
||||
<y>160</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>572</x>
|
||||
@@ -561,8 +598,8 @@
|
||||
<slot>updatePreview()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>184</x>
|
||||
<y>60</y>
|
||||
<x>192</x>
|
||||
<y>56</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>354</x>
|
||||
@@ -577,8 +614,8 @@
|
||||
<slot>updateSelection(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>780</x>
|
||||
<y>337</y>
|
||||
<x>777</x>
|
||||
<y>385</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>368</x>
|
||||
@@ -593,8 +630,8 @@
|
||||
<slot>updatePreview()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>232</x>
|
||||
<y>132</y>
|
||||
<x>245</x>
|
||||
<y>126</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>350</x>
|
||||
@@ -609,8 +646,8 @@
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>272</x>
|
||||
<y>677</y>
|
||||
<x>281</x>
|
||||
<y>707</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
@@ -625,8 +662,8 @@
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>340</x>
|
||||
<y>677</y>
|
||||
<x>349</x>
|
||||
<y>707</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
@@ -641,8 +678,8 @@
|
||||
<slot>checkInput()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>194</x>
|
||||
<y>236</y>
|
||||
<x>192</x>
|
||||
<y>206</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>368</x>
|
||||
@@ -657,8 +694,8 @@
|
||||
<slot>matchSimilar()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>780</x>
|
||||
<y>378</y>
|
||||
<x>777</x>
|
||||
<y>418</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>368</x>
|
||||
@@ -673,8 +710,8 @@
|
||||
<slot>toggleAdvancedSection(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>214</x>
|
||||
<y>259</y>
|
||||
<x>265</x>
|
||||
<y>241</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>393</x>
|
||||
|
||||
+160
-27
@@ -27,6 +27,7 @@
|
||||
#include "RemoteDatabase.h"
|
||||
#include "FindReplaceDialog.h"
|
||||
#include "Data.h"
|
||||
#include "CondFormat.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QApplication>
|
||||
@@ -76,6 +77,28 @@ QDataStream& operator>>(QDataStream& ds, sqlb::ObjectIdentifier& objid)
|
||||
return ds;
|
||||
}
|
||||
|
||||
// These are temporary helper functions to turn a vector of sorted columns into a single column to sort and vice verse. This is done by just taking the
|
||||
// first sort column there is and ignoring all the others or creating a single item vector respectively. These functions can be removed once all parts
|
||||
// of the application have been converted to deal with vectors of sorted columns.
|
||||
void fromSortOrderVector(const QVector<BrowseDataTableSettings::SortedColumn>& vector, int& index, Qt::SortOrder& mode)
|
||||
{
|
||||
if(vector.size())
|
||||
{
|
||||
index = vector.at(0).index;
|
||||
mode = vector.at(0).mode;
|
||||
} else {
|
||||
index = 0;
|
||||
mode = Qt::AscendingOrder;
|
||||
}
|
||||
}
|
||||
QVector<BrowseDataTableSettings::SortedColumn> toSortOrderVector(int index, Qt::SortOrder mode)
|
||||
{
|
||||
QVector<BrowseDataTableSettings::SortedColumn> vector;
|
||||
vector.push_back(BrowseDataTableSettings::SortedColumn(index, mode));
|
||||
return vector;
|
||||
}
|
||||
|
||||
|
||||
MainWindow::MainWindow(QWidget* parent)
|
||||
: QMainWindow(parent),
|
||||
ui(new Ui::MainWindow),
|
||||
@@ -127,6 +150,8 @@ void MainWindow::init()
|
||||
|
||||
// Set up filters
|
||||
connect(ui->dataTable->filterHeader(), SIGNAL(filterChanged(int,QString)), this, SLOT(updateFilter(int,QString)));
|
||||
connect(ui->dataTable->filterHeader(), SIGNAL(addCondFormat(int,QString)), this, SLOT(addCondFormat(int,QString)));
|
||||
connect(ui->dataTable->filterHeader(), SIGNAL(clearAllCondFormats(int)), this, SLOT(clearAllCondFormats(int)));
|
||||
connect(m_browseTableModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataTableSelectionChanged(QModelIndex)));
|
||||
|
||||
// Select in table the rows correspoding to the selected points in plot
|
||||
@@ -410,6 +435,7 @@ bool MainWindow::fileOpen(const QString& fileName, bool dontAddToRecentFiles, bo
|
||||
if (!QFile::exists(wFile))
|
||||
{
|
||||
wFile = FileDialog::getOpenFileName(
|
||||
OpenDatabaseFile,
|
||||
this,
|
||||
tr("Choose a database file")
|
||||
#ifndef Q_OS_MAC // Filters on OS X are buggy
|
||||
@@ -463,9 +489,11 @@ bool MainWindow::fileOpen(const QString& fileName, bool dontAddToRecentFiles, bo
|
||||
|
||||
void MainWindow::fileNew()
|
||||
{
|
||||
QString fileName = FileDialog::getSaveFileName(this,
|
||||
tr("Choose a filename to save under"),
|
||||
FileDialog::getSqlDatabaseFileFilter());
|
||||
QString fileName = FileDialog::getSaveFileName(
|
||||
CreateDatabaseFile,
|
||||
this,
|
||||
tr("Choose a filename to save under"),
|
||||
FileDialog::getSqlDatabaseFileFilter());
|
||||
if(!fileName.isEmpty())
|
||||
{
|
||||
if(QFile::exists(fileName))
|
||||
@@ -648,10 +676,14 @@ void MainWindow::populateTable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int sortOrderIndex;
|
||||
Qt::SortOrder sortOrderMode;
|
||||
fromSortOrderVector(storedData.sortOrder, sortOrderIndex, sortOrderMode);
|
||||
if(only_defaults)
|
||||
m_browseTableModel->setTable(tablename, storedData.sortOrderIndex, storedData.sortOrderMode, storedData.filterValues);
|
||||
m_browseTableModel->setTable(tablename, sortOrderIndex, sortOrderMode, storedData.filterValues);
|
||||
else
|
||||
m_browseTableModel->setTable(tablename, storedData.sortOrderIndex, storedData.sortOrderMode, storedData.filterValues, v);
|
||||
m_browseTableModel->setTable(tablename, sortOrderIndex, sortOrderMode, storedData.filterValues, v);
|
||||
|
||||
// There is information stored for this table, so extract it and apply it
|
||||
applyBrowseTableSettings(storedData);
|
||||
@@ -703,7 +735,10 @@ void MainWindow::applyBrowseTableSettings(BrowseDataTableSettings storedData, bo
|
||||
ui->dataTable->setColumnWidth(widthIt.key(), widthIt.value());
|
||||
|
||||
// Sorting
|
||||
ui->dataTable->filterHeader()->setSortIndicator(storedData.sortOrderIndex, storedData.sortOrderMode);
|
||||
int sortOrderIndex;
|
||||
Qt::SortOrder sortOrderMode;
|
||||
fromSortOrderVector(storedData.sortOrder, sortOrderIndex, sortOrderMode);
|
||||
ui->dataTable->filterHeader()->setSortIndicator(sortOrderIndex, sortOrderMode);
|
||||
|
||||
// Filters
|
||||
if(!skipFilters)
|
||||
@@ -713,7 +748,12 @@ void MainWindow::applyBrowseTableSettings(BrowseDataTableSettings storedData, bo
|
||||
bool oldState = filterHeader->blockSignals(true);
|
||||
for(auto filterIt=storedData.filterValues.constBegin();filterIt!=storedData.filterValues.constEnd();++filterIt)
|
||||
filterHeader->setFilter(filterIt.key(), filterIt.value());
|
||||
filterHeader->blockSignals(oldState);
|
||||
|
||||
// Conditional formats
|
||||
for(auto formatIt=storedData.condFormats.constBegin(); formatIt!=storedData.condFormats.constEnd(); ++formatIt)
|
||||
m_browseTableModel->setCondFormats(formatIt.key(), formatIt.value());
|
||||
|
||||
filterHeader->blockSignals(oldState);
|
||||
}
|
||||
|
||||
// Encoding
|
||||
@@ -1298,6 +1338,13 @@ void MainWindow::executeQuery()
|
||||
{
|
||||
// What type of query is this?
|
||||
QString qtail = QString(tail).trimmed();
|
||||
// Remove trailing comments so we don't get fooled by some trailing text at the end of the stream.
|
||||
// Otherwise we'll pass them to SQLite and its execution will trigger a savepoint that wouldn't be
|
||||
// reverted.
|
||||
SqliteTableModel::removeCommentsFromQuery(qtail);
|
||||
if (qtail.isEmpty())
|
||||
break;
|
||||
|
||||
StatementType query_type = getQueryType(qtail);
|
||||
|
||||
// Check whether the DB structure is changed by this statement
|
||||
@@ -1521,9 +1568,10 @@ void MainWindow::mainTabSelected(int tabindex)
|
||||
void MainWindow::importTableFromCSV()
|
||||
{
|
||||
QStringList wFiles = FileDialog::getOpenFileNames(
|
||||
this,
|
||||
tr("Choose text files"),
|
||||
tr("Text files(*.csv *.txt);;All files(*)"));
|
||||
OpenCSVFile,
|
||||
this,
|
||||
tr("Choose text files"),
|
||||
tr("Text files(*.csv *.txt);;All files(*)"));
|
||||
|
||||
QStringList validFiles;
|
||||
for(const auto& file : wFiles) {
|
||||
@@ -1629,6 +1677,7 @@ void MainWindow::importDatabaseFromSQL()
|
||||
{
|
||||
// Get file name to import
|
||||
QString fileName = FileDialog::getOpenFileName(
|
||||
OpenSQLFile,
|
||||
this,
|
||||
tr("Choose a file to import"),
|
||||
tr("Text files(*.sql *.txt);;All files(*)"));
|
||||
@@ -1646,6 +1695,7 @@ void MainWindow::importDatabaseFromSQL()
|
||||
QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) || !db.isOpen())
|
||||
{
|
||||
newDbFile = FileDialog::getSaveFileName(
|
||||
CreateDatabaseFile,
|
||||
this,
|
||||
tr("Choose a filename to save under"),
|
||||
FileDialog::getSqlDatabaseFileFilter());
|
||||
@@ -1932,9 +1982,12 @@ void MainWindow::browseTableHeaderClicked(int logicalindex)
|
||||
|
||||
// instead of the column name we just use the column index, +2 because 'rowid, *' is the projection
|
||||
BrowseDataTableSettings& settings = browseTableSettings[currentlyBrowsedTableName()];
|
||||
settings.sortOrderIndex = logicalindex;
|
||||
settings.sortOrderMode = settings.sortOrderMode == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder;
|
||||
ui->dataTable->sortByColumn(settings.sortOrderIndex, settings.sortOrderMode);
|
||||
int dummy;
|
||||
Qt::SortOrder order;
|
||||
fromSortOrderVector(settings.sortOrder, dummy, order);
|
||||
order = order == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder;
|
||||
settings.sortOrder = toSortOrderVector(logicalindex, order);
|
||||
ui->dataTable->sortByColumn(logicalindex, order);
|
||||
|
||||
// select the first item in the column so the header is bold
|
||||
// we might try to select the last selected item
|
||||
@@ -2109,6 +2162,7 @@ void MainWindow::changeSqlTab(int /*index*/)
|
||||
void MainWindow::openSqlFile()
|
||||
{
|
||||
QString file = FileDialog::getOpenFileName(
|
||||
OpenSQLFile,
|
||||
this,
|
||||
tr("Select SQL file to open"),
|
||||
tr("Text files(*.sql *.txt);;All files(*)"));
|
||||
@@ -2169,6 +2223,7 @@ void MainWindow::saveSqlFileAs()
|
||||
return;
|
||||
|
||||
QString file = FileDialog::getSaveFileName(
|
||||
CreateSQLFile,
|
||||
this,
|
||||
tr("Select file name"),
|
||||
tr("Text files(*.sql *.txt);;All files(*)"));
|
||||
@@ -2194,6 +2249,7 @@ void MainWindow::saveSqlResultsAsView()
|
||||
void MainWindow::loadExtension()
|
||||
{
|
||||
QString file = FileDialog::getOpenFileName(
|
||||
OpenExtensionFile,
|
||||
this,
|
||||
tr("Select extension file"),
|
||||
tr("Extensions(*.so *.dll);;All files(*)"));
|
||||
@@ -2405,15 +2461,33 @@ void MainWindow::updateBrowseDataColumnWidth(int section, int /*old_size*/, int
|
||||
|
||||
static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, QXmlStreamReader& xml)
|
||||
{
|
||||
settings.sortOrderIndex = xml.attributes().value("sort_order_index").toInt();
|
||||
settings.sortOrderMode = static_cast<Qt::SortOrder>(xml.attributes().value("sort_order_mode").toInt());
|
||||
// TODO Remove this in the near future. This file format was only created temporarily by the nightlies from the late 3.11 development period.
|
||||
if(xml.attributes().hasAttribute("sort_order_index"))
|
||||
{
|
||||
int sortOrderIndex = xml.attributes().value("sort_order_index").toInt();
|
||||
Qt::SortOrder sortOrderMode = static_cast<Qt::SortOrder>(xml.attributes().value("sort_order_mode").toInt());
|
||||
settings.sortOrder = toSortOrderVector(sortOrderIndex, sortOrderMode);
|
||||
}
|
||||
|
||||
settings.showRowid = xml.attributes().value("show_row_id").toInt();
|
||||
settings.encoding = xml.attributes().value("encoding").toString();
|
||||
settings.plotXAxis = xml.attributes().value("plot_x_axis").toString();
|
||||
settings.unlockViewPk = xml.attributes().value("unlock_view_pk").toString();
|
||||
|
||||
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != "table") {
|
||||
if(xml.name() == "column_widths") {
|
||||
if(xml.name() == "sort")
|
||||
{
|
||||
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != "sort")
|
||||
{
|
||||
if(xml.name() == "column")
|
||||
{
|
||||
int index = xml.attributes().value("index").toInt();
|
||||
int mode = xml.attributes().value("mode").toInt();
|
||||
settings.sortOrder.push_back(BrowseDataTableSettings::SortedColumn(index, mode));
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if(xml.name() == "column_widths") {
|
||||
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != "column_widths") {
|
||||
if (xml.name() == "column") {
|
||||
int index = xml.attributes().value("index").toInt();
|
||||
@@ -2429,6 +2503,21 @@ static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, QXmlS
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else if(xml.name() == "conditional_formats") {
|
||||
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != "conditional_formats") {
|
||||
if (xml.name() == "column") {
|
||||
int index = xml.attributes().value("index").toInt();
|
||||
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != "column") {
|
||||
if(xml.name() == "format") {
|
||||
CondFormat newCondFormat(xml.attributes().value("condition").toString(),
|
||||
QColor(xml.attributes().value("color").toString()),
|
||||
settings.encoding);
|
||||
settings.condFormats[index].append(newCondFormat);
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(xml.name() == "display_formats") {
|
||||
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != "display_formats") {
|
||||
if (xml.name() == "column") {
|
||||
@@ -2467,9 +2556,11 @@ bool MainWindow::loadProject(QString filename, bool readOnly)
|
||||
// Show the open file dialog when no filename was passed as parameter
|
||||
if(filename.isEmpty())
|
||||
{
|
||||
filename = FileDialog::getOpenFileName(this,
|
||||
tr("Choose a project file to open"),
|
||||
tr("DB Browser for SQLite project file (*.sqbpro)"));
|
||||
filename = FileDialog::getOpenFileName(
|
||||
OpenProjectFile,
|
||||
this,
|
||||
tr("Choose a project file to open"),
|
||||
tr("DB Browser for SQLite project file (*.sqbpro)"));
|
||||
}
|
||||
|
||||
if(!filename.isEmpty())
|
||||
@@ -2610,8 +2701,11 @@ bool MainWindow::loadProject(QString filename, bool readOnly)
|
||||
{
|
||||
populateTable(); // Refresh view
|
||||
sqlb::ObjectIdentifier current_table = currentlyBrowsedTableName();
|
||||
ui->dataTable->sortByColumn(browseTableSettings[current_table].sortOrderIndex,
|
||||
browseTableSettings[current_table].sortOrderMode);
|
||||
|
||||
int sortIndex;
|
||||
Qt::SortOrder sortMode;
|
||||
fromSortOrderVector(browseTableSettings[current_table].sortOrder, sortIndex, sortMode);
|
||||
ui->dataTable->sortByColumn(sortIndex, sortMode);
|
||||
showRowidColumn(browseTableSettings[current_table].showRowid);
|
||||
unlockViewEditing(!browseTableSettings[current_table].unlockViewPk.isEmpty(), browseTableSettings[current_table].unlockViewPk);
|
||||
}
|
||||
@@ -2667,12 +2761,21 @@ static void saveDbTreeState(const QTreeView* tree, QXmlStreamWriter& xml, QModel
|
||||
|
||||
static void saveBrowseDataTableSettings(const BrowseDataTableSettings& object, QXmlStreamWriter& xml)
|
||||
{
|
||||
xml.writeAttribute("sort_order_index", QString::number(object.sortOrderIndex));
|
||||
xml.writeAttribute("sort_order_mode", QString::number(object.sortOrderMode));
|
||||
xml.writeAttribute("show_row_id", QString::number(object.showRowid));
|
||||
xml.writeAttribute("encoding", object.encoding);
|
||||
xml.writeAttribute("plot_x_axis", object.plotXAxis);
|
||||
xml.writeAttribute("unlock_view_pk", object.unlockViewPk);
|
||||
|
||||
xml.writeStartElement("sort");
|
||||
for(const auto& column : object.sortOrder)
|
||||
{
|
||||
xml.writeStartElement("column");
|
||||
xml.writeAttribute("index", QString::number(column.index));
|
||||
xml.writeAttribute("mode", QString::number(column.mode));
|
||||
xml.writeEndElement();
|
||||
}
|
||||
xml.writeEndElement();
|
||||
|
||||
xml.writeStartElement("column_widths");
|
||||
for(auto iter=object.columnWidths.constBegin(); iter!=object.columnWidths.constEnd(); ++iter) {
|
||||
xml.writeStartElement("column");
|
||||
@@ -2689,6 +2792,19 @@ static void saveBrowseDataTableSettings(const BrowseDataTableSettings& object, Q
|
||||
xml.writeEndElement();
|
||||
}
|
||||
xml.writeEndElement();
|
||||
xml.writeStartElement("conditional_formats");
|
||||
for(auto iter=object.condFormats.constBegin(); iter!=object.condFormats.constEnd(); ++iter) {
|
||||
xml.writeStartElement("column");
|
||||
xml.writeAttribute("index", QString::number(iter.key()));
|
||||
for(auto format : iter.value()) {
|
||||
xml.writeStartElement("format");
|
||||
xml.writeAttribute("condition", format.filter());
|
||||
xml.writeAttribute("color", format.color().name());
|
||||
xml.writeEndElement();
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
xml.writeEndElement();
|
||||
xml.writeStartElement("display_formats");
|
||||
for(auto iter=object.displayFormats.constBegin(); iter!=object.displayFormats.constEnd(); ++iter) {
|
||||
xml.writeStartElement("column");
|
||||
@@ -2721,10 +2837,12 @@ static void saveBrowseDataTableSettings(const BrowseDataTableSettings& object, Q
|
||||
|
||||
void MainWindow::saveProject()
|
||||
{
|
||||
QString filename = FileDialog::getSaveFileName(this,
|
||||
tr("Choose a filename to save under"),
|
||||
tr("DB Browser for SQLite project file (*.sqbpro)"),
|
||||
db.currentFile());
|
||||
QString filename = FileDialog::getSaveFileName(
|
||||
CreateProjectFile,
|
||||
this,
|
||||
tr("Choose a filename to save under"),
|
||||
tr("DB Browser for SQLite project file (*.sqbpro)"),
|
||||
db.currentFile());
|
||||
if(!filename.isEmpty())
|
||||
{
|
||||
// Make sure the file has got a .sqbpro ending
|
||||
@@ -2837,6 +2955,7 @@ void MainWindow::fileAttach()
|
||||
{
|
||||
// Get file name of database to attach
|
||||
QString file = FileDialog::getOpenFileName(
|
||||
OpenDatabaseFile,
|
||||
this,
|
||||
tr("Choose a database file"),
|
||||
FileDialog::getSqlDatabaseFileFilter());
|
||||
@@ -2858,6 +2977,20 @@ void MainWindow::updateFilter(int column, const QString& value)
|
||||
applyBrowseTableSettings(settings, true);
|
||||
}
|
||||
|
||||
void MainWindow::addCondFormat(int column, const QString& value)
|
||||
{
|
||||
CondFormat newCondFormat(value, m_condFormatPalette.nextSerialColor(Palette::appHasDarkTheme()), m_browseTableModel->encoding());
|
||||
m_browseTableModel->addCondFormat(column, newCondFormat);
|
||||
browseTableSettings[currentlyBrowsedTableName()].condFormats[column].append(newCondFormat);
|
||||
}
|
||||
|
||||
void MainWindow::clearAllCondFormats(int column)
|
||||
{
|
||||
QVector<CondFormat> emptyCondFormatVector = QVector<CondFormat>();
|
||||
m_browseTableModel->setCondFormats(column, emptyCondFormatVector);
|
||||
browseTableSettings[currentlyBrowsedTableName()].condFormats[column].clear();
|
||||
}
|
||||
|
||||
void MainWindow::editEncryption()
|
||||
{
|
||||
#ifdef ENABLE_SQLCIPHER
|
||||
|
||||
+30
-8
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "sqlitedb.h"
|
||||
#include "PlotDock.h"
|
||||
#include "Palette.h"
|
||||
#include "CondFormat.h"
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QMap>
|
||||
@@ -26,10 +28,28 @@ class MainWindow;
|
||||
|
||||
struct BrowseDataTableSettings
|
||||
{
|
||||
int sortOrderIndex;
|
||||
Qt::SortOrder sortOrderMode;
|
||||
struct SortedColumn
|
||||
{
|
||||
SortedColumn() :
|
||||
index(0),
|
||||
mode(Qt::AscendingOrder)
|
||||
{}
|
||||
SortedColumn(int index_, Qt::SortOrder mode_) :
|
||||
index(index_),
|
||||
mode(mode_)
|
||||
{}
|
||||
SortedColumn(int index_, int mode_) :
|
||||
index(index_),
|
||||
mode(static_cast<Qt::SortOrder>(mode_))
|
||||
{}
|
||||
|
||||
int index;
|
||||
Qt::SortOrder mode;
|
||||
};
|
||||
QVector<SortedColumn> sortOrder;
|
||||
QMap<int, int> columnWidths;
|
||||
QMap<int, QString> filterValues;
|
||||
QMap<int, QVector<CondFormat>> condFormats;
|
||||
QMap<int, QString> displayFormats;
|
||||
bool showRowid;
|
||||
QString encoding;
|
||||
@@ -39,18 +59,16 @@ struct BrowseDataTableSettings
|
||||
QMap<int, bool> hiddenColumns;
|
||||
|
||||
BrowseDataTableSettings() :
|
||||
sortOrderIndex(0),
|
||||
sortOrderMode(Qt::AscendingOrder),
|
||||
showRowid(false)
|
||||
{
|
||||
}
|
||||
|
||||
friend QDataStream& operator>>(QDataStream& stream, BrowseDataTableSettings& object)
|
||||
{
|
||||
stream >> object.sortOrderIndex;
|
||||
int sortordermode;
|
||||
stream >> sortordermode;
|
||||
object.sortOrderMode = static_cast<Qt::SortOrder>(sortordermode);
|
||||
int sortOrderIndex, sortOrderMode;
|
||||
stream >> sortOrderIndex;
|
||||
stream >> sortOrderMode;
|
||||
object.sortOrder.push_back(SortedColumn(sortOrderIndex, sortOrderMode));
|
||||
stream >> object.columnWidths;
|
||||
stream >> object.filterValues;
|
||||
stream >> object.displayFormats;
|
||||
@@ -177,6 +195,8 @@ private:
|
||||
|
||||
QString defaultBrowseTableEncoding;
|
||||
|
||||
Palette m_condFormatPalette;
|
||||
|
||||
void init();
|
||||
void clearCompleterModelsFields();
|
||||
|
||||
@@ -278,6 +298,8 @@ private slots:
|
||||
void saveProject();
|
||||
void fileAttach();
|
||||
void updateFilter(int column, const QString& value);
|
||||
void addCondFormat(int column, const QString& value);
|
||||
void clearAllCondFormats(int column);
|
||||
void editEncryption();
|
||||
void on_buttonClearFilters_clicked();
|
||||
void copyCurrentCreateStatement();
|
||||
|
||||
+2
-2
@@ -194,10 +194,10 @@ You can drag SQL statements from an object row and drop them into other applicat
|
||||
<item>
|
||||
<widget class="QToolButton" name="buttonPrintTable">
|
||||
<property name="toolTip">
|
||||
<string>Print currrently browsed table data [Ctrl+P]</string>
|
||||
<string>Print currently browsed table data [Ctrl+P]</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Print currrently browsed table data. Print selection if more than one cell is selected.</string>
|
||||
<string>Print currently browsed table data. Print selection if more than one cell is selected.</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons/icons.qrc">
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
#include "Palette.h"
|
||||
|
||||
#include <QPalette>
|
||||
|
||||
Palette::Palette()
|
||||
: m_lastColourIndex(0)
|
||||
{
|
||||
}
|
||||
|
||||
QColor Palette::nextSerialColor(bool dark)
|
||||
{
|
||||
if (dark) {
|
||||
switch(m_lastColourIndex++)
|
||||
{
|
||||
case 0:
|
||||
return QColor(0, 69, 134);
|
||||
break;
|
||||
case 1:
|
||||
return QColor(255, 66, 14);
|
||||
break;
|
||||
case 2:
|
||||
return QColor(255, 211, 32);
|
||||
break;
|
||||
case 3:
|
||||
return QColor(87, 157, 28);
|
||||
break;
|
||||
case 4:
|
||||
return QColor(126, 0, 33);
|
||||
break;
|
||||
case 5:
|
||||
return QColor(131, 202, 255);
|
||||
break;
|
||||
case 6:
|
||||
return QColor(49, 64, 4);
|
||||
break;
|
||||
case 7:
|
||||
return QColor(174, 207, 0);
|
||||
break;
|
||||
case 8:
|
||||
return QColor(75, 31, 111);
|
||||
break;
|
||||
case 9:
|
||||
return QColor(255, 149, 14);
|
||||
break;
|
||||
case 10:
|
||||
return QColor(197, 00, 11);
|
||||
break;
|
||||
case 11:
|
||||
|
||||
// Since this is the last colour in our table, reset the counter back
|
||||
// to the first colour
|
||||
m_lastColourIndex = 0;
|
||||
|
||||
return QColor(0, 132, 209);
|
||||
break;
|
||||
default:
|
||||
// NOTE: This shouldn't happen!
|
||||
m_lastColourIndex = 0;
|
||||
return QColor(0, 0, 0);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// TODO: review the bright colours
|
||||
switch(m_lastColourIndex++)
|
||||
{
|
||||
case 0:
|
||||
return QColor(Qt::yellow);
|
||||
break;
|
||||
case 1:
|
||||
return QColor(Qt::cyan);
|
||||
break;
|
||||
case 2:
|
||||
return QColor(Qt::green);
|
||||
break;
|
||||
case 3:
|
||||
return QColor(Qt::magenta);
|
||||
break;
|
||||
case 4:
|
||||
return QColor(Qt::lightGray);
|
||||
break;
|
||||
case 5:
|
||||
return QColor("beige");
|
||||
break;
|
||||
case 6:
|
||||
return QColor("lightblue");
|
||||
break;
|
||||
case 7:
|
||||
return QColor("turquoise");
|
||||
break;
|
||||
case 8:
|
||||
return QColor("mediumspringgreen");
|
||||
break;
|
||||
case 9:
|
||||
return QColor("lightskyblue");
|
||||
break;
|
||||
case 10:
|
||||
return QColor("moccasin");
|
||||
break;
|
||||
case 11:
|
||||
|
||||
// Since this is the last colour in our table, reset the counter back
|
||||
// to the first colour
|
||||
m_lastColourIndex = 0;
|
||||
|
||||
return QColor("pink");
|
||||
break;
|
||||
default:
|
||||
// NOTE: This shouldn't happen!
|
||||
return QColor(0, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Palette::appHasDarkTheme()
|
||||
{
|
||||
QColor backgroundColour = QPalette().color(QPalette::Active, QPalette::Base);
|
||||
QColor foregroundColour = QPalette().color(QPalette::Active, QPalette::Text);
|
||||
return backgroundColour.value() < foregroundColour.value();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef PALETTE_H
|
||||
#define PALETTE_H
|
||||
|
||||
#include <QColor>
|
||||
|
||||
// Class providing series of colours in a given dark or light side of
|
||||
// the spectrum.
|
||||
class Palette
|
||||
{
|
||||
public:
|
||||
Palette();
|
||||
|
||||
QColor nextSerialColor(bool dark = true);
|
||||
|
||||
static bool appHasDarkTheme();
|
||||
|
||||
private:
|
||||
int m_lastColourIndex;
|
||||
};
|
||||
|
||||
#endif
|
||||
+6
-54
@@ -492,56 +492,7 @@ void PlotDock::on_treePlotColumns_itemChanged(QTreeWidgetItem* changeitem, int c
|
||||
// Generate a default colour if none isn't set yet
|
||||
QColor colour = changeitem->backgroundColor(column);
|
||||
if(!colour.isValid())
|
||||
{
|
||||
static int last_colour_index = 0;
|
||||
switch(last_colour_index++)
|
||||
{
|
||||
case 0:
|
||||
colour = QColor(0, 69, 134);
|
||||
break;
|
||||
case 1:
|
||||
colour = QColor(255, 66, 14);
|
||||
break;
|
||||
case 2:
|
||||
colour = QColor(255, 211, 32);
|
||||
break;
|
||||
case 3:
|
||||
colour = QColor(87, 157, 28);
|
||||
break;
|
||||
case 4:
|
||||
colour = QColor(126, 0, 33);
|
||||
break;
|
||||
case 5:
|
||||
colour = QColor(131, 202, 255);
|
||||
break;
|
||||
case 6:
|
||||
colour = QColor(49, 64, 4);
|
||||
break;
|
||||
case 7:
|
||||
colour = QColor(174, 207, 0);
|
||||
break;
|
||||
case 8:
|
||||
colour = QColor(75, 31, 111);
|
||||
break;
|
||||
case 9:
|
||||
colour = QColor(255, 149, 14);
|
||||
break;
|
||||
case 10:
|
||||
colour = QColor(197, 00, 11);
|
||||
break;
|
||||
case 11:
|
||||
colour = QColor(0, 132, 209);
|
||||
|
||||
// Since this is the last colour in our table, reset the counter back
|
||||
// to the first colour
|
||||
last_colour_index = 0;
|
||||
break;
|
||||
default:
|
||||
// NOTE: This shouldn't happen!
|
||||
colour = QColor(0, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
colour = m_graphPalette.nextSerialColor(true);
|
||||
|
||||
// Set colour
|
||||
changeitem->setBackgroundColor(column, colour);
|
||||
@@ -606,10 +557,11 @@ void PlotDock::on_treePlotColumns_itemDoubleClicked(QTreeWidgetItem* item, int c
|
||||
|
||||
void PlotDock::on_butSavePlot_clicked()
|
||||
{
|
||||
QString fileName = FileDialog::getSaveFileName(this,
|
||||
tr("Choose a filename to save under"),
|
||||
tr("PNG(*.png);;JPG(*.jpg);;PDF(*.pdf);;BMP(*.bmp);;All Files(*)")
|
||||
);
|
||||
QString fileName = FileDialog::getSaveFileName(
|
||||
CreateDataFile,
|
||||
this,
|
||||
tr("Choose a filename to save under"),
|
||||
tr("PNG(*.png);;JPG(*.jpg);;PDF(*.pdf);;BMP(*.bmp);;All Files(*)"));
|
||||
if(!fileName.isEmpty())
|
||||
{
|
||||
if(fileName.endsWith(".png", Qt::CaseInsensitive))
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef PLOTDOCK_H
|
||||
#define PLOTDOCK_H
|
||||
|
||||
#include "Palette.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QVariant>
|
||||
#include <QMenu>
|
||||
@@ -89,6 +91,7 @@ private:
|
||||
QMenu* m_contextMenu;
|
||||
bool m_showLegend;
|
||||
bool m_stackedBars;
|
||||
Palette m_graphPalette;
|
||||
|
||||
/*!
|
||||
* \brief guessdatatype try to parse the first 10 rows and decide the datatype
|
||||
|
||||
@@ -56,6 +56,7 @@ PreferencesDialog::~PreferencesDialog()
|
||||
void PreferencesDialog::chooseLocation()
|
||||
{
|
||||
QString s = FileDialog::getExistingDirectory(
|
||||
NoSpecificType,
|
||||
this,
|
||||
tr("Choose a directory"),
|
||||
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
||||
@@ -368,6 +369,7 @@ bool PreferencesDialog::eventFilter(QObject *obj, QEvent *event)
|
||||
void PreferencesDialog::addExtension()
|
||||
{
|
||||
QString file = FileDialog::getOpenFileName(
|
||||
OpenExtensionFile,
|
||||
this,
|
||||
tr("Select extension file"),
|
||||
tr("Extensions(*.so *.dll);;All files(*)"));
|
||||
@@ -503,7 +505,7 @@ void PreferencesDialog::addClientCertificate()
|
||||
{
|
||||
// Get certificate file to import and abort here if no file gets selected
|
||||
// NOTE: We assume here that this file contains both, certificate and private key!
|
||||
QString path = FileDialog::getOpenFileName(this, tr("Import certificate file"), "*.pem");
|
||||
QString path = FileDialog::getOpenFileName(OpenCertificateFile, this, tr("Import certificate file"), "*.pem");
|
||||
if(path.isEmpty())
|
||||
return;
|
||||
|
||||
@@ -575,6 +577,7 @@ void PreferencesDialog::addClientCertToTable(const QString& path, const QSslCert
|
||||
void PreferencesDialog::chooseRemoteCloneDirectory()
|
||||
{
|
||||
QString s = FileDialog::getExistingDirectory(
|
||||
NoSpecificType,
|
||||
this,
|
||||
tr("Choose a directory"),
|
||||
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
||||
|
||||
+67
-46
@@ -986,16 +986,18 @@ bool DBBrowserDB::executeMultiSQL(const QString& statement, bool dirty, bool log
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant DBBrowserDB::querySingleValueFromDb(const QString& statement, bool log)
|
||||
QByteArray DBBrowserDB::querySingleValueFromDb(const QString& sql, bool log)
|
||||
{
|
||||
waitForDbRelease();
|
||||
if(!_db)
|
||||
return QVariant();
|
||||
return QByteArray();
|
||||
|
||||
if(log)
|
||||
logSQL(statement, kLogMsg_App);
|
||||
logSQL(sql, kLogMsg_App);
|
||||
|
||||
QByteArray utf8Query = statement.toUtf8();
|
||||
QByteArray retval;
|
||||
|
||||
QByteArray utf8Query = sql.toUtf8();
|
||||
sqlite3_stmt* stmt;
|
||||
if(sqlite3_prepare_v2(_db, utf8Query, utf8Query.size(), &stmt, nullptr) == SQLITE_OK)
|
||||
{
|
||||
@@ -1005,16 +1007,22 @@ QVariant DBBrowserDB::querySingleValueFromDb(const QString& statement, bool log)
|
||||
{
|
||||
int bytes = sqlite3_column_bytes(stmt, 0);
|
||||
if(bytes)
|
||||
return QByteArray(static_cast<const char*>(sqlite3_column_blob(stmt, 0)), bytes);
|
||||
retval = QByteArray(static_cast<const char*>(sqlite3_column_blob(stmt, 0)), bytes);
|
||||
else
|
||||
return "";
|
||||
retval = "";
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
} else {
|
||||
lastErrorMessage = tr("didn't receive any output from %1").arg(sql);
|
||||
qWarning() << lastErrorMessage;
|
||||
}
|
||||
} else {
|
||||
lastErrorMessage = tr("could not execute command: %1").arg(sqlite3_errmsg(_db));
|
||||
qWarning() << lastErrorMessage;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool DBBrowserDB::getRow(const sqlb::ObjectIdentifier& table, const QString& rowid, QVector<QByteArray>& rowdata)
|
||||
@@ -1296,7 +1304,7 @@ bool DBBrowserDB::addColumn(const sqlb::ObjectIdentifier& tablename, const sqlb:
|
||||
return executeSQL(sql);
|
||||
}
|
||||
|
||||
bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb::Table& table, const QString& name, const sqlb::Field* to, int move, QString newSchemaName)
|
||||
bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb::Table& table, QString name, const sqlb::Field* to, int move, QString newSchemaName)
|
||||
{
|
||||
/*
|
||||
* USE CASES:
|
||||
@@ -1306,16 +1314,6 @@ bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb
|
||||
* 4) Set table, name, to and move: Change table constraints, rename/edit column and move it.
|
||||
*/
|
||||
|
||||
// NOTE: This function is working around the incomplete ALTER TABLE command in SQLite.
|
||||
// If SQLite should fully support this command one day, this entire
|
||||
// function can be changed to executing something like this:
|
||||
//QString sql;
|
||||
//if(to.isNull())
|
||||
// sql = QString("ALTER TABLE %1 DROP COLUMN %2;").arg(sqlb::escapeIdentifier(table)).arg(sqlb::escapeIdentifier(column));
|
||||
//else
|
||||
// sql = QString("ALTER TABLE %1 MODIFY %2 %3").arg(sqlb::escapeIdentifier(tablename)).arg(sqlb::escapeIdentifier(to)).arg(type); // This is wrong...
|
||||
//return executeSQL(sql);
|
||||
|
||||
// TODO: This function needs to be cleaned up. It might make sense to split it up in several parts than can be reused
|
||||
// more easily. Besides that, it might make sense to support some potential use cases in a more sophisticated way. These include:
|
||||
// 1) Allow modifying multiple columns at once in order to only have to call this function (including all its overhead) once instead of once per change.
|
||||
@@ -1335,7 +1333,7 @@ bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb
|
||||
}
|
||||
|
||||
// Create table schema
|
||||
const sqlb::TablePtr oldSchema = getObjectByName<sqlb::Table>(tablename);
|
||||
sqlb::TablePtr oldSchema = getObjectByName<sqlb::Table>(tablename);
|
||||
|
||||
// Check if field actually exists
|
||||
if(!name.isNull() && sqlb::findField(oldSchema, name) == oldSchema->fields.end())
|
||||
@@ -1352,6 +1350,55 @@ bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb
|
||||
return false;
|
||||
}
|
||||
|
||||
// No automatic schema updates from now on
|
||||
NoStructureUpdateChecks nup(*this);
|
||||
|
||||
// Newer versions of SQLite add a better ALTER TABLE support which we can use
|
||||
#if SQLITE_VERSION_NUMBER >= 3025000
|
||||
// If the name of the field should be changed do that by using SQLite's ALTER TABLE feature
|
||||
if(!name.isNull() && to && name != to->name())
|
||||
{
|
||||
if(!executeSQL(QString("ALTER TABLE %1 RENAME COLUMN %2 TO %3;")
|
||||
.arg(tablename.toString())
|
||||
.arg(sqlb::escapeIdentifier(name))
|
||||
.arg(sqlb::escapeIdentifier(to->name()))))
|
||||
{
|
||||
QString error(tr("renameColumn: renaming the column failed. DB says:\n%1").arg(lastErrorMessage));
|
||||
revertToSavepoint(savepointName);
|
||||
lastErrorMessage = error;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update our schema representation to get all the changed triggers, views and indices
|
||||
updateSchema();
|
||||
|
||||
// Check if that was all we were asked to do. That's the case if the field is not to be deleted (which we already checked for above), if the field
|
||||
// is not to be moved, if the table is not to be moved and if nothing besides the name of the field changed in the field definition.
|
||||
sqlb::Field oldFieldWithNewName = *sqlb::findField(oldSchema, name);
|
||||
oldFieldWithNewName.setName(to->name());
|
||||
if(move == 0 && tablename.schema() == newSchemaName && oldFieldWithNewName == *to)
|
||||
{
|
||||
// We're done.
|
||||
|
||||
// Release the savepoint - everything went fine
|
||||
if(!releaseSavepoint(savepointName))
|
||||
{
|
||||
lastErrorMessage = tr("renameColumn: releasing savepoint failed. DB says: %1").arg(lastErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// There's more to do.
|
||||
|
||||
// We can have the rest of the function deal with the remaining changes by reloading the table schema as it is now and updating the name of the column
|
||||
// to change.
|
||||
oldSchema = getObjectByName<sqlb::Table>(tablename);
|
||||
name = to->name();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create a new table with a name that hopefully doesn't exist yet.
|
||||
// Its layout is exactly the same as the one of the table to change - except for the column to change
|
||||
// of course, and the table constraints which are copied from the table parameter.
|
||||
@@ -1391,7 +1438,6 @@ bool DBBrowserDB::alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb
|
||||
}
|
||||
|
||||
// Create the new table
|
||||
NoStructureUpdateChecks nup(*this);
|
||||
if(!executeSQL(newSchema.sql(newSchemaName), true, true))
|
||||
{
|
||||
QString error(tr("renameColumn: creating new table failed. DB says: %1").arg(lastErrorMessage));
|
||||
@@ -1689,37 +1735,12 @@ void DBBrowserDB::updateSchema()
|
||||
|
||||
QString DBBrowserDB::getPragma(const QString& pragma)
|
||||
{
|
||||
waitForDbRelease();
|
||||
|
||||
if(!isOpen())
|
||||
return QString();
|
||||
|
||||
QString sql;
|
||||
if (pragma=="case_sensitive_like")
|
||||
sql = "SELECT 'x' NOT LIKE 'X'";
|
||||
else
|
||||
sql = QString("PRAGMA %1").arg(pragma);
|
||||
|
||||
sqlite3_stmt* vm;
|
||||
const char* tail;
|
||||
QString retval;
|
||||
|
||||
// Get value from DB
|
||||
int err = sqlite3_prepare_v2(_db, sql.toUtf8(), sql.toUtf8().length(), &vm, &tail);
|
||||
if(err == SQLITE_OK){
|
||||
logSQL(sql, kLogMsg_App);
|
||||
if(sqlite3_step(vm) == SQLITE_ROW)
|
||||
retval = QString::fromUtf8((const char *) sqlite3_column_text(vm, 0));
|
||||
else
|
||||
qWarning() << tr("didn't receive any output from pragma %1").arg(pragma);
|
||||
|
||||
sqlite3_finalize(vm);
|
||||
} else {
|
||||
qWarning() << tr("could not execute pragma command: %1, %2").arg(err).arg(sqlite3_errmsg(_db));
|
||||
}
|
||||
|
||||
// Return it
|
||||
return retval;
|
||||
return querySingleValueFromDb(sql);
|
||||
}
|
||||
|
||||
bool DBBrowserDB::setPragma(const QString& pragma, const QString& value)
|
||||
|
||||
+2
-2
@@ -96,7 +96,7 @@ public:
|
||||
|
||||
bool executeSQL(QString statement, bool dirtyDB = true, bool logsql = true);
|
||||
bool executeMultiSQL(const QString& statement, bool dirty = true, bool log = false);
|
||||
QVariant querySingleValueFromDb(const QString& statement, bool log = true);
|
||||
QByteArray querySingleValueFromDb(const QString& sql, bool log = true);
|
||||
|
||||
const QString& lastError() const { return lastErrorMessage; }
|
||||
|
||||
@@ -152,7 +152,7 @@ public:
|
||||
* @param newSchema Set this to a non-empty string to move the table to a new schema
|
||||
* @return true if renaming was successful, false if not. In the latter case also lastErrorMessage is set
|
||||
*/
|
||||
bool alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb::Table& table, const QString& name, const sqlb::Field* to, int move = 0, QString newSchemaName = QString());
|
||||
bool alterTable(const sqlb::ObjectIdentifier& tablename, const sqlb::Table& table, QString name, const sqlb::Field* to, int move = 0, QString newSchemaName = QString());
|
||||
|
||||
objectMap getBrowsableObjects(const QString& schema) const;
|
||||
|
||||
|
||||
+35
-100
@@ -107,6 +107,7 @@ void SqliteTableModel::reset()
|
||||
m_vDataTypes.clear();
|
||||
m_vDisplayFormat.clear();
|
||||
m_pseudoPk.clear();
|
||||
m_mCondFormats.clear();
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
@@ -300,6 +301,24 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const
|
||||
return QColor(Settings::getValue("databrowser", "null_bg_colour").toString());
|
||||
else if (nosync_isBinary(index))
|
||||
return QColor(Settings::getValue("databrowser", "bin_bg_colour").toString());
|
||||
else if (m_mCondFormats.contains(index.column())) {
|
||||
QString value = cached_row->at(index.column());
|
||||
bool isNumber;
|
||||
value.toFloat(&isNumber);
|
||||
QString sql;
|
||||
// For each conditional format for this column,
|
||||
// if the condition matches the current data, return the associated colour.
|
||||
for (const CondFormat& eachCondFormat : m_mCondFormats.value(index.column())) {
|
||||
if (isNumber && !eachCondFormat.sqlCondition().contains("'"))
|
||||
sql = QString("SELECT %1 %2").arg(value, eachCondFormat.sqlCondition());
|
||||
else
|
||||
sql = QString("SELECT '%1' %2").arg(value, eachCondFormat.sqlCondition());
|
||||
|
||||
if (m_db.querySingleValueFromDb(sql, false) == "1")
|
||||
return eachCondFormat.color();
|
||||
}
|
||||
}
|
||||
// Regular case (not null, not binary and no matching conditional format)
|
||||
return QColor(Settings::getValue("databrowser", "reg_bg_colour").toString());
|
||||
} else if(role == Qt::ToolTipRole) {
|
||||
sqlb::ForeignKeyClause fk = getForeignKeyClause(index.column()-1);
|
||||
@@ -714,104 +733,26 @@ QStringList SqliteTableModel::getColumns(std::shared_ptr<sqlite3> pDb, const QSt
|
||||
return listColumns;
|
||||
}
|
||||
|
||||
void SqliteTableModel::addCondFormat(int column, const CondFormat& condFormat)
|
||||
{
|
||||
m_mCondFormats[column].append(condFormat);
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
void SqliteTableModel::setCondFormats(int column, const QVector<CondFormat>& condFormats)
|
||||
{
|
||||
m_mCondFormats[column] = condFormats;
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
void SqliteTableModel::updateFilter(int column, const QString& value, bool applyQuery)
|
||||
{
|
||||
// Check for any special comparison operators at the beginning of the value string. If there are none default to LIKE.
|
||||
QString op = "LIKE";
|
||||
QString val, val2;
|
||||
QString escape;
|
||||
bool numeric = false, ok = false;
|
||||
|
||||
// range/BETWEEN operator
|
||||
if (value.contains("~")) {
|
||||
int sepIdx = value.indexOf('~');
|
||||
val = value.mid(0, sepIdx);
|
||||
val2 = value.mid(sepIdx+1);
|
||||
val.toFloat(&ok);
|
||||
if (ok) {
|
||||
val2.toFloat(&ok);
|
||||
ok = ok && (val.toFloat() < val2.toFloat());
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
op = "BETWEEN";
|
||||
numeric = true;
|
||||
} else {
|
||||
val.clear();
|
||||
val2.clear();
|
||||
if(value.left(2) == ">=" || value.left(2) == "<=" || value.left(2) == "<>")
|
||||
{
|
||||
// Check if we're filtering for '<> NULL'. In this case we need a special comparison operator.
|
||||
if(value.left(2) == "<>" && value.mid(2) == "NULL")
|
||||
{
|
||||
// We are filtering for '<>NULL'. Override the comparison operator to search for NULL values in this column. Also treat search value (NULL) as number,
|
||||
// in order to avoid putting quotes around it.
|
||||
op = "IS NOT";
|
||||
numeric = true;
|
||||
val = "NULL";
|
||||
} else if(value.left(2) == "<>" && value.mid(2) == "''") {
|
||||
// We are filtering for "<>''", i.e. for everything which is not an empty string
|
||||
op = "<>";
|
||||
numeric = true;
|
||||
val = "''";
|
||||
} else {
|
||||
value.mid(2).toFloat(&numeric);
|
||||
op = value.left(2);
|
||||
val = value.mid(2);
|
||||
}
|
||||
} else if(value.left(1) == ">" || value.left(1) == "<") {
|
||||
value.mid(1).toFloat(&numeric);
|
||||
op = value.left(1);
|
||||
val = value.mid(1);
|
||||
} else if(value.left(1) == "=") {
|
||||
val = value.mid(1);
|
||||
|
||||
// Check if value to compare with is 'NULL'
|
||||
if(val != "NULL")
|
||||
{
|
||||
// It's not, so just compare normally to the value, whatever it is.
|
||||
op = "=";
|
||||
} else {
|
||||
// It is NULL. Override the comparison operator to search for NULL values in this column. Also treat search value (NULL) as number,
|
||||
// in order to avoid putting quotes around it.
|
||||
op = "IS";
|
||||
numeric = true;
|
||||
}
|
||||
} else {
|
||||
// Keep the default LIKE operator
|
||||
|
||||
// Set the escape character if one has been specified in the settings dialog
|
||||
QString escape_character = Settings::getValue("databrowser", "filter_escape").toString();
|
||||
if(escape_character == "'") escape_character = "''";
|
||||
if(escape_character.length())
|
||||
escape = QString("ESCAPE '%1'").arg(escape_character);
|
||||
|
||||
// Add % wildcards at the start and at the beginning of the filter query, but only if there weren't set any
|
||||
// wildcards manually. The idea is to assume that a user who's just typing characters expects the wildcards to
|
||||
// be added but a user who adds them herself knows what she's doing and doesn't want us to mess up her query.
|
||||
if(!value.contains("%"))
|
||||
{
|
||||
val = value;
|
||||
val.prepend('%');
|
||||
val.append('%');
|
||||
}
|
||||
}
|
||||
}
|
||||
if(val.isEmpty())
|
||||
val = value;
|
||||
QString whereClause = CondFormat::filterToSqlCondition(value, m_encoding);
|
||||
|
||||
// If the value was set to an empty string remove any filter for this column. Otherwise insert a new filter rule or replace the old one if there is already one
|
||||
if(val == "" || val == "%" || val == "%%")
|
||||
if(whereClause.isEmpty())
|
||||
m_mWhere.remove(column);
|
||||
else {
|
||||
// Quote and escape value, but only if it's not numeric and not the empty string sequence
|
||||
if(!numeric && val != "''")
|
||||
val = QString("'%1'").arg(val.replace("'", "''"));
|
||||
|
||||
QString whereClause(op + " " + QString(encode(val.toUtf8())));
|
||||
if (!val2.isEmpty())
|
||||
whereClause += " AND " + QString(encode(val2.toUtf8()));
|
||||
whereClause += " " + escape;
|
||||
m_mWhere.insert(column, whereClause);
|
||||
}
|
||||
|
||||
@@ -858,18 +799,12 @@ bool SqliteTableModel::nosync_isBinary(const QModelIndex& index) const
|
||||
|
||||
QByteArray SqliteTableModel::encode(const QByteArray& str) const
|
||||
{
|
||||
if(m_encoding.isEmpty())
|
||||
return str;
|
||||
else
|
||||
return QTextCodec::codecForName(m_encoding.toUtf8())->fromUnicode(str);
|
||||
return encodeString(str, m_encoding);
|
||||
}
|
||||
|
||||
QByteArray SqliteTableModel::decode(const QByteArray& str) const
|
||||
{
|
||||
if(m_encoding.isEmpty())
|
||||
return str;
|
||||
else
|
||||
return QTextCodec::codecForName(m_encoding.toUtf8())->toUnicode(str).toUtf8();
|
||||
return decodeString(str, m_encoding);
|
||||
}
|
||||
|
||||
Qt::DropActions SqliteTableModel::supportedDropActions() const
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include <QVector>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QColor>
|
||||
#include <memory>
|
||||
|
||||
#include "sqlitetypes.h"
|
||||
#include "RowCache.h"
|
||||
#include "CondFormat.h"
|
||||
|
||||
struct sqlite3;
|
||||
class DBBrowserDB;
|
||||
@@ -109,6 +111,9 @@ public:
|
||||
// Helper function for removing all comments from a SQL query
|
||||
static void removeCommentsFromQuery(QString& query);
|
||||
|
||||
void addCondFormat(int column, const CondFormat& condFormat);
|
||||
void setCondFormats(int column, const QVector<CondFormat>& condFormats);
|
||||
|
||||
public slots:
|
||||
void updateFilter(int column, const QString& value, bool applyQuery = true);
|
||||
|
||||
@@ -175,6 +180,7 @@ private:
|
||||
QMap<int, QString> m_mWhere;
|
||||
QVector<QString> m_vDisplayFormat;
|
||||
QVector<int> m_vDataTypes;
|
||||
QMap<int, QVector<CondFormat>> m_mCondFormats;
|
||||
|
||||
/**
|
||||
* @brief m_chunkSize Size of the next chunk fetch more will try to fetch.
|
||||
|
||||
+6
-2
@@ -68,7 +68,9 @@ HEADERS += \
|
||||
FileExtensionManager.h \
|
||||
Data.h \
|
||||
CipherSettings.h \
|
||||
DotenvFormat.h
|
||||
DotenvFormat.h \
|
||||
Palette.h \
|
||||
CondFormat.h
|
||||
|
||||
SOURCES += \
|
||||
sqlitedb.cpp \
|
||||
@@ -113,7 +115,9 @@ SOURCES += \
|
||||
FileExtensionManager.cpp \
|
||||
Data.cpp \
|
||||
CipherSettings.cpp \
|
||||
DotenvFormat.cpp
|
||||
DotenvFormat.cpp \
|
||||
Palette.cpp \
|
||||
CondFormat.cpp
|
||||
|
||||
RESOURCES += icons/icons.qrc \
|
||||
translations/flags/flags.qrc \
|
||||
|
||||
@@ -19,6 +19,7 @@ set(TESTSQLOBJECTS_SRC
|
||||
../Data.cpp
|
||||
../CipherSettings.cpp
|
||||
../DotenvFormat.cpp
|
||||
../CondFormat.cpp
|
||||
)
|
||||
|
||||
set(TESTSQLOBJECTS_HDR
|
||||
@@ -36,6 +37,7 @@ set(TESTSQLOBJECTS_MOC_HDR
|
||||
testsqlobjects.h
|
||||
../CipherSettings.h
|
||||
../DotenvFormat.h
|
||||
../CondFormat.h
|
||||
)
|
||||
|
||||
if(sqlcipher)
|
||||
@@ -100,6 +102,7 @@ set(TESTREGEX_SRC
|
||||
../Data.cpp
|
||||
../CipherSettings.cpp
|
||||
../DotenvFormat.cpp
|
||||
../CondFormat.cpp
|
||||
)
|
||||
|
||||
set(TESTREGEX_HDR
|
||||
@@ -117,6 +120,7 @@ set(TESTREGEX_MOC_HDR
|
||||
TestRegex.h
|
||||
../CipherSettings.h
|
||||
../DotenvFormat.h
|
||||
../CondFormat.h
|
||||
)
|
||||
|
||||
if(sqlcipher)
|
||||
|
||||
@@ -2243,12 +2243,12 @@ You can drag SQL statements from an object row and drop them into other applicat
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="197"/>
|
||||
<source>Print currrently browsed table data [Ctrl+P]</source>
|
||||
<source>Print currently browsed table data [Ctrl+P]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="200"/>
|
||||
<source>Print currrently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<source>Print currently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -3286,12 +3286,12 @@ You can drag SQL statements from an object row and drop them into other applicat
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="197"/>
|
||||
<source>Print currrently browsed table data [Ctrl+P]</source>
|
||||
<source>Print currently browsed table data [Ctrl+P]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="200"/>
|
||||
<source>Print currrently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<source>Print currently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -2479,12 +2479,12 @@ Sie können SQL-Statements aus einer Objektzeile fassen und in anderen Anwendung
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="197"/>
|
||||
<source>Print currrently browsed table data [Ctrl+P]</source>
|
||||
<source>Print currently browsed table data [Ctrl+P]</source>
|
||||
<translation>Aktuell angezeigte Tabellendaten drucken [Strg+P]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="200"/>
|
||||
<source>Print currrently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<source>Print currently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<translation>Die aktuell angezeigten Tabellendaten drucken. Druckauswahl, falls mehr als eine Zelle ausgewählt ist.</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -2359,12 +2359,12 @@ You can drag SQL statements from an object row and drop them into other applicat
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="197"/>
|
||||
<source>Print currrently browsed table data [Ctrl+P]</source>
|
||||
<source>Print currently browsed table data [Ctrl+P]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="200"/>
|
||||
<source>Print currrently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<source>Print currently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
<message>
|
||||
<location filename="../AddRecordDialog.ui" line="84"/>
|
||||
<source>Value</source>
|
||||
<translation >Valor</translation>
|
||||
<translation>Valor</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../AddRecordDialog.ui" line="87"/>
|
||||
@@ -182,12 +182,12 @@
|
||||
<message>
|
||||
<location filename="../Application.cpp" line="90"/>
|
||||
<source> -s, --sql [file] Execute this SQL file after opening the DB</source>
|
||||
<translation> -s, --sql [archivo] Ejecutar este archivo de SQL tras abrir la base de datos</translation>
|
||||
<translation> -s, --sql [archivo] Ejecutar este archivo de SQL tras abrir la base de datos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../Application.cpp" line="91"/>
|
||||
<source> -t, --table [table] Browse this table after opening the DB</source>
|
||||
<translation> -t, --table [table] Mostrar esta tabla en la hoja de datos tras abrir la base de datos</translation>
|
||||
<translation> -t, --table [tabla] Mostrar esta tabla en la hoja de datos tras abrir la base de datos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../Application.cpp" line="89"/>
|
||||
@@ -197,7 +197,7 @@
|
||||
<message>
|
||||
<location filename="../Application.cpp" line="92"/>
|
||||
<source> -R, --read-only Open database in read-only mode</source>
|
||||
<translation> -R, --read-only\tAbrir base de datos en modo de solo-lectura</translation>
|
||||
<translation> -R, --read-only Abrir base de datos en modo de solo-lectura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../Application.cpp" line="93"/>
|
||||
@@ -2850,12 +2850,12 @@ Usted puede arrastrar sentencias SQL desde una fila de objeto y soltarlas en otr
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="197"/>
|
||||
<source>Print currrently browsed table data [Ctrl+P]</source>
|
||||
<source>Print currently browsed table data [Ctrl+P]</source>
|
||||
<translation>Imprime los datos de la tabla mostrada actualmente [Ctrl+P]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="200"/>
|
||||
<source>Print currrently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<source>Print currently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<translation>Imprime los datos de la tabla mostrada actualmente. Imprime la selección si se ha seleccionado más de una celda.</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -4493,7 +4493,7 @@ Are you sure?</source>
|
||||
<message>
|
||||
<location filename="../MainWindow.cpp" line="3316"/>
|
||||
<source>Set a new name for the SQL tab. Use the '&&' character to allow using the following character as a keyboard shortcut.</source>
|
||||
<translation>Establezca el nuevo nombre para la pestaña SQL. Use el carácter «&» para permitir usar el carácter siguiente como un atajo de teclado.</translation>
|
||||
<translation>Establezca el nuevo nombre para la pestaña SQL. Use el carácter «&&» para permitir usar el carácter siguiente como un atajo de teclado.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.cpp" line="3359"/>
|
||||
|
||||
+491
-438
File diff suppressed because it is too large
Load Diff
@@ -3242,12 +3242,12 @@ You can drag SQL statements from an object row and drop them into other applicat
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="197"/>
|
||||
<source>Print currrently browsed table data [Ctrl+P]</source>
|
||||
<source>Print currently browsed table data [Ctrl+P]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="200"/>
|
||||
<source>Print currrently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<source>Print currently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -2566,12 +2566,12 @@ You can drag SQL statements from the Schema column and drop them into the SQL ed
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="197"/>
|
||||
<source>Print currrently browsed table data [Ctrl+P]</source>
|
||||
<source>Print currently browsed table data [Ctrl+P]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="200"/>
|
||||
<source>Print currrently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<source>Print currently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -3706,11 +3706,11 @@ Faça um backup!</translation>
|
||||
<translation>Pressione Help para abrir a página de referência SQL correspondente.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Print currrently browsed table data [Ctrl+P]</source>
|
||||
<source>Print currently browsed table data [Ctrl+P]</source>
|
||||
<translation>Imprimir dados da tabela atual [Ctrl+P]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Print currrently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<source>Print currently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<translation>Imprimir dados da tabela atual. Imprime a seleção se mais de uma célula está selecionada.</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -2274,12 +2274,12 @@ x~y Диапазон: значения между x и y</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="197"/>
|
||||
<source>Print currrently browsed table data [Ctrl+P]</source>
|
||||
<source>Print currently browsed table data [Ctrl+P]</source>
|
||||
<translation>Печатать отображаемую таблицу [Ctrl+P]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="200"/>
|
||||
<source>Print currrently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<source>Print currently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<translation>Распечатывайте текущие данные таблицы. Выбор печати, если выбрано несколько ячеек.</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -3100,12 +3100,12 @@ You can drag SQL statements from an object row and drop them into other applicat
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="197"/>
|
||||
<source>Print currrently browsed table data [Ctrl+P]</source>
|
||||
<source>Print currently browsed table data [Ctrl+P]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="200"/>
|
||||
<source>Print currrently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<source>Print currently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -3625,12 +3625,12 @@ You can drag SQL statements from an object row and drop them into other applicat
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="197"/>
|
||||
<source>Print currrently browsed table data [Ctrl+P]</source>
|
||||
<source>Print currently browsed table data [Ctrl+P]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="200"/>
|
||||
<source>Print currrently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<source>Print currently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
+412
-363
File diff suppressed because it is too large
Load Diff
@@ -3026,12 +3026,12 @@ You can drag SQL statements from an object row and drop them into other applicat
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="197"/>
|
||||
<source>Print currrently browsed table data [Ctrl+P]</source>
|
||||
<source>Print currently browsed table data [Ctrl+P]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../MainWindow.ui" line="200"/>
|
||||
<source>Print currrently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<source>Print currently browsed table data. Print selection if more than one cell is selected.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
Reference in New Issue
Block a user