From 74de0010bdf71e8f4cf7495c53853065b78462ec Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 28 Aug 2018 12:55:35 +0200 Subject: UI improvements --- LICENSE | 8 ++++ src/i18n/de.ts | 33 +++++++------- src/icons.qrc | 7 +++ src/icons/checkmark.svg | 93 ++++++++++++++++++++++++++++++++++++++ src/icons/projector_icon.svg | 65 +++++++++++++++++++++++++++ src/icons/screen_icon.svg | 44 ++++++++++++++++++ src/widget.cpp | 103 ++++++++++++++++++++++++++----------------- src/widget.h | 4 +- src/widget.ui | 77 ++++++++++++++++++++++++-------- src/x.cpp | 73 +++++++++++++++++++----------- 10 files changed, 405 insertions(+), 102 deletions(-) create mode 100644 LICENSE create mode 100644 src/icons.qrc create mode 100644 src/icons/checkmark.svg create mode 100644 src/icons/projector_icon.svg create mode 100644 src/icons/screen_icon.svg diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c56fa74 --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +GPL blabla + +Icons: +Projector: +Icons made by "http://www.freepik.com" Freepik, from "https://www.flaticon.com/", "Creative Commons BY 3.0" (CC 3.0 BY) + +Screen: +Icons made by "https://www.flaticon.com/authors/icon-works" Icon Works, from "https://www.flaticon.com/", "Creative Commons BY 3.0" (CC 3.0 BY) diff --git a/src/i18n/de.ts b/src/i18n/de.ts index 6fbd9c4..a9ade8d 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -4,7 +4,7 @@ QCoreApplication - + %1x%2 @@ -12,7 +12,7 @@ TimeOutDialog - + %v seconds @@ -20,65 +20,64 @@ Widget - + Clone - + In this mode, all connected outputs will display the same image. This is most useful if there is one monitor and one projector currently connected to the computer. - + Please select the mode you want to apply. The mode that has been detected as the potentially best mode is highlighted. - - - + + + Apply - - + Dual Screen - + <- Swap -> - + Advanced Setup - + (off) - + Output - + Position - + Do you want to keep this resolution? - + Keep diff --git a/src/icons.qrc b/src/icons.qrc new file mode 100644 index 0000000..3a700e5 --- /dev/null +++ b/src/icons.qrc @@ -0,0 +1,7 @@ + + + icons/projector_icon.svg + icons/screen_icon.svg + icons/checkmark.svg + + diff --git a/src/icons/checkmark.svg b/src/icons/checkmark.svg new file mode 100644 index 0000000..db50cb9 --- /dev/null +++ b/src/icons/checkmark.svg @@ -0,0 +1,93 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/icons/projector_icon.svg b/src/icons/projector_icon.svg new file mode 100644 index 0000000..a22f105 --- /dev/null +++ b/src/icons/projector_icon.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/screen_icon.svg b/src/icons/screen_icon.svg new file mode 100644 index 0000000..f2ca511 --- /dev/null +++ b/src/icons/screen_icon.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/widget.cpp b/src/widget.cpp index 34684b5..d0f0312 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -66,10 +66,12 @@ static void addBoldListener(QComboBox *combo) Widget::Widget(QWidget *parent) : QWidget(parent), _ui(new Ui::Widget), - _popupCount(0) + _popupCount(0), + _iProjector(QIcon(":projector")), + _iScreen(QIcon(":screen")) { _ui->setupUi(this); - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); QTimer *top = new QTimer(this); connect(top, &QTimer::timeout, [=]() { // Move window to current screen @@ -136,29 +138,7 @@ Widget::~Widget() { void Widget::showEvent(QShowEvent *event) { QWidget::showEvent(event); - ScreenMode mode = ScreenSetup::inst()->getCurrentMode(); - if (ScreenSetup::inst()->getOutputCount() >= 2 || mode == ScreenMode::Dual) { - if (_ui->tabWidget->widget(1) != _ui->tabDual) { - _ui->tabWidget->insertTab(1, _ui->tabDual, tr("Dual Screen")); - } - } else { - if (_ui->tabWidget->widget(1) == _ui->tabDual) { - _ui->tabWidget->removeTab(1); - } - } - switch (mode) { - case ScreenMode::Single: - case ScreenMode::Clone: - _ui->tabWidget->setCurrentWidget(_ui->tabClone); - break; - case ScreenMode::Dual: - _ui->tabWidget->setCurrentWidget(_ui->tabDual); - break; - case ScreenMode::Advanced: - _ui->tabWidget->setCurrentWidget(_ui->tabAdvanced); - break; - } - initControls(); + initControls(true); } static void fillCombo(QComboBox *combo, const ResolutionVector &resolutions, const QSize &preselected, const QSize &preferred = QSize()) @@ -202,8 +182,38 @@ void Widget::comboBold(int index) } } -void Widget::initControls() +void Widget::initControls(bool jumpToTab) { + // + ScreenMode currentOpMode = ScreenSetup::inst()->getCurrentMode(); + _ui->tabWidget->setTabEnabled(1, CommandLine::testMode() || ScreenSetup::inst()->getOutputCount() == 2 || currentOpMode == ScreenMode::Dual); + QWidget *current = nullptr; + switch (currentOpMode) { + case ScreenMode::Single: + case ScreenMode::Clone: + current = _ui->tabClone; + break; + case ScreenMode::Dual: + current = _ui->tabDual; + break; + case ScreenMode::Advanced: + current = _ui->tabAdvanced; + break; + } + if (current != nullptr) { + if (jumpToTab) { + _ui->tabWidget->setCurrentWidget(current); + } + for (int i = 0; i < _ui->tabWidget->count(); ++i) { + auto w = _ui->tabWidget->widget(i); + if (w == current) { + _ui->tabWidget->setTabIcon(i, QIcon(":check")); + } else { + _ui->tabWidget->setTabIcon(i, QIcon()); + } + } + } + // if (_ui->btnDualSwap->isChecked()) { _ui->btnDualSwap->toggle(); } @@ -228,7 +238,7 @@ void Widget::initControls() int j = 0; for (int i = 0; i < screenList.size() && j < 2; ++i) { QSize selected; - if (ScreenSetup::inst()->getCurrentMode() == ScreenMode::Dual) { + if (currentOpMode == ScreenMode::Dual) { // When we're not in dualhead mode, pre-select the preferred solution, so in case the user wants // to switch to dualhead, they just need to switch to the "dual" tab and hit apply to get // each screen configured to its preferred resolution. @@ -236,8 +246,11 @@ void Widget::initControls() } fillCombo(lists[j], screenList[i].modes, selected, screenList[i].preferredResolution); lists[j]->setProperty("output", screenList[i].output); - QLabel *sl = ( j == 0 ? _ui->lblDualLeft : _ui->lblDualRight ); - sl->setText(screenList[i].output + "\n" + screenList[i].name); + QWidget *sl = ( j == 0 ? _ui->wDualLeft : _ui->wDualRight ); + const QIcon &icon = screenList[i].isProjector ? _iProjector : _iScreen; + auto labels = sl->findChildren(); + labels[0]->setPixmap(icon.pixmap(QSize(32, 32))); + labels[1]->setText(screenList[i].output + "\n" + screenList[i].name); if (screenList[i].currentResolution.isEmpty()) continue; ++j; @@ -304,22 +317,27 @@ void Widget::initControls() }); } // Header - _ui->advancedCombos->addWidget(new QLabel(tr("Output")), 0, 0); - _ui->advancedCombos->addWidget(new QLabel(tr("Position")), 0, 2); + _ui->advancedCombos->addWidget(new QLabel(tr("Output")), 0, 0, 1, 2); + _ui->advancedCombos->addWidget(new QLabel(tr("Position")), 0, 3); // List int row = 1; for (auto it = screenMap.begin(); it != screenMap.end(); ++it) { + int col = 0; AdvancedOutput *a = new AdvancedOutput(it.value()); a->assignmentLabel = new QLabel(it.key(), this); a->assignmentLabel->hide(); a->rowLabel = new QLabel(it.key()); - _ui->advancedCombos->addWidget(a->rowLabel, row, 0); - _ui->advancedCombos->addWidget(new QLabel(a->info.name), row, 1); + auto ico = new QLabel(); + const QIcon &icon = (a->info.isProjector ? _iProjector : _iScreen); + auto h = a->assignmentLabel->fontMetrics().height(); + ico->setPixmap(icon.pixmap(QSize(h + 3, h))); + _ui->advancedCombos->addWidget(ico, row, col++); + _ui->advancedCombos->addWidget(a->rowLabel, row, col++); + _ui->advancedCombos->addWidget(new QLabel(a->info.name), row, col++); QComboBox *cbo = new QComboBox(); a->cboPosition = cbo; - cbo->setProperty("output", QVariant::fromValue(a)); - _ui->advancedCombos->addWidget(cbo, row, 2); - _ui->advancedCombos->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum), row, 3); + _ui->advancedCombos->addWidget(cbo, row, col++); + _ui->advancedCombos->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum), row, col++); _advancedOutput.append(a); row++; // Logic @@ -327,9 +345,6 @@ void Widget::initControls() // TODO Signal connect(cbo, QOverload::of(&QComboBox::currentIndexChanged), [a, this](int index) { a->info.position = index - 1; - if (a->assignmentLabel->layout() != nullptr) { - a->assignmentLabel->layout()->removeWidget(a->cboPosition); - } if (index > 0) { _advancedScreens[index - 1]->screen->layout()->addWidget(a->assignmentLabel); a->assignmentLabel->show(); @@ -377,6 +392,14 @@ void Widget::initControls() }); cbo->setCurrentIndex(a->info.position + 1); } + // Apply Spacing + QLayoutItem *control; + int i = 0; + while ((control = _ui->advancedCombos->itemAt(i++)) != nullptr) { + if (control->widget() != nullptr) { + control->widget()->setStyleSheet(".QLabel {padding: 1px 7px;}"); + } + } } bool Widget::keepResolution() @@ -484,7 +507,7 @@ void Widget::connectButtons() { int index = e->cboPosition->currentIndex() - 1; if (index < 0 || index >= list.size()) continue; - list[index].second.append(e->cboPosition->property("output").toString()); + list[index].second.append(e->info.output); } if (!ScreenSetup::inst()->setCustom(list) || !keepResolution()) { qDebug() << "reverting custom"; diff --git a/src/widget.h b/src/widget.h index 4e3c932..f784ce9 100644 --- a/src/widget.h +++ b/src/widget.h @@ -3,6 +3,7 @@ #include // for Qt5 #include +#include namespace Ui { class Widget; @@ -32,8 +33,9 @@ private: QVector _advancedOutput; QList _qtScreens; bool _popupCount; + QIcon _iProjector, _iScreen; - void initControls(); + void initControls(bool jumpToTab = false); void connectButtons(); bool keepResolution(); }; diff --git a/src/widget.ui b/src/widget.ui index 5836a42..7a09c4c 100644 --- a/src/widget.ui +++ b/src/widget.ui @@ -16,13 +16,17 @@ 0 + + + :/projector:/projector + - 1 + 2 @@ -93,7 +97,7 @@ - #lblDualLeft, #lblDualRight { + #wDualLeft, #wDualRight { border-radius: 3px; border: 2px solid black; } @@ -107,13 +111,29 @@ border: 2px solid black; - - - - - - Qt::AlignCenter - + + + + + + + + + Qt::AlignCenter + + + + + + + + + + Qt::AlignCenter + + + + @@ -134,16 +154,32 @@ border: 2px solid black; - - - - - - Qt::AlignCenter - - + + 0 + + + + + + + + Qt::AlignCenter + + + + + + + + + + Qt::AlignCenter + + + + @@ -180,6 +216,9 @@ border: 2px solid black; + + + Advanced Setup @@ -223,6 +262,8 @@ border: 2px solid black; - + + + diff --git a/src/x.cpp b/src/x.cpp index 73662c5..ef43f08 100644 --- a/src/x.cpp +++ b/src/x.cpp @@ -15,7 +15,7 @@ enum class Projector { struct OutputInfo { OutputInfo(RROutput id, XRROutputInfo *output, XRRCrtcInfo *crtc, XRRModeInfo *mode) - : output(output), crtc(crtc), mode(mode), id(id), position(-1), isProjector(Projector::No) {} + : output(output), crtc(crtc), mode(mode), id(id), position(-1), outputType(Projector::No) {} ~OutputInfo() { XRRFreeOutputInfo(output); } @@ -26,12 +26,12 @@ struct OutputInfo { QString modelName; QString outputName; int position; - Projector isProjector; + Projector outputType; }; ScreenInfo::ScreenInfo(const OutputInfo *oi, const ModeMap &om) : position(oi->position), name(oi->modelName), output(oi->outputName), - isProjector(oi->isProjector == Projector::Yes) + isProjector(oi->outputType == Projector::Yes) { if (oi->mode != nullptr) { currentResolution = QSize(QSize(int(oi->mode->width), int(oi->mode->height))); @@ -180,12 +180,15 @@ void ScreenSetup::updateScreenResources() } const QString outputName = QString::fromLocal8Bit(info->name, info->nameLen); tempMap.insert(_screenResources->outputs[i], outputName); - if (info->connection != RR_Connected) { + if (info->connection == RR_Disconnected) { qDebug() << "Ignoring disconnected output" << outputName; XRRFreeOutputInfo(info); continue; } + bool disconnected = false; if (info->crtc == None) { + disconnected = true; + qDebug() << "Connected output" << outputName << "has no CRTC -- trying to find free one"; info->crtc = getFreeCrtc(info); } if (!_crtcMap.contains(info->crtc)) { @@ -195,23 +198,25 @@ void ScreenSetup::updateScreenResources() } XRRCrtcInfo *crtc = _crtcMap.value(info->crtc); XRRModeInfo *mode = nullptr; - if (!_modeMap.contains(crtc->mode)) { - qDebug() << "Have output" << outputName << " with crtc with no known mode -- offline?"; - } else { - mode = _modeMap.value(crtc->mode); + if (!disconnected) { + if (!_modeMap.contains(crtc->mode)) { + qDebug() << "Have output" << outputName << " with crtc with no known mode -- offline?"; + } else { + mode = _modeMap.value(crtc->mode); + } } OutputInfo *oi = new OutputInfo(_screenResources->outputs[i], info, crtc, mode); oi->outputName = outputName; if (!this->readEdid(oi)) { // We have no EDID - take it as an indicator that there's a dumb output split/switch box - oi->isProjector = Projector::Maybe; + oi->outputType = Projector::Maybe; } else if ((info->mm_height == 0 && info->mm_width == 0) || isProjectorName(oi->modelName)) { - oi->isProjector = Projector::Yes; // Screens with size 0x0 are projectors by convention + oi->outputType = Projector::Yes; // Screens with size 0x0 are projectors by convention } else if (info->mm_width > 500 && info->mm_height > 500) { // Big screen - probably should be handled like a projector - oi->isProjector = Projector::Yes; + oi->outputType = Projector::Yes; } else if (info->mm_width > 350 && info->mm_height > 350) { // Somewhere in-between - oi->isProjector = Projector::Maybe; + oi->outputType = Projector::Maybe; } - typeCount[oi->isProjector]++; + typeCount[oi->outputType]++; _outputMap.insert(_screenResources->outputs[i], oi); qDebug() << "Connected" << outputName << "-- Clones:" << info->nclone << "Modes:" << info->nmode << "Crtcs:" << info->ncrtc << "Preferred:" << info->npreferred; } @@ -221,8 +226,8 @@ void ScreenSetup::updateScreenResources() if (typeCount[Projector::Maybe] > 0 && typeCount[Projector::No] > 0) { // Potential projector(s) and normal screen, promote for (OutputInfo *info : _outputMap) { - if (info->isProjector == Projector::Maybe) { - info->isProjector = Projector::Yes; + if (info->outputType == Projector::Maybe) { + info->outputType = Projector::Yes; break; } } @@ -244,6 +249,9 @@ void ScreenSetup::updateScreenResources() int endX = -0xffff; qDebug() << "From left to right"; for (OutputInfo* output : screens) { + if (output->mode == nullptr) { + qDebug() << "(Ignoring" << output->outputName << "since it's disconnected)"; + } if (output->crtc->x >= endX) { QSize res(0, 0); if (_modeMap.contains(output->crtc->mode)) { @@ -355,7 +363,7 @@ void ScreenSetup::initModes() // Finally copy all those the projector supports to other outputs for (auto key : _outputMap.keys()) { OutputInfo *oi = _outputMap[key]; - if (oi->isProjector == Projector::Yes) { + if (oi->outputType == Projector::Yes) { copyModesToAll(key, oi->output->nmode); } } @@ -421,9 +429,10 @@ bool ScreenSetup::setOutputResolution(OutputInfo *oi, int x, int y, const QSize { RRMode mode = getOutputModeForResolution(oi->output, size); if (mode == None) { + qDebug() << "Cannot set" << oi->outputName << "to" << size << " since it's not supported"; if (oi->output->nmode == 0) return false; - qDebug() << oi->outputName << "doesn't support" << size << "- falling back to its default"; + qDebug() << "falling back to its default"; mode = oi->output->modes[0]; } if (!dryRun) { @@ -441,19 +450,17 @@ bool ScreenSetup::setOutputResolution(OutputInfo *oi, int x, int y, const QSize ScreenMode ScreenSetup::getCurrentMode() { - int numConnected = 0; bool notAtOrigin = false; for (auto oi : _outputMap) { if (oi->mode != nullptr) { - ++numConnected; if (oi->crtc->x != 0 || oi->crtc->y != 0) { notAtOrigin = true; } } } - if (numConnected == 1) + if (_outputMap.size() == 1) return ScreenMode::Single; - if (numConnected > 2) + if (_outputMap.size() > 2) return ScreenMode::Advanced; if (notAtOrigin) return ScreenMode::Dual; @@ -467,8 +474,8 @@ ScreenMode ScreenSetup::setDefaultMode(bool dryRun) QMap screenMap; QMap projectorMap; for (auto o : _outputMap) { - qDebug() << o->outputName << quint32(o->isProjector); - if (o->isProjector == Projector::Yes) { + qDebug() << o->outputName << quint32(o->outputType); + if (o->outputType == Projector::Yes) { projectorMap.insert(o->outputName, o); } else { screenMap.insert(o->outputName, o); @@ -572,8 +579,16 @@ RRCrtc ScreenSetup::getFreeCrtc(const XRROutputInfo* output) const { for (int i = 0; i < output->ncrtc; ++i) { RRCrtc c = output->crtcs[i]; - for (auto oi : _outputMap) { - if (oi->output->crtc == c) + if (!_crtcMap.contains(c)) { + // Unknown CRTC!? Scan already known outputs + qDebug() << "BUG: Output has unknown possible CRTC"; + for (auto oi : _outputMap) { + if (oi->output->crtc == c) + goto next; + } + } else { + // Known CRTC -- see if free + if (_crtcMap[c]->noutput > 0) goto next; } return c; @@ -690,11 +705,15 @@ bool ScreenSetup::setClone(const QSize &resolution) bool ScreenSetup::setCustom(const QList>> &list) { - createCrtcBackup(); QList sizes; for (auto e : list) { + if (e.second.isEmpty()) + continue; sizes.append(e.first); } + if (sizes.isEmpty()) + return false; + createCrtcBackup(); auto screenSize = getTotalSizeHorz(sizes); if (screenSize.isEmpty()) return false; @@ -704,6 +723,8 @@ bool ScreenSetup::setCustom(const QList>> &list) int x = 0; bool ok = false; for (auto e : list) { + if (e.second.isEmpty()) + continue; const QSize &res = e.first; for (auto outputName : e.second) { for (auto oi : _outputMap) { -- cgit v1.2.3-55-g7522