ctkPopupWidget.cpp 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148
  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. #define LEAVE_CLOSING_DELAY 100 // we don't want to be too fast to close
  32. #define ENTER_OPENING_DELAY 20 // we want to be responsive but allow "errors"
  33. #define DEFAULT_FADING_DURATION 333 // fast enough without being too slow
  34. // -------------------------------------------------------------------------
  35. QGradient* duplicateGradient(const QGradient* gradient)
  36. {
  37. QGradient* newGradient = 0;
  38. switch (gradient->type())
  39. {
  40. case QGradient::LinearGradient:
  41. {
  42. const QLinearGradient* linearGradient = static_cast<const QLinearGradient*>(gradient);
  43. newGradient = new QLinearGradient(linearGradient->start(), linearGradient->finalStop());
  44. break;
  45. }
  46. case QGradient::RadialGradient:
  47. {
  48. const QRadialGradient* radialGradient = static_cast<const QRadialGradient*>(gradient);
  49. newGradient = new QRadialGradient(radialGradient->center(), radialGradient->radius());
  50. break;
  51. }
  52. case QGradient::ConicalGradient:
  53. {
  54. const QConicalGradient* conicalGradient = static_cast<const QConicalGradient*>(gradient);
  55. newGradient = new QConicalGradient(conicalGradient->center(), conicalGradient->angle());
  56. break;
  57. }
  58. default:
  59. break;
  60. }
  61. if (!newGradient)
  62. {
  63. Q_ASSERT(gradient->type() != QGradient::NoGradient);
  64. return newGradient;
  65. }
  66. newGradient->setCoordinateMode(gradient->coordinateMode());
  67. newGradient->setSpread(gradient->spread());
  68. newGradient->setStops(gradient->stops());
  69. return newGradient;
  70. }
  71. // -------------------------------------------------------------------------
  72. ctkPopupWidgetPrivate::ctkPopupWidgetPrivate(ctkPopupWidget& object)
  73. :q_ptr(&object)
  74. {
  75. this->BaseWidget = 0;
  76. this->AutoShow = true;
  77. this->AutoHide = true;
  78. this->Effect = ctkPopupWidget::ScrollEffect;
  79. this->EffectAlpha = 1.;
  80. this->AlphaAnimation = 0;
  81. this->ForcedTranslucent = false;
  82. this->ScrollAnimation = 0;
  83. this->PopupPixmapWidget = 0;
  84. // Geometry attributes
  85. this->Alignment = Qt::AlignJustify | Qt::AlignBottom;
  86. this->Orientations = Qt::Vertical;
  87. this->VerticalDirection = ctkPopupWidget::TopToBottom;
  88. this->HorizontalDirection = Qt::LeftToRight;
  89. }
  90. // -------------------------------------------------------------------------
  91. ctkPopupWidgetPrivate::~ctkPopupWidgetPrivate()
  92. {
  93. delete this->PopupPixmapWidget;
  94. }
  95. // -------------------------------------------------------------------------
  96. void ctkPopupWidgetPrivate::init()
  97. {
  98. Q_Q(ctkPopupWidget);
  99. // By default, Tooltips are shown only on active windows. In a popup widget
  100. // case, we sometimes aren't the active window but we still would like to
  101. // show the children tooltips.
  102. q->setAttribute(Qt::WA_AlwaysShowToolTips, true);
  103. this->AlphaAnimation = new QPropertyAnimation(q, "effectAlpha", q);
  104. this->AlphaAnimation->setDuration(DEFAULT_FADING_DURATION);
  105. this->AlphaAnimation->setStartValue(0.);
  106. this->AlphaAnimation->setEndValue(1.);
  107. QObject::connect(this->AlphaAnimation, SIGNAL(finished()),
  108. q, SLOT(onEffectFinished()));
  109. this->PopupPixmapWidget = new QLabel(0, Qt::ToolTip | Qt::FramelessWindowHint);
  110. this->PopupPixmapWidget->installEventFilter(q);
  111. this->ScrollAnimation = new QPropertyAnimation(q, "effectGeometry", q);
  112. this->ScrollAnimation->setDuration(DEFAULT_FADING_DURATION);
  113. QObject::connect(this->ScrollAnimation, SIGNAL(finished()),
  114. q, SLOT(onEffectFinished()));
  115. qApp->installEventFilter(this);
  116. q->setAnimationEffect(this->Effect);
  117. q->setEasingCurve(QEasingCurve::OutCubic);
  118. }
  119. // -------------------------------------------------------------------------
  120. QPropertyAnimation* ctkPopupWidgetPrivate::currentAnimation()const
  121. {
  122. return this->Effect == ctkPopupWidget::ScrollEffect ?
  123. this->ScrollAnimation : this->AlphaAnimation;
  124. }
  125. // -------------------------------------------------------------------------
  126. bool ctkPopupWidgetPrivate::isOpening()const
  127. {
  128. return this->currentAnimation()->state() == QAbstractAnimation::Running &&
  129. this->currentAnimation()->direction() == QAbstractAnimation::Forward;
  130. }
  131. // -------------------------------------------------------------------------
  132. bool ctkPopupWidgetPrivate::isClosing()const
  133. {
  134. return this->currentAnimation()->state() == QAbstractAnimation::Running &&
  135. this->currentAnimation()->direction() == QAbstractAnimation::Backward;
  136. }
  137. // -------------------------------------------------------------------------
  138. QList<const QWidget*> ctkPopupWidgetPrivate::focusWidgets(bool onlyVisible)const
  139. {
  140. Q_Q(const ctkPopupWidget);
  141. QList<const QWidget*> res;
  142. if (!onlyVisible || q->isVisible())
  143. {
  144. res << q;
  145. }
  146. if (this->BaseWidget && (!onlyVisible || this->BaseWidget->isVisible()))
  147. {
  148. res << this->BaseWidget;
  149. }
  150. if (this->PopupPixmapWidget && (!onlyVisible || this->PopupPixmapWidget->isVisible()))
  151. {
  152. res << this->PopupPixmapWidget;
  153. }
  154. return res;
  155. }
  156. // -------------------------------------------------------------------------
  157. bool ctkPopupWidgetPrivate::mouseOver()
  158. {
  159. Q_Q(ctkPopupWidget);
  160. QList<const QWidget*> widgets = this->focusWidgets(true);
  161. foreach(const QWidget* widget, widgets)
  162. {
  163. if (widget->underMouse())
  164. {
  165. return true;
  166. }
  167. }
  168. // Warning QApplication::widgetAt(QCursor::pos()) can be a bit slow...
  169. const QPoint pos = QCursor::pos();
  170. QWidget* widgetUnderCursor = qApp->widgetAt(pos);
  171. foreach(const QWidget* focusWidget, widgets)
  172. {
  173. if (this->isAncestorOf(focusWidget, widgetUnderCursor) &&
  174. // Ignore when cursor is above a title bar of a focusWidget, underMouse
  175. // wouldn't have return false, but QApplication::widgetAt would return
  176. // the widget
  177. (focusWidget != widgetUnderCursor ||
  178. QRect(QPoint(0,0), focusWidget->size()).contains(
  179. focusWidget->mapFromGlobal(pos))))
  180. {
  181. widgetUnderCursor->installEventFilter(q);
  182. return true;
  183. }
  184. }
  185. return false;
  186. }
  187. // -------------------------------------------------------------------------
  188. bool ctkPopupWidgetPrivate::isAncestorOf(const QWidget* ancestor, const QWidget* child)const
  189. {
  190. while (child)
  191. {
  192. if (child == ancestor)
  193. return true;
  194. child = child->parentWidget();
  195. }
  196. return false;
  197. }
  198. // -------------------------------------------------------------------------
  199. void ctkPopupWidgetPrivate::setupPopupPixmapWidget()
  200. {
  201. Q_Q(ctkPopupWidget);
  202. this->PopupPixmapWidget->setAlignment(this->pixmapAlignment());
  203. QPixmap pixmap;
  204. if (q->testAttribute(Qt::WA_TranslucentBackground))
  205. {
  206. // only QImage handle transparency correctly
  207. QImage image(q->geometry().size(), QImage::Format_ARGB32);
  208. image.fill(0);
  209. q->render(&image);
  210. pixmap = QPixmap::fromImage(image);
  211. }
  212. else
  213. {
  214. pixmap = QPixmap::grabWidget(q, QRect(QPoint(0,0), q->geometry().size()));
  215. }
  216. this->PopupPixmapWidget->setPixmap(pixmap);
  217. this->PopupPixmapWidget->setAttribute(
  218. Qt::WA_TranslucentBackground, q->testAttribute(Qt::WA_TranslucentBackground));
  219. this->PopupPixmapWidget->setWindowOpacity(q->windowOpacity());
  220. }
  221. // -------------------------------------------------------------------------
  222. Qt::Alignment ctkPopupWidgetPrivate::pixmapAlignment()const
  223. {
  224. Qt::Alignment alignment;
  225. if (this->VerticalDirection == ctkPopupWidget::TopToBottom)
  226. {
  227. alignment |= Qt::AlignBottom;
  228. }
  229. else// if (this->VerticalDirection == ctkPopupWidget::BottomToTop)
  230. {
  231. alignment |= Qt::AlignTop;
  232. }
  233. if (this->HorizontalDirection == Qt::LeftToRight)
  234. {
  235. alignment |= Qt::AlignRight;
  236. }
  237. else// if (this->VerticalDirection == ctkPopupWidget::BottomToTop)
  238. {
  239. alignment |= Qt::AlignLeft;
  240. }
  241. return alignment;
  242. }
  243. // -------------------------------------------------------------------------
  244. QRect ctkPopupWidgetPrivate::closedGeometry()const
  245. {
  246. Q_Q(const ctkPopupWidget);
  247. return this->closedGeometry(q->geometry());
  248. }
  249. // -------------------------------------------------------------------------
  250. QRect ctkPopupWidgetPrivate::closedGeometry(QRect openGeom)const
  251. {
  252. if (this->Orientations & Qt::Vertical)
  253. {
  254. if (this->VerticalDirection == ctkPopupWidget::BottomToTop)
  255. {
  256. openGeom.moveTop(openGeom.bottom());
  257. }
  258. openGeom.setHeight(0);
  259. }
  260. if (this->Orientations & Qt::Horizontal)
  261. {
  262. if (this->HorizontalDirection == Qt::RightToLeft)
  263. {
  264. openGeom.moveLeft(openGeom.right());
  265. }
  266. openGeom.setWidth(0);
  267. }
  268. return openGeom;
  269. }
  270. // -------------------------------------------------------------------------
  271. QRect ctkPopupWidgetPrivate::baseGeometry()const
  272. {
  273. if (!this->BaseWidget)
  274. {
  275. return QRect();
  276. }
  277. return QRect(this->mapToGlobal(this->BaseWidget->geometry().topLeft()),
  278. this->BaseWidget->size());
  279. }
  280. // -------------------------------------------------------------------------
  281. QPoint ctkPopupWidgetPrivate::mapToGlobal(const QPoint& baseWidgetPoint)const
  282. {
  283. QPoint mappedPoint = baseWidgetPoint;
  284. if (this->BaseWidget && this->BaseWidget->parentWidget())
  285. {
  286. mappedPoint = this->BaseWidget->parentWidget()->mapToGlobal(mappedPoint);
  287. }
  288. return mappedPoint;
  289. }
  290. // -------------------------------------------------------------------------
  291. QRect ctkPopupWidgetPrivate::desiredOpenGeometry()const
  292. {
  293. return this->desiredOpenGeometry(this->baseGeometry());
  294. }
  295. // -------------------------------------------------------------------------
  296. QRect ctkPopupWidgetPrivate::desiredOpenGeometry(QRect baseGeometry)const
  297. {
  298. Q_Q(const ctkPopupWidget);
  299. QSize size = q->size();
  300. if (!q->testAttribute(Qt::WA_WState_Created))
  301. {
  302. size = q->sizeHint();
  303. }
  304. if (baseGeometry.isNull())
  305. {
  306. return QRect(q->pos(), size);
  307. }
  308. QRect geometry;
  309. if (this->Alignment & Qt::AlignJustify)
  310. {
  311. if (this->Orientations & Qt::Vertical)
  312. {
  313. size.setWidth(baseGeometry.width());
  314. }
  315. }
  316. if (this->Alignment & Qt::AlignTop &&
  317. this->Alignment & Qt::AlignBottom)
  318. {
  319. size.setHeight(baseGeometry.height());
  320. }
  321. geometry.setSize(size);
  322. QPoint topLeft = baseGeometry.topLeft();
  323. QPoint bottomRight = baseGeometry.bottomRight();
  324. if (this->Alignment & Qt::AlignLeft)
  325. {
  326. if (this->HorizontalDirection == Qt::LeftToRight)
  327. {
  328. geometry.moveLeft(topLeft.x());
  329. }
  330. else
  331. {
  332. geometry.moveRight(topLeft.x());
  333. }
  334. }
  335. else if (this->Alignment & Qt::AlignRight)
  336. {
  337. if (this->HorizontalDirection == Qt::LeftToRight)
  338. {
  339. geometry.moveLeft(bottomRight.x());
  340. }
  341. else
  342. {
  343. geometry.moveRight(bottomRight.x());
  344. }
  345. }
  346. else if (this->Alignment & Qt::AlignHCenter)
  347. {
  348. geometry.moveLeft((topLeft.x() + bottomRight.x()) / 2 - size.width() / 2);
  349. }
  350. else if (this->Alignment & Qt::AlignJustify)
  351. {
  352. geometry.moveLeft(topLeft.x());
  353. }
  354. if (this->Alignment & Qt::AlignTop)
  355. {
  356. if (this->VerticalDirection == ctkPopupWidget::TopToBottom)
  357. {
  358. geometry.moveTop(topLeft.y());
  359. }
  360. else
  361. {
  362. geometry.moveBottom(topLeft.y());
  363. }
  364. }
  365. else if (this->Alignment & Qt::AlignBottom)
  366. {
  367. if (this->VerticalDirection == ctkPopupWidget::TopToBottom)
  368. {
  369. geometry.moveTop(bottomRight.y());
  370. }
  371. else
  372. {
  373. geometry.moveBottom(bottomRight.y());
  374. }
  375. }
  376. else if (this->Alignment & Qt::AlignVCenter)
  377. {
  378. geometry.moveTop((topLeft.y() + bottomRight.y()) / 2 - size.height() / 2);
  379. }
  380. return geometry;
  381. }
  382. // -------------------------------------------------------------------------
  383. bool ctkPopupWidgetPrivate::eventFilter(QObject* obj, QEvent* event)
  384. {
  385. Q_Q(ctkPopupWidget);
  386. QWidget* widget = qobject_cast<QWidget*>(obj);
  387. // Here are the application events, it's a lot of events, so we need to be
  388. // careful to be fast.
  389. if (event->type() == QEvent::ApplicationDeactivate)
  390. {
  391. // We wait to see if there is no other window being active
  392. QTimer::singleShot(0, this, SLOT(onApplicationDeactivate()));
  393. }
  394. else if (event->type() == QEvent::ApplicationActivate)
  395. {
  396. QTimer::singleShot(0, this, SLOT(updateVisibility()));
  397. }
  398. if (!this->BaseWidget)
  399. {
  400. return false;
  401. }
  402. if (event->type() == QEvent::Move && widget != this->BaseWidget)
  403. {
  404. if (widget->isAncestorOf(this->BaseWidget))
  405. {
  406. QMoveEvent* moveEvent = dynamic_cast<QMoveEvent*>(event);
  407. QPoint topLeft = widget->parentWidget() ? widget->parentWidget()->mapToGlobal(moveEvent->pos()) : moveEvent->pos();
  408. topLeft += this->BaseWidget->mapTo(widget, QPoint(0,0));
  409. //q->move(q->pos() + moveEvent->pos() - moveEvent->oldPos());
  410. QRect newBaseGeometry = this->baseGeometry();
  411. newBaseGeometry.moveTopLeft(topLeft);
  412. QRect desiredGeometry = this->desiredOpenGeometry(newBaseGeometry);
  413. q->move(desiredGeometry.topLeft());
  414. }
  415. else if (widget->isWindow() &&
  416. widget->windowType() != Qt::ToolTip &&
  417. widget->windowType() != Qt::Popup)
  418. {
  419. QTimer::singleShot(0, this, SLOT(updateVisibility()));
  420. }
  421. }
  422. else if (event->type() == QEvent::Resize)
  423. {
  424. if (widget->isWindow() &&
  425. widget != this->BaseWidget->window() &&
  426. widget->windowType() != Qt::ToolTip &&
  427. widget->windowType() != Qt::Popup)
  428. {
  429. QTimer::singleShot(0, this, SLOT(updateVisibility()));
  430. }
  431. }
  432. else if (event->type() == QEvent::WindowStateChange &&
  433. widget != this->BaseWidget->window() &&
  434. widget->windowType() != Qt::ToolTip &&
  435. widget->windowType() != Qt::Popup)
  436. {
  437. QTimer::singleShot(0, this, SLOT(updateVisibility()));
  438. }
  439. else if ((event->type() == QEvent::WindowActivate ||
  440. event->type() == QEvent::WindowDeactivate) &&
  441. widget == this->BaseWidget->window())
  442. {
  443. QTimer::singleShot(0, this, SLOT(updateVisibility()));
  444. }
  445. return false;
  446. }
  447. // -------------------------------------------------------------------------
  448. void ctkPopupWidgetPrivate::onApplicationDeactivate()
  449. {
  450. // Still no active window, that means the user now is controlling another
  451. // application, we have no control over when the other app moves over the
  452. // popup, so we hide the popup as it would show on top of the other app.
  453. if (!qApp->activeWindow())
  454. {
  455. this->temporarilyHiddenOn();
  456. }
  457. }
  458. // -------------------------------------------------------------------------
  459. void ctkPopupWidgetPrivate::updateVisibility()
  460. {
  461. Q_Q(ctkPopupWidget);
  462. // If the BaseWidget window is active, then there is no reason to cover the
  463. // popup.
  464. if (!this->BaseWidget ||
  465. // the popupwidget active window is not active
  466. (!this->BaseWidget->window()->isActiveWindow() &&
  467. // and no other active window
  468. (!qApp->activeWindow() ||
  469. // or the active window is a popup/tooltip
  470. (qApp->activeWindow()->windowType() != Qt::ToolTip &&
  471. qApp->activeWindow()->windowType() != Qt::Popup))))
  472. {
  473. foreach(QWidget* topLevelWidget, qApp->topLevelWidgets())
  474. {
  475. // If there is at least 1 window (active or not) that covers the popup,
  476. // then we ensure the popup is hidden.
  477. // We have no way of knowing which toplevel is over (z-order) which one,
  478. // it is an OS specific information.
  479. // Of course, tooltips and popups don't count as covering windows.
  480. if (topLevelWidget->isVisible() &&
  481. !(topLevelWidget->windowState() & Qt::WindowMinimized) &&
  482. topLevelWidget->windowType() != Qt::ToolTip &&
  483. topLevelWidget->windowType() != Qt::Popup &&
  484. topLevelWidget != (this->BaseWidget ? this->BaseWidget->window() : 0) &&
  485. topLevelWidget->frameGeometry().intersects(q->geometry()))
  486. {
  487. //qDebug() << "hide" << q << "because of: " << topLevelWidget
  488. // << " with windowType: " << topLevelWidget->windowType()
  489. // << topLevelWidget->isVisible()
  490. // << (this->BaseWidget ? this->BaseWidget->window() : 0)
  491. // << topLevelWidget->frameGeometry();
  492. this->temporarilyHiddenOn();
  493. return;
  494. }
  495. }
  496. }
  497. // If the base widget is hidden or minimized, we don't want to restore the
  498. // popup.
  499. if (this->BaseWidget &&
  500. (!this->BaseWidget->isVisible() ||
  501. this->BaseWidget->window()->windowState() & Qt::WindowMinimized))
  502. {
  503. return;
  504. }
  505. // Restore the visibility of the popup if it was hidden
  506. this->temporarilyHiddenOff();
  507. }
  508. // -------------------------------------------------------------------------
  509. void ctkPopupWidgetPrivate::onBaseWidgetDestroyed()
  510. {
  511. Q_Q(ctkPopupWidget);
  512. this->hideAll();
  513. q->setBaseWidget(0);
  514. // could be a property.
  515. q->deleteLater();
  516. }
  517. // -------------------------------------------------------------------------
  518. void ctkPopupWidgetPrivate::temporarilyHiddenOn()
  519. {
  520. Q_Q(ctkPopupWidget);
  521. if (!this->AutoHide &&
  522. (q->isVisible() || this->isOpening()) &&
  523. !(q->isHidden() || this->isClosing()))
  524. {
  525. this->setProperty("forcedClosed", this->isOpening() ? 2 : 1);
  526. }
  527. this->currentAnimation()->stop();
  528. this->hideAll();
  529. }
  530. // -------------------------------------------------------------------------
  531. void ctkPopupWidgetPrivate::temporarilyHiddenOff()
  532. {
  533. Q_Q(ctkPopupWidget);
  534. int forcedClosed = this->property("forcedClosed").toInt();
  535. if (forcedClosed > 0)
  536. {
  537. q->show();
  538. if (forcedClosed == 2)
  539. {
  540. emit q->popupOpened(true);
  541. }
  542. this->setProperty("forcedClosed", 0);
  543. }
  544. else
  545. {
  546. q->updatePopup();
  547. }
  548. }
  549. // -------------------------------------------------------------------------
  550. void ctkPopupWidgetPrivate::hideAll()
  551. {
  552. Q_Q(ctkPopupWidget);
  553. // Before hiding, transfer the active window flag to its parent, this will
  554. // prevent the application to send a ApplicationDeactivate signal that
  555. // doesn't need to be done.
  556. if (q->isActiveWindow() && this->BaseWidget)
  557. {
  558. qApp->setActiveWindow(this->BaseWidget->window());
  559. }
  560. q->hide();
  561. this->PopupPixmapWidget->hide();
  562. // If there is a popup open in the ctkPopupWidget children, then hide it
  563. // as well so we don't have a popup open while the ctkPopupWidget is hidden.
  564. QPointer<QWidget> activePopupWidget = qApp->activePopupWidget();
  565. if (activePopupWidget && this->isAncestorOf(q, activePopupWidget))
  566. {
  567. activePopupWidget->close();
  568. }
  569. }
  570. // -------------------------------------------------------------------------
  571. // Qt::FramelessWindowHint is required on Windows for Translucent background
  572. // Qt::Toolip is preferred to Qt::Popup as it would close itself at the first
  573. // click outside the widget (typically a click in the BaseWidget)
  574. ctkPopupWidget::ctkPopupWidget(QWidget* parentWidget)
  575. : Superclass(QApplication::desktop()->screen(QApplication::desktop()->screenNumber(parentWidget)),
  576. Qt::ToolTip | Qt::FramelessWindowHint)
  577. , d_ptr(new ctkPopupWidgetPrivate(*this))
  578. {
  579. Q_D(ctkPopupWidget);
  580. d->init();
  581. }
  582. // -------------------------------------------------------------------------
  583. ctkPopupWidget::~ctkPopupWidget()
  584. {
  585. }
  586. // -------------------------------------------------------------------------
  587. QWidget* ctkPopupWidget::baseWidget()const
  588. {
  589. Q_D(const ctkPopupWidget);
  590. return d->BaseWidget;
  591. }
  592. // -------------------------------------------------------------------------
  593. void ctkPopupWidget::setBaseWidget(QWidget* widget)
  594. {
  595. Q_D(ctkPopupWidget);
  596. if (d->BaseWidget)
  597. {
  598. d->BaseWidget->removeEventFilter(this);
  599. disconnect(d->BaseWidget, SIGNAL(destroyed(QObject*)),
  600. d, SLOT(onBaseWidgetDestroyed()));
  601. }
  602. d->BaseWidget = widget;
  603. if (d->BaseWidget)
  604. {
  605. d->BaseWidget->installEventFilter(this);
  606. connect(d->BaseWidget, SIGNAL(destroyed(QObject*)),
  607. d, SLOT(onBaseWidgetDestroyed()));
  608. }
  609. QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
  610. }
  611. // -------------------------------------------------------------------------
  612. bool ctkPopupWidget::autoShow()const
  613. {
  614. Q_D(const ctkPopupWidget);
  615. return d->AutoShow;
  616. }
  617. // -------------------------------------------------------------------------
  618. void ctkPopupWidget::setAutoShow(bool mode)
  619. {
  620. Q_D(ctkPopupWidget);
  621. d->AutoShow = mode;
  622. QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
  623. }
  624. // -------------------------------------------------------------------------
  625. bool ctkPopupWidget::autoHide()const
  626. {
  627. Q_D(const ctkPopupWidget);
  628. return d->AutoHide;
  629. }
  630. // -------------------------------------------------------------------------
  631. void ctkPopupWidget::setAutoHide(bool mode)
  632. {
  633. Q_D(ctkPopupWidget);
  634. d->AutoHide = mode;
  635. QTimer::singleShot(LEAVE_CLOSING_DELAY, this, SLOT(updatePopup()));
  636. }
  637. // -------------------------------------------------------------------------
  638. ctkPopupWidget::AnimationEffect ctkPopupWidget::animationEffect()const
  639. {
  640. Q_D(const ctkPopupWidget);
  641. return d->Effect;
  642. }
  643. // -------------------------------------------------------------------------
  644. void ctkPopupWidget::setAnimationEffect(ctkPopupWidget::AnimationEffect effect)
  645. {
  646. Q_D(ctkPopupWidget);
  647. /// TODO: handle the case where there is an animation running
  648. d->Effect = effect;
  649. }
  650. // -------------------------------------------------------------------------
  651. QEasingCurve::Type ctkPopupWidget::easingCurve()const
  652. {
  653. Q_D(const ctkPopupWidget);
  654. return d->AlphaAnimation->easingCurve().type();
  655. }
  656. // -------------------------------------------------------------------------
  657. void ctkPopupWidget::setEasingCurve(QEasingCurve::Type easingCurve)
  658. {
  659. Q_D(ctkPopupWidget);
  660. d->AlphaAnimation->setEasingCurve(easingCurve);
  661. d->ScrollAnimation->setEasingCurve(easingCurve);
  662. }
  663. // -------------------------------------------------------------------------
  664. Qt::Alignment ctkPopupWidget::alignment()const
  665. {
  666. Q_D(const ctkPopupWidget);
  667. return d->Alignment;
  668. }
  669. // -------------------------------------------------------------------------
  670. void ctkPopupWidget::setAlignment(Qt::Alignment alignment)
  671. {
  672. Q_D(ctkPopupWidget);
  673. d->Alignment = alignment;
  674. }
  675. // -------------------------------------------------------------------------
  676. Qt::Orientations ctkPopupWidget::orientation()const
  677. {
  678. Q_D(const ctkPopupWidget);
  679. return d->Orientations;
  680. }
  681. // -------------------------------------------------------------------------
  682. void ctkPopupWidget::setOrientation(Qt::Orientations orientations)
  683. {
  684. Q_D(ctkPopupWidget);
  685. d->Orientations = orientations;
  686. }
  687. // -------------------------------------------------------------------------
  688. ctkPopupWidget::VerticalDirection ctkPopupWidget::verticalDirection()const
  689. {
  690. Q_D(const ctkPopupWidget);
  691. return d->VerticalDirection;
  692. }
  693. // -------------------------------------------------------------------------
  694. void ctkPopupWidget::setVerticalDirection(ctkPopupWidget::VerticalDirection verticalDirection)
  695. {
  696. Q_D(ctkPopupWidget);
  697. d->VerticalDirection = verticalDirection;
  698. }
  699. // -------------------------------------------------------------------------
  700. Qt::LayoutDirection ctkPopupWidget::horizontalDirection()const
  701. {
  702. Q_D(const ctkPopupWidget);
  703. return d->HorizontalDirection;
  704. }
  705. // -------------------------------------------------------------------------
  706. void ctkPopupWidget::setHorizontalDirection(Qt::LayoutDirection horizontalDirection)
  707. {
  708. Q_D(ctkPopupWidget);
  709. d->HorizontalDirection = horizontalDirection;
  710. }
  711. // -------------------------------------------------------------------------
  712. void ctkPopupWidget::onEffectFinished()
  713. {
  714. Q_D(ctkPopupWidget);
  715. if (d->ForcedTranslucent)
  716. {
  717. d->ForcedTranslucent = false;
  718. this->setAttribute(Qt::WA_TranslucentBackground, false);
  719. }
  720. if (qobject_cast<QAbstractAnimation*>(this->sender())->direction() == QAbstractAnimation::Backward)
  721. {
  722. d->hideAll();
  723. emit this->popupOpened(false);
  724. /// restore the AutoShow if needed.
  725. if (!this->property("AutoShowOnClose").isNull())
  726. {
  727. d->AutoShow = this->property("AutoShowOnClose").toBool();
  728. this->setProperty("AutoShowOnClose", QVariant());
  729. }
  730. }
  731. else
  732. {
  733. this->show();
  734. #ifdef Q_WS_X11
  735. // If the OS applies effects on window appearance, it
  736. // can take time for the popup to be displayed, we don't want to
  737. // hide the pixmap too early otherwise to would makes it "flicker"
  738. // It has the disadvantage of 'opaquing' if the popup is
  739. // semi transparent because the pixmap and the popup opacities
  740. // get summed up until the pixmap is hidden.
  741. // Alternatively, you could remove effects on windows with Compiz.
  742. QTimer::singleShot(100, d->PopupPixmapWidget, SLOT(hide()));
  743. #else
  744. d->PopupPixmapWidget->hide();
  745. #endif
  746. emit this->popupOpened(true);
  747. }
  748. }
  749. // -------------------------------------------------------------------------
  750. void ctkPopupWidget::paintEvent(QPaintEvent* event)
  751. {
  752. Q_D(ctkPopupWidget);
  753. Q_UNUSED(event);
  754. QPainter painter(this);
  755. QBrush brush = this->palette().window();
  756. if (brush.style() == Qt::LinearGradientPattern ||
  757. brush.style() == Qt::ConicalGradientPattern ||
  758. brush.style() == Qt::RadialGradientPattern)
  759. {
  760. QGradient* newGradient = duplicateGradient(brush.gradient());
  761. QGradientStops stops;
  762. foreach(QGradientStop stop, newGradient->stops())
  763. {
  764. stop.second.setAlpha(stop.second.alpha() * d->EffectAlpha);
  765. stops.push_back(stop);
  766. }
  767. newGradient->setStops(stops);
  768. brush = QBrush(*newGradient);
  769. delete newGradient;
  770. }
  771. else
  772. {
  773. QColor color = brush.color();
  774. color.setAlpha(color.alpha() * d->EffectAlpha);
  775. brush.setColor(color);
  776. }
  777. //QColor semiTransparentColor = this->palette().window().color();
  778. //semiTransparentColor.setAlpha(d->CurrentAlpha);
  779. painter.fillRect(this->rect(), brush);
  780. // Let the QFrame draw itself if needed
  781. this->Superclass::paintEvent(event);
  782. }
  783. // --------------------------------------------------------------------------
  784. void ctkPopupWidget::leaveEvent(QEvent* event)
  785. {
  786. QTimer::singleShot(LEAVE_CLOSING_DELAY, this, SLOT(updatePopup()));
  787. this->Superclass::leaveEvent(event);
  788. }
  789. // --------------------------------------------------------------------------
  790. void ctkPopupWidget::enterEvent(QEvent* event)
  791. {
  792. QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
  793. this->Superclass::enterEvent(event);
  794. }
  795. // --------------------------------------------------------------------------
  796. bool ctkPopupWidget::eventFilter(QObject* obj, QEvent* event)
  797. {
  798. Q_D(ctkPopupWidget);
  799. // Here we listen to PopupPixmapWidget, BaseWidget and ctkPopupWidget
  800. // children popups that were under the mouse
  801. switch(event->type())
  802. {
  803. case QEvent::Move:
  804. {
  805. if (obj != d->BaseWidget)
  806. {
  807. break;
  808. }
  809. QMoveEvent* moveEvent = dynamic_cast<QMoveEvent*>(event);
  810. QRect newBaseGeometry = d->baseGeometry();
  811. newBaseGeometry.moveTopLeft(d->mapToGlobal(moveEvent->pos()));
  812. QRect desiredGeometry = d->desiredOpenGeometry(newBaseGeometry);
  813. this->move(desiredGeometry.topLeft());
  814. //this->move(this->pos() + moveEvent->pos() - moveEvent->oldPos());
  815. this->update();
  816. break;
  817. }
  818. case QEvent::Hide:
  819. case QEvent::Close:
  820. // if the mouse was in a base widget child popup, then when we leave
  821. // the popup we want to check if it needs to be closed.
  822. if (obj != d->BaseWidget)
  823. {
  824. if (obj != d->PopupPixmapWidget &&
  825. qobject_cast<QWidget*>(obj)->windowType() == Qt::Popup)
  826. {
  827. obj->removeEventFilter(this);
  828. QTimer::singleShot(LEAVE_CLOSING_DELAY, this, SLOT(updatePopup()));
  829. }
  830. break;
  831. }
  832. d->temporarilyHiddenOn();
  833. break;
  834. case QEvent::Show:
  835. if (obj != d->BaseWidget)
  836. {
  837. break;
  838. }
  839. this->setGeometry(d->desiredOpenGeometry());
  840. d->temporarilyHiddenOff();
  841. break;
  842. case QEvent::Resize:
  843. if (obj != d->BaseWidget ||
  844. !(d->Alignment & Qt::AlignJustify ||
  845. (d->Alignment & Qt::AlignTop && d->Alignment & Qt::AlignBottom)) ||
  846. !(d->isOpening() || this->isVisible()))
  847. {
  848. break;
  849. }
  850. // TODO: bug when the effect is WindowOpacityFadeEffect
  851. this->setGeometry(d->desiredOpenGeometry());
  852. break;
  853. case QEvent::Enter:
  854. if ( d->currentAnimation()->state() == QAbstractAnimation::Stopped )
  855. {
  856. // Maybe the user moved the mouse on the widget by mistake, don't open
  857. // the popup instantly...
  858. QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
  859. }
  860. else
  861. {
  862. // ... except if the popup is closing, we want to reopen it as sooon as
  863. // possible.
  864. this->updatePopup();
  865. }
  866. break;
  867. case QEvent::Leave:
  868. // Don't listen to base widget children that are popups as what
  869. // matters here is their close event instead
  870. if (obj != d->BaseWidget &&
  871. obj != d->PopupPixmapWidget &&
  872. qobject_cast<QWidget*>(obj)->windowType() == Qt::Popup)
  873. {
  874. break;
  875. }
  876. // The mouse might have left the area that keeps the popup open
  877. QTimer::singleShot(LEAVE_CLOSING_DELAY, this, SLOT(updatePopup()));
  878. if (obj != d->BaseWidget &&
  879. obj != d->PopupPixmapWidget)
  880. {
  881. obj->removeEventFilter(this);
  882. }
  883. break;
  884. default:
  885. break;
  886. }
  887. return this->QObject::eventFilter(obj, event);
  888. }
  889. // --------------------------------------------------------------------------
  890. void ctkPopupWidget::updatePopup()
  891. {
  892. Q_D(ctkPopupWidget);
  893. // Querying mouseOver can be slow, don't do it if not needed.
  894. bool mouseOver = (d->AutoShow || d->AutoHide) && d->mouseOver();
  895. if (d->AutoShow && mouseOver)
  896. {
  897. this->showPopup();
  898. }
  899. else if (d->AutoHide && !mouseOver)
  900. {
  901. this->hidePopup();
  902. }
  903. }
  904. // --------------------------------------------------------------------------
  905. void ctkPopupWidget::showPopup()
  906. {
  907. Q_D(ctkPopupWidget);
  908. if ((this->isVisible() &&
  909. d->currentAnimation()->state() == QAbstractAnimation::Stopped) ||
  910. (d->BaseWidget && !d->BaseWidget->isVisible()))
  911. {
  912. return;
  913. }
  914. // If the layout has never been activated, the widget doesn't know its
  915. // minSize/maxSize and we then wouldn't know what's its true geometry.
  916. if (this->layout() && !this->testAttribute(Qt::WA_WState_Created))
  917. {
  918. this->layout()->activate();
  919. }
  920. this->setGeometry(d->desiredOpenGeometry());
  921. /// Maybe the popup doesn't allow the desiredOpenGeometry if the widget
  922. /// minimum size is larger than the desired size.
  923. QRect openGeometry = this->geometry();
  924. QRect closedGeometry = d->closedGeometry();
  925. d->currentAnimation()->setDirection(QAbstractAnimation::Forward);
  926. switch(d->Effect)
  927. {
  928. case WindowOpacityFadeEffect:
  929. if (!this->testAttribute(Qt::WA_TranslucentBackground))
  930. {
  931. d->ForcedTranslucent = true;
  932. this->setAttribute(Qt::WA_TranslucentBackground, true);
  933. }
  934. this->show();
  935. break;
  936. case ScrollEffect:
  937. {
  938. d->PopupPixmapWidget->setGeometry(closedGeometry);
  939. d->ScrollAnimation->setStartValue(closedGeometry);
  940. d->ScrollAnimation->setEndValue(openGeometry);
  941. d->setupPopupPixmapWidget();
  942. d->PopupPixmapWidget->show();
  943. break;
  944. }
  945. default:
  946. break;
  947. }
  948. switch(d->currentAnimation()->state())
  949. {
  950. case QAbstractAnimation::Stopped:
  951. d->currentAnimation()->start();
  952. break;
  953. case QAbstractAnimation::Paused:
  954. d->currentAnimation()->resume();
  955. break;
  956. default:
  957. case QAbstractAnimation::Running:
  958. break;
  959. }
  960. }
  961. // --------------------------------------------------------------------------
  962. void ctkPopupWidget::hidePopup()
  963. {
  964. Q_D(ctkPopupWidget);
  965. // just in case it was set.
  966. this->setProperty("forcedClosed", 0);
  967. if (!this->isVisible() &&
  968. d->currentAnimation()->state() == QAbstractAnimation::Stopped)
  969. {
  970. return;
  971. }
  972. d->currentAnimation()->setDirection(QAbstractAnimation::Backward);
  973. QRect openGeometry = this->geometry();
  974. QRect closedGeometry = d->closedGeometry();
  975. switch(d->Effect)
  976. {
  977. case WindowOpacityFadeEffect:
  978. if (!this->testAttribute(Qt::WA_TranslucentBackground))
  979. {
  980. d->ForcedTranslucent = true;
  981. this->setAttribute(Qt::WA_TranslucentBackground, true);
  982. }
  983. break;
  984. case ScrollEffect:
  985. {
  986. d->ScrollAnimation->setStartValue(closedGeometry);
  987. d->ScrollAnimation->setEndValue(openGeometry);
  988. d->setupPopupPixmapWidget();
  989. d->PopupPixmapWidget->setGeometry(this->geometry());
  990. d->PopupPixmapWidget->show();
  991. if (this->isActiveWindow())
  992. {
  993. qApp->setActiveWindow(d->BaseWidget ? d->BaseWidget->window() : 0);
  994. }
  995. this->hide();
  996. break;
  997. }
  998. default:
  999. break;
  1000. }
  1001. switch(d->currentAnimation()->state())
  1002. {
  1003. case QAbstractAnimation::Stopped:
  1004. d->currentAnimation()->start();
  1005. break;
  1006. case QAbstractAnimation::Paused:
  1007. d->currentAnimation()->resume();
  1008. break;
  1009. default:
  1010. case QAbstractAnimation::Running:
  1011. break;
  1012. }
  1013. }
  1014. // --------------------------------------------------------------------------
  1015. void ctkPopupWidget::pinPopup(bool pin)
  1016. {
  1017. Q_D(ctkPopupWidget);
  1018. this->setAutoHide(!pin);
  1019. if (pin)
  1020. {
  1021. this->showPopup();
  1022. }
  1023. else
  1024. {
  1025. // When closing, we don't want to inadvertently re-open the menu.
  1026. this->setProperty("AutoShowOnClose", this->autoShow());
  1027. d->AutoShow = false;
  1028. this->hidePopup();
  1029. }
  1030. }
  1031. // --------------------------------------------------------------------------
  1032. double ctkPopupWidget::effectAlpha()const
  1033. {
  1034. Q_D(const ctkPopupWidget);
  1035. return d->EffectAlpha;
  1036. }
  1037. // --------------------------------------------------------------------------
  1038. void ctkPopupWidget::setEffectAlpha(double alpha)
  1039. {
  1040. Q_D(ctkPopupWidget);
  1041. d->EffectAlpha = alpha;
  1042. this->repaint();
  1043. }
  1044. // --------------------------------------------------------------------------
  1045. QRect ctkPopupWidget::effectGeometry()const
  1046. {
  1047. Q_D(const ctkPopupWidget);
  1048. return d->PopupPixmapWidget->geometry();
  1049. }
  1050. // --------------------------------------------------------------------------
  1051. void ctkPopupWidget::setEffectGeometry(QRect newGeometry)
  1052. {
  1053. Q_D(ctkPopupWidget);
  1054. d->PopupPixmapWidget->setGeometry(newGeometry);
  1055. d->PopupPixmapWidget->repaint();
  1056. }