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