8#include "kurlnavigatorbutton_p.h"
10#include "../utils_p.h"
11#include "kurlnavigator.h"
12#include "kurlnavigatormenu_p.h"
13#include <kio/listjob.h>
14#include <kio/statjob.h>
16#include <KLocalizedString>
17#include <KStringHandler>
23#include <QStyleOption>
28QPointer<KUrlNavigatorMenu> KUrlNavigatorButton::m_subDirsMenu;
30KUrlNavigatorButton::KUrlNavigatorButton(
const QUrl &url, KUrlNavigator *parent)
31 : KUrlNavigatorButtonBase(parent)
32 , m_hoverOverIcon(false)
33 , m_pendingTextChange(false)
34 , m_replaceButton(false)
35 , m_showMnemonic(false)
36 , m_drawSeparator(true)
40 , m_openSubDirsTimer(nullptr)
41 , m_subDirsJob(nullptr)
46 setMouseTracking(
true);
48 m_openSubDirsTimer =
new QTimer(
this);
49 m_openSubDirsTimer->setSingleShot(
true);
50 m_openSubDirsTimer->setInterval(300);
56KUrlNavigatorButton::~KUrlNavigatorButton()
60void KUrlNavigatorButton::setUrl(
const QUrl &url)
69 QStringLiteral(
"nfs"),
70 QStringLiteral(
"fish"),
71 QStringLiteral(
"ftp"),
72 QStringLiteral(
"sftp"),
73 QStringLiteral(
"smb"),
74 QStringLiteral(
"webdav"),
75 QStringLiteral(
"mtp"),
78 const bool startTextResolving = m_url.isValid() && !m_url.isLocalFile() && !protocolBlacklist.
contains(m_url.scheme());
80 if (startTextResolving) {
81 m_pendingTextChange =
true;
84 Q_EMIT startedTextResolving();
91QUrl KUrlNavigatorButton::url()
const
96void KUrlNavigatorButton::setText(
const QString &text)
100 adjustedText = m_url.scheme();
105 KUrlNavigatorButtonBase::setText(adjustedText);
106 updateMinimumWidth();
110 m_pendingTextChange =
false;
113void KUrlNavigatorButton::setActiveSubDirectory(
const QString &subDir)
122QString KUrlNavigatorButton::activeSubDirectory()
const
127QSize KUrlNavigatorButton::sizeHint()
const
129 QFont adjustedFont(font());
130 adjustedFont.setBold(m_subDir.isEmpty());
135 return QSize(width, KUrlNavigatorButtonBase::sizeHint().height());
138void KUrlNavigatorButton::setShowMnemonic(
bool show)
140 if (m_showMnemonic != show) {
141 m_showMnemonic = show;
146bool KUrlNavigatorButton::showMnemonic()
const
148 return m_showMnemonic;
151void KUrlNavigatorButton::setDrawSeparator(
bool draw)
153 if (m_drawSeparator != draw) {
154 m_drawSeparator = draw;
159bool KUrlNavigatorButton::drawSeparator()
const
161 return m_drawSeparator;
164void KUrlNavigatorButton::paintEvent(
QPaintEvent *event)
170 QFont adjustedFont(font());
171 adjustedFont.setBold(m_subDir.isEmpty());
172 painter.setFont(adjustedFont);
174 int buttonWidth = width();
175 int preferredWidth = sizeHint().width();
176 if (preferredWidth < minimumWidth()) {
177 preferredWidth = minimumWidth();
179 if (buttonWidth > preferredWidth) {
180 buttonWidth = preferredWidth;
182 const int buttonHeight = height();
184 const QColor fgColor = foregroundColor();
185 drawHoverBackground(&painter);
188 int textWidth = buttonWidth;
193 const int iconW = iconWidth();
194 const int iconX = !leftToRight ? (buttonWidth - iconW) - m_padding / 2 : m_padding / 2;
195 const int iconY = (buttonHeight - iconW) / 2;
199 option.rect =
QRect(iconX, iconY, iconW, iconW);
200 option.palette = palette();
205 if (m_hoverOverIcon) {
210 const int widthModifier = iconW + m_padding / 2;
211 auto pixmap = icon().pixmap(iconSize(), devicePixelRatioF());
214 textLeft += widthModifier;
216 textWidth -= widthModifier;
218 painter.setPen(fgColor);
219 const bool clipped = isTextClipped();
220 QRect textRect(textLeft, 0, textWidth, buttonHeight);
226 gradient.setFinalStop(
QPoint(gradient.finalStop().x() - m_padding, gradient.finalStop().y()));
227 gradient.setColorAt(0.8, fgColor);
228 gradient.setColorAt(1.0, bgColor);
230 gradient.setStart(
QPoint(gradient.start().x() + m_padding, gradient.start().y()));
231 gradient.setColorAt(0.0, bgColor);
232 gradient.setColorAt(0.2, fgColor);
243 textRect.setLeft(textRect.left() + m_padding / 2);
245 textRect.setRight(textRect.right() - m_padding / 2);
248 if (m_showMnemonic) {
250 painter.drawText(textRect, textFlags, text());
252 painter.drawText(textRect, textFlags, plainText());
255 if (m_drawSeparator) {
259 option.rect =
QRect(rect().topRight(), rect().bottomRight());
261 option.rect =
QRect(rect().topLeft(), rect().bottomLeft());
270void KUrlNavigatorButton::enterEvent(
QEnterEvent *event)
272 KUrlNavigatorButtonBase::enterEvent(event);
276 if (isTextClipped()) {
277 setToolTip(plainText());
281void KUrlNavigatorButton::leaveEvent(
QEvent *event)
283 KUrlNavigatorButtonBase::leaveEvent(event);
286 if (m_hoverOverIcon) {
287 m_hoverOverIcon =
false;
292void KUrlNavigatorButton::keyPressEvent(
QKeyEvent *event)
294 switch (
event->key()) {
304 KUrlNavigatorButtonBase::keyPressEvent(event);
308void KUrlNavigatorButton::dropEvent(
QDropEvent *event)
310 if (
event->mimeData()->hasUrls()) {
311 setDisplayHintEnabled(DraggedHint,
true);
313 Q_EMIT urlsDroppedOnNavButton(m_url, event);
315 setDisplayHintEnabled(DraggedHint,
false);
322 if (
event->mimeData()->hasUrls()) {
323 setDisplayHintEnabled(DraggedHint,
true);
324 event->acceptProposedAction();
332 QRect rect =
event->answerRect();
333 if (isAboveIcon(rect.
center().
x())) {
334 m_hoverOverIcon =
true;
337 if (m_subDirsMenu ==
nullptr) {
339 }
else if (m_subDirsMenu->parent() !=
this) {
340 m_subDirsMenu->close();
341 m_subDirsMenu->deleteLater();
342 m_subDirsMenu =
nullptr;
347 if (m_openSubDirsTimer->isActive()) {
348 cancelSubDirsRequest();
351 m_subDirsMenu->deleteLater();
352 m_subDirsMenu =
nullptr;
354 m_hoverOverIcon =
false;
361 KUrlNavigatorButtonBase::dragLeaveEvent(event);
363 m_hoverOverIcon =
false;
364 setDisplayHintEnabled(DraggedHint,
false);
368void KUrlNavigatorButton::mousePressEvent(
QMouseEvent *event)
374 KUrlNavigatorButtonBase::mousePressEvent(event);
377void KUrlNavigatorButton::mouseReleaseEvent(
QMouseEvent *event)
382 Q_EMIT navigatorButtonActivated(m_url,
event->button(),
event->modifiers());
383 cancelSubDirsRequest();
385 KUrlNavigatorButtonBase::mouseReleaseEvent(event);
388void KUrlNavigatorButton::mouseMoveEvent(
QMouseEvent *event)
390 KUrlNavigatorButtonBase::mouseMoveEvent(event);
392 const bool hoverOverIcon = isAboveIcon(qRound(
event->position().x()));
393 if (hoverOverIcon != m_hoverOverIcon) {
394 m_hoverOverIcon = hoverOverIcon;
399void KUrlNavigatorButton::wheelEvent(
QWheelEvent *event)
401 if (
event->angleDelta().y() != 0) {
402 m_wheelSteps =
event->angleDelta().y() / 120;
403 m_replaceButton =
true;
407 KUrlNavigatorButtonBase::wheelEvent(event);
410void KUrlNavigatorButton::requestSubDirs()
412 if (!m_openSubDirsTimer->isActive() && (m_subDirsJob ==
nullptr)) {
413 m_openSubDirsTimer->start();
417void KUrlNavigatorButton::startSubDirsJob()
419 if (m_subDirsJob !=
nullptr) {
424 const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(parent());
425 Q_ASSERT(urlNavigator);
432 if (m_replaceButton) {
441 Q_ASSERT(job == m_subDirsJob);
460 const int result = action->
data().
toInt();
462 url.
setPath(Utils::concatPaths(url.
path(), m_subDirs.
at(result).name));
463 Q_EMIT urlsDroppedOnNavButton(url, event);
468 const int result = action->
data().
toInt();
470 url.
setPath(Utils::concatPaths(url.
path(), m_subDirs.
at(result).name));
474void KUrlNavigatorButton::statFinished(
KJob *job)
478 if (m_pendingTextChange) {
479 m_pendingTextChange =
false;
483 name = m_url.fileName();
487 Q_EMIT finishedTextResolving();
499struct FolderNameNaturalLessThan {
500 FolderNameNaturalLessThan(
bool sortHiddenLast)
501 : m_sortHiddenLast(sortHiddenLast)
504 m_collator.setNumericMode(
true);
507 bool operator()(
const KUrlNavigatorButton::SubDirInfo &a,
const KUrlNavigatorButton::SubDirInfo &b)
509 if (m_sortHiddenLast) {
510 const bool isHiddenA = a.name.startsWith(QLatin1Char(
'.'));
511 const bool isHiddenB = b.name.startsWith(QLatin1Char(
'.'));
512 if (isHiddenA && !isHiddenB) {
515 if (!isHiddenA && isHiddenB) {
519 return m_collator.compare(a.name, b.name) < 0;
523 QCollator m_collator;
524 bool m_sortHiddenLast;
527void KUrlNavigatorButton::openSubDirsMenu(
KJob *job)
529 Q_ASSERT(job == m_subDirsJob);
530 m_subDirsJob =
nullptr;
532 if (job->
error() || m_subDirs.empty()) {
537 const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(parent());
538 Q_ASSERT(urlNavigator);
540 std::sort(m_subDirs.begin(), m_subDirs.end(), less);
541 setDisplayHintEnabled(PopupActiveHint,
true);
544 if (m_subDirsMenu !=
nullptr) {
545 m_subDirsMenu->close();
546 m_subDirsMenu->deleteLater();
547 m_subDirsMenu =
nullptr;
550 m_subDirsMenu =
new KUrlNavigatorMenu(
this);
551 initMenu(m_subDirsMenu, 0);
554 const int popupX = !leftToRight ? width() - iconWidth() : 0;
555 const QPoint popupPos = parentWidget()->mapToGlobal(geometry().bottomLeft() +
QPoint(popupX, 0));
559 m_subDirsMenu->exec(popupPos);
568 delete m_subDirsMenu;
569 m_subDirsMenu =
nullptr;
571 setDisplayHintEnabled(PopupActiveHint,
false);
574void KUrlNavigatorButton::replaceButton(
KJob *job)
576 Q_ASSERT(job == m_subDirsJob);
577 m_subDirsJob =
nullptr;
578 m_replaceButton =
false;
580 if (job->
error() || m_subDirs.empty()) {
584 const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(parent());
585 Q_ASSERT(urlNavigator);
587 std::sort(m_subDirs.begin(), m_subDirs.end(), less);
590 const QString currentDir = m_url.fileName();
591 int currentIndex = 0;
592 const int subDirsCount = m_subDirs.
size();
593 while (currentIndex < subDirsCount) {
594 if (m_subDirs[currentIndex].name == currentDir) {
602 int targetIndex = currentIndex - m_wheelSteps;
603 if (targetIndex < 0) {
605 }
else if (targetIndex >= subDirsCount) {
606 targetIndex = subDirsCount - 1;
610 url.
setPath(Utils::concatPaths(url.
path(), m_subDirs[targetIndex].name));
616void KUrlNavigatorButton::cancelSubDirsRequest()
618 m_openSubDirsTimer->stop();
619 if (m_subDirsJob !=
nullptr) {
620 m_subDirsJob->kill();
621 m_subDirsJob =
nullptr;
625QString KUrlNavigatorButton::plainText()
const
630 const int sourceLength = source.
length();
633 dest.
resize(sourceLength);
637 while (sourceIndex < sourceLength) {
640 if (sourceIndex >= sourceLength) {
644 dest[destIndex] = source.
at(sourceIndex);
654int KUrlNavigatorButton::iconWidth()
const
656 return iconSize().width() * devicePixelRatioF();
659bool KUrlNavigatorButton::isAboveIcon(
int x)
const
662 return !leftToRight ? (x >= width() - iconWidth() - m_padding) : (x < iconWidth() + m_padding);
665bool KUrlNavigatorButton::isTextClipped()
const
668 int availableWidth = width() - m_padding;
669 if (!m_subDir.isEmpty()) {
670 availableWidth -= iconWidth();
673 QFont adjustedFont(font());
674 adjustedFont.setBold(m_subDir.isEmpty());
678void KUrlNavigatorButton::updateMinimumWidth()
680 const int oldMinWidth = minimumWidth();
682 int minWidth = sizeHint().width();
685 }
else if (minWidth > 150) {
689 if (oldMinWidth != minWidth) {
690 setMinimumWidth(minWidth);
694void KUrlNavigatorButton::initMenu(KUrlNavigatorMenu *menu,
int startIndex)
696 connect(menu, &KUrlNavigatorMenu::mouseButtonClicked,
this, &KUrlNavigatorButton::slotMenuActionClicked);
697 connect(menu, &KUrlNavigatorMenu::urlsDropped,
this, &KUrlNavigatorButton::slotUrlsDropped);
706 const int maxIndex = startIndex + 30;
707 const int subDirsSize = m_subDirs.size();
708 const int lastIndex = std::min(subDirsSize - 1, maxIndex);
709 for (
int i = startIndex; i <= lastIndex; ++i) {
710 const auto &[subDirName, subDirDisplayName] = m_subDirs[i];
714 if (m_subDir == subDirName) {
720 menu->addAction(action);
722 if (subDirsSize > maxIndex) {
724 menu->addSeparator();
725 KUrlNavigatorMenu *subDirsMenu =
new KUrlNavigatorMenu(menu);
726 subDirsMenu->setTitle(
i18nc(
"@action:inmenu",
"More"));
727 initMenu(subDirsMenu, maxIndex);
728 menu->addMenu(subDirsMenu);
734#include "moc_kurlnavigatorbutton_p.cpp"
The base class for all jobs.
@ IncludeHidden
Include hidden files in the listing.
void entries(KIO::Job *job, const KIO::UDSEntryList &list)
This signal emits the entry found by the job while listing.
A KIO job that retrieves information about a file or directory.
Universal Directory Service.
QString stringValue(uint field) const
@ UDS_DISPLAY_NAME
If set, contains the label to display instead of the 'real name' in UDS_NAME.
@ UDS_NAME
Filename - as displayed in directory listings etc.
@ UDS_ICON_NAME
Name of the icon, that should be used for displaying.
Widget that allows to navigate through the paths of an URL.
bool showHiddenFolders() const
Returns whether to show hidden folders in the subdirectories popup.
bool sortHiddenFoldersLast() const
Returns whether to sort hidden folders in the subdirectories popup last.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
void update(Part *part, const QByteArray &data, qint64 dataSize)
KIOCORE_EXPORT QString iconNameForUrl(const QUrl &url)
Return the icon name for a URL.
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
Find all details for one file or directory.
KIOCORE_EXPORT ListJob * listDir(const QUrl &url, JobFlags flags=DefaultFlags, ListJob::ListFlags listFlags=ListJob::ListFlag::IncludeHidden)
List the contents of url, which is assumed to be a directory.
KIOCORE_EXPORT QUrl upUrl(const QUrl &url)
This function is useful to implement the "Up" button in a file manager for example.
QList< UDSEntry > UDSEntryList
A directory listing is a list of UDSEntry instances.
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
QString name(StandardAction id)
KCOREADDONS_EXPORT QString csqueeze(const QString &str, int maxlen=40)
QVariant data() const const
void setData(const QVariant &data)
QSize size(int flags, const QString &text, int tabStops, int *tabArray) const const
QIcon fromTheme(const QString &name)
void setBrush(const QBrush &brush)
QPoint center() const const
QRect marginsRemoved(const QMargins &margins) const const
bool contains(const QSet< T > &other) const const
const QChar at(qsizetype position) const const
bool isEmpty() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
void resize(qsizetype newSize, QChar fillChar)
qsizetype size() const const
void initFrom(const QWidget *widget)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString path(ComponentFormattingOptions options) const const
void setPath(const QString &path, ParsingMode mode)
int toInt(bool *ok) const const