ctkPopupWidget.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  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.commontk.org/LICENSE
  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 <QEvent>
  19. #include <QLayout>
  20. #include <QMouseEvent>
  21. #include <QPainter>
  22. #include <QPropertyAnimation>
  23. #include <QStyle>
  24. #include <QTimer>
  25. // CTK includes
  26. #include "ctkPopupWidget.h"
  27. #define LEAVE_CLOSING_DELAY 0
  28. #define ENTER_OPENING_DELAY 0
  29. #define DEFAULT_FADING_DURATION 333 // fast enough
  30. // -------------------------------------------------------------------------
  31. class ctkPopupWidgetPrivate
  32. {
  33. Q_DECLARE_PUBLIC(ctkPopupWidget);
  34. protected:
  35. ctkPopupWidget* const q_ptr;
  36. public:
  37. ctkPopupWidgetPrivate(ctkPopupWidget& object);
  38. void init();
  39. QWidget* BaseWidget;
  40. int Alpha;
  41. int WindowAlpha;
  42. QPropertyAnimation* AlphaAnimation;
  43. bool AutoHide;
  44. };
  45. QGradient* duplicateGradient(const QGradient* gradient)
  46. {
  47. QGradient* newGradient = 0;
  48. switch (gradient->type())
  49. {
  50. case QGradient::LinearGradient:
  51. {
  52. const QLinearGradient* linearGradient = static_cast<const QLinearGradient*>(gradient);
  53. newGradient = new QLinearGradient(linearGradient->start(), linearGradient->finalStop());
  54. break;
  55. }
  56. case QGradient::RadialGradient:
  57. {
  58. const QRadialGradient* radialGradient = static_cast<const QRadialGradient*>(gradient);
  59. newGradient = new QRadialGradient(radialGradient->center(), radialGradient->radius());
  60. break;
  61. }
  62. case QGradient::ConicalGradient:
  63. {
  64. const QConicalGradient* conicalGradient = static_cast<const QConicalGradient*>(gradient);
  65. newGradient = new QConicalGradient(conicalGradient->center(), conicalGradient->angle());
  66. break;
  67. }
  68. default:
  69. break;
  70. }
  71. if (!newGradient)
  72. {
  73. Q_ASSERT(gradient->type() != QGradient::NoGradient);
  74. return newGradient;
  75. }
  76. newGradient->setCoordinateMode(gradient->coordinateMode());
  77. newGradient->setSpread(gradient->spread());
  78. newGradient->setStops(gradient->stops());
  79. return newGradient;
  80. }
  81. // -------------------------------------------------------------------------
  82. ctkPopupWidgetPrivate::ctkPopupWidgetPrivate(ctkPopupWidget& object)
  83. :q_ptr(&object)
  84. {
  85. this->BaseWidget = 0;
  86. this->Alpha = 255;
  87. this->WindowAlpha = 0;
  88. this->AlphaAnimation = 0;
  89. this->AutoHide = true;
  90. }
  91. // -------------------------------------------------------------------------
  92. void ctkPopupWidgetPrivate::init()
  93. {
  94. Q_Q(ctkPopupWidget);
  95. q->setAttribute(Qt::WA_NoSystemBackground);
  96. q->setAttribute(Qt::WA_OpaquePaintEvent, false);
  97. q->setAttribute(Qt::WA_TranslucentBackground);
  98. this->Alpha = q->style()->styleHint(QStyle::SH_ToolTipLabel_Opacity);
  99. this->AlphaAnimation = new QPropertyAnimation(q, "windowAlpha", q);
  100. this->AlphaAnimation->setDuration(DEFAULT_FADING_DURATION);
  101. this->AlphaAnimation->setStartValue(0);
  102. this->AlphaAnimation->setEndValue(this->Alpha);
  103. this->AlphaAnimation->setEasingCurve(QEasingCurve::InOutQuad);
  104. QObject::connect(this->AlphaAnimation, SIGNAL(finished()),
  105. q, SLOT(onEffectFinished()));
  106. }
  107. // -------------------------------------------------------------------------
  108. // Qt::FramelessWindowHint is required on Windows for Translucent background
  109. // Qt::Toolip is preferred to Qt::Popup as it would close itself at the first
  110. // click outside the widget (typically a click in the BaseWidget)
  111. ctkPopupWidget::ctkPopupWidget(QWidget* parentWidget)
  112. : Superclass(parentWidget, Qt::ToolTip | Qt::FramelessWindowHint)
  113. , d_ptr(new ctkPopupWidgetPrivate(*this))
  114. {
  115. Q_D(ctkPopupWidget);
  116. d->init();
  117. }
  118. // -------------------------------------------------------------------------
  119. ctkPopupWidget::~ctkPopupWidget()
  120. {
  121. }
  122. // -------------------------------------------------------------------------
  123. QWidget* ctkPopupWidget::baseWidget()const
  124. {
  125. Q_D(const ctkPopupWidget);
  126. return d->BaseWidget;
  127. }
  128. // -------------------------------------------------------------------------
  129. void ctkPopupWidget::setBaseWidget(QWidget* widget)
  130. {
  131. Q_D(ctkPopupWidget);
  132. if (d->BaseWidget)
  133. {
  134. d->BaseWidget->removeEventFilter(this);
  135. }
  136. d->BaseWidget = widget;
  137. if (d->BaseWidget)
  138. {
  139. d->BaseWidget->installEventFilter(this);
  140. }
  141. QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
  142. }
  143. // -------------------------------------------------------------------------
  144. int ctkPopupWidget::opacity()const
  145. {
  146. Q_D(const ctkPopupWidget);
  147. return d->Alpha;
  148. }
  149. // -------------------------------------------------------------------------
  150. void ctkPopupWidget::setOpacity(int alpha)
  151. {
  152. Q_D(ctkPopupWidget);
  153. d->Alpha = alpha;
  154. if (d->AlphaAnimation->state() == QAbstractAnimation::Stopped)
  155. {
  156. d->WindowAlpha = d->Alpha;
  157. }
  158. this->update();
  159. }
  160. // -------------------------------------------------------------------------
  161. bool ctkPopupWidget::autoHide()const
  162. {
  163. Q_D(const ctkPopupWidget);
  164. return d->AutoHide;
  165. }
  166. // -------------------------------------------------------------------------
  167. void ctkPopupWidget::setAutoHide(bool mode)
  168. {
  169. Q_D(ctkPopupWidget);
  170. d->AutoHide = mode;
  171. QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
  172. }
  173. // -------------------------------------------------------------------------
  174. void ctkPopupWidget::onEffectFinished()
  175. {
  176. Q_D(ctkPopupWidget);
  177. if (qobject_cast<QAbstractAnimation*>(this->sender())->direction() == QAbstractAnimation::Backward)
  178. {
  179. this->hide();
  180. }
  181. else
  182. {
  183. this->show();
  184. }
  185. }
  186. // -------------------------------------------------------------------------
  187. void ctkPopupWidget::paintEvent(QPaintEvent* event)
  188. {
  189. Q_D(ctkPopupWidget);
  190. Q_UNUSED(event);
  191. if (d->AlphaAnimation->state() != QAbstractAnimation::Stopped)
  192. {
  193. QPainter painter(this);
  194. QBrush brush = this->palette().window();
  195. if (brush.style() == Qt::LinearGradientPattern ||
  196. brush.style() == Qt::ConicalGradientPattern ||
  197. brush.style() == Qt::RadialGradientPattern)
  198. {
  199. QGradient* newGradient = duplicateGradient(brush.gradient());
  200. QGradientStops stops;
  201. foreach(QGradientStop stop, newGradient->stops())
  202. {
  203. stop.second.setAlpha(d->WindowAlpha);
  204. stops.push_back(stop);
  205. }
  206. newGradient->setStops(stops);
  207. brush = QBrush(*newGradient);
  208. delete newGradient;
  209. }
  210. else
  211. {
  212. QColor color = brush.color();
  213. color.setAlpha(d->WindowAlpha);
  214. brush.setColor(color);
  215. }
  216. //QColor semiTransparentColor = this->palette().window().color();
  217. //semiTransparentColor.setAlpha(d->CurrentAlpha);
  218. painter.fillRect(this->rect(), brush);
  219. }
  220. // Let the QFrame draw itself if needed
  221. this->Superclass::paintEvent(event);
  222. }
  223. // --------------------------------------------------------------------------
  224. void ctkPopupWidget::leaveEvent(QEvent* event)
  225. {
  226. QTimer::singleShot(LEAVE_CLOSING_DELAY, this, SLOT(updatePopup()));
  227. this->Superclass::leaveEvent(event);
  228. }
  229. // --------------------------------------------------------------------------
  230. void ctkPopupWidget::enterEvent(QEvent* event)
  231. {
  232. QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
  233. this->Superclass::enterEvent(event);
  234. }
  235. // --------------------------------------------------------------------------
  236. bool ctkPopupWidget::eventFilter(QObject* obj, QEvent* event)
  237. {
  238. Q_D(ctkPopupWidget);
  239. if (obj == d->BaseWidget &&
  240. event->type() == QEvent::Enter)
  241. {
  242. QTimer::singleShot(ENTER_OPENING_DELAY, this, SLOT(updatePopup()));
  243. }
  244. else if (obj == d->BaseWidget &&
  245. event->type() == QEvent::Leave)
  246. {
  247. QTimer::singleShot(LEAVE_CLOSING_DELAY, this, SLOT(updatePopup()));
  248. }
  249. return this->QObject::eventFilter(obj, event);
  250. }
  251. // --------------------------------------------------------------------------
  252. void ctkPopupWidget::updatePopup()
  253. {
  254. Q_D(ctkPopupWidget);
  255. if (!d->AutoHide)
  256. {
  257. return;
  258. }
  259. if (this->underMouse() || (d->BaseWidget && d->BaseWidget->underMouse()))
  260. {
  261. this->showPopup();
  262. }
  263. else
  264. {
  265. this->hidePopup();
  266. }
  267. }
  268. // --------------------------------------------------------------------------
  269. void ctkPopupWidget::showPopup()
  270. {
  271. Q_D(ctkPopupWidget);
  272. if ((this->isVisible() &&
  273. d->AlphaAnimation->state() == QAbstractAnimation::Stopped) ||
  274. (d->BaseWidget && !d->BaseWidget->isVisible()))
  275. {
  276. return;
  277. }
  278. if (d->BaseWidget)
  279. {
  280. QPoint bottomLeft = QPoint(d->BaseWidget->geometry().x(), d->BaseWidget->geometry().bottom());
  281. QPoint pos = d->BaseWidget->parentWidget() ? d->BaseWidget->parentWidget()->mapToGlobal(bottomLeft) : bottomLeft;
  282. this->move(pos);
  283. /// TODO: need some refinement
  284. if ((this->sizePolicy().horizontalPolicy() & QSizePolicy::GrowFlag &&
  285. this->width() < d->BaseWidget->width()) ||
  286. (this->sizePolicy().horizontalPolicy() & QSizePolicy::ShrinkFlag &&
  287. this->width() > d->BaseWidget->width()))
  288. {
  289. // Fit to BaseWidget size
  290. this->resize(d->BaseWidget->width(), this->sizeHint().height());
  291. }
  292. }
  293. d->AlphaAnimation->setDirection(QAbstractAnimation::Forward);
  294. switch(d->AlphaAnimation->state())
  295. {
  296. case QAbstractAnimation::Stopped:
  297. d->AlphaAnimation->start();
  298. break;
  299. case QAbstractAnimation::Paused:
  300. d->AlphaAnimation->resume();
  301. break;
  302. default:
  303. case QAbstractAnimation::Running:
  304. break;
  305. }
  306. // just in case it wasn't visible, usually it's a no op
  307. this->show();
  308. }
  309. // --------------------------------------------------------------------------
  310. void ctkPopupWidget::hidePopup()
  311. {
  312. Q_D(ctkPopupWidget);
  313. if (!this->isVisible() &&
  314. d->AlphaAnimation->state() == QAbstractAnimation::Stopped)
  315. {
  316. return;
  317. }
  318. d->AlphaAnimation->setDirection(QAbstractAnimation::Backward);
  319. switch(d->AlphaAnimation->state())
  320. {
  321. case QAbstractAnimation::Stopped:
  322. d->AlphaAnimation->start();
  323. break;
  324. case QAbstractAnimation::Paused:
  325. d->AlphaAnimation->resume();
  326. break;
  327. default:
  328. case QAbstractAnimation::Running:
  329. break;
  330. }
  331. }
  332. // --------------------------------------------------------------------------
  333. int ctkPopupWidget::windowAlpha()const
  334. {
  335. Q_D(const ctkPopupWidget);
  336. return d->WindowAlpha;
  337. }
  338. // --------------------------------------------------------------------------
  339. void ctkPopupWidget::setWindowAlpha(int alpha)
  340. {
  341. Q_D(ctkPopupWidget);
  342. d->WindowAlpha = alpha;
  343. this->repaint();
  344. }