]> cloud.milkyroute.net Git - dolphin.git/blob - src/infosidebarpage.cpp
use flat buttons for the information sidebar page (TODO: this code has been duplicate...
[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
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 #include <kvbox.h>
49
50 #include "dolphinmainwindow.h"
51 #include "pixmapviewer.h"
52 #include "dolphinsettings.h"
53
54 InfoSidebarPage::InfoSidebarPage(DolphinMainWindow* mainWindow, QWidget* parent) :
55 SidebarPage(mainWindow, parent),
56 m_multipleSelection(false),
57 m_pendingPreview(false),
58 m_timer(0),
59 m_preview(0),
60 m_name(0),
61 m_currInfoLineIdx(0),
62 m_infoGrid(0),
63 m_actionBox(0)
64 {
65 const int spacing = KDialog::spacingHint();
66
67 m_timer = new QTimer(this);
68 connect(m_timer, SIGNAL(timeout()),
69 this, SLOT(slotTimeout()));
70
71 Q3VBoxLayout* layout = new Q3VBoxLayout(this);
72 layout->setSpacing(spacing);
73
74 // preview
75 m_preview = new PixmapViewer(this);
76 m_preview->setMinimumWidth(K3Icon::SizeEnormous);
77 m_preview->setFixedHeight(K3Icon::SizeEnormous);
78
79 // name
80 m_name = new QLabel(this);
81 m_name->setTextFormat(Qt::RichText);
82 m_name->setAlignment(m_name->alignment() | Qt::AlignHCenter);
83 QFontMetrics fontMetrics(m_name->font());
84 m_name->setMinimumHeight(fontMetrics.height() * 3);
85 m_name->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
86
87 QWidget* sep1 = new Q3HGroupBox(this); // TODO: check whether default widget exist for this?
88 sep1->setFixedHeight(1);
89
90 // general information
91 m_infoGrid = new Q3Grid(2, this);
92 m_infoGrid->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
93
94 QWidget* sep2 = new Q3HGroupBox(this); // TODO: check whether default widget exist for this?
95 sep2->setFixedHeight(1);
96
97 // actions
98 m_actionBox = new KVBox(this);
99 m_actionBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
100
101 // Add a dummy widget with no restriction regarding a vertical resizing.
102 // This assures that information is always top aligned.
103 QWidget* dummy = new QWidget(this);
104
105 layout->addItem(new QSpacerItem(spacing, spacing, QSizePolicy::Preferred, QSizePolicy::Fixed));
106 layout->addWidget(m_preview);
107 layout->addWidget(m_name);
108 layout->addWidget(sep1);
109 layout->addWidget(m_infoGrid);
110 layout->addWidget(sep2);
111 layout->addWidget(m_actionBox);
112 layout->addWidget(dummy);
113
114 connect(mainWindow, SIGNAL(selectionChanged()),
115 this, SLOT(showItemInfo()));
116
117 connectToActiveView();
118 }
119
120 InfoSidebarPage::~InfoSidebarPage()
121 {
122 }
123
124 void InfoSidebarPage::activeViewChanged()
125 {
126 connectToActiveView();
127 }
128
129 void InfoSidebarPage::requestDelayedItemInfo(const KUrl& url)
130 {
131 cancelRequest();
132
133 if (!url.isEmpty() && !m_multipleSelection) {
134 m_urlCandidate = url;
135 m_timer->start(300, true);
136 }
137 }
138
139 void InfoSidebarPage::requestItemInfo(const KUrl& url)
140 {
141 cancelRequest();
142
143 if (!url.isEmpty() && !m_multipleSelection) {
144 m_shownUrl = url;
145 showItemInfo();
146 }
147 }
148
149 void InfoSidebarPage::showItemInfo()
150 {
151 cancelRequest();
152
153 m_multipleSelection = false;
154
155 // show the preview...
156 DolphinView* view = mainWindow()->activeView();
157 const KFileItemList* selectedItems = view->selectedItems();
158 if ((selectedItems != 0) && selectedItems->count() > 1) {
159 m_multipleSelection = true;
160 }
161
162 if (m_multipleSelection) {
163 KIconLoader iconLoader;
164 QPixmap icon = iconLoader.loadIcon("exec",
165 K3Icon::NoGroup,
166 K3Icon::SizeEnormous);
167 m_preview->setPixmap(icon);
168 m_name->setText(i18n("%1 items selected",selectedItems->count()));
169 }
170 else if (!applyBookmark()) {
171 // try to get a preview pixmap from the item...
172 KUrl::List list;
173 list.append(m_shownUrl);
174
175 m_pendingPreview = true;
176 m_preview->setPixmap(QPixmap());
177
178 KIO::PreviewJob* job = KIO::filePreview(list,
179 m_preview->width(),
180 K3Icon::SizeEnormous);
181 connect(job, SIGNAL(gotPreview(const KFileItem*, const QPixmap&)),
182 this, SLOT(gotPreview(const KFileItem*, const QPixmap&)));
183 connect(job, SIGNAL(failed(const KFileItem*)),
184 this, SLOT(slotPreviewFailed(const KFileItem*)));
185
186 QString text("<b>");
187 text.append(m_shownUrl.fileName());
188 text.append("</b>");
189 m_name->setText(text);
190 }
191
192 createMetaInfo();
193 insertActions();
194 }
195
196 void InfoSidebarPage::slotTimeout()
197 {
198 m_shownUrl = m_urlCandidate;
199 showItemInfo();
200 }
201
202 void InfoSidebarPage::slotPreviewFailed(const KFileItem* item)
203 {
204 m_pendingPreview = false;
205 if (!applyBookmark()) {
206 m_preview->setPixmap(item->pixmap(K3Icon::SizeEnormous));
207 }
208 }
209
210 void InfoSidebarPage::gotPreview(const KFileItem* /* item */,
211 const QPixmap& pixmap)
212 {
213 if (m_pendingPreview) {
214 m_preview->setPixmap(pixmap);
215 m_pendingPreview = false;
216 }
217 }
218
219 void InfoSidebarPage::startService(int index)
220 {
221 DolphinView* view = mainWindow()->activeView();
222 if (view->hasSelection()) {
223 KUrl::List selectedUrls = view->selectedUrls();
224 KDEDesktopMimeType::executeService(selectedUrls, m_actionsVector[index]);
225 }
226 else {
227 KDEDesktopMimeType::executeService(m_shownUrl, m_actionsVector[index]);
228 }
229 }
230
231 void InfoSidebarPage::connectToActiveView()
232 {
233 cancelRequest();
234
235 DolphinView* view = mainWindow()->activeView();
236 connect(view, SIGNAL(signalRequestItemInfo(const KUrl&)),
237 this, SLOT(requestDelayedItemInfo(const KUrl&)));
238 connect(view, SIGNAL(signalUrlChanged(const KUrl&)),
239 this, SLOT(requestItemInfo(const KUrl&)));
240
241 m_shownUrl = view->url();
242 showItemInfo();
243 }
244
245 bool InfoSidebarPage::applyBookmark()
246 {
247 KBookmarkGroup root = DolphinSettings::instance().bookmarkManager()->root();
248 KBookmark bookmark = root.first();
249 while (!bookmark.isNull()) {
250 if (m_shownUrl.equals(bookmark.url(), KUrl::CompareWithoutTrailingSlash)) {
251 QString text("<b>");
252 text.append(bookmark.text());
253 text.append("</b>");
254 m_name->setText(text);
255
256 KIconLoader iconLoader;
257 QPixmap icon = iconLoader.loadIcon(bookmark.icon(),
258 K3Icon::NoGroup,
259 K3Icon::SizeEnormous);
260 m_preview->setPixmap(icon);
261 return true;
262 }
263 bookmark = root.next(bookmark);
264 }
265
266 return false;
267 }
268
269 void InfoSidebarPage::cancelRequest()
270 {
271 m_timer->stop();
272 m_pendingPreview = false;
273 }
274
275 void InfoSidebarPage::createMetaInfo()
276 {
277 // To prevent a flickering it's important to reuse available
278 // labels instead of deleting them and recreate them afterwards.
279 // The methods beginInfoLines(), addInfoLine() and endInfoLines()
280 // take care of this.
281 beginInfoLines();
282 DolphinView* view = mainWindow()->activeView();
283 if (!view->hasSelection()) {
284 KFileItem fileItem(S_IFDIR, KFileItem::Unknown, m_shownUrl);
285 fileItem.refresh();
286
287 if (fileItem.isDir()) {
288 addInfoLine(i18n("Type:"), i18n("Directory"));
289 }
290 else {
291 addInfoLine(i18n("Type:"), fileItem.mimeComment());
292
293 QString sizeText(KIO::convertSize(fileItem.size()));
294 addInfoLine(i18n("Size:"), sizeText);
295 addInfoLine(i18n("Modified:"), fileItem.timeString());
296
297 const KFileMetaInfo& metaInfo = fileItem.metaInfo();
298 if (metaInfo.isValid()) {
299 QStringList keys = metaInfo.supportedKeys();
300 for (QStringList::Iterator it = keys.begin(); it != keys.end(); ++it) {
301 if (showMetaInfo(*it)) {
302 KFileMetaInfoItem metaInfoItem = metaInfo.item(*it);
303 addInfoLine(*it, metaInfoItem.string());
304 }
305 }
306 }
307 }
308 }
309 endInfoLines();
310 }
311
312 void InfoSidebarPage::beginInfoLines()
313 {
314 m_currInfoLineIdx = 0;
315 }
316
317 void InfoSidebarPage::endInfoLines()
318 {
319 if (m_currInfoLineIdx <= 0) {
320 return;
321 }
322
323 // remove labels which have not been used
324 if (m_currInfoLineIdx < static_cast<int>(m_infoWidgets.count())) {
325 Q3PtrListIterator<QLabel> deleteIter(m_infoWidgets);
326 deleteIter += m_currInfoLineIdx;
327
328 QWidget* widget = 0;
329 int removeCount = 0;
330 while ((widget = deleteIter.current()) != 0) {
331 widget->close();
332 widget->deleteLater();
333 ++deleteIter;
334 ++removeCount;
335 }
336 for (int i = 0; i < removeCount; ++i) {
337 m_infoWidgets.removeLast();
338 }
339 }
340 }
341
342 bool InfoSidebarPage::showMetaInfo(const QString& key) const
343 {
344 // sorted list of keys, where it's data should be shown
345 static const char* keys[] = {
346 "Album",
347 "Artist",
348 "Author",
349 "Bitrate",
350 "Date",
351 "Dimensions",
352 "Genre",
353 "Length",
354 "Lines",
355 "Pages",
356 "Title",
357 "Words"
358 };
359
360 // do a binary search for the key...
361 int top = 0;
362 int bottom = sizeof(keys) / sizeof(char*) - 1;
363 while (top < bottom) {
364 const int middle = (top + bottom) / 2;
365 const int result = key.compare(keys[middle]);
366 if (result < 0) {
367 bottom = middle - 1;
368 }
369 else if (result > 0) {
370 top = middle + 1;
371 }
372 else {
373 return true;
374 }
375 }
376
377 return false;
378 }
379
380 void InfoSidebarPage::addInfoLine(const QString& labelText, const QString& infoText)
381 {
382 QString labelStr("<b>");
383 labelStr.append(labelText);
384 labelStr.append("</b>&nbsp;");
385
386 const int count = m_infoWidgets.count();
387 if (m_currInfoLineIdx < count - 1) {
388 // reuse available labels
389 m_infoWidgets.at(m_currInfoLineIdx++)->setText(labelStr);
390 m_infoWidgets.at(m_currInfoLineIdx++)->setText(infoText);
391 }
392 else {
393 // no labels are available anymore, hence create 2 new ones
394 QLabel* label = new QLabel(labelStr, m_infoGrid);
395 label->setTextFormat(Qt::RichText);
396 label->setAlignment(Qt::AlignRight |
397 Qt::AlignTop);
398 label->show();
399 m_infoWidgets.append(label);
400
401 QLabel* info = new QLabel(infoText, m_infoGrid);
402 info->setTextFormat(Qt::RichText);
403 info->setAlignment(Qt::AlignTop | Qt::TextWordWrap);
404 info->show();
405 m_infoWidgets.append(info);
406
407 m_currInfoLineIdx += 2;
408 }
409 }
410
411 void InfoSidebarPage::insertActions()
412 {
413 // delete all existing action widgets
414 // TODO: just use children() from QObject...
415 Q3PtrListIterator<QWidget> deleteIter(m_actionWidgets);
416 QWidget* widget = 0;
417 while ((widget = deleteIter.current()) != 0) {
418 widget->close();
419 widget->deleteLater();
420 ++deleteIter;
421 }
422
423 m_actionWidgets.clear();
424 m_actionsVector.clear();
425
426 int actionsIndex = 0;
427
428 // The algorithm for searching the available actions works on a list
429 // of KFileItems. If no selection is given, a temporary KFileItem
430 // by the given Url 'url' is created and added to the list.
431 KFileItem fileItem(S_IFDIR, KFileItem::Unknown, m_shownUrl);
432 KFileItemList localList;
433 const KFileItemList* itemList = mainWindow()->activeView()->selectedItems();
434 if ((itemList == 0) || itemList->isEmpty()) {
435 fileItem.refresh();
436 localList.append(&fileItem);
437 itemList = &localList;
438 }
439
440 // 'itemList' contains now all KFileItems, where an item information should be shown.
441 // TODO: the following algorithm is quite equal to DolphinContextMenu::insertActionItems().
442 // It's open yet whether they should be merged or whether they have to work slightly different.
443 QStringList dirs = KGlobal::dirs()->findDirs("data", "dolphin/servicemenus/");
444 for (QStringList::ConstIterator dirIt = dirs.begin(); dirIt != dirs.end(); ++dirIt) {
445 QDir dir(*dirIt);
446 QStringList entries = dir.entryList("*.desktop", QDir::Files);
447
448 for (QStringList::ConstIterator entryIt = entries.begin(); entryIt != entries.end(); ++entryIt) {
449 KSimpleConfig cfg(*dirIt + *entryIt, true);
450 cfg.setDesktopGroup();
451 if ((cfg.hasKey("Actions") || cfg.hasKey("X-KDE-GetActionMenu")) && cfg.hasKey("ServiceTypes")) {
452 const QStringList types = cfg.readListEntry("ServiceTypes");
453 for (QStringList::ConstIterator it = types.begin(); it != types.end(); ++it) {
454 // check whether the mime type is equal or whether the
455 // mimegroup (e. g. image/*) is supported
456
457 bool insert = false;
458 if ((*it) == "all/allfiles") {
459 // The service type is valid for all files, but not for directories.
460 // Check whether the selected items only consist of files...
461 QListIterator<KFileItem*> mimeIt(*itemList);
462 insert = true;
463 while (insert && mimeIt.hasNext()) {
464 KFileItem* item = mimeIt.next();
465 insert = !item->isDir();
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 QListIterator<KFileItem*> mimeIt(*itemList);
474 insert = true;
475 while (insert && mimeIt.hasNext()) {
476 KFileItem* item = mimeIt.next();
477 const QString mimeType(item->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 }
484 }
485
486 if (insert) {
487 const QString submenuName = cfg.readEntry( "X-KDE-Submenu" );
488 Q3PopupMenu* popup = 0;
489 if (!submenuName.isEmpty()) {
490 // create a sub menu containing all actions
491 popup = new Q3PopupMenu();
492 connect(popup, SIGNAL(activated(int)),
493 this, SLOT(startService(int)));
494
495 QPushButton* button = new QPushButton(submenuName, m_actionBox);
496 button->setFlat(true);
497 button->setPopup(popup);
498 button->show();
499 m_actionWidgets.append(button);
500 }
501
502 Q3ValueList<KDEDesktopMimeType::Service> userServices =
503 KDEDesktopMimeType::userDefinedServices(*dirIt + *entryIt, true);
504
505 // iterate through all actions and add them to a widget
506 Q3ValueList<KDEDesktopMimeType::Service>::Iterator serviceIt;
507 for (serviceIt = userServices.begin(); serviceIt != userServices.end(); ++serviceIt) {
508 KDEDesktopMimeType::Service service = (*serviceIt);
509 if (popup == 0) {
510 ServiceButton* button = new ServiceButton(SmallIcon(service.m_strIcon),
511 service.m_strName,
512 m_actionBox,
513 actionsIndex);
514 connect(button, SIGNAL(requestServiceStart(int)),
515 this, SLOT(startService(int)));
516 m_actionWidgets.append(button);
517 button->show();
518 }
519 else {
520 popup->insertItem(SmallIcon(service.m_strIcon), service.m_strName, actionsIndex);
521 }
522
523 m_actionsVector.append(service);
524 ++actionsIndex;
525 }
526 }
527 }
528 }
529 }
530 }
531 }
532
533 ServiceButton::ServiceButton(const QIcon& icon,
534 const QString& text,
535 QWidget* parent,
536 int index) :
537 QPushButton(icon, text, parent),
538 m_hover(false),
539 m_index(index)
540 {
541 setEraseColor(colorGroup().background());
542 setFocusPolicy(Qt::NoFocus);
543 connect(this, SIGNAL(released()),
544 this, SLOT(slotReleased()));
545 }
546
547 ServiceButton::~ServiceButton()
548 {
549 }
550
551 void ServiceButton::paintEvent(QPaintEvent* event)
552 {
553 QPainter painter(this);
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(Qt::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 - K3Icon::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 += K3Icon::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
635 #include "infosidebarpage.moc"