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"
KDE 4.6 API Reference