| 1 | /* |
| 2 | This file is part of the KDE project |
| 3 | SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org> |
| 4 | SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org> |
| 5 | SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org> |
| 6 | |
| 7 | SPDX-License-Identifier: LGPL-2.0-only |
| 8 | */ |
| 9 | |
| 10 | #ifndef KFILEPLACESMODEL_H |
| 11 | #define KFILEPLACESMODEL_H |
| 12 | |
| 13 | #include "kiofilewidgets_export.h" |
| 14 | |
| 15 | #include <KBookmark> |
| 16 | #include <QAbstractItemModel> |
| 17 | #include <QUrl> |
| 18 | |
| 19 | #include <solid/device.h> |
| 20 | #include <solid/solidnamespace.h> |
| 21 | |
| 22 | #include <memory> |
| 23 | |
| 24 | class KFilePlacesModelPrivate; |
| 25 | class KBookmarkManager; |
| 26 | |
| 27 | class QMimeData; |
| 28 | class QAction; |
| 29 | |
| 30 | /*! |
| 31 | * \class KFilePlacesModel |
| 32 | * \inmodule KIOFileWidgets |
| 33 | * |
| 34 | * This class is a list view model. Each entry represents a "place" |
| 35 | * where user can access files. Only relevant when |
| 36 | * used with QListView or QTableView. |
| 37 | * |
| 38 | * \reentrant |
| 39 | */ |
| 40 | class KIOFILEWIDGETS_EXPORT KFilePlacesModel : public QAbstractItemModel |
| 41 | { |
| 42 | Q_OBJECT |
| 43 | |
| 44 | /*! |
| 45 | * \property KFilePlacesModel::supportedSchemes |
| 46 | */ |
| 47 | Q_PROPERTY(QStringList supportedSchemes READ supportedSchemes WRITE setSupportedSchemes NOTIFY supportedSchemesChanged) |
| 48 | |
| 49 | public: |
| 50 | // Note: run printf "0x%08X\n" $(($RANDOM*$RANDOM)) |
| 51 | // to define additional roles. |
| 52 | /*! |
| 53 | * \value UrlRole roleName is "url". See url() |
| 54 | * \value HiddenRole roleName is "isHidden". See isHidden() |
| 55 | * \value SetupNeededRole roleName is "isSetupNeeded". See setupNeeded() |
| 56 | * \value FixedDeviceRole Whether the place is a fixed device (neither hotpluggable nor removable). roleName is "isFixedDevice". |
| 57 | * \value CapacityBarRecommendedRole Whether the place should have its free space displayed in a capacity bar. roleName is "isCapacityBarRecommended". |
| 58 | * \value[since 5.40] GroupRole The name of the group, for example "Remote" or "Devices". roleName is "group". |
| 59 | * \value[since 5.41] IconNameRole roleName is "iconName". See icon(). |
| 60 | * \value[since 5.42] GroupHiddenRole roleName is "isGroupHidden". |
| 61 | * \value[since 5.91] TeardownAllowedRole roleName is "isTeardownAllowed". |
| 62 | * \value[since 5.94] EjectAllowedRole roleName is "isEjectAllowed". |
| 63 | * \value[since 5.95] TeardownOverlayRecommendedRole roleName is "isTeardownOverlayRecommended". |
| 64 | * \value[since 5.99] DeviceAccessibilityRole roleName is "deviceAccessibility". |
| 65 | */ |
| 66 | enum AdditionalRoles { |
| 67 | UrlRole = 0x069CD12B, |
| 68 | HiddenRole = 0x0741CAAC, |
| 69 | SetupNeededRole = 0x059A935D, |
| 70 | FixedDeviceRole = 0x332896C1, |
| 71 | CapacityBarRecommendedRole = 0x1548C5C4, |
| 72 | GroupRole = 0x0a5b64ee, |
| 73 | IconNameRole = 0x00a45c00, |
| 74 | GroupHiddenRole = 0x21a4b936, |
| 75 | TeardownAllowedRole = 0x02533364, |
| 76 | EjectAllowedRole = 0x0A16AC5B, |
| 77 | TeardownOverlayRecommendedRole = 0x032EDCCE, |
| 78 | DeviceAccessibilityRole = 0x023FFD93, |
| 79 | }; |
| 80 | |
| 81 | /*! |
| 82 | * Describes the available group types used in this model. |
| 83 | * \since 5.42 |
| 84 | * |
| 85 | * \value PlacesType "Places" section |
| 86 | * \value RemoteType "Remote" section |
| 87 | * \value RecentlySavedType "Recent" section |
| 88 | * \value SearchForType "Search for" section |
| 89 | * \value DevicesType "Devices" section |
| 90 | * \value RemovableDevicesType "Removable Devices" section |
| 91 | * \value UnknownType Unknown GroupType |
| 92 | * \value[since 5.54] TagsType "Tags" section |
| 93 | */ |
| 94 | enum GroupType { |
| 95 | PlacesType, |
| 96 | RemoteType, |
| 97 | RecentlySavedType, |
| 98 | SearchForType, |
| 99 | DevicesType, |
| 100 | RemovableDevicesType, |
| 101 | UnknownType, |
| 102 | TagsType, |
| 103 | }; |
| 104 | Q_ENUM(GroupType) |
| 105 | |
| 106 | /*! |
| 107 | * \value SetupNeeded |
| 108 | * \value SetupInProgress |
| 109 | * \value Accessible |
| 110 | * \value TeardownInProgress |
| 111 | */ |
| 112 | enum DeviceAccessibility { |
| 113 | SetupNeeded, |
| 114 | SetupInProgress, |
| 115 | Accessible, |
| 116 | TeardownInProgress |
| 117 | }; |
| 118 | Q_ENUM(DeviceAccessibility) |
| 119 | |
| 120 | /*! |
| 121 | * |
| 122 | */ |
| 123 | explicit KFilePlacesModel(QObject *parent = nullptr); |
| 124 | ~KFilePlacesModel() override; |
| 125 | |
| 126 | /*! |
| 127 | * Returns The URL of the place at index \a index. |
| 128 | */ |
| 129 | Q_INVOKABLE QUrl url(const QModelIndex &index) const; |
| 130 | |
| 131 | /*! |
| 132 | * Returns Whether the place at index \a index needs to be mounted before it can be used. |
| 133 | */ |
| 134 | Q_INVOKABLE bool setupNeeded(const QModelIndex &index) const; |
| 135 | |
| 136 | /*! |
| 137 | * Returns Whether the place is a device that can be unmounted, e.g. it is |
| 138 | * mounted but does not point at system Root or the user's Home directory. |
| 139 | * |
| 140 | * It does not indicate whether the teardown can succeed. |
| 141 | * \since 5.91 |
| 142 | */ |
| 143 | Q_INVOKABLE bool isTeardownAllowed(const QModelIndex &index) const; |
| 144 | |
| 145 | /*! |
| 146 | * Returns Whether the place is a device that can be ejected, e.g. it is |
| 147 | * a CD, DVD, etc. |
| 148 | * |
| 149 | * It does not indicate whether the eject can succeed. |
| 150 | * \since 5.94 |
| 151 | */ |
| 152 | Q_INVOKABLE bool isEjectAllowed(const QModelIndex &index) const; |
| 153 | |
| 154 | /*! |
| 155 | * Returns Whether showing an inline teardown button is recommended, |
| 156 | * e.g. when it is a removable drive. |
| 157 | * |
| 158 | * \since 5.95 |
| 159 | **/ |
| 160 | Q_INVOKABLE bool isTeardownOverlayRecommended(const QModelIndex &index) const; |
| 161 | |
| 162 | /*! |
| 163 | * Returns Whether this device is currently accessible or being (un)mounted. |
| 164 | * |
| 165 | * \since 5.99 |
| 166 | */ |
| 167 | Q_INVOKABLE KFilePlacesModel::DeviceAccessibility deviceAccessibility(const QModelIndex &index) const; |
| 168 | |
| 169 | /*! |
| 170 | * Returns The icon of the place at index \a index. |
| 171 | */ |
| 172 | Q_INVOKABLE QIcon icon(const QModelIndex &index) const; |
| 173 | |
| 174 | /*! |
| 175 | * Returns The user-visible text of the place at index \a index. |
| 176 | */ |
| 177 | Q_INVOKABLE QString text(const QModelIndex &index) const; |
| 178 | |
| 179 | /*! |
| 180 | * Returns Whether the place at index \a index is hidden or is inside an hidden group. |
| 181 | */ |
| 182 | Q_INVOKABLE bool isHidden(const QModelIndex &index) const; |
| 183 | |
| 184 | /*! |
| 185 | * Returns Whether the group type \a type is hidden. |
| 186 | * \since 5.42 |
| 187 | */ |
| 188 | Q_INVOKABLE bool isGroupHidden(const GroupType type) const; |
| 189 | |
| 190 | /*! |
| 191 | * Returns Whether the group of the place at index \a index is hidden. |
| 192 | * \since 5.42 |
| 193 | */ |
| 194 | Q_INVOKABLE bool isGroupHidden(const QModelIndex &index) const; |
| 195 | |
| 196 | /*! |
| 197 | * Returns Whether the place at index \a index is a device handled by Solid. |
| 198 | * \sa deviceForIndex() |
| 199 | */ |
| 200 | Q_INVOKABLE bool isDevice(const QModelIndex &index) const; |
| 201 | |
| 202 | /*! |
| 203 | * Returns The solid device of the place at index \a index, if it is a device. Otherwise a default Solid::Device() instance is returned. |
| 204 | * \sa isDevice() |
| 205 | */ |
| 206 | Solid::Device deviceForIndex(const QModelIndex &index) const; |
| 207 | |
| 208 | /*! |
| 209 | * Returns The KBookmark instance of the place at index \a index. |
| 210 | * If the index is not valid, a default KBookmark instance is returned. |
| 211 | */ |
| 212 | KBookmark bookmarkForIndex(const QModelIndex &index) const; |
| 213 | |
| 214 | /*! |
| 215 | * Returns The KBookmark instance of the place with url \a searchUrl. |
| 216 | * If the bookmark corresponding to searchUrl is not found, a default KBookmark instance is returned. |
| 217 | * \since 5.63 |
| 218 | */ |
| 219 | KBookmark bookmarkForUrl(const QUrl &searchUrl) const; |
| 220 | |
| 221 | /*! |
| 222 | * Returns The group type of the place at index \a index. |
| 223 | * \since 5.42 |
| 224 | */ |
| 225 | Q_INVOKABLE GroupType groupType(const QModelIndex &index) const; |
| 226 | |
| 227 | /*! |
| 228 | * Returns The list of model indexes that have @ type as their group type. |
| 229 | * \sa groupType() |
| 230 | * \since 5.42 |
| 231 | */ |
| 232 | Q_INVOKABLE QModelIndexList groupIndexes(const GroupType type) const; |
| 233 | |
| 234 | /*! |
| 235 | * Returns A QAction with a proper translated label that can be used to trigger the requestTeardown() |
| 236 | * method for the place at index \a index. |
| 237 | * \sa requestTeardown() |
| 238 | */ |
| 239 | Q_INVOKABLE QAction *teardownActionForIndex(const QModelIndex &index) const; |
| 240 | |
| 241 | /*! |
| 242 | * Returns A QAction with a proper translated label that can be used to trigger the requestEject() |
| 243 | * method for the place at index \a index. |
| 244 | * \sa requestEject() |
| 245 | */ |
| 246 | Q_INVOKABLE QAction *ejectActionForIndex(const QModelIndex &index) const; |
| 247 | |
| 248 | /*! |
| 249 | * Returns A QAction with a proper translated label that can be used to open a partitioning menu for the device. nullptr if not a device. |
| 250 | */ |
| 251 | Q_INVOKABLE QAction *partitionActionForIndex(const QModelIndex &index) const; |
| 252 | |
| 253 | /*! |
| 254 | * Unmounts the place at index \a index by triggering the teardown functionality of its Solid device. |
| 255 | * \sa deviceForIndex() |
| 256 | */ |
| 257 | Q_INVOKABLE void requestTeardown(const QModelIndex &index); |
| 258 | |
| 259 | /*! |
| 260 | * Ejects the place at index \a index by triggering the eject functionality of its Solid device. |
| 261 | * \sa deviceForIndex() |
| 262 | */ |
| 263 | Q_INVOKABLE void requestEject(const QModelIndex &index); |
| 264 | |
| 265 | /*! |
| 266 | * Mounts the place at index \a index by triggering the setup functionality of its Solid device. |
| 267 | * \sa deviceForIndex() |
| 268 | */ |
| 269 | Q_INVOKABLE void requestSetup(const QModelIndex &index); |
| 270 | |
| 271 | /*! |
| 272 | * Adds a new place to the model. |
| 273 | * |
| 274 | * \a text The user-visible text for the place |
| 275 | * |
| 276 | * \a url The URL of the place. It will be stored in its QUrl::FullyEncoded string format. |
| 277 | * |
| 278 | * \a iconName The icon of the place |
| 279 | * |
| 280 | * \a appName If set as the value of QCoreApplication::applicationName(), will make the place visible only in this application. |
| 281 | */ |
| 282 | Q_INVOKABLE void addPlace(const QString &text, const QUrl &url, const QString &iconName = QString(), const QString &appName = QString()); |
| 283 | |
| 284 | /*! |
| 285 | * Adds a new place to the model. |
| 286 | * \a text The user-visible text for the place |
| 287 | * |
| 288 | * \a url The URL of the place. It will be stored in its QUrl::FullyEncoded string format. |
| 289 | * |
| 290 | * \a iconName The icon of the place |
| 291 | * |
| 292 | * \a appName If set as the value of QCoreApplication::applicationName(), will make the place visible only in this application. |
| 293 | * |
| 294 | * \a after The index after which the new place will be added. |
| 295 | */ |
| 296 | Q_INVOKABLE void addPlace(const QString &text, const QUrl &url, const QString &iconName, const QString &appName, const QModelIndex &after); |
| 297 | |
| 298 | /*! |
| 299 | * Edits the place with index \a index. |
| 300 | * |
| 301 | * \a text The new user-visible text for the place |
| 302 | * |
| 303 | * \a url The new URL of the place |
| 304 | * |
| 305 | * \a iconName The new icon of the place |
| 306 | * |
| 307 | * \a appName The new application-local filter for the place (see addPlace()). |
| 308 | */ |
| 309 | Q_INVOKABLE void |
| 310 | editPlace(const QModelIndex &index, const QString &text, const QUrl &url, const QString &iconName = QString(), const QString &appName = QString()); |
| 311 | |
| 312 | /*! |
| 313 | * Deletes the place with index \a index from the model. |
| 314 | */ |
| 315 | Q_INVOKABLE void removePlace(const QModelIndex &index) const; |
| 316 | |
| 317 | /*! |
| 318 | * Changes the visibility of the place with index \a index, but only if the place is not inside an hidden group. |
| 319 | * |
| 320 | * \a hidden Whether the place should be hidden or visible. |
| 321 | * \sa isGroupHidden() |
| 322 | */ |
| 323 | Q_INVOKABLE void setPlaceHidden(const QModelIndex &index, bool hidden); |
| 324 | |
| 325 | /*! |
| 326 | * Changes the visibility of the group with type \a type. |
| 327 | * |
| 328 | * \a hidden Whether the group should be hidden or visible. |
| 329 | * \sa isGroupHidden() |
| 330 | * \since 5.42 |
| 331 | */ |
| 332 | Q_INVOKABLE void setGroupHidden(const GroupType type, bool hidden); |
| 333 | |
| 334 | /*! |
| 335 | * Move place at \a itemRow to a position before \a row |
| 336 | * |
| 337 | * Returns whether the place has been moved. |
| 338 | * \since 5.41 |
| 339 | */ |
| 340 | Q_INVOKABLE bool movePlace(int itemRow, int row); |
| 341 | |
| 342 | /*! |
| 343 | * Returns the number of hidden places in the model. |
| 344 | * \sa isHidden() |
| 345 | */ |
| 346 | Q_INVOKABLE int hiddenCount() const; |
| 347 | |
| 348 | QVariant data(const QModelIndex &index, int role) const override; |
| 349 | |
| 350 | QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; |
| 351 | |
| 352 | QModelIndex parent(const QModelIndex &child) const override; |
| 353 | |
| 354 | QHash<int, QByteArray> roleNames() const override; |
| 355 | |
| 356 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; |
| 357 | |
| 358 | int columnCount(const QModelIndex &parent = QModelIndex()) const override; |
| 359 | |
| 360 | /*! |
| 361 | * Returns the closest item for the URL \a url. |
| 362 | * The closest item is defined as item which is equal to |
| 363 | * the URL or at least is a parent URL. If there are more than |
| 364 | * one possible parent URL candidates, the item which covers |
| 365 | * the bigger range of the URL is returned. |
| 366 | * |
| 367 | * Example: the url is '/home/peter/Documents/Music'. |
| 368 | * Available items are: |
| 369 | * - /home/peter |
| 370 | * - /home/peter/Documents |
| 371 | * |
| 372 | * The returned item will the one for '/home/peter/Documents'. |
| 373 | */ |
| 374 | QModelIndex closestItem(const QUrl &url) const; |
| 375 | |
| 376 | Qt::DropActions supportedDropActions() const override; |
| 377 | Qt::ItemFlags flags(const QModelIndex &index) const override; |
| 378 | QStringList mimeTypes() const override; |
| 379 | QMimeData *mimeData(const QModelIndexList &indexes) const override; |
| 380 | bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; |
| 381 | |
| 382 | /*! |
| 383 | * Reload bookmark information |
| 384 | * \since 5.41 |
| 385 | */ |
| 386 | Q_INVOKABLE void refresh() const; |
| 387 | |
| 388 | /*! |
| 389 | * Converts the URL, which contains "virtual" URLs for system-items like |
| 390 | * "timeline:/lastmonth" into a Query-URL "timeline:/2017-10" |
| 391 | * that will be handled by the corresponding KIO worker. |
| 392 | * Virtual URLs for bookmarks are used to be independent from |
| 393 | * internal format changes. |
| 394 | * |
| 395 | * \a an url |
| 396 | * |
| 397 | * Returns the converted URL, which can be handled by a KIO worker |
| 398 | * \since 5.41 |
| 399 | */ |
| 400 | static QUrl convertedUrl(const QUrl &url); |
| 401 | |
| 402 | /*! |
| 403 | * Set the URL schemes that the file widget should allow navigating to. |
| 404 | * |
| 405 | * If the returned list is empty, all schemes are supported. Examples for |
| 406 | * schemes are "file" or "ftp". |
| 407 | * |
| 408 | * \sa QFileDialog::setSupportedSchemes |
| 409 | * \since 5.43 |
| 410 | */ |
| 411 | void setSupportedSchemes(const QStringList &schemes); |
| 412 | |
| 413 | /*! |
| 414 | * Returns the URL schemes that the file widget should allow navigating to. |
| 415 | * |
| 416 | * If the returned list is empty, all schemes are supported. |
| 417 | * |
| 418 | * \sa QFileDialog::supportedSchemes |
| 419 | * \since 5.43 |
| 420 | */ |
| 421 | QStringList supportedSchemes() const; |
| 422 | |
| 423 | Q_SIGNALS: |
| 424 | /*! |
| 425 | * \a message An error message explaining what went wrong. |
| 426 | */ |
| 427 | void errorMessage(const QString &message); |
| 428 | |
| 429 | /*! |
| 430 | * Emitted after the Solid setup ends. |
| 431 | * |
| 432 | * \a success Whether the Solid setup has been successful. |
| 433 | * \sa requestSetup() |
| 434 | */ |
| 435 | void setupDone(const QModelIndex &index, bool success); |
| 436 | |
| 437 | /*! |
| 438 | * Emitted after the teardown of a device ends. |
| 439 | * |
| 440 | * \note In case of an error, the errorMessage() signal |
| 441 | * will also be emitted with a message describing the error. |
| 442 | * |
| 443 | * \a error Type of error that occurred, if any. |
| 444 | * |
| 445 | * \a errorData More information about the error, if any. |
| 446 | * |
| 447 | * \since 5.100 |
| 448 | */ |
| 449 | void teardownDone(const QModelIndex &index, Solid::ErrorType error, const QVariant &errorData); |
| 450 | |
| 451 | /*! |
| 452 | * Emitted whenever the visibility of the group \a group changes. |
| 453 | * |
| 454 | * \a hidden The new visibility of the group. |
| 455 | * |
| 456 | * \sa setGroupHidden() |
| 457 | * \since 5.42 |
| 458 | */ |
| 459 | void groupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden); |
| 460 | |
| 461 | /*! |
| 462 | * Called once the model has been reloaded |
| 463 | * |
| 464 | * \since 5.71 |
| 465 | */ |
| 466 | void reloaded(); |
| 467 | |
| 468 | /*! |
| 469 | * Emitted whenever the list of supported schemes has been changed |
| 470 | * |
| 471 | * \since 5.94 |
| 472 | */ |
| 473 | void supportedSchemesChanged(); |
| 474 | |
| 475 | private: |
| 476 | friend class KFilePlacesModelPrivate; |
| 477 | std::unique_ptr<KFilePlacesModelPrivate> d; |
| 478 | }; |
| 479 | |
| 480 | #endif |
| 481 | |