Merge branch 'master' into browser_formats

Necessary changes:

- Added function `ExtendedTableWidget::colsInSelection` to emulate former
behaviour of `ExtendedTableWidget::selectedCols`, which is needed by the
format toolbar.

- Added call to `ui->dataTable->scrollTo(match);` in TableBrowser::find,
otherwise the scroll was not advancing to the current index.

# Conflicts:
#	src/TableBrowser.cpp
#	src/TableBrowser.ui
#	src/icons/icons.qrc
#	src/sqlitetablemodel.cpp
This commit is contained in:
mgrojo
2019-09-29 19:39:11 +02:00
26 changed files with 712 additions and 127 deletions

View File

@@ -1,6 +1,9 @@
#include "Data.h"
#include <QBuffer>
#include <QImageReader>
#include <QTextCodec>
#include <algorithm>
// Note that these aren't all possible BOMs. But they are probably the most common ones.
@@ -88,6 +91,19 @@ QByteArray removeBom(QByteArray& data)
}
}
QString isImageData(const QByteArray& data)
{
// Check if it's an image. First do a quick test by calling canRead() which only checks the first couple of bytes or so. Only if
// that returned true, do a more sophisticated test of the data. This way we get both, good performance and proper data checking.
QBuffer imageBuffer(const_cast<QByteArray*>(&data));
QImageReader readerBuffer(&imageBuffer);
QString imageFormat = readerBuffer.format();
if(readerBuffer.canRead() && !readerBuffer.read().isNull())
return imageFormat;
else
return QString();
}
QStringList toStringList(const QList<QByteArray>& list) {
QStringList strings;
for (const QByteArray &item : list) {

View File

@@ -21,6 +21,9 @@ bool startsWithBom(const QByteArray& data);
// with a BOM an empty byte array is returned and the original data is not modified.
QByteArray removeBom(QByteArray& data);
// Check if a byte array contains an image. Returns the name of the image format for images or a null string for non-image data.
QString isImageData(const QByteArray& data);
QStringList toStringList(const QList<QByteArray>& list);
QByteArray encodeString(const QByteArray& str, const QString& encoding);

View File

@@ -11,7 +11,6 @@
#include <QKeySequence>
#include <QShortcut>
#include <QImageReader>
#include <QBuffer>
#include <QModelIndex>
#include <QtXml/QDomDocument>
#include <QMessageBox>
@@ -856,12 +855,9 @@ int EditDialog::checkDataType(const QByteArray& bArrdata)
return Null;
}
// Check if it's an image. First do a quick test by calling canRead() which only checks the first couple of bytes or so. Only if
// that returned true, do a more sophisticated test of the data. This way we get both, good performance and proper data checking.
QBuffer imageBuffer(&cellData);
QImageReader readerBuffer(&imageBuffer);
QString imageFormat = readerBuffer.format();
if(readerBuffer.canRead() && !readerBuffer.read().isNull())
// Check if it's an image
QString imageFormat = isImageData(cellData);
if(!imageFormat.isNull())
return imageFormat == "svg" ? SVG : Image;
// Check if it's text only

View File

@@ -374,7 +374,6 @@ void EditTableDialog::checkInput()
if (normTableName != m_table.name()) {
const std::string oldTableName = m_table.name();
m_table.setName(normTableName);
m_fkEditorDelegate->updateTablesList(oldTableName);
// update fk's that refer to table itself recursively
const auto& fields = m_table.fields;
@@ -505,6 +504,7 @@ void EditTableDialog::fieldItemChanged(QTreeWidgetItem *item, int column)
field.setName(item->text(column).toStdString());
m_table.renameKeyInAllConstraints(oldFieldName.toStdString(), item->text(column).toStdString());
qobject_cast<QComboBox*>(ui->treeWidget->itemWidget(item, kType))->setProperty("column", item->text(column));
qobject_cast<QComboBox*>(ui->treeWidget->itemWidget(item, kCollation))->setProperty("column", item->text(column));
// Update the field name in the map of old column names to new column names
if(!m_bNewTable)
@@ -845,24 +845,46 @@ void EditTableDialog::fieldSelectionChanged()
if(hasSelection)
{
ui->buttonMoveUp->setEnabled(ui->treeWidget->selectionModel()->currentIndex().row() != 0);
ui->buttonMoveTop->setEnabled(ui->buttonMoveUp->isEnabled());
ui->buttonMoveDown->setEnabled(ui->treeWidget->selectionModel()->currentIndex().row() != ui->treeWidget->topLevelItemCount() - 1);
ui->buttonMoveBottom->setEnabled(ui->buttonMoveDown->isEnabled());
}
}
void EditTableDialog::moveUp()
{
moveCurrentField(false);
moveCurrentField(MoveUp);
}
void EditTableDialog::moveDown()
{
moveCurrentField(true);
moveCurrentField(MoveDown);
}
void EditTableDialog::moveCurrentField(bool down)
void EditTableDialog::moveTop()
{
moveCurrentField(MoveTop);
}
void EditTableDialog::moveBottom()
{
moveCurrentField(MoveBottom);
}
void EditTableDialog::moveCurrentField(MoveFieldDirection dir)
{
int currentRow = ui->treeWidget->currentIndex().row();
int newRow = currentRow + (down ? 1 : -1);
int newRow;
if(dir == MoveUp)
newRow = currentRow - 1;
else if(dir == MoveDown)
newRow = currentRow + 1;
else if(dir == MoveTop)
newRow = 0;
else if(dir == MoveBottom)
newRow = ui->treeWidget->topLevelItemCount() - 1;
else
return;
// Save the comboboxes first by making copies
QComboBox* newCombo[2];
@@ -891,7 +913,9 @@ void EditTableDialog::moveCurrentField(bool down)
ui->treeWidget->setCurrentIndex(ui->treeWidget->currentIndex().sibling(newRow, 0));
// Finally update the table SQL
std::swap(m_table.fields[static_cast<size_t>(newRow)], m_table.fields[static_cast<size_t>(currentRow)]);
sqlb::Field temp = m_table.fields[static_cast<size_t>(currentRow)];
m_table.fields.erase(m_table.fields.begin() + currentRow);
m_table.fields.insert(m_table.fields.begin() + newRow, temp);
// Update the SQL preview
updateSqlText();

View File

@@ -50,10 +50,18 @@ private:
kConstraintSql = 3
};
enum MoveFieldDirection
{
MoveUp,
MoveDown,
MoveTop,
MoveBottom
};
void updateColumnWidth();
void updateSqlText();
void moveCurrentField(bool down);
void moveCurrentField(MoveFieldDirection dir);
private slots:
void populateFields();
@@ -71,6 +79,8 @@ private slots:
void updateTypeAndCollation();
void moveUp();
void moveDown();
void moveTop();
void moveBottom();
void setWithoutRowid(bool without_rowid);
void changeSchema(const QString& schema);
void removeConstraint();

View File

@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>650</width>
<width>652</width>
<height>600</height>
</rect>
</property>
@@ -111,7 +111,7 @@
<item>
<widget class="QToolButton" name="addFieldButton">
<property name="text">
<string>Add field</string>
<string>Add</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
@@ -131,7 +131,7 @@
<bool>false</bool>
</property>
<property name="text">
<string>Remove field</string>
<string>Remove</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
@@ -145,13 +145,36 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="buttonMoveTop">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Move to top</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/arrow_top</normaloff>:/icons/arrow_top</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
<property name="arrowType">
<enum>Qt::NoArrow</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="buttonMoveUp">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Move field up</string>
<string>Move up</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
@@ -171,7 +194,7 @@
<bool>false</bool>
</property>
<property name="text">
<string>Move field down</string>
<string>Move down</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
@@ -185,6 +208,26 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="buttonMoveBottom">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Move to bottom</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/arrow_bottom</normaloff>:/icons/arrow_bottom</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
@@ -275,7 +318,7 @@
<string>Check constraint</string>
</property>
</column>
<column>
<column>
<property name="text">
<string>Collation</string>
</property>
@@ -463,12 +506,18 @@
<tabstop>buttonMore</tabstop>
<tabstop>comboSchema</tabstop>
<tabstop>checkWithoutRowid</tabstop>
<tabstop>groupDefinition</tabstop>
<tabstop>addFieldButton</tabstop>
<tabstop>removeFieldButton</tabstop>
<tabstop>buttonMoveTop</tabstop>
<tabstop>buttonMoveUp</tabstop>
<tabstop>buttonMoveDown</tabstop>
<tabstop>buttonMoveBottom</tabstop>
<tabstop>treeWidget</tabstop>
<tabstop>sqlTextEdit</tabstop>
<tabstop>buttonAddConstraint</tabstop>
<tabstop>buttonRemoveConstraint</tabstop>
<tabstop>tableConstraints</tabstop>
</tabstops>
<resources>
<include location="icons/icons.qrc"/>
@@ -529,8 +578,8 @@
<slot>addField()</slot>
<hints>
<hint type="sourcelabel">
<x>94</x>
<y>253</y>
<x>78</x>
<y>255</y>
</hint>
<hint type="destinationlabel">
<x>79</x>
@@ -545,8 +594,8 @@
<slot>removeField()</slot>
<hints>
<hint type="sourcelabel">
<x>222</x>
<y>253</y>
<x>167</x>
<y>255</y>
</hint>
<hint type="destinationlabel">
<x>249</x>
@@ -613,8 +662,8 @@
<y>78</y>
</hint>
<hint type="destinationlabel">
<x>117</x>
<y>176</y>
<x>138</x>
<y>172</y>
</hint>
</hints>
</connection>
@@ -625,8 +674,8 @@
<slot>setWithoutRowid(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>344</x>
<y>143</y>
<x>485</x>
<y>160</y>
</hint>
<hint type="destinationlabel">
<x>324</x>
@@ -657,8 +706,8 @@
<slot>changeSchema(QString)</slot>
<hints>
<hint type="sourcelabel">
<x>186</x>
<y>155</y>
<x>327</x>
<y>139</y>
</hint>
<hint type="destinationlabel">
<x>647</x>
@@ -673,8 +722,8 @@
<slot>removeConstraint()</slot>
<hints>
<hint type="sourcelabel">
<x>186</x>
<y>155</y>
<x>295</x>
<y>255</y>
</hint>
<hint type="destinationlabel">
<x>647</x>
@@ -682,6 +731,38 @@
</hint>
</hints>
</connection>
<connection>
<sender>buttonMoveTop</sender>
<signal>clicked()</signal>
<receiver>EditTableDialog</receiver>
<slot>moveTop()</slot>
<hints>
<hint type="sourcelabel">
<x>207</x>
<y>240</y>
</hint>
<hint type="destinationlabel">
<x>202</x>
<y>190</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonMoveBottom</sender>
<signal>clicked()</signal>
<receiver>EditTableDialog</receiver>
<slot>moveBottom()</slot>
<hints>
<hint type="sourcelabel">
<x>530</x>
<y>246</y>
</hint>
<hint type="destinationlabel">
<x>400</x>
<y>186</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>fieldSelectionChanged()</slot>
@@ -695,5 +776,7 @@
<slot>setWithoutRowid(bool)</slot>
<slot>changeSchema(QString)</slot>
<slot>removeConstraint()</slot>
<slot>moveTop()</slot>
<slot>moveBottom()</slot>
</slots>
</ui>

View File

@@ -844,11 +844,19 @@ int ExtendedTableWidget::numVisibleRows() const
std::unordered_set<int> ExtendedTableWidget::selectedCols() const
{
std::unordered_set<int> selectedCols;
for(const QModelIndex & idx : selectedIndexes())
for(const auto& idx : selectionModel()->selectedColumns())
selectedCols.insert(idx.column());
return selectedCols;
}
std::unordered_set<int> ExtendedTableWidget::colsInSelection() const
{
std::unordered_set<int> colsInSelection;
for(const QModelIndex & idx : selectedIndexes())
colsInSelection.insert(idx.column());
return colsInSelection;
}
void ExtendedTableWidget::cellClicked(const QModelIndex& index)
{
// If Ctrl-Shift is pressed try to jump to the row referenced by the foreign key of the clicked cell

View File

@@ -52,7 +52,11 @@ public:
FilterTableHeader* filterHeader() { return m_tableHeader; }
public:
// Get set of selected columns (all cells in column has to be selected)
std::unordered_set<int> selectedCols() const;
// Get set of columns traversed by selection (only some cells in column has to be selected)
std::unordered_set<int> colsInSelection() const;
int numVisibleRows() const;
void sortByColumns(const std::vector<sqlb::SortedColumn>& columns);

View File

@@ -85,7 +85,8 @@ ForeignKeyEditorDelegate::ForeignKeyEditorDelegate(const DBBrowserDB& db, sqlb::
{
for(const auto& jt : it.second)
{
if(jt.second->type() == sqlb::Object::Types::Table)
// Don't insert the current table into the list. The name and fields of the current table are always taken from the m_table reference
if(jt.second->type() == sqlb::Object::Types::Table && jt.second->name() != m_table.name())
m_tablesIds.insert({jt.second->name(), std::dynamic_pointer_cast<sqlb::Table>(jt.second)->fieldNames()});
}
}
@@ -105,14 +106,25 @@ QWidget* ForeignKeyEditorDelegate::createEditor(QWidget* parent, const QStyleOpt
QComboBox* box = editor->idsComboBox;
box->clear();
box->addItem(QString()); // for those heroes who don't like to specify key explicitly
for(const auto& n : m_tablesIds[tableName.toStdString()])
box->addItem(QString::fromStdString(n));
// For recursive foreign keys get the field list from the m_table reference. For other foreign keys from the prepared field lists.
if(tableName.toStdString() == m_table.name())
{
for(const auto& n : m_table.fieldNames())
box->addItem(QString::fromStdString(n));
} else {
for(const auto& n : m_tablesIds[tableName.toStdString()])
box->addItem(QString::fromStdString(n));
}
box->setCurrentIndex(0);
});
editor->tablesComboBox->clear();
for(const auto& i : m_tablesIds)
editor->tablesComboBox->addItem(QString::fromStdString(i.first));
editor->tablesComboBox->addItem(QString::fromStdString(m_table.name())); // For recursive foreign keys
return editor;
}
@@ -175,12 +187,4 @@ void ForeignKeyEditorDelegate::updateEditorGeometry(QWidget* editor, const QStyl
editor->setGeometry(option.rect);
}
void ForeignKeyEditorDelegate::updateTablesList(const std::string& oldTableName)
{
// this is used for recursive table constraints when
// table column references column within same table
m_tablesIds.erase(oldTableName);
m_tablesIds.insert({m_table.name(), m_table.fieldNames()});
}
#include "ForeignKeyEditorDelegate.moc"

View File

@@ -25,8 +25,6 @@ public:
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void updateTablesList(const std::string& oldTableName);
private:
const DBBrowserDB& m_db;
sqlb::Table& m_table;

View File

@@ -109,6 +109,7 @@ void PreferencesDialog::loadSettings()
ui->spinSymbolLimit->setValue(Settings::getValue("databrowser", "symbol_limit").toInt());
ui->spinCompleteThreshold->setValue(Settings::getValue("databrowser", "complete_threshold").toInt());
ui->checkShowImagesInline->setChecked(Settings::getValue("databrowser", "image_preview").toBool());
ui->txtNull->setText(Settings::getValue("databrowser", "null_text").toString());
ui->txtBlob->setText(Settings::getValue("databrowser", "blob_text").toString());
ui->editFilterEscape->setText(Settings::getValue("databrowser", "filter_escape").toString());
@@ -217,6 +218,7 @@ void PreferencesDialog::saveSettings()
Settings::setValue("databrowser", "font", ui->comboDataBrowserFont->currentText());
Settings::setValue("databrowser", "fontsize", ui->spinDataBrowserFontSize->value());
Settings::setValue("databrowser", "image_preview", ui->checkShowImagesInline->isChecked());
saveColorSetting(ui->fr_null_fg, "null_fg");
saveColorSetting(ui->fr_null_bg, "null_bg");
saveColorSetting(ui->fr_reg_fg, "reg_fg");

View File

@@ -821,6 +821,23 @@ Can be set to 0 for disabling completion.</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Show images in cell</string>
</property>
<property name="buddy">
<cstring>checkShowImagesInline</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="checkShowImagesInline">
<property name="toolTip">
<string>Enable this option to show a preview of BLOBs containing image data in the cells. This can affect the performance of the data browser, however.</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -1866,10 +1883,12 @@ Can be set to 0 for disabling completion.</string>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>comboDefaultLocation</tabstop>
<tabstop>locationEdit</tabstop>
<tabstop>setLocationButton</tabstop>
<tabstop>languageComboBox</tabstop>
<tabstop>appStyleCombo</tabstop>
<tabstop>toolbarStyleComboMain</tabstop>
<tabstop>toolbarStyleComboStructure</tabstop>
<tabstop>toolbarStyleComboBrowse</tabstop>
@@ -1877,6 +1896,7 @@ Can be set to 0 for disabling completion.</string>
<tabstop>toolbarStyleComboEditCell</tabstop>
<tabstop>checkUseRemotes</tabstop>
<tabstop>checkUpdates</tabstop>
<tabstop>buttonManageFileExtension</tabstop>
<tabstop>encodingComboBox</tabstop>
<tabstop>foreignKeysCheckBox</tabstop>
<tabstop>checkHideSchemaLinebreaks</tabstop>
@@ -1886,6 +1906,9 @@ Can be set to 0 for disabling completion.</string>
<tabstop>editDatabaseDefaultSqlText</tabstop>
<tabstop>comboDataBrowserFont</tabstop>
<tabstop>spinDataBrowserFontSize</tabstop>
<tabstop>spinSymbolLimit</tabstop>
<tabstop>spinCompleteThreshold</tabstop>
<tabstop>checkShowImagesInline</tabstop>
<tabstop>fr_null_fg</tabstop>
<tabstop>fr_null_bg</tabstop>
<tabstop>txtNull</tabstop>
@@ -1902,20 +1925,25 @@ Can be set to 0 for disabling completion.</string>
<tabstop>spinEditorFontSize</tabstop>
<tabstop>spinLogFontSize</tabstop>
<tabstop>spinTabSize</tabstop>
<tabstop>wrapComboBox</tabstop>
<tabstop>quoteComboBox</tabstop>
<tabstop>checkAutoCompletion</tabstop>
<tabstop>checkCompleteUpper</tabstop>
<tabstop>checkErrorIndicators</tabstop>
<tabstop>checkHorizontalTiling</tabstop>
<tabstop>listExtensions</tabstop>
<tabstop>buttonAddExtension</tabstop>
<tabstop>buttonRemoveExtension</tabstop>
<tabstop>checkRegexDisabled</tabstop>
<tabstop>editRemoteCloneDirectory</tabstop>
<tabstop>buttonRemoteBrowseCloneDirectory</tabstop>
<tabstop>tableCaCerts</tabstop>
<tabstop>checkAllowLoadExtension</tabstop>
<tabstop>tabCertificates</tabstop>
<tabstop>tableClientCerts</tabstop>
<tabstop>buttonClientCertAdd</tabstop>
<tabstop>buttonClientCertRemove</tabstop>
<tabstop>tabWidget</tabstop>
<tabstop>buttonProxy</tabstop>
<tabstop>editRemoteCloneDirectory</tabstop>
<tabstop>buttonRemoteBrowseCloneDirectory</tabstop>
<tabstop>tableCaCerts</tabstop>
</tabstops>
<resources>
<include location="icons/icons.qrc"/>
@@ -1992,12 +2020,12 @@ Can be set to 0 for disabling completion.</string>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>365</x>
<y>207</y>
<x>344</x>
<y>230</y>
</hint>
<hint type="destinationlabel">
<x>108</x>
<y>280</y>
<x>119</x>
<y>273</y>
</hint>
</hints>
</connection>
@@ -2008,8 +2036,8 @@ Can be set to 0 for disabling completion.</string>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>365</x>
<y>207</y>
<x>344</x>
<y>230</y>
</hint>
<hint type="destinationlabel">
<x>365</x>
@@ -2024,8 +2052,8 @@ Can be set to 0 for disabling completion.</string>
<slot>activateRemoteTab(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>161</x>
<y>172</y>
<x>231</x>
<y>393</y>
</hint>
<hint type="destinationlabel">
<x>382</x>
@@ -2040,8 +2068,8 @@ Can be set to 0 for disabling completion.</string>
<slot>addClientCertificate()</slot>
<hints>
<hint type="sourcelabel">
<x>578</x>
<y>315</y>
<x>722</x>
<y>110</y>
</hint>
<hint type="destinationlabel">
<x>596</x>
@@ -2056,8 +2084,8 @@ Can be set to 0 for disabling completion.</string>
<slot>removeClientCertificate()</slot>
<hints>
<hint type="sourcelabel">
<x>578</x>
<y>353</y>
<x>722</x>
<y>139</y>
</hint>
<hint type="destinationlabel">
<x>597</x>
@@ -2072,8 +2100,8 @@ Can be set to 0 for disabling completion.</string>
<slot>chooseRemoteCloneDirectory()</slot>
<hints>
<hint type="sourcelabel">
<x>567</x>
<y>54</y>
<x>732</x>
<y>538</y>
</hint>
<hint type="destinationlabel">
<x>595</x>
@@ -2088,12 +2116,12 @@ Can be set to 0 for disabling completion.</string>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>474</x>
<y>464</y>
<x>642</x>
<y>450</y>
</hint>
<hint type="destinationlabel">
<x>474</x>
<y>492</y>
<x>642</x>
<y>480</y>
</hint>
</hints>
</connection>
@@ -2104,8 +2132,8 @@ Can be set to 0 for disabling completion.</string>
<slot>on_buttonBox_clicked(QAbstractButton*)</slot>
<hints>
<hint type="sourcelabel">
<x>385</x>
<y>591</y>
<x>394</x>
<y>584</y>
</hint>
<hint type="destinationlabel">
<x>385</x>
@@ -2120,8 +2148,8 @@ Can be set to 0 for disabling completion.</string>
<slot>configureProxy()</slot>
<hints>
<hint type="sourcelabel">
<x>385</x>
<y>591</y>
<x>225</x>
<y>506</y>
</hint>
<hint type="destinationlabel">
<x>385</x>

View File

@@ -208,6 +208,8 @@ QVariant Settings::getDefaultValue(const std::string& group, const std::string&
return 5000;
if(name == "complete_threshold")
return 1000;
if(name == "image_preview")
return false;
if(name == "indent_compact")
return false;
if(name == "auto_switch_mode")

View File

@@ -130,31 +130,31 @@ TableBrowser::TableBrowser(QWidget* parent) :
connect(ui->dataTable, &ExtendedTableWidget::selectedRowsToBeDeleted, this, &TableBrowser::deleteRecord);
connect(ui->fontComboBox, &QFontComboBox::currentFontChanged, this, [this](const QFont &font) {
modifyColumnFormat(ui->dataTable->selectedCols(), [font](CondFormat& format) { format.setFontFamily(font.family()); });
modifyColumnFormat(ui->dataTable->colsInSelection(), [font](CondFormat& format) { format.setFontFamily(font.family()); });
});
connect(ui->fontSizeBox, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int pointSize) {
modifyColumnFormat(ui->dataTable->selectedCols(), [pointSize](CondFormat& format) { format.setFontPointSize(pointSize); });
modifyColumnFormat(ui->dataTable->colsInSelection(), [pointSize](CondFormat& format) { format.setFontPointSize(pointSize); });
});
connect(ui->actionFontColor, &QAction::triggered, this, [this]() {
QColor color = QColorDialog::getColor(QColor(m_browseTableModel->data(currentIndex(), Qt::ForegroundRole).toString()));
if(color.isValid())
modifyColumnFormat(ui->dataTable->selectedCols(), [color](CondFormat& format) { format.setForegroundColor(color); });
modifyColumnFormat(ui->dataTable->colsInSelection(), [color](CondFormat& format) { format.setForegroundColor(color); });
});
connect(ui->actionBackgroundColor, &QAction::triggered, this, [this]() {
QColor color = QColorDialog::getColor(QColor(m_browseTableModel->data(currentIndex(), Qt::BackgroundRole).toString()));
if(color.isValid())
modifyColumnFormat(ui->dataTable->selectedCols(), [color](CondFormat& format) { format.setBackgroundColor(color); });
modifyColumnFormat(ui->dataTable->colsInSelection(), [color](CondFormat& format) { format.setBackgroundColor(color); });
});
connect(ui->actionBold, &QAction::toggled, this, [this](bool checked) {
modifyColumnFormat(ui->dataTable->selectedCols(), [checked](CondFormat& format) { format.setBold(checked); });
modifyColumnFormat(ui->dataTable->colsInSelection(), [checked](CondFormat& format) { format.setBold(checked); });
});
connect(ui->actionItalic, &QAction::toggled, this, [this](bool checked) {
modifyColumnFormat(ui->dataTable->selectedCols(), [checked](CondFormat& format) { format.setItalic(checked); });
modifyColumnFormat(ui->dataTable->colsInSelection(), [checked](CondFormat& format) { format.setItalic(checked); });
});
connect(ui->actionUnderline, &QAction::toggled, this, [this](bool checked) {
modifyColumnFormat(ui->dataTable->selectedCols(), [checked](CondFormat& format) { format.setUnderline(checked); });
modifyColumnFormat(ui->dataTable->colsInSelection(), [checked](CondFormat& format) { format.setUnderline(checked); });
});
connect(ui->actionLeftAlign, &QAction::triggered, this, [this]() {
@@ -162,33 +162,33 @@ TableBrowser::TableBrowser(QWidget* parent) :
ui->actionRightAlign->setChecked(false);
ui->actionCenter->setChecked(false);
ui->actionJustify->setChecked(false);
modifyColumnFormat(ui->dataTable->selectedCols(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignLeft); });
modifyColumnFormat(ui->dataTable->colsInSelection(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignLeft); });
});
connect(ui->actionRightAlign, &QAction::triggered, this, [this]() {
ui->actionLeftAlign->setChecked(false);
ui->actionRightAlign->setChecked(true);
ui->actionCenter->setChecked(false);
ui->actionJustify->setChecked(false);
modifyColumnFormat(ui->dataTable->selectedCols(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignRight); });
modifyColumnFormat(ui->dataTable->colsInSelection(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignRight); });
});
connect(ui->actionCenter, &QAction::triggered, this, [this]() {
ui->actionLeftAlign->setChecked(false);
ui->actionRightAlign->setChecked(false);
ui->actionCenter->setChecked(true);
ui->actionJustify->setChecked(false);
modifyColumnFormat(ui->dataTable->selectedCols(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignCenter); });
modifyColumnFormat(ui->dataTable->colsInSelection(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignCenter); });
});
connect(ui->actionJustify, &QAction::triggered, this, [this]() {
ui->actionLeftAlign->setChecked(false);
ui->actionRightAlign->setChecked(false);
ui->actionCenter->setChecked(false);
ui->actionJustify->setChecked(true);
modifyColumnFormat(ui->dataTable->selectedCols(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignJustify); });
modifyColumnFormat(ui->dataTable->colsInSelection(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignJustify); });
});
connect(ui->actionEditCondFormats, &QAction::triggered, this, [this]() { editCondFormats(currentIndex().column()); });
connect(ui->actionClearFormat, &QAction::triggered, this, [this]() {
for (int column : ui->dataTable->selectedCols())
for (int column : ui->dataTable->colsInSelection())
clearAllCondFormats(column);
});
@@ -236,6 +236,41 @@ TableBrowser::TableBrowser(QWidget* parent) :
connect(ui->actionToggleFormatToolbar, &QAction::toggled, ui->formatFrame, &QFrame::setVisible);
ui->actionToggleFormatToolbar->setChecked(false);
ui->formatFrame->setVisible(false);
// Set up find frame
ui->frameFind->hide();
QShortcut* shortcutHideFindFrame = new QShortcut(QKeySequence("ESC"), ui->editFindExpression);
connect(shortcutHideFindFrame, &QShortcut::activated, ui->buttonFindClose, &QToolButton::click);
connect(ui->actionFind, &QAction::triggered, [this](bool checked) {
if(checked)
{
ui->frameFind->show();
ui->editFindExpression->setFocus();
} else {
ui->buttonFindClose->click();
}
});
connect(ui->editFindExpression, &QLineEdit::returnPressed, ui->buttonFindNext, &QToolButton::click);
connect(ui->editFindExpression, &QLineEdit::textChanged, this, [this]() {
// When the text has changed but neither Return nor F3 or similar nor any buttons were pressed, we want to include the current
// cell in the search as well. This makes sure the selected cell does not jump around every time the text is changed but only
// when the current cell does not match the search expression anymore.
find(ui->editFindExpression->text(), true, true);
});
connect(ui->buttonFindClose, &QToolButton::clicked, this, [this](){
ui->dataTable->setFocus();
ui->frameFind->hide();
ui->actionFind->setChecked(false);
});
connect(ui->buttonFindPrevious, &QToolButton::clicked, this, [this](){
find(ui->editFindExpression->text(), false);
});
connect(ui->buttonFindNext, &QToolButton::clicked, this, [this](){
find(ui->editFindExpression->text(), true);
});
}
TableBrowser::~TableBrowser()
@@ -328,6 +363,7 @@ void TableBrowser::setEnabled(bool enable)
ui->actionRefresh->setEnabled(enable);
ui->actionPrintTable->setEnabled(enable);
ui->editGlobalFilter->setEnabled(enable);
ui->actionFind->setEnabled(enable);
updateInsertDeleteRecordButton();
}
@@ -379,7 +415,7 @@ void TableBrowser::updateTable()
}
statusMessage += tr(". Sum: %1; Average: %2; Min: %3; Max: %4").arg(sum).arg(sum/sel.count()).arg(min).arg(max);
}
};
}
emit statusMessageRequested(statusMessage);
});
}
@@ -1310,3 +1346,54 @@ void TableBrowser::jumpToRow(const sqlb::ObjectIdentifier& table, QString column
ui->dataTable->filterHeader()->setFilter(static_cast<size_t>(column_index-obj->fields.begin()+1), QString("=") + value);
updateTable();
}
void TableBrowser::find(const QString& expr, bool forward, bool include_first)
{
// Get the cell from which the search should be started. If there is a selected cell, use that. If there is no selected cell, start at the first cell.
QModelIndex start;
if(ui->dataTable->selectionModel()->hasSelection())
start = ui->dataTable->selectionModel()->selectedIndexes().front();
else
start = m_browseTableModel->index(0, 0);
// Prepare the match flags with all the search settings
Qt::MatchFlags flags = Qt::MatchWrap;
if(ui->checkFindCaseSensitive->isChecked())
flags |= Qt::MatchCaseSensitive;
if(ui->checkFindWholeCell->isChecked())
flags |= Qt::MatchFixedString;
else
flags |= Qt::MatchContains;
if(ui->checkFindRegEx->isChecked())
flags |= Qt::MatchRegExp;
// Prepare list of columns to search in. We only search in non-hidden rows
std::vector<int> column_list;
sqlb::ObjectIdentifier tableName = currentlyBrowsedTableName();
if(browseTableSettings[tableName].showRowid)
column_list.push_back(0);
for(int i=1;i<m_browseTableModel->columnCount();i++)
{
if(browseTableSettings[tableName].hiddenColumns.contains(i) == false)
column_list.push_back(i);
else if(browseTableSettings[tableName].hiddenColumns[i] == false)
column_list.push_back(i);
}
// Perform the actual search using the model class
const auto match = m_browseTableModel->nextMatch(start, column_list, expr, flags, !forward, include_first);
// Select the next match if we found one
if(match.isValid()) {
ui->dataTable->setCurrentIndex(match);
ui->dataTable->scrollTo(match);
}
// Make the expression control red if no results were found
if(match.isValid() || expr == "")
ui->editFindExpression->setStyleSheet("");
else
ui->editFindExpression->setStyleSheet("QLineEdit {color: white; background-color: rgb(255, 102, 102)}");
}

