vnote/src/utils/widgetutils.cpp
Le Tan a7600fa7f7
codesign --remove-signature will cause broken binary (#2530)
* codesign --remove-signature will cause broken binary

* fix
2024-07-19 19:44:24 +08:00

507 lines
14 KiB
C++

#include "widgetutils.h"
#include <QGuiApplication>
#include <QScreen>
#include <QWidget>
#include <QStyle>
#include <QAbstractScrollArea>
#include <QScrollBar>
#include <QApplication>
#include <QDesktopServices>
#include <QKeyEvent>
#include <QActionGroup>
#include <QAction>
#include <QKeySequence>
#include <QScrollArea>
#include <QTimer>
#include <QShortcut>
#include <QListView>
#include <QModelIndex>
#include <QFontDatabase>
#include <QMenu>
#include <QDebug>
#include <QLineEdit>
#include <QLayout>
#include <QPushButton>
#include <QSplitter>
#include <QFormLayout>
#include <QFileInfo>
#include <core/global.h>
#include <widgets/messageboxhelper.h>
#include <widgets/mainwindow.h>
using namespace vnotex;
void WidgetUtils::setPropertyDynamically(QWidget *p_widget,
const char *p_prop,
const QVariant &p_val)
{
p_widget->setProperty(p_prop, p_val);
updateStyle(p_widget);
}
void WidgetUtils::updateStyle(QWidget *p_widget)
{
p_widget->style()->unpolish(p_widget);
p_widget->style()->polish(p_widget);
p_widget->update();
}
qreal WidgetUtils::calculateScaleFactor(const QScreen *p_screen)
{
static qreal factor = -1;
if (factor < 0 || p_screen) {
auto screen = p_screen ? p_screen : QGuiApplication::primaryScreen();
factor = screen->devicePixelRatio();
qDebug() << screen->name() << "dpi" << factor;
}
return factor;
}
bool WidgetUtils::isScrollBarVisible(QAbstractScrollArea *p_widget, bool p_horizontal)
{
auto scrollBar = p_horizontal ? p_widget->horizontalScrollBar() : p_widget->verticalScrollBar();
if (scrollBar && scrollBar->isVisible() && scrollBar->minimum() != scrollBar->maximum()) {
return true;
}
return false;
}
QSize WidgetUtils::availableScreenSize(QWidget *p_widget)
{
return p_widget->screen()->availableGeometry().size();
}
void WidgetUtils::openUrlByDesktop(const QUrl &p_url)
{
const auto scheme = p_url.scheme();
if (scheme == "http" || scheme == "https" ||
(p_url.isLocalFile() && QFileInfo(p_url.toLocalFile()).isDir())) {
QDesktopServices::openUrl(p_url);
return;
}
// Prompt for user.
int ret = MessageBoxHelper::questionYesNo(MessageBoxHelper::Warning,
MainWindow::tr("Are you sure to open link (%1)?").arg(p_url.toString()),
MainWindow::tr("Malicious link might do harm to your device."),
QString(),
nullptr);
if (ret == QMessageBox::No) {
return;
}
}
bool WidgetUtils::processKeyEventLikeVi(QWidget *p_widget,
QKeyEvent *p_event,
QWidget *p_escTargetWidget)
{
Q_ASSERT(p_widget);
bool eventHandled = false;
int key = p_event->key();
int modifiers = p_event->modifiers();
if (!p_escTargetWidget) {
p_escTargetWidget = p_widget;
}
switch (key) {
case Qt::Key_BracketLeft:
{
if (isViControlModifier(modifiers)) {
auto escEvent = new QKeyEvent(QEvent::KeyPress,
Qt::Key_Escape,
Qt::NoModifier);
QCoreApplication::postEvent(p_escTargetWidget, escEvent);
eventHandled = true;
}
break;
}
case Qt::Key_J:
{
if (isViControlModifier(modifiers)) {
// The event must be allocated on the heap since the post event queue will take ownership
// of the event and delete it once it has been posted.
auto downEvent = new QKeyEvent(QEvent::KeyPress,
Qt::Key_Down,
Qt::NoModifier);
QCoreApplication::postEvent(p_widget, downEvent);
eventHandled = true;
}
break;
}
case Qt::Key_K:
{
if (isViControlModifier(modifiers)) {
auto upEvent = new QKeyEvent(QEvent::KeyPress,
Qt::Key_Up,
Qt::NoModifier);
QCoreApplication::postEvent(p_widget, upEvent);
eventHandled = true;
}
break;
}
case Qt::Key_H:
{
if (isViControlModifier(modifiers)) {
auto upEvent = new QKeyEvent(QEvent::KeyPress,
Qt::Key_Left,
Qt::NoModifier);
QCoreApplication::postEvent(p_widget, upEvent);
eventHandled = true;
}
break;
}
case Qt::Key_L:
{
if (isViControlModifier(modifiers)) {
auto upEvent = new QKeyEvent(QEvent::KeyPress,
Qt::Key_Right,
Qt::NoModifier);
QCoreApplication::postEvent(p_widget, upEvent);
eventHandled = true;
}
break;
}
default:
break;
}
if (eventHandled) {
p_event->accept();
}
return eventHandled;
}
bool WidgetUtils::isViControlModifier(int p_modifiers)
{
#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
return p_modifiers == Qt::MetaModifier;
#else
return p_modifiers == Qt::ControlModifier;
#endif
}
void WidgetUtils::clearActionGroup(QActionGroup *p_actGroup)
{
auto actions = p_actGroup->actions();
for (auto action : actions) {
p_actGroup->removeAction(action);
}
}
void WidgetUtils::addActionShortcut(QAction *p_action,
const QString &p_shortcut,
Qt::ShortcutContext p_context)
{
QKeySequence kseq(p_shortcut);
if (kseq.isEmpty()) {
return;
}
p_action->setShortcut(kseq);
p_action->setShortcutContext(p_context);
p_action->setText(QString("%1\t%2").arg(p_action->text(), kseq.toString(QKeySequence::NativeText)));
}
void WidgetUtils::addActionShortcutText(QAction *p_action, const QString &p_shortcut)
{
if (p_shortcut.isEmpty()) {
return;
}
QKeySequence kseq(p_shortcut);
if (kseq.isEmpty()) {
return;
}
p_action->setText(QString("%1\t%2").arg(p_action->text(), kseq.toString(QKeySequence::NativeText)));
}
void WidgetUtils::addButtonShortcutText(QPushButton *p_button, const QString &p_shortcut)
{
if (p_shortcut.isEmpty()) {
return;
}
QKeySequence kseq(p_shortcut);
if (kseq.isEmpty()) {
return;
}
p_button->setText(QString("%1 (%2)").arg(p_button->text(), kseq.toString(QKeySequence::NativeText)));
}
void WidgetUtils::updateSize(QWidget *p_widget)
{
p_widget->adjustSize();
p_widget->updateGeometry();
}
void WidgetUtils::resizeToHideScrollBarLater(QScrollArea *p_scroll, bool p_vertical, bool p_horizontal)
{
QTimer::singleShot(200, p_scroll, [p_scroll, p_vertical, p_horizontal]() {
WidgetUtils::resizeToHideScrollBar(p_scroll, p_vertical, p_horizontal);
});
}
void WidgetUtils::resizeToHideScrollBar(QScrollArea *p_scroll, bool p_vertical, bool p_horizontal)
{
bool changed = false;
auto parentWidget = p_scroll->parentWidget();
if (p_horizontal && WidgetUtils::isScrollBarVisible(p_scroll, true)) {
auto scrollBar = p_scroll->horizontalScrollBar();
auto delta = scrollBar->maximum() - scrollBar->minimum();
auto availableSize = WidgetUtils::availableScreenSize(p_scroll);
if (parentWidget) {
int newWidth = parentWidget->width() + delta;
if (newWidth <= availableSize.width()) {
changed = true;
p_scroll->resize(p_scroll->width() + delta, p_scroll->height());
auto geo = parentWidget->geometry();
parentWidget->setGeometry(geo.x() - delta / 2,
geo.y(),
newWidth,
geo.height());
}
} else {
int newWidth = p_scroll->width() + delta;
if (newWidth <= availableSize.width()) {
changed = true;
p_scroll->resize(newWidth, p_scroll->height());
}
}
}
if (p_vertical && WidgetUtils::isScrollBarVisible(p_scroll, false)) {
auto scrollBar = p_scroll->verticalScrollBar();
auto delta = scrollBar->maximum() - scrollBar->minimum();
auto availableSize = WidgetUtils::availableScreenSize(p_scroll);
if (parentWidget) {
int newHeight = parentWidget->height() + delta;
if (newHeight <= availableSize.height()) {
changed = true;
p_scroll->resize(p_scroll->width(), p_scroll->height() + delta);
auto geo = parentWidget->geometry();
parentWidget->setGeometry(geo.x(),
geo.y() - delta / 2,
geo.width(),
newHeight);
}
} else {
int newHeight = p_scroll->height() + delta;
if (newHeight <= availableSize.height()) {
changed = true;
p_scroll->resize(p_scroll->width(), newHeight);
}
}
}
if (changed) {
p_scroll->updateGeometry();
}
}
QShortcut *WidgetUtils::createShortcut(const QString &p_shortcut,
QWidget *p_widget,
Qt::ShortcutContext p_context)
{
QKeySequence kseq(p_shortcut);
if (kseq.isEmpty()) {
return nullptr;
}
auto shortcut = new QShortcut(kseq, p_widget, nullptr, nullptr, p_context);
if (shortcut->key().isEmpty()) {
delete shortcut;
return nullptr;
}
return shortcut;
}
bool WidgetUtils::isMetaKey(int p_key)
{
return p_key == Qt::Key_Control
|| p_key == Qt::Key_Shift
|| p_key == Qt::Key_Meta
#if defined(Q_OS_LINUX)
// For mapping Caps as Ctrl in KDE.
|| p_key == Qt::Key_CapsLock
#endif
|| p_key == Qt::Key_Alt;
}
QVector<QModelIndex> WidgetUtils::getVisibleIndexes(const QListView *p_view)
{
QVector<QModelIndex> indexes;
auto firstItem = p_view->indexAt(QPoint(0, 0));
if (!firstItem.isValid()) {
return indexes;
}
auto lastItem = p_view->indexAt(p_view->viewport()->rect().bottomLeft());
int firstRow = firstItem.row();
int lastRow = lastItem.isValid() ? lastItem.row() : (p_view->model()->rowCount() - 1);
for (int i = firstRow; i <= lastRow; ++i) {
if (p_view->isRowHidden(i)) {
continue;
}
auto item = firstItem.siblingAtRow(i);
if (item.isValid()) {
indexes.append(item);
}
}
return indexes;
}
QString WidgetUtils::getMonospaceFont()
{
static QString font;
if (font.isNull()) {
QStringList candidates;
candidates << QStringLiteral("YaHei Consolas Hybrid")
<< QStringLiteral("Consolas")
<< QStringLiteral("Monaco")
<< QStringLiteral("Andale Mono")
<< QStringLiteral("Monospace")
<< QStringLiteral("Courier New");
auto availFamilies = QFontDatabase().families();
for (const auto &candidate : candidates) {
QString family = candidate.trimmed().toLower();
for (auto availFamily : availFamilies) {
availFamily.remove(QRegularExpression("\\[.*\\]"));
if (family == availFamily.trimmed().toLower()) {
font = availFamily;
return font;
}
}
}
// Fallback to current font.
font = QFont().family();
}
return font;
}
QAction *WidgetUtils::findActionByObjectName(const QList<QAction *> &p_actions, const QString &p_objName)
{
for (auto act : p_actions) {
if (act->objectName() == p_objName) {
return act;
}
}
return nullptr;
}
// Insert @p_action into @p_menu after action @p_after.
void WidgetUtils::insertActionAfter(QMenu *p_menu, QAction *p_after, QAction *p_action)
{
p_menu->insertAction(p_after, p_action);
if (p_after) {
p_menu->removeAction(p_after);
p_menu->insertAction(p_action, p_after);
}
}
void WidgetUtils::selectBaseName(QLineEdit *p_lineEdit)
{
auto text = p_lineEdit->text();
int dotIndex = text.lastIndexOf(QLatin1Char('.'));
p_lineEdit->setSelection(0, (dotIndex == -1) ? text.size() : dotIndex);
}
void WidgetUtils::setContentsMargins(QLayout *p_layout)
{
// Use 0 bottom margin to align dock widgets with the content area.
p_layout->setContentsMargins(CONTENTS_MARGIN, CONTENTS_MARGIN, CONTENTS_MARGIN, 0);
}
bool WidgetUtils::distributeWidgetsOfSplitter(QSplitter *p_splitter)
{
if (!p_splitter) {
return false;
}
if (p_splitter->count() == 0) {
return false;
} else if (p_splitter->count() == 1) {
return true;
}
auto sizes = p_splitter->sizes();
int totalWidth = 0;
for (auto sz : sizes) {
totalWidth += sz;
}
int newWidth = totalWidth / sizes.size();
if (newWidth == 0) {
return false;
}
bool changed = false;
for (int i = 0; i < sizes.size(); ++i) {
if (sizes[i] != newWidth) {
sizes[i] = newWidth;
changed = true;
}
}
if (changed) {
p_splitter->setSizes(sizes);
return true;
}
return false;
}
void WidgetUtils::clearLayout(QFormLayout *p_layout)
{
for (int i = p_layout->rowCount() - 1; i >= 0; --i) {
p_layout->removeRow(i);
}
}
// Different from QWidget::isAncestorOf(): unnecessary to be within the same window.
bool WidgetUtils::isOrAncestorOf(const QWidget *p_widget, const QWidget *p_child)
{
Q_ASSERT(p_widget);
if (!p_child) {
return false;
}
const QWidget *pa = p_child;
while (pa) {
if (pa == p_widget) {
return true;
}
pa = pa->parentWidget();
}
return false;
}