Unity 8
PressedOutsideNotifier.cpp
1 /*
2  * Copyright (C) 2013 Canonical, Ltd.
3  *
4  * This program 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; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include "PressedOutsideNotifier.h"
18 
19 #include <QMouseEvent>
20 
21 PressedOutsideNotifier::PressedOutsideNotifier(QQuickItem *parent)
22  : QQuickItem(parent)
23 {
24  connect(this, &QQuickItem::enabledChanged,
25  this, &PressedOutsideNotifier::setupOrTearDownEventFiltering);
26 
27  m_signalEmissionTimer.setSingleShot(true);
28  m_signalEmissionTimer.setInterval(0); // times out on the next iteration of the event loop
29  connect(&m_signalEmissionTimer, &QTimer::timeout,
30  this, &PressedOutsideNotifier::pressedOutside);
31 }
32 
33 bool PressedOutsideNotifier::eventFilter(QObject *watched, QEvent *event)
34 {
35  Q_UNUSED(watched);
36  Q_ASSERT(watched == m_filteredWindow);
37 
38  // We are already going to emit pressedOutside() anyway, thus no need
39  // for new checks.
40  // This case takes place when a QTouchEvent comes in and isn't handled by any item,
41  // causing QQuickWindow to synthesize a QMouseEvent out of it, which would
42  // be filtered by us as well and count as a second press, which is wrong.
43  if (m_signalEmissionTimer.isActive()) {
44  return false;
45  }
46 
47  switch (event->type()) {
48  case QEvent::MouseButtonPress: {
49  QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
50  QPointF p = mapFromScene(mouseEvent->windowPos());
51  if (!contains(p)) {
52  m_signalEmissionTimer.start();
53  }
54  break;
55  }
56  case QEvent::TouchBegin:
57  processFilteredTouchBegin(static_cast<QTouchEvent*>(event));
58  default:
59  break;
60  }
61 
62  // let the event be handled further
63  return false;
64 }
65 
66 void PressedOutsideNotifier::itemChange(ItemChange change, const ItemChangeData &value)
67 {
68  if (change == QQuickItem::ItemSceneChange) {
69  setupOrTearDownEventFiltering();
70  }
71 
72  QQuickItem::itemChange(change, value);
73 }
74 
75 void PressedOutsideNotifier::setupOrTearDownEventFiltering()
76 {
77  if (isEnabled() && window()) {
78  setupEventFiltering();
79  } else if (m_filteredWindow) {
80  tearDownEventFiltering();
81  }
82 }
83 
84 void PressedOutsideNotifier::setupEventFiltering()
85 {
86  QQuickWindow *currentWindow = window();
87  Q_ASSERT(currentWindow != nullptr);
88 
89  if (currentWindow == m_filteredWindow)
90  return;
91 
92  if (m_filteredWindow) {
93  m_filteredWindow->removeEventFilter(this);
94  }
95 
96  currentWindow->installEventFilter(this);
97  m_filteredWindow = currentWindow;
98 }
99 
100 void PressedOutsideNotifier::tearDownEventFiltering()
101 {
102  m_filteredWindow->removeEventFilter(this);
103  m_filteredWindow.clear();
104 }
105 
106 void PressedOutsideNotifier::processFilteredTouchBegin(QTouchEvent *event)
107 {
108  const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
109  for (int i = 0; i < touchPoints.count(); ++i) {
110  const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
111  if (touchPoint.state() == Qt::TouchPointPressed) {
112  QPointF p = mapFromScene(touchPoint.pos());
113  if (!contains(p)) {
114  m_signalEmissionTimer.start();
115  return;
116  }
117  }
118  }
119 }