]> cloud.milkyroute.net Git - dolphin.git/blob - src/infosidebarpage.cpp
reactivated info sidebar again and temporary removed the code which resets the URL...
[dolphin.git] / src / infosidebarpage.cpp
1 /***************************************************************************
2 * Copyright (C) 2006 by Peter Penz <peter.penz@gmx.at> *
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 <config-kmetadata.h>
21
22 #include "infosidebarpage.h"
23
24 #include <QLayout>
25 #include <QPixmap>
26 #include <QLabel>
27 #include <QTimer>
28 #include <QPushButton>
29 #include <QMenu>
30 #include <QPainter>
31 #include <QFontMetrics>
32 #include <QEvent>
33 #include <QInputDialog>
34 #include <QDir>
35
36 #include <kfileplacesmodel.h>
37 #include <klocale.h>
38 #include <kstandarddirs.h>
39 #include <kio/previewjob.h>
40 #include <kfileitem.h>
41 #include <kdialog.h>
42 #include <kglobalsettings.h>
43 #include <kfilemetainfo.h>
44 #include <kvbox.h>
45 #include <kseparator.h>
46 #include <kiconloader.h>
47
48 #include "pixmapviewer.h"
49 #include "dolphinsettings.h"
50 #include "metadatawidget.h"
51
52 InfoSidebarPage::InfoSidebarPage(QWidget* parent) :
53 SidebarPage(parent),
54 m_multipleSelection(false), //TODO:check if I'm needed
55 m_pendingPreview(false),
56 m_timer(0),
57 m_currentSelection(KFileItemList()),
58 m_preview(0),
59 m_name(0),
60 m_infos(0)
61 {
62 const int spacing = KDialog::spacingHint();
63
64 m_timer = new QTimer(this);
65 connect(m_timer, SIGNAL(timeout()),
66 this, SLOT(slotTimeout()));
67
68 QVBoxLayout* layout = new QVBoxLayout;
69 layout->setSpacing(spacing);
70
71 // preview
72 m_preview = new PixmapViewer(this);
73 m_preview->setMinimumWidth(K3Icon::SizeEnormous);
74 m_preview->setFixedHeight(K3Icon::SizeEnormous);
75
76 // name
77 m_name = new QLabel(this);
78 m_name->setTextFormat(Qt::RichText);
79 m_name->setAlignment(m_name->alignment() | Qt::AlignHCenter);
80 QFontMetrics fontMetrics(m_name->font());
81 m_name->setMinimumHeight(fontMetrics.height() * 3);
82 m_name->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
83
84 KSeparator* sep1 = new KSeparator(this);
85
86 // general information
87 m_infos = new QLabel(this);
88 m_infos->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
89 m_infos->setTextFormat(Qt::RichText);
90
91 KSeparator* sep2 = new KSeparator(this);
92
93 if (MetaDataWidget::metaDataAvailable())
94 m_metadataWidget = new MetaDataWidget(this);
95 else
96 m_metadataWidget = 0;
97
98 // actions
99 m_actionBox = new KVBox(this);
100 m_actionBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
101
102 layout->addItem(new QSpacerItem(spacing, spacing, QSizePolicy::Preferred, QSizePolicy::Fixed));
103 layout->addWidget(m_preview);
104 layout->addWidget(m_name);
105 layout->addWidget(sep1);
106 layout->addWidget(m_infos);
107 layout->addWidget(sep2);
108 if (m_metadataWidget) {
109 layout->addWidget(m_metadataWidget);
110 layout->addWidget(new KSeparator(this));
111 }
112 layout->addWidget(m_actionBox);
113 // ensure that widgets in the information side bar are aligned towards the top
114 layout->addStretch(1);
115 setLayout(layout);
116 }
117
118 InfoSidebarPage::~InfoSidebarPage()
119 {}
120
121 void InfoSidebarPage::setUrl(const KUrl& url)
122 {
123 if (!m_shownUrl.equals(url, KUrl::CompareWithoutTrailingSlash)) {
124 cancelRequest();
125 m_shownUrl = url;
126 showItemInfo();
127 }
128 }
129
130 void InfoSidebarPage::setSelection(const KFileItemList& selection)
131 {
132 // TODO: deactivated the following code, as it has side effects. To
133 // reproduce start Dolphin and open a folder -> the URL navigator gets
134 // reset. First guess: it seems that a setUrl signal is emitted
135 // by the following code
136
137 Q_UNUSED(selection);
138 /*cancelRequest();
139 m_currentSelection = selection;
140 m_multipleSelection = (m_currentSelection.size() > 1);
141 showItemInfo();*/
142 }
143
144 void InfoSidebarPage::requestDelayedItemInfo(const KUrl& url)
145 {
146 cancelRequest();
147
148 if (!url.isEmpty() && !m_multipleSelection) {
149 m_urlCandidate = url;
150 m_timer->setSingleShot(true);
151 m_timer->start(300);
152 }
153 }
154
155 void InfoSidebarPage::showItemInfo()
156 {
157 cancelRequest();
158
159 KFileItemList selectedItems = m_currentSelection;
160 KUrl file;
161 if (selectedItems.count() == 0) {
162 file = m_shownUrl;
163 } else {
164 file = selectedItems[0]->url();
165 }
166 if (m_multipleSelection) {
167 KIconLoader iconLoader;
168 QPixmap icon = iconLoader.loadIcon("exec",
169 K3Icon::NoGroup,
170 K3Icon::SizeEnormous);
171 m_preview->setPixmap(icon);
172 m_name->setText(i18n("%1 items selected", selectedItems.count()));
173 } else if (!applyBookmark(file)) {
174 // try to get a preview pixmap from the item...
175 KUrl::List list;
176 list.append(file);
177
178 m_pendingPreview = true;
179 m_preview->setPixmap(QPixmap());
180
181 KIO::PreviewJob* job = KIO::filePreview(list,
182 m_preview->width(),
183 K3Icon::SizeEnormous,
184 0,
185 0,
186 true,
187 false);
188 job->setIgnoreMaximumSize(true);
189
190 connect(job, SIGNAL(gotPreview(const KFileItem*, const QPixmap&)),
191 this, SLOT(gotPreview(const KFileItem*, const QPixmap&)));
192 connect(job, SIGNAL(failed(const KFileItem*)),
193 this, SLOT(slotPreviewFailed(const KFileItem*)));
194
195 QString text("<b>");
196 text.append(file.fileName());
197 text.append("</b>");
198 m_name->setText(text);
199 }
200
201 createMetaInfo();
202 insertActions();
203 }
204
205 void InfoSidebarPage::slotTimeout()
206 {
207 m_shownUrl = m_urlCandidate;
208 showItemInfo();
209 }
210
211 void InfoSidebarPage::slotPreviewFailed(const KFileItem* item)
212 {
213 m_pendingPreview = false;
214 if (!applyBookmark(item->url())) {
215 m_preview->setPixmap(item->pixmap(K3Icon::SizeEnormous));
216 }
217 }
218
219 void InfoSidebarPage::gotPreview(const KFileItem* item,
220 const QPixmap& pixmap)
221 {
222 Q_UNUSED(item);
223 if (m_pendingPreview) {
224 m_preview->setPixmap(pixmap);
225 m_pendingPreview = false;
226 }
227 }
228
229 void InfoSidebarPage::startService(int index)
230 {
231 if (m_currentSelection.count() > 0) {
232 // TODO: Use "at()" as soon as executeService is fixed to take a const param (BIC)
233 KDesktopFileActions::executeService(m_currentSelection.urlList(), m_actionsVector[index]);
234 } else {
235 // TODO: likewise
236 KDesktopFileActions::executeService(m_shownUrl, m_actionsVector[index]);
237 }
238 }
239
240 bool InfoSidebarPage::applyBookmark(const KUrl& url)
241 {
242 KFilePlacesModel *placesModel = DolphinSettings::instance().placesModel();
243 int count = placesModel->rowCount();
244
245 for (int i = 0; i < count; ++i) {
246 QModelIndex index = placesModel->index(i, 0);
247
248 if (url.equals(placesModel->url(index), KUrl::CompareWithoutTrailingSlash)) {
249 QString text("<b>");
250 text.append(placesModel->text(index));
251 text.append("</b>");
252 m_name->setText(text);
253
254 m_preview->setPixmap(placesModel->icon(index).pixmap(128, 128));
255 return true;
256 }
257 }
258
259 return false;
260 }
261
262 void InfoSidebarPage::cancelRequest()
263 {
264 m_timer->stop();
265 m_pendingPreview = false;
266 }
267
268 void InfoSidebarPage::createMetaInfo()
269 {
270 beginInfoLines();
271 if (m_currentSelection.size() == 0) {
272 KFileItem fileItem(S_IFDIR, KFileItem::Unknown, m_shownUrl);
273 fileItem.refresh();
274
275 if (fileItem.isDir()) {
276 addInfoLine(i18n("Type:"), i18n("Directory"));
277 }
278 if (MetaDataWidget::metaDataAvailable())
279 m_metadataWidget->setFile(fileItem.url());
280 } else if (m_currentSelection.count() == 1) {
281 KFileItem* fileItem = m_currentSelection.at(0);
282 addInfoLine(i18n("Type:"), fileItem->mimeComment());
283
284 QString sizeText(KIO::convertSize(fileItem->size()));
285 addInfoLine(i18n("Size:"), sizeText);
286 addInfoLine(i18n("Modified:"), fileItem->timeString());
287
288 const KFileMetaInfo& metaInfo = fileItem->metaInfo();
289 if (metaInfo.isValid()) {
290 QStringList keys = metaInfo.supportedKeys();
291 for (QStringList::Iterator it = keys.begin(); it != keys.end(); ++it) {
292 if (showMetaInfo(*it)) {
293 KFileMetaInfoItem metaInfoItem = metaInfo.item(*it);
294 addInfoLine(*it, metaInfoItem.value().toString());
295 }
296 }
297 }
298 if (MetaDataWidget::metaDataAvailable())
299 m_metadataWidget->setFile(fileItem->url());
300 } else {
301 if (MetaDataWidget::metaDataAvailable())
302 m_metadataWidget->setFiles(m_currentSelection.urlList());
303 unsigned long int totSize = 0;
304 foreach(KFileItem* item, m_currentSelection) {
305 totSize += item->size(); //FIXME what to do with directories ? (same with the one-item-selected-code), item->size() does not return the size of the content : not very instinctive for users
306 }
307 addInfoLine(i18n("Total size:"), KIO::convertSize(totSize));
308 }
309 endInfoLines();
310 }
311
312 void InfoSidebarPage::beginInfoLines()
313 {
314 m_infoLines = QString("");
315 }
316
317 void InfoSidebarPage::endInfoLines()
318 {
319 m_infos->setText(m_infoLines);
320 }
321
322 bool InfoSidebarPage::showMetaInfo(const QString& key) const
323 {
324 // sorted list of keys, where it's data should be shown
325 static const char* keys[] = {
326 "Album",
327 "Artist",
328 "Author",
329 "Bitrate",
330 "Date",
331 "Dimensions",
332 "Genre",
333 "Length",
334 "Lines",
335 "Pages",
336 "Title",
337 "Words"
338 };
339
340 // do a binary search for the key...
341 int top = 0;
342 int bottom = sizeof(keys) / sizeof(char*) - 1;
343 while (top < bottom) {
344 const int middle = (top + bottom) / 2;
345 const int result = key.compare(keys[middle]);
346 if (result < 0) {
347 bottom = middle - 1;
348 } else if (result > 0) {
349 top = middle + 1;
350 } else {
351 return true;
352 }
353 }
354
355 return false;
356 }
357
358 void InfoSidebarPage::addInfoLine(const QString& labelText, const QString& infoText)
359 {
360 if (!m_infoLines.isEmpty())
361 m_infoLines += "<br/>";
362 m_infoLines += QString("<b>%1</b> %2").arg(labelText).arg(infoText);
363 }
364
365 void InfoSidebarPage::insertActions()
366 {
367 QListIterator<QPushButton*> deleteIter(m_actionBox->findChildren<QPushButton*>());
368 QWidget* widget = 0;
369 while (deleteIter.hasNext()) {
370 widget = deleteIter.next();
371 widget->close();
372 widget->deleteLater();
373 }
374
375 m_actionsVector.clear();
376
377 int actionsIndex = 0;
378
379 // The algorithm for searching the available actions works on a list
380 // of KFileItems. If no selection is given, a temporary KFileItem
381 // by the given Url 'url' is created and added to the list.
382 KFileItem fileItem(S_IFDIR, KFileItem::Unknown, m_shownUrl);
383 KFileItemList itemList = m_currentSelection;
384 if (itemList.isEmpty()) {
385 fileItem.refresh();
386 itemList.append(&fileItem);
387 }
388
389 // 'itemList' contains now all KFileItems, where an item information should be shown.
390 // TODO: the following algorithm is quite equal to DolphinContextMenu::insertActionItems().
391 // It's open yet whether they should be merged or whether they have to work slightly different.
392 QStringList dirs = KGlobal::dirs()->findDirs("data", "dolphin/servicemenus/");
393 for (QStringList::ConstIterator dirIt = dirs.begin(); dirIt != dirs.end(); ++dirIt) {
394 QDir dir(*dirIt);
395 QStringList entries = dir.entryList(QStringList("*.desktop"), QDir::Files);
396
397 for (QStringList::ConstIterator entryIt = entries.begin(); entryIt != entries.end(); ++entryIt) {
398 KConfigGroup cfg(KSharedConfig::openConfig(*dirIt + *entryIt, KConfig::OnlyLocal), "Desktop Entry");
399 if ((cfg.hasKey("Actions") || cfg.hasKey("X-KDE-GetActionMenu")) && cfg.hasKey("ServiceTypes")) {
400 const QStringList types = cfg.readEntry("ServiceTypes", QStringList(), ',');
401 for (QStringList::ConstIterator it = types.begin(); it != types.end(); ++it) {
402 // check whether the mime type is equal or whether the
403 // mimegroup (e. g. image/*) is supported
404
405 bool insert = false;
406 if ((*it) == "all/allfiles") {
407 // The service type is valid for all files, but not for directories.
408 // Check whether the selected items only consist of files...
409 QListIterator<KFileItem*> mimeIt(itemList);
410 insert = true;
411 while (insert && mimeIt.hasNext()) {
412 KFileItem* item = mimeIt.next();
413 insert = !item->isDir();
414 }
415 }
416
417 if (!insert) {
418 // Check whether the MIME types of all selected files match
419 // to the mimetype of the service action. As soon as one MIME
420 // type does not match, no service menu is shown at all.
421 QListIterator<KFileItem*> mimeIt(itemList);
422 insert = true;
423 while (insert && mimeIt.hasNext()) {
424 KFileItem* item = mimeIt.next();
425 const QString mimeType(item->mimetype());
426 const QString mimeGroup(mimeType.left(mimeType.indexOf('/')));
427
428 insert = (*it == mimeType) ||
429 ((*it).right(1) == "*") &&
430 ((*it).left((*it).indexOf('/')) == mimeGroup);
431 }
432 }
433
434 if (insert) {
435 const QString submenuName = cfg.readEntry("X-KDE-Submenu");
436 QMenu* popup = 0;
437 if (!submenuName.isEmpty()) {
438 // create a sub menu containing all actions
439 popup = new QMenu();
440 connect(popup, SIGNAL(activated(int)),
441 this, SLOT(startService(int)));
442
443 QPushButton* button = new QPushButton(submenuName, m_actionBox);
444 button->setFlat(true);
445 button->setMenu(popup);
446 button->show();
447 }
448
449 QList<KDesktopFileActions::Service> userServices =
450 KDesktopFileActions::userDefinedServices(*dirIt + *entryIt, true);
451
452 // iterate through all actions and add them to a widget
453 QList<KDesktopFileActions::Service>::Iterator serviceIt;
454 for (serviceIt = userServices.begin(); serviceIt != userServices.end(); ++serviceIt) {
455 KDesktopFileActions::Service service = (*serviceIt);
456 if (popup == 0) {
457 ServiceButton* button = new ServiceButton(KIcon(service.m_strIcon),
458 service.m_strName,
459 m_actionBox,
460 actionsIndex);
461 connect(button, SIGNAL(requestServiceStart(int)),
462 this, SLOT(startService(int)));
463 button->show();
464 } else {
465 popup->insertItem(KIcon(service.m_strIcon), service.m_strName, actionsIndex);
466 }
467
468 m_actionsVector.append(service);
469 ++actionsIndex;
470 }
471 }
472 }
473 }
474 }
475 }
476 }
477
478
479
480 ServiceButton::ServiceButton(const QIcon& icon,
481 const QString& text,
482 QWidget* parent,
483 int index) :
484 QPushButton(icon, text, parent),
485 m_hover(false),
486 m_index(index)
487 {
488 setEraseColor(palette().brush(QPalette::Background).color());
489 setFocusPolicy(Qt::NoFocus);
490 connect(this, SIGNAL(released()),
491 this, SLOT(slotReleased()));
492 }
493
494 ServiceButton::~ServiceButton()
495 {}
496
497 void ServiceButton::paintEvent(QPaintEvent* event)
498 {
499 Q_UNUSED(event);
500 QPainter painter(this);
501 const int buttonWidth = width();
502 const int buttonHeight = height();
503
504 QColor backgroundColor;
505 QColor foregroundColor;
506 if (m_hover) {
507 backgroundColor = KGlobalSettings::highlightColor();
508 foregroundColor = KGlobalSettings::highlightedTextColor();
509 } else {
510 backgroundColor = palette().brush(QPalette::Background).color();
511 foregroundColor = KGlobalSettings::buttonTextColor();
512 }
513
514 // draw button background
515 painter.setPen(Qt::NoPen);
516 painter.setBrush(backgroundColor);
517 painter.drawRect(0, 0, buttonWidth, buttonHeight);
518
519 const int spacing = KDialog::spacingHint();
520
521 // draw icon
522 int x = spacing;
523 const int y = (buttonHeight - K3Icon::SizeSmall) / 2;
524 const QIcon &set = icon();
525 if (!set.isNull()) {
526 painter.drawPixmap(x, y, set.pixmap(QIcon::Small, QIcon::Normal));
527 }
528 x += K3Icon::SizeSmall + spacing;
529
530 // draw text
531 painter.setPen(foregroundColor);
532
533 const int textWidth = buttonWidth - x;
534 QFontMetrics fontMetrics(font());
535 const bool clipped = fontMetrics.width(text()) >= textWidth;
536 //const int align = clipped ? Qt::AlignVCenter : Qt::AlignCenter;
537 painter.drawText(QRect(x, 0, textWidth, buttonHeight), Qt::AlignVCenter, text());
538
539 if (clipped) {
540 // Blend the right area of the text with the background, as the
541 // text is clipped.
542 // TODO #1: use alpha blending in Qt4 instead of drawing the text that often
543 // TODO #2: same code as in UrlNavigatorButton::drawButton() -> provide helper class?
544 const int blendSteps = 16;
545
546 QColor blendColor(backgroundColor);
547 const int redInc = (foregroundColor.red() - backgroundColor.red()) / blendSteps;
548 const int greenInc = (foregroundColor.green() - backgroundColor.green()) / blendSteps;
549 const int blueInc = (foregroundColor.blue() - backgroundColor.blue()) / blendSteps;
550 for (int i = 0; i < blendSteps; ++i) {
551 painter.setClipRect(QRect(x + textWidth - i, 0, 1, buttonHeight));
552 painter.setPen(blendColor);
553 painter.drawText(QRect(x, 0, textWidth, buttonHeight), Qt::AlignVCenter, text());
554
555 blendColor.setRgb(blendColor.red() + redInc,
556 blendColor.green() + greenInc,
557 blendColor.blue() + blueInc);
558 }
559 }
560 }
561
562 void ServiceButton::enterEvent(QEvent* event)
563 {
564 QPushButton::enterEvent(event);
565 m_hover = true;
566 update();
567 }
568
569 void ServiceButton::leaveEvent(QEvent* event)
570 {
571 QPushButton::leaveEvent(event);
572 m_hover = false;
573 update();
574 }
575
576 void ServiceButton::slotReleased()
577 {
578 emit requestServiceStart(m_index);
579 }
580
581 #include "infosidebarpage.moc"