View File

@@ -152,6 +152,9 @@ private slots:
void browseDataSetTableEncoding(bool forAllTables = false);
void browseDataSetDefaultTableEncoding();
private:
void find(const QString& expr, bool forward, bool include_first = false);
private:
Ui::TableBrowser* ui;
QIntValidator* gotoValidator;

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>651</width>
<height>362</height>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
@@ -85,6 +85,7 @@
<addaction name="actionDeleteRecord"/>
<addaction name="separator"/>
<addaction name="actionToggleFormatToolbar"/>
<addaction name="actionFind"/>
</widget>
</item>
<item>
@@ -194,6 +195,152 @@
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frameFind">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>31</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout">
<property name="leftMargin">
<number>1</number>
</property>
<property name="topMargin">
<number>1</number>
</property>
<property name="rightMargin">
<number>1</number>
</property>
<property name="bottomMargin">
<number>1</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="0" column="2">
<widget class="QToolButton" name="buttonFindNext">
<property name="toolTip">
<string>Find next match [Enter, F3]</string>
</property>
<property name="whatsThis">
<string>Find next match with wrapping</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/down</normaloff>:/icons/down</iconset>
</property>
<property name="shortcut">
<string>F3</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QToolButton" name="buttonFindPrevious">
<property name="toolTip">
<string>Find previous match [Shift+F3]</string>
</property>
<property name="whatsThis">
<string>Find previous match with mapping</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/up</normaloff>:/icons/up</iconset>
</property>
<property name="shortcut">
<string>Shift+F3</string>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QCheckBox" name="checkFindRegEx">
<property name="toolTip">
<string>Interpret search pattern as a regular expression</string>
</property>
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When checked, the pattern to find is interpreted as a UNIX regular expression. See &lt;a href=&quot;https://en.wikibooks.org/wiki/Regular_Expressions&quot;&gt;Regular Expression in Wikibooks&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Regular Expression</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QCheckBox" name="checkFindWholeCell">
<property name="whatsThis">
<string>The found pattern must be a whole word</string>
</property>
<property name="text">
<string>Whole Cell</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLineEdit" name="editFindExpression">
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="whatsThis">
<string>Text pattern to find considering the checks in this frame</string>
</property>
<property name="placeholderText">
<string>Find in table</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="8">
<widget class="QToolButton" name="buttonFindClose">
<property name="toolTip">
<string>Close Find Bar</string>
</property>
<property name="text">
<string>Close Find Bar</string>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/close</normaloff>:/icons/close</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="6">
<spacer name="horizontalSpacer_1">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="3">
<widget class="QCheckBox" name="checkFindCaseSensitive">
<property name="whatsThis">
<string>The found pattern must match in letter case</string>
</property>
<property name="text">
<string>Case Sensitive</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
@@ -588,6 +735,24 @@
<enum>Qt::WidgetShortcut</enum>
</property>
</action>
<action name="actionFind">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/find</normaloff>:/icons/find</iconset>
</property>
<property name="text">
<string>Find in cells</string>
</property>
<property name="toolTip">
<string>Open the find tool bar which allows you to search for values in the table view below.</string>
</property>
<property name="shortcut">
<string>Ctrl+F</string>
</property>
</action>
<action name="actionBold">
<property name="checkable">
<bool>true</bool>
@@ -788,6 +953,14 @@
<tabstops>
<tabstop>comboBrowseTable</tabstop>
<tabstop>dataTable</tabstop>
<tabstop>editGlobalFilter</tabstop>
<tabstop>editFindExpression</tabstop>
<tabstop>buttonFindPrevious</tabstop>
<tabstop>buttonFindNext</tabstop>
<tabstop>checkFindCaseSensitive</tabstop>
<tabstop>checkFindWholeCell</tabstop>
<tabstop>checkFindRegEx</tabstop>
<tabstop>buttonFindClose</tabstop>
<tabstop>buttonBegin</tabstop>
<tabstop>buttonPrevious</tabstop>
<tabstop>buttonNext</tabstop>
@@ -806,8 +979,8 @@
<slot>updateTable()</slot>
<hints>
<hint type="sourcelabel">
<x>118</x>
<y>141</y>
<x>159</x>
<y>31</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
@@ -822,8 +995,8 @@
<slot>navigatePrevious()</slot>
<hints>
<hint type="sourcelabel">
<x>86</x>
<y>539</y>
<x>54</x>
<y>358</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
@@ -838,8 +1011,8 @@
<slot>navigateNext()</slot>
<hints>
<hint type="sourcelabel">
<x>183</x>
<y>539</y>
<x>139</x>
<y>358</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
@@ -854,8 +1027,8 @@
<slot>navigateGoto()</slot>
<hints>
<hint type="sourcelabel">
<x>365</x>
<y>539</y>
<x>403</x>
<y>360</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
@@ -870,8 +1043,8 @@
<slot>navigateGoto()</slot>
<hints>
<hint type="sourcelabel">
<x>506</x>
<y>538</y>
<x>550</x>
<y>360</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
@@ -886,8 +1059,8 @@
<slot>navigateEnd()</slot>
<hints>
<hint type="sourcelabel">
<x>223</x>
<y>539</y>
<x>169</x>
<y>358</y>
</hint>
<hint type="destinationlabel">
<x>499</x>
@@ -902,8 +1075,8 @@
<slot>navigateBegin()</slot>
<hints>
<hint type="sourcelabel">
<x>50</x>
<y>539</y>
<x>24</x>
<y>358</y>
</hint>
<hint type="destinationlabel">
<x>499</x>
@@ -1147,7 +1320,7 @@
</hint>
<hint type="destinationlabel">
<x>326</x>
<y>347</y>
<y>291</y>
</hint>
</hints>
</connection>

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

