mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-26 17:58:43 -05:00
628 lines
18 KiB
C++
628 lines
18 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
|
// written by Christian Daniel //
|
|
// Copyright (C) 2017, 2019-2020, 2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
|
// Copyright (C) 2020 Texas.C <ytexas96@yahoo.com> //
|
|
// Copyright (C) 2021-2022 Jon Beniston, M7RCE <jon@beniston.com> //
|
|
// //
|
|
// This program is free software; you can redistribute it and/or modify //
|
|
// it under the terms of the GNU General Public License as published by //
|
|
// the Free Software Foundation as version 3 of the License, or //
|
|
// (at your option) any later version. //
|
|
// //
|
|
// This program is distributed in the hope that it will be useful, //
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
|
// GNU General Public License V3 for more details. //
|
|
// //
|
|
// You should have received a copy of the GNU General Public License //
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <QEvent>
|
|
#include <QPainter>
|
|
#include <QPainterPath>
|
|
#include <QMouseEvent>
|
|
#include <QDialog>
|
|
#include <QDesktopServices>
|
|
|
|
#include "gui/rollupwidget.h"
|
|
#include "settings/rollupstate.h"
|
|
|
|
RollupWidget::RollupWidget(QWidget* parent) :
|
|
QWidget(parent),
|
|
m_highlighted(false),
|
|
m_contextMenuType(ContextMenuNone),
|
|
m_streamIndicator("S"),
|
|
m_channelWidget(true),
|
|
m_newHeight(0)
|
|
{
|
|
setMinimumSize(250, 150);
|
|
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
|
setBackgroundRole(QPalette::Window);
|
|
|
|
setAutoFillBackground(false);
|
|
setAttribute(Qt::WA_OpaquePaintEvent, true);
|
|
|
|
// Vorgaben aus der Palette
|
|
m_titleColor = palette().highlight().color();
|
|
m_titleTextColor = palette().highlightedText().color();
|
|
}
|
|
|
|
// QByteArray RollupWidget::saveState(int version) const
|
|
// {
|
|
// QByteArray state;
|
|
// QDataStream stream(&state, QIODevice::WriteOnly);
|
|
// int count = 0;
|
|
|
|
// for (int i = 0; i < children().count(); ++i)
|
|
// {
|
|
// QWidget* r = qobject_cast<QWidget*>(children()[i]);
|
|
|
|
// if (r) {
|
|
// count++;
|
|
// }
|
|
// }
|
|
|
|
// stream << VersionMarker;
|
|
// stream << version;
|
|
// stream << count;
|
|
|
|
// for (int i = 0; i < children().count(); ++i)
|
|
// {
|
|
// QWidget* r = qobject_cast<QWidget*>(children()[i]);
|
|
|
|
// if (r)
|
|
// {
|
|
// stream << r->objectName();
|
|
|
|
// if (r->isHidden()) {
|
|
// stream << (int) 0;
|
|
// } else {
|
|
// stream << (int) 1;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// return state;
|
|
// }
|
|
|
|
void RollupWidget::saveState(RollupState &state) const
|
|
{
|
|
QList<RollupState::RollupChildState>& childrenStates = state.getChildren();
|
|
childrenStates.clear();
|
|
|
|
for (const auto &child : children())
|
|
{
|
|
QWidget* r = qobject_cast<QWidget*>(child);
|
|
|
|
if (r && isRollupChild(r)) {
|
|
childrenStates.push_back({r->objectName(), r->isHidden()});
|
|
}
|
|
}
|
|
}
|
|
|
|
// bool RollupWidget::restoreState(const QByteArray& state, int version)
|
|
// {
|
|
// if (state.isEmpty()) {
|
|
// return false;
|
|
// }
|
|
|
|
// QByteArray sd = state;
|
|
// QDataStream stream(&sd, QIODevice::ReadOnly);
|
|
// int marker, v;
|
|
// stream >> marker;
|
|
// stream >> v;
|
|
|
|
// if ((stream.status() != QDataStream::Ok) || (marker != VersionMarker) || (v != version)) {
|
|
// return false;
|
|
// }
|
|
|
|
// int count;
|
|
// stream >> count;
|
|
|
|
// if (stream.status() != QDataStream::Ok) {
|
|
// return false;
|
|
// }
|
|
|
|
// for (int i = 0; i < count; ++i)
|
|
// {
|
|
// QString name;
|
|
// int visible;
|
|
|
|
// stream >> name;
|
|
// stream >> visible;
|
|
|
|
// if (stream.status() != QDataStream::Ok) {
|
|
// return false;
|
|
// }
|
|
|
|
// for (int j = 0; j < children().count(); ++j)
|
|
// {
|
|
// QWidget* r = qobject_cast<QWidget*>(children()[j]);
|
|
|
|
// if (r)
|
|
// {
|
|
// if (r->objectName() == name)
|
|
// {
|
|
// if (visible) {
|
|
// r->show();
|
|
// } else {
|
|
// r->hide();
|
|
// }
|
|
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// return true;
|
|
// }
|
|
|
|
void RollupWidget::restoreState(const RollupState& state)
|
|
{
|
|
const QList<RollupState::RollupChildState>& childrenStates = state.getChildren();
|
|
|
|
for (const auto &object : children())
|
|
{
|
|
QWidget* r = qobject_cast<QWidget*>(object);
|
|
|
|
if (r && isRollupChild(r))
|
|
{
|
|
for (const auto &childState : childrenStates)
|
|
{
|
|
if (childState.m_objectName.compare(r->objectName()) == 0)
|
|
{
|
|
if (childState.m_isHidden) {
|
|
r->hide();
|
|
} else {
|
|
r->show();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RollupWidget::setTitleColor(const QColor& c)
|
|
{
|
|
m_titleColor = c;
|
|
float l = 0.2126*c.redF() + 0.7152*c.greenF() + 0.0722*c.blueF();
|
|
m_titleTextColor = l < 0.5f ? Qt::white : Qt::black;
|
|
update();
|
|
}
|
|
|
|
void RollupWidget::setHighlighted(bool highlighted)
|
|
{
|
|
if (m_highlighted != highlighted)
|
|
{
|
|
m_highlighted = highlighted;
|
|
update();
|
|
}
|
|
}
|
|
|
|
int RollupWidget::arrangeRollups()
|
|
{
|
|
QFontMetrics fm(font());
|
|
int pos;
|
|
|
|
// First calculate minimum height needed, to determine how much extra space
|
|
// we have that can be split between expanding widgets
|
|
pos = fm.height() + 4;
|
|
int expandingChildren = 0;
|
|
for (int i = 0; i < children().count(); ++i)
|
|
{
|
|
QWidget* r = qobject_cast<QWidget*>(children()[i]);
|
|
|
|
if ((r != nullptr) && isRollupChild(r))
|
|
{
|
|
pos += fm.height() + 2;
|
|
if (!r->isHidden())
|
|
{
|
|
if (r->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag) {
|
|
expandingChildren++;
|
|
}
|
|
int h = 0;
|
|
if (r->hasHeightForWidth()) {
|
|
h = r->heightForWidth(width() - 4);
|
|
} else {
|
|
h = r->minimumSizeHint().height();
|
|
}
|
|
pos += h + 5;
|
|
}
|
|
}
|
|
}
|
|
|
|
setMinimumHeight(pos);
|
|
|
|
// Split extra space equally between widgets
|
|
// If there's a remainder, we give it to the first widget
|
|
// In the future, we should probably respect 'Vertical Stretch'
|
|
int extraSpace;
|
|
int firstExtra;
|
|
if ((expandingChildren > 0) && (m_newHeight > pos))
|
|
{
|
|
int totalExtra = m_newHeight - pos;
|
|
extraSpace = totalExtra / expandingChildren;
|
|
firstExtra = totalExtra - (extraSpace * expandingChildren);
|
|
}
|
|
else
|
|
{
|
|
extraSpace = 0;
|
|
firstExtra = 0;
|
|
}
|
|
|
|
// Now reposition and resize child widgets
|
|
pos = fm.height() + 4;
|
|
for (int i = 0; i < children().count(); ++i)
|
|
{
|
|
QWidget* r = qobject_cast<QWidget*>(children()[i]);
|
|
|
|
if ((r != nullptr) && isRollupChild(r))
|
|
{
|
|
pos += fm.height() + 2;
|
|
|
|
if (!r->isHidden())
|
|
{
|
|
r->move(2, pos + 3);
|
|
|
|
int h = 0;
|
|
if (r->hasHeightForWidth()) {
|
|
h = r->heightForWidth(width() - 4);
|
|
} else {
|
|
h = r->minimumSizeHint().height();
|
|
}
|
|
if (r->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag)
|
|
{
|
|
h += extraSpace;
|
|
h += firstExtra;
|
|
firstExtra = 0;
|
|
}
|
|
|
|
r->resize(width() - 4, h);
|
|
pos += r->height() + 5;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (expandingChildren == 0) {
|
|
setMaximumHeight(pos);
|
|
} else {
|
|
setMaximumHeight(16777215);
|
|
}
|
|
updateGeometry();
|
|
return pos;
|
|
}
|
|
|
|
void RollupWidget::paintEvent(QPaintEvent*)
|
|
{
|
|
QPainter p(this);
|
|
QColor frame = palette().highlight().color();
|
|
|
|
// Eigenbau
|
|
QFontMetrics fm(font());
|
|
|
|
p.setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
// Ecken (corners)
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(palette().base());
|
|
p.drawRect(0, 0, 5, 5);
|
|
p.drawRect(width() - 5, 0, 5, 5);
|
|
p.drawRect(0, height() - 5, 5, 5);
|
|
p.drawRect(width() - 5, height() - 5, 5, 5);
|
|
|
|
// Rahmen (frame)
|
|
p.setPen(m_highlighted ? Qt::white : frame);
|
|
p.setBrush(palette().window());
|
|
QRectF r(rect());
|
|
r.adjust(0.5, 0.5, -0.5, -0.5);
|
|
p.drawRoundedRect(r, 3.0, 3.0, Qt::AbsoluteSize);
|
|
|
|
// Titel-Hintergrund (Title background)
|
|
p.setPen(Qt::NoPen);
|
|
p.setBrush(m_titleColor);
|
|
QPainterPath path;
|
|
path.moveTo(1.5, fm.height() + 2.5);
|
|
path.lineTo(width() - 1.5, fm.height() + 2.5);
|
|
path.lineTo(width() - 1.5, 3.5);
|
|
path.arcTo(QRectF(width() - 3.5, 0, 2.5, 2.5), 270, -90);
|
|
path.lineTo(3.5, 1.5);
|
|
path.arcTo(QRectF(1.5, 2.5, 2.5, 2.5), 90, 90);
|
|
p.drawPath(path);
|
|
|
|
// Titel-Abschlusslinie (Title closing line)
|
|
p.setPen(frame);
|
|
p.drawLine(QPointF(0.5, 2 + fm.height() + 1.5), QPointF(width() - 1.5, 2 + fm.height() + 1.5));
|
|
|
|
// Aktiv-Button links
|
|
p.setPen(QPen(palette().windowText().color(), 1.0));
|
|
p.setBrush(palette().light());
|
|
p.drawRoundedRect(QRectF(3.5, 3.5, fm.ascent(), fm.ascent()), 2.0, 2.0, Qt::AbsoluteSize);
|
|
p.setPen(QPen(Qt::white, 1.0));
|
|
p.drawText(QRectF(3.5, 2.5, fm.ascent(), fm.ascent()), Qt::AlignCenter, "c");
|
|
|
|
if (m_channelWidget)
|
|
{
|
|
// Stromkanal-Button links (Current channel)
|
|
p.setPen(QPen(palette().windowText().color(), 1.0));
|
|
p.setBrush(palette().light());
|
|
p.drawRoundedRect(QRectF(5.5 + fm.ascent(), 2.5, fm.ascent() + 2.0, fm.ascent() + 2.0), 2.0, 2.0, Qt::AbsoluteSize);
|
|
p.setPen(QPen(Qt::white, 1.0));
|
|
p.drawText(QRectF(5.5 + fm.ascent(), 2.5, fm.ascent() + 2.0, fm.ascent() + 2.0), Qt::AlignCenter, m_streamIndicator);
|
|
}
|
|
|
|
// Help button
|
|
if (!m_helpURL.isEmpty())
|
|
{
|
|
p.setRenderHint(QPainter::Antialiasing, true);
|
|
p.setPen(QPen(palette().windowText().color(), 1.0));
|
|
p.setBrush(palette().light());
|
|
r = QRectF(width() - 2*(3.5 + fm.ascent()), 3.5, fm.ascent(), fm.ascent());
|
|
p.drawRoundedRect(r, 2.0, 2.0, Qt::AbsoluteSize);
|
|
p.drawText(QRectF(width() - 2*(3.5 + fm.ascent()), 5, fm.ascent(), fm.ascent() - 2), Qt::AlignCenter, "?");
|
|
}
|
|
|
|
//p.drawLine(r.topLeft() + QPointF(1, 1), r.bottomRight() + QPointF(-1, -1));
|
|
//p.drawLine(r.bottomLeft() + QPointF(1, -1), r.topRight() + QPointF(-1, 1));
|
|
|
|
// Schließen-Button rechts (Close button on the right)
|
|
p.setRenderHint(QPainter::Antialiasing, true);
|
|
p.setPen(QPen(palette().windowText().color(), 1.0));
|
|
p.setBrush(palette().light());
|
|
r = QRectF(width() - 3.5 - fm.ascent(), 3.5, fm.ascent(), fm.ascent());
|
|
p.drawRoundedRect(r, 2.0, 2.0, Qt::AbsoluteSize);
|
|
p.setPen(QPen(palette().windowText().color(), 1.5));
|
|
p.drawLine(r.topLeft() + QPointF(1, 1), r.bottomRight() + QPointF(-1, -1));
|
|
p.drawLine(r.bottomLeft() + QPointF(1, -1), r.topRight() + QPointF(-1, 1));
|
|
|
|
// Titel
|
|
//p.setPen(palette().highlightedText().color());
|
|
p.setPen(m_titleTextColor);
|
|
p.drawText(QRect(2 + 2*fm.height() + 2, 2, width() - 6 - 3*fm.height(), fm.height()),
|
|
fm.elidedText(windowTitle(), Qt::ElideMiddle, width() - 6 - 3*fm.height(), 0));
|
|
|
|
// Rollups
|
|
int pos = fm.height() + 4;
|
|
|
|
const QObjectList& c = children();
|
|
QObjectList::ConstIterator w = c.begin();
|
|
QObjectList::ConstIterator n = c.begin();
|
|
|
|
for (n = c.begin(); n != c.end(); ++n)
|
|
{
|
|
if (qobject_cast<QWidget*>(*n) != nullptr) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (w = n; w != c.end(); w = n)
|
|
{
|
|
if (n != c.end()) {
|
|
++n;
|
|
}
|
|
|
|
for (; n != c.end(); ++n)
|
|
{
|
|
if (qobject_cast<QWidget*>(*n) != nullptr) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
pos += paintRollup(qobject_cast<QWidget*>(*w), pos, &p, n == c.end(), frame);
|
|
}
|
|
}
|
|
|
|
int RollupWidget::paintRollup(QWidget* rollup, int pos, QPainter* p, bool last, const QColor& frame)
|
|
{
|
|
QFontMetrics fm(font());
|
|
int height = 1;
|
|
|
|
// Titel-Abschlusslinie
|
|
if (!rollup->isHidden())
|
|
{
|
|
p->setPen(palette().dark().color());
|
|
p->drawLine(QPointF(1.5, pos + fm.height() + 1.5), QPointF(width() - 1.5, pos + fm.height() + 1.5));
|
|
p->setPen(palette().light().color());
|
|
p->drawLine(QPointF(1.5, pos + fm.height() + 2.5), QPointF(width() - 1.5, pos + fm.height() + 2.5));
|
|
height += 2;
|
|
}
|
|
else
|
|
{
|
|
if (!last)
|
|
{
|
|
p->setPen(frame);
|
|
p->drawLine(QPointF(1.5, pos + fm.height() + 1.5), QPointF(width() - 1.5, pos + fm.height() + 1.5));
|
|
height++;
|
|
}
|
|
}
|
|
|
|
// Titel
|
|
p->setPen(palette().windowText().color());
|
|
p->drawText(QRect(2 + fm.height(), pos, width() - 4 - fm.height(), fm.height()),
|
|
fm.elidedText(rollup->windowTitle(), Qt::ElideMiddle, width() - 4 - fm.height(), 0));
|
|
height += fm.height();
|
|
|
|
// Ausklapp-Icon
|
|
p->setPen(palette().windowText().color());
|
|
p->setBrush(palette().windowText());
|
|
|
|
if (!rollup->isHidden())
|
|
{
|
|
QPolygonF a;
|
|
a.append(QPointF(3.5, pos + 2));
|
|
a.append(QPointF(3.5 + fm.ascent(), pos + 2));
|
|
a.append(QPointF(3.5 + fm.ascent() / 2.0, pos + fm.height() - 2));
|
|
p->drawPolygon(a);
|
|
}
|
|
else
|
|
{
|
|
QPolygonF a;
|
|
a.append(QPointF(3.5, pos + 2));
|
|
a.append(QPointF(3.5, pos + fm.height() - 2));
|
|
a.append(QPointF(3.5 + fm.ascent(), pos + fm.height() / 2));
|
|
p->drawPolygon(a);
|
|
}
|
|
|
|
// Inhalt
|
|
if (!rollup->isHidden() && (!last))
|
|
{
|
|
// Rollup-Abschlusslinie
|
|
p->setPen(frame);
|
|
p->drawLine(QPointF(1.5, pos + fm.height() + rollup->height() + 6.5),
|
|
QPointF(width() - 1.5, pos + fm.height() + rollup->height() + 6.5));
|
|
height += rollup->height() + 4;
|
|
}
|
|
|
|
return height;
|
|
}
|
|
|
|
void RollupWidget::resizeEvent(QResizeEvent* size)
|
|
{
|
|
m_newHeight = size->size().height();
|
|
arrangeRollups();
|
|
QWidget::resizeEvent(size);
|
|
}
|
|
|
|
void RollupWidget::mousePressEvent(QMouseEvent* event)
|
|
{
|
|
QFontMetrics fm(font());
|
|
|
|
// menu box left
|
|
if (QRectF(3.5, 3.5, fm.ascent(), fm.ascent()).contains(event->pos()))
|
|
{
|
|
m_contextMenuType = ContextMenuChannelSettings;
|
|
emit customContextMenuRequested(event->globalPos());
|
|
return;
|
|
}
|
|
|
|
if (m_channelWidget)
|
|
{
|
|
// Stream channel menu left
|
|
if (QRectF(5.5 + fm.ascent(), 2.5, fm.ascent() + 2.0, fm.ascent() + 2.0).contains(event->pos()))
|
|
{
|
|
m_contextMenuType = ContextMenuStreamSettings;
|
|
emit customContextMenuRequested(event->globalPos());
|
|
return;
|
|
}
|
|
}
|
|
|
|
// help button
|
|
if(!m_helpURL.isEmpty() && QRectF(width() - 2*(3.5 + fm.ascent()), 3.5, fm.ascent(), fm.ascent()).contains(event->pos()))
|
|
{
|
|
QString url;
|
|
if (m_helpURL.startsWith("http")) {
|
|
url = m_helpURL;
|
|
} else {
|
|
url = QString("https://github.com/f4exb/sdrangel/blob/master/%1").arg(m_helpURL); // Something like "plugins/channelrx/chanalyzer/readme.md"
|
|
}
|
|
QDesktopServices::openUrl(QUrl(url));
|
|
return;
|
|
}
|
|
|
|
// close button right
|
|
if(QRectF(width() - 3.5 - fm.ascent(), 3.5, fm.ascent(), fm.ascent()).contains(event->pos())) {
|
|
close();
|
|
return;
|
|
}
|
|
|
|
// check if we need to change a rollup widget
|
|
int pos = fm.height() + 4;
|
|
|
|
for (int i = 0; i < children().count(); ++i)
|
|
{
|
|
QWidget* r = qobject_cast<QWidget*>(children()[i]);
|
|
|
|
if (r)
|
|
{
|
|
if ((event->y() >= pos) && (event->y() < (pos + fm.height() + 3)))
|
|
{
|
|
if (r->isHidden())
|
|
{
|
|
r->show();
|
|
//emit widgetRolled(r, true);
|
|
}
|
|
else
|
|
{
|
|
r->hide();
|
|
//emit widgetRolled(r, false);
|
|
}
|
|
|
|
arrangeRollups();
|
|
repaint();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
pos += fm.height() + 2;
|
|
|
|
if (!r->isHidden()) {
|
|
pos += r->height() + 5;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool RollupWidget::event(QEvent* event)
|
|
{
|
|
if (event->type() == QEvent::ChildAdded)
|
|
{
|
|
((QChildEvent*)event)->child()->installEventFilter(this);
|
|
arrangeRollups();
|
|
}
|
|
else if (event->type() == QEvent::ChildRemoved)
|
|
{
|
|
((QChildEvent*)event)->child()->removeEventFilter(this);
|
|
arrangeRollups();
|
|
}
|
|
else if (event->type() == QEvent::LayoutRequest)
|
|
{
|
|
arrangeRollups();
|
|
}
|
|
|
|
return QWidget::event(event);
|
|
}
|
|
|
|
bool RollupWidget::eventFilter(QObject* object, QEvent* event)
|
|
{
|
|
if (event->type() == QEvent::Show)
|
|
{
|
|
if (children().contains(object))
|
|
{
|
|
arrangeRollups();
|
|
emit widgetRolled(qobject_cast<QWidget*>(object), true);
|
|
}
|
|
}
|
|
else if (event->type() == QEvent::Hide)
|
|
{
|
|
if (children().contains(object))
|
|
{
|
|
arrangeRollups();
|
|
emit widgetRolled(qobject_cast<QWidget*>(object), false);
|
|
}
|
|
}
|
|
else if (event->type() == QEvent::WindowTitleChange)
|
|
{
|
|
if (children().contains(object)) {
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
return QWidget::eventFilter(object, event);
|
|
}
|
|
|
|
void RollupWidget::setStreamIndicator(const QString& indicator)
|
|
{
|
|
m_streamIndicator = indicator;
|
|
update();
|
|
}
|
|
|
|
bool RollupWidget::isRollupChild(QWidget *childWidget)
|
|
{
|
|
return (qobject_cast<QDialog*>(childWidget) == nullptr); // exclude Dialogs from rollups
|
|
}
|