KDEGames

kgamepopupitem.cpp
1 /*******************************************************************
2  Copyright 2007 Dmitry Suzdalev <[email protected]>
3 
4  This library is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or
7  (at your option) any later version.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with this program; if not, write to the Free Software
16  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  ********************************************************************/
18 #include "kgamepopupitem.h"
19 #include <QPainter>
20 #include <QTimeLine>
21 #include <QTimer>
22 #include <QGraphicsScene>
23 #include <QGraphicsView>
24 #include <QGraphicsTextItem>
25 #include <QIcon>
26 
27 #include <KColorScheme>
28 
29 // margin on the sides of message box
30 static const int MARGIN = 15;
31 // offset of message from start of the scene
32 static const int SHOW_OFFSET = 5;
33 // space between pixmap and text
34 static const int SOME_SPACE = 10;
35 // width of the border in pixels
36 static const qreal BORDER_PEN_WIDTH = 1.0;
37 
38 class TextItemWithOpacity : public QGraphicsTextItem
39 {
40  Q_OBJECT
41 
42 public:
43  TextItemWithOpacity( QGraphicsItem* parent = nullptr )
44  :QGraphicsTextItem(parent), m_opacity(1.0) {}
45  void setOpacity(qreal opa) { m_opacity = opa; }
46  void setTextColor(const KStatefulBrush &brush) { m_brush = brush; }
47  void paint( QPainter* p, const QStyleOptionGraphicsItem *option, QWidget* widget ) override;
48 
49 Q_SIGNALS:
50  void mouseClicked();
51 
52 private:
54 
55 private:
56  qreal m_opacity;
57  KStatefulBrush m_brush;
58 };
59 
60 void TextItemWithOpacity::paint( QPainter* p, const QStyleOptionGraphicsItem *option, QWidget* widget )
61 {
62  // hope that it is ok to call this function here - i.e. I hope it won't be too expensive :)
63  // we call it here (and not in setTextColor), because KstatefulBrush
64  // absolutely needs QWidget parameter :)
65  //NOTE from majewsky: For some weird reason, setDefaultTextColor does on some systems not check
66  //whether the given color is equal to the one already set. Just calling setDefaultTextColor without
67  //this check may result in an infinite loop of paintEvent -> setDefaultTextColor -> update -> paintEvent...
68  const QColor textColor = m_brush.brush(widget).color();
69  if (textColor != defaultTextColor())
70  {
71  setDefaultTextColor(textColor);
72  }
73  //render contents
74  p->save();
75  p->setOpacity(m_opacity);
76  QGraphicsTextItem::paint(p,option,widget);
77  p->restore();
78 }
79 
80 void TextItemWithOpacity::mouseReleaseEvent(QGraphicsSceneMouseEvent* ev)
81 {
82  // NOTE: this item is QGraphicsTextItem which "eats" mouse events
83  // because of interaction with links. Because of that let's make a
84  // special signal to indicate mouse click
85  Q_EMIT mouseClicked();
87 }
88 
89 class KGamePopupItemPrivate
90 {
91 private:
92  KGamePopupItemPrivate(const KGamePopupItemPrivate&);
93  const KGamePopupItemPrivate& operator=(const KGamePopupItemPrivate&);
94 public:
95  KGamePopupItemPrivate()
96  : m_position( KGamePopupItem::BottomLeft ), m_timeout(2000),
97  m_opacity(1.0), m_animOpacity(-1), m_hoveredByMouse(false),
98  m_hideOnClick(true), m_textChildItem(nullptr),
99  m_sharpness(KGamePopupItem::Square), m_linkHovered(false) {}
103  QTimeLine m_timeLine;
107  QTimer m_timer;
111  QRectF m_boundRect;
115  KGamePopupItem::Position m_position;
119  int m_timeout;
123  qreal m_opacity;
127  qreal m_animOpacity;
131  QPixmap m_iconPix;
135  bool m_hoveredByMouse;
139  bool m_hideOnClick;
143  TextItemWithOpacity* m_textChildItem;
149  QRectF m_visibleSceneRect;
153  KStatefulBrush m_brush;
157  KGamePopupItem::Sharpness m_sharpness;
161  QPainterPath m_path;
165  bool m_linkHovered;
166 };
167 
169  : QGraphicsItem(parent), d(new KGamePopupItemPrivate)
170 {
171  hide();
172  d->m_textChildItem = new TextItemWithOpacity(this);
173  d->m_textChildItem->setTextInteractionFlags( Qt::LinksAccessibleByMouse );
174  // above call said to enable ItemIsFocusable which we don't need.
175  // So disabling it
176  d->m_textChildItem->setFlag( QGraphicsItem::ItemIsFocusable, false );
177 
178  connect(d->m_textChildItem, &TextItemWithOpacity::linkActivated, this, &KGamePopupItem::linkActivated);
179  connect(d->m_textChildItem, &TextItemWithOpacity::linkHovered, this, &KGamePopupItem::onLinkHovered);
180  connect(d->m_textChildItem, &TextItemWithOpacity::mouseClicked, this, &KGamePopupItem::onTextItemClicked);
181 
182  setZValue(100); // is 100 high enough???
183  d->m_textChildItem->setZValue(100);
184 
185  QIcon infoIcon = QIcon::fromTheme( QStringLiteral( "dialog-information" ));
186  // default size is 32
187  setMessageIcon( infoIcon.pixmap(32, 32) );
188 
189  d->m_timer.setSingleShot(true);
190 
191  setAcceptHoverEvents(true);
192  // ignore scene transformations
194 
195  // setup default colors
197  d->m_textChildItem->setTextColor( KStatefulBrush(KColorScheme::Tooltip, KColorScheme::NormalText) );
198 
199  connect(&d->m_timeLine, &QTimeLine::frameChanged, this, &KGamePopupItem::animationFrame);
200  connect(&d->m_timeLine, &QTimeLine::finished, this, &KGamePopupItem::hideMe);
201  connect(&d->m_timer, &QTimer::timeout, this, &KGamePopupItem::playHideAnimation);
202 }
203 
205 {
206  Q_UNUSED(option);
207  Q_UNUSED(widget);
208 
209  p->save();
210 
211  QPen pen = p->pen();
212  pen.setWidthF( BORDER_PEN_WIDTH );
213  p->setPen(pen);
214 
215  if( d->m_animOpacity != -1) // playing Center animation
216  {
217  p->setOpacity(d->m_animOpacity);
218  }
219  else
220  {
221  p->setOpacity(d->m_opacity);
222  }
223  p->setBrush(d->m_brush.brush(widget));
224  p->drawPath(d->m_path);
225  p->drawPixmap( MARGIN, static_cast<int>(d->m_boundRect.height()/2) - d->m_iconPix.height()/2,
226  d->m_iconPix );
227  p->restore();
228 }
229 
231 {
232  if(d->m_timeLine.state() == QTimeLine::Running || d->m_timer.isActive())
233  {
234  if (mode == ReplacePrevious)
235  {
236  forceHide(InstantHide);
237  }
238  else
239  {
240  return;// we're already showing a message
241  }
242  }
243 
244  // NOTE: we blindly take first visible view we found. I.e. we don't support
245  // multiple views. If no visible scene is found, we simply pick the first one.
246  QGraphicsView *sceneView = nullptr;
247  const auto views = scene()->views();
248  for (QGraphicsView *view : views) {
249  if (view->isVisible()) {
250  sceneView = view;
251  break;
252  }
253  }
254  if (!sceneView)
255  {
256  sceneView = views.at(0);
257  }
258 
259  QPolygonF poly = sceneView->mapToScene( sceneView->viewport()->contentsRect() );
260  d->m_visibleSceneRect = poly.boundingRect();
261 
262  d->m_textChildItem->setHtml(text);
263 
264  d->m_position = pos;
265 
266  // do as QGS docs say: notify the scene about rect change
268 
269  // recalculate bounding rect
270  qreal w = d->m_textChildItem->boundingRect().width()+MARGIN*2+d->m_iconPix.width()+SOME_SPACE;
271  qreal h = d->m_textChildItem->boundingRect().height()+MARGIN*2;
272  if( d->m_iconPix.height() > h )
273  {
274  h = d->m_iconPix.height() + MARGIN*2;
275  }
276  d->m_boundRect = QRectF(0, 0, w, h);
277 
278  // adjust to take into account the width of the pen
279  // used to draw the border
280  const qreal borderRadius = BORDER_PEN_WIDTH / 2.0;
281  d->m_boundRect.adjust( -borderRadius ,
282  -borderRadius ,
283  borderRadius ,
284  borderRadius );
285 
286  QPainterPath roundRectPath;
287  roundRectPath.moveTo(w, d->m_sharpness);
288  roundRectPath.arcTo(w-(2*d->m_sharpness), 0.0,(2*d->m_sharpness), (d->m_sharpness), 0.0, 90.0);
289  roundRectPath.lineTo(d->m_sharpness, 0.0);
290  roundRectPath.arcTo(0.0, 0.0, (2*d->m_sharpness), (2*d->m_sharpness), 90.0, 90.0);
291  roundRectPath.lineTo(0.0, h-(d->m_sharpness));
292  roundRectPath.arcTo(0.0, h-(2*d->m_sharpness), 2*d->m_sharpness, 2*d->m_sharpness, 180.0, 90.0);
293  roundRectPath.lineTo(w-(d->m_sharpness), h);
294  roundRectPath.arcTo(w-(2*d->m_sharpness), h-(2*d->m_sharpness), (2*d->m_sharpness), (2*d->m_sharpness), 270.0, 90.0);
295  roundRectPath.closeSubpath();
296 
297  d->m_path = roundRectPath;
298 
299  // adjust y-pos of text item so it appears centered
300  d->m_textChildItem->setPos( d->m_textChildItem->x(),
301  d->m_boundRect.height()/2 - d->m_textChildItem->boundingRect().height()/2);
302 
303  // setup animation
304  setupTimeline();
305 
306  // move to the start position
307  animationFrame(d->m_timeLine.startFrame());
308  show();
309  d->m_timeLine.start();
310 
311  if(d->m_timeout != 0)
312  {
313  // 300 msec to animate showing message + d->m_timeout to stay visible => then hide
314  d->m_timer.start( 300+d->m_timeout );
315  }
316 }
317 
318 void KGamePopupItem::setupTimeline()
319 {
320  d->m_timeLine.setDirection( QTimeLine::Forward );
321  d->m_timeLine.setDuration(300);
322  if( d->m_position == TopLeft || d->m_position == TopRight )
323  {
324  int start = static_cast<int>(d->m_visibleSceneRect.top() - d->m_boundRect.height() - SHOW_OFFSET);
325  int end = static_cast<int>(d->m_visibleSceneRect.top() + SHOW_OFFSET);
326  d->m_timeLine.setFrameRange( start, end );
327  }
328  else if( d->m_position == BottomLeft || d->m_position == BottomRight )
329  {
330  int start = static_cast<int>(d->m_visibleSceneRect.bottom()+SHOW_OFFSET);
331  int end = static_cast<int>(d->m_visibleSceneRect.bottom() - d->m_boundRect.height() - SHOW_OFFSET);
332  d->m_timeLine.setFrameRange( start, end );
333  }
334  else if( d->m_position == Center )
335  {
336  d->m_timeLine.setFrameRange(0, d->m_timeLine.duration());
337  setPos( d->m_visibleSceneRect.left() +
338  d->m_visibleSceneRect.width()/2 - d->m_boundRect.width()/2,
339  d->m_visibleSceneRect.top() +
340  d->m_visibleSceneRect.height()/2 - d->m_boundRect.height()/2);
341  }
342 
343 }
344 
345 void KGamePopupItem::animationFrame(int frame)
346 {
347  if( d->m_position == TopLeft || d->m_position == BottomLeft )
348  {
349  setPos( d->m_visibleSceneRect.left()+SHOW_OFFSET, frame );
350  }
351  else if( d->m_position == TopRight || d->m_position == BottomRight )
352  {
353  setPos( d->m_visibleSceneRect.right()-d->m_boundRect.width()-SHOW_OFFSET, frame );
354  }
355  else if( d->m_position == Center )
356  {
357  d->m_animOpacity = frame*d->m_opacity/d->m_timeLine.duration();
358  d->m_textChildItem->setOpacity( d->m_animOpacity );
359  update();
360  }
361 }
362 
363 void KGamePopupItem::playHideAnimation()
364 {
365  if( d->m_hoveredByMouse )
366  {
367  return;
368  }
369  // let's hide
370  d->m_timeLine.setDirection( QTimeLine::Backward );
371  d->m_timeLine.start();
372 }
373 
375 {
376  d->m_timeout = msec;
377 }
378 
380 {
381  d->m_hideOnClick = hide;
382 }
383 
385 {
386  return d->m_hideOnClick;
387 }
388 
390 {
391  d->m_opacity = opacity;
392  d->m_textChildItem->setOpacity(opacity);
393 }
394 
396 {
397  return d->m_boundRect;
398 }
399 
401 {
402  delete d;
403 }
404 
405 void KGamePopupItem::hideMe()
406 {
407  d->m_animOpacity = -1;
408  // and restore child's opacity too
409  d->m_textChildItem->setOpacity(d->m_opacity);
410 
411  // if we just got moved out of visibility, let's do more - let's hide :)
412  if( d->m_timeLine.direction() == QTimeLine::Backward )
413  {
414  hide();
415  Q_EMIT hidden();
416  }
417 }
418 
419 void KGamePopupItem::hoverEnterEvent( QGraphicsSceneHoverEvent* )
420 {
421  d->m_hoveredByMouse = true;
422 }
423 
424 void KGamePopupItem::hoverLeaveEvent( QGraphicsSceneHoverEvent* )
425 {
426  d->m_hoveredByMouse = false;
427 
428  if( d->m_timeout != 0 && !d->m_timer.isActive() && d->m_timeLine.state() != QTimeLine::Running )
429  {
430  playHideAnimation(); // let's hide
431  }
432 }
433 
435 {
436  d->m_iconPix = pix;
437  d->m_textChildItem->setPos( MARGIN+pix.width()+SOME_SPACE, MARGIN );
438  // bounding rect is updated in showMessage()
439 }
440 
442 {
443  return d->m_timeout;
444 }
445 
447 {
448  if(!isVisible())
449  {
450  return;
451  }
452 
453  if(howToHide == InstantHide)
454  {
455  d->m_timeLine.stop();
456  d->m_timer.stop();
457  hide();
458  Q_EMIT hidden();
459  }
460  else if(howToHide == AnimatedHide)
461  {
462  // forcefully unset it even if it is set
463  // so we'll hide in any event
464  d->m_hoveredByMouse = false;
465  d->m_timer.stop();
466  playHideAnimation();
467  }
468 }
469 
471 {
472  return d->m_opacity;
473 }
474 
476 {
477  d->m_brush = KStatefulBrush(brush);
478 }
479 
481 {
482  KStatefulBrush brush(color, d->m_brush.brush(QPalette::Active));
483  d->m_textChildItem->setTextColor(brush);
484 }
485 
486 void KGamePopupItem::onLinkHovered(const QString& link)
487 {
488  if(link.isEmpty())
489  {
490  d->m_textChildItem->setCursor( Qt::ArrowCursor );
491  }
492  else
493  {
494  d->m_textChildItem->setCursor( Qt::PointingHandCursor );
495  }
496 
497  d->m_linkHovered = !link.isEmpty();
498  Q_EMIT linkHovered(link);
499 }
500 
502 {
503  d->m_sharpness = sharpness;
504 }
505 
507 {
508  return d->m_sharpness;
509 }
510 
511 void KGamePopupItem::mousePressEvent( QGraphicsSceneMouseEvent* )
512 {
513  // it is needed to reimplement this function to receive future
514  // mouse release events
515 }
516 
517 void KGamePopupItem::mouseReleaseEvent( QGraphicsSceneMouseEvent* )
518 {
519  // NOTE: text child item is QGraphicsTextItem which "eats" mouse events
520  // because of interaction with links. Because of that TextItemWithOpacity has
521  // special signal to indicate mouse click which we catch in a onTextItemClicked()
522  // slot
523  if (d->m_hideOnClick)
524  {
525  forceHide();
526  }
527 }
528 
529 void KGamePopupItem::onTextItemClicked()
530 {
531  // if link is hovered we don't hide as click should go to the link
532  if (d->m_hideOnClick && !d->m_linkHovered)
533  {
534  forceHide();
535  }
536 }
537 
538 #include "moc_kgamepopupitem.cpp" // For automocing KGamePopupItem
539 #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.
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.
~KGamePopupItem()
Destructs a message item.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Nov 30 2020 22:37:54 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.