KIO

kurlcombobox.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 2000, 2001 Carsten Pfeiffer <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-only
6 */
7 
8 #include "kurlcombobox.h"
9 
10 #include "../pathhelpers_p.h" // isAbsoluteLocalPath()
11 
12 #include <QApplication>
13 #include <QDir>
14 #include <QDrag>
15 #include <QMimeData>
16 #include <QMouseEvent>
17 
18 #include <KIconLoader>
19 #include <KLocalizedString>
20 #include <QDebug>
21 #include <kio/global.h>
22 
23 #include <algorithm>
24 #include <vector>
25 
26 class KUrlComboBoxPrivate
27 {
28 public:
29  KUrlComboBoxPrivate(KUrlComboBox *parent)
30  : m_parent(parent)
31  , dirIcon(QIcon::fromTheme(QStringLiteral("folder")))
32  {
33  }
34 
35  struct KUrlComboItem {
36  KUrlComboItem(const QUrl &url, const QIcon &icon, const QString &text = QString())
37  : url(url)
38  , icon(icon)
39  , text(text)
40  {
41  }
42  QUrl url;
43  QIcon icon;
44  QString text; // if empty, calculated from the QUrl
45  };
46 
47  void init(KUrlComboBox::Mode mode);
48  QString textForItem(const KUrlComboItem *item) const;
49  void insertUrlItem(const KUrlComboItem *);
50  QIcon getIcon(const QUrl &url) const;
51  void updateItem(const KUrlComboItem *item, int index, const QIcon &icon);
52 
53  void _k_slotActivated(int);
54 
55  KUrlComboBox *const m_parent;
56  QIcon dirIcon;
57  bool urlAdded;
58  int myMaximum;
59  KUrlComboBox::Mode myMode;
60  QPoint m_dragPoint;
61 
62  using KUrlComboItemList = std::vector<std::unique_ptr<const KUrlComboItem>>;
63  KUrlComboItemList itemList;
64  KUrlComboItemList defaultList;
66 
67  QIcon opendirIcon;
68 };
69 
70 QString KUrlComboBoxPrivate::textForItem(const KUrlComboItem *item) const
71 {
72  if (!item->text.isEmpty()) {
73  return item->text;
74  }
75  QUrl url = item->url;
76 
77  if (myMode == KUrlComboBox::Directories) {
78  if (!url.path().isEmpty() && !url.path().endsWith(QLatin1Char('/'))) {
79  url.setPath(url.path() + QLatin1Char('/'));
80  }
81  } else {
83  }
84  if (url.isLocalFile()) {
85  return url.toLocalFile();
86  } else {
87  return url.toDisplayString();
88  }
89 }
90 
92  : KComboBox(parent)
93  , d(new KUrlComboBoxPrivate(this))
94 {
95  d->init(mode);
96 }
97 
98 KUrlComboBox::KUrlComboBox(Mode mode, bool rw, QWidget *parent)
99  : KComboBox(rw, parent)
100  , d(new KUrlComboBoxPrivate(this))
101 {
102  d->init(mode);
103 }
104 
105 KUrlComboBox::~KUrlComboBox() = default;
106 
107 void KUrlComboBoxPrivate::init(KUrlComboBox::Mode mode)
108 {
109  myMode = mode;
110  urlAdded = false;
111  myMaximum = 10; // default
112  m_parent->setInsertPolicy(KUrlComboBox::NoInsert);
113  m_parent->setTrapReturnKey(true);
114  m_parent->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
115  m_parent->setLayoutDirection(Qt::LeftToRight);
116  if (m_parent->completionObject()) {
117  m_parent->completionObject()->setOrder(KCompletion::Sorted);
118  }
119 
120  opendirIcon = QIcon::fromTheme(QStringLiteral("folder-open"));
121 
122  m_parent->connect(m_parent, qOverload<int>(&KUrlComboBox::activated), m_parent, [this](int index) {
123  _k_slotActivated(index);
124  });
125 }
126 
127 QStringList KUrlComboBox::urls() const
128 {
129  // qDebug() << "::urls()";
131  QString url;
132  for (int i = static_cast<int>(d->defaultList.size()); i < count(); i++) {
133  url = itemText(i);
134  if (!url.isEmpty()) {
135  if (isAbsoluteLocalPath(url)) {
137  } else {
138  list.append(url);
139  }
140  }
141  }
142 
143  return list;
144 }
145 
146 void KUrlComboBox::addDefaultUrl(const QUrl &url, const QString &text)
147 {
148  addDefaultUrl(url, d->getIcon(url), text);
149 }
150 
151 void KUrlComboBox::addDefaultUrl(const QUrl &url, const QIcon &icon, const QString &text)
152 {
153  d->defaultList.push_back(std::unique_ptr<KUrlComboBoxPrivate::KUrlComboItem>(new KUrlComboBoxPrivate::KUrlComboItem(url, icon, text)));
154 }
155 
157 {
158  clear();
159  d->itemMapper.clear();
160 
161  for (const auto &item : d->defaultList) {
162  d->insertUrlItem(item.get());
163  }
164 }
165 
167 {
168  setUrls(urls, RemoveBottom);
169 }
170 
172 {
173  setDefaults();
174  d->itemList.clear();
175  d->urlAdded = false;
176 
177  if (_urls.isEmpty()) {
178  return;
179  }
180 
181  QStringList urls;
183 
184  // kill duplicates
185  while (it != _urls.constEnd()) {
186  if (!urls.contains(*it)) {
187  urls += *it;
188  }
189  ++it;
190  }
191 
192  // limit to myMaximum items
193  /* Note: overload is an (old) C++ keyword, some compilers (KCC) choke
194  on that, so call it Overload (capital 'O'). (matz) */
195  int Overload = urls.count() - d->myMaximum + static_cast<int>(d->defaultList.size());
196  while (Overload > 0) {
197  if (remove == RemoveBottom) {
198  if (!urls.isEmpty()) {
199  urls.removeLast();
200  }
201  } else {
202  if (!urls.isEmpty()) {
203  urls.removeFirst();
204  }
205  }
206  Overload--;
207  }
208 
209  it = urls.constBegin();
210 
211  while (it != urls.constEnd()) {
212  if ((*it).isEmpty()) {
213  ++it;
214  continue;
215  }
216  QUrl u;
217  if (isAbsoluteLocalPath(*it)) {
218  u = QUrl::fromLocalFile(*it);
219  } else {
220  u.setUrl(*it);
221  }
222 
223  // Don't restore if file doesn't exist anymore
224  if (u.isLocalFile() && !QFile::exists(u.toLocalFile())) {
225  ++it;
226  continue;
227  }
228 
229  std::unique_ptr<KUrlComboBoxPrivate::KUrlComboItem> item(new KUrlComboBoxPrivate::KUrlComboItem(u, d->getIcon(u)));
230  d->insertUrlItem(item.get());
231  d->itemList.push_back(std::move(item));
232  ++it;
233  }
234 }
235 
236 void KUrlComboBox::setUrl(const QUrl &url)
237 {
238  if (url.isEmpty()) {
239  return;
240  }
241 
242  bool blocked = blockSignals(true);
243 
244  // check for duplicates
245  auto mit = d->itemMapper.constBegin();
246  QString urlToInsert = url.toString(QUrl::StripTrailingSlash);
247  while (mit != d->itemMapper.constEnd()) {
248  Q_ASSERT(mit.value());
249 
250  if (urlToInsert == mit.value()->url.toString(QUrl::StripTrailingSlash)) {
251  setCurrentIndex(mit.key());
252 
253  if (d->myMode == Directories) {
254  d->updateItem(mit.value(), mit.key(), d->opendirIcon);
255  }
256 
257  blockSignals(blocked);
258  return;
259  }
260  ++mit;
261  }
262 
263  // not in the combo yet -> create a new item and insert it
264 
265  // first remove the old item
266  if (d->urlAdded) {
267  Q_ASSERT(!d->itemList.empty());
268  d->itemList.pop_back();
269  d->urlAdded = false;
270  }
271 
272  setDefaults();
273 
274  const int offset = qMax(0, static_cast<int>(d->itemList.size() + d->defaultList.size()) - d->myMaximum);
275  for (size_t i = offset; i < d->itemList.size(); ++i) {
276  d->insertUrlItem(d->itemList.at(i).get());
277  }
278 
279  std::unique_ptr<KUrlComboBoxPrivate::KUrlComboItem> item(new KUrlComboBoxPrivate::KUrlComboItem(url, d->getIcon(url)));
280 
281  const int id = count();
282  const QString text = d->textForItem(item.get());
283  if (d->myMode == Directories) {
284  KComboBox::insertItem(id, d->opendirIcon, text);
285  } else {
286  KComboBox::insertItem(id, item->icon, text);
287  }
288 
289  d->itemMapper.insert(id, item.get());
290  d->itemList.push_back(std::move(item));
291 
292  setCurrentIndex(id);
293  Q_ASSERT(!d->itemList.empty());
294  d->urlAdded = true;
295  blockSignals(blocked);
296 }
297 
298 void KUrlComboBoxPrivate::_k_slotActivated(int index)
299 {
300  auto item = itemMapper.value(index);
301 
302  if (item) {
303  m_parent->setUrl(item->url);
304  Q_EMIT m_parent->urlActivated(item->url);
305  }
306 }
307 
308 void KUrlComboBoxPrivate::insertUrlItem(const KUrlComboItem *item)
309 {
310  Q_ASSERT(item);
311 
312  // qDebug() << "insertURLItem " << d->textForItem(item);
313  int id = m_parent->count();
314  m_parent->KComboBox::insertItem(id, item->icon, textForItem(item));
315  itemMapper.insert(id, item);
316 }
317 
319 {
320  d->myMaximum = max;
321 
322  if (count() > d->myMaximum) {
323  int oldCurrent = currentIndex();
324 
325  setDefaults();
326 
327  const int offset = qMax(0, static_cast<int>(d->itemList.size() + d->defaultList.size()) - d->myMaximum);
328  for (size_t i = offset; i < d->itemList.size(); ++i) {
329  d->insertUrlItem(d->itemList.at(i).get());
330  }
331 
332  if (count() > 0) { // restore the previous currentItem
333  if (oldCurrent >= count()) {
334  oldCurrent = count() - 1;
335  }
336  setCurrentIndex(oldCurrent);
337  }
338  }
339 }
340 
341 int KUrlComboBox::maxItems() const
342 {
343  return d->myMaximum;
344 }
345 
346 void KUrlComboBox::removeUrl(const QUrl &url, bool checkDefaultURLs)
347 {
348  auto mit = d->itemMapper.constBegin();
349  while (mit != d->itemMapper.constEnd()) {
350  if (url.toString(QUrl::StripTrailingSlash) == mit.value()->url.toString(QUrl::StripTrailingSlash)) {
351  auto removePredicate = [&mit](const std::unique_ptr<const KUrlComboBoxPrivate::KUrlComboItem> &item) {
352  return item.get() == mit.value();
353  };
354  d->itemList.erase(std::remove_if(d->itemList.begin(), d->itemList.end(), removePredicate), d->itemList.end());
355  if (checkDefaultURLs) {
356  d->defaultList.erase(std::remove_if(d->defaultList.begin(), d->defaultList.end(), removePredicate), d->defaultList.end());
357  }
358  }
359  ++mit;
360  }
361 
362  bool blocked = blockSignals(true);
363  setDefaults();
364  for (const auto &item : d->itemList) {
365  d->insertUrlItem(item.get());
366  }
367  blockSignals(blocked);
368 }
369 
371 {
372  if (compObj) {
373  // on a url combo box we want completion matches to be sorted. This way, if we are given
374  // a suggestion, we match the "best" one. For instance, if we have "foo" and "foobar",
375  // and we write "foo", the match is "foo" and never "foobar". (ereslibre)
377  }
379 }
380 
381 void KUrlComboBox::mousePressEvent(QMouseEvent *event)
382 {
383  QStyleOptionComboBox comboOpt;
384  comboOpt.initFrom(this);
385  const int x0 =
386  QStyle::visualRect(layoutDirection(), rect(), style()->subControlRect(QStyle::CC_ComboBox, &comboOpt, QStyle::SC_ComboBoxEditField, this)).x();
387  const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &comboOpt, this);
388 
389  if (event->x() < (x0 + KIconLoader::SizeSmall + frameWidth)) {
390  d->m_dragPoint = event->pos();
391  } else {
392  d->m_dragPoint = QPoint();
393  }
394 
396 }
397 
398 void KUrlComboBox::mouseMoveEvent(QMouseEvent *event)
399 {
400  const int index = currentIndex();
401  auto item = d->itemMapper.value(index);
402 
403  if (item && !d->m_dragPoint.isNull() && event->buttons() & Qt::LeftButton
404  && (event->pos() - d->m_dragPoint).manhattanLength() > QApplication::startDragDistance()) {
405  QDrag *drag = new QDrag(this);
406  QMimeData *mime = new QMimeData();
407  mime->setUrls(QList<QUrl>() << item->url);
408  mime->setText(itemText(index));
409  if (!itemIcon(index).isNull()) {
410  drag->setPixmap(itemIcon(index).pixmap(KIconLoader::SizeMedium));
411  }
412  drag->setMimeData(mime);
413  drag->exec();
414  }
415 
417 }
418 
419 QIcon KUrlComboBoxPrivate::getIcon(const QUrl &url) const
420 {
421  if (myMode == KUrlComboBox::Directories) {
422  return dirIcon;
423  } else {
425  }
426 }
427 
428 // updates "item" with icon "icon"
429 // kdelibs4 used to also say "and sets the URL instead of text", but this breaks const-ness,
430 // now that it would require clearing the text, and I don't see the point since the URL was already in the text.
431 void KUrlComboBoxPrivate::updateItem(const KUrlComboItem *item, int index, const QIcon &icon)
432 {
433  m_parent->setItemIcon(index, icon);
434  m_parent->setItemText(index, textForItem(item));
435 }
436 
437 #include "moc_kurlcombobox.cpp"
void append(const T &value)
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
void setUrl(const QUrl &url)
Sets the current url.
void setUrls(const QList< QUrl > &urls)
void setUrls(const QStringList &urls)
Inserts urls into the combobox below the "default urls" (see addDefaultUrl).
QCA_EXPORT void init()
int count(const T &value) const const
SC_ComboBoxEditField
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QIcon fromTheme(const QString &name)
void setCurrentIndex(int index)
int x() const const
void initFrom(const QWidget *widget)
QList::const_iterator constBegin() const const
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
Returns a list of directories associated with this file-class.
Definition: krecentdirs.cpp:39
LeftButton
bool exists() const const
Qt::DropAction exec(Qt::DropActions supportedActions)
void setPixmap(const QPixmap &pixmap)
QString itemText(int index) const const
~KUrlComboBox() override
Destructs the combo box.
virtual int pixelMetric(QStyle::PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
void removeLast()
KIOCORE_EXPORT QString iconNameForUrl(const QUrl &url)
Return the icon name for a URL.
Definition: global.cpp:212
QString toString(QUrl::FormattingOptions options) const const
KCompletion * compObj() const
QStyle * style() const const
bool isEmpty() const const
A combo box showing a number of recent URLs/directories.
Definition: kurlcombobox.h:42
QRect visualRect(Qt::LayoutDirection direction, const QRect &boundingRectangle, const QRect &logicalRectangle)
bool blockSignals(bool block)
StripTrailingSlash
char * toString(const T &value)
void addDefaultUrl(const QUrl &url, const QString &text=QString())
Adds a url that will always be shown in the combobox, it can't be "rotated away".
void insertItem(int index, const QString &text, const QVariant &userData)
virtual void mousePressEvent(QMouseEvent *e) override
KUrlComboBox(Mode mode, QWidget *parent=nullptr)
Constructs a KUrlComboBox.
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)
virtual void setCompletionObject(KCompletion *completionObject, bool handleSignals=true)
void removeFirst()
QString toDisplayString(QUrl::FormattingOptions options) const const
QIcon itemIcon(int index) const const
virtual bool event(QEvent *event) override
bool isEmpty() const const
QString toLocalFile() const const
void setMaxItems(int)
Sets how many items should be handled and displayed by the combobox.
PM_DefaultFrameWidth
typedef ConstIterator
void setText(const QString &text)
void setUrl(const QString &url, QUrl::ParsingMode parsingMode)
QList::const_iterator constEnd() const const
QString path(QUrl::ComponentFormattingOptions options) const const
void clear()
void setPath(const QString &path, QUrl::ParsingMode mode)
bool isLocalFile() const const
QUrl adjusted(QUrl::FormattingOptions options) const const
void setDefaults()
Clears all items and inserts the default urls into the combo.
LeftToRight
OverLoadResolving
This Enumeration is used in setUrl() to determine which items will be removed when the given list is ...
Definition: kurlcombobox.h:60
void setMimeData(QMimeData *data)
Mode
This enum describes which kind of items is shown in the combo box.
Definition: kurlcombobox.h:52
virtual void mouseMoveEvent(QMouseEvent *event)
void activated(int index)
void setCompletionObject(KCompletion *compObj, bool hsig=true) override
Reimplemented from KComboBox (from KCompletion)
void removeUrl(const QUrl &url, bool checkDefaultURLs=true)
Removes any occurrence of url.
virtual void setOrder(CompOrder order)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Aug 15 2022 04:01:01 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.