Files
GoldenCheetah/deprecated/qtsegmentcontrol.cpp
Mark Liversedge a4d928e4a0 More muted and modern Toolbar buttons
.. the side bar, bottom bar and related buttons were still
   following a skeuomorphic design that has long since
   fallen into disuse.

.. now have a more muted feel with hover/press colors active
   on mouse events.

.. moved the whatsthis button to the far right since this
   is quite a common placement in other apps.

.. it is noticeable how we use many many different schemes
   for hover/pressed colors across the UI- at some point
   this should be unified.

.. also deprecated the segmentcontrol.
2021-08-24 20:56:31 +01:00

677 lines
21 KiB
C++

#include <QIcon>
#include <QMenu>
#include <QPainter>
#include <QStyleOption>
#include <QMouseEvent>
#include <QDebug>
#include <QPixmapCache>
#include "qtsegmentcontrol.h"
#ifdef Q_WS_MAC
#include <Carbon/Carbon.h>
static ThemeDrawState getDrawState(QStyle::State flags)
{
ThemeDrawState tds = kThemeStateActive;
if (flags & QStyle::State_Sunken) {
tds = kThemeStatePressed;
} else if (flags & QStyle::State_Active) {
if (!(flags & QStyle::State_Enabled))
tds = kThemeStateUnavailable;
} else {
if (flags & QStyle::State_Enabled)
tds = kThemeStateInactive;
else
tds = kThemeStateUnavailableInactive;
}
return tds;
}
#endif
class QtStyleOptionSegmentControlSegment : public QStyleOption
{
public:
enum StyleOptionType { Type = 100000 };
enum StyleOptionVersion { Version = 1 };
enum SegmentPosition { Beginning, Middle, End, OnlyOneSegment };
enum SelectedPosition { NotAdjacent, NextIsSelected, PreviousIsSelected };
QString text;
QIcon icon;
QSize iconSize;
SegmentPosition position;
SelectedPosition selectedPosition;
QtStyleOptionSegmentControlSegment()
: position(OnlyOneSegment), selectedPosition(NotAdjacent) { }
QtStyleOptionSegmentControlSegment(const QtStyleOptionSegmentControlSegment &other)
: QStyleOption(Version, Type) { *this = other; }
protected:
QtStyleOptionSegmentControlSegment(int version);
};
static void drawSegmentControlSegmentSegment(const QStyleOption *option, QPainter *painter, QWidget *widget)
{
// ### Change to qstyleoption_cast!
if (const QtStyleOptionSegmentControlSegment *segment
= static_cast<const QtStyleOptionSegmentControlSegment *>(option)) {
#ifdef Q_WS_MAC
if (qobject_cast<QMacStyle *>(widget->style())) {
CGContextRef cg = qt_mac_cg_context(painter->device());
HIThemeSegmentDrawInfo sgi;
bool selected = (segment->state & QStyle::State_Selected);
sgi.version = 0;
// Things look the same regardless of enabled.
sgi.state = getDrawState(segment->state | QStyle::State_Enabled);
sgi.value = selected ? kThemeButtonOn : kThemeButtonOff;
sgi.size = kHIThemeSegmentSizeNormal;
sgi.kind = kHIThemeSegmentKindNormal;
sgi.adornment = kHIThemeSegmentAdornmentNone;
switch (segment->position) {
case QtStyleOptionSegmentControlSegment::Beginning:
sgi.position = kHIThemeSegmentPositionFirst;
if (segment->selectedPositions == QtStyleOptionSegmentControlSegment::NotAdjacent
|| selected)
sgi.adornment |= kHIThemeSegmentAdornmentTrailingSeparator;
break;
case QtStyleOptionSegmentControlSegment::Middle:
sgi.position = kHIThemeSegmentPositionMiddle;
if (selected && !(segment->selectedPositions & QtStyleOptionSegmentControlSegment::PreviousIsSelected))
sgi.adornment |= kHIThemeSegmentAdornmentLeadingSeparator;
if (selected || !(segment->selectedPositions & QtStyleOptionSegmentControlSegment::NextIsSelected)) // Also when we're selected.
sgi.adornment |= kHIThemeSegmentAdornmentTrailingSeparator;
break;
case QStyleOptionTab::End:
sgi.position = kHIThemeSegmentPositionLast;
if (selected && !(segment->selectedPositions & QtStyleOptionSegmentControlSegment::PreviousIsSelected))
sgi.adornment |= kHIThemeSegmentAdornmentLeadingSeparator;
break;
case QStyleOptionTab::OnlyOneTab:
sgi.position = kHIThemeSegmentPositionOnly;
break;
}
HIRect hirect = CGRectMake(segment->rect.x(), segment->rect.y(),
segment->rect.width(), segment->rect.height());
HIThemeDrawSegment(&hirect, &sgi, cg, kHIThemeOrientationNormal);
CFRelease(cg);
} else
#endif
{
Q_UNUSED(widget);
painter->save();
bool selected = (segment->state & QStyle::State_Selected);
QPixmap pm;
QSize buttonSize = widget->rect().size();
QString key = QString("qt_segment %0 %1 %2").arg(option->state).arg(buttonSize.width()).arg(buttonSize.height());
if (!QPixmapCache::find(key, pm)) {
pm = QPixmap(buttonSize);
pm.fill(Qt::transparent);
QPainter pmPainter(&pm);
QStyleOptionButton btnOpt;
btnOpt.QStyleOption::operator =(*option);
btnOpt.state &= ~QStyle::State_HasFocus;
btnOpt.rect = QRect(QPoint(0, 0), buttonSize);;
btnOpt.state = option->state;
if (selected)
btnOpt.state |= QStyle::State_Sunken;
else
btnOpt.state |= QStyle::State_Raised;
widget->style()->drawPrimitive(QStyle::PE_PanelButtonCommand, &btnOpt, &pmPainter, widget);
pmPainter.end();
QPixmapCache::insert(key, pm);
}
int margin = widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, widget);
switch (segment->position) {
case QtStyleOptionSegmentControlSegment::Beginning:
painter->setClipRect(option->rect);
painter->drawPixmap(0, 0, pm);
painter->setOpacity(0.6);
painter->setPen(option->palette.dark().color());
painter->drawLine(option->rect.topRight() + QPoint(-1, margin), option->rect.bottomRight() + QPoint(-1, -margin));
break;
case QtStyleOptionSegmentControlSegment::Middle:
painter->setClipRect(option->rect);
painter->drawPixmap(0, 0, pm);
painter->setPen(option->palette.dark().color());
painter->drawLine(option->rect.topRight() + QPoint(-1, margin), option->rect.bottomRight() + QPoint(-1, -margin));
break;
case QStyleOptionTab::End:
painter->setClipRect(option->rect);
painter->drawPixmap(0, 0, pm);
break;
case QStyleOptionTab::OnlyOneTab:
painter->setClipRect(option->rect);
painter->drawPixmap(0, 0, pm);
break;
}
painter->restore();
}
}
}
static QSize segmentSizeFromContents(const QStyleOption *option, const QSize &contentSize)
{
QSize ret = contentSize;
if (const QtStyleOptionSegmentControlSegment *segment
= static_cast<const QtStyleOptionSegmentControlSegment *>(option)) {
ret.rwidth() += 20;
ret.rheight() += 10;
if (!segment->icon.isNull())
ret.rwidth() += 5;
}
return ret;
}
static void drawSegmentControlSegmentLabel(const QStyleOption *option, QPainter *painter, QWidget *widget)
{
if (const QtStyleOptionSegmentControlSegment *segment
= static_cast<const QtStyleOptionSegmentControlSegment *>(option)) {
#ifdef Q_WS_MAC
if (qobject_cast<QMacStyle *>(widget->style())) {
retRect.adjust(+11, +4, -11, -6);
switch (segment->position) {
default:
case QtStyleOptionSegmentControlSegment::Middle:
break;
case QtStyleOptionSegmentControlSegment::Beginning:
case QtStyleOptionSegmentControlSegment::End:
retRect.adjust(+1, 0, -1, 0);
break;
case QtStyleOptionSegmentControlSegment::OnlyOneSegment:
retRect.adjust(+2, 0, -2, 0);
break;
}
}
#endif
QStyleOptionButton button;
button.QStyleOption::operator=(*option);
button.text = segment->text;
button.icon = segment->icon;
button.iconSize = segment->iconSize;
widget->style()->drawControl(QStyle::CE_PushButtonLabel, &button, painter, widget);
}
}
static void drawSegmentControlFocusRect(const QStyleOption *option, QPainter *painter, QWidget *widget)
{
QStyleOptionFocusRect focusOpt;
focusOpt.QStyleOption::operator =(*option);
focusOpt.rect.adjust(2, 2, -2, -2); //use subcontrolrect for this
widget->style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, painter, widget);
}
static void drawSegmentControlSegment(const QStyleOption *option,
QPainter *painter, QWidget *widget)
{
drawSegmentControlSegmentSegment(option, painter, widget);
drawSegmentControlSegmentLabel(option, painter, widget);
//drawSegmentControlSegmentIcon(option, painter, widget);
if (option->state & QStyle::State_HasFocus)
drawSegmentControlFocusRect(option, painter, widget);
}
struct SegmentInfo {
SegmentInfo() : menu(0), selected(false), enabled(true) {}
~SegmentInfo() { delete menu; }
QString text;
QString toolTip;
QString whatsThis;
QIcon icon;
QMenu *menu;
bool selected;
bool enabled;
QRect rect;
};
class QtSegmentControlPrivate {
public:
QtSegmentControlPrivate(QtSegmentControl *myQ)
: q(myQ), lastSelected(-1), layoutDirty(true), pressedIndex(-1), wasPressed(-1), focusIndex(-1) {};
~QtSegmentControlPrivate() {};
void layoutSegments();
void postUpdate(int index = -1, bool geoToo = false);
QtSegmentControl *q;
QtSegmentControl::SelectionBehavior selectionBehavior;
QSize iconSize;
QVector<SegmentInfo> segments;
int lastSelected;
bool layoutDirty;
int pressedIndex;
int wasPressed;
int focusIndex;
inline bool validIndex(int index) { return index >= 0 && index < segments.count(); }
};
void QtSegmentControlPrivate::layoutSegments()
{
if (!layoutDirty)
return;
const int segmentCount = segments.count();
QRect rect;
for (int i = 0; i < segmentCount; ++i) {
QSize ssh = q->segmentSizeHint(i);
rect.setSize(ssh);
segments[i].rect = rect;
rect.setLeft(rect.left() + ssh.width());
}
layoutDirty = false;
}
void QtSegmentControlPrivate::postUpdate(int /*index*/, bool geoToo)
{
if (geoToo) {
layoutDirty = true;
q->updateGeometry();
}
q->update();
}
QtSegmentControl::QtSegmentControl(QWidget *parent)
: QWidget(parent), d(new QtSegmentControlPrivate(this))
{
setFocusPolicy(Qt::TabFocus);
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
setAttribute(Qt::WA_WState_OwnSizePolicy, false);
}
QtSegmentControl::~QtSegmentControl()
{
delete d;
}
int QtSegmentControl::count() const
{
return d->segments.count();
}
void QtSegmentControl::setCount(int newCount)
{
d->segments.resize(newCount);
// If current index is not valid, make it the first valid index
if (!d->validIndex(d->focusIndex)) {
for (int i = 0; i < newCount; ++i) {
if (d->validIndex(i) && d->segments[i].enabled) {
d->focusIndex = i;
break;
}
}
}
}
bool QtSegmentControl::isSegmentSelected(int index) const
{
if (!d->validIndex(index))
return false;
return d->segments.at(index).selected;
}
int QtSegmentControl::selectedSegment() const
{
return d->lastSelected;
}
void QtSegmentControl::setSegmentSelected(int index, bool selected)
{
if (!d->validIndex(index))
return;
if (d->segments[index].selected != selected) {
d->segments[index].selected = selected;
d->lastSelected = index;
if (d->selectionBehavior == SelectOne) {
const int segmentCount = d->segments.count();
for (int i = 0; i < segmentCount; ++i) {
SegmentInfo &info = d->segments[i];
if (i != index && info.selected) {
info.selected = false;
d->postUpdate(i);
}
}
}
d->postUpdate(index);
emit segmentSelected(index);
}
}
void QtSegmentControl::setSegmentEnabled(int index, bool enabled)
{
if (!d->validIndex(index))
return;
if (d->segments[index].enabled != enabled) {
d->segments[index].enabled = enabled;
d->postUpdate(index);
}
}
void QtSegmentControl::setSelectionBehavior(SelectionBehavior behavior)
{
if (d->selectionBehavior == behavior)
return;
d->selectionBehavior = behavior;
if (behavior == SelectOne) {
// This call will do the right thing.
setSegmentSelected(d->lastSelected, true);
} else if (behavior == SelectNone) {
d->lastSelected = -1;
const int segmentCount = d->segments.count();
for (int i = 0; i < segmentCount; ++i) {
d->segments[i].selected = false;
}
d->postUpdate(-1);
}
}
QtSegmentControl::SelectionBehavior QtSegmentControl::selectionBehavior() const
{
return d->selectionBehavior;
}
void QtSegmentControl::setSegmentText(int index, const QString &text)
{
if (!d->validIndex(index))
return;
if (d->segments[index].text != text) {
d->segments[index].text = text;
d->postUpdate(index, true);
}
}
bool QtSegmentControl::segmentEnabled(int index) const
{
if (d->validIndex(index))
return d->segments[index].enabled;
return false;
}
QString QtSegmentControl::segmentText(int index) const
{
return d->validIndex(index) ? d->segments.at(index).text : QString();
}
void QtSegmentControl::setSegmentIcon(int index, const QIcon &icon)
{
if (!d->validIndex(index))
return;
d->segments[index].icon = icon;
d->postUpdate(index, true);
}
QIcon QtSegmentControl::segmentIcon(int index) const
{
return d->validIndex(index) ? d->segments.at(index).icon : QIcon();
}
void QtSegmentControl::setIconSize(const QSize &size)
{
if (d->iconSize == size)
return;
d->iconSize = size;
d->postUpdate(-1, true);
}
QSize QtSegmentControl::iconSize() const
{
return d->iconSize;
}
void QtSegmentControl::setSegmentMenu(int index, QMenu *menu)
{
if (!d->validIndex(index))
return;
if (menu != d->segments[index].menu) {
QMenu *oldMenu = d->segments[index].menu;
d->segments[index].menu = menu;
delete oldMenu;
d->postUpdate(index, true);
}
}
QMenu *QtSegmentControl::segmentMenu(int index) const
{
return d->validIndex(index) ? d->segments.at(index).menu : 0;
}
void QtSegmentControl::setSegmentToolTip(int segment, const QString &tipText)
{
if (!d->validIndex(segment))
return;
d->segments[segment].toolTip = tipText;
}
QString QtSegmentControl::segmentToolTip(int segment) const
{
return d->validIndex(segment) ? d->segments.at(segment).toolTip : QString();
}
void QtSegmentControl::setSegmentWhatsThis(int segment, const QString &whatsThisText)
{
if (!d->validIndex(segment))
return;
d->segments[segment].whatsThis = whatsThisText;
}
QString QtSegmentControl::segmentWhatsThis(int segment) const
{
return d->validIndex(segment) ? d->segments.at(segment).whatsThis : QString();
}
QSize QtSegmentControl::segmentSizeHint(int segment) const
{
QSize size;
const SegmentInfo &segmentInfo = d->segments[segment];
QFontMetrics fm(font());
size = fm.size(0, segmentInfo.text);
if (!segmentInfo.icon.isNull()) {
QSize size2 = segmentInfo.icon.actualSize(iconSize());
size.rwidth() += size2.width();
size.rheight() = qMax(size.height(), size2.height());
}
QtStyleOptionSegmentControlSegment opt;
opt.initFrom(this);
opt.text = segmentInfo.text;
opt.icon = segmentInfo.icon;
opt.iconSize = d->iconSize;
size = segmentSizeFromContents(&opt, size);
return size;
}
QSize QtSegmentControl::sizeHint() const
{
d->layoutSegments();
QRect rect;
const int segmentCount = d->segments.count();
for (int i = 0; i < segmentCount; ++i) {
rect = rect.united(segmentRect(i));
}
return rect.size();
}
QRect QtSegmentControl::segmentRect(int index) const
{
return d->validIndex(index) ? d->segments[index].rect : QRect();
}
int QtSegmentControl::segmentAt(const QPoint &pos) const
{
const int segmentCount = d->segments.count();
for (int i = 0; i < segmentCount; ++i) {
QRect rect = segmentRect(i);
if (rect.contains(pos))
return i;
}
return -1;
}
void QtSegmentControl::keyPressEvent(QKeyEvent *event)
{
if (event->key() != Qt::Key_Left && event->key() != Qt::Key_Right
&& event->key() != Qt::Key_Space) {
event->ignore();
return;
}
if (event->key() == Qt::Key_Space) {
d->pressedIndex = d->focusIndex = d->focusIndex;
d->postUpdate(d->wasPressed);
} else {
int dx = event->key() == (isRightToLeft() ? Qt::Key_Right : Qt::Key_Left) ? -1 : 1;
for (int index = d->focusIndex + dx; d->validIndex(index); index += dx) {
if (d->segments[index].enabled) {
d->focusIndex = index;
update();
break;
}
}
}
}
void QtSegmentControl::keyReleaseEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Space) {
int index = d->pressedIndex;
if (d->selectionBehavior != SelectNone) {
if (d->selectionBehavior == SelectAll) {
setSegmentSelected(index, !d->segments[index].selected);
} else {
setSegmentSelected(index, true);
}
} else {
emit segmentSelected(index);
}
d->postUpdate(index);
d->pressedIndex = -1;
}
QWidget::keyReleaseEvent(event);
}
void QtSegmentControl::paintEvent(QPaintEvent *)
{
d->layoutSegments();
QPainter p(this);
QtStyleOptionSegmentControlSegment segmentInfo;
const int segmentCount = d->segments.count();
for (int i = 0; i < segmentCount; ++i) {
initStyleOption(i, &segmentInfo);
drawSegmentControlSegment(&segmentInfo, &p, this);
}
}
void QtSegmentControl::mousePressEvent(QMouseEvent *event)
{
int index = segmentAt(event->pos());
if (segmentEnabled(index)) {
d->wasPressed = d->focusIndex = d->pressedIndex = segmentAt(event->pos());
d->postUpdate(d->pressedIndex);
}
}
void QtSegmentControl::mouseMoveEvent(QMouseEvent *event)
{
int index = segmentAt(event->pos());
if (index != d->wasPressed) {
d->pressedIndex = -1;
d->postUpdate(d->wasPressed);
} else if (index == d->wasPressed && d->pressedIndex == -1) {
d->pressedIndex = d->wasPressed;
d->postUpdate(d->wasPressed);
}
}
void QtSegmentControl::mouseReleaseEvent(QMouseEvent *event)
{
int index = segmentAt(event->pos());
// This order of reset is important.
d->pressedIndex = -1;
if (index == d->wasPressed && d->selectionBehavior != SelectNone) {
if (d->selectionBehavior == SelectAll) {
setSegmentSelected(index, !d->segments[index].selected);
} else {
setSegmentSelected(index, true);
}
} else if (index == d->wasPressed) {
emit segmentSelected(index);
}
d->postUpdate(index);
d->wasPressed = -1;
}
bool QtSegmentControl::event(QEvent *event)
{
return QWidget::event(event);
}
void QtSegmentControl::initStyleOption(int segment, QStyleOption *option) const
{
if (!option || !d->validIndex(segment))
return;
option->initFrom(this);
if (segment == d->pressedIndex)
option->state |= QStyle::State_Sunken;
// ## Change to qstyleoption_cast
if (QtStyleOptionSegmentControlSegment *sgi = static_cast<QtStyleOptionSegmentControlSegment *>(option)) {
sgi->iconSize = d->iconSize;
const SegmentInfo &segmentInfo = d->segments[segment];
if (d->segments.count() == 1) {
sgi->position = QtStyleOptionSegmentControlSegment::OnlyOneSegment;
} else if (segment == 0) {
sgi->position = QtStyleOptionSegmentControlSegment::Beginning;
} else if (segment == d->segments.count() - 1) {
sgi->position = QtStyleOptionSegmentControlSegment::End;
} else {
sgi->position = QtStyleOptionSegmentControlSegment::Middle;
}
if (hasFocus() && segment == d->focusIndex)
sgi->state |= QStyle::State_HasFocus;
else
sgi->state &= ~QStyle::State_HasFocus;
if (segmentInfo.enabled && isEnabled())
sgi->state |= QStyle::State_Enabled;
else
sgi->state &= ~QStyle::State_Enabled;
if (segmentInfo.selected) {
sgi->state |= QStyle::State_Selected;
} else {
if (d->validIndex(segment - 1) && d->segments[segment - 1].selected) {
sgi->selectedPosition = QtStyleOptionSegmentControlSegment::PreviousIsSelected;
} else if (d->validIndex(segment + 1) && d->segments[segment + 1].selected) {
sgi->selectedPosition = QtStyleOptionSegmentControlSegment::NextIsSelected;
} else {
sgi->selectedPosition = QtStyleOptionSegmentControlSegment::NotAdjacent;
}
}
sgi->rect = segmentInfo.rect;
sgi->text = segmentInfo.text;
sgi->icon = segmentInfo.icon;
}
}