Initial commit of BudgetPro
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user