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

KDE's Doxygen guidelines are available online.