blob: 41e7000cd4eb412a64bfeee3128bd121bfaab998 [file] [log] [blame]
#include "android/skin/qt/size-tweaker.h"
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QDesktopWidget>
#include <QPushButton>
#include <QRadioButton>
#include <QScreen>
#include <QStyle>
#include <QTableWidget>
#include <sstream>
const qreal SizeTweaker::BaselineDpi = 96.0;
SizeTweaker::SizeTweaker(QWidget* widget) :
mSubject(widget),
mCurrentScaleFactor(1.0, 1.0) {
//We don't need size tweaking on OS X, but we need
// it for Windows/Linux.
#ifndef Q_OS_MAC
if (mSubject) {
mSubject->installEventFilter(this);
}
#endif
}
bool SizeTweaker::eventFilter(QObject* o, QEvent* event) {
if (event->type() == QEvent::Show || event->type() == QEvent::ScreenChangeInternal) {
adjustSizesAndPositions();
}
return QObject::eventFilter(o, event);
}
#ifndef Q_OS_MAC
static QString getTweakedStylesheet(const QVector2D& scale_factor) {
// KLUDGE: We have customized several controls via QSS.
// Their stylesheets need to be adjusted to properly react to DPI
// changes.
// Unfortunately, there is no way to read those baseline values from the
// stylesheet without actually parsing it., so we hardcode them here.
static const int custom_radiobtn_baseline_size = 24;
static const int custom_checkbox_baseline_width = 36;
static const int custom_checkbox_baseline_height = 24;
static const int custom_headerview_baseline_padding = 16;
std::ostringstream custom_ctrl_stylesheet;
custom_ctrl_stylesheet << "\nQRadioButton::indicator { "
<< "width: " << custom_radiobtn_baseline_size * scale_factor.x() << "; "
<< "height: " << custom_radiobtn_baseline_size * scale_factor.y() << ";"
<< "}\n"
<< "QCheckBox::indicator { "
<< "width: " << custom_checkbox_baseline_width * scale_factor.x() << "; "
<< "height: " << custom_checkbox_baseline_height * scale_factor.y() << ";"
<< "}\n"
<< "QComboBox::down-arrow { "
<< "width: " << custom_radiobtn_baseline_size * scale_factor.x() << "; "
<< "height: " << custom_radiobtn_baseline_size * scale_factor.y() << ";"
<< "}\n"
<< "QHeaderView::section { padding: "
<< custom_headerview_baseline_padding * scale_factor.y() << "; "
<< "}\n";
return QString::fromStdString(custom_ctrl_stylesheet.str());
}
static void scaleWidgetBy(QWidget* widget, const QVector2D& scale_by) {
// First, calculate the expected new size (using QWidget::width and
// QWidget::height after fiddling with min/max sizes is unpredictable
// since changing min/max sizes may actually resize the widget).
QVector2D new_size {widget->width() * scale_by.x(),
widget->height() * scale_by.y()};
// For size-limited widgets, set new limits. The check for QWIDGETSIZE_MAX
// is to avoid potential LOTS of logspam.
widget->setMinimumWidth(widget->minimumWidth() * scale_by.x());
widget->setMinimumHeight(widget->minimumHeight() * scale_by.y());
// Check before setting new maximum width/height.
// Setting this property to a value that is too large will cause Qt
// to print a warning. Since most widgets have no explicit size constraints,
// and the default max width/height is already QWIDGETSIZE_MAX,
// it would produce lots of logspam and actually cause a noticeable lag.
int new_max_width = widget->maximumWidth() * scale_by.x();
if (new_max_width < QWIDGETSIZE_MAX) {
widget->setMaximumWidth(new_max_width);
} else {
widget->setMaximumWidth(QWIDGETSIZE_MAX);
}
int new_max_height = widget->maximumHeight() * scale_by.y();
if (new_max_height < QWIDGETSIZE_MAX) {
widget->setMaximumHeight(new_max_height);
} else {
widget->setMaximumWidth(QWIDGETSIZE_MAX);
}
// Set the new size of the widget!
widget->resize(new_size.x(), new_size.y());
// Fonts need to be adjusted too.
if (widget->font().pixelSize() != -1) {
QFont f = widget->font();
f.setPixelSize(widget->font().pixelSize() * scale_by.y());
widget->setFont(f);
} else if (widget->font().pointSize() != -1) {
QFont f = widget->font();
f.setPointSize(widget->font().pointSize() * scale_by.y());
widget->setFont(f);
}
}
#endif
void SizeTweaker::adjustSizesAndPositions() {
#ifndef Q_OS_MAC
if (!mSubject) {
return;
}
int screen_number = QApplication::desktop()->screenNumber(mSubject.data());
// QDesktopWidget::screenNumber returns -1 if the widget is not on a screen.
// The returned index *may* be out of bounds if the screen change event was
// triggered as a result of turning off one of the screens.
if (screen_number < 0 ||
screen_number >= QApplication::screens().size()) {
return;
}
QScreen* screen = QApplication::screens().at(screen_number);
if (!screen) {
return;
}
// Calculate the scale factors relative to baseline DPI.
QVector2D scale_factor =
QVector2D(screen->logicalDotsPerInchX() / BaselineDpi,
screen->logicalDotsPerInchY() / BaselineDpi);
// Calculate how much the size needs to be changed. For example,
// if we're going from 192 DPI to 96 DPI, the sizes should be changed
// by 0.5. If we're going from 96 to 192, sizes should be changed by
// 2.0.
QVector2D scale_by = scale_factor / mCurrentScaleFactor;
if (qFuzzyCompare(static_cast<double>(scale_by.x()), 1.0) &&
qFuzzyCompare(static_cast<double>(scale_by.y()), 1.0)) {
// No DPI change, sizes don't need to be adjusted.
return;
}
// Update scale factors.
mCurrentScaleFactor = scale_factor;
// Resize the widget itself.
scaleWidgetBy(mSubject.data(), scale_by);
// Generate a stylesheet tweaked for this DPI.
QString tweaked_stylesheet = getTweakedStylesheet(scale_factor);
// Scale and reposition child widgets.
for (QWidget* w : mSubject->findChildren<QWidget*>()) {
// Apply QCheckbox / QRadioButton styles if needed.
if (qobject_cast<QCheckBox*>(w) ||
qobject_cast<QRadioButton*>(w) ||
qobject_cast<QComboBox*>(w) ||
qobject_cast<QTableWidget*>(w)) {
w->setStyleSheet(tweaked_stylesheet);
// Ensure new stylesheet is applied.
w->style()->unpolish(w);
w->style()->polish(w);
}
if (QAbstractButton* button = qobject_cast<QAbstractButton*>(w)) {
// Make sure to use large icons on buttons.
button->setIconSize(QSize(button->iconSize().width() * scale_by.x(),
button->iconSize().height() * scale_by.y()));
}
scaleWidgetBy(w, scale_by);
w->move(w->x() * scale_by.x(), w->y() * scale_by.y());
}
#endif
}