KDEGames

kgamepopupitem.cpp
1 /*
2  SPDX-FileCopyrightText: 2007 Dmitry Suzdalev <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "kgamepopupitem.h"
8 
9 // KF
10 #include <KColorScheme>
11 #include <kconfigwidgets_version.h>
12 #if KCONFIGWIDGETS_VERSION >= QT_VERSION_CHECK(5, 93, 0)
13 #include <KStatefulBrush> // was moved to own header in 5.93.0
14 #endif
15 // Qt
16 #include <QPainter>
17 #include <QTimeLine>
18 #include <QTimer>
19 #include <QGraphicsScene>
20 #include <QGraphicsView>
21 #include <QGraphicsTextItem>
22 #include <QIcon>
23 
24 // margin on the sides of message box
25 static const int MARGIN = 15;
26 // offset of message from start of the scene
27 static const int SHOW_OFFSET = 15;
28 // space between pixmap and text
29 static const int SOME_SPACE = 10;
30 // width of the border in pixels
31 static const qreal BORDER_PEN_WIDTH = 1.0;
32 
33 class TextItemWithOpacity : public QGraphicsTextItem
34 {
35  Q_OBJECT
36 
37 public:
38  TextItemWithOpacity( QGraphicsItem* parent = nullptr )
39  :QGraphicsTextItem(parent), m_opacity(1.0) {}
40  void setOpacity(qreal opa) { m_opacity = opa; }
41  void setTextColor(const KStatefulBrush &brush) { m_brush = brush; }
42  void paint( QPainter* p, const QStyleOptionGraphicsItem *option, QWidget* widget ) override;
43 
44 Q_SIGNALS:
45  void mouseClicked();
46 
47 private:
49 
50 private:
51  qreal m_opacity;
52  KStatefulBrush m_brush;
53 };
54 
55 void TextItemWithOpacity::paint( QPainter* p, const QStyleOptionGraphicsItem *option, QWidget* widget )
56 {
57  // hope that it is ok to call this function here - i.e. I hope it won't be too expensive :)
58  // we call it here (and not in setTextColor), because KstatefulBrush
59  // absolutely needs QWidget parameter :)
60  //NOTE from majewsky: For some weird reason, setDefaultTextColor does on some systems not check
61  //whether the given color is equal to the one already set. Just calling setDefaultTextColor without
62  //this check may result in an infinite loop of paintEvent -> setDefaultTextColor -> update -> paintEvent...
63  const QColor textColor = widget ? m_brush.brush(widget->palette()).color() : QColor(Qt::black);
64  if (textColor != defaultTextColor())
65  {
66  setDefaultTextColor(textColor);
67  }
68  //render contents
69  p->save();
70  p->setOpacity(m_opacity);
71  QGraphicsTextItem::paint(p,option,widget);
72  p->restore();
73 }
74 
75 void TextItemWithOpacity::mouseReleaseEvent(QGraphicsSceneMouseEvent* ev)
76 {
77  // NOTE: this item is QGraphicsTextItem which "eats" mouse events
78  // because of interaction with links. Because of that let's make a
79  // special signal to indicate mouse click
80  Q_EMIT mouseClicked();
82 }
83 
84 class KGamePopupItemPrivate
85 {
86 private:
87  KGamePopupItemPrivate(const KGamePopupItemPrivate&);
88  const KGamePopupItemPrivate& operator=(const KGamePopupItemPrivate&);
89 public:
90  KGamePopupItemPrivate()
91  : m_position( KGamePopupItem::BottomLeft ), m_timeout(2000),
92  m_opacity(1.0), m_animOpacity(-1), m_hoveredByMouse(false),
93  m_hideOnClick(true), m_textChildItem(nullptr),
94  m_sharpness(KGamePopupItem::Square), m_linkHovered(false) {}
95  /**
96  * Timeline for animations
97  */
98  QTimeLine m_timeLine;
99  /**
100  * Timer used to start hiding
101  */
102  QTimer m_timer;
103  /**
104  * Holds bounding rect of an item
105  */
106  QRect m_boundRect;
107  /**
108  * Holds bounding rect of an item (mapped to scene coordinates)
109  */
110  QRectF m_mappedBoundRect;
111  /**
112  * Offset of message from start of the scene (mapped to scene coordinates)
113  */
114  qreal m_mapped_SHOW_OFFSET;
115  /**
116  * Position where item will appear
117  */
118  KGamePopupItem::Position m_position;
119  /**
120  * Timeout to stay visible on screen
121  */
122  int m_timeout;
123  /**
124  * Item opacity
125  */
126  qreal m_opacity;
127  /**
128  * Opacity used while animating appearing in center
129  */
130  qreal m_animOpacity;
131  /**
132  * Pixmap to display at the left of the text
133  */
134  QPixmap m_iconPix;
135  /**
136  * Set to true when mouse hovers the message
137  */
138  bool m_hoveredByMouse;
139  /**
140  * Set to true if this popup item hides on mouse click.
141  */
142  bool m_hideOnClick;
143  /**
144  * Child of KGamePopupItem used to display text
145  */
146  TextItemWithOpacity* m_textChildItem;
147  /**
148  * Part of the scene that is actually visible in QGraphicsView
149  * This is needed for item to work correctly when scene is larger than
150  * the View
151  */
152  QRectF m_visibleSceneRect;
153  /**
154  * Background brush color
155  */
156  KStatefulBrush m_brush;
157  /**
158  * popup angles sharpness
159  */
160  KGamePopupItem::Sharpness m_sharpness;
161  /**
162  * painter path to draw a frame
163  */
164  QPainterPath m_path;
165  /**
166  * Indicates if some link is hovered in text item
167  */
168  bool m_linkHovered;
169 };
170 
172  : QGraphicsItem(parent), d(new KGamePopupItemPrivate)
173 {
174  hide();
175  d->m_textChildItem = new TextItemWithOpacity(this);
176  d->m_textChildItem->setTextInteractionFlags( Qt::LinksAccessibleByMouse );
177  // above call said to enable ItemIsFocusable which we don't need.
178  // So disabling it
179  d->m_textChildItem->setFlag( QGraphicsItem::ItemIsFocusable, false );
180 
181  connect(d->m_textChildItem, &TextItemWithOpacity::linkActivated, this, &KGamePopupItem::linkActivated);
182  connect(d->m_textChildItem, &TextItemWithOpacity::linkHovered, this, &KGamePopupItem::onLinkHovered);
183  connect(d->m_textChildItem, &TextItemWithOpacity::mouseClicked, this, &KGamePopupItem::onTextItemClicked);
184 
185  setZValue(100); // is 100 high enough???
186  d->m_textChildItem->setZValue(100);
187 
188  QIcon infoIcon = QIcon::fromTheme( QStringLiteral( "dialog-information" ));
189  // default size is 32
190  setMessageIcon( infoIcon.pixmap(32, 32) );
191 
192  d->m_timer.setSingleShot(true);
193 
194  setAcceptHoverEvents(true);
195  // ignore scene transformations
197 
198  // setup default colors
200  d->m_textChildItem->setTextColor( KStatefulBrush(KColorScheme::Tooltip, KColorScheme::NormalText) );
201 
202  connect(&d->m_timeLine, &QTimeLine::frameChanged, this, &KGamePopupItem::animationFrame);
203  connect(&d->m_timeLine, &QTimeLine::finished, this, &KGamePopupItem::hideMe);
204  connect(&d->m_timer, &QTimer::timeout, this, &KGamePopupItem::playHideAnimation);
205 }
206 
208 
210 {
211  Q_UNUSED(option);
212 
213  p->save();
214 
215  QPen pen = p->pen();
216  pen.setWidthF( BORDER_PEN_WIDTH );
217  p->setPen(pen);
218 
219  if( d->m_animOpacity != -1) // playing Center animation
220  {
221  p->setOpacity(d->m_animOpacity);
222  }
223  else
224  {
225  p->setOpacity(d->m_opacity);
226  }
227  p->setBrush(widget ? d->m_brush.brush(widget->palette()) : QBrush());
228  p->drawPath(d->m_path);
229  p->drawPixmap( MARGIN, static_cast<int>(d->m_boundRect.height()/2) - d->m_iconPix.height()/2.0/d->m_iconPix.devicePixelRatio(),
230  d->m_iconPix );
231  p->restore();
232 }
233 
235 {
236  if(d->m_timeLine.state() == QTimeLine::Running || d->m_timer.isActive())
237  {
238  if (mode == ReplacePrevious)
239  {
240  forceHide(InstantHide);
241  }
242  else
243  {
244  return;// we're already showing a message
245  }
246  }
247 
248  // NOTE: we blindly take first visible view we found. I.e. we don't support
249  // multiple views. If no visible scene is found, we simply pick the first one.
250  QGraphicsView *sceneView = nullptr;
251  const auto views = scene()->views();
252  for (QGraphicsView *view : views) {
253  if (view->isVisible()) {
254  sceneView = view;
255  break;
256  }
257  }
258  if (!sceneView)
259  {
260  sceneView = views.at(0);
261  }
262 
263  QPolygonF poly = sceneView->mapToScene( sceneView->viewport()->contentsRect() );
264  d->m_visibleSceneRect = poly.boundingRect();
265 
266  d->m_textChildItem->setHtml(text);
267 
268  d->m_position = pos;
269 
270  // do as QGS docs say: notify the scene about rect change
272 
273  // recalculate bounding rect
274  const qreal iconDpr = d->m_iconPix.devicePixelRatio();
275  qreal w = d->m_textChildItem->boundingRect().width()+MARGIN*2+d->m_iconPix.width()/iconDpr+SOME_SPACE;
276  qreal h = d->m_textChildItem->boundingRect().height()+MARGIN*2;
277  if( d->m_iconPix.height()/iconDpr > h )
278  {
279  h = d->m_iconPix.height()/iconDpr + MARGIN*2;
280  }
281  d->m_boundRect = QRect(0, 0, w, h);
282 
283  // adjust to take into account the width of the pen
284  // used to draw the border
285  const qreal borderRadius = BORDER_PEN_WIDTH / 2.0;
286  d->m_boundRect.adjust( -borderRadius ,
287  -borderRadius ,
288  borderRadius ,
289  borderRadius );
290 
291  d->m_mappedBoundRect = sceneView->mapToScene(d->m_boundRect).boundingRect();
292  d->m_mapped_SHOW_OFFSET = qAbs(sceneView->mapToScene(0, SHOW_OFFSET).y());
293 
294  QPainterPath roundRectPath;
295  roundRectPath.moveTo(w, d->m_sharpness);
296  roundRectPath.arcTo(w-(2*d->m_sharpness), 0.0,(2*d->m_sharpness), (d->m_sharpness), 0.0, 90.0);
297  roundRectPath.lineTo(d->m_sharpness, 0.0);
298  roundRectPath.arcTo(0.0, 0.0, (2*d->m_sharpness), (2*d->m_sharpness), 90.0, 90.0);
299  roundRectPath.lineTo(0.0, h-(d->m_sharpness));
300  roundRectPath.arcTo(0.0, h-(2*d->m_sharpness), 2*d->m_sharpness, 2*d->m_sharpness, 180.0, 90.0);
301  roundRectPath.lineTo(w-(d->m_sharpness), h);
302  roundRectPath.arcTo(w-(2*d->m_sharpness), h-(2*d->m_sharpness), (2*d->m_sharpness), (2*d->m_sharpness), 270.0, 90.0);
303  roundRectPath.closeSubpath();
304 
305  d->m_path = roundRectPath;
306 
307  // adjust y-pos of text item so it appears centered
308  d->m_textChildItem->setPos( d->m_textChildItem->x(),
309  d->m_boundRect.height()/2 - d->m_textChildItem->boundingRect().height()/2);
310 
311  // setup animation
312  setupTimeline();
313 
314  // move to the start position
315  animationFrame(d->m_timeLine.startFrame());
316  show();
317  d->m_timeLine.start();
318 
319  if(d->m_timeout != 0)
320  {
321  // 300 msec to animate showing message + d->m_timeout to stay visible => then hide
322  d->m_timer.start( 300+d->m_timeout );
323  }
324 }
325 
326 void KGamePopupItem::setupTimeline()
327 {
328  d->m_timeLine.setDirection( QTimeLine::Forward );
329  d->m_timeLine.setDuration(300);
330  if( d->m_position == TopLeft || d->m_position == TopRight )
331  {
332  int start = static_cast<int>(d->m_visibleSceneRect.top() - d->m_mappedBoundRect.height() - d->m_mapped_SHOW_OFFSET);
333  int end = static_cast<int>(d->m_visibleSceneRect.top() + d->m_mapped_SHOW_OFFSET);
334  d->m_timeLine.setFrameRange( start, end );
335  }
336  else if( d->m_position == BottomLeft || d->m_position == BottomRight )
337  {
338  int start = static_cast<int>(d->m_visibleSceneRect.bottom() + d->m_mapped_SHOW_OFFSET);
339  int end = static_cast<int>(d->m_visibleSceneRect.bottom() - d->m_mappedBoundRect.height() - d->m_mapped_SHOW_OFFSET);
340  d->m_timeLine.setFrameRange( start, end );
341  }
342  else if( d->m_position == Center )
343  {
344  d->m_timeLine.setFrameRange(0, d->m_timeLine.duration());
345  setPos( d->m_visibleSceneRect.left() +
346  d->m_visibleSceneRect.width()/2 - d->m_mappedBoundRect.width()/2,
347  d->m_visibleSceneRect.top() +
348  d->m_visibleSceneRect.height()/2 - d->m_mappedBoundRect.height()/2);
349  }
350 
351 }
352 
353 void KGamePopupItem::animationFrame(int frame)
354 {
355  if( d->m_position == TopLeft || d->m_position == BottomLeft )
356  {
357  setPos( d->m_visibleSceneRect.left()+d->m_mapped_SHOW_OFFSET, frame );
358  }
359  else if( d->m_position == TopRight || d->m_position == BottomRight )
360  {
361  setPos( d->m_visibleSceneRect.right()-d->m_mappedBoundRect.width()-d->m_mapped_SHOW_OFFSET, frame );
362  }
363  else if( d->m_position == Center )
364  {
365  d->m_animOpacity = frame*d->m_opacity/d->m_timeLine.duration();
366  d->m_textChildItem->setOpacity( d->m_animOpacity );
367  update();
368  }
369 }
370 
371 void KGamePopupItem::playHideAnimation()
372 {
373  if( d->m_hoveredByMouse )
374  {
375  return;
376  }
377  // let's hide
378  d->m_timeLine.setDirection( QTimeLine::Backward );
379  d->m_timeLine.start();
380 }
381 
383 {
384  d->m_timeout = msec;
385 }
386 
388 {
389  d->m_hideOnClick = hide;
390 }
391 
393 {
394  return d->m_hideOnClick;
395 }
396 
398 {
399  d->m_opacity = opacity;
400  d->m_textChildItem->setOpacity(opacity);
401 }
402 
404 {
405  return d->m_boundRect;
406 }
407 
408 void KGamePopupItem::hideMe()
409 {
410  d->m_animOpacity = -1;
411  // and restore child's opacity too
412  d->m_textChildItem->setOpacity(d->m_opacity);
413 
414  // if we just got moved out of visibility, let's do more - let's hide :)
415  if( d->m_timeLine.direction() == QTimeLine::Backward )
416  {
417  hide();
418  Q_EMIT hidden();
419  }
420 }
421 
422 void KGamePopupItem::hoverEnterEvent( QGraphicsSceneHoverEvent* )
423 {
424  d->m_hoveredByMouse = true;
425 }
426 
427 void KGamePopupItem::hoverLeaveEvent( QGraphicsSceneHoverEvent* )
428 {
429  d->m_hoveredByMouse = false;
430 
431  if( d->m_timeout != 0 && !d->m_timer.isActive() && d->m_timeLine.state() != QTimeLine::Running )
432  {
433  playHideAnimation(); // let's hide
434  }
435 }
436 
438 {
439  d->m_iconPix = pix;
440  d->m_textChildItem->setPos( MARGIN+pix.width()/pix.devicePixelRatio()+SOME_SPACE, MARGIN );
441  // bounding rect is updated in showMessage()
442 }
443 
445 {
446  return d->m_timeout;
447 }
448 
450 {
451  if(!isVisible())
452  {
453  return;
454  }
455 
456  if(howToHide == InstantHide)
457  {
458  d->m_timeLine.stop();
459  d->m_timer.stop();
460  hide();
461  Q_EMIT hidden();
462  }
463  else if(howToHide == AnimatedHide)
464  {
465  // forcefully unset it even if it is set
466  // so we'll hide in any event
467  d->m_hoveredByMouse = false;
468  d->m_timer.stop();
469  playHideAnimation();
470  }
471 }
472 
474 {
475  return d->m_opacity;
476 }
477 
479 {
480  d->m_brush = KStatefulBrush(brush);
481 }
482 
484 {
485  KStatefulBrush brush(color, d->m_brush.brush(QPalette::Active));
486  d->m_textChildItem->setTextColor(brush);
487 }
488 
489 void KGamePopupItem::onLinkHovered(const QString& link)
490 {
491  if(link.isEmpty())
492  {
493  d->m_textChildItem->setCursor( Qt::ArrowCursor );
494  }
495  else
496  {
497  d->m_textChildItem->setCursor( Qt::PointingHandCursor );
498  }
499 
500  d->m_linkHovered = !link.isEmpty();
501  Q_EMIT linkHovered(link);
502 }
503 
505 {
506  d->m_sharpness = sharpness;
507 }
508 
510 {
511  return d->m_sharpness;
512 }
513 
514 void KGamePopupItem::mousePressEvent( QGraphicsSceneMouseEvent* )
515 {
516  // it is needed to reimplement this function to receive future
517  // mouse release events
518 }
519 
520 void KGamePopupItem::mouseReleaseEvent( QGraphicsSceneMouseEvent* )
521 {
522  // NOTE: text child item is QGraphicsTextItem which "eats" mouse events
523  // because of interaction with links. Because of that TextItemWithOpacity has
524  // special signal to indicate mouse click which we catch in a onTextItemClicked()
525  // slot
526  if (d->m_hideOnClick)
527  {
528  forceHide();
529  }
530 }
531 
532 void KGamePopupItem::onTextItemClicked()
533 {
534  // if link is hovered we don't hide as click should go to the link
535  if (d->m_hideOnClick && !d->m_linkHovered)
536  {
537  forceHide();
538  }
539 }
540 
541 #include "moc_kgamepopupitem.cpp" // For automocing KGamePopupItem
542 #include "kgamepopupitem.moc" // For automocing TextItemWithOpacity
Q_OBJECTQ_OBJECT
void setZValue(qreal z)
void arcTo(const QRectF &rectangle, qreal startAngle, qreal sweepLength)
void setPen(const QColor &color)
HideType
Used to specify how to hide in forceHide() - instantly or animatedly.
int messageTimeout() const
qreal devicePixelRatio() const const
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
Q_EMITQ_EMIT
QRectF boundingRect() const const
void update(const QRectF &rect)
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
void setOpacity(qreal opacity)
Sharpness sharpness() const
QRectF boundingRect() const override
Q_SCRIPTABLE Q_NOREPLY void start()
QIcon fromTheme(const QString &name)
void setHideOnMouseClick(bool hide)
Sets whether to hide this popup item on mouse click.
void paint(QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *widget) override
Paints item.
const QPen & pen() const const
void prepareGeometryChange()
void frameChanged(int frame)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void hidden()
Emitted when the popup finishes hiding.
void setWidthF(qreal width)
ReplaceMode
Possible values for message showing mode in respect to a previous message.
KIOCORE_EXPORT CopyJob * link(const QList< QUrl > &src, const QUrl &destDir, JobFlags flags=DefaultFlags)
void setAcceptHoverEvents(bool enabled)
QList< QGraphicsView * > views() const const
LinksAccessibleByMouse
void setPos(const QPointF &pos)
QGraphicsScene * scene() const const
void timeout()
void drawPath(const QPainterPath &path)
ArrowCursor
void linkActivated(const QString &link)
Emitted when user clicks on a link in item.
~KGamePopupItem() override
Destructs a message item.
void forceHide(HideType type=AnimatedHide)
Requests the item to be hidden immediately.
void setFlag(QGraphicsItem::GraphicsItemFlag flag, bool enabled)
void setBrush(const QBrush &brush)
void setMessageIcon(const QPixmap &pix)
Sets custom pixmap to show instead of default icon on the left.
Position
The possible places in the scene where a message can be shown.
Q_SIGNALSQ_SIGNALS
void setOpacity(qreal opacity)
void linkHovered(const QString &link)
Emitted when user hovers a link in item.
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
void closeSubpath()
void setBackgroundBrush(const QBrush &brush)
Sets brush used to paint item background By default system-default brush is used.
qreal y() const const
void setSharpness(Sharpness sharpness)
Sets the popup angles sharpness.
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) const const
qreal messageOpacity() const
Sharpness
Possible values for the popup angles sharpness.
void moveTo(const QPointF &point)
void showMessage(const QString &text, Position pos, ReplaceMode mode=LeavePrevious)
Shows the message: item will appear at specified place of the scene using simple animation Item will ...
void restore()
KGamePopupItem(QGraphicsItem *parent=nullptr)
Constructs a message item.
QPointF pos() const const
void save()
bool isVisible() const const
void setTextColor(const QColor &color)
Sets default color for unformatted text By default system-default color is used.
qreal opacity() const const
QWidget * viewport() const const
void setMessageOpacity(qreal opacity)
Sets the message opacity from 0 (fully transparent) to 1 (fully opaque) For example 0....
void setMessageTimeout(int msec)
Sets the amount of time the item will stay visible on screen before it goes away.
const QList< QKeySequence > & end()
bool hidesOnMouseClick() const
QPointF mapToScene(const QPoint &point) const const
int width() const const
QRect contentsRect() const const
void finished()
void lineTo(const QPointF &endPoint)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon May 16 2022 04:17:34 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.