Initial commit of BudgetPro

This commit is contained in:
Javi
2026-05-24 23:21:33 +02:00
commit f3096faee6
575 changed files with 90288 additions and 0 deletions
+366
View File
@@ -0,0 +1,366 @@
#include "fiebdc.h"
//#include "globalvars.h" // Asumiendo que globalVars está definido aquí
#include <QDate>
FIEBDC::FIEBDC(QString filename, Budget* budget) {
__budget = budget;
__filename = filename;
if (__budget != nullptr) {
__budget->filename = __filename;
}
__cancel = false;
__format_list = {"FIEBDC-3/95", "FIEBDC-3/98", "FIEBDC-3/2002",
"FIEBDC-3/2004", "FIEBDC-3/2007"};
__character_sets_dict = {{"ANSI", "cp1252"},
{"850", "850"},
{"437", "cp437"}};
__file_format = "FIEBDC-3/2007";
//__generator = globalVars.name + " " + globalVars.version;
__character_set = "850";
initializePatterns();
}
void FIEBDC::initializePatterns() {
__pattern = {
{"control_tilde", QRegularExpression(R"(((\r\n)| |\t)+~)")},
{"control_vbar", QRegularExpression(R"(((\r\n)| |\t)+\|)")},
{"control_backslash", QRegularExpression(R"(((\r\n)| |\t)+\\)")},
{"valid_code", QRegularExpression(R"([^A-Za-z0-9ñÑ.$#%&_])")},
{"special_char", QRegularExpression(R"([#%&])")},
{"no_float", QRegularExpression(R"([^\-0-9.])")},
{"formula", QRegularExpression(R"(.*[^0123456789\.()\+\-\*/\^abcdp ].*)")},
{"comment", QRegularExpression(R"(#.*\r\n)")},
{"empty_line", QRegularExpression(R"((\r\n) *\r\n)")},
{"space_before_backslash", QRegularExpression(R"(( )+\\)")},
{"space_after_backslash", QRegularExpression(R"(\\( )+)")},
{"start_noend_backslash", QRegularExpression(R"((\r\n\\\.*[^\\\])\r\n)")},
{"end_oper", QRegularExpression(R"((\+|-|\*|/|/^|@|&|<|>|<=|>=|=|!) *\r\n)")},
{"matricial_var", QRegularExpression(R"((\r\n *[%|\$][A-ZÑ].*=.*, ) *\r\n)")},
{"descomposition", QRegularExpression(R"(^([^:]+):(.*)$)")},
{"var", QRegularExpression(R"(^([$%][A-ZÑ][()0-9, ]*)=(.*)$)")},
{"after_first_tilde", QRegularExpression(R"(^[^~]*~)")},
{"end_control", QRegularExpression(R"(((\r\n)| |\t)+$)")}
};
}
void FIEBDC::cancel() {
__cancel = true;
}
QString FIEBDC::eraseControlCharacters(const QString& input) {
// Implementación de la función para eliminar caracteres de control
QString string = input;
string = string.replace(__pattern["control_tilde"], "~");
string = string.replace(__pattern["control_vbar"], "|");
// Uncomment if control_backslash processing is needed
// string = string.replace(__pattern["control_backslash"], "\\");
return string;
}
bool FIEBDC::validateCode(const QString& code) {
// Implementación de la función para validar el código
QRegularExpression re("^[A-Z0-9]+$");
return re.match(code).hasMatch();
}
QDate FIEBDC::parseDate(const QString& dateStr) {
/*parseDate(date)
date: in the format:
uneven len: add a Leading 0
len = 8 DDMMYYYY
len <= 6 DDMMYY “80/20”. >80 -> >1980 <80 -> <2080
len < 5 MMYY
len < 3 YY
Test date string and return a tuple (YYYY, MM, DD)
or None if the date format is invalid
*/
if (dateStr.isEmpty()) {
return QDate();
}
QString paddedDate = dateStr;
if (dateStr.length() % 2 != 0) {
paddedDate.prepend("0"); // Agregar un 0 inicial si la longitud es impar
}
// Intentar con diferentes formatos según la longitud
if (paddedDate.length() == 8) {
return QDate::fromString(paddedDate, "ddMMyyyy");
} else if (paddedDate.length() == 6) {
QString yearPrefix = paddedDate.right(2).toInt() < 80 ? "20" : "19";
return QDate::fromString(yearPrefix + paddedDate, "yyyyMMdd");
} else if (paddedDate.length() == 4) {
QString yearPrefix = paddedDate.right(2).toInt() < 80 ? "20" : "19";
return QDate::fromString(yearPrefix + paddedDate, "yyyyMM");
} else if (paddedDate.length() == 2) {
QString yearPrefix = paddedDate.toInt() < 80 ? "20" : "19";
return QDate::fromString(yearPrefix + paddedDate, "yyyy");
}
return QDate(); // Fecha inválida
}
void FIEBDC::parseRecord(const QString& record) {
/*
parseRecord(record, interface)
record: the record line readed from the file whith the format:
type|field|field|subfield\\subfield|...
[a] nothing or "a"
{a} zero or more #-#twice#-# "a"
<a> one or more #-#twice#-# "a"
Types: V C D Y M N T K L Q J G E X B F A
V: Property and Version
1- [File_Owner]
2- Format_Version[\\DDMMYYYY]
3- [Program_Generator]
4- [Header]\\{Title\\}
5- [Chaters_set]
6- [Comment]
C: Record:
1- Code{\\Code}
2- [Unit]
3- [Summary]
4- {Price\\}
5- {Date\\}
6- [Type]
D or Y: DECOMPOSITION or ADD DECOMPOSITION
1- Parent Code
2- <Child Code\\ [Factor]\\ [Yield]>
M or N: MEASURE or ADD MEASURE
1- [Parent Code\\]Child Code
2- {Path\}
3- TOTAL MEASURE
4- {Type\\Comment\\Unit\\Length\\Width\\Height\\}
5- [Label]
T: Text
1- Code
2- Description text
K: Coefficients
1- { DN \\ DD \\ DS \\ DR \\ DI \\ DP \\ DC \\ DM \\ DIVISA \\ }
2- CI \\ GG \\ BI \\ BAJA \\ IVA
3- { DRC \\ DC \\ DRO \\ DFS \\ DRS \\ DFO \\ DUO \\ DI \\ DES \\ DN \\
DD \\ DS \\ DIVISA \\ }
4- [ n ]
L: Sheet of Conditions 1
A)
1- Empty
2- {Section Code\\Section Title}
B)
1- Record Code
2- {Section Code\\Section Text}
3- {Section Code\\RTF file}
4- {Section Code\\HTM file}
Q: Sheet of Conditions 2
1- Record Code
2- {Section Code\\Paragraph key\\{Field key;}\\}|
J: Sheet of Conditions 3
1- Paragraph code
2- [Paragraph text]
3- [RTF file]
4- [HTML file]
G: Grafic info
1- <grafic_file.ext\\>
E: Company
1- company Code
2 [ summary ]
3- [ name ]
4- { [ type ] \\ [ subname ] \\ [ address ] \\ [ postal_code ]
\\ [ town ] \\ [ province ] \\ [ country ] \\ { phone; }
\\ { fax; } \\ {contact_person; } \\ }
5- [ cif ] \\ [ web ] \\ [ email ] \\
X: Tecnical information
A)
1- Empty
2- < TI_Code \\ TI_Descitption \\ TI_Unit >
B)
1- Record_code
2- < TI_Code \\ TI_value >
F: #-#Adjunto#-# File
1- Record code
2- { Type \\ { Filenames; } \\ [Description] }
B: Change code
1- Record Code
2- New code
A: Labels
1- Record Code
2- <Label\\>
interface:
*/
QStringList fields = record.split("|");
if (fields.isEmpty()) {
return;
}
QString recordType = fields.at(0);
if (recordType == "V")
{
}
else if (recordType == "C")
{
}
else if (recordType == "D" || recordType == "Y")
{
}
else if (recordType == "T")
{
} else {
qDebug() << "Unknown record type:" << recordType << fields;
}
}
void FIEBDC::parseV(QStringList fields)
{
/*
parseV(field_list)
field_list: field list of the record
0- V :Property and Version
1- [File_Owner]
2- Format_Version[\DDMMYYYY]
3- [Program_Generator]
4- [Header]\{Title\}
5- [Chaters_set]
6- [Comment]
7- [Data type]
8- [Number budget certificate]
9- [Date budget certificate]
*/
if (fields.size() < 10) {
qWarning() << "Invalid 'V' record, insufficient fields:" << fields;
return;
}
QString owner = fields[1].trimmed();
QString versionDate = fields[2].trimmed();
QString generator = fields[3].trimmed();
QString headerTitle = fields[4].trimmed();
QString characterSet = fields[5].trimmed();
QString comment = fields[6].trimmed();
QString dataType = fields[7].trimmed();
QString numberCertificate = fields[8].trimmed();
QString dateCertificate = fields[9].trimmed();
// Process version and date
QStringList versionDateParts = versionDate.split("\\");
if (!versionDateParts.isEmpty()) {
auto fileFormat = versionDateParts[0];
qDebug() << "File format set to:" << fileFormat;
if (versionDateParts.size() > 1) {
QDate parsedDate = parseDate(versionDateParts[1]);
if (parsedDate.isValid()) {
qDebug() << "Parsed version date:" << parsedDate;
}
}
}
/*
# _____number of fields_____
# Any INFORMATION after last field separator is ignored
if len(field_list) > 10:
field_list = field_list[:10]
# If there are no sufficient fields, the fields are added
# with empty value:""
else:
field_list = field_list + [""]*(10-len(field_list))
# control character are erased: end of line, tab, space
# only leading and trailing whitespace in owner, generator, comment
# _____Fields_____
_record_type = self.delete_control_space(field_list[0])
_owner = field_list[1].strip()
_owner = self.delete_control(_owner)
_version_date = self.delete_control_space(field_list[2])
_generator = field_list[3].strip()
_generator = self.delete_control(_generator)
_header_title = field_list[4].strip()
_header_title = self.delete_control(_header_title)
_character_set = self.delete_control_space(field_list[5])
_comment = field_list[6].strip("\t \n\r")
_data_type = self.delete_control_space(field_list[7])
_number_certificate = self.delete_control_space(field_list[8])
__date_certificate = self.delete_control_space(field_list[9])
# _____Owner_____
self.__budget.setOwner(_owner)
# _____Version-Date_____
_version_date = _version_date.split("\\")
_file_format = _version_date[0]
if _file_format in self.__format_list:
self.__file_format = _file_format
_tuni = _("FIEBDC format: $1")
_uni = utils.mapping(_tuni, (_file_format,))
print(_uni)
if len(_version_date) > 1:
_date = _version_date[1]
if _date != "":
_parsed_date = self.parseDate(_date)
if _parsed_date is not None:
self.__budget.setDate(_parsed_date)
# _____Generator_____
# ignored field
_tuni = _("FIEBDC file generated by $1")
_uni = utils.mapping(_tuni, (_generator,))
print(_uni)
# _____Header_Title_____
_header_title = _header_title.split("\\")
_header_title = [_title.strip() for _title in _header_title]
_header = _header_title.pop(0)
_title = [ ]
for _title_index in _header_title:
if _title_index != "":
_title.append(_title_index)
if _header != "":
self.__budget.setTitleList([ _header, _title])
# _____Characters_set_____
# field parsed in readFile method
# _____Comment_____
if _comment != "":
self.__budget.setComment(_comment)
# _____Data type_____
# 1 -> Base data.
# 2 -> Budget.
# 3 -> Budget certificate.
# 4 -> Base date update.
try:
_data_type = int(_data_type)
except ValueError:
_data_type = ""
if _data_type == 3:
# _____Number budget certificate_____
try:
_number_certificate = int(_number_certificate)
except ValueError:
_number_certificate = ""
# _____Date budget certificate_____
if _date_certificate != "":
_parsed_date_certificate = self.parseDate(_date_certificate)
if _parsed_date_certificate is None:
_date_certificate = ""
else:
_date_certificate = _parsed_date_certificate
self.__budget.setBudgetype(_data_type)
self.__budget.setCertificateOrder(_number_certificate)
self.__budget.setCertificateDate(_parsed_date_cerfificate)
elif _data_type != "":
self.__budget.setBudgeType(_data_type)
self.__statistics.valid = self.__statistics.valid + 1
*/
}
void FIEBDC::readFile(Budget* budget, QString filename) {
// Implementación de la función para leer el archivo
if (budget != nullptr) {
__budget = budget;
}
if (!filename.isEmpty()) {
__filename = filename;
}
// Lógica para leer el archivo
}
+39
View File
@@ -0,0 +1,39 @@
#ifndef FIEBDC_H
#define FIEBDC_H
#include <QString>
#include <QVector>
#include <QMap>
#include <QRegularExpression>
class Budget {
public:
QString filename;
};
class FIEBDC {
private:
Budget* __budget;
QString __filename;
bool __cancel;
QVector<QString> __format_list;
QMap<QString, QString> __character_sets_dict;
QString __file_format;
QString __generator;
QString __character_set;
QMap<QString, QRegularExpression> __pattern;
void initializePatterns();
void parseV(QStringList field_list);
void cancel();
QString eraseControlCharacters(const QString& input);
bool validateCode(const QString& code);
QDate parseDate(const QString& date);
void parseRecord(const QString& record);
public:
FIEBDC(QString filename = "", Budget* budget = nullptr);
void readFile(Budget* budget = nullptr, QString filename = "");
};
#endif // FIEBDC_H
+243
View File
@@ -0,0 +1,243 @@
#include "filterlineedit.h"
//#include "Settings.h"
#include <QTimer>
#include <QKeyEvent>
#include <QMenu>
#include <QWhatsThis>
#include <QDesktopServices>
#include <QUrl>
FilterLineEdit::FilterLineEdit(QWidget* parent, std::vector<FilterLineEdit*>* filters, size_t columnnum) :
QLineEdit(parent),
filterList(filters),
columnNumber(columnnum),
conditional_format(true)
{
setPlaceholderText(tr("Filter"));
setClearButtonEnabled(true);
setProperty("column", static_cast<int>(columnnum)); // Store the column number for later use
// Introduce a timer for delaying the signal triggered whenever the user changes the filter value.
// The idea here is that the textChanged() event isn't connected to the update filter slot directly anymore
// but instead there this timer mechanism in between: whenever the user changes the filter the delay timer
// is (re)started. As soon as the user stops typing the timer has a chance to trigger and call the
// delayedSignalTimerTriggered() method which then stops the timer and emits the delayed signal.
delaySignalTimer = new QTimer(this);
delaySignalTimer->setInterval(1000 /*Settings::getValue("databrowser", "filter_delay").toInt()*/); // This is the milliseconds of not-typing we want to wait before triggering
connect(this, &FilterLineEdit::textChanged, delaySignalTimer, static_cast<void (QTimer::*)()>(&QTimer::start));
connect(delaySignalTimer, &QTimer::timeout, this, &FilterLineEdit::delayedSignalTimerTriggered);
setWhatsThis(tr("These input fields allow you to perform quick filters in the currently selected table.\n"
"By default, the rows containing the input text are filtered out.\n"
"The following operators are also supported:\n"
"%\tWildcard\n"
">\tGreater than\n"
"<\tLess than\n"
">=\tEqual to or greater\n"
"<=\tEqual to or less\n"
"=\tEqual to: exact match\n"
"<>\tUnequal: exact inverse match\n"
"x~y\tRange: values between x and y\n"
"/regexp/\tValues matching the regular expression"));
// Immediately emit the delayed filter value changed signal if the user presses the enter or the return key or
// the line edit widget loses focus
connect(this, &FilterLineEdit::editingFinished, this, &FilterLineEdit::delayedSignalTimerTriggered);
// Prepare for adding the What's This information and filter helper actions to the context menu
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &FilterLineEdit::customContextMenuRequested, this, &FilterLineEdit::showContextMenu);
}
void FilterLineEdit::delayedSignalTimerTriggered()
{
// Stop the timer first to avoid triggering in intervals
delaySignalTimer->stop();
// Only emit text changed signal if the text has actually changed in comparison to the last emitted signal. This is necessary
// because this method is also called whenever the line edit loses focus and not only when its text has definitely been changed.
if(text() != lastValue)
{
// Emit the delayed signal using the current value
emit delayedTextChanged(text());
// Remember this value for the next time
lastValue = text();
}
}
void FilterLineEdit::keyReleaseEvent(QKeyEvent* event)
{
bool actedUponKey = false;
if(event->key() == Qt::Key_Tab)
{
if(filterList && columnNumber < filterList->size() - 1)
{
filterList->at(columnNumber + 1)->setFocus();
} else {
QWidget* grandParent = parentWidget()->parentWidget();
// Pass focus to the table (grandparent).
// Parent would be the table header, which would cycle back to the first
// column (due to proxying).
if(grandParent)
grandParent->setFocus();
}
actedUponKey = true;
} else if(event->key() == Qt::Key_Backtab) {
if(filterList && columnNumber > 0)
{
filterList->at(columnNumber - 1)->setFocus();
actedUponKey = true;
}
}
if(!actedUponKey)
QLineEdit::keyReleaseEvent(event);
}
void FilterLineEdit::focusInEvent(QFocusEvent* event)
{
QLineEdit::focusInEvent(event);
emit filterFocused();
}
void FilterLineEdit::clear()
{
// When programmatically clearing the line edit's value make sure the effects are applied immediately, i.e.
// bypass the delayed signal timer
QLineEdit::clear();
delayedSignalTimerTriggered();
}
void FilterLineEdit::setText(const QString& text)
{
// When programmatically setting the line edit's value make sure the effects are applied immediately, i.e.
// bypass the delayed signal timer
QLineEdit::setText(text);
delayedSignalTimerTriggered();
}
void FilterLineEdit::setFilterHelper(const QString& filterOperator, const QString& operatorSuffix)
{
setText(filterOperator + "?" + operatorSuffix);
// Select the value for easy editing of the expression
setSelection(filterOperator.length(), 1);
}
void FilterLineEdit::showContextMenu(const QPoint &pos)
{
// This has to be created here, otherwise the set of enabled options would not update accordingly.
QMenu* editContextMenu = createStandardContextMenu();
editContextMenu->addSeparator();
QMenu* filterMenu = editContextMenu->addMenu(tr("Set Filter Expression"));
QAction* whatsThisAction = new QAction(QIcon(":/icons/whatis"), tr("What's This?"), editContextMenu);
connect(whatsThisAction, &QAction::triggered, this, [&]() {
QDesktopServices::openUrl(QUrl("https://github.com/sqlitebrowser/sqlitebrowser/wiki/Using-the-Filters"));
QWhatsThis::showText(pos, whatsThis(), this);
});
QAction* isNullAction = new QAction(tr("Is NULL"), editContextMenu);
connect(isNullAction, &QAction::triggered, this, [&]() {
setText("=NULL");
});
QAction* isNotNullAction = new QAction(tr("Is not NULL"), editContextMenu);
connect(isNotNullAction, &QAction::triggered, this, [&]() {
setText("<>NULL");
});
QAction* isEmptyAction = new QAction(tr("Is empty"), editContextMenu);
connect(isEmptyAction, &QAction::triggered, this, [&]() {
setText("=''");
});
QAction* isNotEmptyAction = new QAction(tr("Is not empty"), editContextMenu);
connect(isNotEmptyAction, &QAction::triggered, this, [&]() {
setText("<>''");
});
// Simplify this if we ever support a NOT LIKE filter
QAction* notContainingAction = new QAction(tr("Not containing..."), editContextMenu);
connect(notContainingAction, &QAction::triggered, this, [&]() {
setFilterHelper(QString ("/^((?!"), QString(").)*$/"));
});
QAction* equalToAction = new QAction(tr("Equal to..."), editContextMenu);
connect(equalToAction, &QAction::triggered, this, [&]() {
setFilterHelper(QString ("="));
});
QAction* notEqualToAction = new QAction(tr("Not equal to..."), editContextMenu);
connect(notEqualToAction, &QAction::triggered, this, [&]() {
setFilterHelper(QString ("<>"));
});
QAction* greaterThanAction = new QAction(tr("Greater than..."), editContextMenu);
connect(greaterThanAction, &QAction::triggered, this, [&]() {
setFilterHelper(QString (">"));
});
QAction* lessThanAction = new QAction(tr("Less than..."), editContextMenu);
connect(lessThanAction, &QAction::triggered, this, [&]() {
setFilterHelper(QString ("<"));
});
QAction* greaterEqualAction = new QAction(tr("Greater or equal..."), editContextMenu);
connect(greaterEqualAction, &QAction::triggered, this, [&]() {
setFilterHelper(QString (">="));
});
QAction* lessEqualAction = new QAction(tr("Less or equal..."), editContextMenu);
connect(lessEqualAction, &QAction::triggered, this, [&]() {
setFilterHelper(QString ("<="));
});
QAction* inRangeAction = new QAction(tr("In range..."), editContextMenu);
connect(inRangeAction, &QAction::triggered, this, [&]() {
setFilterHelper(QString ("?~"));
});
QAction* regexpAction = new QAction(tr("Regular expression..."), editContextMenu);
connect(regexpAction, &QAction::triggered, this, [&]() {
setFilterHelper(QString ("/"), QString ("/"));
});
if(conditional_format)
{
QAction* conditionalFormatAction;
if (text().isEmpty()) {
conditionalFormatAction = new QAction(QIcon(":/icons/clear_cond_formats"), tr("Clear All Conditional Formats"), editContextMenu);
connect(conditionalFormatAction, &QAction::triggered, this, [&]() {
emit clearAllCondFormats();
});
} else {
conditionalFormatAction = new QAction(QIcon(":/icons/add_cond_format"), tr("Use for Conditional Format"), editContextMenu);
connect(conditionalFormatAction, &QAction::triggered, this, [&]() {
emit addFilterAsCondFormat(text());
});
}
QAction* editCondFormatsAction = new QAction(QIcon(":/icons/edit_cond_formats"), tr("Edit Conditional Formats..."), editContextMenu);
connect(editCondFormatsAction, &QAction::triggered, this, [&]() {
emit editCondFormats();
});
editContextMenu->addSeparator();
editContextMenu->addAction(conditionalFormatAction);
editContextMenu->addAction(editCondFormatsAction);
}
filterMenu->addAction(whatsThisAction);
filterMenu->addSeparator();
filterMenu->addAction(isNullAction);
filterMenu->addAction(isNotNullAction);
filterMenu->addAction(isEmptyAction);
filterMenu->addAction(isNotEmptyAction);
filterMenu->addSeparator();
filterMenu->addAction(notContainingAction);
filterMenu->addAction(equalToAction);
filterMenu->addAction(notEqualToAction);
filterMenu->addAction(greaterThanAction);
filterMenu->addAction(lessThanAction);
filterMenu->addAction(greaterEqualAction);
filterMenu->addAction(lessEqualAction);
filterMenu->addAction(inRangeAction);
filterMenu->addAction(regexpAction);
editContextMenu->exec(mapToGlobal(pos));
}
+49
View File
@@ -0,0 +1,49 @@
#ifndef FILTERLINEEDIT_H
#define FILTERLINEEDIT_H
#include <QLineEdit>
#include <vector>
class QTimer;
class QKeyEvent;
class FilterLineEdit : public QLineEdit
{
Q_OBJECT
public:
explicit FilterLineEdit(QWidget* parent, std::vector<FilterLineEdit*>* filters = nullptr, size_t columnnum = 0);
// Override methods for programmatically changing the value of the line edit
void clear();
void setText(const QString& text);
void setConditionFormatContextMenuEnabled(bool enable) { conditional_format = enable; }
private slots:
void delayedSignalTimerTriggered();
signals:
void delayedTextChanged(QString text);
void addFilterAsCondFormat(QString text);
void clearAllCondFormats();
void editCondFormats();
void filterFocused();
protected:
void keyReleaseEvent(QKeyEvent* event) override;
void focusInEvent(QFocusEvent* event) override;
void setFilterHelper(const QString& filterOperator, const QString& operatorSuffix = QString());
private:
std::vector<FilterLineEdit*>* filterList;
size_t columnNumber;
QTimer* delaySignalTimer;
QString lastValue;
bool conditional_format;
private slots:
void showContextMenu(const QPoint &pos);
};
#endif
+148
View File
@@ -0,0 +1,148 @@
#include "filtertableheader.h"
#include "filtertableheader.h"
#include <QApplication>
#include <QTableView>
#include <QScrollBar>
#include "utils/filterlineedit.h"
FilterTableHeader::FilterTableHeader(QTableView* parent) :
QHeaderView(Qt::Horizontal, parent)
{
// Activate the click signals to allow sorting
setSectionsClickable(true);
// But use our own indicators allowing multi-column sorting
setSortIndicatorShown(false);
// 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(this, &FilterTableHeader::sectionClicked, this, &FilterTableHeader::adjustPositions);
connect(parent->horizontalScrollBar(), &QScrollBar::valueChanged, this, &FilterTableHeader::adjustPositions);
connect(parent->verticalScrollBar(), &QScrollBar::valueChanged, this, &FilterTableHeader::adjustPositions);
// Set custom context menu handling
setContextMenuPolicy(Qt::CustomContextMenu);
}
void FilterTableHeader::generateFilters(size_t number, size_t number_of_hidden_filters)
{
// Delete all the current filter widgets
qDeleteAll(filterWidgets);
filterWidgets.clear();
// And generate a bunch of new ones
for(size_t i=0; i < number; ++i)
{
FilterLineEdit* l = new FilterLineEdit(this, &filterWidgets, i);
l->setVisible(i >= number_of_hidden_filters);
// Set as focus proxy the first non-row-id visible filter-line.
if(i!=0 && l->isVisible() && !focusProxy())
setFocusProxy(l);
connect(l, &FilterLineEdit::delayedTextChanged, this, &FilterTableHeader::inputChanged);
connect(l, &FilterLineEdit::filterFocused, this, [this](){emit filterFocused();});
connect(l, &FilterLineEdit::addFilterAsCondFormat, this, &FilterTableHeader::addFilterAsCondFormat);
connect(l, &FilterLineEdit::clearAllCondFormats, this, &FilterTableHeader::clearAllCondFormats);
connect(l, &FilterLineEdit::editCondFormats, this, &FilterTableHeader::editCondFormats);
filterWidgets.push_back(l);
}
// Position them correctly
updateGeometries();
}
QSize FilterTableHeader::sizeHint() const
{
// For the size hint just take the value of the standard implementation and add the height of a input widget to it if necessary
QSize s = QHeaderView::sizeHint();
if(filterWidgets.size())
s.setHeight(s.height() + filterWidgets.at(0)->sizeHint().height() + 4); // The 4 adds just adds some extra space
return s;
}
void FilterTableHeader::updateGeometries()
{
// If there are any input widgets add a viewport margin to the header to generate some empty space for them which is not affected by scrolling
if(filterWidgets.size())
setViewportMargins(0, 0, 0, filterWidgets.at(0)->sizeHint().height());
else
setViewportMargins(0, 0, 0, 0);
// Now just call the parent implementation and reposition the input widgets
QHeaderView::updateGeometries();
adjustPositions();
}
void FilterTableHeader::adjustPositions()
{
// The two adds some extra space between the header label and the input widget
const int y = QHeaderView::sizeHint().height() + 2;
// Loop through all widgets
for(int i=0;i < static_cast<int>(filterWidgets.size()); ++i)
{
// Get the current widget, move it and resize it
QWidget* w = filterWidgets.at(static_cast<size_t>(i));
if (QApplication::layoutDirection() == Qt::RightToLeft)
w->move(width() - (sectionPosition(i) + sectionSize(i) - offset()), y);
else
w->move(sectionPosition(i) - offset(), y);
w->resize(sectionSize(i), w->sizeHint().height());
}
}
void FilterTableHeader::inputChanged(const QString& new_value)
{
adjustPositions();
// Just get the column number and the new value and send them to anybody interested in filter changes
emit filterChanged(sender()->property("column").toUInt(), new_value);
}
void FilterTableHeader::addFilterAsCondFormat(const QString& filter)
{
// Just get the column number and the new value and send them to anybody interested in new conditional formatting
emit addCondFormat(sender()->property("column").toUInt(), filter);
}
void FilterTableHeader::clearAllCondFormats()
{
// Just get the column number and send it to anybody responsible or interested in clearing conditional formatting
emit allCondFormatsCleared(sender()->property("column").toUInt());
}
void FilterTableHeader::editCondFormats()
{
// Just get the column number and the new value and send them to anybody interested in editing conditional formatting
emit condFormatsEdited(sender()->property("column").toUInt());
}
void FilterTableHeader::clearFilters()
{
for(FilterLineEdit* filterLineEdit : filterWidgets)
filterLineEdit->clear();
}
void FilterTableHeader::setFilter(size_t column, const QString& value)
{
if(column < filterWidgets.size())
filterWidgets.at(column)->setText(value);
}
QString FilterTableHeader::filterValue(size_t column) const
{
return filterWidgets[column]->text();
}
void FilterTableHeader::setFocusColumn(size_t column)
{
if(column < filterWidgets.size())
filterWidgets.at(column)->setFocus();
}
+47
View File
@@ -0,0 +1,47 @@
#ifndef FILTERTABLEHEADER_H
#define FILTERTABLEHEADER_H
#include <QHeaderView>
#include <vector>
class QTableView;
class FilterLineEdit;
class FilterTableHeader : public QHeaderView
{
Q_OBJECT
public:
explicit FilterTableHeader(QTableView* parent = nullptr);
QSize sizeHint() const override;
bool hasFilters() const {return (filterWidgets.size() > 0);}
QString filterValue(size_t column) const;
void setFocusColumn(size_t column);
public slots:
void generateFilters(size_t number, size_t number_of_hidden_filters = 1);
void adjustPositions();
void clearFilters();
void setFilter(size_t column, const QString& value);
signals:
void filterChanged(size_t column, QString value);
void filterFocused();
void addCondFormat(size_t column, QString filter);
void allCondFormatsCleared(size_t column);
void condFormatsEdited(size_t column);
protected:
void updateGeometries() override;
private slots:
void inputChanged(const QString& new_value);
void addFilterAsCondFormat(const QString& filter);
void clearAllCondFormats();
void editCondFormats();
private:
std::vector<FilterLineEdit*> filterWidgets;
};
#endif
+228
View File
@@ -0,0 +1,228 @@
#include "frameless.h"
FrameLess::FrameLess(QWidget *target) :
_target(target),
_cursorchanged(false),
_leftButtonPressed(false),
_borderWidth(5),
_dragPos(QPoint())
{
_target->setMouseTracking(true);
_target->setWindowFlags(Qt::FramelessWindowHint | Qt::Popup);
_target->setAttribute(Qt::WA_Hover);
_target->installEventFilter(this);
_rubberband = new QRubberBand(QRubberBand::Rectangle);
}
bool FrameLess::eventFilter(QObject *o, QEvent*e)
{
switch (e->type())
{
case QEvent::MouseMove:
mouseMove(static_cast<QMouseEvent*>(e));
return true;
case QEvent::HoverMove:
mouseHover(static_cast<QHoverEvent*>(e));
return true;
case QEvent::Leave:
mouseLeave(e);
return true;
case QEvent::MouseButtonPress:
mousePress(static_cast<QMouseEvent*>(e));
return true;
case QEvent::MouseButtonRelease:
mouseRealese(static_cast<QMouseEvent*>(e));
return true;
default:
return _target->eventFilter(o, e);
}
}
void FrameLess::mouseHover(QHoverEvent *e)
{
updateCursorShape(_target->mapToGlobal(e->pos()));
}
void FrameLess::mouseLeave(QEvent *e)
{
if (!_leftButtonPressed)
{
_target->unsetCursor();
}
}
void FrameLess::mousePress(QMouseEvent *e)
{
if (e->button() & Qt::LeftButton)
{
_leftButtonPressed = true;
calculateCursorPosition(e->globalPos(), _target->frameGeometry(), _mousePress);
if (!_mousePress.testFlag(Edge::None)) {
_rubberband->setGeometry(_target->frameGeometry());
}
if (_target->rect().marginsRemoved(QMargins(borderWidth(), borderWidth(), borderWidth(), borderWidth())).contains(e->pos()))
{
_dragStart = true;
_dragPos = e->pos();
}
}
}
void FrameLess::mouseRealese(QMouseEvent *e)
{
if (e->button() & Qt::LeftButton)
{
_leftButtonPressed = false;
_dragStart = false;
}
}
void FrameLess::mouseMove(QMouseEvent *e)
{
if (_leftButtonPressed)
{
if (_dragStart)
{
_target->move(_target->frameGeometry().topLeft() + (e->pos() - _dragPos));
}
if (!_mousePress.testFlag(Edge::None)) {
int left = _rubberband->frameGeometry().left();
int top = _rubberband->frameGeometry().top();
int right = _rubberband->frameGeometry().right();
int bottom = _rubberband->frameGeometry().bottom();
switch (_mousePress)
{
case Edge::Top:
top = e->globalPos().y();
break;
case Edge::Bottom:
bottom = e->globalPos().y();
break;
case Edge::Left:
left = e->globalPos().x();
break;
case Edge::Right:
right = e->globalPos().x();
break;
case Edge::TopLeft:
top = e->globalPos().y();
left = e->globalPos().x();
break;
case Edge::TopRight:
right = e->globalPos().x();
top = e->globalPos().y();
break;
case Edge::BottomLeft:
bottom = e->globalPos().y();
left = e->globalPos().x();
break;
case Edge::BottomRight:
bottom = e->globalPos().y();
right = e->globalPos().x();
break;
}
QRect newRect(QPoint(left, top), QPoint(right, bottom));
if (newRect.width() < _target->minimumWidth()) {
left = _target->frameGeometry().x();
}
else if (newRect.height() < _target->minimumHeight()) {
top = _target->frameGeometry().y();
}
_target->setGeometry(QRect(QPoint(left, top), QPoint(right, bottom)));
_rubberband->setGeometry(QRect(QPoint(left, top), QPoint(right, bottom)));
}
}
else {
updateCursorShape(e->globalPos());
}
}
void FrameLess::updateCursorShape(const QPoint &pos) {
if (_target->isFullScreen() || _target->isMaximized()) {
if (_cursorchanged) {
_target->unsetCursor();
}
return;
}
if (!_leftButtonPressed) {
calculateCursorPosition(pos, _target->frameGeometry(), _mouseMove);
_cursorchanged = true;
if (_mouseMove.testFlag(Edge::Top) || _mouseMove.testFlag(Edge::Bottom)) {
_target->setCursor(Qt::SizeVerCursor);
}
else if (_mouseMove.testFlag(Edge::Left) || _mouseMove.testFlag(Edge::Right)) {
_target->setCursor(Qt::SizeHorCursor);
}
else if (_mouseMove.testFlag(Edge::TopLeft) || _mouseMove.testFlag(Edge::BottomRight)) {
_target->setCursor(Qt::SizeFDiagCursor);
}
else if (_mouseMove.testFlag(Edge::TopRight) || _mouseMove.testFlag(Edge::BottomLeft)) {
_target->setCursor(Qt::SizeBDiagCursor);
}
else if (_cursorchanged) {
_target->unsetCursor();
_cursorchanged = false;
}
}
}
void FrameLess::calculateCursorPosition(const QPoint &pos, const QRect &framerect, Edges &_edge) {
bool onLeft = pos.x() >= framerect.x() - _borderWidth && pos.x() <= framerect.x() + _borderWidth &&
pos.y() <= framerect.y() + framerect.height() - _borderWidth && pos.y() >= framerect.y() + _borderWidth;
bool onRight = pos.x() >= framerect.x() + framerect.width() - _borderWidth && pos.x() <= framerect.x() + framerect.width() &&
pos.y() >= framerect.y() + _borderWidth && pos.y() <= framerect.y() + framerect.height() - _borderWidth;
bool onBottom = pos.x() >= framerect.x() + _borderWidth && pos.x() <= framerect.x() + framerect.width() - _borderWidth &&
pos.y() >= framerect.y() + framerect.height() - _borderWidth && pos.y() <= framerect.y() + framerect.height();
bool onTop = pos.x() >= framerect.x() + _borderWidth && pos.x() <= framerect.x() + framerect.width() - _borderWidth &&
pos.y() >= framerect.y() && pos.y() <= framerect.y() + _borderWidth;
bool onBottomLeft = pos.x() <= framerect.x() + _borderWidth && pos.x() >= framerect.x() &&
pos.y() <= framerect.y() + framerect.height() && pos.y() >= framerect.y() + framerect.height() - _borderWidth;
bool onBottomRight = pos.x() >= framerect.x() + framerect.width() - _borderWidth && pos.x() <= framerect.x() + framerect.width() &&
pos.y() >= framerect.y() + framerect.height() - _borderWidth && pos.y() <= framerect.y() + framerect.height();
bool onTopRight = pos.x() >= framerect.x() + framerect.width() - _borderWidth && pos.x() <= framerect.x() + framerect.width() &&
pos.y() >= framerect.y() && pos.y() <= framerect.y() + _borderWidth;
bool onTopLeft = pos.x() >= framerect.x() && pos.x() <= framerect.x() + _borderWidth &&
pos.y() >= framerect.y() && pos.y() <= framerect.y() + _borderWidth;
if (onLeft) {
_edge = Left;
}
else if (onRight) {
_edge = Right;
}
else if (onBottom) {
_edge = Bottom;
}
else if (onTop) {
_edge = Top;
}
else if (onBottomLeft) {
_edge = BottomLeft;
}
else if (onBottomRight) {
_edge = BottomRight;
}
else if (onTopRight) {
_edge = TopRight;
}
else if (onTopLeft) {
_edge = TopLeft;
}
else {
_edge = None;
}
}
+67
View File
@@ -0,0 +1,67 @@
#ifndef FRAMELESS_H
#define FRAMELESS_H
#pragma once
#include <QtWidgets/QWidget>
#include <QtWidgets/QRubberBand>
#include <QtCore/QObject>
#include <QtCore/QEvent>
#include <QtCore/QRect>
#include <QtCore/QPoint>
#include <QtCore/Qt>
#include <QtGui/QHoverEvent>
#include <QtGui/QMouseEvent>
class FrameLess : public QObject {
Q_OBJECT
public:
enum Edge {
None = 0x0,
Left = 0x1,
Top = 0x2,
Right = 0x4,
Bottom = 0x8,
TopLeft = 0x10,
TopRight = 0x20,
BottomLeft = 0x40,
BottomRight = 0x80,
};
Q_ENUM(Edge);
Q_DECLARE_FLAGS(Edges, Edge);
FrameLess(QWidget *target);
void setBorderWidth(int w) {
_borderWidth = w;
}
int borderWidth() const {
return _borderWidth;
}
protected:
bool eventFilter(QObject *o, QEvent *e) override;
void mouseHover(QHoverEvent*);
void mouseLeave(QEvent*);
void mousePress(QMouseEvent*);
void mouseRealese(QMouseEvent*);
void mouseMove(QMouseEvent*);
void updateCursorShape(const QPoint &);
void calculateCursorPosition(const QPoint &, const QRect &, Edges &);
private:
QWidget *_target = nullptr;
QRubberBand *_rubberband = nullptr;
bool _cursorchanged;
bool _leftButtonPressed;
Edges _mousePress = Edge::None;
Edges _mouseMove = Edge::None;
int _borderWidth;
QPoint _dragPos;
bool _dragStart = false;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(FrameLess::Edges);
#endif // FRAMELESS_H