]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphinnavigatorswidgetaction.cpp
kitemlistcontrollertest: fix warnings
[dolphin.git] / src / dolphinnavigatorswidgetaction.cpp
1 /*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2020 Felix Ernst <felixernst@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7
8 #include "dolphinnavigatorswidgetaction.h"
9
10 #include "trash/dolphintrash.h"
11
12 #include <KLocalizedString>
13 #include <KNotificationJobUiDelegate>
14 #include <KService>
15
16 #include <KIO/ApplicationLauncherJob>
17
18 #include <QApplication>
19 #include <QHBoxLayout>
20 #include <QPushButton>
21 #include <QToolBar>
22
23 #include <limits>
24
25 DolphinNavigatorsWidgetAction::DolphinNavigatorsWidgetAction(QWidget *parent)
26 : QWidgetAction{parent}
27 , m_splitter{new QSplitter(Qt::Horizontal)}
28 , m_adjustSpacingTimer{new QTimer(this)}
29 , m_viewGeometriesHelper{m_splitter.get(), this}
30 {
31 updateText();
32 setIcon(QIcon::fromTheme(QStringLiteral("dialog-scripts")));
33
34 m_splitter->setChildrenCollapsible(false);
35
36 m_splitter->addWidget(createNavigatorWidget(Primary));
37 m_splitter->setFocusProxy(primaryUrlNavigator());
38
39 m_adjustSpacingTimer->setInterval(100);
40 m_adjustSpacingTimer->setSingleShot(true);
41 connect(m_adjustSpacingTimer.get(), &QTimer::timeout, this, &DolphinNavigatorsWidgetAction::adjustSpacing);
42 }
43
44 void DolphinNavigatorsWidgetAction::adjustSpacing()
45 {
46 m_previousWindowWidth = qobject_cast<QWidget *>(parent())->window()->width();
47 auto viewGeometries = m_viewGeometriesHelper.viewGeometries();
48 const int widthOfSplitterPrimary = viewGeometries.globalXOfPrimary + viewGeometries.widthOfPrimary - viewGeometries.globalXOfNavigatorsWidget;
49 const QList<int> splitterSizes = {widthOfSplitterPrimary, m_splitter->width() - widthOfSplitterPrimary - m_splitter->handleWidth()};
50 m_splitter->setSizes(splitterSizes);
51
52 // primary side of m_splitter
53 int leadingSpacing = viewGeometries.globalXOfPrimary - viewGeometries.globalXOfNavigatorsWidget;
54 if (leadingSpacing < 0) {
55 leadingSpacing = 0;
56 }
57 int trailingSpacing = (viewGeometries.globalXOfNavigatorsWidget + m_splitter->width()) - (viewGeometries.globalXOfPrimary + viewGeometries.widthOfPrimary);
58 if (trailingSpacing < 0 || emptyTrashButton(Primary)->isVisible() || networkFolderButton(Primary)->isVisible()) {
59 trailingSpacing = 0;
60 }
61 const int widthLeftForUrlNavigator = m_splitter->widget(0)->width() - leadingSpacing - trailingSpacing;
62 const int widthNeededForUrlNavigator = primaryUrlNavigator()->sizeHint().width() - widthLeftForUrlNavigator;
63 if (widthNeededForUrlNavigator > 0) {
64 trailingSpacing -= widthNeededForUrlNavigator;
65 if (trailingSpacing < 0) {
66 leadingSpacing += trailingSpacing;
67 trailingSpacing = 0;
68 }
69 if (leadingSpacing < 0) {
70 leadingSpacing = 0;
71 }
72 }
73 spacing(Primary, Leading)->setMinimumWidth(leadingSpacing);
74 spacing(Primary, Trailing)->setFixedWidth(trailingSpacing);
75
76 // secondary side of m_splitter
77 if (viewGeometries.globalXOfSecondary == INT_MIN) {
78 Q_ASSERT(viewGeometries.widthOfSecondary == INT_MIN);
79 return;
80 }
81 spacing(Primary, Trailing)->setFixedWidth(0);
82
83 trailingSpacing = (viewGeometries.globalXOfNavigatorsWidget + m_splitter->width()) - (viewGeometries.globalXOfSecondary + viewGeometries.widthOfSecondary);
84 if (trailingSpacing < 0 || emptyTrashButton(Secondary)->isVisible() || networkFolderButton(Secondary)->isVisible()) {
85 trailingSpacing = 0;
86 } else {
87 const int widthLeftForUrlNavigator2 = m_splitter->widget(1)->width() - trailingSpacing;
88 const int widthNeededForUrlNavigator2 = secondaryUrlNavigator()->sizeHint().width() - widthLeftForUrlNavigator2;
89 if (widthNeededForUrlNavigator2 > 0) {
90 trailingSpacing -= widthNeededForUrlNavigator2;
91 if (trailingSpacing < 0) {
92 trailingSpacing = 0;
93 }
94 }
95 }
96 spacing(Secondary, Trailing)->setMinimumWidth(trailingSpacing);
97 }
98
99 void DolphinNavigatorsWidgetAction::createSecondaryUrlNavigator()
100 {
101 Q_ASSERT(m_splitter->count() == 1);
102 m_splitter->addWidget(createNavigatorWidget(Secondary));
103 Q_ASSERT(m_splitter->count() == 2);
104 updateText();
105 }
106
107 void DolphinNavigatorsWidgetAction::followViewContainersGeometry(QWidget *primaryViewContainer, QWidget *secondaryViewContainer)
108 {
109 m_viewGeometriesHelper.setViewContainers(primaryViewContainer, secondaryViewContainer);
110 adjustSpacing();
111 }
112
113 bool DolphinNavigatorsWidgetAction::isInToolbar() const
114 {
115 return qobject_cast<QToolBar *>(m_splitter->parentWidget());
116 }
117
118 DolphinUrlNavigator *DolphinNavigatorsWidgetAction::primaryUrlNavigator() const
119 {
120 Q_ASSERT(m_splitter);
121 return m_splitter->widget(0)->findChild<DolphinUrlNavigator *>();
122 }
123
124 DolphinUrlNavigator *DolphinNavigatorsWidgetAction::secondaryUrlNavigator() const
125 {
126 Q_ASSERT(m_splitter);
127 if (m_splitter->count() < 2) {
128 return nullptr;
129 }
130 return m_splitter->widget(1)->findChild<DolphinUrlNavigator *>();
131 }
132
133 void DolphinNavigatorsWidgetAction::setSecondaryNavigatorVisible(bool visible)
134 {
135 if (visible) {
136 Q_ASSERT(m_splitter->count() == 2);
137 m_splitter->widget(1)->setVisible(true);
138 } else if (m_splitter->count() > 1) {
139 m_splitter->widget(1)->setVisible(false);
140 // Fix an unlikely event of wrong trash button visibility.
141 emptyTrashButton(Secondary)->setVisible(false);
142 }
143 updateText();
144 }
145
146 QWidget *DolphinNavigatorsWidgetAction::createWidget(QWidget *parent)
147 {
148 QWidget *oldParent = m_splitter->parentWidget();
149 if (oldParent && oldParent->layout()) {
150 oldParent->layout()->removeWidget(m_splitter.get());
151 QGridLayout *layout = qobject_cast<QGridLayout *>(oldParent->layout());
152 if (qobject_cast<QToolBar *>(parent) && layout) {
153 // in DolphinTabPage::insertNavigatorsWidget the minimumHeight of this row was
154 // set to fit the m_splitter. Since we are now removing it again, the
155 // minimumHeight can be reset to 0.
156 layout->setRowMinimumHeight(0, 0);
157 }
158 }
159 m_splitter->setParent(parent);
160 return m_splitter.get();
161 }
162
163 void DolphinNavigatorsWidgetAction::deleteWidget(QWidget *widget)
164 {
165 Q_UNUSED(widget)
166 m_splitter->setParent(nullptr);
167 }
168
169 QWidget *DolphinNavigatorsWidgetAction::createNavigatorWidget(Side side) const
170 {
171 auto navigatorWidget = new QWidget(m_splitter.get());
172 auto layout = new QHBoxLayout{navigatorWidget};
173 layout->setSpacing(0);
174 layout->setContentsMargins(0, 0, 0, 0);
175 if (side == Primary) {
176 auto leadingSpacing = new QWidget{navigatorWidget};
177 layout->addWidget(leadingSpacing);
178 }
179 auto urlNavigator = new DolphinUrlNavigator(navigatorWidget);
180 layout->addWidget(urlNavigator);
181
182 auto emptyTrashButton = newEmptyTrashButton(urlNavigator, navigatorWidget);
183 layout->addWidget(emptyTrashButton);
184
185 auto networkFolderButton = newNetworkFolderButton(urlNavigator, navigatorWidget);
186 layout->addWidget(networkFolderButton);
187
188 connect(
189 urlNavigator,
190 &KUrlNavigator::urlChanged,
191 this,
192 [urlNavigator, this]() {
193 // Update URL navigator to show a server URL entry placeholder text if we
194 // just loaded the remote:/ page, to make it easier for users to figure out
195 // that they can enter arbitrary remote URLs. See bug 414670
196 if (urlNavigator->locationUrl().scheme() == QLatin1String("remote")) {
197 if (!urlNavigator->isUrlEditable()) {
198 urlNavigator->setUrlEditable(true);
199 }
200 urlNavigator->clearText();
201 urlNavigator->setPlaceholderText(i18n("Enter server URL (e.g. smb://[ip address])"));
202 } else {
203 urlNavigator->setPlaceholderText(QString());
204 }
205
206 // We have to wait for DolphinUrlNavigator::sizeHint() to update which
207 // happens a little bit later than when urlChanged is emitted.
208 this->m_adjustSpacingTimer->start();
209 },
210 Qt::QueuedConnection);
211
212 auto trailingSpacing = new QWidget{navigatorWidget};
213 layout->addWidget(trailingSpacing);
214 return navigatorWidget;
215 }
216
217 QPushButton *DolphinNavigatorsWidgetAction::emptyTrashButton(DolphinNavigatorsWidgetAction::Side side)
218 {
219 int sideIndex = (side == Primary ? 0 : 1);
220 if (side == Primary) {
221 return static_cast<QPushButton *>(m_splitter->widget(sideIndex)->layout()->itemAt(2)->widget());
222 }
223 return static_cast<QPushButton *>(m_splitter->widget(sideIndex)->layout()->itemAt(1)->widget());
224 }
225
226 QPushButton *DolphinNavigatorsWidgetAction::newEmptyTrashButton(const DolphinUrlNavigator *urlNavigator, QWidget *parent) const
227 {
228 auto emptyTrashButton = new QPushButton(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:button", "Empty Trash"), parent);
229 emptyTrashButton->setToolTip(i18n("Empties Trash to create free space"));
230
231 emptyTrashButton->setFlat(true);
232 connect(emptyTrashButton, &QPushButton::clicked, this, [parent]() {
233 Trash::empty(parent);
234 });
235 connect(&Trash::instance(), &Trash::emptinessChanged, emptyTrashButton, &QPushButton::setDisabled);
236 emptyTrashButton->hide();
237 connect(urlNavigator, &KUrlNavigator::urlChanged, this, [emptyTrashButton, urlNavigator]() {
238 emptyTrashButton->setVisible(urlNavigator->locationUrl().scheme() == QLatin1String("trash"));
239 });
240 emptyTrashButton->setDisabled(Trash::isEmpty());
241 return emptyTrashButton;
242 }
243
244 QPushButton *DolphinNavigatorsWidgetAction::networkFolderButton(DolphinNavigatorsWidgetAction::Side side)
245 {
246 int sideIndex = (side == Primary ? 0 : 1);
247 if (side == Primary) {
248 return static_cast<QPushButton *>(m_splitter->widget(sideIndex)->layout()->itemAt(3)->widget());
249 }
250 return static_cast<QPushButton *>(m_splitter->widget(sideIndex)->layout()->itemAt(2)->widget());
251 }
252
253 QPushButton *DolphinNavigatorsWidgetAction::newNetworkFolderButton(const DolphinUrlNavigator *urlNavigator, QWidget *parent) const
254 {
255 auto networkFolderButton = new QPushButton(QIcon::fromTheme(QStringLiteral("folder-add")), i18nc("@action:button", "Add Network Folder"), parent);
256 networkFolderButton->setFlat(true);
257 connect(networkFolderButton, &QPushButton::clicked, this, [networkFolderButton]() {
258 const KService::Ptr service = KService::serviceByDesktopName(QStringLiteral("org.kde.knetattach"));
259 auto *job = new KIO::ApplicationLauncherJob(service, networkFolderButton);
260 auto *delegate = new KNotificationJobUiDelegate;
261 delegate->setAutoErrorHandlingEnabled(true);
262 job->setUiDelegate(delegate);
263 job->start();
264 });
265 networkFolderButton->hide();
266 connect(urlNavigator, &KUrlNavigator::urlChanged, this, [networkFolderButton, urlNavigator]() {
267 if (urlNavigator->locationUrl().scheme() == QLatin1String("remote")) {
268 // Looking up a service can be a bit slow, so we only do it now when it becomes necessary.
269 const KService::Ptr service = KService::serviceByDesktopName(QStringLiteral("org.kde.knetattach"));
270 networkFolderButton->setVisible(service);
271 } else {
272 networkFolderButton->setVisible(false);
273 }
274 });
275 return networkFolderButton;
276 }
277
278 QWidget *DolphinNavigatorsWidgetAction::spacing(Side side, Position position) const
279 {
280 int sideIndex = (side == Primary ? 0 : 1);
281 if (position == Leading) {
282 Q_ASSERT(side == Primary); // The secondary side of the splitter has no leading spacing.
283 return m_splitter->widget(sideIndex)->layout()->itemAt(0)->widget();
284 }
285 if (side == Primary) {
286 return m_splitter->widget(sideIndex)->layout()->itemAt(4)->widget();
287 }
288 return m_splitter->widget(sideIndex)->layout()->itemAt(3)->widget();
289 }
290
291 void DolphinNavigatorsWidgetAction::updateText()
292 {
293 const int urlNavigatorsAmount = m_splitter->count() > 1 && m_splitter->widget(1)->isVisible() ? 2 : 1;
294 setText(i18ncp("@action:inmenu", "Location Bar", "Location Bars", urlNavigatorsAmount));
295 }
296
297 DolphinNavigatorsWidgetAction::ViewGeometriesHelper::ViewGeometriesHelper(QWidget *navigatorsWidget, DolphinNavigatorsWidgetAction *navigatorsWidgetAction)
298 : m_navigatorsWidget{navigatorsWidget}
299 , m_navigatorsWidgetAction{navigatorsWidgetAction}
300 {
301 Q_CHECK_PTR(navigatorsWidget);
302 Q_CHECK_PTR(navigatorsWidgetAction);
303 }
304
305 bool DolphinNavigatorsWidgetAction::ViewGeometriesHelper::eventFilter(QObject *watched, QEvent *event)
306 {
307 if (event->type() == QEvent::Resize) {
308 if (qobject_cast<QWidget*>(m_navigatorsWidgetAction->parent())->window()->width() != m_navigatorsWidgetAction->m_previousWindowWidth) {
309 // The window is being resized which means not all widgets have gotten their new sizes yet.
310 // Let's wait a bit so the sizes of the navigatorsWidget and the viewContainers have all
311 // had a chance to be updated.
312 m_navigatorsWidgetAction->m_adjustSpacingTimer->start();
313 } else {
314 m_navigatorsWidgetAction->adjustSpacing();
315 // We could always use the m_adjustSpacingTimer instead of calling adjustSpacing() directly
316 // here but then the navigatorsWidget doesn't fluently align with the viewContainers when
317 // the DolphinTabPage::m_expandViewAnimation is animating.
318 }
319 return false;
320 }
321 return QObject::eventFilter(watched, event);
322 }
323
324 void DolphinNavigatorsWidgetAction::ViewGeometriesHelper::setViewContainers(QWidget *primaryViewContainer, QWidget *secondaryViewContainer)
325 {
326 Q_CHECK_PTR(primaryViewContainer);
327 if (m_primaryViewContainer) {
328 m_primaryViewContainer->removeEventFilter(this);
329 }
330 primaryViewContainer->installEventFilter(this);
331 m_primaryViewContainer = primaryViewContainer;
332
333 // It is not possible to resize the secondaryViewContainer without simultaneously
334 // resizing the primaryViewContainer so we don't have to installEventFilter() here.
335 m_secondaryViewContainer = secondaryViewContainer;
336 }
337
338 DolphinNavigatorsWidgetAction::ViewGeometriesHelper::Geometries DolphinNavigatorsWidgetAction::ViewGeometriesHelper::viewGeometries()
339 {
340 Q_ASSERT(m_primaryViewContainer);
341 Geometries geometries;
342
343 // width
344 geometries.widthOfPrimary = m_primaryViewContainer->width();
345 if (m_secondaryViewContainer) {
346 geometries.widthOfSecondary = m_secondaryViewContainer->width();
347 } else {
348 geometries.widthOfSecondary = INT_MIN;
349 }
350
351 // globalX
352 if (QApplication::layoutDirection() == Qt::LeftToRight) {
353 geometries.globalXOfNavigatorsWidget = m_navigatorsWidget->mapToGlobal(QPoint(0, 0)).x();
354 geometries.globalXOfPrimary = m_primaryViewContainer->mapToGlobal(QPoint(0, 0)).x();
355 geometries.globalXOfSecondary = !m_secondaryViewContainer ? INT_MIN : m_secondaryViewContainer->mapToGlobal(QPoint(0, 0)).x();
356 } else {
357 // When the direction is reversed, globalX does not change.
358 // For the adjustSpacing() code to work we need globalX to measure from right to left
359 // and to measure up to the rightmost point of a widget instead of the leftmost.
360 geometries.globalXOfNavigatorsWidget = (-1) * (m_navigatorsWidget->mapToGlobal(QPoint(0, 0)).x() + m_navigatorsWidget->width());
361 geometries.globalXOfPrimary = (-1) * (m_primaryViewContainer->mapToGlobal(QPoint(0, 0)).x() + geometries.widthOfPrimary);
362 geometries.globalXOfSecondary =
363 !m_secondaryViewContainer ? INT_MIN : (-1) * (m_secondaryViewContainer->mapToGlobal(QPoint(0, 0)).x() + geometries.widthOfSecondary);
364 }
365 return geometries;
366 }
367
368 #include "moc_dolphinnavigatorswidgetaction.cpp"