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