mirror of
https://github.com/sqlitebrowser/sqlitebrowser.git
synced 2026-01-20 02:50:46 -06:00
Formats conditioned to row-id values to allow free cell formatting
The conditional formatting is extended to cover free single-cell formatting. The row-id formats have precedence over regular conditional formats. In the styling toolbar, when single cells are selected, row-id formats are created or updated. When entire columns are selected, regular conditional formats are instead. Clearing formats for entire columns, remove both. For single cells, only corresponding row-id formats. New row-id formats are also saved to project files and loaded. See issue #1976
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
#include "Settings.h"
|
||||
#include "Data.h"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
CondFormat::Alignment CondFormat::fromCombinedAlignment(Qt::Alignment align)
|
||||
{
|
||||
if (align.testFlag(Qt::AlignLeft))
|
||||
@@ -30,6 +32,22 @@ CondFormat::CondFormat(const QString& filter,
|
||||
m_sqlCondition = filterToSqlCondition(filter, encoding);
|
||||
}
|
||||
|
||||
CondFormat::CondFormat(const QString& filter,
|
||||
const QAbstractTableModel* model,
|
||||
const QModelIndex index,
|
||||
const QString& encoding)
|
||||
: m_filter(filter)
|
||||
{
|
||||
|
||||
if (!filter.isEmpty())
|
||||
m_sqlCondition = filterToSqlCondition(filter, encoding);
|
||||
|
||||
m_bgColor = QColor(model->data(index, Qt::BackgroundRole).toString());
|
||||
m_fgColor = QColor(model->data(index, Qt::ForegroundRole).toString());
|
||||
m_align = fromCombinedAlignment(Qt::Alignment(model->data(index, Qt::TextAlignmentRole).toInt()));
|
||||
m_font.fromString(model->data(index, Qt::FontRole).toString());
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
#include <QString>
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QModelIndex>
|
||||
|
||||
class QAbstractTableModel;
|
||||
|
||||
// Conditional formatting for given format to table cells based on a specified condition.
|
||||
class CondFormat
|
||||
@@ -27,12 +30,18 @@ public:
|
||||
static Alignment fromCombinedAlignment(Qt::Alignment align);
|
||||
|
||||
CondFormat() {}
|
||||
explicit CondFormat(const QString& filter,
|
||||
const QColor& foreground,
|
||||
const QColor& background,
|
||||
const QFont& font,
|
||||
const Alignment alignment = AlignLeft,
|
||||
const QString& encoding = QString());
|
||||
CondFormat(const QString& filter,
|
||||
const QColor& foreground,
|
||||
const QColor& background,
|
||||
const QFont& font,
|
||||
const Alignment alignment = AlignLeft,
|
||||
const QString& encoding = QString());
|
||||
|
||||
// Create a new CondFormat from values obtained from the model
|
||||
CondFormat(const QString& filter,
|
||||
const QAbstractTableModel* model,
|
||||
const QModelIndex index,
|
||||
const QString& encoding = QString());
|
||||
|
||||
static QString filterToSqlCondition(const QString& value, const QString& encoding = QString());
|
||||
|
||||
|
||||
@@ -2195,6 +2195,38 @@ void MainWindow::on_actionDonatePatreon_triggered()
|
||||
QDesktopServices::openUrl(QUrl("https://www.patreon.com/bePatron?u=11578749"));
|
||||
}
|
||||
|
||||
static void loadCondFormatMap(BrowseDataTableSettings::CondFormatMap& condFormats, QXmlStreamReader& xml, const QString& encoding)
|
||||
{
|
||||
const QStringRef name = xml.name();
|
||||
|
||||
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != name) {
|
||||
if (xml.name() == "column") {
|
||||
size_t index = xml.attributes().value("index").toUInt();
|
||||
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != "column") {
|
||||
if(xml.name() == "format") {
|
||||
QFont font;
|
||||
if (xml.attributes().hasAttribute("font"))
|
||||
font.fromString(xml.attributes().value("font").toString());
|
||||
else
|
||||
Settings::getValue("databrowser", "font").toString();
|
||||
|
||||
CondFormat::Alignment align;
|
||||
if (xml.attributes().hasAttribute("align"))
|
||||
align = static_cast<CondFormat::Alignment>(xml.attributes().value("align").toInt());
|
||||
else
|
||||
align = CondFormat::AlignLeft;
|
||||
|
||||
condFormats[index].emplace_back(xml.attributes().value("condition").toString(),
|
||||
QColor(xml.attributes().value("foreground").toString()),
|
||||
QColor(xml.attributes().value("background").toString()),
|
||||
font, align, encoding);
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, QXmlStreamReader& xml)
|
||||
{
|
||||
// TODO Remove this in the near future. This file format was only created temporarily by the nightlies from the late 3.11 development period.
|
||||
@@ -2240,32 +2272,9 @@ static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, QXmlS
|
||||
}
|
||||
}
|
||||
} else if(xml.name() == "conditional_formats") {
|
||||
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != "conditional_formats") {
|
||||
if (xml.name() == "column") {
|
||||
size_t index = xml.attributes().value("index").toUInt();
|
||||
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != "column") {
|
||||
if(xml.name() == "format") {
|
||||
QFont font;
|
||||
if (xml.attributes().hasAttribute("font"))
|
||||
font.fromString(xml.attributes().value("font").toString());
|
||||
else
|
||||
Settings::getValue("databrowser", "font").toString();
|
||||
|
||||
CondFormat::Alignment align;
|
||||
if (xml.attributes().hasAttribute("align"))
|
||||
align = static_cast<CondFormat::Alignment>(xml.attributes().value("align").toInt());
|
||||
else
|
||||
align = CondFormat::AlignLeft;
|
||||
|
||||
settings.condFormats[index].emplace_back(xml.attributes().value("condition").toString(),
|
||||
QColor(xml.attributes().value("foreground").toString()),
|
||||
QColor(xml.attributes().value("background").toString()),
|
||||
font, align, settings.encoding);
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loadCondFormatMap(settings.condFormats, xml, settings.encoding);
|
||||
} else if(xml.name() == "row_id_formats") {
|
||||
loadCondFormatMap(settings.rowIdFormats, xml, settings.encoding);
|
||||
} else if(xml.name() == "display_formats") {
|
||||
while(xml.readNext() != QXmlStreamReader::EndElement && xml.name() != "display_formats") {
|
||||
if (xml.name() == "column") {
|
||||
@@ -2556,6 +2565,26 @@ static void saveDbTreeState(const QTreeView* tree, QXmlStreamWriter& xml, QModel
|
||||
}
|
||||
}
|
||||
|
||||
static void saveCondFormatMap(const QString& elementName, const BrowseDataTableSettings::CondFormatMap& condFormats, QXmlStreamWriter& xml)
|
||||
{
|
||||
xml.writeStartElement(elementName);
|
||||
for(auto iter=condFormats.constBegin(); iter!=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("background", format.backgroundColor().name());
|
||||
xml.writeAttribute("foreground", format.foregroundColor().name());
|
||||
xml.writeAttribute("font", format.font().toString());
|
||||
xml.writeAttribute("align", QString().setNum(format.alignment()));
|
||||
xml.writeEndElement();
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
|
||||
static void saveBrowseDataTableSettings(const BrowseDataTableSettings& object, QXmlStreamWriter& xml)
|
||||
{
|
||||
xml.writeAttribute("show_row_id", QString::number(object.showRowid));
|
||||
@@ -2589,22 +2618,8 @@ 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("background", format.backgroundColor().name());
|
||||
xml.writeAttribute("foreground", format.foregroundColor().name());
|
||||
xml.writeAttribute("font", format.font().toString());
|
||||
xml.writeAttribute("align", QString().setNum(format.alignment()));
|
||||
xml.writeEndElement();
|
||||
}
|
||||
xml.writeEndElement();
|
||||
}
|
||||
xml.writeEndElement();
|
||||
saveCondFormatMap("conditional_formats", object.condFormats, xml);
|
||||
saveCondFormatMap("row_id_formats", object.rowIdFormats, xml);
|
||||
xml.writeStartElement("display_formats");
|
||||
for(auto iter=object.displayFormats.constBegin(); iter!=object.displayFormats.constEnd(); ++iter) {
|
||||
xml.writeStartElement("column");
|
||||
|
||||
@@ -92,7 +92,7 @@ TableBrowser::TableBrowser(QWidget* parent) :
|
||||
|
||||
// Set up filters
|
||||
connect(ui->dataTable->filterHeader(), &FilterTableHeader::filterChanged, this, &TableBrowser::updateFilter);
|
||||
connect(ui->dataTable->filterHeader(), &FilterTableHeader::addCondFormat, this, &TableBrowser::addCondFormat);
|
||||
connect(ui->dataTable->filterHeader(), &FilterTableHeader::addCondFormat, this, &TableBrowser::addCondFormatFromFilter);
|
||||
connect(ui->dataTable->filterHeader(), &FilterTableHeader::allCondFormatsCleared, this, &TableBrowser::clearAllCondFormats);
|
||||
connect(ui->dataTable->filterHeader(), &FilterTableHeader::condFormatsEdited, this, &TableBrowser::editCondFormats);
|
||||
connect(ui->dataTable, &ExtendedTableWidget::editCondFormats, this, &TableBrowser::editCondFormats);
|
||||
@@ -130,32 +130,32 @@ 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->colsInSelection(), [font](CondFormat& format) { format.setFontFamily(font.family()); });
|
||||
modifyFormat([font](CondFormat& format) { format.setFontFamily(font.family()); });
|
||||
});
|
||||
connect(ui->fontSizeBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
|
||||
[this](int pointSize) {
|
||||
modifyColumnFormat(ui->dataTable->colsInSelection(), [pointSize](CondFormat& format) { format.setFontPointSize(pointSize); });
|
||||
modifyFormat([pointSize](CondFormat& format) { format.setFontPointSize(pointSize); });
|
||||
});
|
||||
|
||||
connect(ui->actionFontColor, &QAction::triggered, this, [this]() {
|
||||
QColor color = QColorDialog::getColor(QColor(m_model->data(currentIndex(), Qt::ForegroundRole).toString()));
|
||||
if(color.isValid())
|
||||
modifyColumnFormat(ui->dataTable->colsInSelection(), [color](CondFormat& format) { format.setForegroundColor(color); });
|
||||
modifyFormat([color](CondFormat& format) { format.setForegroundColor(color); });
|
||||
});
|
||||
connect(ui->actionBackgroundColor, &QAction::triggered, this, [this]() {
|
||||
QColor color = QColorDialog::getColor(QColor(m_model->data(currentIndex(), Qt::BackgroundRole).toString()));
|
||||
if(color.isValid())
|
||||
modifyColumnFormat(ui->dataTable->colsInSelection(), [color](CondFormat& format) { format.setBackgroundColor(color); });
|
||||
modifyFormat([color](CondFormat& format) { format.setBackgroundColor(color); });
|
||||
});
|
||||
|
||||
connect(ui->actionBold, &QAction::toggled, this, [this](bool checked) {
|
||||
modifyColumnFormat(ui->dataTable->colsInSelection(), [checked](CondFormat& format) { format.setBold(checked); });
|
||||
modifyFormat([checked](CondFormat& format) { format.setBold(checked); });
|
||||
});
|
||||
connect(ui->actionItalic, &QAction::toggled, this, [this](bool checked) {
|
||||
modifyColumnFormat(ui->dataTable->colsInSelection(), [checked](CondFormat& format) { format.setItalic(checked); });
|
||||
modifyFormat([checked](CondFormat& format) { format.setItalic(checked); });
|
||||
});
|
||||
connect(ui->actionUnderline, &QAction::toggled, this, [this](bool checked) {
|
||||
modifyColumnFormat(ui->dataTable->colsInSelection(), [checked](CondFormat& format) { format.setUnderline(checked); });
|
||||
modifyFormat([checked](CondFormat& format) { format.setUnderline(checked); });
|
||||
});
|
||||
|
||||
connect(ui->actionLeftAlign, &QAction::triggered, this, [this]() {
|
||||
@@ -163,34 +163,37 @@ TableBrowser::TableBrowser(QWidget* parent) :
|
||||
ui->actionRightAlign->setChecked(false);
|
||||
ui->actionCenter->setChecked(false);
|
||||
ui->actionJustify->setChecked(false);
|
||||
modifyColumnFormat(ui->dataTable->colsInSelection(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignLeft); });
|
||||
modifyFormat([](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->colsInSelection(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignRight); });
|
||||
modifyFormat([](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->colsInSelection(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignCenter); });
|
||||
modifyFormat([](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->colsInSelection(), [](CondFormat& format) { format.setAlignment(CondFormat::AlignJustify); });
|
||||
modifyFormat([](CondFormat& format) { format.setAlignment(CondFormat::AlignJustify); });
|
||||
});
|
||||
|
||||
connect(ui->actionEditCondFormats, &QAction::triggered, this, [this]() { editCondFormats(static_cast<size_t>(currentIndex().column())); });
|
||||
connect(ui->actionClearFormat, &QAction::triggered, this, [this]() {
|
||||
for (size_t column : ui->dataTable->colsInSelection())
|
||||
for (const size_t column : ui->dataTable->selectedCols())
|
||||
clearAllCondFormats(column);
|
||||
for (const QModelIndex& index : ui->dataTable->selectionModel()->selectedIndexes())
|
||||
clearRowIdFormats(index);
|
||||
|
||||
});
|
||||
|
||||
connect(ui->dataTable, &ExtendedTableWidget::currentIndexChanged, this, [this](const QModelIndex ¤t, const QModelIndex &) {
|
||||
@@ -582,7 +585,7 @@ void TableBrowser::updateFilter(size_t column, const QString& value)
|
||||
applySettings(settings, true);
|
||||
}
|
||||
|
||||
void TableBrowser::addCondFormat(size_t column, const QString& value)
|
||||
void TableBrowser::addCondFormatFromFilter(size_t column, const QString& value)
|
||||
{
|
||||
QFont font = QFont(Settings::getValue("databrowser", "font").toString());
|
||||
font.setPointSize(Settings::getValue("databrowser", "fontsize").toInt());
|
||||
@@ -594,59 +597,86 @@ void TableBrowser::addCondFormat(size_t column, const QString& value)
|
||||
font,
|
||||
CondFormat::AlignLeft,
|
||||
m_model->encoding());
|
||||
m_model->addCondFormat(column, newCondFormat);
|
||||
m_settings[currentlyBrowsedTableName()].condFormats[column].push_back(newCondFormat);
|
||||
addCondFormat(false, column, newCondFormat);
|
||||
}
|
||||
|
||||
void TableBrowser::addCondFormat(bool isRowIdFormat, size_t column, const CondFormat& newCondFormat)
|
||||
{
|
||||
BrowseDataTableSettings& settings = m_settings[currentlyBrowsedTableName()];
|
||||
std::vector<CondFormat>& formats = isRowIdFormat ? settings.rowIdFormats[column] : settings.condFormats[column];
|
||||
m_model->addCondFormat(isRowIdFormat, column, newCondFormat);
|
||||
// Conditionless formats are pushed back and others inserted at the begining, so they aren't eclipsed.
|
||||
if (newCondFormat.filter().isEmpty())
|
||||
formats.push_back(newCondFormat);
|
||||
else
|
||||
formats.insert(formats.begin(), newCondFormat);
|
||||
}
|
||||
|
||||
void TableBrowser::clearAllCondFormats(size_t column)
|
||||
{
|
||||
std::vector<CondFormat> emptyCondFormatVector = std::vector<CondFormat>();
|
||||
m_model->setCondFormats(column, emptyCondFormatVector);
|
||||
m_model->setCondFormats(false, column, emptyCondFormatVector);
|
||||
m_settings[currentlyBrowsedTableName()].condFormats[column].clear();
|
||||
emit projectModified();
|
||||
}
|
||||
|
||||
void TableBrowser::clearRowIdFormats(const QModelIndex index)
|
||||
{
|
||||
|
||||
std::vector<CondFormat>& rowIdFormats = m_settings[currentlyBrowsedTableName()].rowIdFormats[index.column()];
|
||||
rowIdFormats.erase(std::remove_if(rowIdFormats.begin(), rowIdFormats.end(), [&](const CondFormat& format) {
|
||||
return format.filter() == QString("=%1").arg(m_model->data(index.sibling(index.row(), 0)).toString());
|
||||
}), rowIdFormats.end());
|
||||
m_model->setCondFormats(true, index.column(), rowIdFormats);
|
||||
emit projectModified();
|
||||
|
||||
}
|
||||
|
||||
void TableBrowser::editCondFormats(size_t column)
|
||||
{
|
||||
CondFormatManager condFormatDialog(m_settings[currentlyBrowsedTableName()].condFormats[column],
|
||||
m_model->encoding(), this);
|
||||
condFormatDialog.setWindowTitle(tr("Conditional formats for \"%1\"").
|
||||
arg(m_model->headerData(static_cast<int>(column), Qt::Horizontal).toString()));
|
||||
arg(m_model->headerData(static_cast<int>(column), Qt::Horizontal, Qt::EditRole).toString()));
|
||||
if (condFormatDialog.exec()) {
|
||||
std::vector<CondFormat> condFormatVector = condFormatDialog.getCondFormats();
|
||||
m_model->setCondFormats(column, condFormatVector);
|
||||
m_model->setCondFormats(false, column, condFormatVector);
|
||||
m_settings[currentlyBrowsedTableName()].condFormats[column] = condFormatVector;
|
||||
emit projectModified();
|
||||
}
|
||||
}
|
||||
|
||||
void TableBrowser::modifyColumnFormat(std::unordered_set<size_t> columns, std::function<void(CondFormat&)> changeFunction)
|
||||
void TableBrowser::modifySingleFormat(const bool isRowIdFormat, const QString& filter, const QModelIndex refIndex, std::function<void(CondFormat&)> changeFunction)
|
||||
{
|
||||
for (size_t column : columns) {
|
||||
std::vector<CondFormat>& columnFormats = m_settings[currentlyBrowsedTableName()].condFormats[column];
|
||||
BrowseDataTableSettings& settings = m_settings[currentlyBrowsedTableName()];
|
||||
std::vector<CondFormat>& formats = isRowIdFormat ? settings.rowIdFormats[refIndex.column()] : settings.condFormats[refIndex.column()];
|
||||
auto it = std::find_if(formats.begin(), formats.end(), [&filter](const CondFormat& format) {
|
||||
return format.filter() == filter;
|
||||
});
|
||||
if(it != formats.end()) {
|
||||
changeFunction(*it);
|
||||
m_model->addCondFormat(isRowIdFormat, refIndex.column(), *it);
|
||||
} else {
|
||||
// Create a new conditional format based on the current reference index and then modify it as requested using the passed function.
|
||||
CondFormat newCondFormat(filter, m_model, refIndex, m_model->encoding());
|
||||
changeFunction(newCondFormat);
|
||||
addCondFormat(isRowIdFormat, refIndex.column(), newCondFormat);
|
||||
}
|
||||
}
|
||||
|
||||
auto it = std::find_if(columnFormats.begin(), columnFormats.end(), [](const CondFormat& format) {
|
||||
return format.sqlCondition().isEmpty();
|
||||
});
|
||||
if(it != columnFormats.end()) {
|
||||
changeFunction(*it);
|
||||
m_model->addCondFormat(column, *it);
|
||||
} else {
|
||||
// Create a new conditional format based on defaults and then modify it as requested using the passed function.
|
||||
// Alignment is get from the current column since the default is different from text and numbers.
|
||||
QFont font = QFont(Settings::getValue("databrowser", "font").toString());
|
||||
font.setPointSize(Settings::getValue("databrowser", "fontsize").toInt());
|
||||
Qt::Alignment align = Qt::Alignment(m_model->data(currentIndex().sibling(currentIndex().row(), static_cast<int>(column)),
|
||||
Qt::TextAlignmentRole).toInt());
|
||||
|
||||
CondFormat newCondFormat(QString(""), QColor(Settings::getValue("databrowser", "reg_fg_colour").toString()),
|
||||
QColor(Settings::getValue("databrowser", "reg_bg_colour").toString()),
|
||||
font,
|
||||
CondFormat::fromCombinedAlignment(align),
|
||||
m_model->encoding());
|
||||
changeFunction(newCondFormat);
|
||||
m_model->addCondFormat(column, newCondFormat);
|
||||
m_settings[currentlyBrowsedTableName()].condFormats[column].push_back(newCondFormat);
|
||||
void TableBrowser::modifyFormat(std::function<void(CondFormat&)> changeFunction)
|
||||
{
|
||||
// Modify the conditional formats from entirely selected columns having the current filter value,
|
||||
// or modify the row-id formats of selected cells, when the selection does not cover entire columns.
|
||||
const std::unordered_set<size_t>& columns = ui->dataTable->selectedCols();
|
||||
if (columns.size() > 0) {
|
||||
for (size_t column : columns) {
|
||||
const QString& filter = m_settings[currentlyBrowsedTableName()].filterValues.value(static_cast<int>(column));
|
||||
modifySingleFormat(false, filter, currentIndex().sibling(currentIndex().row(), static_cast<int>(column)), changeFunction);
|
||||
}
|
||||
} else {
|
||||
for(const QModelIndex& index : ui->dataTable->selectionModel()->selectedIndexes()) {
|
||||
const QString filter = QString("=%1").arg(m_model->data(index.sibling(index.row(), 0)).toString());
|
||||
modifySingleFormat(true, filter, index, changeFunction);
|
||||
}
|
||||
}
|
||||
emit projectModified();
|
||||
@@ -721,19 +751,23 @@ void TableBrowser::applySettings(const BrowseDataTableSettings& storedData, bool
|
||||
for(auto filterIt=storedData.filterValues.constBegin();filterIt!=storedData.filterValues.constEnd();++filterIt)
|
||||
filterHeader->setFilter(static_cast<size_t>(filterIt.key()), filterIt.value());
|
||||
|
||||
// Conditional formats
|
||||
// Regular conditional formats
|
||||
for(auto formatIt=storedData.condFormats.constBegin(); formatIt!=storedData.condFormats.constEnd(); ++formatIt)
|
||||
m_model->setCondFormats(formatIt.key(), formatIt.value());
|
||||
m_model->setCondFormats(false, formatIt.key(), formatIt.value());
|
||||
|
||||
filterHeader->blockSignals(oldState);
|
||||
// Row Id formats
|
||||
for(auto formatIt=storedData.rowIdFormats.constBegin(); formatIt!=storedData.rowIdFormats.constEnd(); ++formatIt)
|
||||
m_model->setCondFormats(true, formatIt.key(), formatIt.value());
|
||||
|
||||
ui->editGlobalFilter->blockSignals(true);
|
||||
QString text;
|
||||
for(const auto& f : storedData.globalFilters)
|
||||
text += f + " ";
|
||||
text.chop(1);
|
||||
ui->editGlobalFilter->setText(text);
|
||||
ui->editGlobalFilter->blockSignals(false);
|
||||
filterHeader->blockSignals(oldState);
|
||||
|
||||
ui->editGlobalFilter->blockSignals(true);
|
||||
QString text;
|
||||
for(const auto& f : storedData.globalFilters)
|
||||
text += f + " ";
|
||||
text.chop(1);
|
||||
ui->editGlobalFilter->setText(text);
|
||||
ui->editGlobalFilter->blockSignals(false);
|
||||
}
|
||||
|
||||
// Encoding
|
||||
|
||||
@@ -25,10 +25,12 @@ class TableBrowser;
|
||||
|
||||
struct BrowseDataTableSettings
|
||||
{
|
||||
using CondFormatMap = QMap<size_t, std::vector<CondFormat>>;
|
||||
sqlb::Query query; // NOTE: We only store the sort order in here (for now)
|
||||
QMap<int, int> columnWidths;
|
||||
QMap<int, QString> filterValues;
|
||||
QMap<size_t, std::vector<CondFormat>> condFormats;
|
||||
CondFormatMap condFormats;
|
||||
CondFormatMap rowIdFormats;
|
||||
QMap<int, QString> displayFormats;
|
||||
bool showRowid;
|
||||
QString encoding;
|
||||
@@ -121,8 +123,10 @@ signals:
|
||||
private slots:
|
||||
void clear();
|
||||
void updateFilter(size_t column, const QString& value);
|
||||
void addCondFormat(size_t column, const QString& value);
|
||||
void addCondFormatFromFilter(size_t column, const QString& value);
|
||||
void addCondFormat(bool isRowIdFormat, size_t column, const CondFormat& newCondFormat);
|
||||
void clearAllCondFormats(size_t column);
|
||||
void clearRowIdFormats(const QModelIndex index);
|
||||
void editCondFormats(size_t column);
|
||||
void applySettings(const BrowseDataTableSettings& storedData, bool skipFilters = false);
|
||||
void enableEditing(bool enable_edit);
|
||||
@@ -176,7 +180,9 @@ private:
|
||||
|
||||
Palette m_condFormatPalette;
|
||||
|
||||
void modifyColumnFormat(std::unordered_set<size_t> columns, std::function<void(CondFormat&)> changeFunction);
|
||||
void modifySingleFormat(const bool isRowIdFormat, const QString& filter, const QModelIndex refIndex,
|
||||
std::function<void(CondFormat&)> changeFunction);
|
||||
void modifyFormat(std::function<void(CondFormat&)> changeFunction);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -875,6 +875,9 @@
|
||||
<property name="toolTip">
|
||||
<string>Edit Conditional Formats...</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Edit conditional formats for the current column</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionClearFormat">
|
||||
<property name="icon">
|
||||
@@ -885,7 +888,13 @@
|
||||
<string>Clear Format</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Clear All Conditional Formats</string>
|
||||
<string>Clear All Formats</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Clear all cell formatting from selected cells and all conditional formats from selected columns</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Clear all cell formatting from selected cells and all conditional formats from selected columns</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionFontColor">
|
||||
|
||||
@@ -112,6 +112,7 @@ void SqliteTableModel::reset()
|
||||
m_headers.clear();
|
||||
m_vDataTypes.clear();
|
||||
m_mCondFormats.clear();
|
||||
m_mRowIdFormats.clear();
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
@@ -256,9 +257,9 @@ QVariant SqliteTableModel::headerData(int section, Qt::Orientation orientation,
|
||||
return QString::number(section + 1);
|
||||
}
|
||||
|
||||
QVariant SqliteTableModel::getMatchingCondFormat(size_t column, const QString& value, int role) const
|
||||
QVariant SqliteTableModel::getMatchingCondFormat(const std::map<size_t, std::vector<CondFormat>>& mCondFormats, size_t column, const QString& value, int role) const
|
||||
{
|
||||
if (!m_mCondFormats.count(column))
|
||||
if (!mCondFormats.count(column))
|
||||
return QVariant();
|
||||
|
||||
bool isNumber;
|
||||
@@ -267,7 +268,7 @@ QVariant SqliteTableModel::getMatchingCondFormat(size_t column, const QString& v
|
||||
|
||||
// For each conditional format for this column,
|
||||
// if the condition matches the current data, return the associated format.
|
||||
for (const CondFormat& eachCondFormat : m_mCondFormats.at(column)) {
|
||||
for (const CondFormat& eachCondFormat : mCondFormats.at(column)) {
|
||||
if (isNumber && !eachCondFormat.sqlCondition().contains("'"))
|
||||
sql = QString("SELECT %1 %2").arg(value, eachCondFormat.sqlCondition());
|
||||
else
|
||||
@@ -290,6 +291,26 @@ QVariant SqliteTableModel::getMatchingCondFormat(size_t column, const QString& v
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant SqliteTableModel::getMatchingCondFormat(size_t row, size_t column, const QString& value, int role) const
|
||||
{
|
||||
QVariant format;
|
||||
// Check first for a row-id format and when there is none, for a conditional format.
|
||||
if (m_mRowIdFormats.count(column)) {
|
||||
QMutexLocker lock(&m_mutexDataCache);
|
||||
const bool row_available = m_cache.count(row);
|
||||
const QByteArray blank_data("");
|
||||
const QByteArray& row_id_data = row_available ? m_cache.at(row).at(0) : blank_data;
|
||||
lock.unlock();
|
||||
format = getMatchingCondFormat(m_mRowIdFormats, column, row_id_data, role);
|
||||
if (format.isValid())
|
||||
return format;
|
||||
}
|
||||
if (m_mCondFormats.count(column))
|
||||
return getMatchingCondFormat(m_mCondFormats, column, value, role);
|
||||
else
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant SqliteTableModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
@@ -335,7 +356,7 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const
|
||||
else {
|
||||
// Unlock before querying from DB
|
||||
lock.unlock();
|
||||
QVariant condFormatFont = getMatchingCondFormat(column, data, role);
|
||||
QVariant condFormatFont = getMatchingCondFormat(row, column, data, role);
|
||||
if (condFormatFont.isValid())
|
||||
return condFormatFont;
|
||||
}
|
||||
@@ -347,10 +368,10 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const
|
||||
return m_nullFgColour;
|
||||
else if (isBinary(data))
|
||||
return m_binFgColour;
|
||||
else if (m_mCondFormats.count(column)) {
|
||||
else {
|
||||
// Unlock before querying from DB
|
||||
lock.unlock();
|
||||
QVariant condFormatColor = getMatchingCondFormat(column, data, role);
|
||||
QVariant condFormatColor = getMatchingCondFormat(row, column, data, role);
|
||||
if (condFormatColor.isValid())
|
||||
return condFormatColor;
|
||||
}
|
||||
@@ -363,10 +384,10 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const
|
||||
return m_nullBgColour;
|
||||
else if (isBinary(data))
|
||||
return m_binBgColour;
|
||||
else if (m_mCondFormats.count(column)) {
|
||||
else {
|
||||
// Unlock before querying from DB
|
||||
lock.unlock();
|
||||
QVariant condFormatColor = getMatchingCondFormat(column, data, role);
|
||||
QVariant condFormatColor = getMatchingCondFormat(row, column, data, role);
|
||||
if (condFormatColor.isValid())
|
||||
return condFormatColor;
|
||||
}
|
||||
@@ -383,7 +404,7 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const
|
||||
// Align horizontally according to conditional format or default (left for text and right for numbers)
|
||||
// Align vertically to the center, which displays better.
|
||||
lock.unlock();
|
||||
QVariant condFormat = getMatchingCondFormat(column, data, role);
|
||||
QVariant condFormat = getMatchingCondFormat(row, column, data, role);
|
||||
if (condFormat.isValid())
|
||||
return condFormat;
|
||||
bool isNumber;
|
||||
@@ -799,23 +820,38 @@ std::vector<std::string> SqliteTableModel::getColumns(std::shared_ptr<sqlite3> p
|
||||
return listColumns;
|
||||
}
|
||||
|
||||
void SqliteTableModel::addCondFormat(size_t column, const CondFormat& condFormat)
|
||||
void addCondFormatToMap(std::map<size_t, std::vector<CondFormat>>& mCondFormats, size_t column, const CondFormat& condFormat)
|
||||
{
|
||||
// If the condition is already present in the vector, update that entry and respect the order, since two entries with the same
|
||||
// condition do not make sense.
|
||||
auto it = std::find_if(m_mCondFormats[column].begin(), m_mCondFormats[column].end(), [condFormat](const CondFormat& format) {
|
||||
auto it = std::find_if(mCondFormats[column].begin(), mCondFormats[column].end(), [condFormat](const CondFormat& format) {
|
||||
return format.sqlCondition() == condFormat.sqlCondition();
|
||||
});
|
||||
if(it != m_mCondFormats[column].end()) {
|
||||
// Replace cond-format if present. push it back if it's a conditionless format (apply to every cell in column) or insert
|
||||
// as first element otherwise.
|
||||
if(it != mCondFormats[column].end()) {
|
||||
*it = condFormat;
|
||||
} else
|
||||
m_mCondFormats[column].push_back(condFormat);
|
||||
} else if (condFormat.filter().isEmpty())
|
||||
mCondFormats[column].push_back(condFormat);
|
||||
else
|
||||
mCondFormats[column].insert(mCondFormats[column].begin(), condFormat);
|
||||
}
|
||||
|
||||
void SqliteTableModel::addCondFormat(const bool isRowIdFormat, size_t column, const CondFormat& condFormat)
|
||||
{
|
||||
if(isRowIdFormat)
|
||||
addCondFormatToMap(m_mRowIdFormats, column, condFormat);
|
||||
else
|
||||
addCondFormatToMap(m_mCondFormats, column, condFormat);
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
void SqliteTableModel::setCondFormats(size_t column, const std::vector<CondFormat>& condFormats)
|
||||
void SqliteTableModel::setCondFormats(const bool isRowIdFormat, size_t column, const std::vector<CondFormat>& condFormats)
|
||||
{
|
||||
m_mCondFormats[column] = condFormats;
|
||||
if(isRowIdFormat)
|
||||
m_mRowIdFormats[column] = condFormats;
|
||||
else
|
||||
m_mCondFormats[column] = condFormats;
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -113,8 +113,11 @@ public:
|
||||
// Helper function for removing all comments from a SQL query
|
||||
static void removeCommentsFromQuery(QString& query);
|
||||
|
||||
void addCondFormat(size_t column, const CondFormat& condFormat);
|
||||
void setCondFormats(size_t column, const std::vector<CondFormat>& condFormats);
|
||||
// Conditional formats are of two kinds: regular conditional formats (including condition-free formats applying to any value in the
|
||||
// column) and formats applying to a particular row-id and which have always precedence over the first kind and whose filter apply
|
||||
// to the row-id column.
|
||||
void addCondFormat(const bool isRowIdFormat, size_t column, const CondFormat& condFormat);
|
||||
void setCondFormats(const bool isRowIdFormat, size_t 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.
|
||||
@@ -166,7 +169,8 @@ private:
|
||||
|
||||
// Return matching conditional format color/font or invalid value, otherwise.
|
||||
// Only format roles are expected in role (Qt::ItemDataRole)
|
||||
QVariant getMatchingCondFormat(size_t column, const QString& value, int role) const;
|
||||
QVariant getMatchingCondFormat(size_t row, size_t column, const QString& value, int role) const;
|
||||
QVariant getMatchingCondFormat(const std::map<size_t, std::vector<CondFormat>>& mCondFormats, size_t column, const QString& value, int role) const;
|
||||
|
||||
DBBrowserDB& m_db;
|
||||
|
||||
@@ -199,6 +203,7 @@ private:
|
||||
QString m_sQuery;
|
||||
std::vector<int> m_vDataTypes;
|
||||
std::map<size_t, std::vector<CondFormat>> m_mCondFormats;
|
||||
std::map<size_t, std::vector<CondFormat>> m_mRowIdFormats;
|
||||
sqlb::Query m_query;
|
||||
|
||||
QString m_encoding;
|
||||
|
||||
Reference in New Issue
Block a user