1 /***************************************************************************
2 * Copyright (C) 2006 by Peter Penz <peter.penz@gmx.at> *
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. *
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. *
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 ***************************************************************************/
20 #include "infosidebarpage.h"
27 #include <qpushbutton.h>
29 #include <q3vgroupbox.h>
30 #include <q3popupmenu.h>
32 #include <qfontmetrics.h>
34 #include <q3hgroupbox.h>
36 #include <Q3ValueList>
38 #include <Q3VBoxLayout>
40 #include <kbookmarkmanager.h>
42 #include <kstandarddirs.h>
43 #include <kio/previewjob.h>
44 #include <kfileitem.h>
46 #include <kglobalsettings.h>
47 #include <kfilemetainfo.h>
50 #include "pixmapviewer.h"
51 #include "dolphinsettings.h"
53 InfoSidebarPage::InfoSidebarPage(QWidget
* parent
) :
55 m_multipleSelection(false),
56 m_pendingPreview(false),
64 const int spacing
= KDialog::spacingHint();
66 m_timer
= new QTimer(this);
67 connect(m_timer
, SIGNAL(timeout()),
68 this, SLOT(slotTimeout()));
70 Q3VBoxLayout
* layout
= new Q3VBoxLayout(this);
71 layout
->setSpacing(spacing
);
74 m_preview
= new PixmapViewer(this);
75 m_preview
->setMinimumWidth(KIcon::SizeEnormous
);
76 m_preview
->setFixedHeight(KIcon::SizeEnormous
);
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
);
86 QWidget
* sep1
= new Q3HGroupBox(this); // TODO: check whether default widget exist for this?
87 sep1
->setFixedHeight(1);
89 // general information
90 m_infoGrid
= new Q3Grid(2, this);
91 m_infoGrid
->setSizePolicy(QSizePolicy::Preferred
, QSizePolicy::Fixed
);
93 QWidget
* sep2
= new Q3HGroupBox(this); // TODO: check whether default widget exist for this?
94 sep2
->setFixedHeight(1);
97 m_actionBox
= new Q3VBox(this);
98 m_actionBox
->setSizePolicy(QSizePolicy::Preferred
, QSizePolicy::Fixed
);
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);
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
);
113 connect(&Dolphin::mainWin(), SIGNAL(selectionChanged()),
114 this, SLOT(showItemInfo()));
116 connectToActiveView();
119 InfoSidebarPage::~InfoSidebarPage()
123 void InfoSidebarPage::activeViewChanged()
125 connectToActiveView();
128 void InfoSidebarPage::requestDelayedItemInfo(const KURL
& url
)
132 if (!url
.isEmpty() && !m_multipleSelection
) {
133 m_urlCandidate
= url
;
134 m_timer
->start(300, true);
138 void InfoSidebarPage::requestItemInfo(const KURL
& url
)
142 if (!url
.isEmpty() && !m_multipleSelection
) {
148 void InfoSidebarPage::showItemInfo()
152 m_multipleSelection
= false;
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;
161 if (m_multipleSelection
) {
162 KIconLoader iconLoader
;
163 QPixmap icon
= iconLoader
.loadIcon("exec",
165 KIcon::SizeEnormous
);
166 m_preview
->setPixmap(icon
);
167 m_name
->setText(i18n("%1 items selected").arg(selectedItems
->count()));
169 else if (!applyBookmark()) {
170 // try to get a preview pixmap from the item...
172 list
.append(m_shownURL
);
174 m_pendingPreview
= true;
175 m_preview
->setPixmap(QPixmap());
177 KIO::PreviewJob
* job
= KIO::filePreview(list
,
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
*)));
186 text
.append(m_shownURL
.fileName());
188 m_name
->setText(text
);
195 void InfoSidebarPage::slotTimeout()
197 m_shownURL
= m_urlCandidate
;
201 void InfoSidebarPage::slotPreviewFailed(const KFileItem
* item
)
203 m_pendingPreview
= false;
204 if (!applyBookmark()) {
205 m_preview
->setPixmap(item
->pixmap(KIcon::SizeEnormous
));
209 void InfoSidebarPage::gotPreview(const KFileItem
* /* item */,
210 const QPixmap
& pixmap
)
212 if (m_pendingPreview
) {
213 m_preview
->setPixmap(pixmap
);
214 m_pendingPreview
= false;
218 void InfoSidebarPage::startService(int index
)
220 DolphinView
* view
= Dolphin::mainWin().activeView();
221 if (view
->hasSelection()) {
222 KURL::List selectedURLs
= view
->selectedURLs();
223 KDEDesktopMimeType::executeService(selectedURLs
, m_actionsVector
[index
]);
226 KDEDesktopMimeType::executeService(m_shownURL
, m_actionsVector
[index
]);
230 void InfoSidebarPage::connectToActiveView()
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
&)));
240 m_shownURL
= view
->url();
244 bool InfoSidebarPage::applyBookmark()
246 KBookmarkGroup root
= DolphinSettings::instance().bookmarkManager()->root();
247 KBookmark bookmark
= root
.first();
248 while (!bookmark
.isNull()) {
249 if (m_shownURL
.equals(bookmark
.url(), true)) {
251 text
.append(bookmark
.text());
253 m_name
->setText(text
);
255 KIconLoader iconLoader
;
256 QPixmap icon
= iconLoader
.loadIcon(bookmark
.icon(),
258 KIcon::SizeEnormous
);
259 m_preview
->setPixmap(icon
);
262 bookmark
= root
.next(bookmark
);
268 void InfoSidebarPage::cancelRequest()
271 m_pendingPreview
= false;
274 void InfoSidebarPage::createMetaInfo()
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.
281 DolphinView
* view
= Dolphin::mainWin().activeView();
282 if (!view
->hasSelection()) {
283 KFileItem
fileItem(S_IFDIR
, KFileItem::Unknown
, m_shownURL
);
286 if (fileItem
.isDir()) {
287 addInfoLine(i18n("Type:"), i18n("Directory"));
290 addInfoLine(i18n("Type:"), fileItem
.mimeComment());
292 QString
sizeText(KIO::convertSize(fileItem
.size()));
293 addInfoLine(i18n("Size:"), sizeText
);
294 addInfoLine(i18n("Modified:"), fileItem
.timeString());
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());
311 void InfoSidebarPage::beginInfoLines()
313 m_currInfoLineIdx
= 0;
316 void InfoSidebarPage::endInfoLines()
318 if (m_currInfoLineIdx
<= 0) {
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
;
329 while ((widget
= deleteIter
.current()) != 0) {
331 widget
->deleteLater();
335 for (int i
= 0; i
< removeCount
; ++i
) {
336 m_infoWidgets
.removeLast();
341 bool InfoSidebarPage::showMetaInfo(const QString
& key
) const
343 // sorted list of keys, where it's data should be shown
344 static const char* keys
[] = {
359 // do a binary search for the key...
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
]);
368 else if (result
> 0) {
379 void InfoSidebarPage::addInfoLine(const QString
& labelText
, const QString
& infoText
)
381 QString
labelStr("<b>");
382 labelStr
.append(labelText
);
383 labelStr
.append("</b> ");
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
);
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
|
398 m_infoWidgets
.append(label
);
400 QLabel
* info
= new QLabel(infoText
, m_infoGrid
);
401 info
->setTextFormat(Qt::RichText
);
402 info
->setAlignment(Qt::AlignTop
| Qt::WordBreak
);
404 m_infoWidgets
.append(info
);
406 m_currInfoLineIdx
+= 2;
410 void InfoSidebarPage::insertActions()
412 // delete all existing action widgets
413 // TODO: just use children() from QObject...
414 Q3PtrListIterator
<QWidget
> deleteIter(m_actionWidgets
);
416 while ((widget
= deleteIter
.current()) != 0) {
418 widget
->deleteLater();
422 m_actionWidgets
.clear();
423 m_actionsVector
.clear();
425 int actionsIndex
= 0;
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()) {
435 localList
.append(&fileItem
);
436 itemList
= &localList
;
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
) {
445 QStringList entries
= dir
.entryList("*.desktop", QDir::Files
);
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
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
);
463 while (insert
&& ((item
= mimeIt
.current()) != 0)) {
464 insert
= !item
->isDir();
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
);
476 while (insert
&& ((item
= mimeIt
.current()) != 0)) {
477 const QString
mimeType((*mimeIt
)->mimetype());
478 const QString
mimeGroup(mimeType
.left(mimeType
.find('/')));
480 insert
= (*it
== mimeType
) ||
481 ((*it
).right(1) == "*") &&
482 ((*it
).left((*it
).find('/')) == mimeGroup
);
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)));
496 QPushButton
* button
= new QPushButton(submenuName
, m_actionBox
);
497 button
->setFlat(true);
498 button
->setPopup(popup
);
500 m_actionWidgets
.append(button
);
503 Q3ValueList
<KDEDesktopMimeType::Service
> userServices
=
504 KDEDesktopMimeType::userDefinedServices(*dirIt
+ *entryIt
, true);
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
);
511 ServiceButton
* button
= new ServiceButton(SmallIcon(service
.m_strIcon
),
515 connect(button
, SIGNAL(requestServiceStart(int)),
516 this, SLOT(startService(int)));
517 m_actionWidgets
.append(button
);
521 popup
->insertItem(SmallIcon(service
.m_strIcon
), service
.m_strName
, actionsIndex
);
524 m_actionsVector
.append(service
);
534 ServiceButton::ServiceButton(const QIcon
& icon
,
538 QPushButton(icon
, text
, parent
),
542 setEraseColor(colorGroup().background());
543 setFocusPolicy(Qt::NoFocus
);
544 connect(this, SIGNAL(released()),
545 this, SLOT(slotReleased()));
548 ServiceButton::~ServiceButton()
552 void ServiceButton::drawButton(QPainter
* painter
)
554 const int buttonWidth
= width();
555 const int buttonHeight
= height();
557 QColor backgroundColor
;
558 QColor foregroundColor
;
560 backgroundColor
= KGlobalSettings::highlightColor();
561 foregroundColor
= KGlobalSettings::highlightedTextColor();
564 backgroundColor
= colorGroup().background();
565 foregroundColor
= KGlobalSettings::buttonTextColor();
568 // draw button background
569 painter
->setPen(NoPen
);
570 painter
->setBrush(backgroundColor
);
571 painter
->drawRect(0, 0, buttonWidth
, buttonHeight
);
573 const int spacing
= KDialog::spacingHint();
577 const int y
= (buttonHeight
- KIcon::SizeSmall
) / 2;
578 const QIcon
* set
= iconSet();
580 painter
->drawPixmap(x
, y
, set
->pixmap(QIcon::Small
, QIcon::Normal
));
582 x
+= KIcon::SizeSmall
+ spacing
;
585 painter
->setPen(foregroundColor
);
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());
594 // Blend the right area of the text with the background, as the
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;
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());
609 blendColor
.setRgb(blendColor
.red() + redInc
,
610 blendColor
.green() + greenInc
,
611 blendColor
.blue() + blueInc
);
616 void ServiceButton::enterEvent(QEvent
* event
)
618 QPushButton::enterEvent(event
);
623 void ServiceButton::leaveEvent(QEvent
* event
)
625 QPushButton::leaveEvent(event
);
630 void ServiceButton::slotReleased()
632 emit
requestServiceStart(m_index
);