File indexing completed on 2024-04-28 07:29:28

0001 /*
0002     KmPlot - a math. function plotter for the KDE-Desktop
0003 
0004     SPDX-FileCopyrightText: 2006 David Saxton <david@bluehaze.org>
0005 
0006     This file is part of the KDE Project.
0007     KmPlot is part of the KDE-EDU Project.
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 
0011 */
0012 
0013 #include "kgradientdialog.h"
0014 
0015 #include <KLocalizedString>
0016 
0017 #include <QApplication>
0018 #include <QColorDialog>
0019 #include <QDialogButtonBox>
0020 #include <QLabel>
0021 #include <QLinearGradient>
0022 #include <QPaintEvent>
0023 #include <QPainter>
0024 #include <QPointer>
0025 #include <QStyleOption>
0026 #include <QStyleOptionButton>
0027 #include <QTileRules>
0028 #include <QVBoxLayout>
0029 
0030 const double SQRT_3 = 1.732050808;
0031 const double ArrowLength = 8;
0032 const double ArrowHalfWidth = ArrowLength / SQRT_3;
0033 
0034 // BEGIN class KGradientEditor
0035 KGradientEditor::KGradientEditor(QWidget *parent)
0036     : QWidget(parent)
0037 {
0038     m_haveArrow = false;
0039     m_clickOffset = 0;
0040     m_orientation = Qt::Horizontal;
0041     findGradientStop();
0042 }
0043 
0044 KGradientEditor::~KGradientEditor()
0045 {
0046 }
0047 
0048 void KGradientEditor::setGradient(const QGradient &gradient)
0049 {
0050     if (m_gradient == gradient)
0051         return;
0052     setGradient(gradient.stops());
0053     findGradientStop();
0054 }
0055 
0056 void KGradientEditor::setColor(const QColor &color)
0057 {
0058     // Hmm...why doesn't qvector have some sortof search / replace functionality?
0059     QGradientStops stops = m_gradient.stops();
0060     for (int i = 0; i < stops.size(); ++i) {
0061         if (stops[i] != m_currentStop)
0062             continue;
0063 
0064         if (stops[i].second == color)
0065             return;
0066 
0067         m_currentStop.second = color;
0068         stops[i] = m_currentStop;
0069         break;
0070     }
0071 
0072     setGradient(stops);
0073 }
0074 
0075 QSize KGradientEditor::minimumSizeHint() const
0076 {
0077     double w = 3 * ArrowHalfWidth;
0078     double h = 12 + ArrowLength;
0079 
0080     if (m_orientation == Qt::Vertical)
0081         qSwap(w, h);
0082 
0083     return QSizeF(w, h).toSize();
0084 }
0085 
0086 void KGradientEditor::paintEvent(QPaintEvent *)
0087 {
0088     QPainter painter(this);
0089 
0090     // BEGIN draw gradient
0091     QRectF r;
0092     QLinearGradient lg;
0093 
0094     if (m_orientation == Qt::Horizontal) {
0095         lg = QLinearGradient(0, 0, width(), 0);
0096         r = QRectF(ArrowHalfWidth - 1, 0, width() - 2 * ArrowHalfWidth + 1, height() - ArrowLength);
0097     } else {
0098         lg = QLinearGradient(0, 0, 0, height());
0099         r = QRectF(0, ArrowHalfWidth - 1, width() - ArrowLength, height() - 2 * ArrowHalfWidth + 1);
0100     }
0101 
0102     lg.setStops(m_gradient.stops());
0103     painter.setBrush(lg);
0104     painter.setPen(QPen(Qt::black, 1));
0105     painter.drawRect(r);
0106     // END draw gradient
0107 
0108     // BEGIN draw arrows
0109     painter.setRenderHint(QPainter::Antialiasing, true);
0110     const QGradientStops stops = m_gradient.stops();
0111     for (const QGradientStop &stop : stops)
0112         drawArrow(&painter, stop);
0113     // END draw arrows
0114 }
0115 
0116 void KGradientEditor::drawArrow(QPainter *painter, const QGradientStop &stop)
0117 {
0118     QPolygonF arrow(3);
0119 
0120     double mid = toArrowPos(stop.first);
0121 
0122     if (m_orientation == Qt::Horizontal) {
0123         arrow[0] = QPointF(mid, height() - ArrowLength + 0.5);
0124         arrow[1] = QPointF(mid + ArrowHalfWidth, height() - 0.5);
0125         arrow[2] = QPointF(mid - ArrowHalfWidth, height() - 0.5);
0126     } else {
0127         arrow[0] = QPointF(width() - ArrowLength + 0.5, mid);
0128         arrow[1] = QPointF(width() - 0.5, mid + ArrowHalfWidth);
0129         arrow[2] = QPointF(width() - 0.5, mid - ArrowHalfWidth);
0130     }
0131 
0132     bool selected = (stop == m_currentStop);
0133     QColor color(selected ? palette().color(QPalette::Dark) : Qt::black);
0134 
0135     painter->setPen(color);
0136     painter->setBrush(stop.second);
0137     painter->drawPolygon(arrow);
0138 }
0139 
0140 void KGradientEditor::contextMenuEvent(QContextMenuEvent *e)
0141 {
0142     // Prevent the "QWhatsThis" menu from popping up when right-clicking
0143     e->accept();
0144 }
0145 
0146 void KGradientEditor::removeStop()
0147 {
0148     QGradientStops stops = m_gradient.stops();
0149     for (int i = 0; i < stops.size(); ++i) {
0150         if (stops[i] != m_currentStop)
0151             continue;
0152 
0153         stops.remove(i);
0154         break;
0155     }
0156 
0157     setGradient(stops);
0158     findGradientStop();
0159 }
0160 
0161 void KGradientEditor::mousePressEvent(QMouseEvent *e)
0162 {
0163     if (!getGradientStop(e->pos()))
0164         return;
0165     e->accept();
0166 
0167     if (e->button() == Qt::RightButton)
0168         removeStop();
0169     else
0170         m_haveArrow = true;
0171 }
0172 
0173 bool KGradientEditor::getGradientStop(const QPoint &point)
0174 {
0175     double dl; // the vertical (for horizontal layout) distance from the tip of the arrows
0176     if (m_orientation == Qt::Horizontal)
0177         dl = point.y() - (height() - ArrowLength);
0178     else
0179         dl = point.x() - (width() - ArrowLength);
0180 
0181     // Is the arrow in the strip?
0182     if (dl < 0)
0183         return false;
0184 
0185     QGradientStops stops = m_gradient.stops();
0186 
0187     // Iterate over stops in reverse as the last stops are displayed on top of
0188     // the first stops.
0189     for (int i = stops.size() - 1; i >= 0; --i) {
0190         QGradientStop stop = stops[i];
0191 
0192         double pos = toArrowPos(stop.first);
0193 
0194         // Is the click inside the arrow?
0195         double lower = pos - dl * (ArrowHalfWidth / ArrowLength);
0196         double upper = pos + dl * (ArrowHalfWidth / ArrowLength);
0197 
0198         double x = (m_orientation == Qt::Horizontal) ? point.x() : point.y();
0199         if (x < lower || x > upper)
0200             continue;
0201 
0202         // Is inside arrow! :)
0203         m_clickOffset = x - pos;
0204 
0205         setCurrentStop(stop);
0206         return true;
0207     }
0208 
0209     return false;
0210 }
0211 
0212 void KGradientEditor::mouseMoveEvent(QMouseEvent *e)
0213 {
0214     if (!m_haveArrow)
0215         return;
0216 
0217     e->accept();
0218     QPoint point = e->pos();
0219 
0220     // Hmm...why doesn't qvector have some sortof search / replace functionality?
0221     QGradientStops stops = m_gradient.stops();
0222     for (int i = 0; i < stops.size(); ++i) {
0223         if (stops[i] != m_currentStop)
0224             continue;
0225 
0226         double x = (m_orientation == Qt::Horizontal) ? point.x() : point.y();
0227 
0228         m_currentStop.first = fromArrowPos(x - m_clickOffset);
0229 
0230         stops[i] = m_currentStop;
0231         break;
0232     }
0233 
0234     setGradient(stops);
0235 }
0236 
0237 void KGradientEditor::mouseReleaseEvent(QMouseEvent *)
0238 {
0239     m_haveArrow = false;
0240 }
0241 
0242 void KGradientEditor::mouseDoubleClickEvent(QMouseEvent *e)
0243 {
0244     e->accept();
0245 
0246     if (getGradientStop(e->pos()))
0247         return;
0248 
0249     // Create new stop
0250     QPoint point = e->pos();
0251     double pos = fromArrowPos((m_orientation == Qt::Horizontal) ? point.x() : point.y());
0252 
0253     QGradientStop stop;
0254     stop.first = pos;
0255     stop.second = Qt::red;
0256 
0257     QGradientStops stops = m_gradient.stops();
0258     stops << stop;
0259 
0260     setGradient(stops);
0261     setCurrentStop(stop);
0262 }
0263 
0264 void KGradientEditor::setOrientation(Qt::Orientation orientation)
0265 {
0266     m_orientation = orientation;
0267     update();
0268 }
0269 
0270 void KGradientEditor::findGradientStop()
0271 {
0272     QGradientStops stops = m_gradient.stops();
0273 
0274     // The QGradientStops should always have at least one stop in, since
0275     // QGradient returns a Black->White gradient if its stops are empty.
0276     Q_ASSERT(!stops.isEmpty());
0277 
0278     // Pick a stop in the center
0279     setCurrentStop(stops[stops.size() / 2]);
0280 }
0281 
0282 void KGradientEditor::setCurrentStop(const QGradientStop &stop)
0283 {
0284     if (m_currentStop == stop)
0285         return;
0286 
0287     bool colorChanged = stop.second != m_currentStop.second;
0288 
0289     m_currentStop = stop;
0290     update();
0291 
0292     if (colorChanged)
0293         emit colorSelected(stop.second);
0294 }
0295 
0296 void KGradientEditor::setGradient(const QGradientStops &stops)
0297 {
0298     if (stops == m_gradient.stops())
0299         return;
0300 
0301     m_gradient.setStops(stops);
0302     update();
0303     emit gradientChanged(m_gradient);
0304 }
0305 
0306 double KGradientEditor::toArrowPos(double stop) const
0307 {
0308     double l = (m_orientation == Qt::Horizontal) ? width() : height();
0309     l -= 2 * ArrowHalfWidth;
0310     return stop * l + ArrowHalfWidth;
0311 }
0312 
0313 double KGradientEditor::fromArrowPos(double pos) const
0314 {
0315     double l = (m_orientation == Qt::Horizontal) ? width() : height();
0316     l -= 2 * ArrowHalfWidth;
0317 
0318     double stop = (pos - ArrowHalfWidth) / l;
0319 
0320     if (stop < 0)
0321         stop = 0;
0322     else if (stop > 1)
0323         stop = 1;
0324 
0325     return stop;
0326 }
0327 // END class KGradientEditor
0328 
0329 // BEGIN class KGradientDialog
0330 KGradientDialog::KGradientDialog(QWidget *parent, bool modal)
0331     : QDialog(parent)
0332 {
0333     QWidget *widget = new QWidget(this);
0334     m_gradient = new KGradientEditor(widget);
0335     m_colorDialog = new QColorDialog(widget);
0336     m_colorDialog->setWindowFlags(Qt::Widget);
0337     m_colorDialog->setOptions(QColorDialog::DontUseNativeDialog | QColorDialog::NoButtons);
0338 
0339     QLabel *label = new QLabel(i18n("(Double-click on the gradient to add a stop)"), widget);
0340     QPushButton *button = new QPushButton(i18n("Remove stop"), widget);
0341     connect(button, &QPushButton::clicked, m_gradient, &KGradientEditor::removeStop);
0342 
0343     QDialogButtonBox *buttonBox = new QDialogButtonBox(modal ? QDialogButtonBox::Ok | QDialogButtonBox::Cancel : QDialogButtonBox::Close);
0344     connect(buttonBox, &QDialogButtonBox::accepted, this, &KGradientDialog::accept);
0345     connect(buttonBox, &QDialogButtonBox::rejected, this, &KGradientDialog::reject);
0346 
0347     // BEGIN layout widgets
0348     QVBoxLayout *layout = new QVBoxLayout(this);
0349     layout->setContentsMargins(0, 0, 0, 0);
0350 
0351     m_gradient->setFixedHeight(24);
0352     layout->addWidget(m_gradient);
0353 
0354     QHBoxLayout *hLayout = new QHBoxLayout;
0355     hLayout->addWidget(label);
0356     hLayout->addStretch(1);
0357     hLayout->addWidget(button);
0358     layout->addLayout(hLayout);
0359     layout->addWidget(m_colorDialog);
0360     layout->addWidget(buttonBox);
0361     resize(layout->minimumSize());
0362     // END layout widgets
0363 
0364     setWindowTitle(i18nc("@title:window", "Choose a Gradient"));
0365 
0366     setModal(modal);
0367 
0368     connect(m_gradient, &KGradientEditor::colorSelected, m_colorDialog, &QColorDialog::setCurrentColor);
0369     connect(m_colorDialog, &QColorDialog::currentColorChanged, m_gradient, &KGradientEditor::setColor);
0370     connect(m_gradient, &KGradientEditor::gradientChanged, this, &KGradientDialog::gradientChanged);
0371 
0372     m_colorDialog->setCurrentColor(m_gradient->color());
0373 }
0374 
0375 KGradientDialog::~KGradientDialog()
0376 {
0377 }
0378 
0379 // static
0380 int KGradientDialog::getGradient(QGradient &gradient, QWidget *parent)
0381 {
0382     QPointer<KGradientDialog> dlg = new KGradientDialog(parent, true);
0383     dlg->setGradient(gradient);
0384 
0385     int result = dlg->exec();
0386     if (result == Accepted)
0387         gradient = dlg->gradient();
0388     delete dlg;
0389     return result;
0390 }
0391 
0392 void KGradientDialog::setGradient(const QGradient &gradient)
0393 {
0394     m_gradient->setGradient(gradient);
0395 }
0396 
0397 QGradient KGradientDialog::gradient() const
0398 {
0399     return m_gradient->gradient();
0400 }
0401 // END class KGradientDialog
0402 
0403 // BEGIN class KGradientButton
0404 KGradientButton::KGradientButton(QWidget *parent)
0405     : QPushButton(parent)
0406 {
0407     connect(this, &KGradientButton::clicked, this, &KGradientButton::chooseGradient);
0408 }
0409 
0410 KGradientButton::~KGradientButton()
0411 {
0412 }
0413 
0414 void KGradientButton::initStyleOption(QStyleOptionButton *opt) const
0415 {
0416     opt->initFrom(this);
0417     opt->text.clear();
0418     opt->icon = QIcon();
0419     opt->features = QStyleOptionButton::None;
0420 }
0421 
0422 QSize KGradientButton::sizeHint() const
0423 {
0424     QStyleOptionButton opt;
0425     initStyleOption(&opt);
0426     return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(40, 15), this);
0427 }
0428 
0429 void KGradientButton::setGradient(const QGradient &gradient)
0430 {
0431     if (m_gradient.stops() == gradient.stops())
0432         return;
0433 
0434     m_gradient.setStops(gradient.stops());
0435     emit gradientChanged(m_gradient);
0436 }
0437 
0438 void KGradientButton::chooseGradient()
0439 {
0440     int result = KGradientDialog::getGradient(m_gradient, this);
0441     if (result == KGradientDialog::Accepted)
0442         emit gradientChanged(m_gradient);
0443 }
0444 
0445 void KGradientButton::paintEvent(QPaintEvent *)
0446 {
0447     // Mostly copied verbatim from KColorButton - thanks! :)
0448 
0449     QPainter painter(this);
0450 
0451     // First, we need to draw the bevel.
0452     QStyleOptionButton butOpt;
0453     initStyleOption(&butOpt);
0454     style()->drawControl(QStyle::CE_PushButtonBevel, &butOpt, &painter, this);
0455 
0456     // OK, now we can muck around with drawing out pretty little color box
0457     // First, sort out where it goes
0458     QRect labelRect = style()->subElementRect(QStyle::SE_PushButtonContents, &butOpt, this);
0459     int shift = style()->pixelMetric(QStyle::PM_ButtonMargin);
0460     labelRect.adjust(shift, shift, -shift, -shift);
0461     int x, y, w, h;
0462     labelRect.getRect(&x, &y, &w, &h);
0463 
0464     if (isChecked() || isDown()) {
0465         x += style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal);
0466         y += style()->pixelMetric(QStyle::PM_ButtonShiftVertical);
0467     }
0468 
0469     qDrawShadePanel(&painter, x, y, w, h, palette(), true, 1, nullptr);
0470 
0471     if (isEnabled()) {
0472         QLinearGradient lg(x + 1, 0, x + w - 1, 0);
0473         lg.setStops(m_gradient.stops());
0474         painter.setBrush(lg);
0475     } else
0476         painter.setBrush(palette().color(backgroundRole()));
0477 
0478     painter.drawRect(x + 1, y + 1, w - 2, h - 2);
0479 
0480     if (hasFocus()) {
0481         QRect focusRect = style()->subElementRect(QStyle::SE_PushButtonFocusRect, &butOpt, this);
0482         QStyleOptionFocusRect focusOpt;
0483         focusOpt.initFrom(this);
0484         focusOpt.rect = focusRect;
0485         focusOpt.backgroundColor = palette().window().color();
0486         style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, &painter, this);
0487     }
0488 }
0489 // END class KGradientButton
0490 
0491 #include "moc_kgradientdialog.cpp"