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