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