]> cloud.milkyroute.net Git - dolphin.git/blob - src/infosidebarpage.cpp
commited initial version of Dolphin
[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(KIcon::SizeEnormous);
76 m_preview->setFixedHeight(KIcon::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 KIcon::NoGroup,
165 KIcon::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 KIcon::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(KIcon::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(), true)) {
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 KIcon::NoGroup,
258 KIcon::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::WordBreak);
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 KFileItemListIterator mimeIt(*itemList);
461 KFileItem* item = 0;
462 insert = true;
463 while (insert && ((item = mimeIt.current()) != 0)) {
464 insert = !item->isDir();
465 ++mimeIt;
466 }
467 }
468
469 if (!insert) {
470 // Check whether the MIME types of all selected files match
471 // to the mimetype of the service action. As soon as one MIME
472 // type does not match, no service menu is shown at all.
473 KFileItemListIterator mimeIt(*itemList);
474 KFileItem* item = 0;
475 insert = true;
476 while (insert && ((item = mimeIt.current()) != 0)) {
477 const QString mimeType((*mimeIt)->mimetype());
478 const QString mimeGroup(mimeType.left(mimeType.find('/')));
479
480 insert = (*it == mimeType) ||
481 ((*it).right(1) == "*") &&
482 ((*it).left((*it).find('/')) == mimeGroup);
483 ++mimeIt;
484 }
485 }
486
487 if (insert) {
488 const QString submenuName = cfg.readEntry( "X-KDE-Submenu" );
489 Q3PopupMenu* popup = 0;
490 if (!submenuName.isEmpty()) {
491 // create a sub menu containing all actions
492 popup = new Q3PopupMenu();
493 connect(popup, SIGNAL(activated(int)),
494 this, SLOT(startService(int)));
495
496 QPushButton* button = new QPushButton(submenuName, m_actionBox);
497 button->setFlat(true);
498 button->setPopup(popup);
499 button->show();
500 m_actionWidgets.append(button);
501 }
502
503 Q3ValueList<KDEDesktopMimeType::Service> userServices =
504 KDEDesktopMimeType::userDefinedServices(*dirIt + *entryIt, true);
505
506 // iterate through all actions and add them to a widget
507 Q3ValueList<KDEDesktopMimeType::Service>::Iterator serviceIt;
508 for (serviceIt = userServices.begin(); serviceIt != userServices.end(); ++serviceIt) {
509 KDEDesktopMimeType::Service service = (*serviceIt);
510 if (popup == 0) {
511 ServiceButton* button = new ServiceButton(SmallIcon(service.m_strIcon),
512 service.m_strName,
513 m_actionBox,
514 actionsIndex);
515 connect(button, SIGNAL(requestServiceStart(int)),
516 this, SLOT(startService(int)));
517 m_actionWidgets.append(button);
518 button->show();
519 }
520 else {
521 popup->insertItem(SmallIcon(service.m_strIcon), service.m_strName, actionsIndex);
522 }
523
524 m_actionsVector.append(service);
525 ++actionsIndex;
526 }
527 }
528 }
529 }
530 }
531 }
532 }
533
534 ServiceButton::ServiceButton(const QIcon& icon,
535 const QString& text,
536 QWidget* parent,
537 int index) :
538 QPushButton(icon, text, parent),
539 m_hover(false),
540 m_index(index)
541 {
542 setEraseColor(colorGroup().background());
543 setFocusPolicy(Qt::NoFocus);
544 connect(this, SIGNAL(released()),
545 this, SLOT(slotReleased()));
546 }
547
548 ServiceButton::~ServiceButton()
549 {
550 }
551
552 void ServiceButton::drawButton(QPainter* painter)
553 {
554 const int buttonWidth = width();
555 const int buttonHeight = height();
556
557 QColor backgroundColor;
558 QColor foregroundColor;
559 if (m_hover) {
560 backgroundColor = KGlobalSettings::highlightColor();
561 foregroundColor = KGlobalSettings::highlightedTextColor();
562 }
563 else {
564 backgroundColor = colorGroup().background();
565 foregroundColor = KGlobalSettings::buttonTextColor();
566 }
567
568 // draw button background
569 painter->setPen(NoPen);
570 painter->setBrush(backgroundColor);
571 painter->drawRect(0, 0, buttonWidth, buttonHeight);
572
573 const int spacing = KDialog::spacingHint();
574
575 // draw icon
576 int x = spacing;
577 const int y = (buttonHeight - KIcon::SizeSmall) / 2;
578 const QIcon* set = iconSet();
579 if (set != 0) {
580 painter->drawPixmap(x, y, set->pixmap(QIcon::Small, QIcon::Normal));
581 }
582 x += KIcon::SizeSmall + spacing;
583
584 // draw text
585 painter->setPen(foregroundColor);
586
587 const int textWidth = buttonWidth - x;
588 QFontMetrics fontMetrics(font());
589 const bool clipped = fontMetrics.width(text()) >= textWidth;
590 //const int align = clipped ? Qt::AlignVCenter : Qt::AlignCenter;
591 painter->drawText(QRect(x, 0, textWidth, buttonHeight), Qt::AlignVCenter, text());
592
593 if (clipped) {
594 // Blend the right area of the text with the background, as the
595 // text is clipped.
596 // TODO #1: use alpha blending in Qt4 instead of drawing the text that often
597 // TODO #2: same code as in URLNavigatorButton::drawButton() -> provide helper class?
598 const int blendSteps = 16;
599
600 QColor blendColor(backgroundColor);
601 const int redInc = (foregroundColor.red() - backgroundColor.red()) / blendSteps;
602 const int greenInc = (foregroundColor.green() - backgroundColor.green()) / blendSteps;
603 const int blueInc = (foregroundColor.blue() - backgroundColor.blue()) / blendSteps;
604 for (int i = 0; i < blendSteps; ++i) {
605 painter->setClipRect(QRect(x + textWidth - i, 0, 1, buttonHeight));
606 painter->setPen(blendColor);
607 painter->drawText(QRect(x, 0, textWidth, buttonHeight), Qt::AlignVCenter, text());
608
609 blendColor.setRgb(blendColor.red() + redInc,
610 blendColor.green() + greenInc,
611 blendColor.blue() + blueInc);
612 }
613 }
614 }
615
616 void ServiceButton::enterEvent(QEvent* event)
617 {
618 QPushButton::enterEvent(event);
619 m_hover = true;
620 update();
621 }
622
623 void ServiceButton::leaveEvent(QEvent* event)
624 {
625 QPushButton::leaveEvent(event);
626 m_hover = false;
627 update();
628 }
629
630 void ServiceButton::slotReleased()
631 {
632 emit requestServiceStart(m_index);
633 }
634