]> cloud.milkyroute.net Git - dolphin.git/blob - src/tooltips/tooltipmanager.cpp
SVN_SILENT: documentation fixes
[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_timer(0),
42 m_previewTimer(0),
43 m_waitOnPreviewTimer(0),
44 m_fileMetaDataToolTip(0),
45 m_item(),
46 m_itemRect(),
47 m_generatingPreview(false),
48 m_hasDefaultIcon(false),
49 m_previewPixmap()
50 {
51 m_dolphinModel = static_cast<DolphinModel*>(m_proxyModel->sourceModel());
52 connect(parent, SIGNAL(entered(const QModelIndex&)),
53 this, SLOT(requestToolTip(const QModelIndex&)));
54 connect(parent, SIGNAL(viewportEntered()),
55 this, SLOT(hideToolTip()));
56
57 m_timer = new QTimer(this);
58 m_timer->setSingleShot(true);
59 connect(m_timer, SIGNAL(timeout()),
60 this, SLOT(prepareToolTip()));
61
62 m_previewTimer = new QTimer(this);
63 m_previewTimer->setSingleShot(true);
64 connect(m_previewTimer, SIGNAL(timeout()),
65 this, SLOT(startPreviewJob()));
66
67 m_waitOnPreviewTimer = new QTimer(this);
68 m_waitOnPreviewTimer->setSingleShot(true);
69 connect(m_waitOnPreviewTimer, SIGNAL(timeout()),
70 this, SLOT(prepareToolTip()));
71
72 // When the mousewheel is used, the items don't get a hovered indication
73 // (Qt-issue #200665). To assure that the tooltip still gets hidden,
74 // the scrollbars are observed.
75 connect(parent->horizontalScrollBar(), SIGNAL(valueChanged(int)),
76 this, SLOT(hideTip()));
77 connect(parent->verticalScrollBar(), SIGNAL(valueChanged(int)),
78 this, SLOT(hideTip()));
79
80 m_view->viewport()->installEventFilter(this);
81 m_view->installEventFilter(this);
82
83 static FileMetaDataToolTip* sharedToolTip = 0;
84 if (sharedToolTip == 0) {
85 sharedToolTip = new FileMetaDataToolTip();
86 // TODO: Using K_GLOBAL_STATIC would be preferable to maintain the
87 // instance, but the cleanup of KMetaDataWidget at this stage does
88 // not work.
89 }
90 m_fileMetaDataToolTip = sharedToolTip;
91 }
92
93 ToolTipManager::~ToolTipManager()
94 {
95 }
96
97 void ToolTipManager::hideTip()
98 {
99 hideToolTip();
100 }
101
102 bool ToolTipManager::eventFilter(QObject* watched, QEvent* event)
103 {
104 if (watched == m_view->viewport()) {
105 switch (event->type()) {
106 case QEvent::Leave:
107 case QEvent::MouseButtonPress:
108 hideToolTip();
109 break;
110 default:
111 break;
112 }
113 } else if ((watched == m_view) && (event->type() == QEvent::KeyPress)) {
114 hideToolTip();
115 }
116
117 return QObject::eventFilter(watched, event);
118 }
119
120 void ToolTipManager::requestToolTip(const QModelIndex& index)
121 {
122 // Only request a tooltip for the name column and when no selection or
123 // drag & drop operation is done (indicated by the left mouse button)
124 if ((index.column() == DolphinModel::Name) && !(QApplication::mouseButtons() & Qt::LeftButton)) {
125 m_waitOnPreviewTimer->stop();
126 hideToolTip();
127
128 m_itemRect = m_view->visualRect(index);
129 const QPoint pos = m_view->viewport()->mapToGlobal(m_itemRect.topLeft());
130 m_itemRect.moveTo(pos);
131
132 const QModelIndex dirIndex = m_proxyModel->mapToSource(index);
133 m_item = m_dolphinModel->itemForIndex(dirIndex);
134
135 // Only start the previewJob when the mouse has been over this item for 200 milliseconds.
136 // This prevents a lot of useless preview jobs when passing rapidly over a lot of items.
137 m_previewTimer->start(200);
138 m_previewPixmap = QPixmap();
139 m_hasDefaultIcon = false;
140
141 m_timer->start(500);
142 } else {
143 hideToolTip();
144 }
145 }
146
147 void ToolTipManager::hideToolTip()
148 {
149 m_timer->stop();
150 m_previewTimer->stop();
151 m_waitOnPreviewTimer->stop();
152
153 m_fileMetaDataToolTip->hide();
154 m_fileMetaDataToolTip->setItems(KFileItemList());
155 }
156
157 void ToolTipManager::prepareToolTip()
158 {
159 if (m_generatingPreview) {
160 m_waitOnPreviewTimer->start(250);
161 }
162
163 if (!m_previewPixmap.isNull()) {
164 showToolTip(m_previewPixmap);
165 } else if (!m_hasDefaultIcon) {
166 const QPixmap image(KIcon(m_item.iconName()).pixmap(128, 128));
167 showToolTip(image);
168 m_hasDefaultIcon = true;
169 }
170 }
171
172 void ToolTipManager::startPreviewJob()
173 {
174 m_generatingPreview = true;
175 KIO::PreviewJob* job = KIO::filePreview(KFileItemList() << m_item, 256, 256);
176
177 connect(job, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)),
178 this, SLOT(setPreviewPix(const KFileItem&, const QPixmap&)));
179 connect(job, SIGNAL(failed(const KFileItem&)),
180 this, SLOT(previewFailed()));
181 }
182
183
184 void ToolTipManager::setPreviewPix(const KFileItem& item,
185 const QPixmap& pixmap)
186 {
187 if ((m_item.url() != item.url()) || pixmap.isNull()) {
188 // An old preview or an invalid preview has been received
189 previewFailed();
190 } else {
191 m_previewPixmap = pixmap;
192 m_generatingPreview = false;
193 }
194 }
195
196 void ToolTipManager::previewFailed()
197 {
198 m_generatingPreview = false;
199 }
200
201
202 void ToolTipManager::showToolTip(const QPixmap& pixmap)
203 {
204 if (QApplication::mouseButtons() & Qt::LeftButton) {
205 return;
206 }
207
208 m_fileMetaDataToolTip->setPreview(pixmap);
209 m_fileMetaDataToolTip->setName(m_item.text());
210 m_fileMetaDataToolTip->setItems(KFileItemList() << m_item);
211
212 // Calculate the x- and y-position of the tooltip
213 const QSize size = m_fileMetaDataToolTip->sizeHint();
214 const QRect desktop = QApplication::desktop()->screenGeometry(m_itemRect.bottomRight());
215
216 // m_itemRect defines the area of the item, where the tooltip should be
217 // shown. Per default the tooltip is shown in the bottom right corner.
218 // If the tooltip content exceeds the desktop borders, it must be assured that:
219 // - the content is fully visible
220 // - the content is not drawn inside m_itemRect
221 const bool hasRoomToLeft = (m_itemRect.left() - size.width() >= desktop.left());
222 const bool hasRoomToRight = (m_itemRect.right() + size.width() <= desktop.right());
223 const bool hasRoomAbove = (m_itemRect.top() - size.height() >= desktop.top());
224 const bool hasRoomBelow = (m_itemRect.bottom() + size.height() <= desktop.bottom());
225 if (!hasRoomAbove && !hasRoomBelow && !hasRoomToLeft && !hasRoomToRight) {
226 return;
227 }
228
229 // The size hint provided by the tooltip is not necessarily equal to the
230 // real size of the tooltip after showing it. As long as the tooltip is aligned
231 // on the upper-left edge, this is no problem. If the tooltip is aligned at
232 // another edge, the real size must be respected and the position
233 // corrected. Whether a correction must be done, is indicated by the variables
234 // updateWidth and updateHeight:
235 bool updateWidth = false;
236 bool updateHeight = false;
237
238 int x = 0;
239 int y = 0;
240 if (hasRoomBelow || hasRoomAbove) {
241 x = QCursor::pos().x() + 16; // TODO: use mouse pointer width instead of the magic value of 16
242 if (x + size.width() >= desktop.right()) {
243 x = desktop.right() - size.width();
244 }
245 if (hasRoomBelow) {
246 y = m_itemRect.bottom();
247 } else {
248 y = m_itemRect.top() - size.height();
249 updateHeight = true;
250 }
251 } else {
252 Q_ASSERT(hasRoomToLeft || hasRoomToRight);
253 if (hasRoomToRight) {
254 x = m_itemRect.right();
255 } else {
256 x = m_itemRect.left() - size.width();
257 updateWidth = true;
258 }
259
260 // Put the tooltip at the bottom of the screen. The x-coordinate has already
261 // been adjusted, so that no overlapping with m_itemRect occurs.
262 y = desktop.bottom() - size.height();
263 updateHeight = true;
264 }
265
266 if (!updateWidth && !updateHeight) {
267 // Default case: There is enough room below and right of the mouse
268 // pointer and the tooltip can be positioned there.
269 m_fileMetaDataToolTip->move(x, y);
270 m_fileMetaDataToolTip->show();
271 } else {
272 // There is not enough room to show the tooltip at the default position and
273 // it must be moved left or upwards. In this case the size hint of the
274 // tooltip is not sufficient and the real size must be respected.
275 // To prevent a flickering, the tooltip is first opened outside the visible
276 // desktop area and moved afterwards.
277 m_fileMetaDataToolTip->move(desktop.right() + 1, desktop.bottom() + 1);
278 m_fileMetaDataToolTip->show();
279
280 const QSize shownSize = m_fileMetaDataToolTip->size();
281 const int xDiff = updateWidth ? shownSize.width() - size.width() : 0;
282 const int yDiff = updateHeight ? shownSize.height() - size.height() : 0;
283 m_fileMetaDataToolTip->move(x - xDiff, y - yDiff);
284 }
285 }
286
287 #include "tooltipmanager.moc"