ctkPopupWidget.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. /*=========================================================================
  2. Library: CTK
  3. Copyright (c) Kitware Inc.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0.txt
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. =========================================================================*/
  14. // Qt includes
  15. #include <QApplication>
  16. #include <QDebug>
  17. #include <QDesktopWidget>
  18. #include <QDir>
  19. #include <QEvent>
  20. #include <QLabel>
  21. #include <QLayout>
  22. #include <QMouseEvent>
  23. #include <QMoveEvent>
  24. #include <QPainter>
  25. #include <QPointer>
  26. #include <QPropertyAnimation>
  27. #include <QStyle>
  28. #include <QTimer>
  29. // CTK includes
  30. #include "ctkPopupWidget_p.h"
  31. // -------------------------------------------------------------------------
  32. ctkPopupWidgetPrivate::ctkPopupWidgetPrivate(ctkPopupWidget& object)
  33. :Superclass(object)
  34. {
  35. this->Active = false;
  36. this->AutoShow = true;
  37. this->ShowDelay = 20;
  38. this->AutoHide = true;
  39. this->HideDelay = 200;
  40. }
  41. // -------------------------------------------------------------------------
  42. ctkPopupWidgetPrivate::~ctkPopupWidgetPrivate()
  43. {
  44. }
  45. // -------------------------------------------------------------------------
  46. void ctkPopupWidgetPrivate::init()
  47. {
  48. Q_Q(ctkPopupWidget);
  49. this->setParent(q);
  50. q->setActive(true);
  51. this->Superclass::init();
  52. }
  53. // -------------------------------------------------------------------------
  54. QWidget* ctkPopupWidgetPrivate::mouseOver()
  55. {
  56. Q_Q(ctkPopupWidget);
  57. QWidget* widgetUnderCursor = this->Superclass::mouseOver();
  58. if (widgetUnderCursor &&
  59. !this->focusWidgets(true).contains(widgetUnderCursor))
  60. {
  61. widgetUnderCursor->installEventFilter(q);
  62. }
  63. return widgetUnderCursor;
  64. }
  65. // -------------------------------------------------------------------------
  66. bool ctkPopupWidgetPrivate::eventFilter(QObject* obj, QEvent* event)
  67. {
  68. Q_Q(ctkPopupWidget);
  69. QWidget* widget = qobject_cast<QWidget*>(obj);
  70. // Here are the application events, it's a lot of events, so we need to be
  71. // careful to be fast.
  72. if (event->type() == QEvent::ApplicationDeactivate)
  73. {
  74. // We wait to see if there is no other window being active
  75. QTimer::singleShot(0, this, SLOT(onApplicationDeactivate()));
  76. }
  77. else if (event->type() == QEvent::ApplicationActivate)
  78. {
  79. QTimer::singleShot(0, this, SLOT(updateVisibility()));
  80. }
  81. if (!this->BaseWidget)
  82. {
  83. return false;
  84. }
  85. if (event->type() == QEvent::Move && widget != this->BaseWidget)
  86. {
  87. if (widget->isAncestorOf(this->BaseWidget))
  88. {
  89. QMoveEvent* moveEvent = dynamic_cast<QMoveEvent*>(event);
  90. QPoint topLeft = widget->parentWidget() ? widget->parentWidget()->mapToGlobal(moveEvent->pos()) : moveEvent->pos();
  91. topLeft += this->BaseWidget->mapTo(widget, QPoint(0,0));
  92. //q->move(q->pos() + moveEvent->pos() - moveEvent->oldPos());
  93. QRect newBaseGeometry = this->baseGeometry();
  94. newBaseGeometry.moveTopLeft(topLeft);
  95. QRect desiredGeometry = this->desiredOpenGeometry(newBaseGeometry);
  96. q->move(desiredGeometry.topLeft());
  97. }
  98. else if (widget->isWindow() &&
  99. widget->windowType() != Qt::ToolTip &&
  100. widget->windowType() != Qt::Popup)
  101. {
  102. QTimer::singleShot(0, this, SLOT(updateVisibility()));
  103. }
  104. }
  105. else if (event->type() == QEvent::Resize)
  106. {
  107. if (widget->isWindow() &&
  108. widget != this->BaseWidget->window() &&
  109. widget->windowType() != Qt::ToolTip &&
  110. widget->windowType() != Qt::Popup)
  111. {
  112. QTimer::singleShot(0, this, SLOT(updateVisibility()));
  113. }
  114. }
  115. else if (event->type() == QEvent::WindowStateChange &&
  116. widget != this->BaseWidget->window() &&
  117. widget->windowType() != Qt::ToolTip &&
  118. widget->windowType() != Qt::Popup)
  119. {
  120. QTimer::singleShot(0, this, SLOT(updateVisibility()));
  121. }
  122. else if ((event->type() == QEvent::WindowActivate ||
  123. event->type() == QEvent::WindowDeactivate) &&
  124. widget == this->BaseWidget->window())
  125. {
  126. QTimer::singleShot(0, this, SLOT(updateVisibility()));
  127. }
  128. else if (event->type() == QEvent::RequestSoftwareInputPanel)
  129. {
  130. qApp->setActiveWindow(widget->window());
  131. }
  132. return false;
  133. }
  134. // -------------------------------------------------------------------------
  135. void ctkPopupWidgetPrivate::onApplicationDeactivate()
  136. {
  137. // Still no active window, that means the user now is controlling another
  138. // application, we have no control over when the other app moves over the
  139. // popup, so we hide the popup as it would show on top of the other app.
  140. if (!qApp->activeWindow())
  141. {
  142. this->temporarilyHiddenOn();
  143. }
  144. }
  145. // -------------------------------------------------------------------------
  146. void ctkPopupWidgetPrivate::updateVisibility()
  147. {
  148. Q_Q(ctkPopupWidget);
  149. // If the BaseWidget window is active, then there is no reason to cover the
  150. // popup.
  151. if (!this->BaseWidget ||
  152. // the popupwidget active window is not active
  153. (!this->BaseWidget->window()->isActiveWindow() &&
  154. // and no other active window
  155. (!qApp->activeWindow() ||
  156. // or the active window is a popup/tooltip
  157. (qApp->activeWindow()->windowType() != Qt::ToolTip &&
  158. qApp->activeWindow()->windowType() != Qt::Popup))))
  159. {
  160. foreach(QWidget* topLevelWidget, qApp->topLevelWidgets())
  161. {
  162. // If there is at least 1 window (active or not) that covers the popup,
  163. // then we ensure the popup is hidden.
  164. // We have no way of knowing which toplevel is over (z-order) which one,
  165. // it is an OS specific information.
  166. // Of course, tooltips and popups don't count as covering windows.
  167. if (topLevelWidget->isVisible() &&
  168. !(topLevelWidget->windowState() & Qt::WindowMinimized) &&
  169. topLevelWidget->windowType() != Qt::ToolTip &&
  170. topLevelWidget->windowType() != Qt::Popup &&
  171. topLevelWidget != (this->BaseWidget ? this->BaseWidget->window() : 0) &&
  172. topLevelWidget->frameGeometry().intersects(q->geometry()))
  173. {
  174. //qDebug() << "hide" << q << "because of: " << topLevelWidget
  175. // << " with windowType: " << topLevelWidget->windowType()
  176. // << topLevelWidget->isVisible()
  177. // << (this->BaseWidget ? this->BaseWidget->window() : 0)
  178. // << topLevelWidget->frameGeometry();
  179. this->temporarilyHiddenOn();
  180. return;
  181. }
  182. }
  183. }
  184. // If the base widget is hidden or minimized, we don't want to restore the
  185. // popup.
  186. if (this->BaseWidget &&
  187. (!this->BaseWidget->isVisible() ||
  188. this->BaseWidget->window()->windowState() & Qt::WindowMinimized))
  189. {
  190. return;
  191. }
  192. // Restore the visibility of the popup if it was hidden
  193. this->temporarilyHiddenOff();
  194. }
  195. // -------------------------------------------------------------------------
  196. void ctkPopupWidgetPrivate::temporarilyHiddenOn()
  197. {
  198. Q_Q(ctkPopupWidget);
  199. if (!this->AutoHide &&
  200. (q->isVisible() || this->isOpening()) &&
  201. !(q->isHidden() || this->isClosing()))
  202. {
  203. this->setProperty("forcedClosed", this->isOpening() ? 2 : 1);
  204. }
  205. this->currentAnimation()->stop();
  206. this->hideAll();
  207. }
  208. // -------------------------------------------------------------------------
  209. void ctkPopupWidgetPrivate::temporarilyHiddenOff()
  210. {
  211. Q_Q(ctkPopupWidget);
  212. int forcedClosed = this->property("forcedClosed").toInt();
  213. if (forcedClosed > 0)
  214. {
  215. q->show();
  216. if (forcedClosed == 2)
  217. {
  218. emit q->popupOpened(true);
  219. }
  220. this->setProperty("forcedClosed", 0);
  221. }
  222. else
  223. {
  224. q->updatePopup();
  225. }
  226. }
  227. // -------------------------------------------------------------------------
  228. // Qt::FramelessWindowHint is required on Windows for Translucent background
  229. // Qt::Toolip is preferred to Qt::Popup as it would close itself at the first
  230. // click outside the widget (typically a click in the BaseWidget)
  231. ctkPopupWidget::ctkPopupWidget(QWidget* parentWidget)
  232. : Superclass(new ctkPopupWidgetPrivate(*this), parentWidget)
  233. {
  234. Q_D(ctkPopupWidget);
  235. d->init();
  236. }
  237. // -------------------------------------------------------------------------
  238. ctkPopupWidget::~ctkPopupWidget()
  239. {
  240. }
  241. // -------------------------------------------------------------------------
  242. bool ctkPopupWidget::isActive()const
  243. {
  244. Q_D(const ctkPopupWidget);
  245. return d->Active;
  246. }
  247. // -------------------------------------------------------------------------
  248. void ctkPopupWidget::setActive(bool active)
  249. {
  250. Q_D(ctkPopupWidget);
  251. if (active == d->Active)
  252. {
  253. return;
  254. }
  255. d->Active = active;
  256. if (d->Active)
  257. {
  258. if (d->BaseWidget)
  259. {
  260. d->BaseWidget->installEventFilter(this);
  261. }
  262. if (d->PopupPixmapWidget)
  263. {
  264. d->PopupPixmapWidget->installEventFilter(this);
  265. }
  266. qApp->installEventFilter(d);
  267. }
  268. else // not active
  269. {
  270. if (d->BaseWidget)
  271. {
  272. d->BaseWidget->removeEventFilter(this);
  273. }
  274. if (d->PopupPixmapWidget)
  275. {
  276. d->PopupPixmapWidget->removeEventFilter(this);
  277. }
  278. qApp->removeEventFilter(d);
  279. }
  280. }
  281. // -------------------------------------------------------------------------
  282. void ctkPopupWidget::setBaseWidget(QWidget* widget)
  283. {
  284. Q_D(ctkPopupWidget);
  285. if (d->BaseWidget)
  286. {
  287. d->BaseWidget->removeEventFilter(this);
  288. }
  289. this->Superclass::setBaseWidget(widget);
  290. if (d->BaseWidget && d->Active)
  291. {
  292. d->BaseWidget->installEventFilter(this);
  293. }
  294. QTimer::singleShot(d->ShowDelay, this, SLOT(updatePopup()));
  295. }
  296. // -------------------------------------------------------------------------
  297. bool ctkPopupWidget::autoShow()const
  298. {
  299. Q_D(const ctkPopupWidget);
  300. return d->AutoShow;
  301. }
  302. // -------------------------------------------------------------------------
  303. void ctkPopupWidget::setAutoShow(bool mode)
  304. {
  305. Q_D(ctkPopupWidget);
  306. d->AutoShow = mode;
  307. QTimer::singleShot(d->ShowDelay, this, SLOT(updatePopup()));
  308. }
  309. // -------------------------------------------------------------------------
  310. int ctkPopupWidget::showDelay()const
  311. {
  312. Q_D(const ctkPopupWidget);
  313. return d->ShowDelay;
  314. }
  315. // -------------------------------------------------------------------------
  316. void ctkPopupWidget::setShowDelay(int delay)
  317. {
  318. Q_D(ctkPopupWidget);
  319. d->ShowDelay = delay;
  320. }
  321. // -------------------------------------------------------------------------
  322. bool ctkPopupWidget::autoHide()const
  323. {
  324. Q_D(const ctkPopupWidget);
  325. return d->AutoHide;
  326. }
  327. // -------------------------------------------------------------------------
  328. void ctkPopupWidget::setAutoHide(bool mode)
  329. {
  330. Q_D(ctkPopupWidget);
  331. d->AutoHide = mode;
  332. QTimer::singleShot(d->HideDelay, this, SLOT(updatePopup()));
  333. }
  334. // -------------------------------------------------------------------------
  335. int ctkPopupWidget::hideDelay()const
  336. {
  337. Q_D(const ctkPopupWidget);
  338. return d->HideDelay;
  339. }
  340. // -------------------------------------------------------------------------
  341. void ctkPopupWidget::setHideDelay(int delay)
  342. {
  343. Q_D(ctkPopupWidget);
  344. d->HideDelay = delay;
  345. }
  346. // -------------------------------------------------------------------------
  347. void ctkPopupWidget::onEffectFinished()
  348. {
  349. Q_D(ctkPopupWidget);
  350. bool wasClosing = d->wasClosing();
  351. this->Superclass::onEffectFinished();
  352. if (wasClosing)
  353. {
  354. /// restore the AutoShow if needed.
  355. if (!this->property("AutoShowOnClose").isNull())
  356. {
  357. d->AutoShow = this->property("AutoShowOnClose").toBool();
  358. this->setProperty("AutoShowOnClose", QVariant());
  359. }
  360. }
  361. }
  362. // --------------------------------------------------------------------------
  363. void ctkPopupWidget::leaveEvent(QEvent* event)
  364. {
  365. Q_D(ctkPopupWidget);
  366. QTimer::singleShot(d->HideDelay, this, SLOT(updatePopup()));
  367. this->Superclass::leaveEvent(event);
  368. }
  369. // --------------------------------------------------------------------------
  370. void ctkPopupWidget::enterEvent(QEvent* event)
  371. {
  372. Q_D(ctkPopupWidget);
  373. QTimer::singleShot(d->ShowDelay, this, SLOT(updatePopup()));
  374. this->Superclass::enterEvent(event);
  375. }
  376. // --------------------------------------------------------------------------
  377. bool ctkPopupWidget::eventFilter(QObject* obj, QEvent* event)
  378. {
  379. Q_D(ctkPopupWidget);
  380. // Here we listen to PopupPixmapWidget, BaseWidget and ctkPopupWidget
  381. // children popups that were under the mouse
  382. switch(event->type())
  383. {
  384. case QEvent::Move:
  385. {
  386. if (obj != d->BaseWidget)
  387. {
  388. break;
  389. }
  390. QMoveEvent* moveEvent = dynamic_cast<QMoveEvent*>(event);
  391. QRect newBaseGeometry = d->baseGeometry();
  392. newBaseGeometry.moveTopLeft(d->mapToGlobal(moveEvent->pos()));
  393. QRect desiredGeometry = d->desiredOpenGeometry(newBaseGeometry);
  394. this->move(desiredGeometry.topLeft());
  395. //this->move(this->pos() + moveEvent->pos() - moveEvent->oldPos());
  396. this->update();
  397. break;
  398. }
  399. case QEvent::Hide:
  400. case QEvent::Close:
  401. // if the mouse was in a base widget child popup, then when we leave
  402. // the popup we want to check if it needs to be closed.
  403. if (obj != d->BaseWidget)
  404. {
  405. if (obj != d->PopupPixmapWidget &&
  406. qobject_cast<QWidget*>(obj)->windowType() == Qt::Popup)
  407. {
  408. obj->removeEventFilter(this);
  409. QTimer::singleShot(d->HideDelay, this, SLOT(updatePopup()));
  410. }
  411. break;
  412. }
  413. d->temporarilyHiddenOn();
  414. break;
  415. case QEvent::Show:
  416. if (obj != d->BaseWidget)
  417. {
  418. break;
  419. }
  420. this->setGeometry(d->desiredOpenGeometry());
  421. d->temporarilyHiddenOff();
  422. break;
  423. case QEvent::Resize:
  424. if (obj != d->BaseWidget ||
  425. !(d->Alignment & Qt::AlignJustify ||
  426. (d->Alignment & Qt::AlignTop && d->Alignment & Qt::AlignBottom)) ||
  427. !(d->isOpening() || this->isVisible()))
  428. {
  429. break;
  430. }
  431. // TODO: bug when the effect is WindowOpacityFadeEffect
  432. this->setGeometry(d->desiredOpenGeometry());
  433. break;
  434. case QEvent::Enter:
  435. if ( d->currentAnimation()->state() == QAbstractAnimation::Stopped )
  436. {
  437. // Maybe the user moved the mouse on the widget by mistake, don't open
  438. // the popup instantly...
  439. QTimer::singleShot(d->ShowDelay, this, SLOT(updatePopup()));
  440. }
  441. else
  442. {
  443. // ... except if the popup is closing, we want to reopen it as sooon as
  444. // possible.
  445. this->updatePopup();
  446. }
  447. break;
  448. case QEvent::Leave:
  449. // Don't listen to base widget children that are popups as what
  450. // matters here is their close event instead
  451. if (obj != d->BaseWidget &&
  452. obj != d->PopupPixmapWidget &&
  453. qobject_cast<QWidget*>(obj)->windowType() == Qt::Popup)
  454. {
  455. break;
  456. }
  457. // The mouse might have left the area that keeps the popup open
  458. QTimer::singleShot(d->HideDelay, this, SLOT(updatePopup()));
  459. if (obj != d->BaseWidget &&
  460. obj != d->PopupPixmapWidget)
  461. {
  462. obj->removeEventFilter(this);
  463. }
  464. break;
  465. default:
  466. break;
  467. }
  468. return this->QObject::eventFilter(obj, event);
  469. }
  470. // --------------------------------------------------------------------------
  471. void ctkPopupWidget::updatePopup()
  472. {
  473. Q_D(ctkPopupWidget);
  474. // Querying mouseOver can be slow, don't do it if not needed.
  475. QWidget* mouseOver = (d->AutoShow || d->AutoHide) ? d->mouseOver() : 0;
  476. if ((d->AutoShow ||
  477. // Even if there is no AutoShow, we might still want to reopen the popup
  478. // when closing it inadvertently, except if we are un-pin-ing the popup
  479. (d->AutoHide && d->isClosing() && this->property("AutoShowOnClose").toBool())) &&
  480. // to be automatically open, the mouse has to be over a child widget
  481. mouseOver &&
  482. // disable opening the popup when the popup is disabled
  483. (!d->BaseWidget || d->BaseWidget->isEnabled()))
  484. {
  485. this->showPopup();
  486. }
  487. else if (d->AutoHide && !mouseOver)
  488. {
  489. this->hidePopup();
  490. }
  491. }
  492. // --------------------------------------------------------------------------
  493. void ctkPopupWidget::hidePopup()
  494. {
  495. // just in case it was set.
  496. this->setProperty("forcedClosed", 0);
  497. this->Superclass::hidePopup();
  498. }
  499. // --------------------------------------------------------------------------
  500. void ctkPopupWidget::pinPopup(bool pin)
  501. {
  502. Q_D(ctkPopupWidget);
  503. this->setAutoHide(!pin);
  504. if (pin)
  505. {
  506. this->showPopup();
  507. }
  508. else
  509. {
  510. // When closing, we don't want to inadvertently re-open the menu.
  511. this->setProperty("AutoShowOnClose", this->autoShow());
  512. d->AutoShow = false;
  513. this->hidePopup();
  514. }
  515. }