diff --git a/src/CondFormat.cpp b/src/CondFormat.cpp index 1f963576..db3699f5 100644 --- a/src/CondFormat.cpp +++ b/src/CondFormat.cpp @@ -2,10 +2,29 @@ #include "Settings.h" #include "Data.h" -CondFormat::CondFormat(const QString& filter, const QColor& foreground, const QColor& background, const QString& encoding) +CondFormat::Alignment CondFormat::fromCombinedAlignment(Qt::Alignment align) +{ + if (align.testFlag(Qt::AlignLeft)) + return AlignLeft; + if (align.testFlag(Qt::AlignRight)) + return AlignRight; + if (align.testFlag(Qt::AlignCenter)) + return AlignCenter; + if (align.testFlag(Qt::AlignJustify)) + return AlignJustify; +} + +CondFormat::CondFormat(const QString& filter, + const QColor& foreground, + const QColor& background, + const QFont& font, + const Alignment alignment, + const QString& encoding) : m_filter(filter), m_bgColor(background), - m_fgColor(foreground) + m_fgColor(foreground), + m_font(font), + m_align(alignment) { if (!filter.isEmpty()) m_sqlCondition = filterToSqlCondition(filter, encoding); @@ -115,3 +134,17 @@ QString CondFormat::filterToSqlCondition(const QString& value, const QString& en return whereClause; } } + +Qt::AlignmentFlag CondFormat::alignmentFlag() const +{ + switch (m_align) { + case AlignLeft: + return Qt::AlignLeft; + case AlignCenter: + return Qt::AlignCenter; + case AlignRight: + return Qt::AlignRight; + case AlignJustify: + return Qt::AlignJustify; + } +} diff --git a/src/CondFormat.h b/src/CondFormat.h index 20a81346..edbafb65 100644 --- a/src/CondFormat.h +++ b/src/CondFormat.h @@ -3,13 +3,36 @@ #include #include +#include // Conditional formatting for given format to table cells based on a specified condition. class CondFormat { public: + + enum Alignment { + AlignLeft = 0, + AlignRight, + AlignCenter, + AlignJustify + }; + + // List of alignment texts. Order must be as Alignment definition above. + static QStringList alignmentTexts() { + return {QObject::tr("Left"), QObject::tr("Right"), QObject::tr("Center"), QObject::tr("Justify")}; + }; + + // Get alignment from combined Qt alignment (note that this will lose any combination of our Alignment enum + // with other values present in the flag (e.g. vertical alignment). + static Alignment fromCombinedAlignment(Qt::Alignment align); + CondFormat() {} - explicit CondFormat(const QString& filter, const QColor& foreground, const QColor& background, const QString& encoding = QString()); + explicit CondFormat(const QString& filter, + const QColor& foreground, + const QColor& background, + const QFont& font, + const Alignment alignment = AlignLeft, + const QString& encoding = QString()); static QString filterToSqlCondition(const QString& value, const QString& encoding = QString()); @@ -18,13 +41,32 @@ private: QString m_filter; QColor m_bgColor; QColor m_fgColor; + QFont m_font; + Alignment m_align; public: QString sqlCondition() const { return m_sqlCondition; } QString filter() const { return m_filter; } + QColor backgroundColor() const { return m_bgColor; } QColor foregroundColor() const { return m_fgColor; } + void setBackgroundColor(QColor color) { m_bgColor = color; } + void setForegroundColor(QColor color) { m_fgColor = color; } + bool isBold() const { return m_font.bold(); } + bool isItalic() const { return m_font.italic(); } + bool isUnderline() const { return m_font.underline(); } + void setBold(bool value) { m_font.setBold(value); } + void setItalic(bool value) { m_font.setItalic(value); } + void setUnderline(bool value) { m_font.setUnderline(value); } + + QFont font() const { return m_font; } + void setFontFamily(const QString &family) { m_font.setFamily(family); } + void setFontPointSize(int pointSize) { m_font.setPointSize(pointSize); } + + Alignment alignment() const { return m_align; } + void setAlignment(Alignment value) { m_align = value; } + Qt::AlignmentFlag alignmentFlag() const; }; #endif // CONDFORMAT_H diff --git a/src/CondFormatManager.cpp b/src/CondFormatManager.cpp index 15bfe346..88c298e9 100644 --- a/src/CondFormatManager.cpp +++ b/src/CondFormatManager.cpp @@ -8,6 +8,9 @@ #include #include #include +#include +#include +#include CondFormatManager::CondFormatManager(const std::vector& condFormats, const QString& encoding, QWidget *parent) : QDialog(parent), @@ -20,6 +23,10 @@ CondFormatManager::CondFormatManager(const std::vector& condFormats, for(const CondFormat& aCondFormat : condFormats) addItem(aCondFormat); + // Resize columns to contents, except for the condition + for(int col = ColumnForeground; col < ColumnFilter; ++col) + ui->tableCondFormats->resizeColumnToContents(col); + ui->tableCondFormats->setEditTriggers(QAbstractItemView::AllEditTriggers); connect(ui->buttonAdd, SIGNAL(clicked(bool)), this, SLOT(addNewItem())); @@ -38,22 +45,51 @@ CondFormatManager::~CondFormatManager() void CondFormatManager::addNewItem() { + QFont font = QFont(Settings::getValue("databrowser", "font").toString()); + font.setPointSize(Settings::getValue("databrowser", "fontsize").toInt()); + CondFormat newCondFormat("", QColor(Settings::getValue("databrowser", "reg_fg_colour").toString()), m_condFormatPalette.nextSerialColor(Palette::appHasDarkTheme()), + font, + CondFormat::AlignLeft, m_encoding); addItem(newCondFormat); + + // Resize columns to contents, except for the condition + for(int col = ColumnForeground; col < ColumnFilter; ++col) + ui->tableCondFormats->resizeColumnToContents(col); } void CondFormatManager::addItem(const CondFormat& aCondFormat) { int i = ui->tableCondFormats->topLevelItemCount(); - QTreeWidgetItem *newItem = new QTreeWidgetItem({"", "", aCondFormat.filter()}); + QTreeWidgetItem *newItem = new QTreeWidgetItem(ui->tableCondFormats); newItem->setForeground(ColumnForeground, aCondFormat.foregroundColor()); newItem->setBackground(ColumnForeground, aCondFormat.foregroundColor()); newItem->setForeground(ColumnBackground, aCondFormat.backgroundColor()); newItem->setBackground(ColumnBackground, aCondFormat.backgroundColor()); newItem->setToolTip(ColumnBackground, tr("Click to select color")); newItem->setToolTip(ColumnForeground, tr("Click to select color")); + + QFontComboBox* fontCombo = new QFontComboBox(ui->tableCondFormats); + fontCombo->setCurrentFont(aCondFormat.font()); + ui->tableCondFormats->setItemWidget(newItem, ColumnFont, fontCombo); + + QSpinBox* sizeBox = new QSpinBox(ui->tableCondFormats); + sizeBox->setMinimum(1); + sizeBox->setValue(aCondFormat.font().pointSize()); + ui->tableCondFormats->setItemWidget(newItem, ColumnSize, sizeBox); + + newItem->setCheckState(ColumnBold, aCondFormat.isBold() ? Qt::Checked : Qt::Unchecked); + newItem->setCheckState(ColumnItalic, aCondFormat.isItalic() ? Qt::Checked : Qt::Unchecked); + newItem->setCheckState(ColumnUnderline, aCondFormat.isUnderline() ? Qt::Checked : Qt::Unchecked); + + QComboBox* alignCombo = new QComboBox(ui->tableCondFormats); + alignCombo->addItems(CondFormat::alignmentTexts()); + alignCombo->setCurrentIndex(aCondFormat.alignment()); + ui->tableCondFormats->setItemWidget(newItem, ColumnAlignment, alignCombo); + + newItem->setText(ColumnFilter, aCondFormat.filter()); ui->tableCondFormats->insertTopLevelItem(i, newItem); ui->tableCondFormats->openPersistentEditor(newItem, ColumnFilter); } @@ -64,48 +100,78 @@ void CondFormatManager::removeItem() delete item; } -void CondFormatManager::upItem() +void CondFormatManager::moveItem(int offset) { if (!ui->tableCondFormats->currentIndex().isValid()) return; int selectedRow = ui->tableCondFormats->currentIndex().row(); - if(selectedRow == 0) + int newRow = selectedRow + offset; + if(newRow < 0 || newRow >= ui->tableCondFormats->topLevelItemCount()) return; - QTreeWidgetItem* item; + QTreeWidgetItem* item = ui->tableCondFormats->topLevelItem(selectedRow); + + // Rescue widgets, since they will be deleted, and add them later. + QFontComboBox* fontCombo = qobject_cast(ui->tableCondFormats->itemWidget(item, ColumnFont)); + QFontComboBox* fontCombo2 = new QFontComboBox(ui->tableCondFormats); + fontCombo2->setCurrentFont(fontCombo->currentFont()); + + QSpinBox* sizeBox = qobject_cast(ui->tableCondFormats->itemWidget(item, ColumnSize)); + QSpinBox* sizeBox2 = new QSpinBox(ui->tableCondFormats); + sizeBox2->setValue(sizeBox->value()); + sizeBox2->setMinimum(sizeBox->minimum()); + + QComboBox* alignCombo = qobject_cast(ui->tableCondFormats->itemWidget(item, ColumnAlignment)); + QComboBox* alignCombo2 = new QComboBox(ui->tableCondFormats); + alignCombo2->addItems(CondFormat::alignmentTexts()); + alignCombo2->setCurrentIndex(alignCombo->currentIndex()); + item = ui->tableCondFormats->takeTopLevelItem(selectedRow); - ui->tableCondFormats->insertTopLevelItem(selectedRow-1, item); + ui->tableCondFormats->insertTopLevelItem(newRow, item); + + // Restore widgets and state + ui->tableCondFormats->setItemWidget(item, ColumnFont, fontCombo2); + ui->tableCondFormats->setItemWidget(item, ColumnSize, sizeBox2); + ui->tableCondFormats->setItemWidget(item, ColumnAlignment, alignCombo2); ui->tableCondFormats->openPersistentEditor(item, ColumnFilter); - ui->tableCondFormats->setCurrentIndex(ui->tableCondFormats->currentIndex().sibling(selectedRow-1, + ui->tableCondFormats->setCurrentIndex(ui->tableCondFormats->currentIndex().sibling(newRow, ui->tableCondFormats->currentIndex().column())); } +void CondFormatManager::upItem() +{ + moveItem(-1); +} + void CondFormatManager::downItem() { - if (!ui->tableCondFormats->currentIndex().isValid()) return; - - int selectedRow = ui->tableCondFormats->currentIndex().row(); - if(selectedRow == ui->tableCondFormats->topLevelItemCount() - 1) - return; - - QTreeWidgetItem* item; - item = ui->tableCondFormats->takeTopLevelItem(selectedRow); - ui->tableCondFormats->insertTopLevelItem(selectedRow+1, item); - ui->tableCondFormats->openPersistentEditor(item, ColumnFilter); - ui->tableCondFormats->setCurrentIndex(ui->tableCondFormats->currentIndex().sibling(selectedRow+1, - ui->tableCondFormats->currentIndex().column())); + moveItem(+1); } std::vector CondFormatManager::getCondFormats() { std::vector result; + for (int i = 0; i < ui->tableCondFormats->topLevelItemCount(); ++i) { QTreeWidgetItem* item = ui->tableCondFormats->topLevelItem(i); + + QFontComboBox* fontCombo = qobject_cast(ui->tableCondFormats->itemWidget(item, ColumnFont)); + QSpinBox* sizeBox = qobject_cast(ui->tableCondFormats->itemWidget(item, ColumnSize)); + QFont font = fontCombo->currentFont(); + font.setPointSize(sizeBox->value()); + font.setBold(item->checkState(ColumnBold) == Qt::Checked); + font.setItalic(item->checkState(ColumnItalic) == Qt::Checked); + font.setUnderline(item->checkState(ColumnUnderline) == Qt::Checked); + QComboBox* alignCombo = qobject_cast(ui->tableCondFormats->itemWidget(item, ColumnAlignment)); + result.emplace_back(item->text(ColumnFilter), item->background(ColumnForeground).color(), - item->background(ColumnBackground).color(), m_encoding); + item->background(ColumnBackground).color(), + font, + static_cast(alignCombo->currentIndex()), + m_encoding); } return result; } @@ -124,7 +190,7 @@ void CondFormatManager::itemClicked(QTreeWidgetItem* item, int column) } break; } - case ColumnFilter: + default: // Nothing to do break; } diff --git a/src/CondFormatManager.h b/src/CondFormatManager.h index 429c16f4..344d2e9c 100644 --- a/src/CondFormatManager.h +++ b/src/CondFormatManager.h @@ -26,18 +26,25 @@ public: private: enum Columns { ColumnForeground = 0, - ColumnBackground = 1, - ColumnFilter = 2 + ColumnBackground, + ColumnFont, + ColumnSize, + ColumnBold, + ColumnItalic, + ColumnUnderline, + ColumnAlignment, + ColumnFilter }; Ui::CondFormatManager *ui; std::vector m_condFormats; Palette m_condFormatPalette; QString m_encoding; - + private slots: void addNewItem(); void addItem(const CondFormat& aCondFormat); void removeItem(); + void moveItem(int offset); void upItem(); void downItem(); void on_buttonBox_clicked(QAbstractButton* button); diff --git a/src/CondFormatManager.ui b/src/CondFormatManager.ui index 319dd725..e36c0297 100644 --- a/src/CondFormatManager.ui +++ b/src/CondFormatManager.ui @@ -6,8 +6,8 @@ 0 0 - 578 - 463 + 700 + 400 @@ -17,7 +17,7 @@ - This dialog allows creating and editing conditional formats, where the cell text and background will be colored based on one or more conditions. Conditional formats can be moved up and down, where those at higher rows take precedence over those at lower. + This dialog allows creating and editing conditional formats. Each cell style will be selected by the first accomplished condition for that cell data. Conditional formats can be moved up and down, where those at higher rows take precedence over those at lower. An empty condition applies to all values. true @@ -150,19 +150,76 @@ false - - 150 + + 25 + Foreground + + Text color + Background + + Background color + + + Font + + + + + Size + + + + + + + + Bold + + + + :/icons/text_bold.png:/icons/text_bold.png + + + + + + + + Italic + + + + :/icons/text_italic.png:/icons/text_italic.png + + + + + + + + Underline + + + + :/icons/text_underline.png:/icons/text_underline.png + + + + + Alignment + + Condition diff --git a/src/ExtendedTableWidget.cpp b/src/ExtendedTableWidget.cpp index efe5055c..a6980e7d 100644 --- a/src/ExtendedTableWidget.cpp +++ b/src/ExtendedTableWidget.cpp @@ -849,6 +849,14 @@ std::unordered_set ExtendedTableWidget::selectedCols() const return selectedCols; } +std::unordered_set ExtendedTableWidget::colsInSelection() const +{ + std::unordered_set 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 diff --git a/src/ExtendedTableWidget.h b/src/ExtendedTableWidget.h index 86d650f7..2091954e 100644 --- a/src/ExtendedTableWidget.h +++ b/src/ExtendedTableWidget.h @@ -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 selectedCols() const; + // Get set of columns traversed by selection (only some cells in column has to be selected) + std::unordered_set colsInSelection() const; + int numVisibleRows() const; void sortByColumns(const std::vector& columns); @@ -70,6 +74,8 @@ signals: void openFileFromDropEvent(QString); void selectedRowsToBeDeleted(); void editCondFormats(int column); + // Make the inherited protected signal public + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); private: void copyMimeData(const QModelIndexList& fromIndices, QMimeData* mimeData, const bool withHeaders, const bool inSQL); diff --git a/src/FilterLineEdit.cpp b/src/FilterLineEdit.cpp index 39008437..c2717182 100644 --- a/src/FilterLineEdit.cpp +++ b/src/FilterLineEdit.cpp @@ -184,7 +184,7 @@ void FilterLineEdit::showContextMenu(const QPoint &pos) emit clearAllCondFormats(); }); } else { - conditionalFormatAction = new QAction(QIcon(":/icons/cond_formats"), tr("Use for Conditional Format"), editContextMenu); + conditionalFormatAction = new QAction(QIcon(":/icons/add_cond_format"), tr("Use for Conditional Format"), editContextMenu); connect(conditionalFormatAction, &QAction::triggered, [&]() { emit addFilterAsCondFormat(text()); }); diff --git a/src/FilterTableHeader.cpp b/src/FilterTableHeader.cpp index cd6894b3..c95f40b4 100644 --- a/src/FilterTableHeader.cpp +++ b/src/FilterTableHeader.cpp @@ -17,6 +17,9 @@ FilterTableHeader::FilterTableHeader(QTableView* parent) : // Make sure to not automatically resize the columns according to the contents setSectionResizeMode(QHeaderView::Interactive); + // Highlight column headers of selected cells to emulate spreadsheet behaviour + setHighlightSections(true); + // Do some connects: Basically just resize and reposition the input widgets whenever anything changes connect(this, &FilterTableHeader::sectionResized, this, &FilterTableHeader::adjustPositions); connect(parent->horizontalScrollBar(), &QScrollBar::valueChanged, this, &FilterTableHeader::adjustPositions); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index e397a73f..3f39987e 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -2246,10 +2246,22 @@ static void loadBrowseDataTableSettings(BrowseDataTableSettings& settings, QXmlS int index = xml.attributes().value("index").toInt(); 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(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()), - settings.encoding); + font, align, settings.encoding); xml.skipCurrentElement(); } } @@ -2587,6 +2599,8 @@ static void saveBrowseDataTableSettings(const BrowseDataTableSettings& object, Q 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(); diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 360d0f29..b0689085 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -124,6 +124,21 @@ You can drag SQL statements from an object row and drop them into other applicat Browse Data + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + diff --git a/src/TableBrowser.cpp b/src/TableBrowser.cpp index f716184b..38509007 100644 --- a/src/TableBrowser.cpp +++ b/src/TableBrowser.cpp @@ -18,6 +18,7 @@ #include #include #include +#include QMap TableBrowser::browseTableSettings; QString TableBrowser::defaultBrowseTableEncoding; @@ -128,6 +129,115 @@ TableBrowser::TableBrowser(QWidget* parent) : connect(ui->dataTable, &ExtendedTableWidget::openFileFromDropEvent, this, &TableBrowser::requestFileOpen); 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()); }); + }); + connect(ui->fontSizeBox, QOverload::of(&QSpinBox::valueChanged), this, [this](int 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->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->colsInSelection(), [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); }); + }); + connect(ui->actionItalic, &QAction::toggled, this, [this](bool checked) { + modifyColumnFormat(ui->dataTable->colsInSelection(), [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); }); + }); + + connect(ui->actionLeftAlign, &QAction::triggered, this, [this]() { + ui->actionLeftAlign->setChecked(true); + ui->actionRightAlign->setChecked(false); + ui->actionCenter->setChecked(false); + ui->actionJustify->setChecked(false); + 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->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->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->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->colsInSelection()) + clearAllCondFormats(column); + }); + + connect(ui->dataTable, &ExtendedTableWidget::currentChanged, this, [this](const QModelIndex ¤t, const QModelIndex &) { + // Get cell current format for updating the format toolbar values. Block signals, so the format change is not reapplied. + QFont font; + font.fromString(m_browseTableModel->data(current, Qt::FontRole).toString()); + ui->fontComboBox->blockSignals(true); + ui->fontComboBox->setCurrentFont(font); + ui->fontComboBox->blockSignals(false); + + ui->fontSizeBox->blockSignals(true); + ui->fontSizeBox->setValue(font.pointSize()); + ui->fontSizeBox->blockSignals(false); + + ui->actionBold->blockSignals(true); + ui->actionBold->setChecked(font.bold()); + ui->actionBold->blockSignals(false); + + ui->actionItalic->blockSignals(true); + ui->actionItalic->setChecked(font.italic()); + ui->actionItalic->blockSignals(false); + + ui->actionUnderline->blockSignals(true); + ui->actionUnderline->setChecked(font.underline()); + ui->actionUnderline->blockSignals(false); + + Qt::Alignment align = Qt::Alignment(m_browseTableModel->data(current, Qt::TextAlignmentRole).toInt()); + ui->actionLeftAlign->blockSignals(true); + ui->actionLeftAlign->setChecked(align.testFlag(Qt::AlignLeft)); + ui->actionLeftAlign->blockSignals(false); + + ui->actionRightAlign->blockSignals(true); + ui->actionRightAlign->setChecked(align.testFlag(Qt::AlignRight)); + ui->actionRightAlign->blockSignals(false); + + ui->actionCenter->blockSignals(true); + ui->actionCenter->setChecked(align.testFlag(Qt::AlignCenter)); + ui->actionCenter->blockSignals(false); + + ui->actionJustify->blockSignals(true); + ui->actionJustify->setChecked(align.testFlag(Qt::AlignJustify)); + ui->actionJustify->blockSignals(false); + }); + + connect(ui->actionToggleFormatToolbar, &QAction::toggled, ui->formatFrame, &QFrame::setVisible); + ui->actionToggleFormatToolbar->setChecked(false); + ui->formatFrame->setVisible(false); + // Set up find frame ui->frameFind->hide(); @@ -473,10 +583,15 @@ void TableBrowser::updateFilter(int column, const QString& value) void TableBrowser::addCondFormat(int column, const QString& value) { + QFont font = QFont(Settings::getValue("databrowser", "font").toString()); + font.setPointSize(Settings::getValue("databrowser", "fontsize").toInt()); + // Create automatically a new conditional format with the next serial background color according to the theme and the regular foreground - // color in the settings. + // color and font in the settings. CondFormat newCondFormat(value, QColor(Settings::getValue("databrowser", "reg_fg_colour").toString()), m_condFormatPalette.nextSerialColor(Palette::appHasDarkTheme()), + font, + CondFormat::AlignLeft, m_browseTableModel->encoding()); m_browseTableModel->addCondFormat(column, newCondFormat); browseTableSettings[currentlyBrowsedTableName()].condFormats[column].push_back(newCondFormat); @@ -494,6 +609,8 @@ void TableBrowser::editCondFormats(int column) { CondFormatManager condFormatDialog(browseTableSettings[currentlyBrowsedTableName()].condFormats[column], m_browseTableModel->encoding(), this); + condFormatDialog.setWindowTitle(tr("Conditional formats for \"%1\""). + arg(m_browseTableModel->headerData(column, Qt::Horizontal).toString())); if (condFormatDialog.exec()) { std::vector condFormatVector = condFormatDialog.getCondFormats(); m_browseTableModel->setCondFormats(column, condFormatVector); @@ -502,6 +619,38 @@ void TableBrowser::editCondFormats(int column) } } +void TableBrowser::modifyColumnFormat(std::unordered_set columns, std::function changeFunction) +{ + for (int column : columns) { + std::vector& columnFormats = browseTableSettings[currentlyBrowsedTableName()].condFormats[column]; + + auto it = std::find_if(columnFormats.begin(), columnFormats.end(), [](const CondFormat& format) { + return format.sqlCondition().isEmpty(); + }); + if(it != columnFormats.end()) { + changeFunction(*it); + m_browseTableModel->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_browseTableModel->data(currentIndex().sibling(currentIndex().row(), 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_browseTableModel->encoding()); + changeFunction(newCondFormat); + m_browseTableModel->addCondFormat(column, newCondFormat); + browseTableSettings[currentlyBrowsedTableName()].condFormats[column].push_back(newCondFormat); + } + } + emit projectModified(); +} + void TableBrowser::updateRecordsetLabel() { // Get all the numbers, i.e. the number of the first row and the last row as well as the total number of rows @@ -1239,9 +1388,10 @@ void TableBrowser::find(const QString& expr, bool forward, bool include_first) 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()) + 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(""); diff --git a/src/TableBrowser.h b/src/TableBrowser.h index 91b15c7a..e6164410 100644 --- a/src/TableBrowser.h +++ b/src/TableBrowser.h @@ -9,6 +9,8 @@ #include #include +#include + class DBBrowserDB; class ExtendedTableWidget; class SqliteTableModel; @@ -172,6 +174,8 @@ private: static QString defaultBrowseTableEncoding; Palette m_condFormatPalette; + + void modifyColumnFormat(std::unordered_set columns, std::function changeFunction); }; #endif diff --git a/src/TableBrowser.ui b/src/TableBrowser.ui index 40ca842b..3ea88432 100644 --- a/src/TableBrowser.ui +++ b/src/TableBrowser.ui @@ -6,7 +6,7 @@ 0 0 - 617 + 651 400 @@ -14,6 +14,9 @@ Browse Data + + 1 + 0 @@ -28,6 +31,12 @@ + + 2 + + + 2 + @@ -75,6 +84,7 @@ + @@ -99,6 +109,64 @@ + + + + + 5 + + + 2 + + + 0 + + + 2 + + + 0 + + + + + + + + + 50 + 16777215 + + + + 1 + + + + + + + Qt::ToolButtonIconOnly + + + + + + + + + + + + + + + + + + + + @@ -275,6 +343,18 @@ + + 2 + + + 2 + + + 2 + + + 2 + @@ -673,6 +753,186 @@ Ctrl+F + + + true + + + + :/icons/text_bold.png:/icons/text_bold.png + + + Bold + + + Bold + + + Ctrl+B + + + + + true + + + + :/icons/text_italic.png:/icons/text_italic.png + + + Italic + + + Italic + + + + + true + + + + :/icons/text_underline.png:/icons/text_underline.png + + + Underline + + + Underline + + + Ctrl+U + + + + + true + + + + :/icons/text_align_right.png:/icons/text_align_right.png + + + Align Right + + + Align Right + + + + + true + + + + :/icons/text_align_left.png:/icons/text_align_left.png + + + Align Left + + + Align Left + + + + + true + + + + :/icons/text_align_center.png:/icons/text_align_center.png + + + Center Horizontally + + + Center Horizontally + + + + + true + + + + :/icons/text_align_justify.png:/icons/text_align_justify.png + + + Justify + + + Justify + + + + + + :/icons/edit_cond_formats:/icons/edit_cond_formats + + + Edit Conditional Formats... + + + Edit Conditional Formats... + + + + + + :/icons/clear_cond_formats:/icons/clear_cond_formats + + + Clear Format + + + Clear All Conditional Formats + + + + + + :/icons/foreground_color:/icons/foreground_color + + + Font Color + + + Font Color + + + + + + :/icons/background_color:/icons/background_color + + + Background Color + + + Background Color + + + + + true + + + + :/icons/cond_formats:/icons/cond_formats + + + Toggle Format Toolbar + + + Show/hide format toolbar + + + This button shows or hides the formatting toolbar of the Data Browser + + + This button shows or hides the formatting toolbar of the Data Browser + + diff --git a/src/icons/clear_cond_formats.png b/src/icons/clear_cond_formats.png deleted file mode 100644 index 1646b12c..00000000 Binary files a/src/icons/clear_cond_formats.png and /dev/null differ diff --git a/src/icons/icons.qrc b/src/icons/icons.qrc index ce7ceebf..4e59acb3 100644 --- a/src/icons/icons.qrc +++ b/src/icons/icons.qrc @@ -80,11 +80,23 @@ page_foreign_key.png save_all.png page_white_text.png - color_swatch.png - clear_cond_formats.png - edit_cond_formats.png + color_swatch.png + edit_cond_formats.png clear_sorting.png bullet_arrow_bottom.png bullet_arrow_top.png + text_bold.png + text_italic.png + text_underline.png + text_align_center.png + text_align_justify.png + text_align_left.png + text_align_right.png + page_paintbrush.png + text_paintbrush.png + style.png + style_edit.png + style_delete.png + style_add.png diff --git a/src/icons/page_paintbrush.png b/src/icons/page_paintbrush.png new file mode 100644 index 00000000..246a2f0b Binary files /dev/null and b/src/icons/page_paintbrush.png differ diff --git a/src/icons/style.png b/src/icons/style.png new file mode 100644 index 00000000..81e41de7 Binary files /dev/null and b/src/icons/style.png differ diff --git a/src/icons/style_add.png b/src/icons/style_add.png new file mode 100644 index 00000000..e0369c6b Binary files /dev/null and b/src/icons/style_add.png differ diff --git a/src/icons/style_delete.png b/src/icons/style_delete.png new file mode 100644 index 00000000..640f187e Binary files /dev/null and b/src/icons/style_delete.png differ diff --git a/src/icons/style_edit.png b/src/icons/style_edit.png new file mode 100644 index 00000000..25bb5b67 Binary files /dev/null and b/src/icons/style_edit.png differ diff --git a/src/icons/text_align_center.png b/src/icons/text_align_center.png new file mode 100644 index 00000000..57beb381 Binary files /dev/null and b/src/icons/text_align_center.png differ diff --git a/src/icons/text_align_justify.png b/src/icons/text_align_justify.png new file mode 100644 index 00000000..2fbdd692 Binary files /dev/null and b/src/icons/text_align_justify.png differ diff --git a/src/icons/text_align_left.png b/src/icons/text_align_left.png new file mode 100644 index 00000000..6c8fcc11 Binary files /dev/null and b/src/icons/text_align_left.png differ diff --git a/src/icons/text_align_right.png b/src/icons/text_align_right.png new file mode 100644 index 00000000..a1502571 Binary files /dev/null and b/src/icons/text_align_right.png differ diff --git a/src/icons/text_bold.png b/src/icons/text_bold.png new file mode 100644 index 00000000..889ae80e Binary files /dev/null and b/src/icons/text_bold.png differ diff --git a/src/icons/text_italic.png b/src/icons/text_italic.png new file mode 100644 index 00000000..8482ac8c Binary files /dev/null and b/src/icons/text_italic.png differ diff --git a/src/icons/text_paintbrush.png b/src/icons/text_paintbrush.png new file mode 100644 index 00000000..61876930 Binary files /dev/null and b/src/icons/text_paintbrush.png differ diff --git a/src/icons/text_underline.png b/src/icons/text_underline.png new file mode 100644 index 00000000..90d0df28 Binary files /dev/null and b/src/icons/text_underline.png differ diff --git a/src/sqlitetablemodel.cpp b/src/sqlitetablemodel.cpp index 65ddf9eb..eacfdd02 100644 --- a/src/sqlitetablemodel.cpp +++ b/src/sqlitetablemodel.cpp @@ -275,13 +275,17 @@ QVariant SqliteTableModel::headerData(int section, Qt::Orientation orientation, return QString("%1").arg(section + 1); } -QColor SqliteTableModel::getMatchingCondFormatColor(int column, const QString& value, int role) const +QVariant SqliteTableModel::getMatchingCondFormat(int column, const QString& value, int role) const { + if (m_mCondFormats.find(column) == m_mCondFormats.end()) + return QVariant(); + bool isNumber; - value.toFloat(&isNumber); + value.toDouble(&isNumber); QString sql; + // For each conditional format for this column, - // if the condition matches the current data, return the associated colour. + // if the condition matches the current data, return the associated format. for (const CondFormat& eachCondFormat : m_mCondFormats.at(column)) { if (isNumber && !eachCondFormat.sqlCondition().contains("'")) sql = QString("SELECT %1 %2").arg(value, eachCondFormat.sqlCondition()); @@ -291,9 +295,18 @@ QColor SqliteTableModel::getMatchingCondFormatColor(int column, const QString& v // Empty filter means: apply format to any row. // Query the DB for the condition, waiting in case there is a loading in progress. if (eachCondFormat.filter().isEmpty() || m_db.querySingleValueFromDb(sql, false, DBBrowserDB::Wait) == "1") - return role == Qt::ForegroundRole ? eachCondFormat.foregroundColor() : eachCondFormat.backgroundColor(); + switch (role) { + case Qt::ForegroundRole: + return eachCondFormat.foregroundColor(); + case Qt::BackgroundRole: + return eachCondFormat.backgroundColor(); + case Qt::FontRole: + return eachCondFormat.font(); + case Qt::TextAlignmentRole: + return static_cast(eachCondFormat.alignmentFlag() | Qt::AlignVCenter); + } } - return QColor(); + return QVariant(); } QVariant SqliteTableModel::data(const QModelIndex &index, int role) const @@ -347,6 +360,14 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const QFont font; if(!row_available || cached_row->at(column).isNull() || nosync_isBinary(index)) font.setItalic(true); + else { + QString value = cached_row->at(column); + // Unlock before querying from DB + lock.unlock(); + QVariant condFormatFont = getMatchingCondFormat(index.column(), value, role); + if (condFormatFont.isValid()) + return condFormatFont; + } return font; } else if(role == Qt::ForegroundRole) { if(!row_available) @@ -359,7 +380,7 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const QString value = cached_row->at(column); // Unlock before querying from DB lock.unlock(); - QColor condFormatColor = getMatchingCondFormatColor(index.column(), value, role); + QVariant condFormatColor = getMatchingCondFormat(index.column(), value, role); if (condFormatColor.isValid()) return condFormatColor; } @@ -376,7 +397,7 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const QString value = cached_row->at(column); // Unlock before querying from DB lock.unlock(); - QColor condFormatColor = getMatchingCondFormatColor(index.column(), value, role); + QVariant condFormatColor = getMatchingCondFormat(index.column(), value, role); if (condFormatColor.isValid()) return condFormatColor; } @@ -391,6 +412,17 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const .arg(QKeySequence(Qt::CTRL).toString(QKeySequence::NativeText)); else return QString(); + } else if (role == Qt::TextAlignmentRole) { + // Align horizontally according to conditional format or default (left for text and right for numbers) + // Align vertically to the center, which displays better. + QString value = cached_row->at(column); + lock.unlock(); + QVariant condFormat = getMatchingCondFormat(index.column(), value, role); + if (condFormat.isValid()) + return condFormat; + bool isNumber; + value.toDouble(&isNumber); + return static_cast((isNumber ? Qt::AlignRight : Qt::AlignLeft) | Qt::AlignVCenter); } else if(role == Qt::DecorationRole) { if(!row_available) return QVariant(); @@ -403,6 +435,7 @@ QVariant SqliteTableModel::data(const QModelIndex &index, int role) const } } + return QVariant(); } @@ -802,7 +835,15 @@ std::vector SqliteTableModel::getColumns(std::shared_ptr p void SqliteTableModel::addCondFormat(int column, const CondFormat& condFormat) { - m_mCondFormats[column].push_back(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) { + return format.sqlCondition() == condFormat.sqlCondition(); + }); + if(it != m_mCondFormats[column].end()) { + *it = condFormat; + } else + m_mCondFormats[column].push_back(condFormat); emit layoutChanged(); } diff --git a/src/sqlitetablemodel.h b/src/sqlitetablemodel.h index 1ef41a80..2fa7cfc8 100644 --- a/src/sqlitetablemodel.h +++ b/src/sqlitetablemodel.h @@ -3,7 +3,7 @@ #include #include -#include + #include #include #include @@ -163,9 +163,9 @@ private: QByteArray encode(const QByteArray& str) const; QByteArray decode(const QByteArray& str) const; - // Return matching conditional format color or invalid color, otherwise. - // Only Qt::ForegroundRole and Qt::BackgroundRole are expected in role (Qt::ItemDataRole) - QColor getMatchingCondFormatColor(int column, const QString& value, int role) const; + // Return matching conditional format color/font or invalid value, otherwise. + // Only format roles are expected in role (Qt::ItemDataRole) + QVariant getMatchingCondFormat(int column, const QString& value, int role) const; DBBrowserDB& m_db;