1 | /* |
2 | SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at> |
3 | SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | |
8 | #include "kurlnavigatorbutton_p.h" |
9 | |
10 | #include "../utils_p.h" |
11 | #include "kurlnavigator.h" |
12 | #include "kurlnavigatormenu_p.h" |
13 | #include <kio/listjob.h> |
14 | #include <kio/statjob.h> |
15 | |
16 | #include <KLocalizedString> |
17 | #include <KStringHandler> |
18 | |
19 | #include <QCollator> |
20 | #include <QKeyEvent> |
21 | #include <QMimeData> |
22 | #include <QPainter> |
23 | #include <QStyleOption> |
24 | #include <QTimer> |
25 | |
26 | namespace KDEPrivate |
27 | { |
28 | QPointer<KUrlNavigatorMenu> KUrlNavigatorButton::; |
29 | |
30 | KUrlNavigatorButton::KUrlNavigatorButton(const QUrl &url, KUrlNavigator *parent) |
31 | : KUrlNavigatorButtonBase(parent) |
32 | , m_hoverOverArrow(false) |
33 | , m_hoverOverButton(false) |
34 | , m_pendingTextChange(false) |
35 | , m_replaceButton(false) |
36 | , m_showMnemonic(false) |
37 | , m_drawSeparator(true) |
38 | , m_wheelSteps(0) |
39 | , m_url(url) |
40 | , m_subDir() |
41 | , m_openSubDirsTimer(nullptr) |
42 | , m_subDirsJob(nullptr) |
43 | , m_padding(5) |
44 | { |
45 | setAcceptDrops(true); |
46 | setUrl(url); |
47 | setMouseTracking(true); |
48 | |
49 | m_openSubDirsTimer = new QTimer(this); |
50 | m_openSubDirsTimer->setSingleShot(true); |
51 | m_openSubDirsTimer->setInterval(300); |
52 | connect(sender: m_openSubDirsTimer, signal: &QTimer::timeout, context: this, slot: &KUrlNavigatorButton::startSubDirsJob); |
53 | |
54 | connect(sender: this, signal: &QAbstractButton::pressed, context: this, slot: &KUrlNavigatorButton::requestSubDirs); |
55 | } |
56 | |
57 | KUrlNavigatorButton::~KUrlNavigatorButton() |
58 | { |
59 | } |
60 | |
61 | void KUrlNavigatorButton::setUrl(const QUrl &url) |
62 | { |
63 | m_url = url; |
64 | |
65 | // Doing a text-resolving with KIO::stat() for all non-local |
66 | // URLs leads to problems for protocols where a limit is given for |
67 | // the number of parallel connections. A black-list |
68 | // is given where KIO::stat() should not be used: |
69 | static const QSet<QString> protocolBlacklist = QSet<QString>{ |
70 | QStringLiteral("nfs" ), |
71 | QStringLiteral("fish" ), |
72 | QStringLiteral("ftp" ), |
73 | QStringLiteral("sftp" ), |
74 | QStringLiteral("smb" ), |
75 | QStringLiteral("webdav" ), |
76 | QStringLiteral("mtp" ), |
77 | }; |
78 | |
79 | const bool startTextResolving = m_url.isValid() && !m_url.isLocalFile() && !protocolBlacklist.contains(value: m_url.scheme()); |
80 | |
81 | if (startTextResolving) { |
82 | m_pendingTextChange = true; |
83 | KIO::StatJob *job = KIO::stat(url: m_url, flags: KIO::HideProgressInfo); |
84 | connect(sender: job, signal: &KJob::result, context: this, slot: &KUrlNavigatorButton::statFinished); |
85 | Q_EMIT startedTextResolving(); |
86 | } else { |
87 | setText(m_url.fileName().replace(c: QLatin1Char('&'), after: QLatin1String("&&" ))); |
88 | } |
89 | setIcon(QIcon::fromTheme(name: KIO::iconNameForUrl(url))); |
90 | } |
91 | |
92 | QUrl KUrlNavigatorButton::url() const |
93 | { |
94 | return m_url; |
95 | } |
96 | |
97 | void KUrlNavigatorButton::setText(const QString &text) |
98 | { |
99 | QString adjustedText = text; |
100 | if (adjustedText.isEmpty()) { |
101 | adjustedText = m_url.scheme(); |
102 | } |
103 | // Assure that the button always consists of one line |
104 | adjustedText.remove(c: QLatin1Char('\n')); |
105 | |
106 | KUrlNavigatorButtonBase::setText(adjustedText); |
107 | updateMinimumWidth(); |
108 | |
109 | // Assure that statFinished() does not overwrite a text that has been |
110 | // set by a client of the URL navigator button |
111 | m_pendingTextChange = false; |
112 | } |
113 | |
114 | void KUrlNavigatorButton::setActiveSubDirectory(const QString &subDir) |
115 | { |
116 | m_subDir = subDir; |
117 | |
118 | // We use a different (bold) font on active, so the size hint changes |
119 | updateGeometry(); |
120 | update(); |
121 | } |
122 | |
123 | QString KUrlNavigatorButton::activeSubDirectory() const |
124 | { |
125 | return m_subDir; |
126 | } |
127 | |
128 | QSize KUrlNavigatorButton::sizeHint() const |
129 | { |
130 | QFont adjustedFont(font()); |
131 | adjustedFont.setBold(m_subDir.isEmpty()); |
132 | // preferred width is textWidth, iconWidth and padding combined |
133 | // add extra padding in end to make sure the space between divider and button is consistent |
134 | // the first padding is used between icon and text, second in the end of text |
135 | const int width = m_padding + textWidth() + arrowWidth() + m_padding; |
136 | return QSize(width, KUrlNavigatorButtonBase::sizeHint().height()); |
137 | } |
138 | |
139 | void KUrlNavigatorButton::setShowMnemonic(bool show) |
140 | { |
141 | if (m_showMnemonic != show) { |
142 | m_showMnemonic = show; |
143 | update(); |
144 | } |
145 | } |
146 | |
147 | bool KUrlNavigatorButton::showMnemonic() const |
148 | { |
149 | return m_showMnemonic; |
150 | } |
151 | |
152 | void KUrlNavigatorButton::setDrawSeparator(bool draw) |
153 | { |
154 | if (m_drawSeparator != draw) { |
155 | m_drawSeparator = draw; |
156 | update(); |
157 | } |
158 | } |
159 | |
160 | bool KUrlNavigatorButton::drawSeparator() const |
161 | { |
162 | return m_drawSeparator; |
163 | } |
164 | |
165 | void KUrlNavigatorButton::paintEvent(QPaintEvent *event) |
166 | { |
167 | Q_UNUSED(event); |
168 | |
169 | QPainter painter(this); |
170 | |
171 | QFont adjustedFont(font()); |
172 | adjustedFont.setBold(m_subDir.isEmpty()); |
173 | painter.setFont(adjustedFont); |
174 | |
175 | int buttonWidth = width(); |
176 | int arrowWidth = KUrlNavigatorButton::arrowWidth(); |
177 | |
178 | int preferredWidth = sizeHint().width(); |
179 | if (preferredWidth < minimumWidth()) { |
180 | preferredWidth = minimumWidth(); |
181 | } |
182 | if (buttonWidth > preferredWidth) { |
183 | buttonWidth = preferredWidth; |
184 | } |
185 | const int buttonHeight = height(); |
186 | const QColor fgColor = foregroundColor(); |
187 | const bool leftToRight = (layoutDirection() == Qt::LeftToRight); |
188 | |
189 | // Prepare sizes for icon |
190 | QRect textRect; |
191 | const int textRectWidth = buttonWidth - arrowWidth - m_padding; |
192 | if (leftToRight) { |
193 | textRect = QRect(m_padding, 0, textRectWidth, buttonHeight); |
194 | } else { |
195 | // If no separator is drawn, we can start writing text from 0 |
196 | textRect = QRect(m_drawSeparator ? arrowWidth : 0, 0, textRectWidth, buttonHeight); |
197 | } |
198 | |
199 | drawHoverBackground(painter: &painter); |
200 | |
201 | // Draw gradient overlay if text is clipped |
202 | painter.setPen(fgColor); |
203 | const bool clipped = isTextClipped(); |
204 | if (clipped) { |
205 | QColor bgColor = fgColor; |
206 | bgColor.setAlpha(0); |
207 | QLinearGradient gradient(textRect.topLeft(), textRect.topRight()); |
208 | if (leftToRight) { |
209 | gradient.setFinalStop(QPoint(gradient.finalStop().x() - m_padding, gradient.finalStop().y())); |
210 | gradient.setColorAt(pos: 0.8, color: fgColor); |
211 | gradient.setColorAt(pos: 1.0, color: bgColor); |
212 | } else { |
213 | gradient.setStart(QPoint(gradient.start().x() + m_padding, gradient.start().y())); |
214 | gradient.setColorAt(pos: 0.0, color: bgColor); |
215 | gradient.setColorAt(pos: 0.2, color: fgColor); |
216 | } |
217 | |
218 | QPen pen; |
219 | pen.setBrush(QBrush(gradient)); |
220 | painter.setPen(pen); |
221 | } |
222 | |
223 | // Draw folder name |
224 | int textFlags = Qt::AlignVCenter; |
225 | if (m_showMnemonic) { |
226 | textFlags |= Qt::TextShowMnemonic; |
227 | painter.drawText(r: textRect, flags: textFlags, text: text()); |
228 | } else { |
229 | painter.drawText(r: textRect, flags: textFlags, text: plainText()); |
230 | } |
231 | |
232 | // Draw separator arrow |
233 | if (m_drawSeparator) { |
234 | QStyleOption option; |
235 | option.initFrom(w: this); |
236 | option.palette = palette(); |
237 | option.palette.setColor(acr: QPalette::Text, acolor: fgColor); |
238 | option.palette.setColor(acr: QPalette::WindowText, acolor: fgColor); |
239 | option.palette.setColor(acr: QPalette::ButtonText, acolor: fgColor); |
240 | |
241 | if (leftToRight) { |
242 | option.rect = QRect(textRect.right(), 0, arrowWidth, buttonHeight); |
243 | } else { |
244 | // Separator is the first item in RtL mode |
245 | option.rect = QRect(0, 0, arrowWidth, buttonHeight); |
246 | } |
247 | |
248 | if (!m_hoverOverArrow) { |
249 | option.state = QStyle::State_None; |
250 | } |
251 | style()->drawPrimitive(pe: leftToRight ? QStyle::PE_IndicatorArrowRight : QStyle::PE_IndicatorArrowLeft, opt: &option, p: &painter, w: this); |
252 | } |
253 | } |
254 | |
255 | void KUrlNavigatorButton::enterEvent(QEnterEvent *event) |
256 | { |
257 | KUrlNavigatorButtonBase::enterEvent(event); |
258 | |
259 | // if the text is clipped due to a small window width, the text should |
260 | // be shown as tooltip |
261 | if (isTextClipped()) { |
262 | setToolTip(plainText()); |
263 | } |
264 | if (!m_hoverOverButton) { |
265 | m_hoverOverButton = true; |
266 | update(); |
267 | } |
268 | } |
269 | |
270 | void KUrlNavigatorButton::leaveEvent(QEvent *event) |
271 | { |
272 | KUrlNavigatorButtonBase::leaveEvent(event); |
273 | setToolTip(QString()); |
274 | |
275 | if (m_hoverOverArrow) { |
276 | m_hoverOverArrow = false; |
277 | update(); |
278 | } |
279 | if (m_hoverOverButton) { |
280 | m_hoverOverButton = false; |
281 | update(); |
282 | } |
283 | } |
284 | |
285 | void KUrlNavigatorButton::keyPressEvent(QKeyEvent *event) |
286 | { |
287 | switch (event->key()) { |
288 | case Qt::Key_Enter: |
289 | case Qt::Key_Return: |
290 | Q_EMIT navigatorButtonActivated(url: m_url, button: Qt::LeftButton, modifiers: event->modifiers()); |
291 | break; |
292 | case Qt::Key_Down: |
293 | case Qt::Key_Space: |
294 | startSubDirsJob(); |
295 | break; |
296 | default: |
297 | KUrlNavigatorButtonBase::keyPressEvent(event); |
298 | } |
299 | } |
300 | |
301 | void KUrlNavigatorButton::dropEvent(QDropEvent *event) |
302 | { |
303 | if (event->mimeData()->hasUrls()) { |
304 | setDisplayHintEnabled(hint: DraggedHint, enable: true); |
305 | |
306 | Q_EMIT urlsDroppedOnNavButton(destination: m_url, event); |
307 | |
308 | setDisplayHintEnabled(hint: DraggedHint, enable: false); |
309 | update(); |
310 | } |
311 | } |
312 | |
313 | void KUrlNavigatorButton::dragEnterEvent(QDragEnterEvent *event) |
314 | { |
315 | if (event->mimeData()->hasUrls()) { |
316 | setDisplayHintEnabled(hint: DraggedHint, enable: true); |
317 | event->acceptProposedAction(); |
318 | |
319 | update(); |
320 | } |
321 | } |
322 | |
323 | void KUrlNavigatorButton::dragMoveEvent(QDragMoveEvent *event) |
324 | { |
325 | QRect rect = event->answerRect(); |
326 | |
327 | if (isAboveSeparator(x: rect.center().x())) { |
328 | m_hoverOverArrow = true; |
329 | update(); |
330 | |
331 | if (m_subDirsMenu == nullptr) { |
332 | requestSubDirs(); |
333 | } else if (m_subDirsMenu->parent() != this) { |
334 | m_subDirsMenu->close(); |
335 | m_subDirsMenu->deleteLater(); |
336 | m_subDirsMenu = nullptr; |
337 | |
338 | requestSubDirs(); |
339 | } |
340 | } else { |
341 | if (m_openSubDirsTimer->isActive()) { |
342 | cancelSubDirsRequest(); |
343 | } |
344 | if (m_subDirsMenu) { |
345 | m_subDirsMenu->deleteLater(); |
346 | m_subDirsMenu = nullptr; |
347 | } |
348 | m_hoverOverArrow = false; |
349 | update(); |
350 | } |
351 | } |
352 | |
353 | void KUrlNavigatorButton::dragLeaveEvent(QDragLeaveEvent *event) |
354 | { |
355 | KUrlNavigatorButtonBase::dragLeaveEvent(event); |
356 | |
357 | m_hoverOverArrow = false; |
358 | setDisplayHintEnabled(hint: DraggedHint, enable: false); |
359 | update(); |
360 | } |
361 | |
362 | void KUrlNavigatorButton::mousePressEvent(QMouseEvent *event) |
363 | { |
364 | if (isAboveSeparator(x: qRound(d: event->position().x())) && (event->button() == Qt::LeftButton)) { |
365 | // the mouse is pressed above the folder button |
366 | startSubDirsJob(); |
367 | } |
368 | KUrlNavigatorButtonBase::mousePressEvent(e: event); |
369 | } |
370 | |
371 | void KUrlNavigatorButton::mouseReleaseEvent(QMouseEvent *event) |
372 | { |
373 | if (!isAboveSeparator(x: qRound(d: event->position().x())) || (event->button() != Qt::LeftButton)) { |
374 | // the mouse has been released above the text area and not |
375 | // above the folder button |
376 | Q_EMIT navigatorButtonActivated(url: m_url, button: event->button(), modifiers: event->modifiers()); |
377 | cancelSubDirsRequest(); |
378 | } |
379 | KUrlNavigatorButtonBase::mouseReleaseEvent(e: event); |
380 | } |
381 | |
382 | void KUrlNavigatorButton::mouseMoveEvent(QMouseEvent *event) |
383 | { |
384 | KUrlNavigatorButtonBase::mouseMoveEvent(event); |
385 | |
386 | const bool hoverOverIcon = isAboveSeparator(x: qRound(d: event->position().x())); |
387 | if (hoverOverIcon != m_hoverOverArrow) { |
388 | m_hoverOverArrow = hoverOverIcon; |
389 | update(); |
390 | } |
391 | } |
392 | |
393 | void KUrlNavigatorButton::wheelEvent(QWheelEvent *event) |
394 | { |
395 | if (event->angleDelta().y() != 0) { |
396 | m_wheelSteps = event->angleDelta().y() / 120; |
397 | m_replaceButton = true; |
398 | startSubDirsJob(); |
399 | } |
400 | |
401 | KUrlNavigatorButtonBase::wheelEvent(event); |
402 | } |
403 | |
404 | void KUrlNavigatorButton::requestSubDirs() |
405 | { |
406 | if (!m_openSubDirsTimer->isActive() && (m_subDirsJob == nullptr)) { |
407 | m_openSubDirsTimer->start(); |
408 | } |
409 | } |
410 | |
411 | void KUrlNavigatorButton::startSubDirsJob() |
412 | { |
413 | if (m_subDirsJob != nullptr) { |
414 | return; |
415 | } |
416 | |
417 | const QUrl url = m_replaceButton ? KIO::upUrl(url: m_url) : m_url; |
418 | const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(object: parent()); |
419 | Q_ASSERT(urlNavigator); |
420 | m_subDirsJob = |
421 | KIO::listDir(url, flags: KIO::HideProgressInfo, listFlags: urlNavigator->showHiddenFolders() ? KIO::ListJob::ListFlag::IncludeHidden : KIO::ListJob::ListFlags{}); |
422 | m_subDirs.clear(); // just to be ++safe |
423 | |
424 | connect(sender: m_subDirsJob, signal: &KIO::ListJob::entries, context: this, slot: &KUrlNavigatorButton::addEntriesToSubDirs); |
425 | |
426 | if (m_replaceButton) { |
427 | connect(sender: m_subDirsJob, signal: &KJob::result, context: this, slot: &KUrlNavigatorButton::replaceButton); |
428 | } else { |
429 | connect(sender: m_subDirsJob, signal: &KJob::result, context: this, slot: &KUrlNavigatorButton::openSubDirsMenu); |
430 | } |
431 | } |
432 | |
433 | void KUrlNavigatorButton::addEntriesToSubDirs(KIO::Job *job, const KIO::UDSEntryList &entries) |
434 | { |
435 | Q_ASSERT(job == m_subDirsJob); |
436 | Q_UNUSED(job); |
437 | |
438 | for (const KIO::UDSEntry &entry : entries) { |
439 | if (entry.isDir()) { |
440 | const QString name = entry.stringValue(field: KIO::UDSEntry::UDS_NAME); |
441 | QString displayName = entry.stringValue(field: KIO::UDSEntry::UDS_DISPLAY_NAME); |
442 | if (displayName.isEmpty()) { |
443 | displayName = name; |
444 | } |
445 | if (name != QLatin1String("." ) && name != QLatin1String(".." )) { |
446 | m_subDirs.push_back(x: {.name: name, .displayName: displayName}); |
447 | } |
448 | } |
449 | } |
450 | } |
451 | |
452 | void KUrlNavigatorButton::slotUrlsDropped(QAction *action, QDropEvent *event) |
453 | { |
454 | const int result = action->data().toInt(); |
455 | QUrl url(m_url); |
456 | url.setPath(path: Utils::concatPaths(path1: url.path(), path2: m_subDirs.at(n: result).name)); |
457 | Q_EMIT urlsDroppedOnNavButton(destination: url, event); |
458 | } |
459 | |
460 | void KUrlNavigatorButton::(QAction *action, Qt::MouseButton button) |
461 | { |
462 | const int result = action->data().toInt(); |
463 | QUrl url(m_url); |
464 | url.setPath(path: Utils::concatPaths(path1: url.path(), path2: m_subDirs.at(n: result).name)); |
465 | Q_EMIT navigatorButtonActivated(url, button, modifiers: Qt::NoModifier); |
466 | } |
467 | |
468 | void KUrlNavigatorButton::statFinished(KJob *job) |
469 | { |
470 | const KIO::UDSEntry entry = static_cast<KIO::StatJob *>(job)->statResult(); |
471 | |
472 | if (m_pendingTextChange) { |
473 | m_pendingTextChange = false; |
474 | |
475 | QString name = entry.stringValue(field: KIO::UDSEntry::UDS_DISPLAY_NAME); |
476 | if (name.isEmpty()) { |
477 | name = m_url.fileName(); |
478 | } |
479 | setText(name); |
480 | |
481 | Q_EMIT finishedTextResolving(); |
482 | } |
483 | |
484 | const QString iconName = entry.stringValue(field: KIO::UDSEntry::UDS_ICON_NAME); |
485 | if (!iconName.isEmpty()) { |
486 | setIcon(QIcon::fromTheme(name: iconName)); |
487 | } |
488 | } |
489 | |
490 | /* |
491 | * Helper struct for sorting folder names |
492 | */ |
493 | struct FolderNameNaturalLessThan { |
494 | FolderNameNaturalLessThan(bool sortHiddenLast) |
495 | : m_sortHiddenLast(sortHiddenLast) |
496 | { |
497 | m_collator.setCaseSensitivity(Qt::CaseInsensitive); |
498 | m_collator.setNumericMode(true); |
499 | } |
500 | |
501 | bool operator()(const KUrlNavigatorButton::SubDirInfo &a, const KUrlNavigatorButton::SubDirInfo &b) |
502 | { |
503 | if (m_sortHiddenLast) { |
504 | const bool isHiddenA = a.name.startsWith(c: QLatin1Char('.')); |
505 | const bool isHiddenB = b.name.startsWith(c: QLatin1Char('.')); |
506 | if (isHiddenA && !isHiddenB) { |
507 | return false; |
508 | } |
509 | if (!isHiddenA && isHiddenB) { |
510 | return true; |
511 | } |
512 | } |
513 | return m_collator.compare(s1: a.name, s2: b.name) < 0; |
514 | } |
515 | |
516 | private: |
517 | QCollator m_collator; |
518 | bool m_sortHiddenLast; |
519 | }; |
520 | |
521 | void KUrlNavigatorButton::(KJob *job) |
522 | { |
523 | Q_ASSERT(job == m_subDirsJob); |
524 | m_subDirsJob = nullptr; |
525 | |
526 | if (job->error() || m_subDirs.empty()) { |
527 | // clear listing |
528 | return; |
529 | } |
530 | |
531 | const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(object: parent()); |
532 | Q_ASSERT(urlNavigator); |
533 | FolderNameNaturalLessThan less(urlNavigator->showHiddenFolders() && urlNavigator->sortHiddenFoldersLast()); |
534 | std::sort(first: m_subDirs.begin(), last: m_subDirs.end(), comp: less); |
535 | setDisplayHintEnabled(hint: PopupActiveHint, enable: true); |
536 | update(); // ensure the button is drawn highlighted |
537 | |
538 | if (m_subDirsMenu != nullptr) { |
539 | m_subDirsMenu->close(); |
540 | m_subDirsMenu->deleteLater(); |
541 | m_subDirsMenu = nullptr; |
542 | } |
543 | |
544 | m_subDirsMenu = new KUrlNavigatorMenu(this); |
545 | initMenu(menu: m_subDirsMenu, startIndex: 0); |
546 | |
547 | const bool leftToRight = (layoutDirection() == Qt::LeftToRight); |
548 | const int = leftToRight ? width() - arrowWidth() : 0; |
549 | const QPoint = parentWidget()->mapToGlobal(geometry().bottomLeft() + QPoint(popupX, 0)); |
550 | |
551 | QPointer<QObject> guard(this); |
552 | |
553 | m_subDirsMenu->exec(pos: popupPos); |
554 | |
555 | // If 'this' has been deleted in the menu's nested event loop, we have to return |
556 | // immediately because any access to a member variable might cause a crash. |
557 | if (!guard) { |
558 | return; |
559 | } |
560 | |
561 | m_subDirs.clear(); |
562 | delete m_subDirsMenu; |
563 | m_subDirsMenu = nullptr; |
564 | |
565 | setDisplayHintEnabled(hint: PopupActiveHint, enable: false); |
566 | } |
567 | |
568 | void KUrlNavigatorButton::replaceButton(KJob *job) |
569 | { |
570 | Q_ASSERT(job == m_subDirsJob); |
571 | m_subDirsJob = nullptr; |
572 | m_replaceButton = false; |
573 | |
574 | if (job->error() || m_subDirs.empty()) { |
575 | return; |
576 | } |
577 | |
578 | const KUrlNavigator *urlNavigator = qobject_cast<KUrlNavigator *>(object: parent()); |
579 | Q_ASSERT(urlNavigator); |
580 | FolderNameNaturalLessThan less(urlNavigator->showHiddenFolders() && urlNavigator->sortHiddenFoldersLast()); |
581 | std::sort(first: m_subDirs.begin(), last: m_subDirs.end(), comp: less); |
582 | |
583 | // Get index of the directory that is shown currently in the button |
584 | const QString currentDir = m_url.fileName(); |
585 | int currentIndex = 0; |
586 | const int subDirsCount = m_subDirs.size(); |
587 | while (currentIndex < subDirsCount) { |
588 | if (m_subDirs[currentIndex].name == currentDir) { |
589 | break; |
590 | } |
591 | ++currentIndex; |
592 | } |
593 | |
594 | // Adjust the index by respecting the wheel steps and |
595 | // trigger a replacing of the button content |
596 | int targetIndex = currentIndex - m_wheelSteps; |
597 | if (targetIndex < 0) { |
598 | targetIndex = 0; |
599 | } else if (targetIndex >= subDirsCount) { |
600 | targetIndex = subDirsCount - 1; |
601 | } |
602 | |
603 | QUrl url(KIO::upUrl(url: m_url)); |
604 | url.setPath(path: Utils::concatPaths(path1: url.path(), path2: m_subDirs[targetIndex].name)); |
605 | Q_EMIT navigatorButtonActivated(url, button: Qt::LeftButton, modifiers: Qt::NoModifier); |
606 | |
607 | m_subDirs.clear(); |
608 | } |
609 | |
610 | void KUrlNavigatorButton::cancelSubDirsRequest() |
611 | { |
612 | m_openSubDirsTimer->stop(); |
613 | if (m_subDirsJob != nullptr) { |
614 | m_subDirsJob->kill(); |
615 | m_subDirsJob = nullptr; |
616 | } |
617 | } |
618 | |
619 | QString KUrlNavigatorButton::plainText() const |
620 | { |
621 | // Replace all "&&" by '&' and remove all single |
622 | // '&' characters |
623 | const QString source = text(); |
624 | const int sourceLength = source.length(); |
625 | |
626 | QString dest; |
627 | dest.resize(size: sourceLength); |
628 | |
629 | int sourceIndex = 0; |
630 | int destIndex = 0; |
631 | while (sourceIndex < sourceLength) { |
632 | if (source.at(i: sourceIndex) == QLatin1Char('&')) { |
633 | ++sourceIndex; |
634 | if (sourceIndex >= sourceLength) { |
635 | break; |
636 | } |
637 | } |
638 | dest[destIndex] = source.at(i: sourceIndex); |
639 | ++sourceIndex; |
640 | ++destIndex; |
641 | } |
642 | |
643 | dest.resize(size: destIndex); |
644 | |
645 | return dest; |
646 | } |
647 | |
648 | int KUrlNavigatorButton::arrowWidth() const |
649 | { |
650 | // if there isn't arrow then return 0 |
651 | int width = 0; |
652 | if (!m_subDir.isEmpty()) { |
653 | width = height() / 2; |
654 | if (width < 4) { |
655 | width = 4; |
656 | } |
657 | } |
658 | |
659 | return width; |
660 | } |
661 | |
662 | int KUrlNavigatorButton::textWidth() const |
663 | { |
664 | QFont adjustedFont(font()); |
665 | adjustedFont.setBold(m_subDir.isEmpty()); |
666 | return QFontMetrics(adjustedFont).size(flags: Qt::TextSingleLine, str: plainText()).width(); |
667 | } |
668 | |
669 | bool KUrlNavigatorButton::isAboveSeparator(int x) const |
670 | { |
671 | const bool leftToRight = (layoutDirection() == Qt::LeftToRight); |
672 | return leftToRight ? (x >= width() - arrowWidth()) : (x < arrowWidth() + m_padding); |
673 | } |
674 | |
675 | bool KUrlNavigatorButton::isTextClipped() const |
676 | { |
677 | // Ignore padding when resizing, so text doesnt go under it |
678 | int availableWidth = width() - arrowWidth() - m_padding; |
679 | |
680 | return textWidth() >= availableWidth; |
681 | } |
682 | |
683 | void KUrlNavigatorButton::updateMinimumWidth() |
684 | { |
685 | const int oldMinWidth = minimumWidth(); |
686 | |
687 | int minWidth = sizeHint().width(); |
688 | if (minWidth < 10) { |
689 | minWidth = 10; |
690 | } else if (minWidth > 150) { |
691 | // don't let an overlong path name waste all the URL navigator space |
692 | minWidth = 150; |
693 | } |
694 | if (oldMinWidth != minWidth) { |
695 | setMinimumWidth(minWidth); |
696 | } |
697 | } |
698 | |
699 | void KUrlNavigatorButton::(KUrlNavigatorMenu *, int startIndex) |
700 | { |
701 | connect(sender: menu, signal: &KUrlNavigatorMenu::mouseButtonClicked, context: this, slot: &KUrlNavigatorButton::slotMenuActionClicked); |
702 | connect(sender: menu, signal: &KUrlNavigatorMenu::urlsDropped, context: this, slot: &KUrlNavigatorButton::slotUrlsDropped); |
703 | |
704 | // So that triggering a menu item with the keyboard works |
705 | connect(sender: menu, signal: &QMenu::triggered, context: this, slot: [this](QAction *act) { |
706 | slotMenuActionClicked(action: act, button: Qt::LeftButton); |
707 | }); |
708 | |
709 | menu->setLayoutDirection(Qt::LeftToRight); |
710 | |
711 | const int maxIndex = startIndex + 30; // Don't show more than 30 items in a menu |
712 | const int = m_subDirs.size(); |
713 | const int lastIndex = std::min(a: subDirsSize - 1, b: maxIndex); |
714 | for (int i = startIndex; i <= lastIndex; ++i) { |
715 | const auto &[subDirName, subDirDisplayName] = m_subDirs[i]; |
716 | QString text = KStringHandler::csqueeze(str: subDirDisplayName, maxlen: 60); |
717 | text.replace(c: QLatin1Char('&'), after: QLatin1String("&&" )); |
718 | QAction *action = new QAction(text, this); |
719 | if (m_subDir == subDirName) { |
720 | QFont font(action->font()); |
721 | font.setBold(true); |
722 | action->setFont(font); |
723 | } |
724 | action->setData(i); |
725 | menu->addAction(action); |
726 | } |
727 | if (subDirsSize > maxIndex) { |
728 | // If too much items are shown, move them into a sub menu |
729 | menu->addSeparator(); |
730 | KUrlNavigatorMenu * = new KUrlNavigatorMenu(menu); |
731 | subDirsMenu->setTitle(i18nc("@action:inmenu" , "More" )); |
732 | initMenu(menu: subDirsMenu, startIndex: maxIndex); |
733 | menu->addMenu(menu: subDirsMenu); |
734 | } |
735 | } |
736 | |
737 | } // namespace KDEPrivate |
738 | |
739 | #include "moc_kurlnavigatorbutton_p.cpp" |
740 | |