ctkPopupWidget.cpp 10 KB

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