File indexing completed on 2024-04-21 16:30:14
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2020 Felix Ernst <felixernst@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "dolphinnavigatorswidgetaction.h" 0009 0010 #include "trash/dolphintrash.h" 0011 0012 #include <KLocalizedString> 0013 #include <KNotificationJobUiDelegate> 0014 #include <KService> 0015 0016 #include <KIO/ApplicationLauncherJob> 0017 0018 #include <QApplication> 0019 #include <QHBoxLayout> 0020 #include <QPushButton> 0021 #include <QToolBar> 0022 0023 #include <limits> 0024 0025 DolphinNavigatorsWidgetAction::DolphinNavigatorsWidgetAction(QWidget *parent) 0026 : QWidgetAction{parent} 0027 , m_splitter{new QSplitter(Qt::Horizontal)} 0028 , m_adjustSpacingTimer{new QTimer(this)} 0029 , m_viewGeometriesHelper{m_splitter.get(), this} 0030 { 0031 updateText(); 0032 setIcon(QIcon::fromTheme(QStringLiteral("dialog-scripts"))); 0033 0034 m_splitter->setChildrenCollapsible(false); 0035 0036 m_splitter->addWidget(createNavigatorWidget(Primary)); 0037 0038 m_adjustSpacingTimer->setInterval(100); 0039 m_adjustSpacingTimer->setSingleShot(true); 0040 connect(m_adjustSpacingTimer.get(), &QTimer::timeout, this, &DolphinNavigatorsWidgetAction::adjustSpacing); 0041 } 0042 0043 void DolphinNavigatorsWidgetAction::adjustSpacing() 0044 { 0045 m_previousWindowWidth = qobject_cast<QWidget *>(parent())->window()->width(); 0046 auto viewGeometries = m_viewGeometriesHelper.viewGeometries(); 0047 const int widthOfSplitterPrimary = viewGeometries.globalXOfPrimary + viewGeometries.widthOfPrimary - viewGeometries.globalXOfNavigatorsWidget; 0048 const QList<int> splitterSizes = {widthOfSplitterPrimary, m_splitter->width() - widthOfSplitterPrimary - m_splitter->handleWidth()}; 0049 m_splitter->setSizes(splitterSizes); 0050 0051 // primary side of m_splitter 0052 int leadingSpacing = viewGeometries.globalXOfPrimary - viewGeometries.globalXOfNavigatorsWidget; 0053 if (leadingSpacing < 0) { 0054 leadingSpacing = 0; 0055 } 0056 int trailingSpacing = (viewGeometries.globalXOfNavigatorsWidget + m_splitter->width()) - (viewGeometries.globalXOfPrimary + viewGeometries.widthOfPrimary); 0057 if (trailingSpacing < 0 || emptyTrashButton(Primary)->isVisible() || networkFolderButton(Primary)->isVisible()) { 0058 trailingSpacing = 0; 0059 } 0060 const int widthLeftForUrlNavigator = m_splitter->widget(0)->width() - leadingSpacing - trailingSpacing; 0061 const int widthNeededForUrlNavigator = primaryUrlNavigator()->sizeHint().width() - widthLeftForUrlNavigator; 0062 if (widthNeededForUrlNavigator > 0) { 0063 trailingSpacing -= widthNeededForUrlNavigator; 0064 if (trailingSpacing < 0) { 0065 leadingSpacing += trailingSpacing; 0066 trailingSpacing = 0; 0067 } 0068 if (leadingSpacing < 0) { 0069 leadingSpacing = 0; 0070 } 0071 } 0072 spacing(Primary, Leading)->setMinimumWidth(leadingSpacing); 0073 spacing(Primary, Trailing)->setFixedWidth(trailingSpacing); 0074 0075 // secondary side of m_splitter 0076 if (viewGeometries.globalXOfSecondary == INT_MIN) { 0077 Q_ASSERT(viewGeometries.widthOfSecondary == INT_MIN); 0078 return; 0079 } 0080 spacing(Primary, Trailing)->setFixedWidth(0); 0081 0082 trailingSpacing = (viewGeometries.globalXOfNavigatorsWidget + m_splitter->width()) - (viewGeometries.globalXOfSecondary + viewGeometries.widthOfSecondary); 0083 if (trailingSpacing < 0 || emptyTrashButton(Secondary)->isVisible() || networkFolderButton(Secondary)->isVisible()) { 0084 trailingSpacing = 0; 0085 } else { 0086 const int widthLeftForUrlNavigator2 = m_splitter->widget(1)->width() - trailingSpacing; 0087 const int widthNeededForUrlNavigator2 = secondaryUrlNavigator()->sizeHint().width() - widthLeftForUrlNavigator2; 0088 if (widthNeededForUrlNavigator2 > 0) { 0089 trailingSpacing -= widthNeededForUrlNavigator2; 0090 if (trailingSpacing < 0) { 0091 trailingSpacing = 0; 0092 } 0093 } 0094 } 0095 spacing(Secondary, Trailing)->setMinimumWidth(trailingSpacing); 0096 } 0097 0098 void DolphinNavigatorsWidgetAction::createSecondaryUrlNavigator() 0099 { 0100 Q_ASSERT(m_splitter->count() == 1); 0101 m_splitter->addWidget(createNavigatorWidget(Secondary)); 0102 Q_ASSERT(m_splitter->count() == 2); 0103 updateText(); 0104 } 0105 0106 void DolphinNavigatorsWidgetAction::followViewContainersGeometry(QWidget *primaryViewContainer, QWidget *secondaryViewContainer) 0107 { 0108 m_viewGeometriesHelper.setViewContainers(primaryViewContainer, secondaryViewContainer); 0109 adjustSpacing(); 0110 } 0111 0112 bool DolphinNavigatorsWidgetAction::isInToolbar() const 0113 { 0114 return qobject_cast<QToolBar *>(m_splitter->parentWidget()); 0115 } 0116 0117 DolphinUrlNavigator *DolphinNavigatorsWidgetAction::primaryUrlNavigator() const 0118 { 0119 Q_ASSERT(m_splitter); 0120 return m_splitter->widget(0)->findChild<DolphinUrlNavigator *>(); 0121 } 0122 0123 DolphinUrlNavigator *DolphinNavigatorsWidgetAction::secondaryUrlNavigator() const 0124 { 0125 Q_ASSERT(m_splitter); 0126 if (m_splitter->count() < 2) { 0127 return nullptr; 0128 } 0129 return m_splitter->widget(1)->findChild<DolphinUrlNavigator *>(); 0130 } 0131 0132 void DolphinNavigatorsWidgetAction::setSecondaryNavigatorVisible(bool visible) 0133 { 0134 if (visible) { 0135 Q_ASSERT(m_splitter->count() == 2); 0136 m_splitter->widget(1)->setVisible(true); 0137 } else if (m_splitter->count() > 1) { 0138 m_splitter->widget(1)->setVisible(false); 0139 // Fix an unlikely event of wrong trash button visibility. 0140 emptyTrashButton(Secondary)->setVisible(false); 0141 } 0142 updateText(); 0143 } 0144 0145 QWidget *DolphinNavigatorsWidgetAction::createWidget(QWidget *parent) 0146 { 0147 QWidget *oldParent = m_splitter->parentWidget(); 0148 if (oldParent && oldParent->layout()) { 0149 oldParent->layout()->removeWidget(m_splitter.get()); 0150 QGridLayout *layout = qobject_cast<QGridLayout *>(oldParent->layout()); 0151 if (qobject_cast<QToolBar *>(parent) && layout) { 0152 // in DolphinTabPage::insertNavigatorsWidget the minimumHeight of this row was 0153 // set to fit the m_splitter. Since we are now removing it again, the 0154 // minimumHeight can be reset to 0. 0155 layout->setRowMinimumHeight(0, 0); 0156 } 0157 } 0158 m_splitter->setParent(parent); 0159 return m_splitter.get(); 0160 } 0161 0162 void DolphinNavigatorsWidgetAction::deleteWidget(QWidget *widget) 0163 { 0164 Q_UNUSED(widget) 0165 m_splitter->setParent(nullptr); 0166 } 0167 0168 QWidget *DolphinNavigatorsWidgetAction::createNavigatorWidget(Side side) const 0169 { 0170 auto navigatorWidget = new QWidget(m_splitter.get()); 0171 auto layout = new QHBoxLayout{navigatorWidget}; 0172 layout->setSpacing(0); 0173 layout->setContentsMargins(0, 0, 0, 0); 0174 if (side == Primary) { 0175 auto leadingSpacing = new QWidget{navigatorWidget}; 0176 layout->addWidget(leadingSpacing); 0177 } 0178 auto urlNavigator = new DolphinUrlNavigator(navigatorWidget); 0179 layout->addWidget(urlNavigator); 0180 0181 auto emptyTrashButton = newEmptyTrashButton(urlNavigator, navigatorWidget); 0182 layout->addWidget(emptyTrashButton); 0183 0184 auto networkFolderButton = newNetworkFolderButton(urlNavigator, navigatorWidget); 0185 layout->addWidget(networkFolderButton); 0186 0187 connect( 0188 urlNavigator, 0189 &KUrlNavigator::urlChanged, 0190 this, 0191 [urlNavigator, this]() { 0192 // Update URL navigator to show a server URL entry placeholder text if we 0193 // just loaded the remote:/ page, to make it easier for users to figure out 0194 // that they can enter arbitrary remote URLs. See bug 414670 0195 if (urlNavigator->locationUrl().scheme() == QLatin1String("remote")) { 0196 if (!urlNavigator->isUrlEditable()) { 0197 urlNavigator->setUrlEditable(true); 0198 } 0199 urlNavigator->clearText(); 0200 urlNavigator->setPlaceholderText(i18n("Enter server URL (e.g. smb://[ip address])")); 0201 } else { 0202 urlNavigator->setPlaceholderText(QString()); 0203 } 0204 0205 // We have to wait for DolphinUrlNavigator::sizeHint() to update which 0206 // happens a little bit later than when urlChanged is emitted. 0207 this->m_adjustSpacingTimer->start(); 0208 }, 0209 Qt::QueuedConnection); 0210 0211 auto trailingSpacing = new QWidget{navigatorWidget}; 0212 layout->addWidget(trailingSpacing); 0213 return navigatorWidget; 0214 } 0215 0216 QPushButton *DolphinNavigatorsWidgetAction::emptyTrashButton(DolphinNavigatorsWidgetAction::Side side) 0217 { 0218 int sideIndex = (side == Primary ? 0 : 1); 0219 if (side == Primary) { 0220 return static_cast<QPushButton *>(m_splitter->widget(sideIndex)->layout()->itemAt(2)->widget()); 0221 } 0222 return static_cast<QPushButton *>(m_splitter->widget(sideIndex)->layout()->itemAt(1)->widget()); 0223 } 0224 0225 QPushButton *DolphinNavigatorsWidgetAction::newEmptyTrashButton(const DolphinUrlNavigator *urlNavigator, QWidget *parent) const 0226 { 0227 auto emptyTrashButton = new QPushButton(QIcon::fromTheme(QStringLiteral("user-trash")), i18nc("@action:button", "Empty Trash"), parent); 0228 emptyTrashButton->setToolTip(i18n("Empties Trash to create free space")); 0229 0230 emptyTrashButton->setFlat(true); 0231 connect(emptyTrashButton, &QPushButton::clicked, this, [parent]() { 0232 Trash::empty(parent); 0233 }); 0234 connect(&Trash::instance(), &Trash::emptinessChanged, emptyTrashButton, &QPushButton::setDisabled); 0235 emptyTrashButton->hide(); 0236 connect(urlNavigator, &KUrlNavigator::urlChanged, this, [emptyTrashButton, urlNavigator]() { 0237 emptyTrashButton->setVisible(urlNavigator->locationUrl().scheme() == QLatin1String("trash")); 0238 }); 0239 emptyTrashButton->setDisabled(Trash::isEmpty()); 0240 return emptyTrashButton; 0241 } 0242 0243 QPushButton *DolphinNavigatorsWidgetAction::networkFolderButton(DolphinNavigatorsWidgetAction::Side side) 0244 { 0245 int sideIndex = (side == Primary ? 0 : 1); 0246 if (side == Primary) { 0247 return static_cast<QPushButton *>(m_splitter->widget(sideIndex)->layout()->itemAt(3)->widget()); 0248 } 0249 return static_cast<QPushButton *>(m_splitter->widget(sideIndex)->layout()->itemAt(2)->widget()); 0250 } 0251 0252 QPushButton *DolphinNavigatorsWidgetAction::newNetworkFolderButton(const DolphinUrlNavigator *urlNavigator, QWidget *parent) const 0253 { 0254 auto networkFolderButton = new QPushButton(QIcon::fromTheme(QStringLiteral("folder-add")), i18nc("@action:button", "Add Network Folder"), parent); 0255 networkFolderButton->setFlat(true); 0256 KService::Ptr service = KService::serviceByDesktopName(QStringLiteral("org.kde.knetattach")); 0257 connect(networkFolderButton, &QPushButton::clicked, this, [networkFolderButton, service]() { 0258 auto *job = new KIO::ApplicationLauncherJob(service, networkFolderButton); 0259 auto *delegate = new KNotificationJobUiDelegate; 0260 delegate->setAutoErrorHandlingEnabled(true); 0261 job->setUiDelegate(delegate); 0262 job->start(); 0263 }); 0264 networkFolderButton->hide(); 0265 connect(urlNavigator, &KUrlNavigator::urlChanged, this, [networkFolderButton, urlNavigator, service]() { 0266 networkFolderButton->setVisible(service && urlNavigator->locationUrl().scheme() == QLatin1String("remote")); 0267 }); 0268 return networkFolderButton; 0269 } 0270 0271 QWidget *DolphinNavigatorsWidgetAction::spacing(Side side, Position position) const 0272 { 0273 int sideIndex = (side == Primary ? 0 : 1); 0274 if (position == Leading) { 0275 Q_ASSERT(side == Primary); // The secondary side of the splitter has no leading spacing. 0276 return m_splitter->widget(sideIndex)->layout()->itemAt(0)->widget(); 0277 } 0278 if (side == Primary) { 0279 return m_splitter->widget(sideIndex)->layout()->itemAt(4)->widget(); 0280 } 0281 return m_splitter->widget(sideIndex)->layout()->itemAt(3)->widget(); 0282 } 0283 0284 void DolphinNavigatorsWidgetAction::updateText() 0285 { 0286 const int urlNavigatorsAmount = m_splitter->count() > 1 && m_splitter->widget(1)->isVisible() ? 2 : 1; 0287 setText(i18ncp("@action:inmenu", "Location Bar", "Location Bars", urlNavigatorsAmount)); 0288 } 0289 0290 DolphinNavigatorsWidgetAction::ViewGeometriesHelper::ViewGeometriesHelper(QWidget *navigatorsWidget, DolphinNavigatorsWidgetAction *navigatorsWidgetAction) 0291 : m_navigatorsWidget{navigatorsWidget} 0292 , m_navigatorsWidgetAction{navigatorsWidgetAction} 0293 { 0294 Q_CHECK_PTR(navigatorsWidget); 0295 Q_CHECK_PTR(navigatorsWidgetAction); 0296 } 0297 0298 bool DolphinNavigatorsWidgetAction::ViewGeometriesHelper::eventFilter(QObject *watched, QEvent *event) 0299 { 0300 if (event->type() == QEvent::Resize) { 0301 if (qobject_cast<QWidget*>(m_navigatorsWidgetAction->parent())->window()->width() != m_navigatorsWidgetAction->m_previousWindowWidth) { 0302 // The window is being resized which means not all widgets have gotten their new sizes yet. 0303 // Let's wait a bit so the sizes of the navigatorsWidget and the viewContainers have all 0304 // had a chance to be updated. 0305 m_navigatorsWidgetAction->m_adjustSpacingTimer->start(); 0306 } else { 0307 m_navigatorsWidgetAction->adjustSpacing(); 0308 // We could always use the m_adjustSpacingTimer instead of calling adjustSpacing() directly 0309 // here but then the navigatorsWidget doesn't fluently align with the viewContainers when 0310 // the DolphinTabPage::m_expandViewAnimation is animating. 0311 } 0312 return false; 0313 } 0314 return QObject::eventFilter(watched, event); 0315 } 0316 0317 void DolphinNavigatorsWidgetAction::ViewGeometriesHelper::setViewContainers(QWidget *primaryViewContainer, QWidget *secondaryViewContainer) 0318 { 0319 Q_CHECK_PTR(primaryViewContainer); 0320 if (m_primaryViewContainer) { 0321 m_primaryViewContainer->removeEventFilter(this); 0322 } 0323 primaryViewContainer->installEventFilter(this); 0324 m_primaryViewContainer = primaryViewContainer; 0325 0326 // It is not possible to resize the secondaryViewContainer without simultaneously 0327 // resizing the primaryViewContainer so we don't have to installEventFilter() here. 0328 m_secondaryViewContainer = secondaryViewContainer; 0329 } 0330 0331 DolphinNavigatorsWidgetAction::ViewGeometriesHelper::Geometries DolphinNavigatorsWidgetAction::ViewGeometriesHelper::viewGeometries() 0332 { 0333 Q_ASSERT(m_primaryViewContainer); 0334 Geometries geometries; 0335 0336 // width 0337 geometries.widthOfPrimary = m_primaryViewContainer->width(); 0338 if (m_secondaryViewContainer) { 0339 geometries.widthOfSecondary = m_secondaryViewContainer->width(); 0340 } else { 0341 geometries.widthOfSecondary = INT_MIN; 0342 } 0343 0344 // globalX 0345 if (QApplication::layoutDirection() == Qt::LeftToRight) { 0346 geometries.globalXOfNavigatorsWidget = m_navigatorsWidget->mapToGlobal(QPoint(0, 0)).x(); 0347 geometries.globalXOfPrimary = m_primaryViewContainer->mapToGlobal(QPoint(0, 0)).x(); 0348 geometries.globalXOfSecondary = !m_secondaryViewContainer ? INT_MIN : m_secondaryViewContainer->mapToGlobal(QPoint(0, 0)).x(); 0349 } else { 0350 // When the direction is reversed, globalX does not change. 0351 // For the adjustSpacing() code to work we need globalX to measure from right to left 0352 // and to measure up to the rightmost point of a widget instead of the leftmost. 0353 geometries.globalXOfNavigatorsWidget = (-1) * (m_navigatorsWidget->mapToGlobal(QPoint(0, 0)).x() + m_navigatorsWidget->width()); 0354 geometries.globalXOfPrimary = (-1) * (m_primaryViewContainer->mapToGlobal(QPoint(0, 0)).x() + geometries.widthOfPrimary); 0355 geometries.globalXOfSecondary = 0356 !m_secondaryViewContainer ? INT_MIN : (-1) * (m_secondaryViewContainer->mapToGlobal(QPoint(0, 0)).x() + geometries.widthOfSecondary); 0357 } 0358 return geometries; 0359 } 0360 0361 #include "moc_dolphinnavigatorswidgetaction.cpp"