339 lines
12 KiB
C++
Executable File
339 lines
12 KiB
C++
Executable File
#include "backuplistwidget.h"
|
||
#include <QHBoxLayout>
|
||
#include <QVBoxLayout>
|
||
#include <QMimeData>
|
||
#include <QFileInfo>
|
||
#include <QScrollBar>
|
||
|
||
#include "../messageboxutils.h"
|
||
#include "../gsettingswrapper.h"
|
||
#include "../globalbackupinfo.h"
|
||
#include "component/imageutil.h"
|
||
|
||
#define WIDTH_ITEM 36
|
||
#define MERGE_IN 8
|
||
#define WIDTH_SPACING 5
|
||
|
||
MyItemWidget::MyItemWidget(QWidget* parent, QListWidgetItem *item) :
|
||
QWidget(parent),
|
||
m_item(item)
|
||
{
|
||
if (parent && item)
|
||
item->setSizeHint(QSize(parent->width() - 5, WIDTH_ITEM));
|
||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||
|
||
QHBoxLayout *hlayout = new QHBoxLayout;
|
||
hlayout->setContentsMargins(5, 2, 2, 2);
|
||
hlayout->setSpacing(WIDTH_SPACING);
|
||
|
||
m_label = new MyLabel;
|
||
m_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||
m_label->setIsOriginal(true);
|
||
|
||
m_buttonDelete = new QPushButton;
|
||
m_buttonDelete->setFlat(true);
|
||
m_buttonDelete->setFixedSize(20, 20);
|
||
m_buttonDelete->setIcon(ImageUtil::loadTheme("window-close-symbolic", ":/symbos/window-close-symbolic", "white", 20));
|
||
m_buttonDelete->setVisible(false);
|
||
|
||
hlayout->addWidget(m_label);
|
||
hlayout->addWidget(m_buttonDelete);
|
||
// hlayout->setAlignment(Qt::AlignLeft);
|
||
|
||
connect(this, &MyItemWidget::setScrollWidth, this, [=](int width) {
|
||
if (parent && this->m_item)
|
||
item->setSizeHint(QSize(width - 5 - this->m_scrollWidth, WIDTH_ITEM));
|
||
int labelWidth = this->m_label->width();
|
||
this->m_extWidth = 0;
|
||
// 隐藏滚动条时会增加右侧边距
|
||
if (this->m_scrollWidth > 0 && width == 0)
|
||
this->m_extWidth = -1 * MERGE_IN;
|
||
// 显示滚动条时去掉右侧边距
|
||
else if (this->m_scrollWidth == 0 && width > 0)
|
||
this->m_extWidth = MERGE_IN;
|
||
this->m_label->setFixedWidth(labelWidth + (this->m_scrollWidth - width) + this->m_extWidth);
|
||
|
||
this->m_label->setElidedText(this->m_text, Qt::ElideLeft);
|
||
this->m_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||
this->m_scrollWidth = width;
|
||
});
|
||
|
||
connect(GlobelBackupInfo::inst().getGlobalSignals(), &GlobalSignals::fontChanged, this, [=](int fontSize) {
|
||
QFont font = this->m_label->font();
|
||
font.setPointSize(fontSize);
|
||
this->m_label->setFont(font);
|
||
// 字体家族、大小变化需重绘,并且字体大小变化也可能会造成显示不全问题
|
||
this->m_label->setElidedText(this->m_text, Qt::ElideLeft);
|
||
});
|
||
|
||
connect(this, &MyItemWidget::selected, this, [=](bool checked) {
|
||
int labelWidth = this->m_label->width();
|
||
if (checked) {
|
||
this->m_buttonDelete->setVisible(true);
|
||
this->m_label->setFixedWidth(labelWidth - WIDTH_SPACING - m_buttonDelete->width());
|
||
this->m_label->setElidedText(this->m_text, Qt::ElideLeft);
|
||
this->m_label->setFontColor(Qt::white);
|
||
this->m_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||
this->m_label->setStyleSheet("QLabel {color:white}"
|
||
"QToolTip {color:palette(windowText)}");
|
||
this->m_checked = true;
|
||
} else {
|
||
this->m_buttonDelete->setVisible(false);
|
||
this->m_label->setFixedWidth(labelWidth + WIDTH_SPACING + m_buttonDelete->width());
|
||
this->m_label->setElidedText(this->m_text, Qt::ElideLeft);
|
||
this->m_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||
this->m_label->setStyleSheet("QLabel {color:palette(windowText)}"
|
||
"QToolTip {color:palette(windowText)}");
|
||
this->m_checked = false;
|
||
}
|
||
});
|
||
|
||
connect(this, &MyItemWidget::resetItemWidth, this, [=](int width) {
|
||
if (parent && item) {
|
||
QListWidget *parentWidget = qobject_cast<QListWidget *>(parent);
|
||
QScrollBar *vertScroll = parentWidget->verticalScrollBar();
|
||
int scrollWidth = 0;
|
||
if (vertScroll->isVisible()) {
|
||
scrollWidth = vertScroll->size().width();
|
||
}
|
||
this->m_scrollWidth = scrollWidth;
|
||
item->setSizeHint(QSize(width - 5 - this->m_scrollWidth, WIDTH_ITEM));
|
||
}
|
||
if (this->m_buttonDelete->isVisible()) {
|
||
this->m_label->setFixedWidth(width - WIDTH_SPACING - m_buttonDelete->width() - 7 - this->m_scrollWidth);
|
||
this->m_label->setElidedText(this->m_text, Qt::ElideLeft);
|
||
this->m_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||
} else {
|
||
this->m_label->setFixedWidth(width - WIDTH_SPACING - 2 - this->m_scrollWidth);
|
||
this->m_label->setElidedText(this->m_text, Qt::ElideLeft);
|
||
this->m_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||
}
|
||
});
|
||
|
||
connect(m_buttonDelete, &QPushButton::clicked, this, [=]() {
|
||
this->m_item = nullptr;
|
||
emit deleteItem();
|
||
});
|
||
|
||
this->setLayout(hlayout);
|
||
}
|
||
|
||
void MyItemWidget::setText(const QString& text)
|
||
{
|
||
m_text = text;
|
||
// m_label->setFixedWidth(this->width() - 7 - this->m_scrollWidth);
|
||
m_label->setElidedText(m_text, Qt::ElideLeft);
|
||
m_label->setToolTip(m_text);
|
||
}
|
||
|
||
MyItemWidget::~MyItemWidget()
|
||
{
|
||
}
|
||
|
||
BackupListWidget::BackupListWidget(QWidget *parent /*= nullptr*/, QHBoxLayout *parentLayout /*= nullptr*/) :
|
||
QListWidget(parent),
|
||
m_parentLayout(parentLayout)
|
||
{
|
||
setSortingEnabled(false);
|
||
setAcceptDrops(true);
|
||
setAlternatingRowColors(true);
|
||
// setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||
// 设置为无边框,默认时为StyledPanel
|
||
setFrameShape(QListWidget::NoFrame);
|
||
if (parentLayout != nullptr) {
|
||
parentLayout->setContentsMargins(MERGE_IN, MERGE_IN, MERGE_IN, MERGE_IN);
|
||
setGridSize(QSize(this->width(), 40 + MERGE_IN / 2));
|
||
} else
|
||
setGridSize(QSize(this->width(), 40));
|
||
|
||
// 列表为空时,展示一个“+”号图标和拖拽文件提示
|
||
m_plusLogo = new PixmapLabel;
|
||
m_plusLogo->setThemeIconSchema("list-add-symbolic", ":/symbos/list-add-symbolic", QSize(16, 16));
|
||
m_plusLogo->setEnabled(false);
|
||
// 文件拖放区域
|
||
m_plusText = new QLabel;
|
||
m_plusText->setText(tr("File drag and drop area"));
|
||
m_plusText->setEnabled(false);
|
||
|
||
QHBoxLayout *hlayout = new QHBoxLayout;
|
||
hlayout->addStretch();
|
||
hlayout->addWidget(m_plusLogo);
|
||
hlayout->addWidget(m_plusText);
|
||
hlayout->addStretch();
|
||
|
||
QVBoxLayout *vlayout = new QVBoxLayout;
|
||
vlayout->addStretch();
|
||
vlayout->addLayout(hlayout);
|
||
vlayout->addStretch();
|
||
setLayout(vlayout);
|
||
|
||
connect(this, &BackupListWidget::currentItemChanged, this, [=](QListWidgetItem *current, QListWidgetItem *previous) {
|
||
int row = this->currentRow();
|
||
if (current) {
|
||
MyItemWidget *widget = qobject_cast<MyItemWidget *>(this->itemWidget(current));
|
||
if (widget) {
|
||
if (-1 == row)
|
||
emit widget->selected(false);
|
||
else
|
||
emit widget->selected(true);
|
||
}
|
||
}
|
||
|
||
if (previous) {
|
||
MyItemWidget *widget = qobject_cast<MyItemWidget *>(this->itemWidget(previous));
|
||
if (widget) {
|
||
emit widget->selected(false);
|
||
}
|
||
}
|
||
});
|
||
|
||
// 大小改变
|
||
connect(GlobelBackupInfo::inst().getGlobalSignals(), &GlobalSignals::widthChanged, this, [=](){
|
||
emit this->resetItemWidth(this->width());
|
||
});
|
||
}
|
||
|
||
BackupListWidget::~BackupListWidget()
|
||
{}
|
||
|
||
bool BackupListWidget::contains(const QString& text)
|
||
{
|
||
// 1、针对使用addItem等的正规使用场景(展示内容在原生item上)
|
||
if (findItems(text, Qt::MatchCaseSensitive).size() > 0)
|
||
return true;
|
||
|
||
// 2、针对使用setItemWidget添加项(展示内容在widget上)的特殊使用场景
|
||
return m_List.contains(text);
|
||
}
|
||
|
||
bool BackupListWidget::appendItem(const QString &text)
|
||
{
|
||
if (!checkPathLimit(text))
|
||
return false;
|
||
|
||
int count = this->count();
|
||
if (count > 0)
|
||
++count;
|
||
|
||
QListWidgetItem *item = new QListWidgetItem(this, m_type);
|
||
MyItemWidget *widget = new MyItemWidget(this, item);
|
||
this->setItemWidget(item, widget);
|
||
// 必须将scrollToBottom等这种滚动条操作放到判断滚动条是否显示isVisible之前
|
||
this->scrollToBottom();
|
||
|
||
connect(this, &BackupListWidget::setScrollWidth, widget, &MyItemWidget::setScrollWidth);
|
||
// 滚动条是否展示
|
||
QScrollBar *vertScroll = verticalScrollBar();
|
||
int scrollWidth = 0;
|
||
if (vertScroll->isVisible()) {
|
||
if (m_parentLayout != nullptr)
|
||
m_parentLayout->setContentsMargins(MERGE_IN, MERGE_IN, 0, MERGE_IN);
|
||
scrollWidth = vertScroll->size().width();
|
||
emit this->setScrollWidth(scrollWidth);
|
||
}
|
||
|
||
widget->setText(text);
|
||
m_List << text;
|
||
|
||
connect(widget, &MyItemWidget::deleteItem, this, [=]() {
|
||
this->m_List.removeOne(widget->text());
|
||
this->removeItemWidget(item);
|
||
this->takeItem(this->row(item));
|
||
delete item;
|
||
this->scrollToBottom();
|
||
if (this->count() == 0) {
|
||
this->m_plusLogo->setVisible(true);
|
||
this->m_plusText->setVisible(true);
|
||
emit this->deleteEmpty();
|
||
} else {
|
||
QScrollBar *vertScrollBar = verticalScrollBar();
|
||
if (!vertScrollBar->isVisible()) {
|
||
if (m_parentLayout != nullptr)
|
||
m_parentLayout->setContentsMargins(MERGE_IN, MERGE_IN, MERGE_IN, MERGE_IN);
|
||
emit this->setScrollWidth(0);
|
||
}
|
||
}
|
||
});
|
||
|
||
connect(this, &BackupListWidget::resetItemWidth, widget, &MyItemWidget::resetItemWidth);
|
||
|
||
m_plusLogo->setVisible(false);
|
||
m_plusText->setVisible(false);
|
||
emit this->addedItem();
|
||
return true;
|
||
}
|
||
|
||
void BackupListWidget::dropEvent(QDropEvent *event)
|
||
{
|
||
if (event->mimeData()->hasUrls()) {
|
||
for (QUrl url : event->mimeData()->urls()) {
|
||
QString file = url.toString();
|
||
if (file.startsWith("file://")) {
|
||
file.replace("file://", "");
|
||
appendItem(url.path());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
bool BackupListWidget::checkPathLimit(const QString &path)
|
||
{
|
||
// 防命令注入
|
||
// 1、形如:mkdir '`id&>id_bak_test.txt`'中的文件夹名称
|
||
// 2、形如:$()的文件夹名称
|
||
// 3、形如:${}的文件夹名称
|
||
// 4、包含[;、&、|]等可以包含并执行系统命令或用于连续执行系统命令的符号
|
||
// if ( path.contains(QRegularExpression(".*`.*`.*"))
|
||
// || path.contains(QRegularExpression(".*\\$\\(.*\\).*"))
|
||
// || path.contains(QRegularExpression(".*\\$\\{.*\\}.*"))
|
||
// || path.contains(QRegularExpression("[;&|]+"))) {
|
||
// MessageBoxUtils::QMESSAGE_BOX_WARNING(this, QObject::tr("Warning"), QObject::tr("Path can not include symbols that such as : ``,$(),${},;,&,|,etc."), QObject::tr("Ok"));
|
||
// return false;
|
||
// }
|
||
|
||
// 1、列表中是否已经存在
|
||
if (contains(path)) {
|
||
MessageBoxUtils::QMESSAGE_BOX_WARNING(this, QObject::tr("Warning"),
|
||
QObject::tr("Path already exists : ") + path,
|
||
QObject::tr("Ok"));
|
||
return false;
|
||
}
|
||
|
||
// 2、路径是否存在
|
||
QFileInfo fileInfo(path);
|
||
if (!fileInfo.exists()) {
|
||
MessageBoxUtils::QMESSAGE_BOX_WARNING(this, QObject::tr("Warning"),
|
||
QObject::tr("The file or directory does not exist : ") + path,
|
||
QObject::tr("Ok"));
|
||
return false;
|
||
}
|
||
|
||
// 3、是否是限定路径及其子路径
|
||
bool blimit = false;
|
||
QString dirCanBeSelected;
|
||
for (const QString &item : m_pathLimit) {
|
||
if (path.startsWith(item)) {
|
||
blimit = true;
|
||
break;
|
||
}
|
||
|
||
if (dirCanBeSelected.isEmpty())
|
||
dirCanBeSelected = item;
|
||
else {
|
||
dirCanBeSelected += ",";
|
||
dirCanBeSelected += item;
|
||
}
|
||
}
|
||
if (m_pathLimit.size() > 0 && !blimit) {
|
||
MessageBoxUtils::QMESSAGE_BOX_WARNING(this, QObject::tr("Warning"),
|
||
QObject::tr("Only data that exists in the follow directorys can be selected: %1.\n Path:%2 is not in them.").arg(dirCanBeSelected).arg(path),
|
||
QObject::tr("Ok"));
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
|