]> cloud.milkyroute.net Git - dolphin.git/blob - src/tooltips/tooltipmanager.cpp
Based on the recent size hint fixes in KFileMetaDataWidget, the handling of the toolt...
[dolphin.git] / src / tooltips / tooltipmanager.cpp
1 /*******************************************************************************
2 * Copyright (C) 2008 by Konstantin Heil <konst.heil@stud.uni-heidelberg.de> *
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; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 *******************************************************************************/
19
20 #include "tooltipmanager.h"
21
22 #include "dolphinmodel.h"
23 #include "dolphinsortfilterproxymodel.h"
24
25 #include "filemetadatatooltip.h"
26 #include <kicon.h>
27 #include <kio/previewjob.h>
28
29 #include <QApplication>
30 #include <QDesktopWidget>
31 #include <QScrollArea>
32 #include <QScrollBar>
33 #include <QTimer>
34
35 ToolTipManager::ToolTipManager(QAbstractItemView* parent,
36 DolphinSortFilterProxyModel* model) :
37 QObject(parent),
38 m_view(parent),
39 m_dolphinModel(0),
40 m_proxyModel(model),
41 m_prepareToolTipTimer(0),
42 m_startPreviewJobTimer(0),
43 m_waitOnPreviewTimer(0),
44 m_showToolTipDelayedTimer(0),
45 m_fileMetaDataToolTip(0),
46 m_item(),
47 m_itemRect(),
48 m_generatingPreview(false),
49 m_hasDefaultIcon(false),
50 m_previewPixmap()
51 {
52 static FileMetaDataToolTip* sharedToolTip = 0;
53 if (sharedToolTip == 0) {
54 sharedToolTip = new FileMetaDataToolTip();
55 // TODO: Using K_GLOBAL_STATIC would be preferable to maintain the
56 // instance, but the cleanup of KFileMetaDataWidget at this stage does
57 // not work.
58 }
59 m_fileMetaDataToolTip = sharedToolTip;
60
61 m_dolphinModel = static_cast<DolphinModel*>(m_proxyModel->sourceModel());
62 connect(parent, SIGNAL(entered(const QModelIndex&)),
63 this, SLOT(requestToolTip(const QModelIndex&)));
64 connect(parent, SIGNAL(viewportEntered()),
65 this, SLOT(hideToolTip()));
66
67 // Initialize timers
68 m_prepareToolTipTimer = new QTimer(this);
69 m_prepareToolTipTimer->setSingleShot(true);
70 m_prepareToolTipTimer->setInterval(500);
71 connect(m_prepareToolTipTimer, SIGNAL(timeout()), this, SLOT(prepareToolTip()));
72
73 m_startPreviewJobTimer = new QTimer(this);
74 m_startPreviewJobTimer->setSingleShot(true);
75 m_startPreviewJobTimer->setInterval(200);
76 connect(m_startPreviewJobTimer, SIGNAL(timeout()), this, SLOT(startPreviewJob()));
77
78 m_waitOnPreviewTimer = new QTimer(this);
79 m_waitOnPreviewTimer->setSingleShot(true);
80 m_waitOnPreviewTimer->setInterval(250);
81 connect(m_waitOnPreviewTimer, SIGNAL(timeout()), this, SLOT(prepareToolTip()));
82
83 m_showToolTipDelayedTimer = new QTimer(this);
84 m_showToolTipDelayedTimer->setSingleShot(true);
85 m_showToolTipDelayedTimer->setInterval(100);
86 connect(m_showToolTipDelayedTimer, SIGNAL(timeout()), this, SLOT(showToolTip()));
87
88 // When the mousewheel is used, the items don't get a hovered indication
89 // (Qt-issue #200665). To assure that the tooltip still gets hidden,
90 // the scrollbars are observed.
91 connect(parent->horizontalScrollBar(), SIGNAL(valueChanged(int)),
92 this, SLOT(hideTip()));
93 connect(parent->verticalScrollBar(), SIGNAL(valueChanged(int)),
94 this, SLOT(hideTip()));
95
96 m_view->viewport()->installEventFilter(this);
97 m_view->installEventFilter(this);
98 }
99
100 ToolTipManager::~ToolTipManager()
101 {
102 }
103
104 void ToolTipManager::hideTip()
105 {
106 hideToolTip();
107 }
108
109 bool ToolTipManager::eventFilter(QObject* watched, QEvent* event)
110 {
111 if (watched == m_view->viewport()) {
112 switch (event->type()) {
113 case QEvent::Leave:
114 case QEvent::MouseButtonPress:
115 hideToolTip();
116 break;
117 default:
118 break;
119 }
120 } else if ((watched == m_view) && (event->type() == QEvent::KeyPress)) {
121 hideToolTip();
122 }
123
124 return QObject::eventFilter(watched, event);
125 }
126
127 void ToolTipManager::requestToolTip(const QModelIndex& index)
128 {
129 hideToolTip();
130
131 // Only request a tooltip for the name column and when no selection or
132 // drag & drop operation is done (indicated by the left mouse button)
133 if ((index.column() == DolphinModel::Name) && !(QApplication::mouseButtons() & Qt::LeftButton)) {
134 m_itemRect = m_view->visualRect(index);
135 const QPoint pos = m_view->viewport()->mapToGlobal(m_itemRect.topLeft());
136 m_itemRect.moveTo(pos);
137
138 const QModelIndex dirIndex = m_proxyModel->mapToSource(index);
139 m_item = m_dolphinModel->itemForIndex(dirIndex);
140
141 // Only start the previewJob when the mouse has been over this item for 200 milliseconds.
142 // This prevents a lot of useless preview jobs when passing rapidly over a lot of items.
143 m_startPreviewJobTimer->start();
144 m_previewPixmap = QPixmap();
145 m_hasDefaultIcon = false;
146
147 m_prepareToolTipTimer->start();
148 }
149 }
150
151 void ToolTipManager::hideToolTip()
152 {
153 m_prepareToolTipTimer->stop();
154 m_startPreviewJobTimer->stop();
155 m_waitOnPreviewTimer->stop();
156 m_showToolTipDelayedTimer->stop();
157
158 m_fileMetaDataToolTip->hide();
159 }
160
161 void ToolTipManager::prepareToolTip()
162 {
163 if (m_generatingPreview) {
164 m_waitOnPreviewTimer->start();
165 }
166
167 if (!m_previewPixmap.isNull()) {
168 showToolTipDelayed(m_previewPixmap);
169 } else if (!m_hasDefaultIcon) {
170 const QPixmap image(KIcon(m_item.iconName()).pixmap(128, 128));
171 showToolTipDelayed(image);
172 m_hasDefaultIcon = true;
173 }
174 }
175
176 void ToolTipManager::startPreviewJob()
177 {
178 m_generatingPreview = true;
179 KIO::PreviewJob* job = KIO::filePreview(KFileItemList() << m_item, 256, 256);
180
181 connect(job, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)),
182 this, SLOT(setPreviewPix(const KFileItem&, const QPixmap&)));
183 connect(job, SIGNAL(failed(const KFileItem&)),
184 this, SLOT(previewFailed()));
185 }
186
187
188 void ToolTipManager::setPreviewPix(const KFileItem& item,
189 const QPixmap& pixmap)
190 {
191 if ((m_item.url() != item.url()) || pixmap.isNull()) {
192 // An old preview or an invalid preview has been received
193 previewFailed();
194 } else {
195 m_previewPixmap = pixmap;
196 m_generatingPreview = false;
197 }
198 }
199
200 void ToolTipManager::previewFailed()
201 {
202 m_generatingPreview = false;
203 }
204
205 void ToolTipManager::showToolTip()
206 {
207 const QRect desktop = QApplication::desktop()->screenGeometry(m_itemRect.bottomRight());
208 const QSize size = m_fileMetaDataToolTip->sizeHint();
209
210 // m_itemRect defines the area of the item, where the tooltip should be
211 // shown. Per default the tooltip is shown in the bottom right corner.
212 // If the tooltip content exceeds the desktop borders, it must be assured that:
213 // - the content is fully visible
214 // - the content is not drawn inside m_itemRect
215 const bool hasRoomToLeft = (m_itemRect.left() - size.width() >= desktop.left());
216 const bool hasRoomToRight = (m_itemRect.right() + size.width() <= desktop.right());
217 const bool hasRoomAbove = (m_itemRect.top() - size.height() >= desktop.top());
218 const bool hasRoomBelow = (m_itemRect.bottom() + size.height() <= desktop.bottom());
219 if (!hasRoomAbove && !hasRoomBelow && !hasRoomToLeft && !hasRoomToRight) {
220 return;
221 }
222
223 int x = 0;
224 int y = 0;
225 if (hasRoomBelow || hasRoomAbove) {
226 x = QCursor::pos().x() + 16; // TODO: use mouse pointer width instead of the magic value of 16
227 if (x + size.width() >= desktop.right()) {
228 x = desktop.right() - size.width();
229 }
230 if (hasRoomBelow) {
231 y = m_itemRect.bottom();
232 } else {
233 y = m_itemRect.top() - size.height();
234 }
235 } else {
236 Q_ASSERT(hasRoomToLeft || hasRoomToRight);
237 if (hasRoomToRight) {
238 x = m_itemRect.right();
239 } else {
240 x = m_itemRect.left() - size.width();
241 }
242
243 // Put the tooltip at the bottom of the screen. The x-coordinate has already
244 // been adjusted, so that no overlapping with m_itemRect occurs.
245 y = desktop.bottom() - size.height();
246 }
247
248 m_fileMetaDataToolTip->move(qMax(x, 0), qMax(y, 0));
249 m_fileMetaDataToolTip->show();
250 }
251
252 void ToolTipManager::showToolTipDelayed(const QPixmap& pixmap)
253 {
254 if (QApplication::mouseButtons() & Qt::LeftButton) {
255 return;
256 }
257
258 m_fileMetaDataToolTip->setPreview(pixmap);
259 m_fileMetaDataToolTip->setName(m_item.text());
260 m_fileMetaDataToolTip->setItems(KFileItemList() << m_item);
261
262 // Because one tooltip instance is reused, the size hint always depends on the previous
263 // content (QWidgets don't update their layout geometry if they are invisible). To
264 // assure having a consistent size without relayout flickering, the tooltip is opened
265 // on an invisible position first. This gives the layout system some time to asynchronously
266 // update the content.
267 const QRect desktop = QApplication::desktop()->screenGeometry(m_itemRect.bottomRight());
268 m_fileMetaDataToolTip->move(desktop.bottomRight());
269 m_fileMetaDataToolTip->show();
270
271 m_showToolTipDelayedTimer->start(); // Calls ToolTipManager::showToolTip()
272 }
273
274 #include "tooltipmanager.moc"