• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

KFile

kurlnavigatorbutton.cpp

Go to the documentation of this file.
00001 /*****************************************************************************
00002  * Copyright (C) 2006 by Peter Penz <peter.penz@gmx.at>                      *
00003  * Copyright (C) 2006 by Aaron J. Seigo <aseigo@kde.org>                     *
00004  *                                                                           *
00005  * This library is free software; you can redistribute it and/or             *
00006  * modify it under the terms of the GNU Library General Public               *
00007  * License as published by the Free Software Foundation; either              *
00008  * version 2 of the License, or (at your option) any later version.          *
00009  *                                                                           *
00010  * This library is distributed in the hope that it will be useful,           *
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU         *
00013  * Library General Public License for more details.                          *
00014  *                                                                           *
00015  * You should have received a copy of the GNU Library General Public License *
00016  * along with this library; see the file COPYING.LIB.  If not, write to      *
00017  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,      *
00018  * Boston, MA 02110-1301, USA.                                               *
00019  *****************************************************************************/
00020 
00021 #include "kurlnavigatorbutton_p.h"
00022 
00023 #include "kurlnavigator.h"
00024 #include "kurlnavigatormenu_p.h"
00025 #include "kdirsortfilterproxymodel.h"
00026 
00027 #include <kio/job.h>
00028 #include <kio/jobclasses.h>
00029 #include <kglobalsettings.h>
00030 #include <klocale.h>
00031 #include <kstringhandler.h>
00032 
00033 #include <QtCore/QTimer>
00034 #include <QtGui/QPainter>
00035 #include <QtGui/QKeyEvent>
00036 #include <QtGui/QStyleOption>
00037 
00038 QPointer<KUrlNavigatorMenu> KUrlNavigatorButton::m_subDirsMenu;
00039 
00040 KUrlNavigatorButton::KUrlNavigatorButton(const KUrl& url, QWidget* parent) :
00041     KUrlNavigatorButtonBase(parent),
00042     m_hoverArrow(false),
00043     m_pendingTextChange(false),
00044     m_replaceButton(false),
00045     m_wheelSteps(0),
00046     m_url(url),
00047     m_subDir(),
00048     m_openSubDirsTimer(0),
00049     m_subDirsJob(0)
00050 {
00051     setAcceptDrops(true);
00052     setUrl(url);
00053     setMouseTracking(true);
00054 
00055     m_openSubDirsTimer = new QTimer(this);
00056     m_openSubDirsTimer->setSingleShot(true);
00057     m_openSubDirsTimer->setInterval(300);
00058     connect(m_openSubDirsTimer, SIGNAL(timeout()), this, SLOT(startSubDirsJob()));
00059 
00060     connect(this, SIGNAL(pressed()), this, SLOT(requestSubDirs()));
00061 }
00062 
00063 KUrlNavigatorButton::~KUrlNavigatorButton()
00064 {
00065 }
00066 
00067 void KUrlNavigatorButton::setUrl(const KUrl& url)
00068 {
00069     m_url = url;
00070 
00071     if (m_url.isLocalFile()) {
00072         setText(m_url.fileName());
00073     } else {
00074         m_pendingTextChange = true;
00075         KIO::StatJob* job = KIO::stat(m_url, KIO::HideProgressInfo);
00076         connect(job, SIGNAL(result(KJob*)),
00077                 this, SLOT(statFinished(KJob*)));
00078         emit startedTextResolving();
00079     }
00080 }
00081 
00082 KUrl KUrlNavigatorButton::url() const
00083 {
00084     return m_url;
00085 }
00086 
00087 void KUrlNavigatorButton::setText(const QString& text)
00088 {
00089     QString adjustedText = text;
00090     if (adjustedText.isEmpty()) {
00091         adjustedText = m_url.protocol();
00092     }
00093     // Assure that the button always consists of one line
00094     adjustedText.remove(QLatin1Char('\n'));
00095 
00096     KUrlNavigatorButtonBase::setText(adjustedText);
00097     updateMinimumWidth();
00098 
00099     // Assure that statFinished() does not overwrite a text that has been
00100     // set by a client of the URL navigator button
00101     m_pendingTextChange = false;
00102 }
00103 
00104 void KUrlNavigatorButton::setActiveSubDirectory(const QString& subDir)
00105 {
00106     m_subDir = subDir;
00107 
00108     // We use a different (bold) font on active, so the size hint changes
00109     updateGeometry();
00110     update();
00111 }
00112 
00113 QString KUrlNavigatorButton::activeSubDirectory() const
00114 {
00115     return m_subDir;
00116 }
00117 
00118 QSize KUrlNavigatorButton::sizeHint() const
00119 {
00120     QFont adjustedFont(font());
00121     adjustedFont.setBold(m_subDir.isEmpty());
00122     // the minimum size is textWidth + arrowWidth() + 2 * BorderWidth; for the
00123     // preferred size we add the BorderWidth 2 times again for having an uncluttered look
00124     const int width = QFontMetrics(adjustedFont).width(text()) + arrowWidth() + 4 * BorderWidth;
00125     return QSize(width, KUrlNavigatorButtonBase::sizeHint().height());
00126 }
00127 
00128 void KUrlNavigatorButton::paintEvent(QPaintEvent* event)
00129 {
00130     Q_UNUSED(event);
00131 
00132     QPainter painter(this);
00133 
00134     QFont adjustedFont(font());
00135     adjustedFont.setBold(m_subDir.isEmpty());
00136     painter.setFont(adjustedFont);
00137 
00138     int buttonWidth  = width();
00139     int preferredWidth = sizeHint().width();
00140     if (preferredWidth < minimumWidth()) {
00141         preferredWidth = minimumWidth();
00142     }
00143     if (buttonWidth > preferredWidth) {
00144         buttonWidth = preferredWidth;
00145     }
00146     const int buttonHeight = height();
00147 
00148     const QColor fgColor = foregroundColor();
00149     drawHoverBackground(&painter);
00150 
00151     int textLeft = 0;
00152     int textWidth = buttonWidth;
00153 
00154     const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
00155 
00156     if (!m_subDir.isEmpty()) {
00157         // draw arrow
00158         const int arrowSize = arrowWidth();
00159         const int arrowX = leftToRight ? (buttonWidth - arrowSize) - BorderWidth : BorderWidth;
00160         const int arrowY = (buttonHeight - arrowSize) / 2;
00161 
00162         QStyleOption option;
00163         option.initFrom(this);
00164         option.rect = QRect(arrowX, arrowY, arrowSize, arrowSize);
00165         option.palette = palette();
00166         option.palette.setColor(QPalette::Text, fgColor);
00167         option.palette.setColor(QPalette::WindowText, fgColor);
00168         option.palette.setColor(QPalette::ButtonText, fgColor);
00169 
00170         if (m_hoverArrow) {
00171             // highlight the background of the arrow to indicate that the directories
00172             // popup can be opened by a mouse click
00173             QColor hoverColor = palette().color(QPalette::HighlightedText);
00174             hoverColor.setAlpha(96);
00175             painter.setPen(Qt::NoPen);
00176             painter.setBrush(hoverColor);
00177 
00178             int hoverX = arrowX;
00179             if (!leftToRight) {
00180                 hoverX -= BorderWidth;
00181             }
00182             painter.drawRect(QRect(hoverX, 0, arrowSize + BorderWidth, buttonHeight));
00183         }
00184 
00185         if (leftToRight) {
00186             style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &option, &painter, this);
00187         } else {
00188             style()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &option, &painter, this);
00189             textLeft += arrowSize + 2 * BorderWidth;
00190         }
00191 
00192         textWidth -= arrowSize + 2 * BorderWidth;
00193     }
00194 
00195     painter.setPen(fgColor);
00196     const bool clipped = isTextClipped();
00197     const int align = clipped ? Qt::AlignVCenter : Qt::AlignCenter;
00198     const QRect textRect(textLeft, 0, textWidth, buttonHeight);
00199     if (clipped) {
00200         QColor bgColor = fgColor;
00201         bgColor.setAlpha(0);
00202         QLinearGradient gradient(textRect.topLeft(), textRect.topRight());
00203         if (leftToRight) {
00204             gradient.setColorAt(0.8, fgColor);
00205             gradient.setColorAt(1.0, bgColor);
00206         } else {
00207             gradient.setColorAt(0.0, bgColor);
00208             gradient.setColorAt(0.2, fgColor);
00209         }
00210 
00211         QPen pen;
00212         pen.setBrush(QBrush(gradient));
00213         painter.setPen(pen);
00214     }
00215     painter.drawText(textRect, align, text());
00216 }
00217 
00218 void KUrlNavigatorButton::enterEvent(QEvent* event)
00219 {
00220     KUrlNavigatorButtonBase::enterEvent(event);
00221 
00222     // if the text is clipped due to a small window width, the text should
00223     // be shown as tooltip
00224     if (isTextClipped()) {
00225         setToolTip(text());
00226     }
00227 }
00228 
00229 void KUrlNavigatorButton::leaveEvent(QEvent* event)
00230 {
00231     KUrlNavigatorButtonBase::leaveEvent(event);
00232     setToolTip(QString());
00233 
00234     if (m_hoverArrow) {
00235         m_hoverArrow = false;
00236         update();
00237     }
00238 }
00239 
00240 void KUrlNavigatorButton::dropEvent(QDropEvent* event)
00241 {
00242     const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
00243     if (!urls.isEmpty()) {
00244         setDisplayHintEnabled(DraggedHint, true);
00245 
00246         emit urlsDropped(m_url, event);
00247 
00248         setDisplayHintEnabled(DraggedHint, false);
00249         update();
00250     }
00251 }
00252 
00253 void KUrlNavigatorButton::dragEnterEvent(QDragEnterEvent* event)
00254 {
00255     if (event->mimeData()->hasUrls()) {
00256         setDisplayHintEnabled(DraggedHint, true);
00257         event->acceptProposedAction();
00258 
00259         update();
00260     }
00261 }
00262 
00263 void KUrlNavigatorButton::dragMoveEvent(QDragMoveEvent* event)
00264 {
00265     QRect rect = event->answerRect();
00266     if (isAboveArrow(rect.center().x())) {
00267         m_hoverArrow = true;
00268         update();
00269 
00270         if (m_subDirsMenu == 0) {
00271             requestSubDirs();
00272         } else if (m_subDirsMenu->parent() != this) {
00273             m_subDirsMenu->close();
00274             m_subDirsMenu->deleteLater();
00275             m_subDirsMenu = 0;
00276 
00277             requestSubDirs();
00278         }
00279     } else {
00280         if (m_openSubDirsTimer->isActive()) {
00281             cancelSubDirsRequest();
00282         }
00283         delete m_subDirsMenu;
00284         m_subDirsMenu = 0;
00285         m_hoverArrow = false;
00286         update();
00287     }
00288 }
00289 
00290 void KUrlNavigatorButton::dragLeaveEvent(QDragLeaveEvent* event)
00291 {
00292     KUrlNavigatorButtonBase::dragLeaveEvent(event);
00293 
00294     m_hoverArrow = false;
00295     setDisplayHintEnabled(DraggedHint, false);
00296     update();
00297 }
00298 
00299 void KUrlNavigatorButton::mousePressEvent(QMouseEvent* event)
00300 {
00301     if (isAboveArrow(event->x()) && (event->button() == Qt::LeftButton)) {
00302         // the mouse is pressed above the [>] button
00303         startSubDirsJob();
00304     }
00305     KUrlNavigatorButtonBase::mousePressEvent(event);
00306 }
00307 
00308 void KUrlNavigatorButton::mouseReleaseEvent(QMouseEvent* event)
00309 {
00310     if (!isAboveArrow(event->x()) || (event->button() != Qt::LeftButton)) {
00311         // the mouse has been released above the text area and not
00312         // above the [>] button
00313         emit clicked(m_url, event->button());
00314         cancelSubDirsRequest();
00315     }
00316     KUrlNavigatorButtonBase::mouseReleaseEvent(event);
00317 }
00318 
00319 void KUrlNavigatorButton::mouseMoveEvent(QMouseEvent* event)
00320 {
00321     KUrlNavigatorButtonBase::mouseMoveEvent(event);
00322 
00323     const bool hoverArrow = isAboveArrow(event->x());
00324     if (hoverArrow != m_hoverArrow) {
00325         m_hoverArrow = hoverArrow;
00326         update();
00327     }
00328 }
00329 
00330 void KUrlNavigatorButton::wheelEvent(QWheelEvent* event)
00331 {
00332     if (event->orientation() == Qt::Vertical) {
00333         m_wheelSteps = event->delta() / 120;
00334         m_replaceButton = true;
00335         startSubDirsJob();
00336         event->accept();
00337     } else {
00338         KUrlNavigatorButtonBase::wheelEvent(event);
00339     }
00340 }
00341 
00342 void KUrlNavigatorButton::requestSubDirs()
00343 {
00344     if (!m_openSubDirsTimer->isActive() && (m_subDirsJob == 0)) {
00345         m_openSubDirsTimer->start();
00346     }
00347 }
00348 
00349 void KUrlNavigatorButton::startSubDirsJob()
00350 {
00351     if (m_subDirsJob != 0) {
00352         return;
00353     }
00354 
00355     const KUrl url = m_replaceButton ? m_url.upUrl() : m_url;
00356     m_subDirsJob = KIO::listDir(url, KIO::HideProgressInfo, false /*no hidden files*/);
00357     m_subDirs.clear(); // just to be ++safe
00358 
00359     connect(m_subDirsJob, SIGNAL(entries(KIO::Job*, const KIO::UDSEntryList &)),
00360             this, SLOT(addEntriesToSubDirs(KIO::Job*, const KIO::UDSEntryList&)));
00361 
00362     if (m_replaceButton) {
00363         connect(m_subDirsJob, SIGNAL(result(KJob*)), this, SLOT(replaceButton(KJob*)));
00364     } else {
00365         connect(m_subDirsJob, SIGNAL(result(KJob*)), this, SLOT(openSubDirsMenu(KJob*)));
00366     }
00367 }
00368 
00369 void KUrlNavigatorButton::addEntriesToSubDirs(KIO::Job* job, const KIO::UDSEntryList& entries)
00370 {
00371     Q_ASSERT(job == m_subDirsJob);
00372 
00373     foreach (const KIO::UDSEntry& entry, entries) {
00374         if (entry.isDir()) {
00375             const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
00376             QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
00377             if (displayName.isEmpty()) {
00378                 displayName = name;
00379             }
00380             if ((name != QLatin1String(".")) && (name != QLatin1String(".."))) {
00381                 m_subDirs.append(qMakePair(name, displayName));
00382             }
00383         }
00384     }
00385 }
00386 
00387 void KUrlNavigatorButton::urlsDropped(QAction* action, QDropEvent* event)
00388 {
00389     const int result = action->data().toInt();
00390     KUrl url = m_url;
00391     url.addPath(m_subDirs.at(result).first);
00392     urlsDropped(url, event);
00393 }
00394 
00395 void KUrlNavigatorButton::slotMenuActionClicked(QAction* action)
00396 {
00397     const int result = action->data().toInt();
00398     KUrl url = m_url;
00399     url.addPath(m_subDirs.at(result).first);
00400     emit clicked(url, Qt::MidButton);
00401 }
00402 
00403 void KUrlNavigatorButton::statFinished(KJob* job)
00404 {
00405     if (m_pendingTextChange) {
00406         m_pendingTextChange = false;
00407 
00408         const KIO::UDSEntry entry = static_cast<KIO::StatJob*>(job)->statResult();
00409         QString name = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
00410         if (name.isEmpty()) {
00411             name = m_url.fileName();
00412         }
00413         setText(name);
00414 
00415         emit finishedTextResolving();
00416     }
00417 }
00418 
00422 static bool naturalLessThan(const QPair<QString, QString>& s1, const QPair<QString, QString>& s2)
00423 {
00424     return KStringHandler::naturalCompare(s1.first, s2.first, Qt::CaseInsensitive) < 0;
00425 }
00426 
00427 void KUrlNavigatorButton::openSubDirsMenu(KJob* job)
00428 {
00429     Q_ASSERT(job == m_subDirsJob);
00430     m_subDirsJob = 0;
00431 
00432     if (job->error() || m_subDirs.isEmpty()) {
00433         // clear listing
00434         return;
00435     }
00436 
00437     qSort(m_subDirs.begin(), m_subDirs.end(), naturalLessThan);
00438     setDisplayHintEnabled(PopupActiveHint, true);
00439     update(); // ensure the button is drawn highlighted
00440 
00441     if (m_subDirsMenu != 0) {
00442         m_subDirsMenu->close();
00443         m_subDirsMenu->deleteLater();
00444         m_subDirsMenu = 0;
00445     }
00446 
00447     m_subDirsMenu = new KUrlNavigatorMenu(this);
00448     initMenu(m_subDirsMenu, 0);
00449 
00450     const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
00451     const int popupX = leftToRight ? width() - arrowWidth() - BorderWidth : 0;
00452     const QPoint popupPos  = parentWidget()->mapToGlobal(geometry().bottomLeft() + QPoint(popupX, 0));
00453 
00454     const QAction* action = m_subDirsMenu->exec(popupPos);
00455     if (action != 0) {
00456         const int result = action->data().toInt();
00457         KUrl url = m_url;
00458         url.addPath(m_subDirs[result].first);
00459         emit clicked(url, Qt::LeftButton);
00460     }
00461 
00462     m_subDirs.clear();
00463     delete m_subDirsMenu;
00464     m_subDirsMenu = 0;
00465 
00466     setDisplayHintEnabled(PopupActiveHint, false);
00467 }
00468 
00469 void KUrlNavigatorButton::replaceButton(KJob* job)
00470 {
00471     Q_ASSERT(job == m_subDirsJob);
00472     m_subDirsJob = 0;
00473     m_replaceButton = false;
00474 
00475     if (job->error() || m_subDirs.isEmpty()) {
00476         return;
00477     }
00478 
00479     qSort(m_subDirs.begin(), m_subDirs.end(), naturalLessThan);
00480 
00481     // Get index of the directory that is shown currently in the button
00482     const QString currentDir = m_url.fileName();
00483     int currentIndex = 0;
00484     const int subDirsCount = m_subDirs.count();
00485     while (currentIndex < subDirsCount) {
00486         if (m_subDirs[currentIndex].first == currentDir) {
00487             break;
00488         }
00489         ++currentIndex;
00490     }
00491 
00492     // Adjust the index by respecting the wheel steps and
00493     // trigger a replacing of the button content
00494     int targetIndex = currentIndex - m_wheelSteps;
00495     if (targetIndex < 0) {
00496         targetIndex = 0;
00497     } else if (targetIndex >= subDirsCount) {
00498         targetIndex = subDirsCount - 1;
00499     }
00500 
00501     KUrl url = m_url.upUrl();
00502     url.addPath(m_subDirs[targetIndex].first);
00503 
00504     emit clicked(url, Qt::LeftButton);
00505 
00506     m_subDirs.clear();
00507 }
00508 
00509 void KUrlNavigatorButton::cancelSubDirsRequest()
00510 {
00511     m_openSubDirsTimer->stop();
00512     if (m_subDirsJob != 0) {
00513         m_subDirsJob->kill();
00514         m_subDirsJob = 0;
00515     }
00516 }
00517 
00518 int KUrlNavigatorButton::arrowWidth() const
00519 {
00520     // if there isn't arrow then return 0
00521     int width = 0;
00522     if (!m_subDir.isEmpty()) {
00523         width = height() / 2;
00524         if (width < 4) {
00525             width = 4;
00526         }
00527     }
00528 
00529     return width;
00530 }
00531 
00532 bool KUrlNavigatorButton::isAboveArrow(int x) const
00533 {
00534     const bool leftToRight = (layoutDirection() == Qt::LeftToRight);
00535     return leftToRight ? (x >= width() - arrowWidth()) : (x < arrowWidth());
00536 }
00537 
00538 bool KUrlNavigatorButton::isTextClipped() const
00539 {
00540     int availableWidth = width() - 2 * BorderWidth;
00541     if (!m_subDir.isEmpty()) {
00542         availableWidth -= arrowWidth() - BorderWidth;
00543     }
00544 
00545     QFont adjustedFont(font());
00546     adjustedFont.setBold(m_subDir.isEmpty());
00547     return QFontMetrics(adjustedFont).width(text()) >= availableWidth;
00548 }
00549 
00550 void KUrlNavigatorButton::updateMinimumWidth()
00551 {
00552     const int oldMinWidth = minimumWidth();
00553 
00554     int minWidth = sizeHint().width();
00555     if (minWidth < 40) {
00556         minWidth = 40;
00557     }
00558     else if (minWidth > 150) {
00559         // don't let an overlong path name waste all the URL navigator space
00560         minWidth = 150;
00561     }
00562     if (oldMinWidth != minWidth) {
00563         setMinimumWidth(minWidth);
00564     }
00565 }
00566 
00567 void KUrlNavigatorButton::initMenu(KUrlNavigatorMenu* menu, int startIndex)
00568 {
00569     connect(menu, SIGNAL(middleMouseButtonClicked(QAction*)),
00570             this, SLOT(slotMenuActionClicked(QAction*)));
00571     connect(menu, SIGNAL(urlsDropped(QAction*, QDropEvent*)),
00572             this, SLOT(urlsDropped(QAction*, QDropEvent*)));
00573 
00574     menu->setLayoutDirection(Qt::LeftToRight);
00575 
00576     const int maxIndex = startIndex + 30;  // Don't show more than 30 items in a menu
00577     const int lastIndex = qMin(m_subDirs.count() - 1, maxIndex);
00578     for (int i = startIndex; i <= lastIndex; ++i) {
00579         const QString subDirName = m_subDirs[i].first;
00580         const QString subDirDisplayName = m_subDirs[i].second;
00581         QString text = KStringHandler::csqueeze(subDirDisplayName, 60);
00582         text.replace(QLatin1Char('&'), QLatin1String("&&"));
00583         QAction* action = new QAction(text, this);
00584         if (m_subDir == subDirName) {
00585             QFont font(action->font());
00586             font.setBold(true);
00587             action->setFont(font);
00588         }
00589         action->setData(i);
00590         menu->addAction(action);
00591     }
00592     if (m_subDirs.count() > maxIndex) {
00593         // If too much items are shown, move them into a sub menu
00594         menu->addSeparator();
00595         KUrlNavigatorMenu* subDirsMenu = new KUrlNavigatorMenu(menu);
00596         subDirsMenu->setTitle(i18nc("@action:inmenu", "More"));
00597         initMenu(subDirsMenu, maxIndex);
00598         menu->addMenu(subDirsMenu);
00599     }
00600 }
00601 
00602 
00603 #include "kurlnavigatorbutton_p.moc"

KFile

Skip menu "KFile"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal