#include "ui/SettingsDialog.h"
#include <QAbstractButton>
#include <QDialogButtonBox>
#include <QFontMetrics>
#include <QFormLayout>
#include <QFrame>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QList>
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QPair>
#include <QSet>
#include <QSpacerItem>
#include <QVBoxLayout>
#include <QWidget>
namespace {
constexpr char kVersion[] = "155.026.1";
constexpr char kSourceText[] = "vgmlr.com/mlwrk";
constexpr char kSourceUrl[] = "https://vgmlr.com/mlwrk";
constexpr char kLinkStyle[] = "text-decoration:none; color:#000000;"
" font-family:monospace; font-size:8pt;";
constexpr char kMetaStyle[] = "font-family: monospace; font-size: 8pt;"
" padding-top: 2px; padding-bottom: 2px;";
constexpr char kHeadStyle[] = "color: #999999; font-size: 8pt;";
constexpr char kRuleStyle[] = "color: #cccccc; background-color: #cccccc;";
constexpr char kSheet[] =
"QDialog { background: #F4F4F4; }"
"QPushButton { border: 1px solid #c1c1c1; border-radius: 4px;"
" padding: 4px 12px; background: #F9F9F9; color: #000000; }"
"QPushButton:hover { background: #F1F1F1; }"
"QPushButton:pressed { background: #F4F4F4; }";
constexpr int kMetricSize = 1 << 0;
constexpr int kMetricTime = 1 << 1;
constexpr int kRowSpacing = 7;
constexpr int kLabelGap = 15;
constexpr int kPillH = 28;
constexpr int kPillR = 4;
constexpr int kPillPad = 14;
constexpr int kActionGap = 8;
const QColor kPillBorder {0xC1, 0xC1, 0xC1};
const QColor kPillOff {0xFA, 0xFA, 0xFA};
const QColor kPillOn {0xE6, 0xE6, 0xE6};
const QColor kPillHover {0xF1, 0xF1, 0xF1};
const QColor kPillText {0x33, 0x33, 0x33};
const QColor kPillTextOn {0x22, 0x22, 0x22};
QSpacerItem* spacer(int h) {
return new QSpacerItem(0, h, QSizePolicy::Minimum, QSizePolicy::Fixed);
}
QLabel* metaLabel(const QString& text) {
auto* l = new QLabel(text);
l->setStyleSheet(QLatin1String(kMetaStyle));
return l;
}
}
namespace whittle {
PillRow::PillRow(const QList<PillItem>& items, bool exclusive, QWidget* parent)
: QWidget(parent), m_items(items), m_exclusive(exclusive) {
setMouseTracking(true);
setCursor(Qt::PointingHandCursor);
const QFontMetrics fm(font());
int x = 0;
for (const PillItem& it : m_items) {
m_x << x;
x += fm.horizontalAdvance(it.first) + kPillPad * 2;
}
m_x << x;
setFixedSize(x, kPillH);
}
void PillRow::check(qint64 v) {
if (m_exclusive) m_checked.clear();
m_checked.insert(v);
update();
}
bool PillRow::isChecked(qint64 v) const { return m_checked.contains(v); }
int PillRow::checkedCount() const { return int(m_checked.size()); }
qint64 PillRow::checkedValue(qint64 fallback) const {
for (const PillItem& it : m_items)
if (m_checked.contains(it.second)) return it.second;
return fallback;
}
int PillRow::hitTest(const QPoint& p) const {
for (int i = 0; i < m_items.size(); ++i)
if (p.x() >= m_x[i] && p.x() < m_x[i + 1]) return i;
return -1;
}
void PillRow::mousePressEvent(QMouseEvent* e) {
const int i = hitTest(e->pos());
if (i < 0) return;
const qint64 v = m_items[i].second;
if (m_exclusive) {
m_checked.clear();
m_checked.insert(v);
} else if (m_checked.contains(v)) {
if (m_checked.size() > 1) m_checked.remove(v); // keep one
} else {
m_checked.insert(v);
}
update();
}
void PillRow::mouseMoveEvent(QMouseEvent* e) {
const int h = hitTest(e->pos());
if (h != m_hover) { m_hover = h; update(); }
}
void PillRow::leaveEvent(QEvent*) {
if (m_hover != -1) { m_hover = -1; update(); }
}
void PillRow::paintEvent(QPaintEvent*) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
QPainterPath outline;
outline.addRoundedRect(QRectF(0.5, 0.5, width() - 1, height() - 1),
kPillR, kPillR);
p.save();
p.setClipPath(outline);
p.fillRect(rect(), kPillOff);
for (int i = 0; i < m_items.size(); ++i) {
const bool on = m_checked.contains(m_items[i].second);
if (!on && i != m_hover) continue;
p.fillRect(QRect(m_x[i], 0, m_x[i + 1] - m_x[i], height()),
on ? kPillOn : kPillHover);
}
p.restore();
p.setPen(QPen(kPillBorder, 1));
for (int i = 1; i < m_items.size(); ++i)
p.drawLine(QPointF(m_x[i] + 0.5, 0.5),
QPointF(m_x[i] + 0.5, height() - 0.5));
p.setBrush(Qt::NoBrush);
p.drawPath(outline);
for (int i = 0; i < m_items.size(); ++i) {
p.setPen(m_checked.contains(m_items[i].second) ? kPillTextOn : kPillText);
p.drawText(QRect(m_x[i], 0, m_x[i + 1] - m_x[i], height()),
Qt::AlignCenter, m_items[i].first);
}
}
SettingsDialog::SettingsDialog(const FilterSettings& initial, QWidget* parent)
: QDialog(parent) {
setWindowTitle(tr("Settings"));
setModal(true);
setStyleSheet(QLatin1String(kSheet));
m_metric = new PillRow({{tr("Size"), kMetricSize},
{tr("mTime"), kMetricTime}}, /*exclusive=*/false);
if (initial.compareSize) m_metric->check(kMetricSize);
if (initial.compareTime) m_metric->check(kMetricTime);
if (m_metric->checkedCount() == 0) {
m_metric->check(kMetricSize);
m_metric->check(kMetricTime);
}
m_tol = new PillRow({{tr("ext4"), qint64(FilterSettings::Tol_ext4)},
{tr("NTFS"), qint64(FilterSettings::Tol_ntfs)},
{tr("ext2/3"), qint64(FilterSettings::Tol_ext23)},
{tr("VFAT"), qint64(FilterSettings::Tol_vfat)}}, true);
m_tol->check(qint64(initial.toleranceNs));
if (m_tol->checkedCount() == 0)
m_tol->check(qint64(FilterSettings::Tol_ext4));
m_display = new PillRow({{tr("Only Diff"), int(FilterSettings::Display::OnlyDiff)},
{tr("Show All"), int(FilterSettings::Display::All)}}, true);
m_display->check(int(initial.display));
m_recursive = new PillRow({{tr("Non"), 0}, {tr("Recursive"), 1}}, true);
m_recursive->check(initial.recursive ? 1 : 0);
m_loadLast = new PillRow({{tr("Load Last"), 1}, {tr("Reset"), 0}}, true);
m_loadLast->check(initial.loadLastScheme ? 1 : 0);
auto* version = metaLabel(QLatin1String(kVersion));
version->setTextInteractionFlags(Qt::TextSelectableByMouse);
auto* source = new QLabel(
QStringLiteral("<a href=\"%1\" style=\"%2\">%3</a>")
.arg(QLatin1String(kSourceUrl), QLatin1String(kLinkStyle),
QLatin1String(kSourceText)));
source->setTextFormat(Qt::RichText);
source->setOpenExternalLinks(true);
source->setTextInteractionFlags(Qt::TextBrowserInteraction);
source->setStyleSheet(QStringLiteral(
"padding-top: 2px; padding-bottom: 2px;"));
auto* form = new QFormLayout;
form->setVerticalSpacing(kRowSpacing);
form->setHorizontalSpacing(kLabelGap);
form->setContentsMargins(0, 0, 0, 0);
form->setLabelAlignment(Qt::AlignLeft | Qt::AlignVCenter);
form->setRowWrapPolicy(QFormLayout::DontWrapRows);
form->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
form->addRow(tr("Metric"), m_metric);
form->addRow(tr("Tolerance"), m_tol);
form->addRow(tr("Display"), m_display);
form->addItem(spacer(4));
form->addRow(tr("Directories"), m_recursive);
form->addRow(tr("Scheme"), m_loadLast);
form->addItem(spacer(4));
form->addRow(tr("Version"), version);
form->addRow(tr("Source"), source);
form->addItem(spacer(6));
auto* buttons = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
for (QAbstractButton* b : buttons->buttons()) b->setIcon(QIcon());
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
auto* v = new QVBoxLayout(this);
v->setContentsMargins(16, 14, 16, 14);
v->setSpacing(0);
v->setSizeConstraint(QLayout::SetFixedSize);
v->addLayout(form);
v->addSpacing(kActionGap);
v->addWidget(buttons);
}
FilterSettings SettingsDialog::settings() const {
FilterSettings f;
f.compareSize = m_metric->isChecked(kMetricSize);
f.compareTime = m_metric->isChecked(kMetricTime);
if (!f.compareSize && !f.compareTime) f.compareSize = f.compareTime = true;
f.toleranceNs = m_tol->checkedValue(qint64(FilterSettings::Tol_ext4));
f.display = (m_display->checkedValue(int(FilterSettings::Display::OnlyDiff))
== int(FilterSettings::Display::All))
? FilterSettings::Display::All
: FilterSettings::Display::OnlyDiff;
f.recursive = m_recursive->checkedValue(1) != 0;
f.loadLastScheme = m_loadLast->checkedValue(0) != 0;
return f;
}
}