View File

@@ -83,6 +83,8 @@
<file>color_swatch.png</file>
<file>edit_cond_formats.png</file>
<file alias="clear_sorting">clear_sorting.png</file>
<file alias="arrow_bottom">bullet_arrow_bottom.png</file>
<file alias="arrow_top">bullet_arrow_top.png</file>
<file>text_bold.png</file>
<file>text_italic.png</file>
<file>text_underline.png</file>

View File

@@ -53,6 +53,11 @@ std::string escapeIdentifier(const std::string& id)
}
}
std::string escapeString(const std::string& literal)
{
return '\'' + duplicate_char(literal, '\'') + '\'';
}
bool ObjectIdentifier::fromSerialised(const std::string& serialised)
{
auto pos_comma = serialised.find(",");

View File

@@ -20,6 +20,9 @@ char getIdentifierQuoteChar();
// Add quotes to an identifier
std::string escapeIdentifier(const std::string& id);
// Add SQL quotes to a string literal and escape any single quote character
std::string escapeString(const std::string& literal);
// Object identifier consisting of schema name and object name
class ObjectIdentifier
{

View File

@@ -45,6 +45,10 @@ QString escapeIdentifier(const QString& id)
{
return QString::fromStdString(escapeIdentifier(id.toStdString()));
}
QString escapeString(const QString& literal)
{
return QString::fromStdString(escapeString(literal.toStdString()));
}
}
// collation callbacks
@@ -324,7 +328,7 @@ bool DBBrowserDB::attach(const QString& filePath, QString attach_as)
}
}
if(!executeSQL(QString("ATTACH '%1' AS %2 %3").arg(filePath).arg(sqlb::escapeIdentifier(attach_as)).arg(key), false))
if(!executeSQL(QString("ATTACH %1 AS %2 %3").arg(sqlb::escapeString(filePath)).arg(sqlb::escapeIdentifier(attach_as)).arg(key), false))
{
QMessageBox::warning(nullptr, qApp->applicationName(), lastErrorMessage);
return false;
@@ -334,7 +338,7 @@ bool DBBrowserDB::attach(const QString& filePath, QString attach_as)
delete cipherSettings;
#else
// Attach database
if(!executeSQL(QString("ATTACH '%1' AS %2").arg(filePath).arg(sqlb::escapeIdentifier(attach_as)), false))
if(!executeSQL(QString("ATTACH %1 AS %2").arg(sqlb::escapeString(filePath)).arg(sqlb::escapeIdentifier(attach_as)), false))
{
QMessageBox::warning(nullptr, qApp->applicationName(), lastErrorMessage);
return false;

View File

@@ -33,6 +33,7 @@ int collCompare(void* pArg, int sizeA, const void* sA, int sizeB, const void* sB
namespace sqlb
{
QString escapeIdentifier(const QString& id);
QString escapeString(const QString& literal);
}
/// represents a single SQLite database. except when noted otherwise,

View File

@@ -13,6 +13,7 @@
#include <QUrl>
#include <QtConcurrent/QtConcurrentRun>
#include <QProgressDialog>
#include <QRegularExpression>
#include <json.hpp>
#include "RowLoader.h"
@@ -422,6 +423,16 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const
bool isNumber;
value.toDouble(&isNumber);
return static_cast<int>((isNumber ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignVCenter);
} else if(role == Qt::DecorationRole) {
if(!row_available)
return QVariant();
if(Settings::getValue("databrowser", "image_preview").toBool() && !isImageData(cached_row->at(column)).isNull())
{
QImage img;
img.loadFromData(cached_row->at(column));
return QPixmap::fromImage(img);
}
}
@@ -1035,3 +1046,108 @@ void SqliteTableModel::waitUntilIdle () const
{
worker->waitUntilIdle();
}
QModelIndex SqliteTableModel::nextMatch(const QModelIndex& start, const std::vector<int>& column_list, const QString& value, Qt::MatchFlags flags, bool reverse, bool dont_skip_to_next_field) const
{
// Extract flags
bool whole_cell = !(flags & Qt::MatchContains);
bool regex = flags & Qt::MatchRegExp;
Qt::CaseSensitivity case_sensitive = ((flags & Qt::MatchCaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive);
bool wrap = flags & Qt::MatchWrap;
int increment = (reverse ? -1 : 1);
// Prepare the regular expression for regex mode
QRegularExpression reg_exp;
if(regex)
{
reg_exp = QRegularExpression(value, (case_sensitive ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption));
if(!reg_exp.isValid())
return QModelIndex();
if(whole_cell)
{
#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0)
reg_exp.setPattern("\\A(" + reg_exp.pattern() + ")\\Z");
#else
reg_exp.setPattern(QRegularExpression::anchoredPattern(reg_exp.pattern()));
#endif
}
}
// Wait until the row count is there
waitUntilIdle();
// Make sure the start position starts in a column from the list of columns to search in
QModelIndex pos = start;
if(std::find(column_list.begin(), column_list.end(), pos.column()) == column_list.end())
{
// If for some weird reason the start index is not in the column list, we simply use the first column of the column list instead
pos = pos.sibling(pos.row(), reverse ? column_list.back() : column_list.front());
}
// Get the last cell to search in. If wrapping is enabled, we search until we hit the start cell again. If wrapping is not enabled, we start at the last
// cell of the table.
QModelIndex end = (wrap ? pos : index(rowCount(), column_list.back()));
// Loop through all cells for the search
while(true)
{
// Go to the next cell and skip all columns in between which we do not care about. This is done as the first step in order
// to skip the start index when matching the first cell is disabled.
if(dont_skip_to_next_field == false)
{
while(true)
{
// Next cell position
int next_row = pos.row();
int next_column = pos.column() + increment;
// Have we reached the end of the row? Then go to the next one
if(next_column < 0 || next_column >= static_cast<int>(m_headers.size()))
{
next_row += increment;
next_column = (reverse ? column_list.back() : column_list.front());
}
// Have we reached the last row? Then wrap around to the first one
if(wrap && (next_row < 0 || next_row >= rowCount()))
next_row = (reverse ? rowCount()-1 : 0);
// Set next index for search
pos = pos.sibling(next_row, next_column);
// Have we hit the last column? We have not found anything then
if(pos == end)
return QModelIndex();
// Is this a column which we are supposed to search in? If so, stop looking for the next cell and start comparing
if(std::find(column_list.begin(), column_list.end(), next_column) != column_list.end())
break;
}
}
// Make sure the next time we hit the above check, we actuall move on to the next cell and do not skip the loop again.
dont_skip_to_next_field = false;
// Get row from cache. If it is not in the cache, load the next chunk from the database
const size_t row = static_cast<size_t>(pos.row());
if(!m_cache.count(row))
{
triggerCacheLoad(static_cast<int>(row));
waitUntilIdle();
}
const Row* row_data = &m_cache.at(row);
// Get cell data
const size_t column = static_cast<size_t>(pos.column());
QString data = row_data->at(column);
// Perform comparison
if(whole_cell && !regex && data.compare(value, case_sensitive) == 0)
return pos;
else if(!whole_cell && !regex && data.contains(value, case_sensitive))
return pos;
else if(regex && reg_exp.match(data).hasMatch())
return pos;
}
}

View File

@@ -117,6 +117,19 @@ public:
void addCondFormat(int column, const CondFormat& condFormat);
void setCondFormats(int column, const std::vector<CondFormat>& condFormats);
// Search for the specified expression in the given cells. This intended as a replacement for QAbstractItemModel::match() even though
// it does not override it, which - because of the different parameters - is not possible.
// start contains the index to start with, column_list contains the ordered list of the columns to look in, value is the value to search for,
// flags allows to modify the search process (Qt::MatchContains, Qt::MatchRegExp, Qt::MatchCaseSensitive, and Qt::MatchWrap are understood),
// reverse can be set to true to progress through the cells in backwards direction, and dont_skip_to_next_field can be set to true if the current
// cell can be matched as well.
QModelIndex nextMatch(const QModelIndex& start,
const std::vector<int>& column_list,
const QString& value,
Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchContains),
bool reverse = false,
bool dont_skip_to_next_field = false) const;
DBBrowserDB& db() { return m_db; }
public slots:

View File

@@ -182,7 +182,7 @@ void TestTable::parseSQL()
"\tinfo VARCHAR(255) CHECK (info == 'x')\n"
");";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.name(), "hero");
QCOMPARE(tab.rowidColumns(), {"_rowid_"});
@@ -212,7 +212,7 @@ void TestTable::parseSQLdefaultexpr()
"date datetime default CURRENT_TIMESTAMP,"
"zoi integer)";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.name(), "chtest");
QCOMPARE(tab.fields.at(0).name(), "id");
@@ -246,7 +246,7 @@ void TestTable::parseSQLMultiPk()
"PRIMARY KEY(\"id1\",\"id2\")\n"
");";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.name(), "hero");
QCOMPARE(tab.fields.at(0).name(), "id1");
@@ -265,7 +265,7 @@ void TestTable::parseSQLForeignKey()
{
std::string sSQL = "CREATE TABLE grammar_test(id, test, FOREIGN KEY(test) REFERENCES other_table);";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.name(), "grammar_test");
QCOMPARE(tab.fields.at(0).name(), "id");
@@ -276,7 +276,7 @@ void TestTable::parseSQLSingleQuotes()
{
std::string sSQL = "CREATE TABLE 'test'('id','test');";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.name(), "test");
QCOMPARE(tab.fields.at(0).name(), "id");
@@ -287,7 +287,7 @@ void TestTable::parseSQLSquareBrackets()
{
std::string sSQL = "CREATE TABLE [test]([id],[test]);";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.name(), "test");
QCOMPARE(tab.fields.at(0).name(), "id");
@@ -299,7 +299,7 @@ void TestTable::parseSQLKeywordInIdentifier()
{
std::string sSQL = "CREATE TABLE deffered(key integer primary key, if text);";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.name(), "deffered");
QCOMPARE(tab.fields.at(0).name(), "key");
@@ -313,7 +313,7 @@ void TestTable::parseSQLSomeKeywordsInIdentifier()
"`Area of Work` TEXT,"
"`Average Number of Volunteers` INTEGER);";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.name(), "Average Number of Volunteers by Area of Work");
QCOMPARE(tab.fields.at(0).name(), "Area of Work");
@@ -324,7 +324,7 @@ void TestTable::parseSQLWithoutRowid()
{
std::string sSQL = "CREATE TABLE test(a integer primary key, b integer) WITHOUT ROWID;";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.primaryKey()->columnList(), {"a"});
QCOMPARE(tab.rowidColumns(), {"a"});
@@ -337,7 +337,7 @@ void TestTable::parseNonASCIIChars()
"PRIMARY KEY(`Fieldöäüß`)"
");";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.name(), "lösung");
QCOMPARE(tab.fields.at(0).name(), "Fieldöäüß");
@@ -350,7 +350,7 @@ void TestTable::parseNonASCIICharsEs()
"PRIMARY KEY(\"Field áéíóúÁÉÍÓÚñÑçÇ\")"
");";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.name(), "Cigüeñas de Alcalá");
QCOMPARE(tab.fields.at(0).name(), "Field áéíóúÁÉÍÓÚñÑçÇ");
@@ -360,7 +360,7 @@ void TestTable::parseSQLEscapedQuotes()
{
std::string sSql = "CREATE TABLE double_quotes(a text default 'a''a');";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSql))));
Table tab(*Table::parseSQL(sSql));
QCOMPARE(tab.name(), "double_quotes");
QCOMPARE(tab.fields.at(0).name(), "a");
@@ -371,7 +371,7 @@ void TestTable::parseSQLForeignKeys()
{
std::string sql = "CREATE TABLE foreign_key_test(a int, b int, foreign key (a) references x, foreign key (b) references w(y,z) on delete set null);";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql))));
Table tab(*Table::parseSQL(sql));
QCOMPARE(tab.name(), "foreign_key_test");
QCOMPARE(tab.fields.at(0).name(), "a");
@@ -386,7 +386,7 @@ void TestTable::parseSQLCheckConstraint()
{
std::string sql = "CREATE TABLE a (\"b\" text CHECK(\"b\"='A' or \"b\"='B'));";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql))));
Table tab(*Table::parseSQL(sql));
QCOMPARE(tab.name(), "a");
QCOMPARE(tab.fields.at(0).name(), "b");
@@ -398,7 +398,7 @@ void TestTable::parseDefaultValues()
{
std::string sql = "CREATE TABLE test(a int DEFAULT 0, b int DEFAULT -1, c text DEFAULT 'hello', d text DEFAULT '0');";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql))));
Table tab(*Table::parseSQL(sql));
QCOMPARE(tab.name(), "test");
QCOMPARE(tab.fields.at(0).name(), "a");
@@ -422,7 +422,7 @@ void TestTable::createTableWithIn()
"value NVARCHAR(5) CHECK (value IN ('a', 'b', 'c'))"
");";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.name(), "not_working");
QCOMPARE(tab.fields.at(1).check(), "\"value\" IN ('a', 'b', 'c')");
@@ -439,7 +439,7 @@ void TestTable::createTableWithNotLikeConstraint()
"value6 INTEGER CHECK(value6 NOT BETWEEN 1 AND 100)\n"
");";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sSQL))));
Table tab(*Table::parseSQL(sSQL));
QCOMPARE(tab.name(), "hopefully_working");
QCOMPARE(tab.fields.at(0).check(), "\"value\" NOT LIKE 'prefix%'");
@@ -458,7 +458,7 @@ void TestTable::rowValues()
"CHECK((a, b) = (1, 2))\n"
");";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql))));
Table tab(*Table::parseSQL(sql));
QCOMPARE(tab.name(), "test");
QCOMPARE(std::dynamic_pointer_cast<sqlb::CheckConstraint>(tab.constraint({}, sqlb::Constraint::CheckConstraintType))->expression(), "(\"a\", \"b\") = (1, 2)");
@@ -473,7 +473,7 @@ void TestTable::complexExpressions()
"d INTEGER CHECK((((d > 0))))\n"
");";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql))));
Table tab(*Table::parseSQL(sql));
QCOMPARE(tab.name(), "test");
QCOMPARE(tab.fields.at(0).check(), "(\"a\" > 0)");
@@ -488,7 +488,7 @@ void TestTable::datetimeExpression()
"entry INTEGER DEFAULT (DATETIME(CURRENT_TIMESTAMP, 'LOCALTIME'))\n"
");";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql))));
Table tab(*Table::parseSQL(sql));
QCOMPARE(tab.name(), "test");
QCOMPARE(tab.fields.at(0).name(), "entry");
@@ -502,7 +502,7 @@ void TestTable::extraParentheses()
"xy INTEGER DEFAULT (1 + (5) - 4)\n"
");";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql))));
Table tab(*Table::parseSQL(sql));
QCOMPARE(tab.name(), "test");
QCOMPARE(tab.fields.at(0).name(), "xy");
@@ -516,7 +516,7 @@ void TestTable::moduloOperator()
"xy INTEGER DEFAULT (7 % 2)\n"
");";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql))));
Table tab(*Table::parseSQL(sql));
QCOMPARE(tab.name(), "test");
QCOMPARE(tab.fields.at(0).name(), "xy");
@@ -533,7 +533,7 @@ void TestTable::complexExpression()
"CHECK((a = 'S' AND b IS NOT NULL) OR (a IN ('A', 'P')))"
");";
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql))));
Table tab(*Table::parseSQL(sql));
QCOMPARE(tab.name(), "test");
QCOMPARE(tab.fields.at(0).name(), "uuid");
@@ -549,7 +549,7 @@ void TestTable::parseTest()
{
QFETCH(std::string, sql);
Table tab(*(std::dynamic_pointer_cast<sqlb::Table>(Table::parseSQL(sql))));
Table tab(*Table::parseSQL(sql));
QVERIFY(tab.fullyParsed());
}