From b2567a5f34c30311dc5ebcf8fe8c4893278c7eb9 Mon Sep 17 00:00:00 2001 From: Alf Gaida Date: Sun, 22 Oct 2017 00:13:03 +0200 Subject: [PATCH] Adding upstream version 0.12.0. Signed-off-by: Alf Gaida --- AUTHORS | 2 +- CHANGELOG | 197 ++- CMakeLists.txt | 10 +- data/CMakeLists.txt | 10 + data/archivers.list | 35 + data/libfm-qt-mimetypes.xml | 52 + data/terminals.list | 77 + src/CMakeLists.txt | 91 +- src/appchoosercombobox.cpp | 179 +-- src/appchoosercombobox.h | 39 +- src/appchooserdialog.cpp | 420 ++--- src/appchooserdialog.h | 53 +- src/applaunchcontext.cpp | 16 +- src/appmenuview.cpp | 212 +-- src/appmenuview.h | 40 +- src/appmenuview_p.h | 59 +- src/archiver.h | 4 +- src/bookmarkaction.cpp | 8 +- src/bookmarkaction.h | 23 +- src/bookmarks.h | 163 -- src/browsehistory.cpp | 75 +- src/browsehistory.h | 145 +- src/cachedfoldermodel.cpp | 67 +- src/cachedfoldermodel.h | 26 +- src/colorbutton.cpp | 26 +- src/colorbutton.h | 20 +- src/config.h | 152 -- src/core/bookmarks.cpp | 162 ++ src/core/bookmarks.h | 96 ++ src/core/compat_p.h | 53 + src/core/copyjob.cpp | 453 ++++++ src/core/copyjob.h | 46 + src/core/cstrptr.h | 42 + src/core/deletejob.cpp | 151 ++ src/core/deletejob.h | 36 + src/core/dirlistjob.cpp | 178 +++ src/core/dirlistjob.h | 65 + src/core/filechangeattrjob.cpp | 9 + src/core/filechangeattrjob.h | 17 + src/core/fileinfo.cpp | 378 +++++ src/core/fileinfo.h | 278 ++++ src/core/fileinfo_p.h | 10 + src/core/fileinfojob.cpp | 42 + src/core/fileinfojob.h | 41 + src/core/filelinkjob.cpp | 9 + src/core/filelinkjob.h | 16 + src/core/filemonitor.cpp | 9 + src/core/filemonitor.h | 26 + src/core/fileoperationjob.cpp | 79 + src/core/fileoperationjob.h | 73 + src/core/filepath.cpp | 21 + src/core/filepath.h | 177 +++ src/core/filesysteminfojob.cpp | 24 + src/core/filesysteminfojob.h | 45 + src/core/folder.cpp | 908 +++++++++++ src/core/folder.h | 196 +++ src/core/gioptrs.h | 137 ++ src/core/gobjectptr.h | 104 ++ src/core/iconinfo.cpp | 138 ++ src/core/iconinfo.h | 112 ++ src/core/iconinfo_p.h | 126 ++ src/core/job.cpp | 57 + src/core/job.h | 120 ++ src/core/job_p.h | 26 + src/core/mimetype.cpp | 64 + src/core/mimetype.h | 172 +++ src/core/terminal.cpp | 127 ++ src/core/terminal.h | 17 + src/core/thumbnailer.cpp | 140 ++ src/core/thumbnailer.h | 37 + src/core/thumbnailjob.cpp | 266 ++++ src/core/thumbnailjob.h | 89 ++ src/core/totalsizejob.cpp | 144 ++ src/core/totalsizejob.h | 56 + src/core/trashjob.cpp | 73 + src/core/trashjob.h | 31 + src/core/untrashjob.cpp | 132 ++ src/core/untrashjob.h | 22 + src/core/userinfocache.cpp | 47 + src/core/userinfocache.h | 82 + src/core/volumemanager.cpp | 111 ++ src/core/volumemanager.h | 237 +++ src/createnewmenu.cpp | 96 +- src/createnewmenu.h | 19 +- src/customaction_p.h | 35 +- src/customactions/fileaction.cpp | 615 ++++++++ src/customactions/fileaction.h | 156 ++ src/customactions/fileactioncondition.cpp | 503 ++++++ src/customactions/fileactioncondition.h | 123 ++ src/customactions/fileactionprofile.cpp | 121 ++ src/customactions/fileactionprofile.h | 45 + src/deepcountjob.h | 128 -- src/dirlistjob.h | 148 -- src/dirtreemodel.cpp | 248 +-- src/dirtreemodel.h | 81 +- src/dirtreemodelitem.cpp | 574 ++++--- src/dirtreemodelitem.h | 83 +- src/dirtreeview.cpp | 450 +++--- src/dirtreeview.h | 80 +- src/dndactionmenu.cpp | 58 +- src/dndactionmenu.h | 16 +- src/dnddest.cpp | 8 +- src/dnddest.h | 26 +- src/dummymonitor.h | 35 - src/editbookmarksdialog.cpp | 120 +- src/editbookmarksdialog.h | 18 +- src/execfiledialog.cpp | 69 +- src/file-operation-dialog.ui | 18 +- src/file.h | 153 -- src/filedialog.cpp | 967 ++++++++++++ src/filedialog.h | 213 +++ src/filedialog.ui | 151 ++ src/fileinfo.h | 511 ------- src/fileinfojob.h | 138 -- src/filelauncher.cpp | 150 +- src/filelauncher.h | 77 +- src/filemenu.cpp | 626 ++++---- src/filemenu.h | 276 ++-- src/filemenu_p.h | 40 +- src/fileoperation.cpp | 425 +++--- src/fileoperation.h | 215 ++- src/fileoperationdialog.cpp | 240 +-- src/fileoperationdialog.h | 41 +- src/fileopsjob.h | 193 --- src/filepropsdialog.cpp | 850 ++++++----- src/filepropsdialog.h | 99 +- src/filesearch.ui | 6 + src/filesearchdialog.cpp | 235 +-- src/filesearchdialog.h | 51 +- src/folder.h | 228 --- src/folderconfig.h | 205 +-- src/folderitemdelegate.cpp | 551 ++++--- src/folderitemdelegate.h | 109 +- src/foldermenu.cpp | 389 ++--- src/foldermenu.h | 146 +- src/foldermodel.cpp | 849 +++++------ src/foldermodel.h | 150 +- src/foldermodelitem.cpp | 157 +- src/foldermodelitem.h | 75 +- src/folderview.cpp | 1681 ++++++++++++--------- src/folderview.h | 197 +-- src/folderview_p.h | 7 +- src/fontbutton.cpp | 36 +- src/fontbutton.h | 20 +- src/icon.h | 168 -- src/icontheme.cpp | 161 +- src/icontheme.h | 42 +- src/job.h | 225 --- src/libfmqt.cpp | 76 +- src/libfmqt.h | 10 +- src/list.h | 153 -- src/mimetype.h | 163 -- src/mountoperation.cpp | 257 ++-- src/mountoperation.h | 194 +-- src/mountoperationpassworddialog.cpp | 167 +- src/mountoperationpassworddialog_p.h | 34 +- src/mountoperationquestiondialog.cpp | 57 +- src/mountoperationquestiondialog_p.h | 19 +- src/navhistory.h | 192 --- src/pathbar.cpp | 477 +++--- src/pathbar.h | 55 +- src/pathbar_p.h | 66 +- src/pathedit.cpp | 317 ++-- src/pathedit.h | 31 +- src/pathedit_p.h | 24 +- src/placesmodel.cpp | 960 ++++++------ src/placesmodel.h | 159 +- src/placesmodelitem.cpp | 202 +-- src/placesmodelitem.h | 146 +- src/placesview.cpp | 629 ++++---- src/placesview.h | 93 +- src/proxyfoldermodel.cpp | 396 ++--- src/proxyfoldermodel.h | 95 +- src/renamedialog.cpp | 176 +-- src/renamedialog.h | 62 +- src/sidepane.cpp | 316 ++-- src/sidepane.h | 119 +- src/templates.h | 4 +- src/terminal.h | 143 -- src/tests/test-filedialog.cpp | 48 + src/tests/test-folder.cpp | 44 + src/tests/test-folderview.cpp | 48 + src/tests/test-placesview.cpp | 20 + src/tests/test-volumemanager.cpp | 24 + src/thumbnailer.h | 158 -- src/thumbnailloader.cpp | 199 --- src/thumbnailloader.h | 98 -- src/utilities.cpp | 491 +++--- src/utilities.h | 38 +- src/xdndworkaround.cpp | 368 ++--- src/xdndworkaround.h | 41 +- 191 files changed, 19121 insertions(+), 11682 deletions(-) create mode 100644 data/CMakeLists.txt create mode 100644 data/archivers.list create mode 100644 data/libfm-qt-mimetypes.xml create mode 100644 data/terminals.list delete mode 100644 src/bookmarks.h delete mode 100644 src/config.h create mode 100644 src/core/bookmarks.cpp create mode 100644 src/core/bookmarks.h create mode 100644 src/core/compat_p.h create mode 100644 src/core/copyjob.cpp create mode 100644 src/core/copyjob.h create mode 100644 src/core/cstrptr.h create mode 100644 src/core/deletejob.cpp create mode 100644 src/core/deletejob.h create mode 100644 src/core/dirlistjob.cpp create mode 100644 src/core/dirlistjob.h create mode 100644 src/core/filechangeattrjob.cpp create mode 100644 src/core/filechangeattrjob.h create mode 100644 src/core/fileinfo.cpp create mode 100644 src/core/fileinfo.h create mode 100644 src/core/fileinfo_p.h create mode 100644 src/core/fileinfojob.cpp create mode 100644 src/core/fileinfojob.h create mode 100644 src/core/filelinkjob.cpp create mode 100644 src/core/filelinkjob.h create mode 100644 src/core/filemonitor.cpp create mode 100644 src/core/filemonitor.h create mode 100644 src/core/fileoperationjob.cpp create mode 100644 src/core/fileoperationjob.h create mode 100644 src/core/filepath.cpp create mode 100644 src/core/filepath.h create mode 100644 src/core/filesysteminfojob.cpp create mode 100644 src/core/filesysteminfojob.h create mode 100644 src/core/folder.cpp create mode 100644 src/core/folder.h create mode 100644 src/core/gioptrs.h create mode 100644 src/core/gobjectptr.h create mode 100644 src/core/iconinfo.cpp create mode 100644 src/core/iconinfo.h create mode 100644 src/core/iconinfo_p.h create mode 100644 src/core/job.cpp create mode 100644 src/core/job.h create mode 100644 src/core/job_p.h create mode 100644 src/core/mimetype.cpp create mode 100644 src/core/mimetype.h create mode 100644 src/core/terminal.cpp create mode 100644 src/core/terminal.h create mode 100644 src/core/thumbnailer.cpp create mode 100644 src/core/thumbnailer.h create mode 100644 src/core/thumbnailjob.cpp create mode 100644 src/core/thumbnailjob.h create mode 100644 src/core/totalsizejob.cpp create mode 100644 src/core/totalsizejob.h create mode 100644 src/core/trashjob.cpp create mode 100644 src/core/trashjob.h create mode 100644 src/core/untrashjob.cpp create mode 100644 src/core/untrashjob.h create mode 100644 src/core/userinfocache.cpp create mode 100644 src/core/userinfocache.h create mode 100644 src/core/volumemanager.cpp create mode 100644 src/core/volumemanager.h create mode 100644 src/customactions/fileaction.cpp create mode 100644 src/customactions/fileaction.h create mode 100644 src/customactions/fileactioncondition.cpp create mode 100644 src/customactions/fileactioncondition.h create mode 100644 src/customactions/fileactionprofile.cpp create mode 100644 src/customactions/fileactionprofile.h delete mode 100644 src/deepcountjob.h delete mode 100644 src/dirlistjob.h delete mode 100644 src/dummymonitor.h delete mode 100644 src/file.h create mode 100644 src/filedialog.cpp create mode 100644 src/filedialog.h create mode 100644 src/filedialog.ui delete mode 100644 src/fileinfo.h delete mode 100644 src/fileinfojob.h delete mode 100644 src/fileopsjob.h delete mode 100644 src/folder.h delete mode 100644 src/icon.h delete mode 100644 src/job.h delete mode 100644 src/list.h delete mode 100644 src/mimetype.h delete mode 100644 src/navhistory.h delete mode 100644 src/terminal.h create mode 100644 src/tests/test-filedialog.cpp create mode 100644 src/tests/test-folder.cpp create mode 100644 src/tests/test-folderview.cpp create mode 100644 src/tests/test-placesview.cpp create mode 100644 src/tests/test-volumemanager.cpp delete mode 100644 src/thumbnailer.h delete mode 100644 src/thumbnailloader.cpp delete mode 100644 src/thumbnailloader.h diff --git a/AUTHORS b/AUTHORS index 83cd2cd..40631a6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,4 +3,4 @@ Upstream Authors: Hong Jen Yee (PCMan) Copyright: - Copyright (c) 2013-2016 LXQt team + Copyright (c) 2013-2017 LXQt team diff --git a/CHANGELOG b/CHANGELOG index 3277cb2..55392c4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,202 @@ -libfm-qt-0.11.2 / 2016-12-21 +libfm-qt-0.12.0 / 2017-10-21 ============================ + * Add data transferred to file operation dialog. + * Bump versions + * Disable context-menu actions that cannot be used + * Don't export github templates + * Fix partially visible toggled path buttons + * Add functions to get and set search settings + * Fix mistakes in listview column width calculation + * Add archiver separator only when needed + * Add a separator before archiver actions + * Enable XDS subfolder drop + * UI improvements for Fm::MountOperationPasswordDialog() + * Respect inactiveness when drawing text + * Grey out files that have been Ctrl-X'ed (#88) + * Ignore button for error dialog + * Inline renaming for detailed list view (#110) + * Remove redundant code. + * Prefer local paths if they exist + * Removed QFileInfo (as @PCMan recommended) + * Simplification, optimization and a fix + * Really focus text entry on showing dialog + * Two small fixes + * Keep selection on reloading (if not CPU-intensive) + * Added back/forward buttons and fixed 3 issues + * Reload button, hidden shortcut and a fix + * Implement FileDialog::selectMimeTypeFilter() and QString FileDialog::selectedMimeTypeFilter(). + * Initialize folder_ to null + * Fixed the quote issue + * Always preserve explicitly set labels + * Update OK button text and state when needed + * Initialize FileInfo::isShortcut_ (#113) + * Set the selected index current + * Fixd open/save and overwrite prompt + * Set open/save text + * Several important fixes + * Added a missing change + * Preliminary fixes + * Hide UI implementation details for Fm::FileDialog. + * Revert the backward incompatible changes in the constructor of Fm::FolderView. + * Fix a bug in creating new folder for Fm::FileDialog. + * Implement toolbar and quick view mode switches for the Fm::FileDialog class. + * Correctly check file types and test the existence of the selected files as needed. + * Correctly handle item activation. + * Correctly handle filename selection for Fm::FileDialog. + * Correctly handle selected files. + * Implement filename filtering for Fm::FileDialog. + * Check nullity of FileInfo before calling FolderMenu + * Arrange Custom Actions + * Support custom folder icons + * Fix multiple pasting of the same cut file(s) + * Fix KDE clipboard tag for cut file(s) + * Add a basic skeleton for Fm::FileDialog. + * Check nullity of QMimeData (#109) + * MountOperationQuestionDialog: Fix handling responses + * Fix all height issues in horizontal layouts (#103) + * Removed a redundant variable (left there from old tests) + * Fix major bugs in Directory Tree + * Consider desktop text color, now that everything is done here + * Inline Renaming + * Fix compact view regression (#102) + * Fix detailed list crash on double clicking folders + * Removed my garbage + * Fixed issues about spacings and click region + * Make Fm::FolderItemDelegate support painting text shadows and margins so it can completely replace PCManFM::DesktopItemDelegate. + * Avoid using grid size on QListView since this disables any spacing settings. + * liblxqt make no sense for libfm-qt + * Copied issue template + * Add noexcept to move constructors and operator=() so they can be used with STL containers. + * FolderView: Optimize selectAll() (#97) + * Emit fileSizeChanged when needed + * Drops Qt5Core_VERSION_STRING (#96) + * Update size column info (#90) + * Fix Detailed List view DND (#94) + * folderview: Don't allow D&D by Back or Forward + * More fixes (#87) + * Added a missing change signal (#86) + * Fix single items when seaching (#85) + * Check for nullity of IconInfo (#84) + * Address compiler warnings + * Remove addRoots() return type + * Remove the unused data/translations/ entry + * Fix broken folder unmount message caused by incorrect FilePath & GFile* comparison. (#80) + * Remove some superfluous semicolons that lead to pedantic warnings (#79) + * Ensure one item per file (#77) + * Fix the broken filesystem status (free disk space) display. (#78) + * Don't make items current on mouseover (#72) + * Fixes a FTBFS in superbuild mode (#75) + * Replace start tilde in PathEdit (#73) + * Really cancel pending thumbnail jobs on chdir (#74) + * Move fixes (#70) + * Fix invalid pointers (#69) + * Continue file selection on next/previous row (#76) + * Code reformat: use 4-space indentation to match the coding style of other LXQt components. + * Make all constructors explicit so we don't get unwanted object construction by C++. + * Prevent a crash since GObjectPtr's move ctor frees resources + * GObjectPtr: Detect & handle "self-assignment" + * Fix compatibility with Qt 5.6. + * No thumbnail for thumbnails + * Fix thumbnail update + * Fixed `PathBar::setPath()` + * Use real name for renaming + * Prevent a crash plus fallback icon + * Fix custom actions + * volumemanager: Return IconInfo as shared_ptr + * FolderModelItem: Check IconInfo existence + * Bookmarks: Check validity of insert position + * Fix a potential crash of bookmark items when the format of the bookmark file is not correct. + * Only load desktop entry files for native filesystems. + * Fix the missing icon and display name handling of desktop entry files in Fm::FileInfo. + * IconEngine: Use weak_ptr for the parent IconInfo + * PathBar: Avoid leak upon QLayout::replaceWidget() + * Use const iterators + * Use the new lxqt-build-tools new FindExif module + * Fix the incorrect header inclusion in fileoperation.cpp. + * Fix incorrect #include of libfmqtglobals.h. + * Fix a bug when copying to and pasting from "x-special/gnome-copied-files" mime-type. + * Fix bugs in the Custom Actions C++ port. + * Try to port libfm custom actions to C++. + * Try to update the content of a folder after its mount status is changed. Handle cancelled dir listing job properly. + * Rename namespace Fm2 to Fm. + * Remove unused header files of the old C API wrappers. + * Fix bugs in search:// support and finish porting file searching to C++. Fix several bugs in Fm2::FileInfo which caused by null parent dir. + * Add a missing test case for places view. + * Try to add support for menu:// and search:// URI scheme implemented in libfm. + * Correctly destroy Fm2::Job objects when their worker threads terminate. + * Fix incorrect handling of PathBar button creation which causes infinite loop when the underlying GFile implementation contains bugs. + * Fix incorrect path of application menu URI. + * Fix QThread related bugs in PathEdit which causes memory leaks. + * Fix a bug in DirTreeModelItem causing crashes. Also speed up batch insertion of large amount of items. + * Use const iterators (#61) + * Fix the broken folder reload(). + * Make all Fm2::Job derived classes reimplement exec() instead of run() method. The new run() method will emit finished() signal automatically when exec() returns. + * Fix memory leaks caused by incorrect use of QThread. + * Fix a memory leak in Fm::ThumbnailJob. + * Fix memory leaks caused by broken cache. + * Fix wrong size of generated thumbnails by external thumbnailers. + * Fix memory bugs in Fm2::GErrorPtr and improve the error handling of Fm2::Job and Fm2::Folder. + * Fix some errors related to incorrect use of std::remove() in Fm2::Folder. Replace QList with std::vector and use binary search when inserting items for the Fm::DirTreeModelItem. + * Change the handling of Fm::FolderView::selChanged signal to make it more efficient. + * Port to the new Fm2::TotalSizeJob API. + * Fix compatibility with libfm custom actions. + * Add some compatibility API which helps migrating old C APIs to the new C++ implementation. + * Convert datetime to locale-aware strings using QDateTime. + * Use QCollator to perform file sorting. + * Fix detailed view. + * Finish porting DirTreeModel to the new API. Fix bugs in Fm2::FilePath and Fm2::FileInfo APIs. + * Port the libfm terminal emulator related APIs to C++. + * Rename some methods in Fm2::Folder and Fm2::FileInfo for consistency. + * Port to the new IconInfo API and deprecate manual icon update when the theme is changed. + * Rename Fm::Icon to Fm::IconInfo. + * Port emblem support to the new libfm C++ API. + * Remove unused files, including some old APIs. Replace QVector in BrowseHistory with STL vector. + * Fix a bug in Fm::FileMenu. + * Port file-click handling to the new C++ API. + * Fix bugs in Fm::PathBar getting wrong path when a path button is toggled. + * Remove Fm::FilePath(const char* path_str) to avoid ambiguity. + * Replace all NULL with C++ 11 nullptr; + * Fix FilePath related errors caused by incomplete porting. + * Make Fm::FolderConfig support the new Fm::FilePath class. + * Fix Fm::BookmarkAction to use the new C++ API. + * Fix missing icons of places view caused by memory errors. + * Fix memory errors in Fm2::Bookmarks::reorder(). Add a small test case for the places view. + * Share the same places model among all views. + * Port most of the existing UI-related APIs to the new C++ APIs (excluding the file operations). + * Port the path bar to the new Fm2 API. + * Implement VolumeManager class which is a QObject wrapper of gio GVolumeMonitor. + * Add some getters for Volume and Mount classes. + * Properly associate external thumbnailers with mimetypes they support and fix thumbnail generation from thumbnailers. + * Start porting thumbnail loaders to the new C++ APIs. Add new Fm::ThumbnailJob used to load thumbnails for a given file list. Add commonDirPath paramter to Fm::FileInfoJob to reduce memory usage. + * Add the missing test case for folder view. + * Start porting Fm::FolderModel and Fm::FolderView to the new libfm core c++ API. + * Work in progress. + * Add a c++ wrapper for GFileMonitor. Add LIBFM_QT_API declaration for all public headers. + * Port error handling mechanism of FmJob to C++ and improve the GError wrapper class. + * Bump year + * Add gioptrs.h which defines smart pointer types wrapping gio related data types. Add some basic skeleton for various I/O jobs classes. + * Start porting Copyjob and add basic skeleton for untrash and symlink jobs. + * Finish porting FmFolder to C++. + * Add a very simple test case to test the new Fm core C++ code. Fix bugs in smart pointers and do empty base class optimization for CStrPtr. + * Improve Fm::Folder. + * Rename UserInfo to UserInfoCache. + * Port Fm::Bookmarks to C++. + * Port FmDeepCountJob to C++. + * Add basic skeletion to Fm::VolumeManager. + * Implement Fm2::UserInfo, which is a cache for uid/gid name mapping. + * Add basic skeleton for other C++ classes waiting for porting. + * Add GSignalHandler to do automatic signal/slot connection management with type safety for GObject. + * Add basic skeleton for the C++ 11 port of FmFileInfoJob. + * Try to port Fm::Folder and Fm::DirListJob to Qt5 and C++ 11. + * Try to port FmIcon, FmFileInfo, and FmMimeType of libfm to clean C++. + * Add smart pointer for GObject-derived classes and add Fm::FilePath class which wraps GFile. + +0.11.2 / 2016-12-21 +=================== + + * Release 0.11.2: Update changelog * Fix enabled state of path arrows on starting (#58) * bump patch version (#56) * Use QByteArray::constData() where we can (#57) diff --git a/CMakeLists.txt b/CMakeLists.txt index d8f068d..064bdd7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,8 @@ project(libfm-qt) set(LIBFM_QT_LIBRARY_NAME "fm-qt" CACHE STRING "fm-qt") set(LIBFM_QT_VERSION_MAJOR 0) -set(LIBFM_QT_VERSION_MINOR 11) -set(LIBFM_QT_VERSION_PATCH 2) +set(LIBFM_QT_VERSION_MINOR 12) +set(LIBFM_QT_VERSION_PATCH 0) set(LIBFM_QT_VERSION ${LIBFM_QT_VERSION_MAJOR}.${LIBFM_QT_VERSION_MINOR}.${LIBFM_QT_VERSION_PATCH}) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") @@ -24,7 +24,7 @@ set(LIBFM_QT_LIB_SOVERSION "3") set(REQUIRED_QT_VERSION "5.2") set(REQUIRED_LIBFM_VERSION "1.2.0") set(REQUIRED_LIBMENUCACHE_VERSION "0.4.0") -set(REQUIRED_LXQT_BUILD_TOOLS_VERSION "0.3.0") +set(REQUIRED_LXQT_BUILD_TOOLS_VERSION "0.4.0") if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) @@ -37,9 +37,10 @@ find_package(Qt5X11Extras "${REQUIRED_QT_VERSION}" REQUIRED) find_package(lxqt-build-tools "${REQUIRED_LXQT_BUILD_TOOLS_VERSION}" REQUIRED) find_package(Fm "${REQUIRED_LIBFM_VERSION}" REQUIRED) find_package(MenuCache "${REQUIRED_LIBMENUCACHE_VERSION}" REQUIRED) +find_package(Exif REQUIRED) find_package(XCB REQUIRED) -message(STATUS "Building ${PROJECT_NAME} with Qt ${Qt5Core_VERSION_STRING}") +message(STATUS "Building ${PROJECT_NAME} with Qt ${Qt5Core_VERSION}") option(UPDATE_TRANSLATIONS "Update source translation translations/*.ts files" OFF) include(GNUInstallDirs) @@ -65,6 +66,7 @@ install(FILES ) add_subdirectory(src) +add_subdirectory(data) # add Doxygen support to generate API docs # References: diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt new file mode 100644 index 0000000..9e9cd2c --- /dev/null +++ b/data/CMakeLists.txt @@ -0,0 +1,10 @@ +install(FILES + "archivers.list" + "terminals.list" + DESTINATION "${CMAKE_INSTALL_DATADIR}/libfm-qt" +) + +install(FILES + "libfm-qt-mimetypes.xml" + DESTINATION "${CMAKE_INSTALL_DATADIR}/mime/packages" +) diff --git a/data/archivers.list b/data/archivers.list new file mode 100644 index 0000000..317953a --- /dev/null +++ b/data/archivers.list @@ -0,0 +1,35 @@ +[file-roller] +create=file-roller --add %U +extract=file-roller --extract %U +extract_to=file-roller --extract-to %d %U +mime_types=application/x-7z-compressed;application/x-7z-compressed-tar;application/x-ace;application/x-alz;application/x-ar;application/x-arj;application/x-bzip;application/x-bzip-compressed-tar;application/x-bzip1;application/x-bzip1-compressed-tar;application/x-cabinet;application/x-cbr;application/x-cbz;application/x-cd-image;application/x-compress;application/x-compressed-tar;application/x-cpio;application/x-deb;application/x-ear;application/x-ms-dos-executable;application/x-gtar;application/x-gzip;application/x-gzpostscript;application/x-java-archive;application/x-lha;application/x-lhz;application/x-lzip;application/x-lzip-compressed-tar;application/x-lzma;application/x-lzma-compressed-tar;application/x-lzop;application/x-lzop-compressed-tar;application/x-rar;application/x-rar-compressed;application/vnd.rar;application/x-rpm;application/x-rzip;application/x-tar;application/x-tarz;application/x-stuffit;application/x-war;application/x-xz;application/x-xz-compressed-tar;application/x-zip;application/x-zip-compressed;application/x-zoo;application/zip;multipart/x-zip; +supports_uris=true + +[xarchiver] +create=xarchiver --add-to %F +extract=xarchiver --extract %F +extract_to=xarchiver --extract-to %d %F +mime_types=application/x-arj;application/arj;application/x-bzip;application/x-bzip-compressed-tar;application/x-gzip;application/x-rar;application/x-rar-compressed;application/vnd.rar;application/x-tar;application/x-zip;application/x-zip-compressed;application/zip;multipart/x-zip;application/x-7z-compressed;application/x-compressed-tar;application/x-bzip2;application/x-bzip2-compressed-tar;application/x-lzma-compressed-tar;application/x-lzma;application/x-deb;application/deb;application/vnd.debian.binary-package;application/x-xz;application/x-xz-compressed-tar;application/x-rpm;application/x-source-rpm;application/x-lzop;application/x-lzop-compressed-tar;application/x-tzo;application/x-war;application/x-compress;application/x-tarz;application/x-java-archive;application/x-lha;application/x-lhz; + +[squeeze] +create=squeeze --new %F +extract=squeeze --extract %F +extract_to=squeeze --extract-to %d %F +mime_types=application/x-bzip-compressed-tar;application/x-bzip2-compressed-tar;application/x-compressed-tar;application/x-tar;application/x-tarz;application/x-tzo;application/x-zip;application/x-zip-compressed;application/zip;application/x-rar;application/vnd.rar;application/x-gzip;application/x-bzip;application/x-lzop;application/x-compress; + +[engrampa] +create=engrampa --add %U +extract=engrampa --extract %U +extract_to=engrampa --extract-to %d %U +mime_types=application/x-7z-compressed;application/x-7z-compressed-tar;application/x-ace;application/x-alz;application/x-ar;application/x-arj;application/x-bzip;application/x-bzip-compressed-tar;application/x-bzip1;application/x-bzip1-compressed-tar;application/x-cabinet;application/x-cbr;application/x-cbz;application/x-cd-image;application/x-compress;application/x-compressed-tar;application/x-cpio;application/x-deb;application/x-ear;application/x-ms-dos-executable;application/x-gtar;application/x-gzip;application/x-gzpostscript;application/x-java-archive;application/x-lha;application/x-lhz;application/x-lzip;application/x-lzip-compressed-tar;application/x-lzma;application/x-lzma-compressed-tar;application/x-lzop;application/x-lzop-compressed-tar;application/x-rar;application/x-rar-compressed;application/vnd.rar;application/x-rpm;application/x-rzip;application/x-tar;application/x-tarz;application/x-stuffit;application/x-war;application/x-xz;application/x-xz-compressed-tar;application/x-zip;application/x-zip-compressed;application/x-zoo;application/zip;multipart/x-zip; +supports_uris=true + +# The KDE archiver Ark +# Here we use %F instead of %U since KDE programs do not know the URI provided by gvfs. +# GIO will pass FUSE-based file paths to the KDE programs, which should still work. +[ark] +create=ark --add --dialog %F +extract=ark --batch --dialog %F +extract_to=ark --batch --destination %d %F +mime_types=application/x-tar;application/x-compressed-tar;application/x-bzip-compressed-tar;application/x-tarz;application/x-xz-compressed-tar;application/x-lzma-compressed-tar;application/x-deb;application/x-cd-image;application/x-bcpio;application/x-cpio;application/x-cpio-compressed;application/x-sv4cpio;application/x-sv4crc;application/x-rpm;application/x-source-rpm;application/vnd.ms-cab-compressed;application/x-servicepack;application/x-rar;application/vnd.rar;application/x-7z-compressed;application/x-java-archive;application/zip;application/x-compress;application/x-gzip;application/x-bzip;application/x-bzip2;application/x-lzma;application/x-xz;application/lha;application/x-lha;application/maclha; +supports_uris=true diff --git a/data/libfm-qt-mimetypes.xml b/data/libfm-qt-mimetypes.xml new file mode 100644 index 0000000..7f509a9 --- /dev/null +++ b/data/libfm-qt-mimetypes.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + Windows installer + Windows 安裝程式 + + + + + MS VBScript + + + + + C# source + C# 程式碼 + + + + + 應用程式捷徑 + + + + + + + + + + + + + + diff --git a/data/terminals.list b/data/terminals.list new file mode 100644 index 0000000..ec6d202 --- /dev/null +++ b/data/terminals.list @@ -0,0 +1,77 @@ +[xterm] +open_arg=-e +noclose_arg=-hold -e +desktop_id=xterm.desktop + +[uxterm] +open_arg=-e +noclose_arg=-hold -e + +[lxterminal] +open_arg=-e +desktop_id=lxterminal.desktop + +[konsole] +open_arg=-e +noclose_arg=--noclose -e +desktop_id=konsole.desktop + +[xfce4-terminal] +open_arg=-x +noclose_arg=--hold -x +desktop_id=xfce4-terminal.desktop + +[terminator] +open_arg=-x +desktop_id=terminator.desktop + +[rxvt] +open_arg=-e + +[urxvt] +open_arg=-e +noclose_arg=-hold -e +desktop_id=rxvt-unicode.desktop + +[eterm] +open_arg=-e +noclose_arg=--pause -e +desktop_id=eterm.desktop + +[gnome-terminal] +open_arg=-x +desktop_id=gnome-terminal.desktop + +[wterm] +open_arg=-e + +[roxterm] +open_arg=-e +desktop_id=roxterm.desktop + +[sakura] +open_arg=-e +desktop_id=sakura.desktop + +[qterminal] +open_arg=-e +desktop_id=qterminal.desktop + +[lilyterm] +open_arg=-e +noclose_arg=--hold -e +desktop_id=lilyterm.desktop + +[urxvtc] +open_arg=-e +noclose_arg=-hold -e + +[terminology] +open_arg=-e +noclose_arg=--hold -e +desktop_id=terminology.desktop + +[termite] +open_arg=-e +noclose_arg=--hold -e +desktop_id=termite.desktop diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5ea17d3..b586381 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,41 @@ +set(libfm_core_SRCS + # core data structures + core/gobjectptr.h + core/filepath.cpp + core/iconinfo.cpp + core/mimetype.cpp + core/fileinfo.cpp + core/folder.cpp + core/filemonitor.cpp + # i/o jobs + core/job.cpp + core/copyjob.cpp + core/deletejob.cpp + core/dirlistjob.cpp + core/filechangeattrjob.cpp + core/fileinfojob.cpp + core/filelinkjob.cpp + core/fileoperationjob.cpp + core/filesysteminfojob.cpp + core/job.cpp + core/totalsizejob.cpp + core/trashjob.cpp + core/untrashjob.cpp + core/thumbnailjob.cpp + # extra desktop services + core/bookmarks.cpp + core/volumemanager.cpp + core/userinfocache.cpp + core/thumbnailer.cpp + core/terminal.cpp + # custom actions + customactions/fileaction.cpp + customactions/fileactionprofile.cpp + customactions/fileactioncondition.cpp +) + set(libfm_SRCS + ${libfm_core_SRCS} libfmqt.cpp bookmarkaction.cpp sidepane.cpp @@ -36,12 +73,12 @@ set(libfm_SRCS utilities.cpp dndactionmenu.cpp editbookmarksdialog.cpp - thumbnailloader.cpp execfiledialog.cpp appchoosercombobox.cpp appmenuview.cpp appchooserdialog.cpp filesearchdialog.cpp + filedialog.cpp fm-search.c # might be moved to libfm later xdndworkaround.cpp ) @@ -55,6 +92,7 @@ set(libfm_UIS exec-file.ui app-chooser-dialog.ui filesearch.ui + filedialog.ui ) qt5_wrap_ui(libfm_UIS_H ${libfm_UIS}) @@ -80,11 +118,6 @@ add_library(${LIBFM_QT_LIBRARY_NAME} SHARED ${QM_FILES} ) -# only turn on custom actions support if it is enabled in libfm. -if(EXISTS "${FM_INCLUDE_DIR}/libfm/fm-actions.h") - target_compile_definitions(${LIBFM_QT_LIBRARY_NAME} PRIVATE CUSTOM_ACTIONS) -endif() - install(EXPORT "${LIBFM_QT_LIBRARY_NAME}-targets" DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}" @@ -97,6 +130,7 @@ target_link_libraries(${LIBFM_QT_LIBRARY_NAME} ${FM_LIBRARIES} ${MENUCACHE_LIBRARIES} ${XCB_LIBRARIES} + ${EXIF_LIBRARIES} ) # set libtool soname @@ -112,6 +146,7 @@ target_include_directories(${LIBFM_QT_LIBRARY_NAME} "${FM_INCLUDE_DIR}/libfm" # to workaround incorrect #include in fm-actions. "${MENUCACHE_INCLUDE_DIRS}" "${XCB_INCLUDE_DIRS}" + "${EXIF_INCLUDE_DIRS}" INTERFACE "$" "$" @@ -128,11 +163,11 @@ install(FILES COMPONENT Devel ) -file(GLOB libfm_HS "${CMAKE_CURRENT_SOURCE_DIR}/*.h") # install include header files (FIXME: can we make this cleaner? should dir name be versioned?) -install(FILES ${libfm_HS} +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libfm-qt" COMPONENT Devel + FILES_MATCHING PATTERN "*.h" ) generate_export_header(${LIBFM_QT_LIBRARY_NAME} @@ -140,10 +175,14 @@ generate_export_header(${LIBFM_QT_LIBRARY_NAME} ) # InTree build -file(COPY ${libfm_HS} ${CMAKE_CURRENT_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}_export.h +file(COPY ${CMAKE_CURRENT_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}_export.h DESTINATION "${LIBFM_QT_INTREE_INCLUDE_DIR}/libfm-qt" ) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/ + DESTINATION "${LIBFM_QT_INTREE_INCLUDE_DIR}/libfm-qt" + FILES_MATCHING PATTERN "*.h" +) configure_package_config_file( "${PROJECT_SOURCE_DIR}/cmake/fm-qt-config.cmake.in" @@ -194,3 +233,37 @@ endif() # prevent the generated files from being deleted during make cleaner set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM true) + + +set(TEST_LIBRARIES + Qt5::Core + Qt5::Widgets + ${FM_LIBRARIES} + ${LIBFM_QT_LIBRARY_NAME} +) +# some simple test cases +add_executable("test-folder" + tests/test-folder.cpp +) +target_link_libraries("test-folder" ${TEST_LIBRARIES}) + +add_executable("test-folderview" + tests/test-folderview.cpp +) +target_link_libraries("test-folderview" ${TEST_LIBRARIES}) + +add_executable("test-filedialog" + tests/test-filedialog.cpp +) +target_link_libraries("test-filedialog" ${TEST_LIBRARIES}) + +add_executable("test-volumemanager" + tests/test-volumemanager.cpp +) +target_link_libraries("test-volumemanager" ${TEST_LIBRARIES}) + +add_executable("test-placesview" + tests/test-placesview.cpp +) +target_link_libraries("test-placesview" ${TEST_LIBRARIES}) + diff --git a/src/appchoosercombobox.cpp b/src/appchoosercombobox.cpp index bdd6cfe..bf7d0f1 100644 --- a/src/appchoosercombobox.cpp +++ b/src/appchoosercombobox.cpp @@ -21,124 +21,113 @@ #include "icontheme.h" #include "appchooserdialog.h" #include "utilities.h" +#include "core/iconinfo.h" namespace Fm { AppChooserComboBox::AppChooserComboBox(QWidget* parent): - QComboBox(parent), - mimeType_(NULL), - appInfos_(NULL), - defaultApp_(NULL), - defaultAppIndex_(-1), - prevIndex_(0), - blockOnCurrentIndexChanged_(false) { - - // the new Qt5 signal/slot syntax cannot handle overloaded methods by default - // hence a type-casting is needed here. really ugly! - // reference: http://qt-project.org/forums/viewthread/21513 - connect((QComboBox*)this, static_cast(&QComboBox::currentIndexChanged), this, &AppChooserComboBox::onCurrentIndexChanged); + QComboBox(parent), + defaultAppIndex_(-1), + prevIndex_(0), + blockOnCurrentIndexChanged_(false) { + + // the new Qt5 signal/slot syntax cannot handle overloaded methods by default + // hence a type-casting is needed here. really ugly! + // reference: http://qt-project.org/forums/viewthread/21513 + connect((QComboBox*)this, static_cast(&QComboBox::currentIndexChanged), this, &AppChooserComboBox::onCurrentIndexChanged); } AppChooserComboBox::~AppChooserComboBox() { - if(mimeType_) - fm_mime_type_unref(mimeType_); - if(defaultApp_) - g_object_unref(defaultApp_); - // delete GAppInfo objects stored for Combobox - if(appInfos_) { - g_list_foreach(appInfos_, (GFunc)g_object_unref, NULL); - g_list_free(appInfos_); - } } -void AppChooserComboBox::setMimeType(FmMimeType* mimeType) { - clear(); - if(mimeType_) - fm_mime_type_unref(mimeType_); - - mimeType_ = fm_mime_type_ref(mimeType); - if(mimeType_) { - const char* typeName = fm_mime_type_get_type(mimeType_); - defaultApp_ = g_app_info_get_default_for_type(typeName, FALSE); - appInfos_ = g_app_info_get_all_for_type(typeName); - int i = 0; - for(GList* l = appInfos_; l; l = l->next, ++i) { - GAppInfo* app = G_APP_INFO(l->data); - GIcon* gicon = g_app_info_get_icon(app); - QString name = QString::fromUtf8(g_app_info_get_name(app)); - // QVariant data = qVariantFromValue(app); - // addItem(IconTheme::icon(gicon), name, data); - addItem(IconTheme::icon(gicon), name); - if(g_app_info_equal(app, defaultApp_)) - defaultAppIndex_ = i; +void AppChooserComboBox::setMimeType(std::shared_ptr mimeType) { + clear(); + defaultApp_.reset(); + appInfos_.clear(); + + mimeType_ = std::move(mimeType); + if(mimeType_) { + const char* typeName = mimeType_->name(); + defaultApp_ = Fm::GAppInfoPtr{g_app_info_get_default_for_type(typeName, FALSE), false}; + GList* appInfos_glist = g_app_info_get_all_for_type(typeName); + int i = 0; + for(GList* l = appInfos_glist; l; l = l->next, ++i) { + Fm::GAppInfoPtr app{G_APP_INFO(l->data), false}; + GIcon* gicon = g_app_info_get_icon(app.get()); + addItem(gicon ? Fm::IconInfo::fromGIcon(gicon)->qicon(): QIcon(), g_app_info_get_name(app.get())); + if(g_app_info_equal(app.get(), defaultApp_.get())) { + defaultAppIndex_ = i; + } + appInfos_.push_back(std::move(app)); + } + g_list_free(appInfos_glist); + } + // add "Other applications" item + insertSeparator(count()); + addItem(tr("Customize")); + if(defaultAppIndex_ != -1) { + setCurrentIndex(defaultAppIndex_); } - } - // add "Other applications" item - insertSeparator(count()); - addItem(tr("Customize")); - if(defaultAppIndex_ != -1) - setCurrentIndex(defaultAppIndex_); } // returns the currently selected app. -GAppInfo* AppChooserComboBox::selectedApp() { - return G_APP_INFO(g_list_nth_data(appInfos_, currentIndex())); +Fm::GAppInfoPtr AppChooserComboBox::selectedApp() const { + int idx = currentIndex(); + return idx >= 0 ? appInfos_[idx] : Fm::GAppInfoPtr{}; } -bool AppChooserComboBox::isChanged() { - return (defaultAppIndex_ != currentIndex()); +bool AppChooserComboBox::isChanged() const { + return (defaultAppIndex_ != currentIndex()); } void AppChooserComboBox::onCurrentIndexChanged(int index) { - if(index == -1 || index == prevIndex_ || blockOnCurrentIndexChanged_) - return; - - // the last item is "Customize" - if(index == (count() - 1)) { - /* TODO: let the user choose an app or add custom actions here. */ - QWidget* toplevel = topLevelWidget(); - AppChooserDialog dlg(mimeType_, toplevel); - dlg.setWindowModality(Qt::WindowModal); - dlg.setCanSetDefault(false); - if(dlg.exec() == QDialog::Accepted) { - GAppInfo* app = dlg.selectedApp(); - if(app) { - /* see if it's already in the list to prevent duplication */ - GList* found = NULL; - for(found = appInfos_; found; found = found->next) { - if(g_app_info_equal(app, G_APP_INFO(found->data))) - break; + if(index == -1 || index == prevIndex_ || blockOnCurrentIndexChanged_) { + return; + } + + // the last item is "Customize" + if(index == (count() - 1)) { + /* TODO: let the user choose an app or add custom actions here. */ + QWidget* toplevel = topLevelWidget(); + AppChooserDialog dlg(mimeType_, toplevel); + dlg.setWindowModality(Qt::WindowModal); + dlg.setCanSetDefault(false); + if(dlg.exec() == QDialog::Accepted) { + auto app = dlg.selectedApp(); + if(app) { + /* see if it's already in the list to prevent duplication */ + auto found = std::find_if(appInfos_.cbegin(), appInfos_.cend(), [&](const Fm::GAppInfoPtr& item) { + return g_app_info_equal(app.get(), item.get()); + }); + + // inserting new items or change current index will recursively trigger onCurrentIndexChanged. + // we need to block our handler to prevent recursive calls. + blockOnCurrentIndexChanged_ = true; + /* if it's already in the list, select it */ + if(found != appInfos_.cend()) { + auto pos = found - appInfos_.cbegin(); + setCurrentIndex(pos); + } + else { /* if it's not found, add it to the list */ + auto it = appInfos_.insert(appInfos_.cbegin(), std::move(app)); + GIcon* gicon = g_app_info_get_icon(it->get()); + insertItem(0, Fm::IconInfo::fromGIcon(gicon)->qicon(), g_app_info_get_name(it->get())); + setCurrentIndex(0); + } + blockOnCurrentIndexChanged_ = false; + return; + } } - // inserting new items or change current index will recursively trigger onCurrentIndexChanged. - // we need to block our handler to prevent recursive calls. + // block our handler to prevent recursive calls. blockOnCurrentIndexChanged_ = true; - /* if it's already in the list, select it */ - if(found) { - setCurrentIndex(g_list_position(appInfos_, found)); - g_object_unref(app); - } - else { /* if it's not found, add it to the list */ - appInfos_ = g_list_prepend(appInfos_, app); - GIcon* gicon = g_app_info_get_icon(app); - QString name = QString::fromUtf8(g_app_info_get_name(app)); - insertItem(0, IconTheme::icon(gicon), name); - setCurrentIndex(0); - } + // restore to previously selected item + setCurrentIndex(prevIndex_); blockOnCurrentIndexChanged_ = false; - return; - } } - - // block our handler to prevent recursive calls. - blockOnCurrentIndexChanged_ = true; - // restore to previously selected item - setCurrentIndex(prevIndex_); - blockOnCurrentIndexChanged_ = false; - } - else { - prevIndex_ = index; - } + else { + prevIndex_ = index; + } } diff --git a/src/appchoosercombobox.h b/src/appchoosercombobox.h index 53ffc3e..03149e0 100644 --- a/src/appchoosercombobox.h +++ b/src/appchoosercombobox.h @@ -24,35 +24,40 @@ #include #include +#include + +#include "core/mimetype.h" +#include "core/gioptrs.h" + namespace Fm { class LIBFM_QT_API AppChooserComboBox : public QComboBox { - Q_OBJECT + Q_OBJECT public: - ~AppChooserComboBox(); - AppChooserComboBox(QWidget* parent); + ~AppChooserComboBox(); + explicit AppChooserComboBox(QWidget* parent); - void setMimeType(FmMimeType* mimeType); + void setMimeType(std::shared_ptr mimeType); - FmMimeType* mimeType() { - return mimeType_; - } + const std::shared_ptr& mimeType() const { + return mimeType_; + } - GAppInfo* selectedApp(); - // const GList* customApps(); + Fm::GAppInfoPtr selectedApp() const; + // const GList* customApps(); - bool isChanged(); + bool isChanged() const; private Q_SLOTS: - void onCurrentIndexChanged(int index); + void onCurrentIndexChanged(int index); private: - FmMimeType* mimeType_; - GList* appInfos_; // applications used to open the file type - GAppInfo* defaultApp_; // default application used to open the file type - int defaultAppIndex_; - int prevIndex_; - bool blockOnCurrentIndexChanged_; + std::shared_ptr mimeType_; + std::vector appInfos_; // applications used to open the file type + Fm::GAppInfoPtr defaultApp_; // default application used to open the file type + int defaultAppIndex_; + int prevIndex_; + bool blockOnCurrentIndexChanged_; }; } diff --git a/src/appchooserdialog.cpp b/src/appchooserdialog.cpp index c331c7e..0b508a2 100644 --- a/src/appchooserdialog.cpp +++ b/src/appchooserdialog.cpp @@ -25,262 +25,262 @@ namespace Fm { -AppChooserDialog::AppChooserDialog(FmMimeType* mimeType, QWidget* parent, Qt::WindowFlags f): - QDialog(parent, f), - ui(new Ui::AppChooserDialog()), - mimeType_(NULL), - canSetDefault_(true), - selectedApp_(NULL) { - ui->setupUi(this); +AppChooserDialog::AppChooserDialog(std::shared_ptr mimeType, QWidget* parent, Qt::WindowFlags f): + QDialog(parent, f), + ui(new Ui::AppChooserDialog()), + mimeType_{std::move(mimeType)}, + canSetDefault_(true) { + ui->setupUi(this); - connect(ui->appMenuView, &AppMenuView::selectionChanged, this, &AppChooserDialog::onSelectionChanged); - connect(ui->tabWidget, &QTabWidget::currentChanged, this, &AppChooserDialog::onTabChanged); + connect(ui->appMenuView, &AppMenuView::selectionChanged, this, &AppChooserDialog::onSelectionChanged); + connect(ui->tabWidget, &QTabWidget::currentChanged, this, &AppChooserDialog::onTabChanged); - if(!ui->appMenuView->isAppSelected()) - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); // disable OK button - - if(mimeType) - setMimeType(mimeType); + if(!ui->appMenuView->isAppSelected()) { + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); // disable OK button + } } AppChooserDialog::~AppChooserDialog() { - delete ui; - if(mimeType_) - fm_mime_type_unref(mimeType_); - if(selectedApp_) - g_object_unref(selectedApp_); + delete ui; } -bool AppChooserDialog::isSetDefault() { - return ui->setDefault->isChecked(); +bool AppChooserDialog::isSetDefault() const { + return ui->setDefault->isChecked(); } - -static void on_temp_appinfo_destroy(gpointer data, GObject* objptr) { - char* filename = (char*)data; - if(g_unlink(filename) < 0) - g_critical("failed to remove %s", filename); - /* else - qDebug("temp file %s removed", filename); */ - g_free(filename); +static void on_temp_appinfo_destroy(gpointer data, GObject* /*objptr*/) { + char* filename = (char*)data; + if(g_unlink(filename) < 0) { + g_critical("failed to remove %s", filename); + } + /* else + qDebug("temp file %s removed", filename); */ + g_free(filename); } static GAppInfo* app_info_create_from_commandline(const char* commandline, - const char* application_name, - const char* bin_name, - const char* mime_type, - gboolean terminal, gboolean keep) { - GAppInfo* app = NULL; - char* dirname = g_build_filename(g_get_user_data_dir(), "applications", NULL); - const char* app_basename = strrchr(bin_name, '/'); + const char* application_name, + const char* bin_name, + const char* mime_type, + gboolean terminal, gboolean keep) { + GAppInfo* app = nullptr; + char* dirname = g_build_filename(g_get_user_data_dir(), "applications", nullptr); + const char* app_basename = strrchr(bin_name, '/'); - if(app_basename) - app_basename++; - else - app_basename = bin_name; - if(g_mkdir_with_parents(dirname, 0700) == 0) { - char* filename = g_strdup_printf("%s/userapp-%s-XXXXXX.desktop", dirname, app_basename); - int fd = g_mkstemp(filename); - if(fd != -1) { - GString* content = g_string_sized_new(256); - g_string_printf(content, - "[" G_KEY_FILE_DESKTOP_GROUP "]\n" - G_KEY_FILE_DESKTOP_KEY_TYPE "=" G_KEY_FILE_DESKTOP_TYPE_APPLICATION "\n" - G_KEY_FILE_DESKTOP_KEY_NAME "=%s\n" - G_KEY_FILE_DESKTOP_KEY_EXEC "=%s\n" - G_KEY_FILE_DESKTOP_KEY_CATEGORIES "=Other;\n" - G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY "=true\n", - application_name, - commandline - ); - if(mime_type) - g_string_append_printf(content, - G_KEY_FILE_DESKTOP_KEY_MIME_TYPE "=%s\n", - mime_type); - g_string_append_printf(content, - G_KEY_FILE_DESKTOP_KEY_TERMINAL "=%s\n", - terminal ? "true" : "false"); - if(terminal) - g_string_append_printf(content, "X-KeepTerminal=%s\n", - keep ? "true" : "false"); - close(fd); /* g_file_set_contents() may fail creating duplicate */ - if(g_file_set_contents(filename, content->str, content->len, NULL)) { - char* fbname = g_path_get_basename(filename); - app = G_APP_INFO(g_desktop_app_info_new(fbname)); - g_free(fbname); - /* if there is mime_type set then created application will be - saved for the mime type (see fm_choose_app_for_mime_type() - below) but if not then we should remove this temp. file */ - if(!mime_type || !application_name[0]) - /* save the name so this file will be removed later */ - g_object_weak_ref(G_OBJECT(app), on_temp_appinfo_destroy, - g_strdup(filename)); - } - else - g_unlink(filename); - g_string_free(content, TRUE); + if(app_basename) { + app_basename++; } - g_free(filename); - } - g_free(dirname); - return app; + else { + app_basename = bin_name; + } + if(g_mkdir_with_parents(dirname, 0700) == 0) { + char* filename = g_strdup_printf("%s/userapp-%s-XXXXXX.desktop", dirname, app_basename); + int fd = g_mkstemp(filename); + if(fd != -1) { + GString* content = g_string_sized_new(256); + g_string_printf(content, + "[" G_KEY_FILE_DESKTOP_GROUP "]\n" + G_KEY_FILE_DESKTOP_KEY_TYPE "=" G_KEY_FILE_DESKTOP_TYPE_APPLICATION "\n" + G_KEY_FILE_DESKTOP_KEY_NAME "=%s\n" + G_KEY_FILE_DESKTOP_KEY_EXEC "=%s\n" + G_KEY_FILE_DESKTOP_KEY_CATEGORIES "=Other;\n" + G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY "=true\n", + application_name, + commandline + ); + if(mime_type) + g_string_append_printf(content, + G_KEY_FILE_DESKTOP_KEY_MIME_TYPE "=%s\n", + mime_type); + g_string_append_printf(content, + G_KEY_FILE_DESKTOP_KEY_TERMINAL "=%s\n", + terminal ? "true" : "false"); + if(terminal) + g_string_append_printf(content, "X-KeepTerminal=%s\n", + keep ? "true" : "false"); + close(fd); /* g_file_set_contents() may fail creating duplicate */ + if(g_file_set_contents(filename, content->str, content->len, nullptr)) { + char* fbname = g_path_get_basename(filename); + app = G_APP_INFO(g_desktop_app_info_new(fbname)); + g_free(fbname); + /* if there is mime_type set then created application will be + saved for the mime type (see fm_choose_app_for_mime_type() + below) but if not then we should remove this temp. file */ + if(!mime_type || !application_name[0]) + /* save the name so this file will be removed later */ + g_object_weak_ref(G_OBJECT(app), on_temp_appinfo_destroy, + g_strdup(filename)); + } + else { + g_unlink(filename); + } + g_string_free(content, TRUE); + } + g_free(filename); + } + g_free(dirname); + return app; } inline static char* get_binary(const char* cmdline, gboolean* arg_found) { - /* see if command line contains %f, %F, %u, or %U. */ - const char* p = strstr(cmdline, " %"); - if(p) { - if(!strchr("fFuU", *(p + 2))) - p = NULL; - } - if(arg_found) - *arg_found = (p != NULL); - if(p) - return g_strndup(cmdline, p - cmdline); - else - return g_strdup(cmdline); -} - -GAppInfo* AppChooserDialog::customCommandToApp() { - GAppInfo* app = NULL; - QByteArray cmdline = ui->cmdLine->text().toLocal8Bit(); - QByteArray app_name = ui->appName->text().toUtf8(); - if(!cmdline.isEmpty()) { - gboolean arg_found = FALSE; - char* bin1 = get_binary(cmdline.constData(), &arg_found); - qDebug("bin1 = %s", bin1); /* see if command line contains %f, %F, %u, or %U. */ - if(!arg_found) { /* append %f if no %f, %F, %u, or %U was found. */ - cmdline += " %f"; + const char* p = strstr(cmdline, " %"); + if(p) { + if(!strchr("fFuU", *(p + 2))) { + p = nullptr; + } + } + if(arg_found) { + *arg_found = (p != nullptr); + } + if(p) { + return g_strndup(cmdline, p - cmdline); + } + else { + return g_strdup(cmdline); } +} - /* FIXME: is there any better way to do this? */ - /* We need to ensure that no duplicated items are added */ - if(mimeType_) { - MenuCache* menu_cache; - /* see if the command is already in the list of known apps for this mime-type */ - GList* apps = g_app_info_get_all_for_type(fm_mime_type_get_type(mimeType_)); - GList* l; - for(l = apps; l; l = l->next) { - GAppInfo* app2 = G_APP_INFO(l->data); - const char* cmd = g_app_info_get_commandline(app2); - char* bin2 = get_binary(cmd, NULL); - if(g_strcmp0(bin1, bin2) == 0) { - app = G_APP_INFO(g_object_ref(app2)); - qDebug("found in app list"); - g_free(bin2); - break; +GAppInfo* AppChooserDialog::customCommandToApp() { + GAppInfo* app = nullptr; + QByteArray cmdline = ui->cmdLine->text().toLocal8Bit(); + QByteArray app_name = ui->appName->text().toUtf8(); + if(!cmdline.isEmpty()) { + gboolean arg_found = FALSE; + char* bin1 = get_binary(cmdline.constData(), &arg_found); + qDebug("bin1 = %s", bin1); + /* see if command line contains %f, %F, %u, or %U. */ + if(!arg_found) { /* append %f if no %f, %F, %u, or %U was found. */ + cmdline += " %f"; } - g_free(bin2); - } - g_list_foreach(apps, (GFunc)g_object_unref, NULL); - g_list_free(apps); - if(app) - goto _out; - /* see if this command can be found in menu cache */ - menu_cache = menu_cache_lookup("applications.menu"); - if(menu_cache) { - MenuCacheDir* root_dir = menu_cache_dup_root_dir(menu_cache); - if(root_dir) { - GSList* all_apps = menu_cache_list_all_apps(menu_cache); - GSList* l; - for(l = all_apps; l; l = l->next) { - MenuCacheApp* ma = MENU_CACHE_APP(l->data); - const char* exec = menu_cache_app_get_exec(ma); - char* bin2; - if(exec == NULL) { - g_warning("application %s has no Exec statement", menu_cache_item_get_id(MENU_CACHE_ITEM(ma))); - continue; + /* FIXME: is there any better way to do this? */ + /* We need to ensure that no duplicated items are added */ + if(mimeType_) { + MenuCache* menu_cache; + /* see if the command is already in the list of known apps for this mime-type */ + GList* apps = g_app_info_get_all_for_type(mimeType_->name()); + GList* l; + for(l = apps; l; l = l->next) { + GAppInfo* app2 = G_APP_INFO(l->data); + const char* cmd = g_app_info_get_commandline(app2); + char* bin2 = get_binary(cmd, nullptr); + if(g_strcmp0(bin1, bin2) == 0) { + app = G_APP_INFO(g_object_ref(app2)); + qDebug("found in app list"); + g_free(bin2); + break; + } + g_free(bin2); } - bin2 = get_binary(exec, NULL); - if(g_strcmp0(bin1, bin2) == 0) { - app = G_APP_INFO(g_desktop_app_info_new(menu_cache_item_get_id(MENU_CACHE_ITEM(ma)))); - qDebug("found in menu cache"); - menu_cache_item_unref(MENU_CACHE_ITEM(ma)); - g_free(bin2); - break; + g_list_foreach(apps, (GFunc)g_object_unref, nullptr); + g_list_free(apps); + if(app) { + goto _out; + } + + /* see if this command can be found in menu cache */ + menu_cache = menu_cache_lookup("applications.menu"); + if(menu_cache) { + MenuCacheDir* root_dir = menu_cache_dup_root_dir(menu_cache); + if(root_dir) { + GSList* all_apps = menu_cache_list_all_apps(menu_cache); + GSList* l; + for(l = all_apps; l; l = l->next) { + MenuCacheApp* ma = MENU_CACHE_APP(l->data); + const char* exec = menu_cache_app_get_exec(ma); + char* bin2; + if(exec == nullptr) { + g_warning("application %s has no Exec statement", menu_cache_item_get_id(MENU_CACHE_ITEM(ma))); + continue; + } + bin2 = get_binary(exec, nullptr); + if(g_strcmp0(bin1, bin2) == 0) { + app = G_APP_INFO(g_desktop_app_info_new(menu_cache_item_get_id(MENU_CACHE_ITEM(ma)))); + qDebug("found in menu cache"); + menu_cache_item_unref(MENU_CACHE_ITEM(ma)); + g_free(bin2); + break; + } + menu_cache_item_unref(MENU_CACHE_ITEM(ma)); + g_free(bin2); + } + g_slist_free(all_apps); + menu_cache_item_unref(MENU_CACHE_ITEM(root_dir)); + } + menu_cache_unref(menu_cache); + } + if(app) { + goto _out; } - menu_cache_item_unref(MENU_CACHE_ITEM(ma)); - g_free(bin2); - } - g_slist_free(all_apps); - menu_cache_item_unref(MENU_CACHE_ITEM(root_dir)); } - menu_cache_unref(menu_cache); - } - if(app) - goto _out; - } - /* FIXME: g_app_info_create_from_commandline force the use of %f or %u, so this is not we need */ - app = app_info_create_from_commandline(cmdline.constData(), app_name.constData(), bin1, - mimeType_ ? fm_mime_type_get_type(mimeType_) : NULL, - ui->useTerminal->isChecked(), ui->keepTermOpen->isChecked()); - _out: - g_free(bin1); - } - return app; + /* FIXME: g_app_info_create_from_commandline force the use of %f or %u, so this is not we need */ + app = app_info_create_from_commandline(cmdline.constData(), app_name.constData(), bin1, + mimeType_ ? mimeType_->name() : nullptr, + ui->useTerminal->isChecked(), ui->keepTermOpen->isChecked()); +_out: + g_free(bin1); + } + return app; } void AppChooserDialog::accept() { - QDialog::accept(); + QDialog::accept(); - if(ui->tabWidget->currentIndex() == 0) { - selectedApp_ = ui->appMenuView->selectedApp(); - } - else { // custom command line - selectedApp_ = customCommandToApp(); - } + if(ui->tabWidget->currentIndex() == 0) { + selectedApp_ = ui->appMenuView->selectedApp(); + } + else { // custom command line + selectedApp_ = customCommandToApp(); + } - if(selectedApp_) { - if(mimeType_ && fm_mime_type_get_type(mimeType_) && g_app_info_get_name(selectedApp_)[0]) { - /* add this app to the mime-type */ + if(selectedApp_) { + if(mimeType_ && g_app_info_get_name(selectedApp_.get())) { + /* add this app to the mime-type */ #if GLIB_CHECK_VERSION(2, 27, 6) - g_app_info_set_as_last_used_for_type(selectedApp_, fm_mime_type_get_type(mimeType_), NULL); + g_app_info_set_as_last_used_for_type(selectedApp_.get(), mimeType_->name(), nullptr); #else - g_app_info_add_supports_type(selectedApp_, fm_mime_type_get_type(mimeType_), NULL); + g_app_info_add_supports_type(selectedApp_.get(), mimeType_->name(), nullptr); #endif - /* if need to set default */ - if(ui->setDefault->isChecked()) - g_app_info_set_as_default_for_type(selectedApp_, fm_mime_type_get_type(mimeType_), NULL); + /* if need to set default */ + if(ui->setDefault->isChecked()) { + g_app_info_set_as_default_for_type(selectedApp_.get(), mimeType_->name(), nullptr); + } + } } - } } void AppChooserDialog::onSelectionChanged() { - bool isAppSelected = ui->appMenuView->isAppSelected(); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isAppSelected); + bool isAppSelected = ui->appMenuView->isAppSelected(); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isAppSelected); } -void AppChooserDialog::setMimeType(FmMimeType* mimeType) { - if(mimeType_) - fm_mime_type_unref(mimeType_); - - mimeType_ = mimeType ? fm_mime_type_ref(mimeType) : NULL; - if(mimeType_) { - QString text = tr("Select an application to open \"%1\" files") - .arg(QString::fromUtf8(fm_mime_type_get_desc(mimeType_))); - ui->fileTypeHeader->setText(text); - } - else { - ui->fileTypeHeader->hide(); - ui->setDefault->hide(); - } +void AppChooserDialog::setMimeType(std::shared_ptr mimeType) { + mimeType_ = std::move(mimeType); + if(mimeType_) { + QString text = tr("Select an application to open \"%1\" files") + .arg(QString::fromUtf8(mimeType_->desc())); + ui->fileTypeHeader->setText(text); + } + else { + ui->fileTypeHeader->hide(); + ui->setDefault->hide(); + } } void AppChooserDialog::setCanSetDefault(bool value) { - canSetDefault_ = value; - ui->setDefault->setVisible(value); + canSetDefault_ = value; + ui->setDefault->setVisible(value); } void AppChooserDialog::onTabChanged(int index) { - if(index == 0) { // app menu view - onSelectionChanged(); - } - else if(index == 1) { // custom command - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - } + if(index == 0) { // app menu view + onSelectionChanged(); + } + else if(index == 1) { // custom command + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + } } } // namespace Fm diff --git a/src/appchooserdialog.h b/src/appchooserdialog.h index 64a5464..ef36dfc 100644 --- a/src/appchooserdialog.h +++ b/src/appchooserdialog.h @@ -25,48 +25,53 @@ #include "libfmqtglobals.h" #include +#include "core/mimetype.h" +#include "core/gioptrs.h" + namespace Ui { - class AppChooserDialog; +class AppChooserDialog; } namespace Fm { class LIBFM_QT_API AppChooserDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - explicit AppChooserDialog(FmMimeType* mimeType, QWidget* parent = NULL, Qt::WindowFlags f = 0); - ~AppChooserDialog(); + explicit AppChooserDialog(std::shared_ptr mimeType, QWidget* parent = nullptr, Qt::WindowFlags f = 0); + ~AppChooserDialog(); + + virtual void accept(); + + void setMimeType(std::shared_ptr mimeType); - virtual void accept(); + const std::shared_ptr& mimeType() const { + return mimeType_; + } - void setMimeType(FmMimeType* mimeType); - FmMimeType* mimeType() { - return mimeType_; - } + void setCanSetDefault(bool value); - void setCanSetDefault(bool value); - bool canSetDefault() { - return canSetDefault_; - } + bool canSetDefault() const { + return canSetDefault_; + } - GAppInfo* selectedApp() { - return G_APP_INFO(g_object_ref(selectedApp_)); - } + const Fm::GAppInfoPtr& selectedApp() const { + return selectedApp_; + } - bool isSetDefault(); + bool isSetDefault() const; private: - GAppInfo* customCommandToApp(); + GAppInfo* customCommandToApp(); private Q_SLOTS: - void onSelectionChanged(); - void onTabChanged(int index); + void onSelectionChanged(); + void onTabChanged(int index); private: - Ui::AppChooserDialog* ui; - FmMimeType* mimeType_; - bool canSetDefault_; - GAppInfo* selectedApp_; + Ui::AppChooserDialog* ui; + std::shared_ptr mimeType_; + bool canSetDefault_; + Fm::GAppInfoPtr selectedApp_; }; } diff --git a/src/applaunchcontext.cpp b/src/applaunchcontext.cpp index 727f462..e599485 100644 --- a/src/applaunchcontext.cpp +++ b/src/applaunchcontext.cpp @@ -28,17 +28,17 @@ typedef struct _FmAppLaunchContext { G_DEFINE_TYPE(FmAppLaunchContext, fm_app_launch_context, G_TYPE_APP_LAUNCH_CONTEXT) -static char* fm_app_launch_context_get_display(GAppLaunchContext *context, GAppInfo *info, GList *files) { +static char* fm_app_launch_context_get_display(GAppLaunchContext * /*context*/, GAppInfo * /*info*/, GList * /*files*/) { Display* dpy = QX11Info::display(); if(dpy) { char* xstr = DisplayString(dpy); return g_strdup(xstr); } - return NULL; + return nullptr; } -static char* fm_app_launch_context_get_startup_notify_id(GAppLaunchContext *context, GAppInfo *info, GList *files) { - return NULL; +static char* fm_app_launch_context_get_startup_notify_id(GAppLaunchContext * /*context*/, GAppInfo * /*info*/, GList * /*files*/) { + return nullptr; } static void fm_app_launch_context_class_init(FmAppLaunchContextClass* klass) { @@ -47,15 +47,15 @@ static void fm_app_launch_context_class_init(FmAppLaunchContextClass* klass) { app_launch_class->get_startup_notify_id = fm_app_launch_context_get_startup_notify_id; } -static void fm_app_launch_context_init(FmAppLaunchContext* context) { +static void fm_app_launch_context_init(FmAppLaunchContext* /*context*/) { } -FmAppLaunchContext* fm_app_launch_context_new_for_widget(QWidget* widget) { - FmAppLaunchContext* context = (FmAppLaunchContext*)g_object_new(FM_TYPE_APP_LAUNCH_CONTEXT, NULL); +FmAppLaunchContext* fm_app_launch_context_new_for_widget(QWidget* /*widget*/) { + FmAppLaunchContext* context = (FmAppLaunchContext*)g_object_new(FM_TYPE_APP_LAUNCH_CONTEXT, nullptr); return context; } FmAppLaunchContext* fm_app_launch_context_new() { - FmAppLaunchContext* context = (FmAppLaunchContext*)g_object_new(FM_TYPE_APP_LAUNCH_CONTEXT, NULL); + FmAppLaunchContext* context = (FmAppLaunchContext*)g_object_new(FM_TYPE_APP_LAUNCH_CONTEXT, nullptr); return context; } diff --git a/src/appmenuview.cpp b/src/appmenuview.cpp index c66dc86..cc669d1 100644 --- a/src/appmenuview.cpp +++ b/src/appmenuview.cpp @@ -26,133 +26,137 @@ namespace Fm { AppMenuView::AppMenuView(QWidget* parent): - QTreeView(parent), - model_(new QStandardItemModel()), - menu_cache(NULL), - menu_cache_reload_notify(NULL) { - - setHeaderHidden(true); - setSelectionMode(SingleSelection); - - // initialize model - // TODO: share one model among all app menu view widgets - // ensure that we're using lxmenu-data (FIXME: should we do this?) - QByteArray oldenv = qgetenv("XDG_MENU_PREFIX"); - qputenv("XDG_MENU_PREFIX", "lxde-"); - menu_cache = menu_cache_lookup("applications.menu"); - // if(!oldenv.isEmpty()) - qputenv("XDG_MENU_PREFIX", oldenv); // restore the original value if needed - - if(menu_cache) { - MenuCacheDir* dir = menu_cache_dup_root_dir(menu_cache); - menu_cache_reload_notify = menu_cache_add_reload_notify(menu_cache, _onMenuCacheReload, this); - if(dir) { /* content of menu is already loaded */ - addMenuItems(NULL, dir); - menu_cache_item_unref(MENU_CACHE_ITEM(dir)); + QTreeView(parent), + model_(new QStandardItemModel()), + menu_cache(nullptr), + menu_cache_reload_notify(nullptr) { + + setHeaderHidden(true); + setSelectionMode(SingleSelection); + + // initialize model + // TODO: share one model among all app menu view widgets + // ensure that we're using lxmenu-data (FIXME: should we do this?) + QByteArray oldenv = qgetenv("XDG_MENU_PREFIX"); + qputenv("XDG_MENU_PREFIX", "lxde-"); + menu_cache = menu_cache_lookup("applications.menu"); + // if(!oldenv.isEmpty()) + qputenv("XDG_MENU_PREFIX", oldenv); // restore the original value if needed + + if(menu_cache) { + MenuCacheDir* dir = menu_cache_dup_root_dir(menu_cache); + menu_cache_reload_notify = menu_cache_add_reload_notify(menu_cache, _onMenuCacheReload, this); + if(dir) { /* content of menu is already loaded */ + addMenuItems(nullptr, dir); + menu_cache_item_unref(MENU_CACHE_ITEM(dir)); + } } - } - setModel(model_); - connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &AppMenuView::selectionChanged); - selectionModel()->select(model_->index(0, 0), QItemSelectionModel::SelectCurrent); + setModel(model_); + connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &AppMenuView::selectionChanged); + selectionModel()->select(model_->index(0, 0), QItemSelectionModel::SelectCurrent); } AppMenuView::~AppMenuView() { - delete model_; - if(menu_cache) { - if(menu_cache_reload_notify) - menu_cache_remove_reload_notify(menu_cache, menu_cache_reload_notify); - menu_cache_unref(menu_cache); - } + delete model_; + if(menu_cache) { + if(menu_cache_reload_notify) { + menu_cache_remove_reload_notify(menu_cache, menu_cache_reload_notify); + } + menu_cache_unref(menu_cache); + } } void AppMenuView::addMenuItems(QStandardItem* parentItem, MenuCacheDir* dir) { - GSList* l; - GSList* list; - /* Iterate over all menu items in this directory. */ - for(l = list = menu_cache_dir_list_children(dir); l != NULL; l = l->next) { - /* Get the menu item. */ - MenuCacheItem* menuItem = MENU_CACHE_ITEM(l->data); - switch(menu_cache_item_get_type(menuItem)) { - case MENU_CACHE_TYPE_NONE: - case MENU_CACHE_TYPE_SEP: - break; - case MENU_CACHE_TYPE_APP: - case MENU_CACHE_TYPE_DIR: { - AppMenuViewItem* newItem = new AppMenuViewItem(menuItem); - if(parentItem) - parentItem->insertRow(parentItem->rowCount(), newItem); - else - model_->insertRow(model_->rowCount(), newItem); - - if(menu_cache_item_get_type(menuItem) == MENU_CACHE_TYPE_DIR) - addMenuItems(newItem, MENU_CACHE_DIR(menuItem)); - break; - } + GSList* l; + GSList* list; + /* Iterate over all menu items in this directory. */ + for(l = list = menu_cache_dir_list_children(dir); l != nullptr; l = l->next) { + /* Get the menu item. */ + MenuCacheItem* menuItem = MENU_CACHE_ITEM(l->data); + switch(menu_cache_item_get_type(menuItem)) { + case MENU_CACHE_TYPE_NONE: + case MENU_CACHE_TYPE_SEP: + break; + case MENU_CACHE_TYPE_APP: + case MENU_CACHE_TYPE_DIR: { + AppMenuViewItem* newItem = new AppMenuViewItem(menuItem); + if(parentItem) { + parentItem->insertRow(parentItem->rowCount(), newItem); + } + else { + model_->insertRow(model_->rowCount(), newItem); + } + + if(menu_cache_item_get_type(menuItem) == MENU_CACHE_TYPE_DIR) { + addMenuItems(newItem, MENU_CACHE_DIR(menuItem)); + } + break; + } + } } - } - g_slist_free_full(list, (GDestroyNotify)menu_cache_item_unref); + g_slist_free_full(list, (GDestroyNotify)menu_cache_item_unref); } void AppMenuView::onMenuCacheReload(MenuCache* mc) { - MenuCacheDir* dir = menu_cache_dup_root_dir(mc); - model_->clear(); - /* FIXME: preserve original selection */ - if(dir) { - addMenuItems(NULL, dir); - menu_cache_item_unref(MENU_CACHE_ITEM(dir)); - selectionModel()->select(model_->index(0, 0), QItemSelectionModel::SelectCurrent); - } + MenuCacheDir* dir = menu_cache_dup_root_dir(mc); + model_->clear(); + /* FIXME: preserve original selection */ + if(dir) { + addMenuItems(nullptr, dir); + menu_cache_item_unref(MENU_CACHE_ITEM(dir)); + selectionModel()->select(model_->index(0, 0), QItemSelectionModel::SelectCurrent); + } } -bool AppMenuView::isAppSelected() { - AppMenuViewItem* item = selectedItem(); - return (item && item->isApp()); +bool AppMenuView::isAppSelected() const { + AppMenuViewItem* item = selectedItem(); + return (item && item->isApp()); } -AppMenuViewItem* AppMenuView::selectedItem() { - QModelIndexList selected = selectedIndexes(); - if(!selected.isEmpty()) { - AppMenuViewItem* item = static_cast(model_->itemFromIndex(selected.first() - )); - return item; - } - return NULL; +AppMenuViewItem* AppMenuView::selectedItem() const { + QModelIndexList selected = selectedIndexes(); + if(!selected.isEmpty()) { + AppMenuViewItem* item = static_cast(model_->itemFromIndex(selected.first() + )); + return item; + } + return nullptr; } -GAppInfo* AppMenuView::selectedApp() { - const char* id = selectedAppDesktopId(); - return id ? G_APP_INFO(g_desktop_app_info_new(id)) : NULL; +Fm::GAppInfoPtr AppMenuView::selectedApp() const { + const char* id = selectedAppDesktopId(); + return Fm::GAppInfoPtr{id ? G_APP_INFO(g_desktop_app_info_new(id)) : nullptr, false}; } -QByteArray AppMenuView::selectedAppDesktopFilePath() { - AppMenuViewItem* item = selectedItem(); - if(item && item->isApp()) { - char* path = menu_cache_item_get_file_path(item->item()); - QByteArray ret(path); - g_free(path); - return ret; - } - return QByteArray(); +QByteArray AppMenuView::selectedAppDesktopFilePath() const { + AppMenuViewItem* item = selectedItem(); + if(item && item->isApp()) { + char* path = menu_cache_item_get_file_path(item->item()); + QByteArray ret(path); + g_free(path); + return ret; + } + return QByteArray(); } -const char* AppMenuView::selectedAppDesktopId() { - AppMenuViewItem* item = selectedItem(); - if(item && item->isApp()) { - return menu_cache_item_get_id(item->item()); - } - return NULL; +const char* AppMenuView::selectedAppDesktopId() const { + AppMenuViewItem* item = selectedItem(); + if(item && item->isApp()) { + return menu_cache_item_get_id(item->item()); + } + return nullptr; } -FmPath* AppMenuView::selectedAppDesktopPath() { - AppMenuViewItem* item = selectedItem(); - if(item && item->isApp()) { - char* mpath = menu_cache_dir_make_path(MENU_CACHE_DIR(item)); - FmPath* path = fm_path_new_relative(fm_path_get_apps_menu(), - mpath + 13 /* skip "/Applications" */); - g_free(mpath); - return path; - } - return NULL; +FmPath* AppMenuView::selectedAppDesktopPath() const { + AppMenuViewItem* item = selectedItem(); + if(item && item->isApp()) { + char* mpath = menu_cache_dir_make_path(MENU_CACHE_DIR(item)); + FmPath* path = fm_path_new_relative(fm_path_get_apps_menu(), + mpath + 13 /* skip "/Applications" */); + g_free(mpath); + return path; + } + return nullptr; } } // namespace Fm diff --git a/src/appmenuview.h b/src/appmenuview.h index 99c5607..12d42a7 100644 --- a/src/appmenuview.h +++ b/src/appmenuview.h @@ -25,6 +25,8 @@ #include #include +#include "core/gioptrs.h" + class QStandardItemModel; class QStandardItem; @@ -33,38 +35,38 @@ namespace Fm { class AppMenuViewItem; class LIBFM_QT_API AppMenuView : public QTreeView { - Q_OBJECT + Q_OBJECT public: - explicit AppMenuView(QWidget* parent = NULL); - ~AppMenuView(); + explicit AppMenuView(QWidget* parent = nullptr); + ~AppMenuView(); - GAppInfo* selectedApp(); + Fm::GAppInfoPtr selectedApp() const; - const char* selectedAppDesktopId(); + const char* selectedAppDesktopId() const; - QByteArray selectedAppDesktopFilePath(); + QByteArray selectedAppDesktopFilePath() const; - FmPath * selectedAppDesktopPath(); + FmPath* selectedAppDesktopPath() const; - bool isAppSelected(); + bool isAppSelected() const; Q_SIGNALS: - void selectionChanged(); + void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); private: - void addMenuItems(QStandardItem* parentItem, MenuCacheDir* dir); - void onMenuCacheReload(MenuCache* mc); - static void _onMenuCacheReload(MenuCache* mc, gpointer user_data) { - static_cast(user_data)->onMenuCacheReload(mc); - } + void addMenuItems(QStandardItem* parentItem, MenuCacheDir* dir); + void onMenuCacheReload(MenuCache* mc); + static void _onMenuCacheReload(MenuCache* mc, gpointer user_data) { + static_cast(user_data)->onMenuCacheReload(mc); + } - AppMenuViewItem* selectedItem(); + AppMenuViewItem* selectedItem() const; private: - // gboolean fm_app_menu_view_is_item_app(, GtkTreeIter* it); - QStandardItemModel* model_; - MenuCache* menu_cache; - MenuCacheNotifyId menu_cache_reload_notify; + // gboolean fm_app_menu_view_is_item_app(, GtkTreeIter* it); + QStandardItemModel* model_; + MenuCache* menu_cache; + MenuCacheNotifyId menu_cache_reload_notify; }; } diff --git a/src/appmenuview_p.h b/src/appmenuview_p.h index 03730d0..815c84e 100644 --- a/src/appmenuview_p.h +++ b/src/appmenuview_p.h @@ -23,49 +23,48 @@ #include #include #include "icontheme.h" +#include "core/iconinfo.h" namespace Fm { class AppMenuViewItem : public QStandardItem { public: - explicit AppMenuViewItem(MenuCacheItem* item): - item_(menu_cache_item_ref(item)) { - FmIcon* fmicon; - if(menu_cache_item_get_icon(item)) - fmicon = fm_icon_from_name(menu_cache_item_get_icon(item)); - else - fmicon = nullptr; - setText(QString::fromUtf8(menu_cache_item_get_name(item))); - setEditable(false); - setDragEnabled(false); - if(fmicon) { - setIcon(IconTheme::icon(fmicon)); - fm_icon_unref(fmicon); + explicit AppMenuViewItem(MenuCacheItem* item): + item_(menu_cache_item_ref(item)) { + std::shared_ptr icon; + if(menu_cache_item_get_icon(item)) { + icon = Fm::IconInfo::fromName(menu_cache_item_get_icon(item)); + } + setText(menu_cache_item_get_name(item)); + setEditable(false); + setDragEnabled(false); + if(icon) { + setIcon(icon->qicon()); + } } - } - ~AppMenuViewItem() { - menu_cache_item_unref(item_); - } + ~AppMenuViewItem() { + menu_cache_item_unref(item_); + } - MenuCacheItem* item() { - return item_; - } + MenuCacheItem* item() { + return item_; + } - MenuCacheType type() { - return menu_cache_item_get_type(item_); - } + int type() const { + return menu_cache_item_get_type(item_); + } - bool isApp() { - return type() == MENU_CACHE_TYPE_APP; - } + bool isApp() { + return type() == MENU_CACHE_TYPE_APP; + } - bool isDir() { - return type() == MENU_CACHE_TYPE_DIR; - } + bool isDir() { + return type() == MENU_CACHE_TYPE_DIR; + } private: - MenuCacheItem* item_; + MenuCacheItem* item_; }; } diff --git a/src/archiver.h b/src/archiver.h index 9688327..216a32c 100644 --- a/src/archiver.h +++ b/src/archiver.h @@ -40,7 +40,7 @@ public: // move constructor - Archiver(Archiver&& other) { + Archiver(Archiver&& other) noexcept { dataPtr_ = reinterpret_cast(other.takeDataPtr()); } @@ -85,7 +85,7 @@ public: // move assignment - Archiver& operator=(Archiver&& other) { + Archiver& operator=(Archiver&& other) noexcept { dataPtr_ = reinterpret_cast(other.takeDataPtr()); return *this; } diff --git a/src/bookmarkaction.cpp b/src/bookmarkaction.cpp index e67b6ac..502126a 100644 --- a/src/bookmarkaction.cpp +++ b/src/bookmarkaction.cpp @@ -22,11 +22,11 @@ namespace Fm { -BookmarkAction::BookmarkAction(FmBookmarkItem* item, QObject* parent): - QAction(parent), - item_(fm_bookmark_item_ref(item)) { +BookmarkAction::BookmarkAction(std::shared_ptr item, QObject* parent): + QAction(parent), + item_(std::move(item)) { - setText(QString::fromUtf8(item->name)); + setText(item_->name()); } } // namespace Fm diff --git a/src/bookmarkaction.h b/src/bookmarkaction.h index 65c5ebd..08fc73f 100644 --- a/src/bookmarkaction.h +++ b/src/bookmarkaction.h @@ -23,30 +23,25 @@ #include "libfmqtglobals.h" #include -#include +#include "core/bookmarks.h" namespace Fm { // action used to create bookmark menu items class LIBFM_QT_API BookmarkAction : public QAction { public: - explicit BookmarkAction(FmBookmarkItem* item, QObject* parent = 0); + explicit BookmarkAction(std::shared_ptr item, QObject* parent = 0); - virtual ~BookmarkAction() { - if(item_) - fm_bookmark_item_unref(item_); - } + const std::shared_ptr& bookmark() const { + return item_; + } - FmBookmarkItem* bookmark() { - return item_; - } - - FmPath* path() { - return item_->path; - } + const Fm::FilePath& path() const { + return item_->path(); + } private: - FmBookmarkItem* item_; + std::shared_ptr item_; }; } diff --git a/src/bookmarks.h b/src/bookmarks.h deleted file mode 100644 index 0545189..0000000 --- a/src/bookmarks.h +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_BOOKMARKS_H__ -#define __LIBFM_QT_FM_BOOKMARKS_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - -class LIBFM_QT_API Bookmarks { -public: - - - // default constructor - Bookmarks() { - dataPtr_ = nullptr; - } - - - Bookmarks(FmBookmarks* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(g_object_ref(dataPtr)) : nullptr; - } - - - // copy constructor - Bookmarks(const Bookmarks& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - Bookmarks(Bookmarks&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - virtual ~Bookmarks() { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static Bookmarks wrapPtr(FmBookmarks* dataPtr) { - Bookmarks obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmBookmarks* takeDataPtr() { - FmBookmarks* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmBookmarks* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmBookmarks*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - Bookmarks& operator=(const Bookmarks& other) { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - Bookmarks& operator=(Bookmarks&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - GList* getAll(void) { - return fm_bookmarks_get_all(dataPtr()); - } - - - void rename(FmBookmarkItem* item, const char* new_name) { - fm_bookmarks_rename(dataPtr(), item, new_name); - } - - - void reorder(FmBookmarkItem* item, int pos) { - fm_bookmarks_reorder(dataPtr(), item, pos); - } - - - void remove(FmBookmarkItem* item) { - fm_bookmarks_remove(dataPtr(), item); - } - - - FmBookmarkItem* insert(FmPath* path, const char* name, int pos) { - return fm_bookmarks_insert(dataPtr(), path, name, pos); - } - - - static Bookmarks dup(void ) { - return Bookmarks::wrapPtr(fm_bookmarks_dup()); - } - - - // automatic type casting for GObject - operator GObject*() { - return reinterpret_cast(dataPtr_); - } - - -protected: - GObject* dataPtr_; // data pointer for the underlying C struct - -}; - - -} - -#endif // __LIBFM_QT_FM_BOOKMARKS_H__ diff --git a/src/browsehistory.cpp b/src/browsehistory.cpp index f33d185..54671fb 100644 --- a/src/browsehistory.cpp +++ b/src/browsehistory.cpp @@ -23,67 +23,70 @@ namespace Fm { BrowseHistory::BrowseHistory(): - currentIndex_(0), - maxCount_(10) { + currentIndex_(0), + maxCount_(10) { } BrowseHistory::~BrowseHistory() { } -void BrowseHistory::add(FmPath* path, int scrollPos) { - int lastIndex = size() - 1; - if(currentIndex_ < lastIndex) { - // if we're not at the last item, remove items after the current one. - erase(begin() + currentIndex_ + 1, end()); - } +void BrowseHistory::add(Fm::FilePath path, int scrollPos) { + int lastIndex = items_.size() - 1; + if(currentIndex_ < lastIndex) { + // if we're not at the last item, remove items after the current one. + items_.erase(items_.cbegin() + currentIndex_ + 1, items_.cend()); + } - if(size() + 1 > maxCount_) { - // if there are too many items, remove the oldest one. - // FIXME: what if currentIndex_ == 0? remove the last item instead? - if(currentIndex_ == 0) - remove(lastIndex); - else { - remove(0); - --currentIndex_; + if(items_.size() + 1 > static_cast(maxCount_)) { + // if there are too many items, remove the oldest one. + // FIXME: what if currentIndex_ == 0? remove the last item instead? + if(currentIndex_ == 0) { + items_.erase(items_.cbegin() + lastIndex); + } + else { + items_.erase(items_.cbegin()); + --currentIndex_; + } } - } - // add a path and current scroll position to browse history - append(BrowseHistoryItem(path, scrollPos)); - currentIndex_ = size() - 1; + // add a path and current scroll position to browse history + items_.push_back(BrowseHistoryItem(path, scrollPos)); + currentIndex_ = items_.size() - 1; } void BrowseHistory::setCurrentIndex(int index) { - if(index >= 0 && index < size()) { - currentIndex_ = index; - // FIXME: should we emit a signal for the change? - } + if(index >= 0 && static_cast(index) < items_.size()) { + currentIndex_ = index; + // FIXME: should we emit a signal for the change? + } } bool BrowseHistory::canBackward() const { - return (currentIndex_ > 0); + return (currentIndex_ > 0); } int BrowseHistory::backward() { - if(canBackward()) - --currentIndex_; - return currentIndex_; + if(canBackward()) { + --currentIndex_; + } + return currentIndex_; } bool BrowseHistory::canForward() const { - return (currentIndex_ + 1 < size()); + return (static_cast(currentIndex_) + 1 < items_.size()); } int BrowseHistory::forward() { - if(canForward()) - ++currentIndex_; - return currentIndex_; + if(canForward()) { + ++currentIndex_; + } + return currentIndex_; } void BrowseHistory::setMaxCount(int maxCount) { - maxCount_ = maxCount; - if(size() > maxCount) { - // TODO: remove some items - } + maxCount_ = maxCount; + if(items_.size() > static_cast(maxCount)) { + // TODO: remove some items + } } diff --git a/src/browsehistory.h b/src/browsehistory.h index 27864f3..6ea570e 100644 --- a/src/browsehistory.h +++ b/src/browsehistory.h @@ -22,9 +22,11 @@ #define FM_BROWSEHISTORY_H #include "libfmqtglobals.h" -#include +#include #include +#include "core/filepath.h" + namespace Fm { // class used to story browsing history of folder views @@ -34,96 +36,95 @@ namespace Fm { class LIBFM_QT_API BrowseHistoryItem { public: - BrowseHistoryItem(): - path_(NULL), - scrollPos_(0) { - } - - BrowseHistoryItem(FmPath* path, int scrollPos = 0): - path_(fm_path_ref(path)), - scrollPos_(scrollPos) { - } - - BrowseHistoryItem(const BrowseHistoryItem& other): - path_(other.path_ ? fm_path_ref(other.path_) : NULL), - scrollPos_(other.scrollPos_) { - } - - ~BrowseHistoryItem() { - if(path_) - fm_path_unref(path_); - } - - BrowseHistoryItem& operator=(const BrowseHistoryItem& other) { - if(path_) - fm_path_unref(path_); - path_ = other.path_ ? fm_path_ref(other.path_) : NULL; - scrollPos_ = other.scrollPos_; - return *this; - } - - FmPath* path() const { - return path_; - } - - int scrollPos() const { - return scrollPos_; - } - - void setScrollPos(int pos) { - scrollPos_ = pos; - } + explicit BrowseHistoryItem(): + scrollPos_(0) { + } + + explicit BrowseHistoryItem(Fm::FilePath path, int scrollPos = 0): + path_(std::move(path)), + scrollPos_(scrollPos) { + } + + BrowseHistoryItem(const BrowseHistoryItem& other) = default; + + ~BrowseHistoryItem() { + } + + BrowseHistoryItem& operator=(const BrowseHistoryItem& other) { + path_ = other.path_; + scrollPos_ = other.scrollPos_; + return *this; + } + + Fm::FilePath path() const { + return path_; + } + + int scrollPos() const { + return scrollPos_; + } + + void setScrollPos(int pos) { + scrollPos_ = pos; + } private: - FmPath* path_; - int scrollPos_; - // TODO: we may need to store current selection as well. reserve room for furutre expansion. - // void* reserved1; - // void* reserved2; + Fm::FilePath path_; + int scrollPos_; + // TODO: we may need to store current selection as well. }; -class LIBFM_QT_API BrowseHistory : public QVector { +class LIBFM_QT_API BrowseHistory { public: - BrowseHistory(); - virtual ~BrowseHistory(); + BrowseHistory(); + virtual ~BrowseHistory(); + + int currentIndex() const { + return currentIndex_; + } + void setCurrentIndex(int index); + + Fm::FilePath currentPath() const { + return items_[currentIndex_].path(); + } - int currentIndex() const { - return currentIndex_; - } - void setCurrentIndex(int index); + int currentScrollPos() const { + return items_[currentIndex_].scrollPos(); + } - FmPath* currentPath() const { - return at(currentIndex_).path(); - } + BrowseHistoryItem& currentItem() { + return items_[currentIndex_]; + } - int currentScrollPos() const { - return at(currentIndex_).scrollPos(); - } + size_t size() const { + return items_.size(); + } - BrowseHistoryItem& currentItem() { - return operator[](currentIndex_); - } + BrowseHistoryItem& at(int index) { + return items_[index]; + } - void add(FmPath* path, int scrollPos = 0); + void add(Fm::FilePath path, int scrollPos = 0); - bool canForward() const; + bool canForward() const; - bool canBackward() const; + bool canBackward() const; - int backward(); + int backward(); - int forward(); + int forward(); - int maxCount() const { - return maxCount_; - } + int maxCount() const { + return maxCount_; + } - void setMaxCount(int maxCount); + void setMaxCount(int maxCount); private: - int currentIndex_; - int maxCount_; + std::vector items_; + int currentIndex_; + int maxCount_; }; } diff --git a/src/cachedfoldermodel.cpp b/src/cachedfoldermodel.cpp index 07b1e95..c594cb9 100644 --- a/src/cachedfoldermodel.cpp +++ b/src/cachedfoldermodel.cpp @@ -21,53 +21,46 @@ namespace Fm { -static GQuark data_id = 0; - - -CachedFolderModel::CachedFolderModel(FmFolder* folder): - FolderModel(), - refCount(1) { - - FolderModel::setFolder(folder); +CachedFolderModel::CachedFolderModel(const std::shared_ptr& folder): + FolderModel(), + refCount(1) { + FolderModel::setFolder(folder); } CachedFolderModel::~CachedFolderModel() { + // qDebug("delete CachedFolderModel"); } -CachedFolderModel* CachedFolderModel::modelFromFolder(FmFolder* folder) { - CachedFolderModel* model = NULL; - if(!data_id) - data_id = g_quark_from_static_string("CachedFolderModel"); - gpointer qdata = g_object_get_qdata(G_OBJECT(folder), data_id); - model = reinterpret_cast(qdata); - if(model) { - // qDebug("cache found!!"); - model->ref(); - } - else { - model = new CachedFolderModel(folder); - g_object_set_qdata(G_OBJECT(folder), data_id, model); - } - return model; +CachedFolderModel* CachedFolderModel::modelFromFolder(const std::shared_ptr& folder) { + QVariant cache = folder->property(cacheKey); + CachedFolderModel* model = cache.value(); + if(model) { + model->ref(); + } + else { + model = new CachedFolderModel(folder); + cache = QVariant::fromValue(model); + folder->setProperty(cacheKey, cache); + } + return model; } -CachedFolderModel* CachedFolderModel::modelFromPath(FmPath* path) { - FmFolder* folder = fm_folder_from_path(path); - if(folder) { - CachedFolderModel* model = modelFromFolder(folder); - g_object_unref(folder); - return model; - } - return NULL; +CachedFolderModel* CachedFolderModel::modelFromPath(const Fm::FilePath& path) { + auto folder = Fm::Folder::fromPath(path); + if(folder) { + CachedFolderModel* model = modelFromFolder(folder); + return model; + } + return nullptr; } void CachedFolderModel::unref() { - // qDebug("unref cache"); - --refCount; - if(refCount <= 0) { - g_object_set_qdata(G_OBJECT(folder()), data_id, NULL); - deleteLater(); - } + // qDebug("unref cache"); + --refCount; + if(refCount <= 0) { + folder()->setProperty(cacheKey, QVariant()); + deleteLater(); + } } diff --git a/src/cachedfoldermodel.h b/src/cachedfoldermodel.h index 273fba9..1c22489 100644 --- a/src/cachedfoldermodel.h +++ b/src/cachedfoldermodel.h @@ -24,25 +24,29 @@ #include "libfmqtglobals.h" #include "foldermodel.h" +#include "core/folder.h" + namespace Fm { +// FIXME: deprecate CachedFolderModel later (ugly API design with manual ref()/unref()) class LIBFM_QT_API CachedFolderModel : public FolderModel { - Q_OBJECT + Q_OBJECT public: - CachedFolderModel(FmFolder* folder); - void ref() { - ++refCount; - } - void unref(); + explicit CachedFolderModel(const std::shared_ptr& folder); + void ref() { + ++refCount; + } + void unref(); - static CachedFolderModel* modelFromFolder(FmFolder* folder); - static CachedFolderModel* modelFromPath(FmPath* path); + static CachedFolderModel* modelFromFolder(const std::shared_ptr& folder); + static CachedFolderModel* modelFromPath(const Fm::FilePath& path); private: - virtual ~CachedFolderModel(); - void setFolder(FmFolder* folder); + virtual ~CachedFolderModel(); + void setFolder(FmFolder* folder); private: - int refCount; + int refCount; + constexpr static const char* cacheKey = "CachedFolderModel"; }; diff --git a/src/colorbutton.cpp b/src/colorbutton.cpp index 346abcd..ac95b45 100644 --- a/src/colorbutton.cpp +++ b/src/colorbutton.cpp @@ -24,7 +24,7 @@ namespace Fm { ColorButton::ColorButton(QWidget* parent): QPushButton(parent) { - connect(this, &QPushButton::clicked, this, &ColorButton::onClicked); + connect(this, &QPushButton::clicked, this, &ColorButton::onClicked); } ColorButton::~ColorButton() { @@ -32,21 +32,21 @@ ColorButton::~ColorButton() { } void ColorButton::onClicked() { - QColorDialog dlg(color_); - if(dlg.exec() == QDialog::Accepted) { - setColor(dlg.selectedColor()); - } + QColorDialog dlg(color_); + if(dlg.exec() == QDialog::Accepted) { + setColor(dlg.selectedColor()); + } } void ColorButton::setColor(const QColor& color) { - if(color != color_) { - color_ = color; - // use qss instead of QPalette to set the background color - // otherwise, this won't work when using the gtk style. - QString style = QString("QPushButton{background-color:%1;}").arg(color.name()); - setStyleSheet(style); - Q_EMIT changed(); - } + if(color != color_) { + color_ = color; + // use qss instead of QPalette to set the background color + // otherwise, this won't work when using the gtk style. + QString style = QString("QPushButton{background-color:%1;}").arg(color.name()); + setStyleSheet(style); + Q_EMIT changed(); + } } diff --git a/src/colorbutton.h b/src/colorbutton.h index e8e0072..d5fa89d 100644 --- a/src/colorbutton.h +++ b/src/colorbutton.h @@ -28,26 +28,26 @@ namespace Fm { class LIBFM_QT_API ColorButton : public QPushButton { -Q_OBJECT + Q_OBJECT public: - explicit ColorButton(QWidget* parent = 0); - virtual ~ColorButton(); + explicit ColorButton(QWidget* parent = 0); + virtual ~ColorButton(); - void setColor(const QColor&); + void setColor(const QColor&); - QColor color() const { - return color_; - } + QColor color() const { + return color_; + } Q_SIGNALS: - void changed(); + void changed(); private Q_SLOTS: - void onClicked(); + void onClicked(); private: - QColor color_; + QColor color_; }; } diff --git a/src/config.h b/src/config.h deleted file mode 100644 index d1cffd9..0000000 --- a/src/config.h +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_CONFIG_H__ -#define __LIBFM_QT_FM_CONFIG_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - -class LIBFM_QT_API Config { -public: - - - Config(void ) { - dataPtr_ = reinterpret_cast(fm_config_new()); - } - - - Config(FmConfig* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(g_object_ref(dataPtr)) : nullptr; - } - - - // copy constructor - Config(const Config& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - Config(Config&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - virtual ~Config() { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static Config wrapPtr(FmConfig* dataPtr) { - Config obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmConfig* takeDataPtr() { - FmConfig* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmConfig* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmConfig*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - Config& operator=(const Config& other) { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - Config& operator=(Config&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - void emitChanged(const char* changed_key) { - fm_config_emit_changed(dataPtr(), changed_key); - } - - - void save(const char* name) { - fm_config_save(dataPtr(), name); - } - - - void loadFromKeyFile(GKeyFile* kf) { - fm_config_load_from_key_file(dataPtr(), kf); - } - - - void loadFromFile(const char* name) { - fm_config_load_from_file(dataPtr(), name); - } - - - // automatic type casting for GObject - operator GObject*() { - return reinterpret_cast(dataPtr_); - } - - -protected: - GObject* dataPtr_; // data pointer for the underlying C struct - -}; - - -} - -#endif // __LIBFM_QT_FM_CONFIG_H__ diff --git a/src/core/bookmarks.cpp b/src/core/bookmarks.cpp new file mode 100644 index 0000000..8397550 --- /dev/null +++ b/src/core/bookmarks.cpp @@ -0,0 +1,162 @@ +#include "bookmarks.h" +#include "cstrptr.h" +#include +#include + +namespace Fm { + +std::weak_ptr Bookmarks::globalInstance_; + +static inline CStrPtr get_legacy_bookmarks_file(void) { + return CStrPtr{g_build_filename(g_get_home_dir(), ".gtk-bookmarks", nullptr)}; +} + +static inline CStrPtr get_new_bookmarks_file(void) { + return CStrPtr{g_build_filename(g_get_user_config_dir(), "gtk-3.0", "bookmarks", nullptr)}; +} + +Bookmarks::Bookmarks(QObject* parent): + QObject(parent), + idle_handler{false} { + + /* trying the gtk-3.0 first and use it if it exists */ + auto fpath = get_new_bookmarks_file(); + file = FilePath::fromLocalPath(fpath.get()); + load(); + if(items_.empty()) { /* not found, use legacy file */ + fpath = get_legacy_bookmarks_file(); + file = FilePath::fromLocalPath(fpath.get()); + load(); + } + mon = GObjectPtr{g_file_monitor_file(file.gfile().get(), G_FILE_MONITOR_NONE, nullptr, nullptr), false}; + if(mon) { + g_signal_connect(mon.get(), "changed", G_CALLBACK(_onFileChanged), this); + } +} + +Bookmarks::~Bookmarks() { + if(mon) { + g_signal_handlers_disconnect_by_data(mon.get(), this); + } +} + +const std::shared_ptr& Bookmarks::insert(const FilePath& path, const QString& name, int pos) { + const auto insert_pos = (pos < 0 || static_cast(pos) > items_.size()) ? items_.cend() : items_.cbegin() + pos; + auto it = items_.insert(insert_pos, std::make_shared(path, name)); + queueSave(); + return *it; +} + +void Bookmarks::remove(const std::shared_ptr& item) { + items_.erase(std::remove(items_.begin(), items_.end(), item), items_.end()); + queueSave(); +} + +void Bookmarks::reorder(const std::shared_ptr& item, int pos) { + auto old_it = std::find(items_.cbegin(), items_.cend(), item); + if(old_it == items_.cend()) + return; + std::shared_ptr newItem = item; + auto old_pos = old_it - items_.cbegin(); + items_.erase(old_it); + if(old_pos < pos) + --pos; + auto new_it = items_.cbegin() + pos; + if(new_it > items_.cend()) + new_it = items_.cend(); + items_.insert(new_it, std::move(newItem)); + queueSave(); +} + +void Bookmarks::rename(const std::shared_ptr& item, QString new_name) { + auto it = std::find_if(items_.cbegin(), items_.cend(), [item](const std::shared_ptr& elem) { + return elem->path() == item->path(); + }); + if(it != items_.cend()) { + // create a new item to replace the old one + // we do not modify the old item directly since this data structure is shared with others + it = items_.insert(it, std::make_shared(item->path(), new_name)); + items_.erase(it + 1); // remove the old item + queueSave(); + } +} + +std::shared_ptr Bookmarks::globalInstance() { + auto bookmarks = globalInstance_.lock(); + if(!bookmarks) { + bookmarks = std::make_shared(); + globalInstance_ = bookmarks; + } + return bookmarks; +} + +void Bookmarks::save() { + std::string buf; + // G_LOCK(bookmarks); + for(auto& item: items_) { + auto uri = item->path().uri(); + buf += uri.get(); + buf += ' '; + buf += item->name().toUtf8().constData(); + buf += '\n'; + } + idle_handler = false; + // G_UNLOCK(bookmarks); + GError* err = nullptr; + if(!g_file_replace_contents(file.gfile().get(), buf.c_str(), buf.length(), nullptr, + FALSE, G_FILE_CREATE_NONE, nullptr, nullptr, &err)) { + g_critical("%s", err->message); + g_error_free(err); + } + /* we changed bookmarks list, let inform who interested in that */ + Q_EMIT changed(); +} + +void Bookmarks::load() { + auto fpath = file.localPath(); + FILE* f; + char buf[1024]; + /* load the file */ + f = fopen(fpath.get(), "r"); + if(f) { + while(fgets(buf, 1024, f)) { + // format of each line in the bookmark file: + // \n + char* sep; + sep = strchr(buf, '\n'); + if(sep) { + *sep = '\0'; + } + + QString name; + sep = strchr(buf, ' '); // find the separator between URI and name + if(sep) { + *sep = '\0'; + name = sep + 1; + } + auto uri = buf; + if(uri[0] != '\0') { + items_.push_back(std::make_shared(FilePath::fromUri(uri), name)); + } + } + fclose(f); + } +} + +void Bookmarks::onFileChanged(GFileMonitor* /*mon*/, GFile* /*gf*/, GFile* /*other*/, GFileMonitorEvent /*evt*/) { + // reload the bookmarks + items_.clear(); + load(); + Q_EMIT changed(); +} + + +void Bookmarks::queueSave() { + if(!idle_handler) { + QTimer::singleShot(0, this, &Bookmarks::save); + idle_handler = true; + } +} + + +} // namespace Fm diff --git a/src/core/bookmarks.h b/src/core/bookmarks.h new file mode 100644 index 0000000..5c19d2f --- /dev/null +++ b/src/core/bookmarks.h @@ -0,0 +1,96 @@ +#ifndef FM2_BOOKMARKS_H +#define FM2_BOOKMARKS_H + +#include "../libfmqtglobals.h" +#include +#include "gobjectptr.h" +#include "fileinfo.h" + + +namespace Fm { + +class LIBFM_QT_API BookmarkItem { +public: + friend class Bookmarks; + + explicit BookmarkItem(const FilePath& path, const QString name): path_{path}, name_{name} { + if(name_.isEmpty()) { // if the name is not specified, use basename of the path + name_ = path_.baseName().get(); + } + } + + const QString& name() const { + return name_; + } + + const FilePath& path() const { + return path_; + } + + const std::shared_ptr& info() const { + return info_; + } + +private: + void setInfo(const std::shared_ptr& info) { + info_ = info; + } + + void setName(const QString& name) { + name_ = name; + } + +private: + FilePath path_; + QString name_; + std::shared_ptr info_; +}; + + +class LIBFM_QT_API Bookmarks : public QObject { + Q_OBJECT +public: + explicit Bookmarks(QObject* parent = 0); + + ~Bookmarks(); + + const std::shared_ptr &insert(const FilePath& path, const QString& name, int pos); + + void remove(const std::shared_ptr& item); + + void reorder(const std::shared_ptr &item, int pos); + + void rename(const std::shared_ptr& item, QString new_name); + + const std::vector>& items() const { + return items_; + } + + static std::shared_ptr globalInstance(); + +Q_SIGNALS: + void changed(); + +private Q_SLOTS: + void save(); + +private: + void load(); + void queueSave(); + + static void _onFileChanged(GFileMonitor* mon, GFile* gf, GFile* other, GFileMonitorEvent evt, Bookmarks* _this) { + _this->onFileChanged(mon, gf, other, evt); + } + void onFileChanged(GFileMonitor* mon, GFile* gf, GFile* other, GFileMonitorEvent evt); + +private: + FilePath file; + GObjectPtr mon; + std::vector> items_; + static std::weak_ptr globalInstance_; + bool idle_handler; +}; + +} // namespace Fm + +#endif // FM2_BOOKMARKS_H diff --git a/src/core/compat_p.h b/src/core/compat_p.h new file mode 100644 index 0000000..86d550f --- /dev/null +++ b/src/core/compat_p.h @@ -0,0 +1,53 @@ +#ifndef LIBFM_QT_COMPAT_P_H +#define LIBFM_QT_COMPAT_P_H + +#include "../libfmqtglobals.h" +#include "core/filepath.h" +#include "core/fileinfo.h" +#include "core/gioptrs.h" + +// deprecated +#include +#include "path.h" + +// compatibility functions bridging the old libfm C APIs and new C++ APIs. + +namespace Fm { + +inline FM_QT_DEPRECATED Fm::Path _convertPath(const Fm::FilePath& path) { + return Fm::Path::newForGfile(path.gfile().get()); +} + +inline FM_QT_DEPRECATED Fm::PathList _convertPathList(const Fm::FilePathList& srcFiles) { + Fm::PathList ret; + for(auto& file: srcFiles) { + ret.pushTail(_convertPath(file)); + } + return ret; +} + +inline FM_QT_DEPRECATED FmFileInfo* _convertFileInfo(const std::shared_ptr& info) { + // conver to GFileInfo first + GFileInfoPtr ginfo{g_file_info_new(), false}; + g_file_info_set_name(ginfo.get(), info->name().c_str()); + g_file_info_set_display_name(ginfo.get(), info->displayName().toUtf8().constData()); + g_file_info_set_content_type(ginfo.get(), info->mimeType()->name()); + + auto mode = info->mode(); + g_file_info_set_attribute_uint32(ginfo.get(), G_FILE_ATTRIBUTE_UNIX_MODE, mode); + GFileType ftype = info->isDir() ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR; // FIXME: generate more accurate type + g_file_info_set_file_type(ginfo.get(), ftype); + g_file_info_set_size(ginfo.get(), info->size()); + g_file_info_set_icon(ginfo.get(), info->icon()->gicon().get()); + + g_file_info_set_attribute_uint64(ginfo.get(), G_FILE_ATTRIBUTE_TIME_MODIFIED, info->mtime()); + g_file_info_set_attribute_uint64(ginfo.get(), G_FILE_ATTRIBUTE_TIME_ACCESS, info->atime()); + g_file_info_set_attribute_uint64(ginfo.get(), G_FILE_ATTRIBUTE_TIME_CHANGED, info->ctime()); + + auto gf = info->path().gfile(); + return fm_file_info_new_from_g_file_data(gf.get(), ginfo.get(), nullptr); +} + +} + +#endif // LIBFM_QT_COMPAT_P_H diff --git a/src/core/copyjob.cpp b/src/core/copyjob.cpp new file mode 100644 index 0000000..73ef3cf --- /dev/null +++ b/src/core/copyjob.cpp @@ -0,0 +1,453 @@ +#include "copyjob.h" +#include "totalsizejob.h" +#include "fileinfo_p.h" + +namespace Fm { + +CopyJob::CopyJob(const FilePathList& paths, const FilePath& destDirPath, Mode mode): + FileOperationJob{}, + srcPaths_{paths}, + destDirPath_{destDirPath}, + mode_{mode}, + skip_dir_content{false} { +} + +CopyJob::CopyJob(const FilePathList &&paths, const FilePath &&destDirPath, Mode mode): + FileOperationJob{}, + srcPaths_{paths}, + destDirPath_{destDirPath}, + mode_{mode}, + skip_dir_content{false} { +} + +void CopyJob::gfileProgressCallback(goffset current_num_bytes, goffset total_num_bytes, CopyJob* _this) { + _this->setCurrentFileProgress(total_num_bytes, current_num_bytes); +} + +bool CopyJob::copyRegularFile(const FilePath& srcPath, GFileInfoPtr /*srcFile*/, const FilePath& destPath) { + int flags = G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS; + GErrorPtr err; +_retry_copy: + if(!g_file_copy(srcPath.gfile().get(), destPath.gfile().get(), GFileCopyFlags(flags), cancellable().get(), + GFileProgressCallback(gfileProgressCallback), this, &err)) { + flags &= ~G_FILE_COPY_OVERWRITE; + /* handle existing files or file name conflict */ + if(err.domain() == G_IO_ERROR && (err.code() == G_IO_ERROR_EXISTS || + err.code() == G_IO_ERROR_INVALID_FILENAME || + err.code() == G_IO_ERROR_FILENAME_TOO_LONG)) { +#if 0 + GFile* dest_cp = new_dest; + bool dest_exists = (err->code == G_IO_ERROR_EXISTS); + FmFileOpOption opt = 0; + g_error_free(err); + err = nullptr; + + new_dest = nullptr; + opt = _fm_file_ops_job_ask_new_name(job, src, dest, &new_dest, dest_exists); + if(!new_dest) { /* restoring status quo */ + new_dest = dest_cp; + } + else if(dest_cp) { /* we got new new_dest, forget old one */ + g_object_unref(dest_cp); + } + switch(opt) { + case FM_FILE_OP_RENAME: + dest = new_dest; + goto _retry_copy; + break; + case FM_FILE_OP_OVERWRITE: + flags |= G_FILE_COPY_OVERWRITE; + goto _retry_copy; + break; + case FM_FILE_OP_CANCEL: + fm_job_cancel(fmjob); + break; + case FM_FILE_OP_SKIP: + ret = true; + delete_src = false; /* don't delete source file. */ + break; + case FM_FILE_OP_SKIP_ERROR: ; /* FIXME */ + } +#endif + } + else { + ErrorAction act = emitError( err, ErrorSeverity::MODERATE); + err.reset(); + if(act == ErrorAction::RETRY) { + // FIXME: job->current_file_finished = 0; + goto _retry_copy; + } +# if 0 + const bool is_no_space = (err.domain() == G_IO_ERROR && + err.code() == G_IO_ERROR_NO_SPACE); + /* FIXME: ask to leave partial content? */ + if(is_no_space) { + g_file_delete(dest, fm_job_get_cancellable(fmjob), nullptr); + } + ret = false; + delete_src = false; +#endif + } + err.reset(); + } + else { + return true; + } + return false; +} + +bool CopyJob::copySpecialFile(const FilePath& srcPath, GFileInfoPtr srcFile, const FilePath& destPath) { + bool ret = false; + GError* err = nullptr; + /* only handle FIFO for local files */ + if(srcPath.isNative() && destPath.isNative()) { + auto src_path = srcPath.localPath(); + struct stat src_st; + int r; + r = lstat(src_path.get(), &src_st); + if(r == 0) { + /* Handle FIFO on native file systems. */ + if(S_ISFIFO(src_st.st_mode)) { + auto dest_path = destPath.localPath(); + if(mkfifo(dest_path.get(), src_st.st_mode) == 0) { + ret = true; + } + } + /* FIXME: how about block device, char device, and socket? */ + } + } + if(!ret) { + g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED, + ("Cannot copy file '%s': not supported"), + g_file_info_get_display_name(srcFile.get())); + // emitError( err, ErrorSeverity::MODERATE); + g_clear_error(&err); + } + return ret; +} + +bool CopyJob::copyDir(const FilePath& srcPath, GFileInfoPtr srcFile, const FilePath& destPath) { + bool ret = false; + if(makeDir(srcPath, srcFile, destPath)) { + GError* err = nullptr; + auto enu = GFileEnumeratorPtr{ + g_file_enumerate_children(srcPath.gfile().get(), + gfile_info_query_attribs, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable().get(), &err), + false}; + if(enu) { + int n_children = 0; + int n_copied = 0; + ret = true; + while(!isCancelled()) { + auto inf = GFileInfoPtr{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false}; + if(inf) { + ++n_children; + /* don't overwrite dir content, only calculate progress. */ + if(Q_UNLIKELY(skip_dir_content)) { + /* FIXME: this is incorrect as we don't do the calculation recursively. */ + addFinishedAmount(g_file_info_get_size(inf.get()), 1); + } + else { + const char* name = g_file_info_get_name(inf.get()); + FilePath childPath = srcPath.child(name); + bool child_ret = copyPath(childPath, inf, destPath, name); + if(child_ret) { + ++n_copied; + } + else { + ret = false; + } + } + } + else { + if(err) { + // FIXME: emitError( err, ErrorSeverity::MODERATE); + g_error_free(err); + err = nullptr; + /* ErrorAction::RETRY is not supported here */ + ret = false; + } + else { /* EOF is reached */ + /* all files are successfully copied. */ + if(isCancelled()) { + ret = false; + } + else { + /* some files are not copied */ + if(n_children != n_copied) { + /* if the copy actions are skipped deliberately, it's ok */ + if(!skip_dir_content) { + ret = false; + } + } + /* else job->skip_dir_content is true */ + } + break; + } + } + } + g_file_enumerator_close(enu.get(), nullptr, &err); + } + } + return false; +} + +bool CopyJob::makeDir(const FilePath& srcPath, GFileInfoPtr srcFile, const FilePath& dirPath) { + GError* err = nullptr; + if(isCancelled()) + return false; + + FilePath destPath = dirPath; + bool mkdir_done = false; + do { + mkdir_done = g_file_make_directory(destPath.gfile().get(), cancellable().get(), &err); + if(err->domain == G_IO_ERROR && (err->code == G_IO_ERROR_EXISTS || + err->code == G_IO_ERROR_INVALID_FILENAME || + err->code == G_IO_ERROR_FILENAME_TOO_LONG)) { + GFileInfoPtr destFile; + // FIXME: query its info + FilePath newDestPath; + FileExistsAction opt = askRename(FileInfo{srcFile, srcPath.parent()}, FileInfo{destFile, dirPath.parent()}, newDestPath); + g_error_free(err); + err = nullptr; + + switch(opt) { + case FileOperationJob::RENAME: + destPath = newDestPath; + break; + case FileOperationJob::SKIP: + /* when a dir is skipped, we need to know its total size to calculate correct progress */ + // job->finished += size; + // fm_file_ops_job_emit_percent(job); + // job->skip_dir_content = skip_dir_content = true; + mkdir_done = true; /* pretend that dir creation succeeded */ + break; + case FileOperationJob::OVERWRITE: + mkdir_done = true; /* pretend that dir creation succeeded */ + break; + case FileOperationJob::CANCEL: + cancel(); + break; + case FileOperationJob::SKIP_ERROR: ; /* FIXME */ + } + } + else { +#if 0 + ErrorAction act = emitError( err, ErrorSeverity::MODERATE); + g_error_free(err); + err = nullptr; + if(act == ErrorAction::RETRY) { + goto _retry_mkdir; + } +#endif + break; + } + // job->finished += size; + } while(!mkdir_done && !isCancelled()); + + if(mkdir_done && !isCancelled()) { + bool chmod_done = false; + mode_t mode = g_file_info_get_attribute_uint32(srcFile.get(), G_FILE_ATTRIBUTE_UNIX_MODE); + if(mode) { + mode |= (S_IRUSR | S_IWUSR); /* ensure we have rw permission to this file. */ + do { + /* chmod the newly created dir properly */ + // if(!fm_job_is_cancelled(fmjob) && !job->skip_dir_content) + chmod_done = g_file_set_attribute_uint32(destPath.gfile().get(), + G_FILE_ATTRIBUTE_UNIX_MODE, + mode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable().get(), &err); + if(!chmod_done) { +/* + ErrorAction act = emitError( err, ErrorSeverity::MODERATE); + g_error_free(err); + err = nullptr; + if(act == ErrorAction::RETRY) { + goto _retry_chmod_for_dir; + } +*/ + /* FIXME: some filesystems may not support this. */ + } + } while(!chmod_done && !isCancelled()); + // finished += size; + // fm_file_ops_job_emit_percent(job); + } + } + return false; +} + +bool CopyJob::copyPath(const FilePath& srcPath, const FilePath& destDirPath, const char* destFileName) { + GErrorPtr err; + GFileInfoPtr srcInfo = GFileInfoPtr { + g_file_query_info(srcPath.gfile().get(), + gfile_info_query_attribs, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable().get(), &err), + false + }; + if(!srcInfo || isCancelled()) { + return false; + } + return copyPath(srcPath, srcInfo, destDirPath, destFileName); +} + +bool CopyJob::copyPath(const FilePath& srcPath, const GFileInfoPtr& srcInfo, const FilePath& destDirPath, const char* destFileName) { + setCurrentFile(srcPath); + GErrorPtr err; + GFileInfoPtr destDirInfo = GFileInfoPtr { + g_file_query_info(destDirPath.gfile().get(), + "id::filesystem", + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable().get(), &err), + false + }; + + if(!destDirInfo || isCancelled()) { + return false; + } + + auto size = g_file_info_get_size(srcInfo.get()); + setCurrentFileProgress(size, 0); + + auto destPath = destDirPath.child(destFileName); + bool success = false; + switch(g_file_info_get_file_type(srcInfo.get())) { + case G_FILE_TYPE_DIRECTORY: + success = copyDir(srcPath, srcInfo, destPath); + break; + case G_FILE_TYPE_SPECIAL: + success = copySpecialFile(srcPath, srcInfo, destPath); + break; + default: + success = copyRegularFile(srcPath, srcInfo, destPath); + break; + } + + if(success) { + addFinishedAmount(size, 1); +#if 0 + + if(ret && dest_folder) { + fm_dest = fm_path_new_for_gfile(dest); + if(!_fm_folder_event_file_added(dest_folder, fm_dest)) { + fm_path_unref(fm_dest); + } + } +#endif + } + + return false; +} + +#if 0 + +bool _fm_file_ops_job_copy_run(FmFileOpsJob* job) { + bool ret = true; + GFile* dest_dir; + GList* l; + FmJob* fmjob = FM_JOB(job); + /* prepare the job, count total work needed with FmDeepCountJob */ + FmDeepCountJob* dc = fm_deep_count_job_new(job->srcs, FM_DC_JOB_DEFAULT); + FmFolder* df; + + /* let the deep count job share the same cancellable object. */ + fm_job_set_cancellable(FM_JOB(dc), fm_job_get_cancellable(fmjob)); + fm_job_run_sync(FM_JOB(dc)); + job->total = dc->total_size; + if(fm_job_is_cancelled(fmjob)) { + g_object_unref(dc); + return false; + } + g_object_unref(dc); + g_debug("total size to copy: %llu", (long long unsigned int)job->total); + + dest_dir = fm_path_to_gfile(job->dest); + /* suspend updates for destination */ + df = fm_folder_find_by_path(job->dest); + if(df) { + fm_folder_block_updates(df); + } + + fm_file_ops_job_emit_prepared(job); + + for(l = fm_path_list_peek_head_link(job->srcs); !fm_job_is_cancelled(fmjob) && l; l = l->next) { + FmPath* path = FM_PATH(l->data); + GFile* src = fm_path_to_gfile(path); + GFile* dest; + char* tmp_basename; + + if(g_file_is_native(src) && g_file_is_native(dest_dir)) + /* both are native */ + { + tmp_basename = nullptr; + } + else if(g_file_is_native(src)) /* copy from native to virtual */ + tmp_basename = g_filename_to_utf8(fm_path_get_basename(path), + -1, nullptr, nullptr, nullptr); + /* gvfs escapes it itself */ + else { /* copy from virtual to native/virtual */ + /* if we drop URI query onto native filesystem, omit query part */ + const char* basename = fm_path_get_basename(path); + char* sub_name; + + sub_name = strchr(basename, '?'); + if(sub_name) { + sub_name = g_strndup(basename, sub_name - basename); + basename = strrchr(sub_name, G_DIR_SEPARATOR); + if(basename) { + basename++; + } + else { + basename = sub_name; + } + } + tmp_basename = fm_uri_subpath_to_native_subpath(basename, nullptr); + g_free(sub_name); + } + dest = g_file_get_child(dest_dir, + tmp_basename ? tmp_basename : fm_path_get_basename(path)); + g_free(tmp_basename); + if(!_fm_file_ops_job_copy_file(job, src, nullptr, dest, nullptr, df)) { + ret = false; + } + g_object_unref(src); + g_object_unref(dest); + } + + /* g_debug("finished: %llu, total: %llu", job->finished, job->total); */ + fm_file_ops_job_emit_percent(job); + + /* restore updates for destination */ + if(df) { + fm_folder_unblock_updates(df); + g_object_unref(df); + } + g_object_unref(dest_dir); + return ret; +} +#endif + +void CopyJob::exec() { + TotalSizeJob totalSizeJob{srcPaths_}; + connect(&totalSizeJob, &TotalSizeJob::error, this, &CopyJob::error); + connect(this, &CopyJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel); + totalSizeJob.run(); + if(isCancelled()) { + return; + } + + setTotalAmount(totalSizeJob.totalSize(), totalSizeJob.fileCount()); + Q_EMIT preparedToRun(); + + for(auto& srcPath : srcPaths_) { + if(isCancelled()) { + break; + } + copyPath(srcPath, destDirPath_, srcPath.baseName().get()); + } +} + + +} // namespace Fm diff --git a/src/core/copyjob.h b/src/core/copyjob.h new file mode 100644 index 0000000..1cdbbe5 --- /dev/null +++ b/src/core/copyjob.h @@ -0,0 +1,46 @@ +#ifndef FM2_COPYJOB_H +#define FM2_COPYJOB_H + +#include "../libfmqtglobals.h" +#include "fileoperationjob.h" +#include "gioptrs.h" + +namespace Fm { + +class LIBFM_QT_API CopyJob : public Fm::FileOperationJob { + Q_OBJECT +public: + + enum class Mode { + COPY, + MOVE + }; + + explicit CopyJob(const FilePathList& paths, const FilePath& destDirPath, Mode mode = Mode::COPY); + + explicit CopyJob(const FilePathList&& paths, const FilePath&& destDirPath, Mode mode = Mode::COPY); + +protected: + void exec() override; + +private: + bool copyPath(const FilePath& srcPath, const FilePath& destPath, const char *destFileName); + bool copyPath(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName); + bool copyRegularFile(const FilePath &srcPath, GFileInfoPtr srcFile, const FilePath& destPath); + bool copySpecialFile(const FilePath &srcPath, GFileInfoPtr srcFile, const FilePath& destPath); + bool copyDir(const FilePath &srcPath, GFileInfoPtr srcFile, const FilePath& destPath); + bool makeDir(const FilePath &srcPath, GFileInfoPtr srcFile, const FilePath& dirPath); + + static void gfileProgressCallback(goffset current_num_bytes, goffset total_num_bytes, CopyJob* _this); + +private: + FilePathList srcPaths_; + FilePath destDirPath_; + Mode mode_; + bool skip_dir_content; +}; + + +} // namespace Fm + +#endif // FM2_COPYJOB_H diff --git a/src/core/cstrptr.h b/src/core/cstrptr.h new file mode 100644 index 0000000..cc12a0f --- /dev/null +++ b/src/core/cstrptr.h @@ -0,0 +1,42 @@ +#ifndef FM2_CSTRPTR_H +#define FM2_CSTRPTR_H + +#include +#include + +namespace Fm { + +struct CStrDeleter { + void operator()(char* ptr) { + g_free(ptr); + } +}; + +// smart pointer for C string (char*) which should be freed by free() +typedef std::unique_ptr CStrPtr; + +struct CStrHash { + std::size_t operator()(const char* str) const { + return g_str_hash(str); + } +}; + +struct CStrEqual { + bool operator()(const char* str1, const char* str2) const { + return g_str_equal(str1, str2); + } +}; + +struct CStrVDeleter { + void operator()(char** ptr) { + g_strfreev(ptr); + } +}; + +// smart pointer for C string array (char**) which should be freed by g_strfreev() of glib +typedef std::unique_ptr CStrArrayPtr; + + +} // namespace Fm + +#endif // FM2_CSTRPTR_H diff --git a/src/core/deletejob.cpp b/src/core/deletejob.cpp new file mode 100644 index 0000000..b2c518f --- /dev/null +++ b/src/core/deletejob.cpp @@ -0,0 +1,151 @@ +#include "deletejob.h" +#include "totalsizejob.h" +#include "fileinfo_p.h" + +namespace Fm { + +bool DeleteJob::deleteFile(const FilePath& path, GFileInfoPtr inf) { + ErrorAction act = ErrorAction::CONTINUE; + while(!inf) { + GErrorPtr err; + inf = GFileInfoPtr{ + g_file_query_info(path.gfile().get(), "standard::*", + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable().get(), &err), + false + }; + if(err) { + act = emitError(err, ErrorSeverity::SEVERE); + if(act == ErrorAction::ABORT) { + return false; + } + if(act != ErrorAction::RETRY) { + break; + } + } + } + + /* currently processed file. */ + setCurrentFile(path); + + if(g_file_info_get_file_type(inf.get()) == G_FILE_TYPE_DIRECTORY) { + // delete the content of the dir prior to deleting itself + deleteDirContent(path, inf); + } + + bool hasError = false; + while(!isCancelled()) { + GErrorPtr err; + // try to delete the path directly + if(g_file_delete(path.gfile().get(), cancellable().get(), &err)) { + break; + } + if(err) { + // FIXME: error handling + /* if it's non-empty dir then descent into it then try again */ + /* trash root gives G_IO_ERROR_PERMISSION_DENIED */ + if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_NOT_EMPTY) { + deleteDirContent(path, inf); + } + else if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_PERMISSION_DENIED) { + /* special case for trash:/// */ + /* FIXME: is there any better way to handle this? */ + auto scheme = path.uriScheme(); + if(g_strcmp0(scheme.get(), "trash") == 0) { + break; + } + } + act = emitError(err, ErrorSeverity::MODERATE); + if(act != ErrorAction::RETRY) { + hasError = true; + break; + } + } + } + + addFinishedAmount(g_file_info_get_size(inf.get()), 1); + + return !hasError; +} + +bool DeleteJob::deleteDirContent(const FilePath& path, GFileInfoPtr inf) { +#if 0 + FmFolder* sub_folder; + /* special handling for trash:/// */ + if(!g_file_is_native(gf)) { + char* scheme = g_file_get_uri_scheme(gf); + if(g_strcmp0(scheme, "trash") == 0) { + /* little trick: basename of trash root is /. */ + char* basename = g_file_get_basename(gf); + if(basename && basename[0] == G_DIR_SEPARATOR) { + is_trash_root = true; + } + g_free(basename); + } + g_free(scheme); + } +#endif + + GErrorPtr err; + GFileEnumeratorPtr enu { + g_file_enumerate_children(path.gfile().get(), gfile_info_query_attribs, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable().get(), &err), + false + }; + if(!enu) { + emitError(err, ErrorSeverity::MODERATE); + return false; + } + + bool hasError = false; + while(!isCancelled()) { + inf = GFileInfoPtr{ + g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), + false + }; + if(inf) { + auto subPath = path.child(g_file_info_get_name(inf.get())); + if(!deleteFile(subPath, inf)) { + continue; + } + } + else { + if(err) { + emitError(err, ErrorSeverity::MODERATE); + /* ErrorAction::RETRY is not supported here */ + hasError = true; + } + else { /* EOF */ + } + break; + } + } + g_file_enumerator_close(enu.get(), nullptr, nullptr); + return !hasError; +} + + +void DeleteJob::exec() { + /* prepare the job, count total work needed with FmDeepCountJob */ + TotalSizeJob totalSizeJob{paths_, TotalSizeJob::Flags::PREPARE_DELETE}; + connect(&totalSizeJob, &TotalSizeJob::error, this, &DeleteJob::error); + connect(this, &DeleteJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel); + totalSizeJob.run(); + + if(isCancelled()) { + return; + } + + setTotalAmount(totalSizeJob.totalSize(), totalSizeJob.fileCount()); + Q_EMIT preparedToRun(); + + for(auto& path : paths_) { + if(isCancelled()) { + break; + } + deleteFile(path, GFileInfoPtr{nullptr}); + } +} + +} // namespace Fm diff --git a/src/core/deletejob.h b/src/core/deletejob.h new file mode 100644 index 0000000..4560098 --- /dev/null +++ b/src/core/deletejob.h @@ -0,0 +1,36 @@ +#ifndef FM2_DELETEJOB_H +#define FM2_DELETEJOB_H + +#include "../libfmqtglobals.h" +#include "fileoperationjob.h" +#include "filepath.h" +#include "gioptrs.h" + +namespace Fm { + +class LIBFM_QT_API DeleteJob : public Fm::FileOperationJob { + Q_OBJECT +public: + explicit DeleteJob(const FilePathList& paths): paths_{paths} { + } + + explicit DeleteJob(FilePathList&& paths): paths_{paths} { + } + + ~DeleteJob() { + } + +protected: + void exec() override; + +private: + bool deleteFile(const FilePath& path, GFileInfoPtr inf); + bool deleteDirContent(const FilePath& path, GFileInfoPtr inf); + +private: + FilePathList paths_; +}; + +} // namespace Fm + +#endif // FM2_DELETEJOB_H diff --git a/src/core/dirlistjob.cpp b/src/core/dirlistjob.cpp new file mode 100644 index 0000000..d782ede --- /dev/null +++ b/src/core/dirlistjob.cpp @@ -0,0 +1,178 @@ +#include "dirlistjob.h" +#include +#include "fileinfo_p.h" +#include "gioptrs.h" +#include + +namespace Fm { + +DirListJob::DirListJob(const FilePath& path, Flags _flags, const std::shared_ptr& cutFilesHashSet): + dir_path{path}, flags{_flags}, cutFilesHashSet_{cutFilesHashSet} { +} + +void DirListJob::exec() { + GErrorPtr err; + GFileInfoPtr dir_inf; + GFilePtr dir_gfile = dir_path.gfile(); + // FIXME: these are hacks for search:/// URI implemented by libfm which contains some bugs + bool isFileSearch = dir_path.hasUriScheme("search"); + if(isFileSearch) { + // NOTE: The GFile instance changes its URI during file enumeration (bad design). + // So we create a copy here to avoid channging the gfile stored in dir_path. + // FIXME: later we should refactor file search and remove this dirty hack. + dir_gfile = GFilePtr{g_file_dup(dir_gfile.get())}; + } +_retry: + err.reset(); + dir_inf = GFileInfoPtr{ + g_file_query_info(dir_gfile.get(), gfile_info_query_attribs, + G_FILE_QUERY_INFO_NONE, cancellable().get(), &err), + false + }; + if(!dir_inf) { + ErrorAction act = emitError(err, ErrorSeverity::MODERATE); + if(act == ErrorAction::RETRY) { + err.reset(); + goto _retry; + } + return; + } + + if(g_file_info_get_file_type(dir_inf.get()) != G_FILE_TYPE_DIRECTORY) { + auto path_str = dir_path.toString(); + err = GErrorPtr{ + G_IO_ERROR, + G_IO_ERROR_NOT_DIRECTORY, + tr("The specified directory '%1' is not valid").arg(path_str.get()) + }; + emitError(err, ErrorSeverity::CRITICAL); + return; + } + else { + std::lock_guard lock{mutex_}; + dir_fi = std::make_shared(dir_inf, dir_path.parent()); + } + + FileInfoList foundFiles; + /* check if FS is R/O and set attr. into inf */ + // FIXME: _fm_file_info_job_update_fs_readonly(gf, inf, nullptr, nullptr); + err.reset(); + GFileEnumeratorPtr enu = GFileEnumeratorPtr{ + g_file_enumerate_children(dir_gfile.get(), gfile_info_query_attribs, + G_FILE_QUERY_INFO_NONE, cancellable().get(), &err), + false + }; + if(enu) { + // qDebug() << "START LISTING:" << dir_path.toString().get(); + while(!isCancelled()) { + err.reset(); + GFileInfoPtr inf{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false}; + if(inf) { +#if 0 + FmPath* dir, *sub; + GFile* child; + if(G_UNLIKELY(job->flags & FM_DIR_LIST_JOB_DIR_ONLY)) { + /* FIXME: handle symlinks */ + if(g_file_info_get_file_type(inf) != G_FILE_TYPE_DIRECTORY) { + g_object_unref(inf); + continue; + } + } +#endif + // virtual folders may return children not within them + // For example: the search:/// URI implemented by libfm might return files from different folders during enumeration. + // So here we call g_file_enumerator_get_container() to get the real parent path rather than simply using dir_path. + // This is not the behaviour of gio, but the extensions by libfm might do this. + // FIXME: after we port these vfs implementation from libfm, we can redesign this. + FilePath realParentPath = FilePath{g_file_enumerator_get_container(enu.get()), true}; + if(isFileSearch) { // this is a file sarch job (search:/// URI) + // FIXME: redesign file search and remove this dirty hack + // the libfm implementation of search:/// URI returns a customized GFile implementation that does not behave normally. + // let's get its actual URI and re-create a normal gio GFile instance from it. + realParentPath = FilePath::fromUri(realParentPath.uri().get()); + } +#if 0 + if(g_file_info_get_file_type(inf) == G_FILE_TYPE_DIRECTORY) + /* for dir: check if its FS is R/O and set attr. into inf */ + { + _fm_file_info_job_update_fs_readonly(child, inf, nullptr, nullptr); + } + fi = fm_file_info_new_from_g_file_data(child, inf, sub); +#endif + auto fileInfo = std::make_shared(inf, realParentPath); + if(emit_files_found) { + // Q_EMIT filesFound(); + } + + if(cutFilesHashSet_ + && cutFilesHashSet_->count(fileInfo->path().hash()) > 0) { + fileInfo->bindCutFiles(cutFilesHashSet_); + } + + foundFiles.push_back(std::move(fileInfo)); + } + else { + if(err) { + ErrorAction act = emitError(err, ErrorSeverity::MILD); + /* ErrorAction::RETRY is not supported. */ + if(act == ErrorAction::ABORT) { + cancel(); + } + } + /* otherwise it's EOL */ + break; + } + } + err.reset(); + g_file_enumerator_close(enu.get(), cancellable().get(), &err); + } + else { + emitError(err, ErrorSeverity::CRITICAL); + } + + // qDebug() << "END LISTING:" << dir_path.toString().get(); + if(!foundFiles.empty()) { + std::lock_guard lock{mutex_}; + files_.swap(foundFiles); + } +} + +#if 0 +//FIXME: incremental.. + +static gboolean emit_found_files(gpointer user_data) { + /* this callback is called from the main thread */ + FmDirListJob* job = FM_DIR_LIST_JOB(user_data); + /* g_print("emit_found_files: %d\n", g_slist_length(job->files_to_add)); */ + + if(g_source_is_destroyed(g_main_current_source())) { + return FALSE; + } + g_signal_emit(job, signals[FILES_FOUND], 0, job->files_to_add); + g_slist_free_full(job->files_to_add, (GDestroyNotify)fm_file_info_unref); + job->files_to_add = nullptr; + job->delay_add_files_handler = 0; + return FALSE; +} + +static gpointer queue_add_file(FmJob* fmjob, gpointer user_data) { + FmDirListJob* job = FM_DIR_LIST_JOB(fmjob); + FmFileInfo* file = FM_FILE_INFO(user_data); + /* this callback is called from the main thread */ + /* g_print("queue_add_file: %s\n", fm_file_info_get_disp_name(file)); */ + job->files_to_add = g_slist_prepend(job->files_to_add, fm_file_info_ref(file)); + if(job->delay_add_files_handler == 0) + job->delay_add_files_handler = g_timeout_add_seconds_full(G_PRIORITY_LOW, + 1, emit_found_files, g_object_ref(job), g_object_unref); + return nullptr; +} + +void fm_dir_list_job_add_found_file(FmDirListJob* job, FmFileInfo* file) { + fm_file_info_list_push_tail(job->files, file); + if(G_UNLIKELY(job->emit_files_found)) { + fm_job_call_main_thread(FM_JOB(job), queue_add_file, file); + } +} +#endif + +} // namespace Fm diff --git a/src/core/dirlistjob.h b/src/core/dirlistjob.h new file mode 100644 index 0000000..e578521 --- /dev/null +++ b/src/core/dirlistjob.h @@ -0,0 +1,65 @@ +#ifndef FM2_DIRLISTJOB_H +#define FM2_DIRLISTJOB_H + +#include "../libfmqtglobals.h" +#include +#include "job.h" +#include "filepath.h" +#include "gobjectptr.h" +#include "fileinfo.h" + +namespace Fm { + +class LIBFM_QT_API DirListJob : public Job { + Q_OBJECT +public: + enum Flags { + FAST = 0, + DIR_ONLY = 1 << 0, + DETAILED = 1 << 1 + }; + + explicit DirListJob(const FilePath& path, Flags flags, const std::shared_ptr& cutFilesHashSet = nullptr); + + FileInfoList& files() { + return files_; + } + + void setIncremental(bool set); + + bool incremental() const { + return emit_files_found; + } + + FilePath dirPath() const { + std::lock_guard lock{mutex_}; + return dir_path; + } + + std::shared_ptr dirInfo() const { + std::lock_guard lock{mutex_}; + return dir_fi; + } + +Q_SIGNALS: + void filesFound(FileInfoList& foundFiles); + +protected: + + void exec() override; + +private: + mutable std::mutex mutex_; + FilePath dir_path; + Flags flags; + std::shared_ptr dir_fi; + FileInfoList files_; + const std::shared_ptr cutFilesHashSet_; + bool emit_files_found; + // guint delay_add_files_handler; + // GSList* files_to_add; +}; + +} // namespace Fm + +#endif // FM2_DIRLISTJOB_H diff --git a/src/core/filechangeattrjob.cpp b/src/core/filechangeattrjob.cpp new file mode 100644 index 0000000..5ebf43d --- /dev/null +++ b/src/core/filechangeattrjob.cpp @@ -0,0 +1,9 @@ +#include "filechangeattrjob.h" + +namespace Fm { + +FileChangeAttrJob::FileChangeAttrJob() { + +} + +} // namespace Fm diff --git a/src/core/filechangeattrjob.h b/src/core/filechangeattrjob.h new file mode 100644 index 0000000..2b0aea4 --- /dev/null +++ b/src/core/filechangeattrjob.h @@ -0,0 +1,17 @@ +#ifndef FM2_FILECHANGEATTRJOB_H +#define FM2_FILECHANGEATTRJOB_H + +#include "../libfmqtglobals.h" +#include "fileoperationjob.h" + +namespace Fm { + +class LIBFM_QT_API FileChangeAttrJob : public Fm::FileOperationJob { + Q_OBJECT +public: + explicit FileChangeAttrJob(); +}; + +} // namespace Fm + +#endif // FM2_FILECHANGEATTRJOB_H diff --git a/src/core/fileinfo.cpp b/src/core/fileinfo.cpp new file mode 100644 index 0000000..0258c67 --- /dev/null +++ b/src/core/fileinfo.cpp @@ -0,0 +1,378 @@ +#include "fileinfo.h" +#include "fileinfo_p.h" +#include + +namespace Fm { + +const char gfile_info_query_attribs[] = "standard::*," + "unix::*," + "time::*," + "access::*," + "id::filesystem," + "metadata::emblems"; + +FileInfo::FileInfo() { + // FIXME: initialize numeric data members +} + +FileInfo::FileInfo(const GFileInfoPtr& inf, const FilePath& parentDirPath) { + setFromGFileInfo(inf, parentDirPath); +} + +FileInfo::~FileInfo() { +} + +void FileInfo::setFromGFileInfo(const GObjectPtr& inf, const FilePath& parentDirPath) { + dirPath_ = parentDirPath; + const char* tmp, *uri; + GIcon* gicon; + GFileType type; + + name_ = g_file_info_get_name(inf.get()); + + dispName_ = g_file_info_get_display_name(inf.get()); + + size_ = g_file_info_get_size(inf.get()); + + tmp = g_file_info_get_content_type(inf.get()); + if(!tmp) { + tmp = "application/octet-stream"; + } + mimeType_ = MimeType::fromName(tmp); + + mode_ = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_UNIX_MODE); + + uid_ = gid_ = -1; + if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_UNIX_UID)) { + uid_ = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_UNIX_UID); + } + if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_UNIX_GID)) { + gid_ = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_UNIX_GID); + } + + type = g_file_info_get_file_type(inf.get()); + if(0 == mode_) { /* if UNIX file mode is not available, compose a fake one. */ + switch(type) { + case G_FILE_TYPE_REGULAR: + mode_ |= S_IFREG; + break; + case G_FILE_TYPE_DIRECTORY: + mode_ |= S_IFDIR; + break; + case G_FILE_TYPE_SYMBOLIC_LINK: + mode_ |= S_IFLNK; + break; + case G_FILE_TYPE_SHORTCUT: + break; + case G_FILE_TYPE_MOUNTABLE: + break; + case G_FILE_TYPE_SPECIAL: + if(mode_) { + break; + } + /* if it's a special file but it doesn't have UNIX mode, compose a fake one. */ + if(strcmp(tmp, "inode/chardevice") == 0) { + mode_ |= S_IFCHR; + } + else if(strcmp(tmp, "inode/blockdevice") == 0) { + mode_ |= S_IFBLK; + } + else if(strcmp(tmp, "inode/fifo") == 0) { + mode_ |= S_IFIFO; + } +#ifdef S_IFSOCK + else if(strcmp(tmp, "inode/socket") == 0) { + mode_ |= S_IFSOCK; + } +#endif + break; + case G_FILE_TYPE_UNKNOWN: + ; + } + } + + if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) { + isAccessible_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_READ); + } + else + /* assume it's accessible */ + { + isAccessible_ = true; + } + + if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) { + isWritable_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); + } + else + /* assume it's writable */ + { + isWritable_ = true; + } + + if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE)) { + isDeletable_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE); + } + else + /* assume it's deletable */ + { + isDeletable_ = true; + } + + /* special handling for symlinks */ + if(g_file_info_get_is_symlink(inf.get())) { + mode_ &= ~S_IFMT; /* reset type */ + mode_ |= S_IFLNK; /* set type to symlink */ + goto _file_is_symlink; + } + + isShortcut_ = false; + + switch(type) { + case G_FILE_TYPE_SHORTCUT: + isShortcut_ = true; + case G_FILE_TYPE_MOUNTABLE: + uri = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); + if(uri) { + if(g_str_has_prefix(uri, "file:///")) { + auto filename = CStrPtr{g_filename_from_uri(uri, nullptr, nullptr)}; + target_ = filename.get(); + } + else { + target_ = uri; + } + if(!mimeType_) { + mimeType_ = MimeType::guessFromFileName(target_.c_str()); + } + } + + /* if the mime-type is not determined or is unknown */ + if(G_UNLIKELY(!mimeType_ || mimeType_->isUnknownType())) { + /* FIXME: is this appropriate? */ + if(type == G_FILE_TYPE_SHORTCUT) { + mimeType_ = MimeType::inodeShortcut(); + } + else { + mimeType_ = MimeType::inodeMountPoint(); + } + } + break; + case G_FILE_TYPE_DIRECTORY: + if(!mimeType_) { + mimeType_ = MimeType::inodeDirectory(); + } + isReadOnly_ = false; /* default is R/W */ + if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)) { + isReadOnly_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_READONLY); + } + /* directories should be writable to be deleted by user */ + if(isReadOnly_ || !isWritable_) { + isDeletable_ = false; + } + break; + case G_FILE_TYPE_SYMBOLIC_LINK: +_file_is_symlink: + uri = g_file_info_get_symlink_target(inf.get()); + if(uri) { + if(g_str_has_prefix(uri, "file:///")) { + auto filename = CStrPtr{g_filename_from_uri(uri, nullptr, nullptr)}; + target_ = filename.get(); + } + else { + target_ = uri; + } + if(!mimeType_) { + mimeType_ = MimeType::guessFromFileName(target_.c_str()); + } + } + /* continue with absent mime type */ + default: /* G_FILE_TYPE_UNKNOWN G_FILE_TYPE_REGULAR G_FILE_TYPE_SPECIAL */ + if(G_UNLIKELY(!mimeType_)) { + if(!mimeType_) { + mimeType_ = MimeType::guessFromFileName(name_.c_str()); + } + } + } + + /* if there is a custom folder icon, use it */ + if(isNative() && type == G_FILE_TYPE_DIRECTORY) { + auto local_path = path().localPath(); + auto dot_dir = CStrPtr{g_build_filename(local_path.get(), ".directory", nullptr)}; + if(g_file_test(dot_dir.get(), G_FILE_TEST_IS_REGULAR)) { + GKeyFile* kf = g_key_file_new(); + if(g_key_file_load_from_file(kf, dot_dir.get(), G_KEY_FILE_NONE, nullptr)) { + CStrPtr icon_name{g_key_file_get_string(kf, "Desktop Entry", "Icon", nullptr)}; + if(icon_name) { + auto dot_icon = IconInfo::fromName(icon_name.get()); + if(dot_icon && dot_icon->isValid()) { + icon_ = dot_icon; + } + } + } + g_key_file_free(kf); + } + } + + if(!icon_) { + /* try file-specific icon first */ + gicon = g_file_info_get_icon(inf.get()); + if(gicon) { + icon_ = IconInfo::fromGIcon(gicon); + } + } + +#if 0 + /* set "locked" icon on unaccesible folder */ + else if(!accessible && type == G_FILE_TYPE_DIRECTORY) { + icon = g_object_ref(icon_locked_folder); + } + else { + icon = g_object_ref(fm_mime_type_get_icon(mime_type)); + } +#endif + + /* if the file has emblems, add them to the icon */ + auto emblem_names = g_file_info_get_attribute_stringv(inf.get(), "metadata::emblems"); + if(emblem_names) { + auto n_emblems = g_strv_length(emblem_names); + for(int i = n_emblems - 1; i >= 0; --i) { + emblems_.emplace_front(Fm::IconInfo::fromName(emblem_names[i])); + } + } + + tmp = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_ID_FILESYSTEM); + filesystemId_ = g_intern_string(tmp); + + mtime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_MODIFIED); + atime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_ACCESS); + ctime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_CHANGED); + isHidden_ = g_file_info_get_is_hidden(inf.get()); + isBackup_ = g_file_info_get_is_backup(inf.get()); + isNameChangeable_ = true; /* GVFS tends to ignore this attribute */ + isIconChangeable_ = isHiddenChangeable_ = false; + if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME)) { + isNameChangeable_ = g_file_info_get_attribute_boolean(inf.get(), G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME); + } + + // special handling for desktop entry files (show the name and icon defined in the desktop entry instead) + if(isNative() && G_UNLIKELY(isDesktopEntry())) { + auto local_path = path().localPath(); + GKeyFile* kf = g_key_file_new(); + if(g_key_file_load_from_file(kf, local_path.get(), G_KEY_FILE_NONE, nullptr)) { + /* check if type is correct and supported */ + CStrPtr type{g_key_file_get_string(kf, "Desktop Entry", "Type", nullptr)}; + if(type) { + // Type == "Link" + if(strcmp(type.get(), G_KEY_FILE_DESKTOP_TYPE_LINK) == 0) { + CStrPtr uri{g_key_file_get_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_URL, nullptr)}; + if(uri) { + isShortcut_ = true; + target_ = uri.get(); + } + } + } + CStrPtr icon_name{g_key_file_get_string(kf, "Desktop Entry", "Icon", nullptr)}; + if(icon_name) { + icon_ = IconInfo::fromName(icon_name.get()); + } + /* Use title of the desktop entry for display */ + CStrPtr displayName{g_key_file_get_locale_string(kf, "Desktop Entry", "Name", nullptr, nullptr)}; + if(displayName) { + dispName_ = displayName.get(); + } + /* handle 'Hidden' key to set hidden attribute */ + if(!isHidden_) { + isHidden_ = g_key_file_get_boolean(kf, "Desktop Entry", "Hidden", nullptr); + } + } + g_key_file_free(kf); + } + + if(!icon_ && mimeType_) + icon_ = mimeType_->icon(); + +#if 0 + GFile* _gf = nullptr; + GFileAttributeInfoList* list; + auto list = g_file_query_settable_attributes(gf, nullptr, nullptr); + if(G_LIKELY(list)) { + if(g_file_attribute_info_list_lookup(list, G_FILE_ATTRIBUTE_STANDARD_ICON)) { + icon_is_changeable = true; + } + if(g_file_attribute_info_list_lookup(list, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN)) { + hidden_is_changeable = true; + } + g_file_attribute_info_list_unref(list); + } + if(G_UNLIKELY(_gf)) { + g_object_unref(_gf); + } +#endif +} + +void FileInfo::bindCutFiles(const std::shared_ptr& cutFilesHashSet) { + cutFilesHashSet_ = cutFilesHashSet; +} + +bool FileInfo::canThumbnail() const { + /* We cannot use S_ISREG here as this exclude all symlinks */ + if(size_ == 0 || /* don't generate thumbnails for empty files */ + !(mode_ & S_IFREG) || + isDesktopEntry() || + isUnknownType()) { + return false; + } + return true; +} + +/* full path of the file is required by this function */ +bool FileInfo::isExecutableType() const { + if(isText()) { /* g_content_type_can_be_executable reports text files as executables too */ + /* We don't execute remote files nor files in trash */ + if(isNative() && (mode_ & (S_IXOTH | S_IXGRP | S_IXUSR))) { + /* it has executable bits so lets check shell-bang */ + auto pathStr = path().toString(); + int fd = open(pathStr.get(), O_RDONLY); + if(fd >= 0) { + char buf[2]; + ssize_t rdlen = read(fd, &buf, 2); + close(fd); + if(rdlen == 2 && buf[0] == '#' && buf[1] == '!') { + return true; + } + } + } + return false; + } + return mimeType_->canBeExecutable(); +} + + +bool FileInfoList::isSameType() const { + if(!empty()) { + auto& item = front(); + for(auto it = cbegin() + 1; it != cend(); ++it) { + auto& item2 = *it; + if(item->mimeType() != item2->mimeType()) { + return false; + } + } + } + return true; +} + +bool FileInfoList::isSameFilesystem() const { + if(!empty()) { + auto& item = front(); + for(auto it = cbegin() + 1; it != cend(); ++it) { + auto& item2 = *it; + if(item->filesystemId() != item2->filesystemId()) { + return false; + } + } + } + return true; +} + + + +} // namespace Fm diff --git a/src/core/fileinfo.h b/src/core/fileinfo.h new file mode 100644 index 0000000..1208e1e --- /dev/null +++ b/src/core/fileinfo.h @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2016 Hong Jen Yee (PCMan) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __LIBFM_QT_FM2_FILE_INFO_H__ +#define __LIBFM_QT_FM2_FILE_INFO_H__ + +#include +#include +#include +#include "../libfmqtglobals.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gioptrs.h" +#include "filepath.h" +#include "iconinfo.h" +#include "mimetype.h" + + +namespace Fm { + +class FileInfoList; +typedef std::set HashSet; + +class LIBFM_QT_API FileInfo { +public: + + explicit FileInfo(); + + explicit FileInfo(const GFileInfoPtr& inf, const FilePath& parentDirPath); + + virtual ~FileInfo(); + + bool canSetHidden() const { + return isHiddenChangeable_; + } + + bool canSetIcon() const { + return isIconChangeable_; + } + + bool canSetName() const { + return isNameChangeable_; + } + + bool canThumbnail() const; + + gid_t gid() const { + return gid_; + } + + uid_t uid() const { + return uid_; + } + + const char* filesystemId() const { + return filesystemId_; + } + + const std::shared_ptr& icon() const { + return icon_; + } + + const std::shared_ptr& mimeType() const { + return mimeType_; + } + + time_t ctime() const { + return ctime_; + } + + + time_t atime() const { + return atime_; + } + + time_t mtime() const { + return mtime_; + } + + const std::string& target() const { + return target_; + } + + bool isWritableDirectory() const { + return (!isReadOnly_ && isDir()); + } + + bool isAccessible() const { + return isAccessible_; + } + + bool isWritable() const { + return isWritable_; + } + + bool isDeletable() const { + return isDeletable_; + } + + bool isExecutableType() const; + + bool isBackup() const { + return isBackup_; + } + + bool isHidden() const { + // FIXME: we might treat backup files as hidden + return isHidden_; + } + + bool isUnknownType() const { + return mimeType_->isUnknownType(); + } + + bool isDesktopEntry() const { + return mimeType_->isDesktopEntry(); + } + + bool isText() const { + return mimeType_->isText(); + } + + bool isImage() const { + return mimeType_->isImage(); + } + + bool isMountable() const { + return mimeType_->isMountable(); + } + + bool isShortcut() const { + return isShortcut_; + } + + bool isSymlink() const { + return S_ISLNK(mode_) ? true : false; + } + + bool isDir() const { + return mimeType_->isDir(); + } + + bool isNative() const { + return dirPath_ ? dirPath_.isNative() : path().isNative(); + } + + bool isCut() const { + return !cutFilesHashSet_.expired(); + } + + mode_t mode() const { + return mode_; + } + + uint64_t realSize() const { + return blksize_ *blocks_; + } + + uint64_t size() const { + return size_; + } + + const std::string& name() const { + return name_; + } + + const QString& displayName() const { + return dispName_; + } + + FilePath path() const { + return dirPath_ ? dirPath_.child(name_.c_str()) : FilePath::fromPathStr(name_.c_str()); + } + + const FilePath& dirPath() const { + return dirPath_; + } + + void setFromGFileInfo(const GFileInfoPtr& inf, const FilePath& parentDirPath); + + void bindCutFiles(const std::shared_ptr& cutFilesHashSet); + + const std::forward_list>& emblems() const { + return emblems_; + } + +private: + std::string name_; + QString dispName_; + + FilePath dirPath_; + + mode_t mode_; + const char* filesystemId_; + uid_t uid_; + gid_t gid_; + uint64_t size_; + time_t mtime_; + time_t atime_; + time_t ctime_; + + uint64_t blksize_; + uint64_t blocks_; + + std::shared_ptr mimeType_; + std::shared_ptr icon_; + std::forward_list> emblems_; + + std::string target_; /* target of shortcut or mountable. */ + + bool isShortcut_ : 1; /* TRUE if file is shortcut type */ + bool isAccessible_ : 1; /* TRUE if can be read by user */ + bool isWritable_ : 1; /* TRUE if can be written to by user */ + bool isDeletable_ : 1; /* TRUE if can be deleted by user */ + bool isHidden_ : 1; /* TRUE if file is hidden */ + bool isBackup_ : 1; /* TRUE if file is backup */ + bool isNameChangeable_ : 1; /* TRUE if name can be changed */ + bool isIconChangeable_ : 1; /* TRUE if icon can be changed */ + bool isHiddenChangeable_ : 1; /* TRUE if hidden can be changed */ + bool isReadOnly_ : 1; /* TRUE if host FS is R/O */ + + std::weak_ptr cutFilesHashSet_; + // std::vector> extraData_; +}; + + +class LIBFM_QT_API FileInfoList: public std::vector> { +public: + + bool isSameType() const; + + bool isSameFilesystem() const; + + FilePathList paths() const { + FilePathList ret; + for(auto& file: *this) { + ret.push_back(file->path()); + } + return ret; + } +}; + + +typedef std::pair, std::shared_ptr> FileInfoPair; + + +} + +Q_DECLARE_METATYPE(std::shared_ptr) + + +#endif // __LIBFM_QT_FM2_FILE_INFO_H__ diff --git a/src/core/fileinfo_p.h b/src/core/fileinfo_p.h new file mode 100644 index 0000000..37553fa --- /dev/null +++ b/src/core/fileinfo_p.h @@ -0,0 +1,10 @@ +#ifndef FILEINFO_P_H +#define FILEINFO_P_H + +namespace Fm { + + extern const char gfile_info_query_attribs[]; + +} // namespace Fm + +#endif // FILEINFO_P_H diff --git a/src/core/fileinfojob.cpp b/src/core/fileinfojob.cpp new file mode 100644 index 0000000..75142a4 --- /dev/null +++ b/src/core/fileinfojob.cpp @@ -0,0 +1,42 @@ +#include "fileinfojob.h" +#include "fileinfo_p.h" + +namespace Fm { + +FileInfoJob::FileInfoJob(FilePathList paths, FilePath commonDirPath, const std::shared_ptr& cutFilesHashSet): + Job(), + paths_{std::move(paths)}, + commonDirPath_{std::move(commonDirPath)}, + cutFilesHashSet_{cutFilesHashSet} { +} + +void FileInfoJob::exec() { + for(const auto& path: paths_) { + if(!isCancelled()) { + GErrorPtr err; + GFileInfoPtr inf{ + g_file_query_info(path.gfile().get(), gfile_info_query_attribs, + G_FILE_QUERY_INFO_NONE, cancellable().get(), &err), + false + }; + if(!inf) + return; + + // Reuse the same dirPath object when the path remains the same (optimize for files in the same dir) + auto dirPath = commonDirPath_.isValid() ? commonDirPath_ : path.parent(); + FileInfo fileInfo(inf, dirPath); + + if(cutFilesHashSet_ + && cutFilesHashSet_->count(fileInfo.path().hash())) { + fileInfo.bindCutFiles(cutFilesHashSet_); + } + + auto fileInfoPtr = std::make_shared(fileInfo); + + results_.push_back(fileInfoPtr); + Q_EMIT gotInfo(path, fileInfoPtr); + } + } +} + +} // namespace Fm diff --git a/src/core/fileinfojob.h b/src/core/fileinfojob.h new file mode 100644 index 0000000..ba59eb7 --- /dev/null +++ b/src/core/fileinfojob.h @@ -0,0 +1,41 @@ +#ifndef FM2_FILEINFOJOB_H +#define FM2_FILEINFOJOB_H + +#include "../libfmqtglobals.h" +#include "job.h" +#include "filepath.h" +#include "fileinfo.h" + +namespace Fm { + + +class LIBFM_QT_API FileInfoJob : public Job { + Q_OBJECT +public: + + explicit FileInfoJob(FilePathList paths, FilePath commonDirPath = FilePath(), const std::shared_ptr& cutFilesHashSet = nullptr); + + const FilePathList& paths() const { + return paths_; + } + + const FileInfoList& files() const { + return results_; + } + +Q_SIGNALS: + void gotInfo(const FilePath& path, std::shared_ptr& info); + +protected: + void exec() override; + +private: + FilePathList paths_; + FileInfoList results_; + FilePath commonDirPath_; + const std::shared_ptr cutFilesHashSet_; +}; + +} // namespace Fm + +#endif // FM2_FILEINFOJOB_H diff --git a/src/core/filelinkjob.cpp b/src/core/filelinkjob.cpp new file mode 100644 index 0000000..253768e --- /dev/null +++ b/src/core/filelinkjob.cpp @@ -0,0 +1,9 @@ +#include "filelinkjob.h" + +namespace Fm { + +FileLinkJob::FileLinkJob() { + +} + +} // namespace Fm diff --git a/src/core/filelinkjob.h b/src/core/filelinkjob.h new file mode 100644 index 0000000..965d089 --- /dev/null +++ b/src/core/filelinkjob.h @@ -0,0 +1,16 @@ +#ifndef FM2_FILELINKJOB_H +#define FM2_FILELINKJOB_H + +#include "../libfmqtglobals.h" +#include "fileoperationjob.h" + +namespace Fm { + +class LIBFM_QT_API FileLinkJob : public Fm::FileOperationJob { +public: + explicit FileLinkJob(); +}; + +} // namespace Fm + +#endif // FM2_FILELINKJOB_H diff --git a/src/core/filemonitor.cpp b/src/core/filemonitor.cpp new file mode 100644 index 0000000..68c6b6b --- /dev/null +++ b/src/core/filemonitor.cpp @@ -0,0 +1,9 @@ +#include "filemonitor.h" + +namespace Fm { + +FileMonitor::FileMonitor() { + +} + +} // namespace Fm diff --git a/src/core/filemonitor.h b/src/core/filemonitor.h new file mode 100644 index 0000000..9a1485d --- /dev/null +++ b/src/core/filemonitor.h @@ -0,0 +1,26 @@ +#ifndef FM2_FILEMONITOR_H +#define FM2_FILEMONITOR_H + +#include "../libfmqtglobals.h" +#include +#include "gioptrs.h" +#include "filepath.h" + +namespace Fm { + +class LIBFM_QT_API FileMonitor: public QObject { + Q_OBJECT +public: + + explicit FileMonitor(); + +Q_SIGNALS: + + +private: + GFileMonitorPtr monitor_; +}; + +} // namespace Fm + +#endif // FM2_FILEMONITOR_H diff --git a/src/core/fileoperationjob.cpp b/src/core/fileoperationjob.cpp new file mode 100644 index 0000000..346da42 --- /dev/null +++ b/src/core/fileoperationjob.cpp @@ -0,0 +1,79 @@ +#include "fileoperationjob.h" + +namespace Fm { + +FileOperationJob::FileOperationJob(): + hasTotalAmount_{false}, + totalSize_{0}, + totalCount_{0}, + finishedSize_{0}, + finishedCount_{0}, + currentFileSize_{0}, + currentFileFinished_{0} { +} + +bool FileOperationJob::totalAmount(uint64_t& fileSize, uint64_t& fileCount) const { + std::lock_guard lock{mutex_}; + if(hasTotalAmount_) { + fileSize = totalSize_; + fileCount = totalCount_; + } + return hasTotalAmount_; +} + +bool FileOperationJob::currentFileProgress(FilePath& path, uint64_t& totalSize, uint64_t& finishedSize) const { + std::lock_guard lock{mutex_}; + if(currentFile_.isValid()) { + path = currentFile_; + totalSize = currentFileSize_; + finishedSize = currentFileFinished_; + } + return currentFile_.isValid(); +} + +FileOperationJob::FileExistsAction FileOperationJob::askRename(const FileInfo &src, const FileInfo &dest, FilePath &newDest) { + FileExistsAction action = SKIP; + Q_EMIT fileExists(src, dest, action, newDest); + return action; +} + +bool FileOperationJob::finishedAmount(uint64_t& finishedSize, uint64_t& finishedCount) const { + std::lock_guard lock{mutex_}; + if(hasTotalAmount_) { + finishedSize = finishedSize_; + finishedCount = finishedCount_; + } + return hasTotalAmount_; +} + +void FileOperationJob::setTotalAmount(uint64_t fileSize, uint64_t fileCount) { + std::lock_guard locl{mutex_}; + hasTotalAmount_ = true; + totalSize_ = fileSize; + totalCount_ = fileCount; +} + +void FileOperationJob::setFinishedAmount(uint64_t finishedSize, uint64_t finishedCount) { + std::lock_guard locl{mutex_}; + finishedSize_ = finishedSize; + finishedCount_ = finishedCount; +} + +void FileOperationJob::addFinishedAmount(uint64_t finishedSize, uint64_t finishedCount) { + std::lock_guard locl{mutex_}; + finishedSize_ += finishedSize; + finishedCount_ += finishedCount; +} + +void FileOperationJob::setCurrentFile(const FilePath& path) { + std::lock_guard locl{mutex_}; + currentFile_ = path; +} + +void FileOperationJob::setCurrentFileProgress(uint64_t totalSize, uint64_t finishedSize) { + std::lock_guard locl{mutex_}; + currentFileSize_ = totalSize; + currentFileFinished_ = finishedSize; +} + +} // namespace Fm diff --git a/src/core/fileoperationjob.h b/src/core/fileoperationjob.h new file mode 100644 index 0000000..c9ce569 --- /dev/null +++ b/src/core/fileoperationjob.h @@ -0,0 +1,73 @@ +#ifndef FM2_FILEOPERATIONJOB_H +#define FM2_FILEOPERATIONJOB_H + +#include "../libfmqtglobals.h" +#include "job.h" +#include +#include +#include +#include "fileinfo.h" +#include "filepath.h" + +namespace Fm { + +class LIBFM_QT_API FileOperationJob : public Fm::Job { + Q_OBJECT +public: + enum FileExistsAction { + CANCEL = 0, + OVERWRITE = 1<<0, + RENAME = 1<<1, + SKIP = 1<<2, + SKIP_ERROR = 1<<3 + }; + + explicit FileOperationJob(); + + bool totalAmount(std::uint64_t& fileSize, std::uint64_t& fileCount) const; + + bool finishedAmount(std::uint64_t& finishedSize, std::uint64_t& finishedCount) const; + + bool currentFileProgress(FilePath& path, std::uint64_t& totalSize, std::uint64_t& finishedSize) const; + +Q_SIGNALS: + + void preparedToRun(); + + // void currentFile(const char* file); + + // void progress(uint32_t percent); + + // to correctly handle the signal, connect with Qt::BlockingQueuedConnection so it's a sync call. + void fileExists(const FileInfo& src, const FileInfo& dest, FileExistsAction& response, FilePath& newDest); + +protected: + + FileExistsAction askRename(const FileInfo& src, const FileInfo& dest, FilePath& newDest); + + void setTotalAmount(std::uint64_t fileSize, std::uint64_t fileCount); + + void setFinishedAmount(std::uint64_t finishedSize, std::uint64_t finishedCount); + + void addFinishedAmount(std::uint64_t finishedSize, std::uint64_t finishedCount); + + void setCurrentFile(const FilePath &path); + + void setCurrentFileProgress(uint64_t totalSize, uint64_t finishedSize); + +private: + bool hasTotalAmount_; + std::uint64_t totalSize_; + std::uint64_t totalCount_; + std::uint64_t finishedSize_; + std::uint64_t finishedCount_; + + FilePath currentFile_; + std::uint64_t currentFileSize_; + std::uint64_t currentFileFinished_; + mutable std::mutex mutex_; +}; + +} // namespace Fm + +#endif // FM2_FILEOPERATIONJOB_H diff --git a/src/core/filepath.cpp b/src/core/filepath.cpp new file mode 100644 index 0000000..36a7910 --- /dev/null +++ b/src/core/filepath.cpp @@ -0,0 +1,21 @@ +#include "filepath.h" +#include +#include +#include + +namespace Fm { + +FilePath FilePath::homeDir_; + +const FilePath &FilePath::homeDir() { + if(!homeDir_) { + const char* home = getenv("HOME"); + if(!home) { + home = g_get_home_dir(); + } + homeDir_ = FilePath::fromLocalPath(home); + } + return homeDir_; +} + +} // namespace Fm diff --git a/src/core/filepath.h b/src/core/filepath.h new file mode 100644 index 0000000..d33658b --- /dev/null +++ b/src/core/filepath.h @@ -0,0 +1,177 @@ +#ifndef FM2_FILEPATH_H +#define FM2_FILEPATH_H + +#include "../libfmqtglobals.h" +#include "gobjectptr.h" +#include "cstrptr.h" +#include +#include +#include + +namespace Fm { + +class LIBFM_QT_API FilePath { +public: + + explicit FilePath() { + } + + explicit FilePath(GFile* gfile, bool add_ref): gfile_{gfile, add_ref} { + } + + FilePath(const FilePath& other): FilePath{} { + *this = other; + } + + FilePath(FilePath&& other) noexcept: FilePath{} { + *this = other; + } + + static FilePath fromUri(const char* uri) { + return FilePath{g_file_new_for_uri(uri), false}; + } + + static FilePath fromLocalPath(const char* path) { + return FilePath{g_file_new_for_path(path), false}; + } + + static FilePath fromDisplayName(const char* path) { + return FilePath{g_file_parse_name(path), false}; + } + + static FilePath fromPathStr(const char* path_str) { + return FilePath{g_file_new_for_commandline_arg(path_str), false}; + } + + bool isValid() const { + return gfile_ != nullptr; + } + + unsigned int hash() const { + return g_file_hash(gfile_.get()); + } + + CStrPtr baseName() const { + return CStrPtr{g_file_get_basename(gfile_.get())}; + } + + CStrPtr localPath() const { + return CStrPtr{g_file_get_path(gfile_.get())}; + } + + CStrPtr uri() const { + return CStrPtr{g_file_get_uri(gfile_.get())}; + } + + CStrPtr toString() const { + if(isNative()) { + return localPath(); + } + return uri(); + } + + // a human readable UTF-8 display name for the path + CStrPtr displayName() const { + return CStrPtr{g_file_get_parse_name(gfile_.get())}; + } + + FilePath parent() const { + return FilePath{g_file_get_parent(gfile_.get()), false}; + } + + bool hasParent() const { + return g_file_has_parent(gfile_.get(), nullptr); + } + + bool isParentOf(const FilePath& other) { + return g_file_has_parent(other.gfile_.get(), gfile_.get()); + } + + bool isPrefixOf(const FilePath& other) { + return g_file_has_prefix(other.gfile_.get(), gfile_.get()); + } + + FilePath child(const char* name) const { + return FilePath{g_file_get_child(gfile_.get(), name), false}; + } + + CStrPtr relativePathStr(const FilePath& descendant) const { + return CStrPtr{g_file_get_relative_path(gfile_.get(), descendant.gfile_.get())}; + } + + FilePath relativePath(const char* relPath) const { + return FilePath{g_file_resolve_relative_path(gfile_.get(), relPath), false}; + } + + bool isNative() const { + return g_file_is_native(gfile_.get()); + } + + bool hasUriScheme(const char* scheme) const { + return g_file_has_uri_scheme(gfile_.get(), scheme); + } + + CStrPtr uriScheme() const { + return CStrPtr{g_file_get_uri_scheme(gfile_.get())}; + } + + const GObjectPtr& gfile() const { + return gfile_; + } + + FilePath& operator = (const FilePath& other) { + gfile_ = other.gfile_; + return *this; + } + + FilePath& operator = (const FilePath&& other) noexcept { + gfile_ = std::move(other.gfile_); + return *this; + } + + bool operator == (const FilePath& other) const { + return operator==(other.gfile_.get()); + } + + bool operator == (GFile* other_gfile) const { + if(gfile_ == other_gfile) { + return true; + } + if(gfile_ && other_gfile) { + return g_file_equal(gfile_.get(), other_gfile); + } + return false; + } + + bool operator != (const FilePath& other) const { + return !operator==(other); + } + + bool operator != (std::nullptr_t) const { + return gfile_ != nullptr; + } + + operator bool() const { + return gfile_ != nullptr; + } + + static const FilePath& homeDir(); + +private: + GObjectPtr gfile_; + static FilePath homeDir_; +}; + +struct FilePathHash { + std::size_t operator() (const FilePath& path) const { + return path.hash(); + } +}; + +typedef std::vector FilePathList; + +} // namespace Fm + +Q_DECLARE_METATYPE(Fm::FilePath) + +#endif // FM2_FILEPATH_H diff --git a/src/core/filesysteminfojob.cpp b/src/core/filesysteminfojob.cpp new file mode 100644 index 0000000..6c3aa69 --- /dev/null +++ b/src/core/filesysteminfojob.cpp @@ -0,0 +1,24 @@ +#include "filesysteminfojob.h" +#include "gobjectptr.h" + +namespace Fm { + +void FileSystemInfoJob::exec() { + GObjectPtr inf = GObjectPtr{ + g_file_query_filesystem_info( + path_.gfile().get(), + G_FILE_ATTRIBUTE_FILESYSTEM_SIZE"," + G_FILE_ATTRIBUTE_FILESYSTEM_FREE, + cancellable().get(), nullptr), + false + }; + if(!inf) + return; + if(g_file_info_has_attribute(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_SIZE)) { + size_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); + freeSize_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_FILESYSTEM_FREE); + isAvailable_ = true; + } +} + +} // namespace Fm diff --git a/src/core/filesysteminfojob.h b/src/core/filesysteminfojob.h new file mode 100644 index 0000000..59c963a --- /dev/null +++ b/src/core/filesysteminfojob.h @@ -0,0 +1,45 @@ +#ifndef FM2_FILESYSTEMINFOJOB_H +#define FM2_FILESYSTEMINFOJOB_H + +#include "../libfmqtglobals.h" +#include "job.h" +#include "filepath.h" + +namespace Fm { + +class LIBFM_QT_API FileSystemInfoJob : public Job { + Q_OBJECT +public: + explicit FileSystemInfoJob(const FilePath& path): + path_{path}, + isAvailable_{false}, + size_{0}, + freeSize_{0} { + } + + bool isAvailable() const { + return isAvailable_; + } + + uint64_t size() const { + return size_; + } + + uint64_t freeSize() const { + return freeSize_; + } + +protected: + + void exec() override; + +private: + FilePath path_; + bool isAvailable_; + uint64_t size_; + uint64_t freeSize_; +}; + +} // namespace Fm + +#endif // FM2_FILESYSTEMINFOJOB_H diff --git a/src/core/folder.cpp b/src/core/folder.cpp new file mode 100644 index 0000000..d91600a --- /dev/null +++ b/src/core/folder.cpp @@ -0,0 +1,908 @@ +/* + * fm-folder.c + * + * Copyright 2009 - 2012 Hong Jen Yee (PCMan) + * Copyright 2012-2016 Andriy Grytsenko (LStranger) + * + * This file is a part of the Libfm library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "folder.h" +#include +#include +#include +#include + +#include "dirlistjob.h" +#include "filesysteminfojob.h" +#include "fileinfojob.h" + +namespace Fm { + +std::unordered_map, FilePathHash> Folder::cache_; +FilePath Folder::cutFilesDirPath_; +FilePath Folder::lastCutFilesDirPath_; +std::shared_ptr Folder::cutFilesHashSet_; +std::mutex Folder::mutex_; + +Folder::Folder(): + dirlist_job{nullptr}, + fsInfoJob_{nullptr}, + volumeManager_{VolumeManager::globalInstance()}, + /* for file monitor */ + has_idle_reload_handler{0}, + has_idle_update_handler{false}, + pending_change_notify{false}, + filesystem_info_pending{false}, + wants_incremental{false}, + stop_emission{false}, /* don't set it 1 bit to not lock other bits */ + /* filesystem info - set in query thread, read in main */ + fs_total_size{0}, + fs_free_size{0}, + has_fs_info{false}, + defer_content_test{false} { + + connect(volumeManager_.get(), &VolumeManager::mountAdded, this, &Folder::onMountAdded); + connect(volumeManager_.get(), &VolumeManager::mountRemoved, this, &Folder::onMountRemoved); +} + +Folder::Folder(const FilePath& path): Folder() { + dirPath_ = path; +} + +Folder::~Folder() { + if(dirMonitor_) { + g_signal_handlers_disconnect_by_data(dirMonitor_.get(), this); + dirMonitor_.reset(); + } + + if(dirlist_job) { + dirlist_job->cancel(); + } + + // cancel any file info job in progress. + for(auto job: fileinfoJobs_) { + job->cancel(); + } + + if(fsInfoJob_) { + fsInfoJob_->cancel(); + } + + // We store a weak_ptr instead of shared_ptr in the hash table, so the hash table + // does not own a reference to the folder. When the last reference to Folder is + // freed, we need to remove its hash table entry. + std::lock_guard lock{mutex_}; + auto it = cache_.find(dirPath_); + if(it != cache_.end()) { + cache_.erase(it); + } +} + +// static +std::shared_ptr Folder::fromPath(const FilePath& path) { + std::lock_guard lock{mutex_}; + auto it = cache_.find(path); + if(it != cache_.end()) { + auto folder = it->second.lock(); + if(folder) { + return folder; + } + else { // FIXME: is this possible? + cache_.erase(it); + } + } + auto folder = std::make_shared(path); + folder->reload(); + cache_.emplace(path, folder); + return folder; +} + +bool Folder::makeDirectory(const char* /*name*/, GError** /*error*/) { + // TODO: + // FIXME: what the API is used for in the original libfm C API? + return false; +} + +bool Folder::isIncremental() const { + return wants_incremental; +} + +bool Folder::isValid() const { + return dirInfo_ != nullptr; +} + +bool Folder::isLoaded() const { + return (dirlist_job == nullptr); +} + +std::shared_ptr Folder::fileByName(const char* name) const { + auto it = files_.find(name); + if(it != files_.end()) { + return it->second; + } + return nullptr; +} + +bool Folder::isEmpty() const { + return files_.empty(); +} + +FileInfoList Folder::files() const { + FileInfoList ret; + ret.reserve(files_.size()); + for(const auto& item : files_) { + ret.push_back(item.second); + } + return ret; +} + + +const FilePath& Folder::path() const { + auto pathStr = dirPath_.toString(); + // qDebug() << this << "FOLDER_PATH:" << pathStr.get() << dirPath_.gfile().get(); + //assert(!g_str_has_prefix(pathStr.get(), "file:")); + return dirPath_; +} + +const std::shared_ptr& Folder::info() const { + return dirInfo_; +} + +#if 0 +void Folder::init(FmFolder* folder) { + files = fm_file_info_list_new(); + G_LOCK(hash); + if(G_UNLIKELY(hash_uses == 0)) { + hash = g_hash_table_new((GHashFunc)fm_path_hash, (GEqualFunc)fm_path_equal); + volume_monitor = g_volume_monitor_get(); + if(G_LIKELY(volume_monitor)) { + g_signal_connect(volume_monitor, "mount-added", G_CALLBACK(on_mount_added), nullptr); + g_signal_connect(volume_monitor, "mount-removed", G_CALLBACK(on_mount_removed), nullptr); + } + } + hash_uses++; + G_UNLOCK(hash); +} +#endif + +void Folder::onIdleReload() { + /* check if folder still exists */ + reload(); + // G_LOCK(query); + has_idle_reload_handler = false; + // G_UNLOCK(query); +} + +void Folder::queueReload() { + // G_LOCK(query); + if(!has_idle_reload_handler) { + has_idle_reload_handler = true; + QTimer::singleShot(0, this, &Folder::onIdleReload); + } + // G_UNLOCK(query); +} + +void Folder::onFileInfoFinished() { + FileInfoJob* job = static_cast(sender()); + fileinfoJobs_.erase(std::find(fileinfoJobs_.cbegin(), fileinfoJobs_.cend(), job)); + + if(job->isCancelled()) + return; + + FileInfoList files_to_add; + std::vector files_to_update; + + const auto& paths = job->paths(); + const auto& infos = job->files(); + auto path_it = paths.cbegin(); + auto info_it = infos.cbegin(); + for(; path_it != paths.cend() && info_it != infos.cend(); ++path_it, ++info_it) { + const auto& path = *path_it; + const auto& info = *info_it; + + if(path == dirPath_) { // got the info for the folder itself. + dirInfo_ = info; + } + else { + auto it = files_.find(info->name()); + if(it != files_.end()) { // the file already exists, update + files_to_update.push_back(std::make_pair(it->second, info)); + } + else { // newly added + files_to_add.push_back(info); + } + files_[info->name()] = info; + } + } + if(!files_to_add.empty()) { + Q_EMIT filesAdded(files_to_add); + } + if(!files_to_update.empty()) { + Q_EMIT filesChanged(files_to_update); + } + Q_EMIT contentChanged(); +} + +void Folder::processPendingChanges() { + has_idle_update_handler = false; + // FmFileInfoJob* job = nullptr; + std::lock_guard lock{mutex_}; + + // idle_handler = 0; + /* if we were asked to block updates let delay it for now */ + if(stop_emission) { + return; + } + + FileInfoJob* info_job = nullptr; + if(!paths_to_update.empty() || !paths_to_add.empty()) { + FilePathList paths; + paths.insert(paths.end(), paths_to_add.cbegin(), paths_to_add.cend()); + paths.insert(paths.end(), paths_to_update.cbegin(), paths_to_update.cend()); + info_job = new FileInfoJob{paths, dirPath_, + hasCutFiles() ? cutFilesHashSet_ : nullptr}; + paths_to_update.clear(); + paths_to_add.clear(); + } + + if(info_job) { + fileinfoJobs_.push_back(info_job); + info_job->setAutoDelete(true); + connect(info_job, &FileInfoJob::finished, this, &Folder::onFileInfoFinished, Qt::BlockingQueuedConnection); + info_job->runAsync(); +#if 0 + pending_jobs = g_slist_prepend(pending_jobs, job); + if(!fm_job_run_async(FM_JOB(job))) { + pending_jobs = g_slist_remove(pending_jobs, job); + g_object_unref(job); + g_critical("failed to start folder update job"); + } +#endif + } + + if(!paths_to_del.empty()) { + FileInfoList deleted_files; + for(const auto &path: paths_to_del) { + auto name = path.baseName(); + auto it = files_.find(name.get()); + if(it != files_.end()) { + deleted_files.push_back(it->second); + files_.erase(it); + } + } + Q_EMIT filesRemoved(deleted_files); + Q_EMIT contentChanged(); + paths_to_del.clear(); + } + + if(pending_change_notify) { + Q_EMIT changed(); + /* update volume info */ + queryFilesystemInfo(); + pending_change_notify = false; + } + + if(filesystem_info_pending) { + Q_EMIT fileSystemChanged(); + filesystem_info_pending = false; + } +} + +/* should be called only with G_LOCK(lists) on! */ +void Folder::queueUpdate() { + // qDebug() << "queue_update:" << !has_idle_handler << paths_to_add.size() << paths_to_update.size() << paths_to_del.size(); + if(!has_idle_update_handler) { + QTimer::singleShot(0, this, &Folder::processPendingChanges); + has_idle_update_handler = true; + } +} + + +/* returns true if reference was taken from path */ +bool Folder::eventFileAdded(const FilePath &path) { + bool added = true; + // G_LOCK(lists); + /* make sure that the file is not already queued for addition. */ + if(std::find(paths_to_add.cbegin(), paths_to_add.cend(), path) == paths_to_add.cend()) { + if(files_.find(path.baseName().get()) != files_.end()) { // the file already exists, update instead + if(std::find(paths_to_update.cbegin(), paths_to_update.cend(), path) == paths_to_update.cend()) { + paths_to_update.push_back(path); + } + } + else { // newly added file + paths_to_add.push_back(path); + } + /* bug #3591771: 'ln -fns . test' leave no file visible in folder. + If it is queued for deletion then cancel that operation */ + paths_to_del.erase(std::remove(paths_to_del.begin(), paths_to_del.end(), path), paths_to_del.cend()); + } + else + /* file already queued for adding, don't duplicate */ + { + added = false; + } + if(added) { + queueUpdate(); + } + // G_UNLOCK(lists); + return added; +} + +bool Folder::eventFileChanged(const FilePath &path) { + bool added; + // G_LOCK(lists); + /* make sure that the file is not already queued for changes or + * it's already queued for addition. */ + if(std::find(paths_to_update.cbegin(), paths_to_update.cend(), path) == paths_to_update.cend() + && std::find(paths_to_add.cbegin(), paths_to_add.cend(), path) == paths_to_add.cend()) { + /* Since this function is called only when a file already exists, even if that file + isn't included in "files_" yet, it will be soon due to a previous call to queueUpdate(). + So, here, we should queue it for changes regardless of what "files_" may contain. */ + paths_to_update.push_back(path); + added = true; + queueUpdate(); + } + else { + added = false; + } + // G_UNLOCK(lists); + return added; +} + +void Folder::eventFileDeleted(const FilePath& path) { + // qDebug() << "delete " << path.baseName().get(); + // G_LOCK(lists); + if(files_.find(path.baseName().get()) != files_.cend()) { + if(std::find(paths_to_del.cbegin(), paths_to_del.cend(), path) == paths_to_del.cend()) { + paths_to_del.push_back(path); + } + } + /* if the file is already queued for addition or update, that operation + will be just a waste, therefore cancel it right now */ + paths_to_add.erase(std::remove(paths_to_add.begin(), paths_to_add.end(), path), paths_to_add.cend()); + paths_to_update.erase(std::remove(paths_to_update.begin(), paths_to_update.end(), path), paths_to_update.cend()); + queueUpdate(); + // G_UNLOCK(lists); +} + + +void Folder::onDirChanged(GFileMonitorEvent evt) { + switch(evt) { + case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: + /* g_debug("folder is going to be unmounted"); */ + break; + case G_FILE_MONITOR_EVENT_UNMOUNTED: + Q_EMIT unmount(); + /* g_debug("folder is unmounted"); */ + queueReload(); + break; + case G_FILE_MONITOR_EVENT_DELETED: + Q_EMIT removed(); + /* g_debug("folder is deleted"); */ + break; + case G_FILE_MONITOR_EVENT_CREATED: + queueReload(); + break; + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + case G_FILE_MONITOR_EVENT_CHANGED: { + std::lock_guard lock{mutex_}; + pending_change_notify = true; + if(std::find(paths_to_update.cbegin(), paths_to_update.cend(), dirPath_) != paths_to_update.cend()) { + paths_to_update.push_back(dirPath_); + queueUpdate(); + } + /* g_debug("folder is changed"); */ + break; + } +#if GLIB_CHECK_VERSION(2,24,0) + case G_FILE_MONITOR_EVENT_MOVED: +#endif + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + ; + default: + break; + } +} + +void Folder::onFileChangeEvents(GFileMonitor* /*monitor*/, GFile* gf, GFile* /*other_file*/, GFileMonitorEvent evt) { + /* const char* names[]={ + "G_FILE_MONITOR_EVENT_CHANGED", + "G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT", + "G_FILE_MONITOR_EVENT_DELETED", + "G_FILE_MONITOR_EVENT_CREATED", + "G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED", + "G_FILE_MONITOR_EVENT_PRE_UNMOUNT", + "G_FILE_MONITOR_EVENT_UNMOUNTED" + }; */ + if(dirPath_ == gf) { + onDirChanged(evt); + return; + } + else { + std::lock_guard lock{mutex_}; + auto path = FilePath{gf, true}; + /* NOTE: sometimes, for unknown reasons, GFileMonitor gives us the + * same event of the same file for multiple times. So we need to + * check for duplications ourselves here. */ + switch(evt) { + case G_FILE_MONITOR_EVENT_CREATED: + eventFileAdded(path); + break; + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + case G_FILE_MONITOR_EVENT_CHANGED: + eventFileChanged(path); + break; + case G_FILE_MONITOR_EVENT_DELETED: + eventFileDeleted(path); + break; + default: + return; + } + queueUpdate(); + } +} + +// checks whether there were cut files here +// and if there were, invalidates this last cut path +bool Folder::hadCutFilesUnset() { + if(lastCutFilesDirPath_ == dirPath_) { + lastCutFilesDirPath_ = FilePath(); + return true; + } + return false; +} + +bool Folder::hasCutFiles() { + return cutFilesHashSet_ + && !cutFilesHashSet_->empty() + && cutFilesDirPath_ == dirPath_; +} + +void Folder::setCutFiles(const std::shared_ptr& cutFilesHashSet) { + if(cutFilesHashSet_ && !cutFilesHashSet_->empty()) { + lastCutFilesDirPath_ = cutFilesDirPath_; + } + cutFilesDirPath_ = dirPath_; + cutFilesHashSet_ = cutFilesHashSet; +} + +void Folder::onDirListFinished() { + DirListJob* job = static_cast(sender()); + if(job->isCancelled()) { // this is a cancelled job, ignore! + if(job == dirlist_job) { + dirlist_job = nullptr; + } + Q_EMIT finishLoading(); + return; + } + dirInfo_ = job->dirInfo(); + + FileInfoList files_to_add; + std::vector files_to_update; + const auto& infos = job->files(); + + // with "search://", there is no update for infos and all of them should be added + if(strcmp(dirPath_.uriScheme().get(), "search") == 0) { + files_to_add = infos; + for(auto& file: files_to_add) { + files_[file->name()] = file; + } + } + else { + auto info_it = infos.cbegin(); + for(; info_it != infos.cend(); ++info_it) { + const auto& info = *info_it; + auto it = files_.find(info->name()); + if(it != files_.end()) { + files_to_update.push_back(std::make_pair(it->second, info)); + } + else { + files_to_add.push_back(info); + } + files_[info->name()] = info; + } + } + + if(!files_to_add.empty()) { + Q_EMIT filesAdded(files_to_add); + } + if(!files_to_update.empty()) { + Q_EMIT filesChanged(files_to_update); + } + +#if 0 + if(dirlist_job->isCancelled() && !wants_incremental) { + GList* l; + for(l = fm_file_info_list_peek_head_link(job->files); l; l = l->next) { + FmFileInfo* inf = (FmFileInfo*)l->data; + files = g_slist_prepend(files, inf); + fm_file_info_list_push_tail(files, inf); + } + if(G_LIKELY(files)) { + GSList* l; + + G_LOCK(lists); + if(defer_content_test && fm_path_is_native(dir_path)) + /* we got only basic info on content, schedule update it now */ + for(l = files; l; l = l->next) + files_to_update = g_slist_prepend(files_to_update, + fm_path_ref(fm_file_info_get_path(l->data))); + G_UNLOCK(lists); + g_signal_emit(folder, signals[FILES_ADDED], 0, files); + g_slist_free(files); + } + + if(job->dir_fi) { + dir_fi = fm_file_info_ref(job->dir_fi); + } + + /* Some new files are created while FmDirListJob is loading the folder. */ + G_LOCK(lists); + if(G_UNLIKELY(files_to_add)) { + /* This should be a very rare case. Could this happen? */ + GSList* l; + for(l = files_to_add; l;) { + FmPath* path = l->data; + GSList* next = l->next; + if(_Folder::get_file_by_path(folder, path)) { + /* we already have the file. remove it from files_to_add, + * and put it in files_to_update instead. + * No strdup for name is needed here. We steal + * the string from files_to_add.*/ + files_to_update = g_slist_prepend(files_to_update, path); + files_to_add = g_slist_delete_link(files_to_add, l); + } + l = next; + } + } + G_UNLOCK(lists); + } + else if(!dir_fi && job->dir_fi) + /* we may need dir_fi for incremental folders too */ + { + dir_fi = fm_file_info_ref(job->dir_fi); + } + g_object_unref(dirlist_job); +#endif + + dirlist_job = nullptr; + Q_EMIT finishLoading(); +} + +#if 0 + + +void on_dirlist_job_files_found(FmDirListJob* job, GSList* files, gpointer user_data) { + FmFolder* folder = FM_FOLDER(user_data); + GSList* l; + for(l = files; l; l = l->next) { + FmFileInfo* file = FM_FILE_INFO(l->data); + fm_file_info_list_push_tail(files, file); + } + if(G_UNLIKELY(!dir_fi && job->dir_fi)) + /* we may want info while folder is still loading */ + { + dir_fi = fm_file_info_ref(job->dir_fi); + } + g_signal_emit(folder, signals[FILES_ADDED], 0, files); +} + +ErrorAction on_dirlist_job_error(FmDirListJob* job, GError* err, FmJobErrorSeverity severity, FmFolder* folder) { + guint ret; + /* it's possible that some signal handlers tries to free the folder + * when errors occurs, so let's g_object_ref here. */ + g_object_ref(folder); + g_signal_emit(folder, signals[ERROR], 0, err, (guint)severity, &ret); + g_object_unref(folder); + return ret; +} + +void free_dirlist_job(FmFolder* folder) { + if(wants_incremental) { + g_signal_handlers_disconnect_by_func(dirlist_job, on_dirlist_job_files_found, folder); + } + g_signal_handlers_disconnect_by_func(dirlist_job, on_dirlist_job_finished, folder); + g_signal_handlers_disconnect_by_func(dirlist_job, on_dirlist_job_error, folder); + fm_job_cancel(FM_JOB(dirlist_job)); + g_object_unref(dirlist_job); + dirlist_job = nullptr; +} + +#endif + + +void Folder::reload() { + // cancel in-progress jobs if there are any + GError* err = nullptr; + // cancel directory monitoring + if(dirMonitor_) { + g_signal_handlers_disconnect_by_data(dirMonitor_.get(), this); + dirMonitor_.reset(); + } + + /* clear all update-lists now, see SF bug #919 - if update comes before + listing job is finished, a duplicate may be created in the folder */ + if(has_idle_update_handler) { + // FIXME: cancel the idle handler + paths_to_add.clear(); + paths_to_update.clear(); + paths_to_del.clear(); + + // cancel any file info job in progress. + for(auto job: fileinfoJobs_) { + job->cancel(); + disconnect(job, &FileInfoJob::finished, this, &Folder::onFileInfoFinished); + } + fileinfoJobs_.clear(); + } + + /* remove all existing files */ + if(!files_.empty()) { + // FIXME: this is not very efficient :( + auto tmp = files(); + files_.clear(); + Q_EMIT filesRemoved(tmp); + } + + /* Tell the world that we're about to reload the folder. + * It might be a good idea for users of the folder to disconnect + * from the folder temporarily and reconnect to it again after + * the folder complete the loading. This might reduce some + * unnecessary signal handling and UI updates. */ + Q_EMIT startLoading(); + + dirInfo_.reset(); // clear dir info + + /* also re-create a new file monitor */ + // mon = GFileMonitorPtr{fm_monitor_directory(dir_path.gfile().get(), &err), false}; + // FIXME: should we make this cancellable? + dirMonitor_ = GFileMonitorPtr{ + g_file_monitor_directory(dirPath_.gfile().get(), G_FILE_MONITOR_WATCH_MOUNTS, nullptr, &err), + false + }; + + if(dirMonitor_) { + g_signal_connect(dirMonitor_.get(), "changed", G_CALLBACK(_onFileChangeEvents), this); + } + else { + qDebug("file monitor cannot be created: %s", err->message); + g_error_free(err); + } + + Q_EMIT contentChanged(); + + /* run a new dir listing job */ + // FIXME: + // defer_content_test = fm_config->defer_content_test; + dirlist_job = new DirListJob(dirPath_, defer_content_test ? DirListJob::FAST : DirListJob::DETAILED, + hasCutFiles() ? cutFilesHashSet_ : nullptr); + dirlist_job->setAutoDelete(true); + connect(dirlist_job, &DirListJob::error, this, &Folder::error, Qt::BlockingQueuedConnection); + connect(dirlist_job, &DirListJob::finished, this, &Folder::onDirListFinished, Qt::BlockingQueuedConnection); + +#if 0 + if(wants_incremental) { + g_signal_connect(dirlist_job, "files-found", G_CALLBACK(on_dirlist_job_files_found), folder); + } + fm_dir_list_job_set_incremental(dirlist_job, wants_incremental); +#endif + + dirlist_job->runAsync(); + + /* also reload filesystem info. + * FIXME: is this needed? */ + queryFilesystemInfo(); +} + +#if 0 + +/** + * Folder::is_incremental + * @folder: folder to test + * + * Checks if a folder is incrementally loaded. + * After an FmFolder object is obtained from calling Folder::from_path(), + * if it's not yet loaded, it begins loading the content of the folder + * and emits "start-loading" signal. Most of the time, the info of the + * files in the folder becomes available only after the folder is fully + * loaded. That means, after the "finish-loading" signal is emitted. + * Before the loading is finished, Folder::get_files() returns nothing. + * You can tell if a folder is still being loaded with Folder::is_loaded(). + * + * However, for some special FmFolder types, such as the ones handling + * search:// URIs, we want to access the file infos while the folder is + * still being loaded (the search is still ongoing). + * The content of the folder grows incrementally and Folder::get_files() + * returns files currently being loaded even when the folder is not + * fully loaded. This is what we called incremental. + * Folder::is_incremental() tells you if the FmFolder has this feature. + * + * Returns: %true if @folder is incrementally loaded + * + * Since: 1.0.2 + */ +bool Folder::is_incremental(FmFolder* folder) { + return wants_incremental; +} + +#endif + +bool Folder::getFilesystemInfo(uint64_t* total_size, uint64_t* free_size) const { + if(has_fs_info) { + *total_size = fs_total_size; + *free_size = fs_free_size; + return true; + } + return false; +} + + +void Folder::onFileSystemInfoFinished() { + FileSystemInfoJob* job = static_cast(sender()); + if(job->isCancelled() || job != fsInfoJob_) { // this is a cancelled job, ignore! + fsInfoJob_ = nullptr; + has_fs_info = false; + return; + } + has_fs_info = job->isAvailable(); + fs_total_size = job->size(); + fs_free_size = job->freeSize(); + filesystem_info_pending = true; + fsInfoJob_ = nullptr; + queueUpdate(); +} + + +void Folder::queryFilesystemInfo() { + // G_LOCK(query); + if(fsInfoJob_) + return; + fsInfoJob_ = new FileSystemInfoJob{dirPath_}; + fsInfoJob_->setAutoDelete(true); + connect(fsInfoJob_, &FileSystemInfoJob::finished, this, &Folder::onFileSystemInfoFinished, Qt::BlockingQueuedConnection); + + fsInfoJob_->runAsync(); + // G_UNLOCK(query); +} + + +#if 0 +/** + * Folder::block_updates + * @folder: folder to apply + * + * Blocks emitting signals for changes in folder, i.e. if some file was + * added, changed, or removed in folder after this API, no signal will be + * sent until next call to Folder::unblock_updates(). + * + * Since: 1.2.0 + */ +void Folder::block_updates(FmFolder* folder) { + /* g_debug("Folder::block_updates %p", folder); */ + G_LOCK(lists); + /* just set the flag */ + stop_emission = true; + G_UNLOCK(lists); +} + +/** + * Folder::unblock_updates + * @folder: folder to apply + * + * Unblocks emitting signals for changes in folder. If some changes were + * in folder after previous call to Folder::block_updates() then these + * changes will be sent after this call. + * + * Since: 1.2.0 + */ +void Folder::unblock_updates(FmFolder* folder) { + /* g_debug("Folder::unblock_updates %p", folder); */ + G_LOCK(lists); + stop_emission = false; + /* query update now */ + queue_update(folder); + G_UNLOCK(lists); + /* g_debug("Folder::unblock_updates OK"); */ +} + +/** + * Folder::make_directory + * @folder: folder to apply + * @name: display name for new directory + * @error: (allow-none) (out): location to save error + * + * Creates new directory in given @folder. + * + * Returns: %true in case of success. + * + * Since: 1.2.0 + */ +bool Folder::make_directory(FmFolder* folder, const char* name, GError** error) { + GFile* dir, *gf; + FmPath* path; + bool ok; + + dir = fm_path_to_gfile(dir_path); + gf = g_file_get_child_for_display_name(dir, name, error); + g_object_unref(dir); + if(gf == nullptr) { + return false; + } + ok = g_file_make_directory(gf, nullptr, error); + if(ok) { + path = fm_path_new_for_gfile(gf); + if(!_Folder::event_file_added(folder, path)) { + fm_path_unref(path); + } + } + g_object_unref(gf); + return ok; +} + +void Folder::content_changed(FmFolder* folder) { + if(has_fs_info && !fs_info_not_avail) { + Folder::query_filesystem_info(folder); + } +} + +#endif + +/* NOTE: + * GFileMonitor has some significant limitations: + * 1. Currently it can correctly emit unmounted event for a directory. + * 2. After a directory is unmounted, its content changes. + * Inotify does not fire events for this so a forced reload is needed. + * 3. If a folder is empty, and later a filesystem is mounted to the + * folder, its content should reflect the content of the newly mounted + * filesystem. However, GFileMonitor and inotify do not emit events + * for this case. A forced reload might be needed for this case as well. + * 4. Some limitations come from Linux/inotify. If FAM/gamin is used, + * the condition may be different. More testing is needed. + */ +void Folder::onMountAdded(const Mount& mnt) { + /* If a filesystem is mounted over an existing folder, + * we need to refresh the content of the folder to reflect + * the changes. Besides, we need to create a new GFileMonitor + * for the newly-mounted filesystem as the inode already changed. + * GFileMonitor cannot detect this kind of changes caused by mounting. + * So let's do it ourselves. */ + auto mountRoot = mnt.root(); + if(mountRoot.isPrefixOf(dirPath_)) { + queueReload(); + } + /* g_debug("FmFolder::mount_added"); */ +} + +void Folder::onMountRemoved(const Mount& mnt) { + /* g_debug("FmFolder::mount_removed"); */ + + /* NOTE: gvfs does not emit unmount signals for remote folders since + * GFileMonitor does not support remote filesystems at all. + * So here is the side effect, no unmount notifications. + * We need to generate the signal ourselves. */ + if(!dirMonitor_) { + // this is only needed when we don't have a GFileMonitor + auto mountRoot = mnt.root(); + if(mountRoot.isPrefixOf(dirPath_)) { + // if the current folder is under the unmounted path, generate the event ourselves + onDirChanged(G_FILE_MONITOR_EVENT_UNMOUNTED); + } + } +} + +} // namespace Fm diff --git a/src/core/folder.h b/src/core/folder.h new file mode 100644 index 0000000..b4b1f39 --- /dev/null +++ b/src/core/folder.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2016 Hong Jen Yee (PCMan) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __LIBFM2_QT_FM_FOLDER_H__ +#define __LIBFM2_QT_FM_FOLDER_H__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "../libfmqtglobals.h" + +#include "gioptrs.h" +#include "fileinfo.h" +#include "job.h" +#include "volumemanager.h" + +namespace Fm { + +class DirListJob; +class FileSystemInfoJob; +class FileInfoJob; + + +class LIBFM_QT_API Folder: public QObject { + Q_OBJECT +public: + + explicit Folder(); + + explicit Folder(const FilePath& path); + + virtual ~Folder(); + + static std::shared_ptr fromPath(const FilePath& path); + + bool makeDirectory(const char* name, GError** error); + + void queryFilesystemInfo(); + + bool getFilesystemInfo(uint64_t* total_size, uint64_t* free_size) const; + + void reload(); + + bool isIncremental() const; + + bool isValid() const; + + bool isLoaded() const; + + std::shared_ptr fileByName(const char* name) const; + + bool isEmpty() const; + + FileInfoList files() const; + + const FilePath& path() const; + + const std::shared_ptr &info() const; + + bool hadCutFilesUnset(); + + bool hasCutFiles(); + + void setCutFiles(const std::shared_ptr& cutFilesHashSet); + + void forEachFile(std::function&)> func) const { + std::lock_guard lock{mutex_}; + for(auto it = files_.begin(); it != files_.end(); ++it) { + func(it->second); + } + } + +Q_SIGNALS: + void startLoading(); + + void finishLoading(); + + void filesAdded(FileInfoList& addedFiles); + + void filesChanged(std::vector& changePairs); + + void filesRemoved(FileInfoList& removedFiles); + + void removed(); + + void changed(); + + void unmount(); + + void contentChanged(); + + void fileSystemChanged(); + + // FIXME: this API design is bad. We leave this here to be compatible with the old libfm C API. + // It might be better to remember the error state while loading the folder, and let the user of the + // API handle the error on finish. + void error(const GErrorPtr& err, Job::ErrorSeverity severity, Job::ErrorAction& response); + +private: + + static void _onFileChangeEvents(GFileMonitor* monitor, GFile* file, GFile* other_file, GFileMonitorEvent event_type, Folder* _this) { + _this->onFileChangeEvents(monitor, file, other_file, event_type); + } + void onFileChangeEvents(GFileMonitor* monitor, GFile* file, GFile* other_file, GFileMonitorEvent event_type); + void onDirChanged(GFileMonitorEvent event_type); + + void queueUpdate(); + void queueReload(); + + bool eventFileAdded(const FilePath &path); + bool eventFileChanged(const FilePath &path); + void eventFileDeleted(const FilePath &path); + +private Q_SLOTS: + + void processPendingChanges(); + + void onDirListFinished(); + + void onFileSystemInfoFinished(); + + void onFileInfoFinished(); + + void onIdleReload(); + + void onMountAdded(const Mount& mnt); + + void onMountRemoved(const Mount& mnt); + +private: + FilePath dirPath_; + GFileMonitorPtr dirMonitor_; + + std::shared_ptr dirInfo_; + DirListJob* dirlist_job; + std::vector fileinfoJobs_; + FileSystemInfoJob* fsInfoJob_; + + std::shared_ptr volumeManager_; + + /* for file monitor */ + bool has_idle_reload_handler; + bool has_idle_update_handler; + std::vector paths_to_add; + std::vector paths_to_update; + std::vector paths_to_del; + // GSList* pending_jobs; + bool pending_change_notify; + bool filesystem_info_pending; + + bool wants_incremental; + bool stop_emission; /* don't set it 1 bit to not lock other bits */ + + std::unordered_map, std::hash> files_; + + /* filesystem info - set in query thread, read in main */ + uint64_t fs_total_size; + uint64_t fs_free_size; + GCancellablePtr fs_size_cancellable; + + bool has_fs_info : 1; + bool defer_content_test : 1; + + static std::unordered_map, FilePathHash> cache_; + static FilePath cutFilesDirPath_; + static FilePath lastCutFilesDirPath_; + static std::shared_ptr cutFilesHashSet_; + static std::mutex mutex_; +}; + +} + +#endif // __LIBFM_QT_FM2_FOLDER_H__ diff --git a/src/core/gioptrs.h b/src/core/gioptrs.h new file mode 100644 index 0000000..401424b --- /dev/null +++ b/src/core/gioptrs.h @@ -0,0 +1,137 @@ +#ifndef GIOPTRS_H +#define GIOPTRS_H +// define smart pointers for GIO data types + +#include +#include +#include "gobjectptr.h" +#include "cstrptr.h" + +namespace Fm { + +typedef GObjectPtr GFilePtr; +typedef GObjectPtr GFileInfoPtr; +typedef GObjectPtr GFileMonitorPtr; +typedef GObjectPtr GCancellablePtr; +typedef GObjectPtr GFileEnumeratorPtr; + +typedef GObjectPtr GInputStreamPtr; +typedef GObjectPtr GFileInputStreamPtr; +typedef GObjectPtr GOutputStreamPtr; +typedef GObjectPtr GFileOutputStreamPtr; + +typedef GObjectPtr GIconPtr; + +typedef GObjectPtr GVolumeMonitorPtr; +typedef GObjectPtr GVolumePtr; +typedef GObjectPtr GMountPtr; + +typedef GObjectPtr GAppInfoPtr; + + +class GErrorPtr { +public: + GErrorPtr(): err_{nullptr} { + } + + GErrorPtr(GError*&& err) noexcept: err_{err} { + err = nullptr; + } + + GErrorPtr(const GErrorPtr& other) = delete; + + GErrorPtr(GErrorPtr&& other) noexcept: err_{other.err_} { + other.err_ = nullptr; + } + + GErrorPtr(std::uint32_t domain, unsigned int code, const char* msg): + GErrorPtr{g_error_new_literal(domain, code, msg)} { + } + + GErrorPtr(std::uint32_t domain, unsigned int code, const QString& msg): + GErrorPtr{domain, code, msg.toUtf8().constData()} { + } + + ~GErrorPtr() { + reset(); + } + + std::uint32_t domain() const { + if(err_ != nullptr) { + return err_->domain; + } + return 0; + } + + unsigned int code() const { + if(err_ != nullptr) { + return err_->code; + } + return 0; + } + + QString message() const { + if(err_ != nullptr) { + return err_->message; + } + return QString(); + } + + void reset() { + if(err_) { + g_error_free(err_); + } + err_ = nullptr; + } + + GError* get() const { + return err_; + } + + GErrorPtr& operator = (const GErrorPtr& other) = delete; + + GErrorPtr& operator = (GErrorPtr&& other) noexcept { + reset(); + err_ = other.err_; + other.err_ = nullptr; + return *this; + } + + GErrorPtr& operator = (GError*&& err) { + reset(); + err_ = err; + err_ = nullptr; + return *this; + } + + GError** operator&() { + return &err_; + } + + GError* operator->() { + return err_; + } + + bool operator == (const GErrorPtr& other) const { + return err_ == other.err_; + } + + bool operator == (GError* err) const { + return err_ == err; + } + + bool operator != (std::nullptr_t) const { + return err_ != nullptr; + } + + operator bool() const { + return err_ != nullptr; + } + +private: + GError* err_; +}; + +} //namespace Fm + +#endif // GIOPTRS_H diff --git a/src/core/gobjectptr.h b/src/core/gobjectptr.h new file mode 100644 index 0000000..333bcc2 --- /dev/null +++ b/src/core/gobjectptr.h @@ -0,0 +1,104 @@ +#ifndef FM2_GOBJECTPTR_H +#define FM2_GOBJECTPTR_H + +#include "../libfmqtglobals.h" +#include +#include +#include +#include + +namespace Fm { + +template +class LIBFM_QT_API GObjectPtr { +public: + + explicit GObjectPtr(): gobj_{nullptr} { + } + + explicit GObjectPtr(T* gobj, bool add_ref = true): gobj_{gobj} { + if(gobj_ != nullptr && add_ref) + g_object_ref(gobj_); + } + + GObjectPtr(const GObjectPtr& other): gobj_{other.gobj_ ? reinterpret_cast(g_object_ref(other.gobj_)) : nullptr} { + } + + GObjectPtr(GObjectPtr&& other) noexcept: gobj_{other.release()} { + } + + ~GObjectPtr() { + if(gobj_ != nullptr) + g_object_unref(gobj_); + } + + T* get() const { + return gobj_; + } + + T* release() { + T* tmp = gobj_; + gobj_ = nullptr; + return tmp; + } + + void reset() { + if(gobj_ != nullptr) + g_object_unref(gobj_); + gobj_ = nullptr; + } + + GObjectPtr& operator = (const GObjectPtr& other) { + if (*this == other) + return *this; + + if(gobj_ != nullptr) + g_object_unref(gobj_); + gobj_ = other.gobj_ ? reinterpret_cast(g_object_ref(other.gobj_)) : nullptr; + return *this; + } + + GObjectPtr& operator = (GObjectPtr&& other) noexcept { + if (this == &other) + return *this; + + if(gobj_ != nullptr) + g_object_unref(gobj_); + gobj_ = other.release(); + return *this; + } + + GObjectPtr& operator = (T* gobj) { + if (*this == gobj) + return *this; + + if(gobj_ != nullptr) + g_object_unref(gobj_); + gobj_ = gobj ? reinterpret_cast(g_object_ref(gobj_)) : nullptr; + return *this; + } + + bool operator == (const GObjectPtr& other) const { + return gobj_ == other.gobj_; + } + + bool operator == (T* gobj) const { + return gobj_ == gobj; + } + + bool operator != (std::nullptr_t) const { + return gobj_ != nullptr; + } + + operator bool() const { + return gobj_ != nullptr; + } + +private: + mutable T* gobj_; +}; + + +} // namespace Fm + +#endif // FM2_GOBJECTPTR_H diff --git a/src/core/iconinfo.cpp b/src/core/iconinfo.cpp new file mode 100644 index 0000000..5ec2abb --- /dev/null +++ b/src/core/iconinfo.cpp @@ -0,0 +1,138 @@ +#include "iconinfo.h" +#include "iconinfo_p.h" + +namespace Fm { + +std::unordered_map, IconInfo::GIconHash, IconInfo::GIconEqual> IconInfo::cache_; +std::mutex IconInfo::mutex_; +QIcon IconInfo::fallbackQicon_; + +static const char* fallbackIconNames[] = { + "unknown", + "application-octet-stream", + "application-x-generic", + "text-x-generic", + nullptr +}; + +IconInfo::IconInfo(const char* name): + gicon_{g_themed_icon_new(name), false} { +} + +IconInfo::IconInfo(const GIconPtr gicon): + gicon_{std::move(gicon)} { +} + +IconInfo::~IconInfo() { +} + +// static +std::shared_ptr IconInfo::fromName(const char* name) { + GObjectPtr gicon{g_themed_icon_new(name), false}; + return fromGIcon(gicon); +} + +// static +std::shared_ptr IconInfo::fromGIcon(GIconPtr gicon) { + if(Q_LIKELY(gicon)) { + std::lock_guard lock{mutex_}; + auto it = cache_.find(gicon.get()); + if(it != cache_.end()) { + return it->second; + } + // not found in the cache, create a new entry for it. + auto icon = std::make_shared(std::move(gicon)); + cache_.insert(std::make_pair(icon->gicon_.get(), icon)); + return icon; + } + return std::shared_ptr{}; +} + +void IconInfo::updateQIcons() { + std::lock_guard lock{mutex_}; + fallbackQicon_ = QIcon(); + for(auto& elem: cache_) { + auto& info = elem.second; + info->internalQicon_ = QIcon(); + } +} + +QIcon IconInfo::qicon(const bool& transparent) const { + if(Q_LIKELY(!transparent)) { + if(Q_UNLIKELY(qicon_.isNull() && gicon_)) { + if(!G_IS_FILE_ICON(gicon_.get())) { + qicon_ = QIcon(new IconEngine{shared_from_this()}); + } + else { + qicon_ = internalQicon_; + } + } + } + else { // transparent == true + if(Q_UNLIKELY(qiconTransparent_.isNull() && gicon_)) { + if(!G_IS_FILE_ICON(gicon_.get())) { + qiconTransparent_ = QIcon(new IconEngine{shared_from_this(), transparent}); + } + else { + qiconTransparent_ = internalQicon_; + } + } + } + return !transparent ? qicon_ : qiconTransparent_; +} + +QIcon IconInfo::qiconFromNames(const char* const* names) { + const gchar* const* name; + // qDebug("names: %p", names); + for(name = names; *name; ++name) { + // qDebug("icon name=%s", *name); + QIcon qicon = QIcon::fromTheme(*name); + if(!qicon.isNull()) { + return qicon; + } + } + return QIcon(); +} + +std::forward_list> IconInfo::emblems() const { + std::forward_list> result; + if(hasEmblems()) { + const GList* emblems_glist = g_emblemed_icon_get_emblems(G_EMBLEMED_ICON(gicon_.get())); + for(auto l = emblems_glist; l; l = l->next) { + auto gemblem = G_EMBLEM(l->data); + GIconPtr gemblem_icon{g_emblem_get_icon(gemblem), true}; + result.emplace_front(fromGIcon(gemblem_icon)); + } + result.reverse(); + } + return result; +} + +QIcon IconInfo::internalQicon() const { + if(Q_UNLIKELY(internalQicon_.isNull())) { + GIcon* gicon = gicon_.get(); + if(G_IS_EMBLEMED_ICON(gicon_.get())) { + gicon = g_emblemed_icon_get_icon(G_EMBLEMED_ICON(gicon)); + } + if(G_IS_THEMED_ICON(gicon)) { + const gchar* const* names = g_themed_icon_get_names(G_THEMED_ICON(gicon)); + internalQicon_ = qiconFromNames(names); + } + else if(G_IS_FILE_ICON(gicon)) { + GFile* file = g_file_icon_get_file(G_FILE_ICON(gicon)); + CStrPtr fpath{g_file_get_path(file)}; + internalQicon_ = QIcon(fpath.get()); + } + + // fallback to default icon + if(Q_UNLIKELY(internalQicon_.isNull())) { + if(Q_UNLIKELY(fallbackQicon_.isNull())) { + fallbackQicon_ = qiconFromNames(fallbackIconNames); + } + internalQicon_ = fallbackQicon_; + } + } + return internalQicon_; +} + +} // namespace Fm diff --git a/src/core/iconinfo.h b/src/core/iconinfo.h new file mode 100644 index 0000000..60924b1 --- /dev/null +++ b/src/core/iconinfo.h @@ -0,0 +1,112 @@ +/* + * fm-icon.h + * + * Copyright 2009 Hong Jen Yee (PCMan) + * Copyright 2013 Andriy Grytsenko (LStranger) + * + * This file is a part of the Libfm library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef __FM2_ICON_INFO_H__ +#define __FM2_ICON_INFO_H__ + +#include "../libfmqtglobals.h" +#include +#include "gioptrs.h" +#include +#include +#include +#include +#include + + +namespace Fm { + +class LIBFM_QT_API IconInfo: public std::enable_shared_from_this { +public: + friend class IconEngine; + + explicit IconInfo() {} + + explicit IconInfo(const char* name); + + explicit IconInfo(const GIconPtr gicon); + + ~IconInfo(); + + static std::shared_ptr fromName(const char* name); + + static std::shared_ptr fromGIcon(GIconPtr gicon); + + static std::shared_ptr fromGIcon(GIcon* gicon) { + return fromGIcon(GIconPtr{gicon, true}); + } + + static void updateQIcons(); + + GIconPtr gicon() const { + return gicon_; + } + + QIcon qicon(const bool& transparent = false) const; + + bool hasEmblems() const { + return G_IS_EMBLEMED_ICON(gicon_.get()); + } + + std::forward_list> emblems() const; + + bool isValid() const { + return gicon_ != nullptr; + } + +private: + + static QIcon qiconFromNames(const char* const* names); + + // actual QIcon loaded by QIcon::fromTheme + QIcon internalQicon() const; + + struct GIconHash { + std::size_t operator()(GIcon* gicon) const { + return g_icon_hash(gicon); + } + }; + + struct GIconEqual { + bool operator()(GIcon* gicon1, GIcon* gicon2) const { + return g_icon_equal(gicon1, gicon2); + } + }; + +private: + GIconPtr gicon_; + mutable QIcon qicon_; + mutable QIcon qiconTransparent_; + mutable QIcon internalQicon_; + + static std::unordered_map, GIconHash, GIconEqual> cache_; + static std::mutex mutex_; + static QIcon fallbackQicon_; +}; + +} // namespace Fm + +Q_DECLARE_METATYPE(std::shared_ptr) + +#endif /* __FM2_ICON_INFO_H__ */ diff --git a/src/core/iconinfo_p.h b/src/core/iconinfo_p.h new file mode 100644 index 0000000..dd03207 --- /dev/null +++ b/src/core/iconinfo_p.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2016 Hong Jen Yee (PCMan) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef FM_ICONENGINE_H +#define FM_ICONENGINE_H + +#include +#include +#include "../libfmqtglobals.h" +#include "iconinfo.h" +#include + +namespace Fm { + +class IconEngine: public QIconEngine { +public: + + IconEngine(std::shared_ptr info, const bool& transparent = false); + + ~IconEngine(); + + virtual QSize actualSize(const QSize& size, QIcon::Mode mode, QIcon::State state) override; + + // not supported + virtual void addFile(const QString& /*fileName*/, const QSize& /*size*/, QIcon::Mode /*mode*/, QIcon::State /*state*/) override {} + + // not supported + virtual void addPixmap(const QPixmap& /*pixmap*/, QIcon::Mode /*mode*/, QIcon::State /*state*/) override {} + + virtual QIconEngine* clone() const override; + + virtual QString key() const override; + + virtual void paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) override; + + virtual QPixmap pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) override; + + virtual void virtual_hook(int id, void* data) override; + +private: + std::weak_ptr info_; + bool transparent_; +}; + +IconEngine::IconEngine(std::shared_ptr info, const bool& transparent): + info_{info}, transparent_{transparent} { +} + +IconEngine::~IconEngine() { +} + +QSize IconEngine::actualSize(const QSize& size, QIcon::Mode mode, QIcon::State state) { + auto info = info_.lock(); + return info ? info->internalQicon().actualSize(size, mode, state) : QSize{}; +} + +QIconEngine* IconEngine::clone() const { + IconEngine* engine = new IconEngine(info_.lock()); + return engine; +} + +QString IconEngine::key() const { + return QStringLiteral("Fm::IconEngine"); +} + +void IconEngine::paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) { + auto info = info_.lock(); + if(info) { + if(transparent_) { + painter->save(); + painter->setOpacity(0.45); + } + info->internalQicon().paint(painter, rect, Qt::AlignCenter, mode, state); + if(transparent_) { + painter->restore(); + } + } +} + +QPixmap IconEngine::pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) { + auto info = info_.lock(); + return info ? info->internalQicon().pixmap(size, mode, state) : QPixmap{}; +} + +void IconEngine::virtual_hook(int id, void* data) { + auto info = info_.lock(); + switch(id) { + case QIconEngine::AvailableSizesHook: { + auto* args = reinterpret_cast(data); + args->sizes = info ? info->internalQicon().availableSizes(args->mode, args->state) : QList{}; + break; + } + case QIconEngine::IconNameHook: { + QString* result = reinterpret_cast(data); + *result = info ? info->internalQicon().name() : QString{}; + break; + } +#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) + case QIconEngine::IsNullHook: { + bool* result = reinterpret_cast(data); + *result = info ? info->internalQicon().isNull() : true; + break; + } +#endif + } +} + +} // namespace Fm + +#endif // FM_ICONENGINE_H diff --git a/src/core/job.cpp b/src/core/job.cpp new file mode 100644 index 0000000..e597975 --- /dev/null +++ b/src/core/job.cpp @@ -0,0 +1,57 @@ +#include "job.h" +#include "job_p.h" + +namespace Fm { + +Job::Job(): + paused_{false}, + cancellable_{g_cancellable_new(), false}, + cancellableHandler_{g_signal_connect(cancellable_.get(), "cancelled", G_CALLBACK(_onCancellableCancelled), this)} { +} + +Job::~Job() { + if(cancellable_) { + g_cancellable_disconnect(cancellable_.get(), cancellableHandler_); + } +} + +void Job::runAsync(QThread::Priority priority) { + auto thread = new JobThread(this); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + if(autoDelete()) { + connect(this, &Job::finished, this, &Job::deleteLater); + } + thread->start(priority); +} + +void Job::cancel() { + g_cancellable_cancel(cancellable_.get()); +} + +void Job::run() { + exec(); + Q_EMIT finished(); +} + + +Job::ErrorAction Job::emitError(const GErrorPtr &err, Job::ErrorSeverity severity) { + ErrorAction response = ErrorAction::CONTINUE; + // if the error is already handled, don't emit it. + if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_FAILED_HANDLED) { + return response; + } + Q_EMIT error(err, severity, response); + + if(severity == ErrorSeverity::CRITICAL || response == ErrorAction::ABORT) { + cancel(); + } + else if(response == ErrorAction::RETRY ) { + /* If the job is already cancelled, retry is not allowed. */ + if(isCancelled() || (err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_CANCELLED)) { + response = ErrorAction::CONTINUE; + } + } + return response; +} + +} // namespace Fm diff --git a/src/core/job.h b/src/core/job.h new file mode 100644 index 0000000..00ca4a5 --- /dev/null +++ b/src/core/job.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016 Hong Jen Yee (PCMan) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __LIBFM_QT_FM_JOB_H__ +#define __LIBFM_QT_FM_JOB_H__ + +#include +#include +#include +#include +#include +#include +#include +#include "gobjectptr.h" +#include "gioptrs.h" +#include "../libfmqtglobals.h" + + +namespace Fm { + +/* + * Fm::Job can be used in several different modes. + * 1. run with QThreadPool::start() + * 2. call runAsync(), which will create a new QThread and move the object to the thread. + * 3. create a new QThread, and connect the started() signal to the slot Job::run() + * 4. Directly call Job::run(), which executes synchrounously as a normal blocking call +*/ + +class LIBFM_QT_API Job: public QObject, public QRunnable { + Q_OBJECT +public: + + enum class ErrorAction{ + CONTINUE, + RETRY, + ABORT + }; + + enum class ErrorSeverity { + UNKNOWN, + WARNING, + MILD, + MODERATE, + SEVERE, + CRITICAL + }; + + explicit Job(); + + virtual ~Job(); + + bool isCancelled() const { + return g_cancellable_is_cancelled(cancellable_.get()); + } + + void runAsync(QThread::Priority priority = QThread::InheritPriority); + + bool pause(); + + void resume(); + + const GCancellablePtr& cancellable() const { + return cancellable_; + } + +Q_SIGNALS: + void cancelled(); + + void finished(); + + // this signal should be connected with Qt::BlockingQueuedConnection + void error(const GErrorPtr& err, ErrorSeverity severity, ErrorAction& response); + +public Q_SLOTS: + + void cancel(); + + void run() override; + +protected: + ErrorAction emitError(const GErrorPtr& err, ErrorSeverity severity = ErrorSeverity::MODERATE); + + // all derived job subclasses should do their work in this method. + virtual void exec() = 0; + +private: + static void _onCancellableCancelled(GCancellable* cancellable, Job* _this) { + _this->onCancellableCancelled(cancellable); + } + + void onCancellableCancelled(GCancellable* /*cancellable*/) { + Q_EMIT cancelled(); + } + +private: + bool paused_; + GCancellablePtr cancellable_; + gulong cancellableHandler_; +}; + + +} + +#endif // __LIBFM_QT_FM_JOB_H__ diff --git a/src/core/job_p.h b/src/core/job_p.h new file mode 100644 index 0000000..d893d5f --- /dev/null +++ b/src/core/job_p.h @@ -0,0 +1,26 @@ +#ifndef JOB_P_H +#define JOB_P_H + +#include +#include "job.h" + +namespace Fm { + +class JobThread: public QThread { + Q_OBJECT +public: + JobThread(Job* job): job_{job} { + } + +protected: + + void run() override { + job_->run(); + } + + Job* job_; +}; + +} // namespace Fm + +#endif // JOB_P_H diff --git a/src/core/mimetype.cpp b/src/core/mimetype.cpp new file mode 100644 index 0000000..19345bb --- /dev/null +++ b/src/core/mimetype.cpp @@ -0,0 +1,64 @@ +#include "mimetype.h" +#include + +#include +#include + +using namespace std; + +namespace Fm { + +std::unordered_map, CStrHash, CStrEqual> MimeType::cache_; +std::mutex MimeType::mutex_; + +std::shared_ptr MimeType::inodeDirectory_; // inode/directory +std::shared_ptr MimeType::inodeShortcut_; // inode/x-shortcut +std::shared_ptr MimeType::inodeMountPoint_; // inode/mount-point +std::shared_ptr MimeType::desktopEntry_; // application/x-desktop + + +MimeType::MimeType(const char* typeName): + name_{g_strdup(typeName)}, + desc_{nullptr} { + + GObjectPtr gicon{g_content_type_get_icon(typeName), false}; + if(strcmp(typeName, "inode/directory") == 0) + g_themed_icon_prepend_name(G_THEMED_ICON(gicon.get()), "folder"); + else if(g_content_type_can_be_executable(typeName)) + g_themed_icon_append_name(G_THEMED_ICON(gicon.get()), "application-x-executable"); + + icon_ = IconInfo::fromGIcon(gicon); +} + +MimeType::~MimeType () { +} + +//static +std::shared_ptr MimeType::fromName(const char* typeName) { + std::shared_ptr ret; + std::lock_guard lock(mutex_); + auto it = cache_.find(typeName); + if(it == cache_.end()) { + ret = std::make_shared(typeName); + cache_.insert(std::make_pair(ret->name_.get(), ret)); + } + else { + ret = it->second; + } + return ret; +} + +// static +std::shared_ptr MimeType::guessFromFileName(const char* fileName) { + gboolean uncertain; + /* let skip scheme and host from non-native names */ + auto uri_scheme = g_strstr_len(fileName, -1, "://"); + if(uri_scheme) + fileName = strchr(uri_scheme + 3, '/'); + if(fileName == nullptr) + fileName = "unknown"; + auto type = CStrPtr{g_content_type_guess(fileName, nullptr, 0, &uncertain)}; + return fromName(type.get()); +} + +} // namespace Fm diff --git a/src/core/mimetype.h b/src/core/mimetype.h new file mode 100644 index 0000000..887ce08 --- /dev/null +++ b/src/core/mimetype.h @@ -0,0 +1,172 @@ +/* + * fm-mime-type.h + * + * Copyright 2009 - 2012 Hong Jen Yee (PCMan) + * + * This file is a part of the Libfm library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _FM2_MIME_TYPE_H_ +#define _FM2_MIME_TYPE_H_ + +#include "../libfmqtglobals.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "cstrptr.h" +#include "gobjectptr.h" +#include "iconinfo.h" +#include "thumbnailer.h" + +namespace Fm { + +class LIBFM_QT_API MimeType { +public: + friend class Thumbnailer; + + explicit MimeType(const char* typeName); + + MimeType() = delete; + + ~MimeType(); + + std::shared_ptr firstThumbnailer() const { + std::lock_guard lock{mutex_}; + return thumbnailers_.empty() ? nullptr : thumbnailers_.front(); + } + + void forEachThumbnailer(std::function&)> func) const { + std::lock_guard lock{mutex_}; + for(auto& thumbnailer: thumbnailers_) { + if(func(thumbnailer)) { + break; + } + } + } + + const std::shared_ptr& icon() const { + return icon_; + } + + const char* name() const { + return name_.get(); + } + + const char* desc() const { + if(!desc_) { + desc_ = CStrPtr{g_content_type_get_description(name_.get())}; + } + return desc_.get(); + } + + static std::shared_ptr fromName(const char* typeName); + + static std::shared_ptr guessFromFileName(const char* fileName); + + bool isUnknownType() const { + return g_content_type_is_unknown(name_.get()); + } + + bool isDesktopEntry() const { + return this == desktopEntry().get(); + } + + bool isText() const { + return g_content_type_is_a(name_.get(), "text/plain"); + } + + bool isImage() const { + return !std::strncmp("image/", name_.get(), 6); + } + + bool isMountable() const { + return this == inodeMountPoint().get(); + } + + bool isShortcut() const { + return this == inodeShortcut().get(); + } + + bool isDir() const { + return this == inodeDirectory().get(); + } + + bool canBeExecutable() const { + return g_content_type_can_be_executable(name_.get()); + } + + static std::shared_ptr inodeDirectory() { // inode/directory + if(!inodeDirectory_) + inodeDirectory_ = fromName("inode/directory"); + return inodeDirectory_; + } + + static std::shared_ptr inodeShortcut() { // inode/x-shortcut + if(!inodeShortcut_) + inodeShortcut_ = fromName("inode/x-shortcut"); + return inodeShortcut_; + } + + static std::shared_ptr inodeMountPoint() { // inode/mount-point + if(!inodeMountPoint_) + inodeMountPoint_ = fromName("inode/mount-point"); + return inodeMountPoint_; + } + + static std::shared_ptr desktopEntry() { // application/x-desktop + if(!desktopEntry_) + desktopEntry_ = fromName("application/x-desktop"); + return desktopEntry_; + } + +private: + void removeThumbnailer(std::shared_ptr& thumbnailer) { + std::lock_guard lock{mutex_}; + thumbnailers_.remove(thumbnailer); + } + + void addThumbnailer(std::shared_ptr thumbnailer) { + std::lock_guard lock{mutex_}; + thumbnailers_.push_front(std::move(thumbnailer)); + } + +private: + std::shared_ptr icon_; + CStrPtr name_; + mutable CStrPtr desc_; + std::forward_list> thumbnailers_; + static std::unordered_map, CStrHash, CStrEqual> cache_; + static std::mutex mutex_; + + static std::shared_ptr inodeDirectory_; // inode/directory + static std::shared_ptr inodeShortcut_; // inode/x-shortcut + static std::shared_ptr inodeMountPoint_; // inode/mount-point + static std::shared_ptr desktopEntry_; // application/x-desktop +}; + + +} // namespace Fm + +#endif diff --git a/src/core/terminal.cpp b/src/core/terminal.cpp new file mode 100644 index 0000000..ae65f3d --- /dev/null +++ b/src/core/terminal.cpp @@ -0,0 +1,127 @@ +#include "terminal.h" + +namespace Fm { + +#include +#include +#include +#include + +#if !GLIB_CHECK_VERSION(2, 28, 0) && !HAVE_DECL_ENVIRON +extern char** environ; +#endif + +static void child_setup(gpointer user_data) { + /* Move child to grandparent group so it will not die with parent */ + setpgid(0, (pid_t)(gsize)user_data); +} + +bool launchTerminal(const char* programName, const FilePath& workingDir, Fm::GErrorPtr& error) { + /* read system terminals file */ + GKeyFile* kf = g_key_file_new(); + if(!g_key_file_load_from_file(kf, LIBFM_QT_DATA_DIR "/terminals.list", G_KEY_FILE_NONE, &error)) { + g_key_file_free(kf); + return false; + } + auto launch = g_key_file_get_string(kf, programName, "launch", nullptr); + auto desktop_id = g_key_file_get_string(kf, programName, "desktop_id", nullptr); + + GDesktopAppInfo* appinfo = nullptr; + if(desktop_id) { + appinfo = g_desktop_app_info_new(desktop_id); + } + + const gchar* cmd; + gchar* _cmd = nullptr; + if(appinfo) { + cmd = g_app_info_get_commandline(G_APP_INFO(appinfo)); + } + else if(launch) { + cmd = _cmd = g_strdup_printf("%s %s", programName, launch); + } + else { + cmd = programName; + } + +#if 0 // FIXME: what's this? + if(custom_args) { + cmd = g_strdup_printf("%s %s", cmd, custom_args); + g_free(_cmd); + _cmd = (char*)cmd; + } +#endif + + char** argv; + int argc; + if(!g_shell_parse_argv(cmd, &argc, &argv, nullptr)) { + argv = nullptr; + } + g_free(_cmd); + + if(appinfo) { + g_object_unref(appinfo); + } + if(!argv) { /* parsing failed */ + return false; + } + char** envp; +#if GLIB_CHECK_VERSION(2, 28, 0) + envp = g_get_environ(); +#else + envp = g_strdupv(environ); +#endif + + auto dir = workingDir ? workingDir.localPath() : nullptr; + if(dir) { +#if GLIB_CHECK_VERSION(2, 32, 0) + envp = g_environ_setenv(envp, "PWD", dir.get(), TRUE); +#else + char** env = envp; + + if(env) while(*env != nullptr) { + if(strncmp(*env, "PWD=", 4) == 0) { + break; + } + env++; + } + if(env == nullptr || *env == nullptr) { + gint length; + + length = envp ? g_strv_length(envp) : 0; + envp = g_renew(gchar*, envp, length + 2); + env = &envp[length]; + env[1] = nullptr; + } + else { + g_free(*env); + } + *env = g_strdup_printf("PWD=%s", dir); +#endif + } + + bool ret = g_spawn_async(dir.get(), argv, envp, G_SPAWN_SEARCH_PATH, + child_setup, (gpointer)(gsize)getpgid(getppid()), + nullptr, &error); + g_strfreev(argv); + g_strfreev(envp); + g_key_file_free(kf); + return ret; +} + +std::vector allKnownTerminals() { + std::vector terminals; + GKeyFile* kf = g_key_file_new(); + if(g_key_file_load_from_file(kf, LIBFM_QT_DATA_DIR "/terminals.list", G_KEY_FILE_NONE, nullptr)) { + gsize n; + auto programs = g_key_file_get_groups(kf, &n); + terminals.reserve(n); + for(auto name = programs; *name; ++name) { + terminals.emplace_back(*name); + } + g_free(programs); + } + g_key_file_free(kf); + return terminals; +} + +} // namespace Fm diff --git a/src/core/terminal.h b/src/core/terminal.h new file mode 100644 index 0000000..86c3f4b --- /dev/null +++ b/src/core/terminal.h @@ -0,0 +1,17 @@ +#ifndef TERMINAL_H +#define TERMINAL_H + +#include "../libfmqtglobals.h" +#include "gioptrs.h" +#include "filepath.h" +#include + +namespace Fm { + +LIBFM_QT_API bool launchTerminal(const char* programName, const FilePath& workingDir, GErrorPtr& error); + +LIBFM_QT_API std::vector allKnownTerminals(); + +} // namespace Fm + +#endif // TERMINAL_H diff --git a/src/core/thumbnailer.cpp b/src/core/thumbnailer.cpp new file mode 100644 index 0000000..8aedd75 --- /dev/null +++ b/src/core/thumbnailer.cpp @@ -0,0 +1,140 @@ +#include "thumbnailer.h" +#include "mimetype.h" +#include +#include +#include +#include + +namespace Fm { + +std::mutex Thumbnailer::mutex_; +std::vector> Thumbnailer::allThumbnailers_; + +Thumbnailer::Thumbnailer(const char* id, GKeyFile* kf): + id_{g_strdup(id)}, + try_exec_{g_key_file_get_string(kf, "Thumbnailer Entry", "TryExec", nullptr)}, + exec_{g_key_file_get_string(kf, "Thumbnailer Entry", "Exec", nullptr)} { +} + +CStrPtr Thumbnailer::commandForUri(const char* uri, const char* output_file, guint size) const { + if(exec_) { + /* FIXME: how to handle TryExec? */ + + /* parse the command line and do required substitutions according to: + * http://developer.gnome.org/integration-guide/stable/thumbnailer.html.en + */ + GString* cmd_line = g_string_sized_new(1024); + const char* p; + for(p = exec_.get(); *p; ++p) { + if(G_LIKELY(*p != '%')) { + g_string_append_c(cmd_line, *p); + } + else { + char* quoted; + ++p; + switch(*p) { + case '\0': + break; + case 's': + g_string_append_printf(cmd_line, "%d", size); + break; + case 'i': { + char* src_path = g_filename_from_uri(uri, nullptr, nullptr); + if(src_path) { + quoted = g_shell_quote(src_path); + g_string_append(cmd_line, quoted); + g_free(quoted); + g_free(src_path); + } + break; + } + case 'u': + quoted = g_shell_quote(uri); + g_string_append(cmd_line, quoted); + g_free(quoted); + break; + case 'o': + g_string_append(cmd_line, output_file); + break; + default: + g_string_append_c(cmd_line, '%'); + if(*p != '%') { + g_string_append_c(cmd_line, *p); + } + } + } + } + return CStrPtr{g_string_free(cmd_line, FALSE)}; + } + return nullptr; +} + +bool Thumbnailer::run(const char* uri, const char* output_file, int size) const { + auto cmd = commandForUri(uri, output_file, size); + qDebug() << cmd.get(); + int status; + bool ret = g_spawn_command_line_sync(cmd.get(), nullptr, nullptr, &status, nullptr); + return ret && status == 0; +} + +static void find_thumbnailers_in_data_dir(std::unordered_map& hash, const char* data_dir) { + CStrPtr dir_path{g_build_filename(data_dir, "thumbnailers", nullptr)}; + GDir* dir = g_dir_open(dir_path.get(), 0, nullptr); + if(dir) { + const char* basename; + while((basename = g_dir_read_name(dir)) != nullptr) { + /* we only want filenames with .thumbnailer extension */ + if(G_LIKELY(g_str_has_suffix(basename, ".thumbnailer"))) { + hash.insert(std::make_pair(basename, data_dir)); + } + } + g_dir_close(dir); + } +} + +void Thumbnailer::loadAll() { + const gchar* const* data_dirs = g_get_system_data_dirs(); + const gchar* const* data_dir; + + /* use a temporary hash table to collect thumbnailer basenames + * key: basename of thumbnailer entry file + * value: data dir the thumbnailer entry file is in */ + std::unordered_map hash; + + /* load user-specific thumbnailers */ + find_thumbnailers_in_data_dir(hash, g_get_user_data_dir()); + + /* load system-wide thumbnailers */ + for(data_dir = data_dirs; *data_dir; ++data_dir) { + find_thumbnailers_in_data_dir(hash, *data_dir); + } + + /* load all found thumbnailers */ + if(!hash.empty()) { + std::lock_guard lock{mutex_}; + GKeyFile* kf = g_key_file_new(); + for(auto& item: hash) { + auto& base_name = item.first; + auto& dir_path = item.second; + CStrPtr file_path{g_build_filename(dir_path, "thumbnailers", base_name.c_str(), nullptr)}; + if(g_key_file_load_from_file(kf, file_path.get(), G_KEY_FILE_NONE, nullptr)) { + auto thumbnailer = std::make_shared(base_name.c_str(), kf); + char** mime_types = g_key_file_get_string_list(kf, "Thumbnailer Entry", "MimeType", nullptr, nullptr); + if(mime_types && thumbnailer->exec_) { + for(char** name = mime_types; *name; ++name) { + auto mime_type = MimeType::fromName(*name); + if(mime_type) { + thumbnailer->mimeTypes_.push_back(mime_type); + std::const_pointer_cast(mime_type)->addThumbnailer(thumbnailer); + } + } + g_strfreev(mime_types); + } + allThumbnailers_.push_back(std::move(thumbnailer)); + } + } + g_key_file_free(kf); + } +} + +} // namespace Fm diff --git a/src/core/thumbnailer.h b/src/core/thumbnailer.h new file mode 100644 index 0000000..c1a7328 --- /dev/null +++ b/src/core/thumbnailer.h @@ -0,0 +1,37 @@ +#ifndef FM2_THUMBNAILER_H +#define FM2_THUMBNAILER_H + +#include "../libfmqtglobals.h" +#include "cstrptr.h" +#include +#include +#include +#include + +namespace Fm { + +class MimeType; + +class LIBFM_QT_API Thumbnailer { +public: + explicit Thumbnailer(const char *id, GKeyFile *kf); + + CStrPtr commandForUri(const char* uri, const char* output_file, guint size) const; + + bool run(const char* uri, const char* output_file, int size) const; + + static void loadAll(); + +private: + CStrPtr id_; + CStrPtr try_exec_; /* FIXME: is this useful? */ + CStrPtr exec_; + std::vector> mimeTypes_; + + static std::mutex mutex_; + static std::vector> allThumbnailers_; +}; + +} // namespace Fm + +#endif // FM2_THUMBNAILER_H diff --git a/src/core/thumbnailjob.cpp b/src/core/thumbnailjob.cpp new file mode 100644 index 0000000..61eccff --- /dev/null +++ b/src/core/thumbnailjob.cpp @@ -0,0 +1,266 @@ +#include "thumbnailjob.h" +#include +#include +#include +#include +#include +#include +#include "thumbnailer.h" + +namespace Fm { + +QThreadPool* ThumbnailJob::threadPool_ = nullptr; + +bool ThumbnailJob::localFilesOnly_ = true; +int ThumbnailJob::maxThumbnailFileSize_ = 0; + +ThumbnailJob::ThumbnailJob(FileInfoList files, int size): + files_{std::move(files)}, + size_{size}, + md5Calc_{g_checksum_new(G_CHECKSUM_MD5)} { +} + +ThumbnailJob::~ThumbnailJob() { + g_checksum_free(md5Calc_); + // qDebug("delete ThumbnailJob"); +} + +void ThumbnailJob::exec() { + for(auto& file: files_) { + if(isCancelled()) { + break; + } + auto image = loadForFile(file); + Q_EMIT thumbnailLoaded(file, size_, image); + results_.emplace_back(std::move(image)); + } +} + +QImage ThumbnailJob::readImageFromStream(GInputStream* stream, size_t len) { + // FIXME: should we set a limit here? Otherwise if len is too large, we can run out of memory. + std::unique_ptr buffer{new unsigned char[len]}; // allocate enough buffer + unsigned char* pbuffer = buffer.get(); + size_t totalReadSize = 0; + while(!isCancelled() && totalReadSize < len) { + size_t bytesToRead = totalReadSize + 4096 > len ? len - totalReadSize : 4096; + gssize readSize = g_input_stream_read(stream, pbuffer, bytesToRead, cancellable_.get(), nullptr); + if(readSize == 0) { // end of file + break; + } + else if(readSize == -1) { // error + return QImage(); + } + totalReadSize += readSize; + pbuffer += readSize; + } + QImage image; + image.loadFromData(buffer.get(), totalReadSize); + return image; +} + +QImage ThumbnailJob::loadForFile(const std::shared_ptr &file) { + if(!file->canThumbnail()) { + return QImage(); + } + + // thumbnails are stored in $XDG_CACHE_HOME/thumbnails/large|normal|failed + QString thumbnailDir{g_get_user_cache_dir()}; + thumbnailDir += "/thumbnails/"; + + // don't make thumbnails for files inside the thumbnail directory + if(FilePath::fromLocalPath(thumbnailDir.toLocal8Bit().constData()).isParentOf(file->dirPath())) { + return QImage(); + } + + const char* subdir = size_ > 128 ? "large" : "normal"; + thumbnailDir += subdir; + + // generate base name of the thumbnail => {md5 of uri}.png + auto origPath = file->path(); + auto uri = origPath.uri(); + + char thumbnailName[32 + 5]; + // calculate md5 hash for the uri of the original file + g_checksum_update(md5Calc_, reinterpret_cast(uri.get()), -1); + memcpy(thumbnailName, g_checksum_get_string(md5Calc_), 32); + mempcpy(thumbnailName + 32, ".png", 5); + g_checksum_reset(md5Calc_); // reset the checksum calculator for next use + + QString thumbnailFilename = thumbnailDir; + thumbnailFilename += '/'; + thumbnailFilename += thumbnailName; + // qDebug() << "thumbnail:" << file->getName().c_str() << thumbnailFilename; + + // try to load the thumbnail file if it exists + QImage thumbnail{thumbnailFilename}; + if(thumbnail.isNull() || isThumbnailOutdated(file, thumbnail)) { + // the existing thumbnail cannot be loaded, generate a new one + + // create the thumbnail dir as needd (FIXME: Qt file I/O is slow) + QDir().mkpath(thumbnailDir); + + thumbnail = generateThumbnail(file, origPath, uri.get(), thumbnailFilename); + } + // resize to the size we need + if(thumbnail.width() > size_ || thumbnail.height() > size_) { + thumbnail = thumbnail.scaled(size_, size_, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + return thumbnail; +} + +bool ThumbnailJob::isSupportedImageType(const std::shared_ptr& mimeType) const { + if(mimeType->isImage()) { + auto supportedTypes = QImageReader::supportedMimeTypes(); + auto found = std::find(supportedTypes.cbegin(), supportedTypes.cend(), mimeType->name()); + if(found != supportedTypes.cend()) + return true; + } + return false; +} + +bool ThumbnailJob::isThumbnailOutdated(const std::shared_ptr& file, const QImage &thumbnail) const { + QString thumb_mtime = thumbnail.text("Thumb::MTime"); + return (thumb_mtime.isEmpty() || thumb_mtime.toInt() != file->mtime()); +} + +bool ThumbnailJob::readJpegExif(GInputStream *stream, QImage& thumbnail, int& rotate_degrees) { + /* try to extract thumbnails embedded in jpeg files */ + ExifLoader* exif_loader = exif_loader_new(); + while(!isCancelled()) { + unsigned char buf[4096]; + gssize read_size = g_input_stream_read(stream, buf, 4096, cancellable_.get(), nullptr); + if(read_size <= 0) { // EOF or error + break; + } + if(exif_loader_write(exif_loader, buf, read_size) == 0) { + break; // no more EXIF data + } + } + ExifData* exif_data = exif_loader_get_data(exif_loader); + exif_loader_unref(exif_loader); + if(exif_data) { + /* reference for EXIF orientation tag: + * http://www.impulseadventure.com/photo/exif-orientation.html */ + ExifEntry* orient_ent = exif_data_get_entry(exif_data, EXIF_TAG_ORIENTATION); + if(orient_ent) { /* orientation flag found in EXIF */ + gushort orient; + ExifByteOrder bo = exif_data_get_byte_order(exif_data); + /* bo == EXIF_BYTE_ORDER_INTEL ; */ + orient = exif_get_short(orient_ent->data, bo); + switch(orient) { + case 1: /* no rotation */ + rotate_degrees = 0; + break; + case 8: + rotate_degrees = 90; + break; + case 3: + rotate_degrees = 180; + break; + case 6: + rotate_degrees = 270; + break; + } + } + if(exif_data->data) { // if an embedded thumbnail is available, load it + thumbnail.loadFromData(exif_data->data, exif_data->size); + } + exif_data_unref(exif_data); + } + return !thumbnail.isNull(); +} + +QImage ThumbnailJob::generateThumbnail(const std::shared_ptr& file, const FilePath& origPath, const char* uri, const QString& thumbnailFilename) { + QImage result; + auto mime_type = file->mimeType(); + if(isSupportedImageType(mime_type)) { + GFileInputStreamPtr ins{g_file_read(origPath.gfile().get(), cancellable_.get(), nullptr), false}; + if(!ins) + return QImage(); + bool fromExif = false; + int rotate_degrees = 0; + if(strcmp(mime_type->name(), "image/jpeg") == 0) { // if this is a jpeg file + // try to get the thumbnail embedded in EXIF data + if(readJpegExif(G_INPUT_STREAM(ins.get()), result, rotate_degrees)) { + fromExif = true; + } + } + if(!fromExif) { // not able to generate a thumbnail from the EXIF data + // load the original file and do the scaling ourselves + g_seekable_seek(G_SEEKABLE(ins.get()), 0, G_SEEK_SET, cancellable_.get(), nullptr); + result = readImageFromStream(G_INPUT_STREAM(ins.get()), file->size()); + } + g_input_stream_close(G_INPUT_STREAM(ins.get()), nullptr, nullptr); + + if(!result.isNull()) { // the image is successfully loaded + // scale the image as needed + int target_size = size_ > 128 ? 256 : 128; + + // only scale the original image if it's too large + if(result.width() > target_size || result.height() > target_size) { + result = result.scaled(target_size, target_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + + if(rotate_degrees != 0) { + // degree values are 0, 90, 180, and 270 counterclockwise. + // In Qt, QMatrix does rotation counterclockwise as well. + // However, because the y axis of widget coordinate system is downward, + // the real effect of the coordinate transformation becomes clockwise rotation. + // So we need to use (360 - degree) here. + // Quote from QMatrix API doc: + // Note that if you apply a QMatrix to a point defined in widget + // coordinates, the direction of the rotation will be clockwise because + // the y-axis points downwards. + result = result.transformed(QMatrix().rotate(360 - rotate_degrees)); + } + + // save the generated thumbnail to disk (don't save png thumbnails for JPEG EXIF thumbnails since loading them is cheap) + if(!fromExif) { + result.setText("Thumb::MTime", QString::number(file->mtime())); + result.setText("Thumb::URI", uri); + result.save(thumbnailFilename, "PNG"); + } + // qDebug() << "save thumbnail:" << thumbnailFilename; + } + } + else { // the image format is not supported, try to find an external thumbnailer + // try all available external thumbnailers for it until sucess + int target_size = size_ > 128 ? 256 : 128; + file->mimeType()->forEachThumbnailer([&](const std::shared_ptr& thumbnailer) { + if(thumbnailer->run(uri, thumbnailFilename.toLocal8Bit().constData(), target_size)) { + result = QImage(thumbnailFilename); + } + return !result.isNull(); // return true on success, and forEachThumbnailer() will stop. + }); + + if(!result.isNull()) { + // Some thumbnailers did not write the proper metadata required by the xdg spec to the output (such as evince-thumbnailer) + // Here we waste some time to fix them so next time we don't need to re-generate these thumbnails. :-( + bool changed = false; + if(Q_UNLIKELY(result.text("Thumb::MTime").isEmpty())) { + result.setText("Thumb::MTime", QString::number(file->mtime())); + changed = true; + } + if(Q_UNLIKELY(result.text("Thumb::URI").isEmpty())) { + result.setText("Thumb::URI", uri); + changed = true; + } + if(Q_UNLIKELY(changed)) { + // save the modified PNG file containing metadata to a file. + result.save(thumbnailFilename, "PNG"); + } + } + } + return result; +} + +QThreadPool* ThumbnailJob::threadPool() { + if(Q_UNLIKELY(threadPool_ == nullptr)) { + threadPool_ = new QThreadPool(); + threadPool_->setMaxThreadCount(1); + } + return threadPool_; +} + + +} // namespace Fm diff --git a/src/core/thumbnailjob.h b/src/core/thumbnailjob.h new file mode 100644 index 0000000..33dbe0a --- /dev/null +++ b/src/core/thumbnailjob.h @@ -0,0 +1,89 @@ +#ifndef FM2_THUMBNAILJOB_H +#define FM2_THUMBNAILJOB_H + +#include "../libfmqtglobals.h" +#include "fileinfo.h" +#include "gioptrs.h" +#include "job.h" +#include + +namespace Fm { + +class LIBFM_QT_API ThumbnailJob: public Job { + Q_OBJECT +public: + + explicit ThumbnailJob(FileInfoList files, int size); + + ~ThumbnailJob(); + + int size() const { + return size_; + } + + static QThreadPool* threadPool(); + + + static void setLocalFilesOnly(bool value) { + localFilesOnly_ = value; + if(fm_config) { + fm_config->thumbnail_local = localFilesOnly_; + } + } + + static bool localFilesOnly() { + return localFilesOnly_; + } + + static int maxThumbnailFileSize() { + return maxThumbnailFileSize_; + } + + static void setMaxThumbnailFileSize(int size) { + maxThumbnailFileSize_ = size; + if(fm_config) { + fm_config->thumbnail_max = maxThumbnailFileSize_; + } + } + + const std::vector& results() const { + return results_; + } + +Q_SIGNALS: + void thumbnailLoaded(const std::shared_ptr& file, int size, QImage thumbnail); + +protected: + + void exec() override; + +private: + + bool isSupportedImageType(const std::shared_ptr& mimeType) const; + + bool isThumbnailOutdated(const std::shared_ptr& file, const QImage& thumbnail) const; + + QImage generateThumbnail(const std::shared_ptr& file, const FilePath& origPath, const char* uri, const QString& thumbnailFilename); + + QImage readImageFromStream(GInputStream* stream, size_t len); + + QImage loadForFile(const std::shared_ptr& file); + + bool readJpegExif(GInputStream* stream, QImage& thumbnail, int& rotate_degrees); + +private: + FileInfoList files_; + int size_; + std::vector results_; + GCancellablePtr cancellable_; + GChecksum* md5Calc_; + + static QThreadPool* threadPool_; + + static bool localFilesOnly_; + static int maxThumbnailFileSize_; +}; + +} // namespace Fm + +#endif // FM2_THUMBNAILJOB_H diff --git a/src/core/totalsizejob.cpp b/src/core/totalsizejob.cpp new file mode 100644 index 0000000..81ff0db --- /dev/null +++ b/src/core/totalsizejob.cpp @@ -0,0 +1,144 @@ +#include "totalsizejob.h" + +namespace Fm { + +static const char query_str[] = + G_FILE_ATTRIBUTE_STANDARD_TYPE"," + G_FILE_ATTRIBUTE_STANDARD_NAME"," + G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL"," + G_FILE_ATTRIBUTE_STANDARD_SIZE"," + G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE"," + G_FILE_ATTRIBUTE_ID_FILESYSTEM; + + +TotalSizeJob::TotalSizeJob(FilePathList paths, Flags flags): + paths_{std::move(paths)}, + flags_{flags}, + totalSize_{0}, + totalOndiskSize_{0}, + fileCount_{0}, + dest_fs_id{nullptr} { +} + + +void TotalSizeJob::exec(FilePath path, GFileInfoPtr inf) { + GFileType type; + const char* fs_id; + bool descend; + +_retry_query_info: + if(!inf) { + GErrorPtr err; + inf = GFileInfoPtr { + g_file_query_info(path.gfile().get(), query_str, + (flags_ & FOLLOW_LINKS) ? G_FILE_QUERY_INFO_NONE : G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable().get(), &err), + false + }; + if(!inf) { + ErrorAction act = emitError( err, ErrorSeverity::MILD); + err = nullptr; + if(act == ErrorAction::RETRY) { + goto _retry_query_info; + } + return; + } + } + if(isCancelled()) { + return; + } + + type = g_file_info_get_file_type(inf.get()); + descend = true; + + ++fileCount_; + /* SF bug #892: dir file size is not relevant in the summary */ + if(type != G_FILE_TYPE_DIRECTORY) { + totalSize_ += g_file_info_get_size(inf.get()); + } + totalOndiskSize_ += g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE); + + /* prepare for moving across different devices */ + if(flags_ & PREPARE_MOVE) { + fs_id = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_ID_FILESYSTEM); + fs_id = g_intern_string(fs_id); + if(g_strcmp0(fs_id, dest_fs_id) != 0) { + /* files on different device requires an additional 'delete' for the source file. */ + ++totalSize_; /* this is for the additional delete */ + ++totalOndiskSize_; + ++fileCount_; + } + else { + descend = false; + } + } + + if(type == G_FILE_TYPE_DIRECTORY) { +#if 0 + FmPath* fm_path = fm_path_new_for_gfile(gf); + /* check if we need to decends into the dir. */ + /* trash:/// doesn't support deleting files recursively */ + if(flags & PREPARE_DELETE && fm_path_is_trash(fm_path) && ! fm_path_is_trash_root(fm_path)) { + descend = false; + } + else { + /* only descends into files on the same filesystem */ + if(flags & FM_DC_JOB_SAME_FS) { + fs_id = g_file_info_get_attribute_string(inf, G_FILE_ATTRIBUTE_ID_FILESYSTEM); + descend = (g_strcmp0(fs_id, dest_fs_id) == 0); + } + } + fm_path_unref(fm_path); +#endif + inf = nullptr; + + if(descend) { +_retry_enum_children: + GErrorPtr err; + auto enu = GFileEnumeratorPtr { + g_file_enumerate_children(path.gfile().get(), query_str, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable().get(), &err), + false + }; + if(enu) { + while(!isCancelled()) { + inf = GFileInfoPtr{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false}; + if(inf) { + FilePath child = path.child(g_file_info_get_name(inf.get())); + exec(std::move(child), std::move(inf)); + } + else { + if(err) { /* error! */ + /* ErrorAction::RETRY is not supported */ + emitError( err, ErrorSeverity::MILD); + err = nullptr; + } + else { + /* EOF is reached, do nothing. */ + break; + } + } + } + g_file_enumerator_close(enu.get(), nullptr, nullptr); + } + else { + ErrorAction act = emitError( err, ErrorSeverity::MILD); + err = nullptr; + if(act == ErrorAction::RETRY) { + goto _retry_enum_children; + } + } + } + } +} + + +void TotalSizeJob::exec() { + for(auto& path : paths_) { + exec(path, GFileInfoPtr{}); + } +} + + +} // namespace Fm diff --git a/src/core/totalsizejob.h b/src/core/totalsizejob.h new file mode 100644 index 0000000..43c19c0 --- /dev/null +++ b/src/core/totalsizejob.h @@ -0,0 +1,56 @@ +#ifndef FM2_TOTALSIZEJOB_H +#define FM2_TOTALSIZEJOB_H + +#include "../libfmqtglobals.h" +#include "fileoperationjob.h" +#include "filepath.h" +#include +#include "gioptrs.h" + +namespace Fm { + +class LIBFM_QT_API TotalSizeJob : public Fm::FileOperationJob { + Q_OBJECT +public: + enum Flags { + DEFAULT = 0, + FOLLOW_LINKS = 1 << 0, + SAME_FS = 1 << 1, + PREPARE_MOVE = 1 << 2, + PREPARE_DELETE = 1 << 3 + }; + + explicit TotalSizeJob(FilePathList paths = FilePathList{}, Flags flags = DEFAULT); + + std::uint64_t totalSize() const { + return totalSize_; + } + + std::uint64_t totalOnDiskSize() const { + return totalOndiskSize_; + } + + unsigned int fileCount() const { + return fileCount_; + } + +protected: + + void exec() override; + +private: + void exec(FilePath path, GFileInfoPtr inf); + +private: + FilePathList paths_; + + int flags_; + std::uint64_t totalSize_; + std::uint64_t totalOndiskSize_; + unsigned int fileCount_; + const char* dest_fs_id; +}; + +} // namespace Fm + +#endif // FM2_TOTALSIZEJOB_H diff --git a/src/core/trashjob.cpp b/src/core/trashjob.cpp new file mode 100644 index 0000000..31a5c3c --- /dev/null +++ b/src/core/trashjob.cpp @@ -0,0 +1,73 @@ +#include "trashjob.h" + +namespace Fm { + +TrashJob::TrashJob(const FilePathList& paths): paths_{paths} { +} + +TrashJob::TrashJob(const FilePathList&& paths): paths_{paths} { +} + +void TrashJob::exec() { + setTotalAmount(paths_.size(), paths_.size()); + Q_EMIT preparedToRun(); + + /* FIXME: we shouldn't trash a file already in trash:/// */ + for(auto& path : paths_) { + if(isCancelled()) { + break; + } + + setCurrentFile(path); + + for(;;) { + GErrorPtr err; + GFile* gf = path.gfile().get(); + GFileInfoPtr inf{ + g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, G_FILE_QUERY_INFO_NONE, + cancellable().get(), &err), + false + }; + + bool ret = FALSE; + if(fm_config->no_usb_trash) { + err.reset(); + GMountPtr mnt{g_file_find_enclosing_mount(gf, nullptr, &err), false}; + if(mnt) { + ret = g_mount_can_unmount(mnt.get()); /* TRUE if it's removable media */ + if(ret) { + unsupportedFiles_.push_back(path); + } + } + } + + if(!ret) { + err.reset(); + ret = g_file_trash(gf, cancellable().get(), &err); + } + if(!ret) { + /* if trashing is not supported by the file system */ + if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_NOT_SUPPORTED) { + unsupportedFiles_.push_back(path); + } + else { + ErrorAction act = emitError(err, ErrorSeverity::MODERATE); + if(act == ErrorAction::RETRY) { + err.reset(); + } + else if(act == ErrorAction::ABORT) { + cancel(); + return; + } + else { + break; + } + } + } + } + addFinishedAmount(1, 1); + } +} + + +} // namespace Fm diff --git a/src/core/trashjob.h b/src/core/trashjob.h new file mode 100644 index 0000000..519fdd8 --- /dev/null +++ b/src/core/trashjob.h @@ -0,0 +1,31 @@ +#ifndef FM2_TRASHJOB_H +#define FM2_TRASHJOB_H + +#include "../libfmqtglobals.h" +#include "fileoperationjob.h" +#include "filepath.h" + +namespace Fm { + +class LIBFM_QT_API TrashJob : public Fm::FileOperationJob { + Q_OBJECT +public: + explicit TrashJob(const FilePathList& paths); + explicit TrashJob(const FilePathList&& paths); + + FilePathList unsupportedFiles() const { + return unsupportedFiles_; + } + +protected: + + void exec() override; + +private: + FilePathList paths_; + FilePathList unsupportedFiles_; +}; + +} // namespace Fm + +#endif // FM2_TRASHJOB_H diff --git a/src/core/untrashjob.cpp b/src/core/untrashjob.cpp new file mode 100644 index 0000000..14148af --- /dev/null +++ b/src/core/untrashjob.cpp @@ -0,0 +1,132 @@ +#include "untrashjob.h" + +namespace Fm { + +UntrashJob::UntrashJob() { + +} + +static const char trash_query[] = + G_FILE_ATTRIBUTE_STANDARD_TYPE"," + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME"," + G_FILE_ATTRIBUTE_STANDARD_NAME"," + G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL"," + G_FILE_ATTRIBUTE_STANDARD_SIZE"," + G_FILE_ATTRIBUTE_UNIX_BLOCKS"," + G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE"," + G_FILE_ATTRIBUTE_ID_FILESYSTEM"," + "trash::orig-path"; + +bool UntrashJob::ensure_parent_dir(GFile* orig_path) { + GFile* parent = g_file_get_parent(orig_path); + gboolean ret = g_file_query_exists(parent, cancellable().get()); + if(!ret) { + GErrorPtr err; +_retry_mkdir: + if(!g_file_make_directory_with_parents(parent, cancellable().get(), &err)) { + if(!isCancelled()) { + ErrorAction act = emitError(err, ErrorSeverity::MODERATE); + err = nullptr; + if(act == ErrorAction::RETRY) { + goto _retry_mkdir; + } + } + } + else { + ret = TRUE; + } + } + g_object_unref(parent); + return ret; +} + + +void UntrashJob::exec() { +#if 0 + gboolean ret = TRUE; + GList* l; + GError* err = nullptr; + FmJob* fmjob = FM_JOB(job); + job->total = fm_path_list_get_length(job->srcs); + fm_file_ops_job_emit_prepared(job); + + l = fm_path_list_peek_head_link(job->srcs); + for(; !fm_job_is_cancelled(fmjob) && l; l = l->next) { + GFile* gf; + GFileInfo* inf; + FmPath* path = FM_PATH(l->data); + if(!fm_path_is_trash(path)) { + continue; + } + gf = fm_path_to_gfile(path); +_retry_get_orig_path: + inf = g_file_query_info(gf, trash_query, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, fm_job_get_cancellable(fmjob), &err); + if(inf) { + const char* orig_path_str = g_file_info_get_attribute_byte_string(inf, "trash::orig-path"); + fm_file_ops_job_emit_cur_file(job, g_file_info_get_display_name(inf)); + + if(orig_path_str) { + /* FIXME: what if orig_path_str is a relative path? + * This is actually allowed by the horrible trash spec. */ + GFile* orig_path = fm_file_new_for_commandline_arg(orig_path_str); + FmFolder* src_folder = fm_folder_find_by_path(fm_path_get_parent(path)); + FmPath* orig_fm_path = fm_path_new_for_gfile(orig_path); + FmFolder* dst_folder = fm_folder_find_by_path(fm_path_get_parent(orig_fm_path)); + fm_path_unref(orig_fm_path); + /* ensure the existence of parent folder. */ + if(ensure_parent_dir(fmjob, orig_path)) { + ret = _fm_file_ops_job_move_file(job, gf, inf, orig_path, path, src_folder, dst_folder); + } + if(src_folder) { + g_object_unref(src_folder); + } + if(dst_folder) { + g_object_unref(dst_folder); + } + g_object_unref(orig_path); + } + else { + ErrorAction act; + + g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED, + _("Cannot untrash file '%s': original path not known"), + g_file_info_get_display_name(inf)); + act = emitError( err, ErrorSeverity::MODERATE); + g_clear_error(&err); + if(act == ErrorAction::ABORT) { + g_object_unref(inf); + g_object_unref(gf); + return FALSE; + } + } + g_object_unref(inf); + } + else { + char* basename = g_file_get_basename(gf); + char* disp = basename ? g_filename_display_name(basename) : nullptr; + g_free(basename); + /* FIXME: translate it */ + fm_file_ops_job_emit_cur_file(job, disp ? disp : "(invalid file)"); + g_free(disp); + + if(err) { + ErrorAction act = emitError( err, ErrorSeverity::MODERATE); + g_error_free(err); + err = nullptr; + if(act == ErrorAction::RETRY) { + goto _retry_get_orig_path; + } + else if(act == ErrorAction::ABORT) { + g_object_unref(gf); + return FALSE; + } + } + } + g_object_unref(gf); + ++job->finished; + fm_file_ops_job_emit_percent(job); + } +#endif +} + +} // namespace Fm diff --git a/src/core/untrashjob.h b/src/core/untrashjob.h new file mode 100644 index 0000000..1574abd --- /dev/null +++ b/src/core/untrashjob.h @@ -0,0 +1,22 @@ +#ifndef FM2_UNTRASHJOB_H +#define FM2_UNTRASHJOB_H + +#include "../libfmqtglobals.h" +#include "fileoperationjob.h" + +namespace Fm { + +class LIBFM_QT_API UntrashJob : public Fm::FileOperationJob { +public: + explicit UntrashJob(); + +protected: + void exec() override; + +private: + bool ensure_parent_dir(GFile *orig_path); +}; + +} // namespace Fm + +#endif // FM2_UNTRASHJOB_H diff --git a/src/core/userinfocache.cpp b/src/core/userinfocache.cpp new file mode 100644 index 0000000..1c237f0 --- /dev/null +++ b/src/core/userinfocache.cpp @@ -0,0 +1,47 @@ +#include "userinfocache.h" +#include +#include + +namespace Fm { + +UserInfoCache* UserInfoCache::globalInstance_ = nullptr; +std::mutex UserInfoCache::mutex_; + +UserInfoCache::UserInfoCache() : QObject() { +} + +const std::shared_ptr& UserInfoCache::userFromId(uid_t uid) { + std::lock_guard lock{mutex_}; + auto it = users_.find(uid); + if(it != users_.end()) + return it->second; + std::shared_ptr user; + auto pw = getpwuid(uid); + if(pw) { + user = std::make_shared(uid, pw->pw_name, pw->pw_gecos); + } + return (users_[uid] = user); +} + +const std::shared_ptr& UserInfoCache::groupFromId(gid_t gid) { + std::lock_guard lock{mutex_}; + auto it = groups_.find(gid); + if(it != groups_.end()) + return it->second; + std::shared_ptr group; + auto gr = getgrgid(gid); + if(gr) { + group = std::make_shared(gid, gr->gr_name); + } + return (groups_[gid] = group); +} + +// static +UserInfoCache* UserInfoCache::globalInstance() { + std::lock_guard lock{mutex_}; + if(!globalInstance_) + globalInstance_ = new UserInfoCache(); + return globalInstance_; +} + +} // namespace Fm diff --git a/src/core/userinfocache.h b/src/core/userinfocache.h new file mode 100644 index 0000000..7338463 --- /dev/null +++ b/src/core/userinfocache.h @@ -0,0 +1,82 @@ +#ifndef FM2_USERINFOCACHE_H +#define FM2_USERINFOCACHE_H + +#include "../libfmqtglobals.h" +#include +#include +#include +#include +#include +#include + +namespace Fm { + +class LIBFM_QT_API UserInfo { +public: + explicit UserInfo(uid_t uid, const char* name, const char* realName): + uid_{uid}, name_{name}, realName_{realName} { + } + + uid_t uid() const { + return uid_; + } + + const QString& name() const { + return name_; + } + + const QString& realName() const { + return realName_; + } + +private: + uid_t uid_; + QString name_; + QString realName_; + +}; + +class LIBFM_QT_API GroupInfo { +public: + explicit GroupInfo(gid_t gid, const char* name): gid_{gid}, name_{name} { + } + + gid_t gid() const { + return gid_; + } + + const QString& name() const { + return name_; + } + +private: + gid_t gid_; + QString name_; +}; + +// FIXME: handle file changes + +class LIBFM_QT_API UserInfoCache : public QObject { + Q_OBJECT +public: + explicit UserInfoCache(); + + const std::shared_ptr& userFromId(uid_t uid); + + const std::shared_ptr& groupFromId(gid_t gid); + + static UserInfoCache* globalInstance(); + +Q_SIGNALS: + void changed(); + +private: + std::unordered_map> users_; + std::unordered_map> groups_; + static UserInfoCache* globalInstance_; + static std::mutex mutex_; +}; + +} // namespace Fm + +#endif // FM2_USERINFOCACHE_H diff --git a/src/core/volumemanager.cpp b/src/core/volumemanager.cpp new file mode 100644 index 0000000..522c38c --- /dev/null +++ b/src/core/volumemanager.cpp @@ -0,0 +1,111 @@ +#include "volumemanager.h" + +namespace Fm { + +std::mutex VolumeManager::mutex_; +std::weak_ptr VolumeManager::globalInstance_; + +VolumeManager::VolumeManager(): + QObject(), + monitor_{g_volume_monitor_get(), false} { + + // connect gobject signal handlers + g_signal_connect(monitor_.get(), "volume-added", G_CALLBACK(_onGVolumeAdded), this); + g_signal_connect(monitor_.get(), "volume-removed", G_CALLBACK(_onGVolumeRemoved), this); + g_signal_connect(monitor_.get(), "volume-changed", G_CALLBACK(_onGVolumeChanged), this); + + g_signal_connect(monitor_.get(), "mount-added", G_CALLBACK(_onGMountAdded), this); + g_signal_connect(monitor_.get(), "mount-removed", G_CALLBACK(_onGMountRemoved), this); + g_signal_connect(monitor_.get(), "mount-changed", G_CALLBACK(_onGMountChanged), this); + + // g_get_volume_monitor() is a slow blocking call, so call it in a low priority thread + auto job = new GetGVolumeMonitorJob(); + job->setAutoDelete(true); + connect(job, &GetGVolumeMonitorJob::finished, this, &VolumeManager::onGetGVolumeMonitorFinished, Qt::BlockingQueuedConnection); + job->runAsync(QThread::LowPriority); +} + +VolumeManager::~VolumeManager() { + if(monitor_) { + g_signal_handlers_disconnect_by_data(monitor_.get(), this); + } +} + +std::shared_ptr VolumeManager::globalInstance() { + std::lock_guard lock{mutex_}; + auto mon = globalInstance_.lock(); + if(mon == nullptr) { + mon = std::make_shared(); + globalInstance_ = mon; + } + return mon; +} + +void VolumeManager::onGetGVolumeMonitorFinished() { + auto job = static_cast(sender()); + monitor_ = std::move(job->monitor_); + GList* vols = g_volume_monitor_get_volumes(monitor_.get()); + for(GList* l = vols; l != nullptr; l = l->next) { + volumes_.push_back(Volume{G_VOLUME(l->data), false}); + Q_EMIT volumeAdded(volumes_.back()); + } + g_list_free(vols); + + GList* mnts = g_volume_monitor_get_mounts(monitor_.get()); + for(GList* l = mnts; l != nullptr; l = l->next) { + mounts_.push_back(Mount{G_MOUNT(l->data), false}); + Q_EMIT mountAdded(mounts_.back()); + } + g_list_free(mnts); +} + +void VolumeManager::onGVolumeAdded(GVolume* vol) { + if(std::find(volumes_.cbegin(), volumes_.cend(), vol) != volumes_.cend()) + return; + volumes_.push_back(Volume{vol, true}); + Q_EMIT volumeAdded(volumes_.back()); +} + +void VolumeManager::onGVolumeRemoved(GVolume* vol) { + auto it = std::find(volumes_.begin(), volumes_.end(), vol); + if(it == volumes_.end()) + return; + Q_EMIT volumeRemoved(*it); + volumes_.erase(it); +} + +void VolumeManager::onGVolumeChanged(GVolume* vol) { + auto it = std::find(volumes_.begin(), volumes_.end(), vol); + if(it == volumes_.end()) + return; + Q_EMIT volumeChanged(*it); +} + +void VolumeManager::onGMountAdded(GMount* mnt) { + if(std::find(mounts_.cbegin(), mounts_.cend(), mnt) != mounts_.cend()) + return; + mounts_.push_back(Mount{mnt, true}); + Q_EMIT mountAdded(mounts_.back()); +} + +void VolumeManager::onGMountRemoved(GMount* mnt) { + auto it = std::find(mounts_.begin(), mounts_.end(), mnt); + if(it == mounts_.end()) + return; + Q_EMIT mountRemoved(*it); + mounts_.erase(it); +} + +void VolumeManager::onGMountChanged(GMount* mnt) { + auto it = std::find(mounts_.begin(), mounts_.end(), mnt); + if(it == mounts_.end()) + return; + Q_EMIT mountChanged(*it); +} + +void VolumeManager::GetGVolumeMonitorJob::exec() { + monitor_ = GVolumeMonitorPtr{g_volume_monitor_get(), false}; +} + + +} // namespace Fm diff --git a/src/core/volumemanager.h b/src/core/volumemanager.h new file mode 100644 index 0000000..642adc8 --- /dev/null +++ b/src/core/volumemanager.h @@ -0,0 +1,237 @@ +#ifndef FM2_VOLUMEMANAGER_H +#define FM2_VOLUMEMANAGER_H + +#include "../libfmqtglobals.h" +#include +#include +#include "gioptrs.h" +#include "filepath.h" +#include "iconinfo.h" +#include "job.h" +#include +#include + +namespace Fm { + +class LIBFM_QT_API Volume: public GVolumePtr { +public: + + explicit Volume(GVolume* gvol, bool addRef): GVolumePtr{gvol, addRef} { + } + + explicit Volume(GVolumePtr gvol): GVolumePtr{std::move(gvol)} { + } + + CStrPtr name() const { + return CStrPtr{g_volume_get_name(get())}; + } + + CStrPtr uuid() const { + return CStrPtr{g_volume_get_uuid(get())}; + } + + std::shared_ptr icon() const { + return IconInfo::fromGIcon(GIconPtr{g_volume_get_icon(get()), false}); + } + + // GDrive * g_volume_get_drive(get()); + GMountPtr mount() const { + return GMountPtr{g_volume_get_mount(get()), false}; + } + + bool canMount() const { + return g_volume_can_mount(get()); + } + + bool shouldAutoMount() const { + return g_volume_should_automount(get()); + } + + FilePath activationRoot() const { + return FilePath{g_volume_get_activation_root(get()), false}; + } + + /* + void g_volume_mount(get()); + gboolean g_volume_mount_finish(get()); + */ + bool canEject() const { + return g_volume_can_eject(get()); + } + + /* + void g_volume_eject(get()); + gboolean g_volume_eject_finish(get()); + void g_volume_eject_with_operation(get()); + gboolean g_volume_eject_with_operation_finish(get()); + char ** g_volume_enumerate_identifiers(get()); + char * g_volume_get_identifier(get()); + const gchar * g_volume_get_sort_key(get()); + */ + + CStrPtr device() const { + return CStrPtr{g_volume_get_identifier(get(), G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE)}; + } + + CStrPtr label() const { + return CStrPtr{g_volume_get_identifier(get(), G_VOLUME_IDENTIFIER_KIND_LABEL)}; + } + +}; + + +class LIBFM_QT_API Mount: public GMountPtr { +public: + + explicit Mount(GMount* mnt, bool addRef): GMountPtr{mnt, addRef} { + } + + explicit Mount(GMountPtr gmnt): GMountPtr{std::move(gmnt)} { + } + + CStrPtr name() const { + return CStrPtr{g_mount_get_name(get())}; + } + + CStrPtr uuid() const { + return CStrPtr{g_mount_get_uuid(get())}; + } + + std::shared_ptr icon() const { + return IconInfo::fromGIcon(GIconPtr{g_mount_get_icon(get()), false}); + } + + // GIcon * g_mount_get_symbolic_icon(get()); + // GDrive * g_mount_get_drive(get()); + FilePath root() const { + return FilePath{g_mount_get_root(get()), false}; + } + + GVolumePtr volume() const { + return GVolumePtr{g_mount_get_volume(get()), false}; + } + + FilePath defaultLocation() const { + return FilePath{g_mount_get_default_location(get()), false}; + } + + bool canUnmount() const { + return g_mount_can_unmount(get()); + } + +/* + void g_mount_unmount(get()); + gboolean g_mount_unmount_finish(get()); + void g_mount_unmount_with_operation(get()); + gboolean g_mount_unmount_with_operation_finish(get()); + void g_mount_remount(get()); + gboolean g_mount_remount_finish(get()); +*/ + bool canEject() const { + return g_mount_can_eject(get()); + } + +/* + void g_mount_eject(get()); + gboolean g_mount_eject_finish(get()); + void g_mount_eject_with_operation(get()); + gboolean g_mount_eject_with_operation_finish(get()); +*/ + // void g_mount_guess_content_type(get()); + // gchar ** g_mount_guess_content_type_finish(get()); + // gchar ** g_mount_guess_content_type_sync(get()); + + bool isShadowed() const { + return g_mount_is_shadowed(get()); + } + + // void g_mount_shadow(get()); + // void g_mount_unshadow(get()); + // const gchar * g_mount_get_sort_key(get()); +}; + + + +class LIBFM_QT_API VolumeManager : public QObject { + Q_OBJECT +public: + explicit VolumeManager(); + + ~VolumeManager(); + + const std::vector& volumes() const { + return volumes_; + } + + const std::vector& mounts() const { + return mounts_; + } + + static std::shared_ptr globalInstance(); + +Q_SIGNALS: + void volumeAdded(const Volume& vol); + void volumeRemoved(const Volume& vol); + void volumeChanged(const Volume& vol); + + void mountAdded(const Mount& mnt); + void mountRemoved(const Mount& mnt); + void mountChanged(const Mount& mnt); + +public Q_SLOTS: + + void onGetGVolumeMonitorFinished(); + +private: + + class GetGVolumeMonitorJob: public Job { + public: + GetGVolumeMonitorJob() {} + GVolumeMonitorPtr monitor_; + protected: + void exec() override; + }; + + static void _onGVolumeAdded(GVolumeMonitor* /*mon*/, GVolume* vol, VolumeManager* _this) { + _this->onGVolumeAdded(vol); + } + void onGVolumeAdded(GVolume* vol); + + static void _onGVolumeRemoved(GVolumeMonitor* /*mon*/, GVolume* vol, VolumeManager* _this) { + _this->onGVolumeRemoved(vol); + } + void onGVolumeRemoved(GVolume* vol); + + static void _onGVolumeChanged(GVolumeMonitor* /*mon*/, GVolume* vol, VolumeManager* _this) { + _this->onGVolumeChanged(vol); + } + void onGVolumeChanged(GVolume* vol); + + static void _onGMountAdded(GVolumeMonitor* /*mon*/, GMount* mnt, VolumeManager* _this) { + _this->onGMountAdded(mnt); + } + void onGMountAdded(GMount* mnt); + + static void _onGMountRemoved(GVolumeMonitor* /*mon*/, GMount* mnt, VolumeManager* _this) { + _this->onGMountRemoved(mnt); + } + void onGMountRemoved(GMount* mnt); + + static void _onGMountChanged(GVolumeMonitor* /*mon*/, GMount* mnt, VolumeManager* _this) { + _this->onGMountChanged(mnt); + } + void onGMountChanged(GMount* mnt); + +private: + GVolumeMonitorPtr monitor_; + + std::vector volumes_; + std::vector mounts_; + + static std::mutex mutex_; + static std::weak_ptr globalInstance_; +}; + +} // namespace Fm + +#endif // FM2_VOLUMEMANAGER_H diff --git a/src/createnewmenu.cpp b/src/createnewmenu.cpp index 1597deb..9191201 100644 --- a/src/createnewmenu.cpp +++ b/src/createnewmenu.cpp @@ -21,70 +21,76 @@ #include "folderview.h" #include "icontheme.h" #include "utilities.h" +#include "core/iconinfo.h" namespace Fm { -CreateNewMenu::CreateNewMenu(QWidget* dialogParent, FmPath* dirPath, QWidget* parent): - QMenu(parent), dialogParent_(dialogParent), dirPath_(dirPath) { - QAction* action = new QAction(QIcon::fromTheme("folder-new"), tr("Folder"), this); - connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNewFolder); - addAction(action); +CreateNewMenu::CreateNewMenu(QWidget* dialogParent, Fm::FilePath dirPath, QWidget* parent): + QMenu(parent), dialogParent_(dialogParent), dirPath_(std::move(dirPath)) { + QAction* action = new QAction(QIcon::fromTheme("folder-new"), tr("Folder"), this); + connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNewFolder); + addAction(action); - action = new QAction(QIcon::fromTheme("document-new"), tr("Blank File"), this); - connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNewFile); - addAction(action); + action = new QAction(QIcon::fromTheme("document-new"), tr("Blank File"), this); + connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNewFile); + addAction(action); - // add more items to "Create New" menu from templates - GList* templates = fm_template_list_all(fm_config->only_user_templates); - if(templates) { - addSeparator(); - for(GList* l = templates; l; l = l->next) { - FmTemplate* templ = (FmTemplate*)l->data; - /* we support directories differently */ - if(fm_template_is_directory(templ)) - continue; - FmMimeType* mime_type = fm_template_get_mime_type(templ); - const char* label = fm_template_get_label(templ); - QString text = QString("%1 (%2)").arg(QString::fromUtf8(label)).arg(QString::fromUtf8(fm_mime_type_get_desc(mime_type))); - FmIcon* icon = fm_template_get_icon(templ); - if(!icon) - icon = fm_mime_type_get_icon(mime_type); - QAction* action = addAction(IconTheme::icon(icon), text); - action->setObjectName(QString::fromUtf8(fm_template_get_name(templ, NULL))); - connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNew); + // add more items to "Create New" menu from templates + GList* templates = fm_template_list_all(fm_config->only_user_templates); + if(templates) { + addSeparator(); + for(GList* l = templates; l; l = l->next) { + FmTemplate* templ = (FmTemplate*)l->data; + /* we support directories differently */ + if(fm_template_is_directory(templ)) { + continue; + } + FmMimeType* mime_type = fm_template_get_mime_type(templ); + const char* label = fm_template_get_label(templ); + QString text = QString("%1 (%2)").arg(QString::fromUtf8(label)).arg(QString::fromUtf8(fm_mime_type_get_desc(mime_type))); + FmIcon* icon = fm_template_get_icon(templ); + if(!icon) { + icon = fm_mime_type_get_icon(mime_type); + } + QAction* action = addAction(Fm::IconInfo::fromGIcon(G_ICON(icon))->qicon(), text); + action->setObjectName(QString::fromUtf8(fm_template_get_name(templ, nullptr))); + connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNew); + } } - } } CreateNewMenu::~CreateNewMenu() { } void CreateNewMenu::onCreateNewFile() { - if(dirPath_) - createFileOrFolder(CreateNewTextFile, dirPath_); + if(dirPath_) { + createFileOrFolder(CreateNewTextFile, dirPath_); + } } void CreateNewMenu::onCreateNewFolder() { - if(dirPath_) - createFileOrFolder(CreateNewFolder, dirPath_); + if(dirPath_) { + createFileOrFolder(CreateNewFolder, dirPath_); + } } void CreateNewMenu::onCreateNew() { - QAction* action = static_cast(sender()); - QByteArray name = action->objectName().toUtf8(); - GList* templates = fm_template_list_all(fm_config->only_user_templates); - FmTemplate* templ = NULL; - for(GList* l = templates; l; l = l->next) { - FmTemplate* t = (FmTemplate*)l->data; - if(name == fm_template_get_name(t, NULL)) { - templ = t; - break; + QAction* action = static_cast(sender()); + QByteArray name = action->objectName().toUtf8(); + GList* templates = fm_template_list_all(fm_config->only_user_templates); + FmTemplate* templ = nullptr; + for(GList* l = templates; l; l = l->next) { + FmTemplate* t = (FmTemplate*)l->data; + if(name == fm_template_get_name(t, nullptr)) { + templ = t; + break; + } + } + if(templ) { // template found + if(dirPath_) { + createFileOrFolder(CreateWithTemplate, dirPath_, templ, dialogParent_); + } } - } - if(templ) { // template found - if(dirPath_) - createFileOrFolder(CreateWithTemplate, dirPath_, templ, dialogParent_); - } } } // namespace Fm diff --git a/src/createnewmenu.h b/src/createnewmenu.h index 09ca9d9..d2e7282 100644 --- a/src/createnewmenu.h +++ b/src/createnewmenu.h @@ -24,26 +24,27 @@ #include #include +#include "core/filepath.h" + namespace Fm { class FolderView; class LIBFM_QT_API CreateNewMenu : public QMenu { -Q_OBJECT + Q_OBJECT public: - explicit CreateNewMenu(QWidget* dialogParent, FmPath* dirPath, - QWidget* parent = 0); - virtual ~CreateNewMenu(); + explicit CreateNewMenu(QWidget* dialogParent, Fm::FilePath dirPath, QWidget* parent = 0); + virtual ~CreateNewMenu(); protected Q_SLOTS: - void onCreateNewFolder(); - void onCreateNewFile(); - void onCreateNew(); + void onCreateNewFolder(); + void onCreateNewFile(); + void onCreateNew(); private: - QWidget* dialogParent_; - FmPath* dirPath_; + QWidget* dialogParent_; + Fm::FilePath dirPath_; }; } diff --git a/src/customaction_p.h b/src/customaction_p.h index e3751f1..7aba3f9 100644 --- a/src/customaction_p.h +++ b/src/customaction_p.h @@ -20,28 +20,31 @@ #ifndef FM_CUSTOMACTION_P_H #define FM_CUSTOMACTION_P_H +#include +#include "customactions/fileaction.h" + namespace Fm { class CustomAction : public QAction { public: - explicit CustomAction(FmFileActionItem* item, QObject* parent = NULL): - QAction(QString::fromUtf8(fm_file_action_item_get_name(item)), parent), - item_(reinterpret_cast(fm_file_action_item_ref(item))) { - const char* icon_name = fm_file_action_item_get_icon(item); - if(icon_name) - setIcon(QIcon::fromTheme(icon_name)); - } - - virtual ~CustomAction() { - fm_file_action_item_unref(item_); - } - - FmFileActionItem* item() { - return item_; - } + explicit CustomAction(std::shared_ptr item, QObject* parent = nullptr): + QAction{QString::fromStdString(item->get_name()), parent}, + item_{item} { + auto& icon_name = item->get_icon(); + if(!icon_name.empty()) { + setIcon(QIcon::fromTheme(icon_name.c_str())); + } + } + + virtual ~CustomAction() { + } + + const std::shared_ptr& item() const { + return item_; + } private: - FmFileActionItem* item_; + std::shared_ptr item_; }; } // namespace Fm diff --git a/src/customactions/fileaction.cpp b/src/customactions/fileaction.cpp new file mode 100644 index 0000000..31d5e3a --- /dev/null +++ b/src/customactions/fileaction.cpp @@ -0,0 +1,615 @@ +#include "fileaction.h" +#include +#include + +using namespace std; + +namespace Fm { + +static const char* desktop_env = nullptr; // current desktop environment +static bool actions_loaded = false; // all actions are loaded? +static unordered_map, CStrHash, CStrEqual> all_actions; // cache all loaded actions + +FileActionObject::FileActionObject() { +} + +FileActionObject::FileActionObject(GKeyFile* kf) { + name = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "Name", nullptr, nullptr)}; + tooltip = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "Tooltip", nullptr, nullptr)}; + icon = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "Icon", nullptr, nullptr)}; + desc = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "Description", nullptr, nullptr)}; + GErrorPtr err; + enabled = g_key_file_get_boolean(kf, "Desktop Entry", "Enabled", &err); + if(err) { // key not found, default to true + err.reset(); + enabled = true; + } + hidden = g_key_file_get_boolean(kf, "Desktop Entry", "Hidden", nullptr); + suggested_shortcut = CStrPtr{g_key_file_get_string(kf, "Desktop Entry", "SuggestedShortcut", nullptr)}; + + condition = unique_ptr {new FileActionCondition(kf, "Desktop Entry")}; + + has_parent = false; +} + +FileActionObject::~FileActionObject() { +} + +//static +bool FileActionObject::is_plural_exec(const char* exec) { + if(!exec) { + return false; + } + // the first relevent code encountered in Exec parameter + // determines whether the command accepts singular or plural forms + for(int i = 0; exec[i]; ++i) { + char ch = exec[i]; + if(ch == '%') { + ++i; + ch = exec[i]; + switch(ch) { + case 'B': + case 'D': + case 'F': + case 'M': + case 'O': + case 'U': + case 'W': + case 'X': + return true; // plural + case 'b': + case 'd': + case 'f': + case 'm': + case 'o': + case 'u': + case 'w': + case 'x': + return false; // singular + default: + // irrelevent code, skip + break; + } + } + } + return false; // singular form by default +} + +std::string FileActionObject::expand_str(const char* templ, const FileInfoList& files, bool for_display, std::shared_ptr first_file) { + if(!templ) { + return string{}; + } + string result; + + if(!first_file) { + first_file = files.front(); + } + + for(int i = 0; templ[i]; ++i) { + char ch = templ[i]; + if(ch == '%') { + ++i; + ch = templ[i]; + switch(ch) { + case 'b': // (first) basename + if(for_display) { + result += first_file->name(); + } + else { + CStrPtr quoted{g_shell_quote(first_file->name().c_str())}; + result += quoted.get(); + } + break; + case 'B': // space-separated list of basenames + for(auto& fi : files) { + if(for_display) { + result += fi->name(); + } + else { + CStrPtr quoted{g_shell_quote(fi->name().c_str())}; + result += quoted.get(); + } + result += ' '; + } + if(result[result.length() - 1] == ' ') { // remove trailing space + result.erase(result.length() - 1); + } + break; + case 'c': // count of selected items + result += to_string(files.size()); + break; + case 'd': { // (first) base directory + // FIXME: should the base dir be a URI? + auto base_dir = first_file->dirPath(); + auto str = base_dir.toString(); + if(for_display) { + // FIXME: str = Filename.display_name(str); + } + CStrPtr quoted{g_shell_quote(str.get())}; + result += quoted.get(); + break; + } + case 'D': // space-separated list of base directory of each selected items + for(auto& fi : files) { + auto base_dir = fi->dirPath(); + auto str = base_dir.toString(); + if(for_display) { + // str = Filename.display_name(str); + } + CStrPtr quoted{g_shell_quote(str.get())}; + result += quoted.get(); + result += ' '; + } + if(result[result.length() - 1] == ' ') { // remove trailing space + result.erase(result.length() - 1); + } + break; + case 'f': { // (first) file name + auto filename = first_file->path().toString(); + if(for_display) { + // filename = Filename.display_name(filename); + } + CStrPtr quoted{g_shell_quote(filename.get())}; + result += quoted.get(); + break; + } + case 'F': // space-separated list of selected file names + for(auto& fi : files) { + auto filename = fi->path().toString(); + if(for_display) { + // filename = Filename.display_name(filename); + } + CStrPtr quoted{g_shell_quote(filename.get())}; + result += quoted.get(); + result += ' '; + } + if(result[result.length() - 1] == ' ') { // remove trailing space + result.erase(result.length() - 1); + } + break; + case 'h': // hostname of the (first) URI + // FIXME: how to support this correctly? + // FIXME: currently we pass g_get_host_name() + result += g_get_host_name(); + break; + case 'm': // mimetype of the (first) selected item + result += first_file->mimeType()->name(); + break; + case 'M': // space-separated list of the mimetypes of the selected items + for(auto& fi : files) { + result += fi->mimeType()->name(); + result += ' '; + } + break; + case 'n': // username of the (first) URI + // FIXME: how to support this correctly? + result += g_get_user_name(); + break; + case 'o': // no-op operator which forces a singular form of execution when specified as first parameter, + case 'O': // no-op operator which forces a plural form of execution when specified as first parameter, + break; + case 'p': // port number of the (first) URI + // FIXME: how to support this correctly? + // result.append("0"); + break; + case 's': // scheme of the (first) URI + result += first_file->path().uriScheme().get(); + break; + case 'u': // (first) URI + result += first_file->path().uri().get(); + break; + case 'U': // space-separated list of selected URIs + for(auto& fi : files) { + result += fi->path().uri().get(); + result += ' '; + } + if(result[result.length() - 1] == ' ') { // remove trailing space + result.erase(result.length() - 1); + } + break; + case 'w': { // (first) basename without the extension + auto basename = first_file->name(); + int pos = basename.rfind('.'); + // FIXME: handle non-UTF8 filenames + if(pos != -1) { + basename.erase(pos, string::npos); + } + CStrPtr quoted{g_shell_quote(basename.c_str())}; + result += quoted.get(); + break; + } + case 'W': // space-separated list of basenames without their extension + for(auto& fi : files) { + auto basename = fi->name(); + int pos = basename.rfind('.'); + // FIXME: for_display ? Shell.quote(str) : str); + if(pos != -1) { + basename.erase(pos, string::npos); + } + CStrPtr quoted{g_shell_quote(basename.c_str())}; + result += quoted.get(); + result += ' '; + } + if(result[result.length() - 1] == ' ') { // remove trailing space + result.erase(result.length() - 1); + } + break; + case 'x': { // (first) extension + auto basename = first_file->name(); + int pos = basename.rfind('.'); + const char* ext = ""; + if(pos >= 0) { + ext = basename.c_str() + pos + 1; + } + CStrPtr quoted{g_shell_quote(ext)}; + result += quoted.get(); + break; + } + case 'X': // space-separated list of extensions + for(auto& fi : files) { + auto basename = fi->name(); + int pos = basename.rfind('.'); + const char* ext = ""; + if(pos >= 0) { + ext = basename.c_str() + pos + 1; + } + CStrPtr quoted{g_shell_quote(ext)}; + result += quoted.get(); + result += ' '; + } + if(result[result.length() - 1] == ' ') { // remove trailing space + result.erase(result.length() - 1); + } + break; + case '%': // the % character + result += '%'; + break; + case '\0': + break; + } + } + else { + result += ch; + } + } + return result; +} + +FileAction::FileAction(GKeyFile* kf): FileActionObject{kf}, target{FILE_ACTION_TARGET_CONTEXT} { + type = FileActionType::ACTION; + + GErrorPtr err; + if(g_key_file_get_boolean(kf, "Desktop Entry", "TargetContext", &err)) { // default to true + target |= FILE_ACTION_TARGET_CONTEXT; + } + else if(!err) { // error means the key is abscent + target &= ~FILE_ACTION_TARGET_CONTEXT; + } + if(g_key_file_get_boolean(kf, "Desktop Entry", "TargetLocation", nullptr)) { + target |= FILE_ACTION_TARGET_LOCATION; + } + if(g_key_file_get_boolean(kf, "Desktop Entry", "TargetToolbar", nullptr)) { + target |= FILE_ACTION_TARGET_TOOLBAR; + } + toolbar_label = CStrPtr{g_key_file_get_locale_string(kf, "Desktop Entry", "ToolbarLabel", nullptr, nullptr)}; + + auto profile_names = CStrArrayPtr{g_key_file_get_string_list(kf, "Desktop Entry", "Profiles", nullptr, nullptr)}; + if(profile_names != nullptr) { + for(auto profile_name = profile_names.get(); *profile_name; ++profile_name) { + // stdout.printf("%s", profile); + profiles.push_back(make_shared(kf, *profile_name)); + } + } +} + +std::shared_ptr FileAction::match(const FileInfoList& files) const { + //qDebug() << "FileAction.match: " << id.get(); + if(hidden || !enabled) { + return nullptr; + } + + if(!condition->match(files)) { + return nullptr; + } + for(const auto& profile : profiles) { + if(profile->match(files)) { + //qDebug() << " profile matched!\n\n"; + return profile; + } + } + // stdout.printf("\n"); + return nullptr; +} + +FileActionMenu::FileActionMenu(GKeyFile* kf): FileActionObject{kf} { + type = FileActionType::MENU; + items_list = CStrArrayPtr{g_key_file_get_string_list(kf, "Desktop Entry", "ItemsList", nullptr, nullptr)}; +} + +bool FileActionMenu::match(const FileInfoList& files) const { + // stdout.printf("FileActionMenu.match: %s\n", id); + if(hidden || !enabled) { + return false; + } + if(!condition->match(files)) { + return false; + } + // stdout.printf("menu matched!: %s\n\n", id); + return true; +} + +void FileActionMenu::cache_children(const FileInfoList& files, const char** items_list) { + for(; *items_list; ++items_list) { + const char* item_id_prefix = *items_list; + size_t len = strlen(item_id_prefix); + if(item_id_prefix[0] == '[' && item_id_prefix[len - 1] == ']') { + // runtime dynamic item list + char* output; + int exit_status; + string prefix{item_id_prefix + 1, len - 2}; // skip [ and ] + auto command = expand_str(prefix.c_str(), files); + if(g_spawn_command_line_sync(command.c_str(), &output, nullptr, &exit_status, nullptr) && exit_status == 0) { + CStrArrayPtr item_ids{g_strsplit(output, ";", -1)}; + g_free(output); + cache_children(files, (const char**)item_ids.get()); + } + } + else if(strcmp(item_id_prefix, "SEPARATOR") == 0) { + // separator item + cached_children.push_back(nullptr); + } + else { + CStrPtr item_id{g_strconcat(item_id_prefix, ".desktop", nullptr)}; + auto it = all_actions.find(item_id.get()); + if(it != all_actions.end()) { + auto child_action = it->second; + child_action->has_parent = true; + cached_children.push_back(child_action); + // stdout.printf("add child: %s to menu: %s\n", item_id, id); + } + } + } +} + +std::shared_ptr FileActionItem::fromActionObject(std::shared_ptr action_obj, const FileInfoList& files) { + std::shared_ptr item; + if(action_obj->type == FileActionType::MENU) { + auto menu = static_pointer_cast(action_obj); + if(menu->match(files)) { + item = make_shared(menu, files); + // eliminate empty menus + if(item->children.empty()) { + item = nullptr; + } + } + } + else { + // handle profiles here + auto action = static_pointer_cast(action_obj); + auto profile = action->match(files); + if(profile) { + item = make_shared(action, profile, files); + } + } + return item; +} + +FileActionItem::FileActionItem(std::shared_ptr _action, std::shared_ptr _profile, const FileInfoList& files): + FileActionItem{static_pointer_cast(_action), files} { + profile = _profile; +} + +FileActionItem::FileActionItem(std::shared_ptr menu, const FileInfoList& files): + FileActionItem{static_pointer_cast(menu), files} { + for(auto& action_obj : menu->cached_children) { + if(action_obj == nullptr) { // separator + children.push_back(nullptr); + } + else { // action item or menu + auto subitem = fromActionObject(action_obj, files); + if(subitem != nullptr) { + children.push_back(subitem); + } + } + } +} + +FileActionItem::FileActionItem(std::shared_ptr _action, const FileInfoList& files) { + action = std::move(_action); + name = FileActionObject::expand_str(action->name.get(), files, true); + desc = FileActionObject::expand_str(action->desc.get(), files, true); + icon = FileActionObject::expand_str(action->icon.get(), files, false); +} + +bool FileActionItem::launch(GAppLaunchContext* ctx, const FileInfoList& files, CStrPtr& output) const { + if(action->type == FileActionType::ACTION) { + if(profile != nullptr) { + profile->launch(ctx, files, output); + } + return true; + } + return false; +} + +static void load_actions_from_dir(const char* dirname, const char* id_prefix) { + //qDebug() << "loading from: " << dirname << endl; + auto dir = g_dir_open(dirname, 0, nullptr); + if(dir != nullptr) { + for(;;) { + const char* name = g_dir_read_name(dir); + if(name == nullptr) { + break; + } + // found a file in file-manager/actions dir, get its full path + CStrPtr full_path{g_build_filename(dirname, name, nullptr)}; + // stdout.printf("\nfound %s\n", full_path); + + // see if it's a sub dir + if(g_file_test(full_path.get(), G_FILE_TEST_IS_DIR)) { + // load sub dirs recursively + CStrPtr new_id_prefix; + if(id_prefix) { + new_id_prefix = CStrPtr{g_strconcat(id_prefix, "-", name, nullptr)}; + } + load_actions_from_dir(full_path.get(), id_prefix ? new_id_prefix.get() : name); + } + else if(g_str_has_suffix(name, ".desktop")) { + CStrPtr new_id_prefix; + if(id_prefix) { + new_id_prefix = CStrPtr{g_strconcat(id_prefix, "-", name, nullptr)}; + } + const char* id = id_prefix ? new_id_prefix.get() : name; + // ensure that it's not already in the cache + if(all_actions.find(id) == all_actions.cend()) { + auto kf = g_key_file_new(); + if(g_key_file_load_from_file(kf, full_path.get(), G_KEY_FILE_NONE, nullptr)) { + auto type = CStrPtr{g_key_file_get_string(kf, "Desktop Entry", "Type", nullptr)}; + if(!type) { + continue; + } + std::shared_ptr action; + if(strcmp(type.get(), "Action") == 0) { + action = static_pointer_cast(make_shared(kf)); + // stdout.printf("load action: %s\n", id); + } + else if(strcmp(type.get(), "Menu") == 0) { + action = static_pointer_cast(make_shared(kf)); + // stdout.printf("load menu: %s\n", id); + } + else { + continue; + } + action->setId(id); + all_actions.insert(make_pair(action->id.get(), action)); // add the id/action pair to hash table + // stdout.printf("add to cache %s\n", id); + } + g_key_file_free(kf); + } + else { + // stdout.printf("cache found for action: %s\n", id); + } + } + } + g_dir_close(dir); + } +} + +void file_actions_set_desktop_env(const char* env) { + desktop_env = env; +} + +static void load_all_actions() { + all_actions.clear(); + auto dirs = g_get_system_data_dirs(); + for(auto dir = dirs; *dir; ++dir) { + CStrPtr dir_path{g_build_filename(*dir, "file-manager/actions", nullptr)}; + load_actions_from_dir(dir_path.get(), nullptr); + } + CStrPtr dir_path{g_build_filename(g_get_user_data_dir(), "file-manager/actions", nullptr)}; + load_actions_from_dir(dir_path.get(), nullptr); + actions_loaded = true; +} + +bool FileActionItem::compare_items(std::shared_ptr a, std::shared_ptr b) +{ + // first get the list of level-zero item names (http://www.nautilus-actions.org/?q=node/377) + static QStringList itemNamesList; + static bool level_zero_checked = false; + if(!level_zero_checked) { + level_zero_checked = true; + auto level_zero = CStrPtr{g_build_filename(g_get_user_data_dir(), + "file-manager/actions/level-zero.directory", nullptr)}; + if(g_file_test(level_zero.get(), G_FILE_TEST_IS_REGULAR)) { + GKeyFile* kf = g_key_file_new(); + if(g_key_file_load_from_file(kf, level_zero.get(), G_KEY_FILE_NONE, nullptr)) { + auto itemsList = CStrArrayPtr{g_key_file_get_string_list(kf, + "Desktop Entry", + "ItemsList", nullptr, nullptr)}; + if(itemsList) { + for(uint i = 0; i < g_strv_length(itemsList.get()); ++i) { + CStrPtr desktop_file_name{g_strconcat(itemsList.get()[i], ".desktop", nullptr)}; + auto desktop_file = CStrPtr{g_build_filename(g_get_user_data_dir(), + "file-manager/actions", + desktop_file_name.get(), nullptr)}; + GKeyFile* desktop_file_key = g_key_file_new(); + if(g_key_file_load_from_file(desktop_file_key, desktop_file.get(), G_KEY_FILE_NONE, nullptr)) { + auto actionName = CStrPtr{g_key_file_get_string(desktop_file_key, + "Desktop Entry", + "Name", NULL)}; + if(actionName) { + itemNamesList << QString::fromUtf8(actionName.get()); + } + } + g_key_file_free(desktop_file_key); + } + } + } + g_key_file_free(kf); + } + } + if(!itemNamesList.isEmpty()) { + int first = itemNamesList.indexOf(QString::fromStdString(a->get_name())); + int second = itemNamesList.indexOf(QString::fromStdString(b->get_name())); + if(first > -1) { + if(second > -1) { + return (first < second); + } + else { + return true; // list items have priority + } + } + else if(second > -1) { + return false; + } + } + return (a->get_name().compare(b->get_name()) < 0); +} + +FileActionItemList FileActionItem::get_actions_for_files(const FileInfoList& files) { + if(!actions_loaded) { + load_all_actions(); + } + + // Iterate over all actions to establish association between parent menu + // and children actions, and to find out toplevel ones which are not + // attached to any parent menu + for(auto& item : all_actions) { + auto& action_obj = item.second; + // stdout.printf("id = %s\n", action_obj.id); + if(action_obj->type == FileActionType::MENU) { // this is a menu + auto menu = static_pointer_cast(action_obj); + // stdout.printf("menu: %s\n", menu.name); + // associate child items with menus + menu->cache_children(files, (const char**)menu->items_list.get()); + } + } + + // Output the menus + FileActionItemList items; + + for(auto& item : all_actions) { + auto& action_obj = item.second; + // only output toplevel items here + if(action_obj->has_parent == false) { // this is a toplevel item + auto item = FileActionItem::fromActionObject(action_obj, files); + if(item != nullptr) { + items.push_back(item); + } + } + } + + // cleanup temporary data cached during menu generation + for(auto& item : all_actions) { + auto& action_obj = item.second; + action_obj->has_parent = false; + if(action_obj->type == FileActionType::MENU) { + auto menu = static_pointer_cast(action_obj); + menu->cached_children.clear(); + } + } + + std::sort(items.begin(), items.end(), compare_items); + return items; +} + +} // namespace Fm diff --git a/src/customactions/fileaction.h b/src/customactions/fileaction.h new file mode 100644 index 0000000..4c2a8bc --- /dev/null +++ b/src/customactions/fileaction.h @@ -0,0 +1,156 @@ +#ifndef FILEACTION_H +#define FILEACTION_H + +#include +#include + +#include "../core/fileinfo.h" +#include "fileactioncondition.h" +#include "fileactionprofile.h" + +namespace Fm { + +enum class FileActionType { + NONE, + ACTION, + MENU +}; + + +enum FileActionTarget { + FILE_ACTION_TARGET_NONE, + FILE_ACTION_TARGET_CONTEXT = 1, + FILE_ACTION_TARGET_LOCATION = 1 << 1, + FILE_ACTION_TARGET_TOOLBAR = 1 << 2 +}; + + +class FileActionObject { +public: + explicit FileActionObject(); + + explicit FileActionObject(GKeyFile* kf); + + virtual ~FileActionObject(); + + void setId(const char* _id) { + id = CStrPtr{g_strdup(_id)}; + } + + static bool is_plural_exec(const char* exec); + + static std::string expand_str(const char* templ, const FileInfoList& files, bool for_display = false, std::shared_ptr first_file = nullptr); + + FileActionType type; + CStrPtr id; + CStrPtr name; + CStrPtr tooltip; + CStrPtr icon; + CStrPtr desc; + bool enabled; + bool hidden; + CStrPtr suggested_shortcut; + std::unique_ptr condition; + + // values cached during menu generation + bool has_parent; +}; + + +class FileAction: public FileActionObject { +public: + + FileAction(GKeyFile* kf); + + std::shared_ptr match(const FileInfoList& files) const; + + int target; // bitwise or of FileActionTarget + CStrPtr toolbar_label; + + // FIXME: currently we don't support dynamic profiles + std::vector> profiles; +}; + + +class FileActionMenu : public FileActionObject { +public: + + FileActionMenu(GKeyFile* kf); + + bool match(const FileInfoList &files) const; + + // called during menu generation + void cache_children(const FileInfoList &files, const char** items_list); + + CStrArrayPtr items_list; + + // values cached during menu generation + std::vector> cached_children; +}; + + +class FileActionItem { +public: + + static std::shared_ptr fromActionObject(std::shared_ptr action_obj, const FileInfoList &files); + + FileActionItem(std::shared_ptr _action, std::shared_ptr _profile, const FileInfoList& files); + + FileActionItem(std::shared_ptr menu, const FileInfoList& files); + + FileActionItem(std::shared_ptr _action, const FileInfoList& files); + + const std::string& get_name() const { + return name; + } + + const std::string& get_desc() const { + return desc; + } + + const std::string& get_icon() const { + return icon; + } + + const char* get_id() const { + return action->id.get(); + } + + FileActionTarget get_target() const { + if(action->type == FileActionType::ACTION) { + return FileActionTarget(static_cast(action.get())->target); + } + return FILE_ACTION_TARGET_CONTEXT; + } + + bool is_menu() const { + return (action->type == FileActionType::MENU); + } + + bool is_action() const { + return (action->type == FileActionType::ACTION); + } + + bool launch(GAppLaunchContext *ctx, const FileInfoList &files, CStrPtr &output) const; + + const std::vector>& get_sub_items() const { + return children; + } + + static bool compare_items(std::shared_ptr a, std::shared_ptr b); + static std::vector> get_actions_for_files(const FileInfoList& files); + + std::string name; + std::string desc; + std::string icon; + std::shared_ptr action; + std::shared_ptr profile; // only used by action item + std::vector> children; // only used by menu +}; + +typedef std::vector> FileActionItemList; + +} // namespace Fm + + +#endif // FILEACTION_H diff --git a/src/customactions/fileactioncondition.cpp b/src/customactions/fileactioncondition.cpp new file mode 100644 index 0000000..16e545b --- /dev/null +++ b/src/customactions/fileactioncondition.cpp @@ -0,0 +1,503 @@ +#include "fileactioncondition.h" +#include "fileaction.h" +#include + + +using namespace std; + +namespace Fm { + +FileActionCondition::FileActionCondition(GKeyFile *kf, const char* group) { + only_show_in = CStrArrayPtr{g_key_file_get_string_list(kf, group, "OnlyShowIn", nullptr, nullptr)}; + not_show_in = CStrArrayPtr{g_key_file_get_string_list(kf, group, "NotShowIn", nullptr, nullptr)}; + try_exec = CStrPtr{g_key_file_get_string(kf, group, "TryExec", nullptr)}; + show_if_registered = CStrPtr{g_key_file_get_string(kf, group, "ShowIfRegistered", nullptr)}; + show_if_true = CStrPtr{g_key_file_get_string(kf, group, "ShowIfTrue", nullptr)}; + show_if_running = CStrPtr{g_key_file_get_string(kf, group, "ShowIfRunning", nullptr)}; + mime_types = CStrArrayPtr{g_key_file_get_string_list(kf, group, "MimeTypes", nullptr, nullptr)}; + base_names = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Basenames", nullptr, nullptr)}; + match_case = g_key_file_get_boolean(kf, group, "Matchcase", nullptr); + + CStrPtr selection_count_str{g_key_file_get_string(kf, group, "SelectionCount", nullptr)}; + if(selection_count_str != nullptr) { + switch(selection_count_str[0]) { + case '<': + case '>': + case '=': + selection_count_cmp = selection_count_str[0]; + selection_count = atoi(selection_count_str.get() + 1); + break; + default: + selection_count_cmp = '>'; + selection_count = 0; + break; + } + } + else { + selection_count_cmp = '>'; + selection_count = 0; + } + + schemes = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Schemes", nullptr, nullptr)}; + folders = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Folders", nullptr, nullptr)}; + auto caps = CStrArrayPtr{g_key_file_get_string_list(kf, group, "Capabilities", nullptr, nullptr)}; + + // FIXME: implement Capabilities support + +} + +bool FileActionCondition::match_try_exec(const FileInfoList& files) { + if(try_exec != nullptr) { + // stdout.printf(" TryExec: %s\n", try_exec); + CStrPtr exec_path{g_find_program_in_path(FileActionObject::expand_str(try_exec.get(), files).c_str())}; + if(!g_file_test(exec_path.get(), G_FILE_TEST_IS_EXECUTABLE)) { + return false; + } + } + return true; +} + +bool FileActionCondition::match_show_if_registered(const FileInfoList& files) { + if(show_if_registered != nullptr) { + // stdout.printf(" ShowIfRegistered: %s\n", show_if_registered); + auto service = FileActionObject::expand_str(show_if_registered.get(), files); + // References: + // http://people.freedesktop.org/~david/eggdbus-20091014/eggdbus-interface-org.freedesktop.DBus.html#eggdbus-method-org.freedesktop.DBus.NameHasOwner + // glib source code: gio/tests/gdbus-names.c + auto con = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); + auto result = g_dbus_connection_call_sync(con, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "NameHasOwner", + g_variant_new("(s)", service.c_str()), + g_variant_type_new("(b)"), + G_DBUS_CALL_FLAGS_NONE, + -1, nullptr, nullptr); + bool name_has_owner; + g_variant_get(result, "(b)", &name_has_owner); + g_variant_unref(result); + // stdout.printf("check if service: %s is in use: %d\n", service, (int)name_has_owner); + if(!name_has_owner) { + return false; + } + } + return true; +} + +bool FileActionCondition::match_show_if_true(const FileInfoList& files) { + if(show_if_true != nullptr) { + auto cmd = FileActionObject::expand_str(show_if_true.get(), files); + int exit_status; + // FIXME: Process.spawn cannot handle shell commands. Use Posix.system() instead. + //if(!Process.spawn_command_line_sync(cmd, nullptr, nullptr, out exit_status) + // || exit_status != 0) + // return false; + exit_status = system(cmd.c_str()); + if(exit_status != 0) { + return false; + } + } + return true; +} + +bool FileActionCondition::match_show_if_running(const FileInfoList& files) { + if(show_if_running != nullptr) { + auto process_name = FileActionObject::expand_str(show_if_running.get(), files); + CStrPtr pgrep{g_find_program_in_path("pgrep")}; + bool running = false; + // pgrep is not fully portable, but we don't have better options here + if(pgrep != nullptr) { + int exit_status; + // cmd = "$pgrep -x '$process_name'" + string cmd = pgrep.get(); + cmd += " -x \'"; + cmd += process_name; + cmd += "\'"; + if(g_spawn_command_line_sync(cmd.c_str(), nullptr, nullptr, &exit_status, nullptr)) { + if(exit_status == 0) { + running = true; + } + } + } + if(!running) { + return false; + } + } + return true; +} + +bool FileActionCondition::match_mime_type(const FileInfoList& files, const char* type, bool negated) { + // stdout.printf("match_mime_type: %s, neg: %d\n", type, (int)negated); + + if(strcmp(type, "all/all") == 0 || strcmp(type, "*") == 0) { + return negated ? false : true; + } + else if(strcmp(type, "all/allfiles") == 0) { + // see if all fileinfos are files + if(negated) { // all fileinfos should not be files + for(auto& fi: files) { + if(!fi->isDir()) { // at least 1 of the fileinfos is a file. + return false; + } + } + } + else { // all fileinfos should be files + for(auto& fi: files) { + if(fi->isDir()) { // at least 1 of the fileinfos is a file. + return false; + } + } + } + } + else if(g_str_has_suffix(type, "/*")) { + // check if all are subtypes of allowed_type + string prefix{type}; + prefix.erase(prefix.length() - 1); // remove the last char + if(negated) { // all files should not have the prefix + for(auto& fi: files) { + if(g_str_has_prefix(fi->mimeType()->name(), prefix.c_str())) { + return false; + } + } + } + else { // all files should have the prefix + for(auto& fi: files) { + if(!g_str_has_prefix(fi->mimeType()->name(), prefix.c_str())) { + return false; + } + } + } + } + else { + if(negated) { // all files should not be of the type + for(auto& fi: files) { + if(strcmp(fi->mimeType()->name(),type) == 0) { + // if(ContentType.is_a(type, fi.get_mime_type().get_type())) { + return false; + } + } + } + else { // all files should be of the type + for(auto& fi: files) { + // stdout.printf("get_type: %s, type: %s\n", fi.get_mime_type().get_type(), type); + if(strcmp(fi->mimeType()->name(),type) != 0) { + // if(!ContentType.is_a(type, fi.get_mime_type().get_type())) { + return false; + } + } + } + } + return true; +} + +bool FileActionCondition::match_mime_types(const FileInfoList& files) { + if(mime_types != nullptr) { + bool allowed = false; + // FIXME: this is inefficient, but easier to implement + // check if all of the mime_types are allowed + for(auto mime_type = mime_types.get(); *mime_type; ++mime_type) { + const char* allowed_type = *mime_type; + const char* type; + bool negated; + if(allowed_type[0] == '!') { + type = allowed_type + 1; + negated = true; + } + else { + type = allowed_type; + negated = false; + } + + if(negated) { // negated mime_type rules are ANDed + bool type_is_allowed = match_mime_type(files, type, negated); + if(!type_is_allowed) { // so any mismatch is not allowed + return false; + } + } + else { // other mime_type rules are ORed + // matching any one of the mime_type is enough + if(!allowed) { // if no rule is matched yet + allowed = match_mime_type(files, type, false); + } + } + } + return allowed; + } + return true; +} + +bool FileActionCondition::match_base_name(const FileInfoList& files, const char* base_name, bool negated) { + // see if all files has the base_name + // FIXME: this is inefficient, some optimization is needed later + GPatternSpec* pattern; + if(match_case) { + pattern = g_pattern_spec_new(base_name); + } + else { + CStrPtr case_fold{g_utf8_casefold(base_name, -1)}; + pattern = g_pattern_spec_new(case_fold.get()); // FIXME: is this correct? + } + for(auto& fi: files) { + const char* name = fi->name().c_str(); + if(match_case) { + if(g_pattern_match_string(pattern, name)) { + // at least 1 file has the base_name + if(negated) { + return false; + } + } + else { + // at least 1 file does not has the scheme + if(!negated) { + return false; + } + } + } + else { + CStrPtr case_fold{g_utf8_casefold(name, -1)}; + if(g_pattern_match_string(pattern, case_fold.get())) { + // at least 1 file has the base_name + if(negated) { + return false; + } + } + else { + // at least 1 file does not has the scheme + if(!negated) { + return false; + } + } + } + } + return true; +} + +bool FileActionCondition::match_base_names(const FileInfoList& files) { + if(base_names != nullptr) { + bool allowed = false; + // FIXME: this is inefficient, but easier to implement + // check if all of the base_names are allowed + for(auto it = base_names.get(); *it; ++it) { + auto allowed_name = *it; + const char* name; + bool negated; + if(allowed_name[0] == '!') { + name = allowed_name + 1; + negated = true; + } + else { + name = allowed_name; + negated = false; + } + + if(negated) { // negated base_name rules are ANDed + bool name_is_allowed = match_base_name(files, name, negated); + if(!name_is_allowed) { // so any mismatch is not allowed + return false; + } + } + else { // other base_name rules are ORed + // matching any one of the base_name is enough + if(!allowed) { // if no rule is matched yet + allowed = match_base_name(files, name, false); + } + } + } + return allowed; + } + return true; +} + +bool FileActionCondition::match_scheme(const FileInfoList& files, const char* scheme, bool negated) { + // FIXME: this is inefficient, some optimization is needed later + // see if all files has the scheme + for(auto& fi: files) { + if(fi->path().hasUriScheme(scheme)) { + // at least 1 file has the scheme + if(negated) { + return false; + } + } + else { + // at least 1 file does not has the scheme + if(!negated) { + return false; + } + } + } + return true; +} + +bool FileActionCondition::match_schemes(const FileInfoList& files) { + if(schemes != nullptr) { + bool allowed = false; + // FIXME: this is inefficient, but easier to implement + // check if all of the schemes are allowed + for(auto it = schemes.get(); *it; ++it) { + auto allowed_scheme = *it; + const char* scheme; + bool negated; + if(allowed_scheme[0] == '!') { + scheme = allowed_scheme + 1; + negated = true; + } + else { + scheme = allowed_scheme; + negated = false; + } + + if(negated) { // negated scheme rules are ANDed + bool scheme_is_allowed = match_scheme(files, scheme, negated); + if(!scheme_is_allowed) { // so any mismatch is not allowed + return false; + } + } + else { // other scheme rules are ORed + // matching any one of the scheme is enough + if(!allowed) { // if no rule is matched yet + allowed = match_scheme(files, scheme, false); + } + } + } + return allowed; + } + return true; +} + +bool FileActionCondition::match_folder(const FileInfoList& files, const char* folder, bool negated) { + // trailing /* should always be implied. + // FIXME: this is inefficient, some optimization is needed later + GPatternSpec* pattern; + if(g_str_has_suffix(folder, "/*")) { + pattern = g_pattern_spec_new(folder); + } + else { + auto pat_str = string(folder) + "/*"; + pattern = g_pattern_spec_new(pat_str.c_str()); + } + for(auto& fi: files) { + auto dirname = fi->dirPath().toString(); + if(g_pattern_match_string(pattern, dirname.get())) { // at least 1 file is in the folder + if(negated) { + return false; + } + } + else { + if(!negated) { + return false; + } + } + } + return true; +} + +bool FileActionCondition::match_folders(const FileInfoList& files) { + if(folders != nullptr) { + bool allowed = false; + // FIXME: this is inefficient, but easier to implement + // check if all of the schemes are allowed + for(auto it = folders.get(); *it; ++it) { + auto allowed_folder = *it; + const char* folder; + bool negated; + if(allowed_folder[0] == '!') { + folder = allowed_folder + 1; + negated = true; + } + else { + folder = allowed_folder; + negated = false; + } + + if(negated) { // negated folder rules are ANDed + bool folder_is_allowed = match_folder(files, folder, negated); + if(!folder_is_allowed) { // so any mismatch is not allowed + return false; + } + } + else { // other folder rules are ORed + // matching any one of the folder is enough + if(!allowed) { // if no rule is matched yet + allowed = match_folder(files, folder, false); + } + } + } + return allowed; + } + return true; +} + +bool FileActionCondition::match_selection_count(const FileInfoList& files) { + const int n_files = files.size(); + switch(selection_count_cmp) { + case '<': + if(n_files >= selection_count) { + return false; + } + break; + case '=': + if(n_files != selection_count) { + return false; + } + break; + case '>': + if(n_files <= selection_count) { + return false; + } + break; + } + return true; +} + +bool FileActionCondition::match_capabilities(const FileInfoList& /*files*/) { + // TODO + return true; +} + +bool FileActionCondition::match(const FileInfoList& files) { + // all of the condition are combined with AND + // So, if any one of the conditions is not matched, we quit. + + // TODO: OnlyShowIn, NotShowIn + if(!match_try_exec(files)) { + return false; + } + + if(!match_mime_types(files)) { + return false; + } + if(!match_base_names(files)) { + return false; + } + if(!match_selection_count(files)) { + return false; + } + if(!match_schemes(files)) { + return false; + } + if(!match_folders(files)) { + return false; + } + // TODO: Capabilities + // currently, due to limitations of Fm.FileInfo, this cannot + // be implemanted correctly. + if(!match_capabilities(files)) { + return false; + } + + if(!match_show_if_registered(files)) { + return false; + } + if(!match_show_if_true(files)) { + return false; + } + if(!match_show_if_running(files)) { + return false; + } + + return true; +} + + +} // namespace Fm diff --git a/src/customactions/fileactioncondition.h b/src/customactions/fileactioncondition.h new file mode 100644 index 0000000..5390cdd --- /dev/null +++ b/src/customactions/fileactioncondition.h @@ -0,0 +1,123 @@ +#ifndef FILEACTIONCONDITION_H +#define FILEACTIONCONDITION_H + +#include +#include "../core/gioptrs.h" +#include "../core/fileinfo.h" + +namespace Fm { + +// FIXME: we can use getgroups() to get groups of current process +// then, call stat() and stat.st_gid to handle capabilities +// in this way, we don't have to call euidaccess + +enum class FileActionCapability { + OWNER = 0, + READABLE = 1 << 1, + WRITABLE = 1 << 2, + EXECUTABLE = 1 << 3, + LOCAL = 1 << 4 +}; + + +class FileActionCondition { +public: + explicit FileActionCondition(GKeyFile* kf, const char* group); + +#if 0 + bool match_base_name_(const FileInfoList& files, const char* allowed_base_name) { + // all files should match the base_name pattern. + bool allowed = true; + if(allowed_base_name.index_of_char('*') >= 0) { + string allowed_base_name_ci; + if(!match_case) { + allowed_base_name_ci = allowed_base_name.casefold(); // FIXME: is this ok? + allowed_base_name = allowed_base_name_ci; + } + var pattern = new PatternSpec(allowed_base_name); + foreach(unowned FileInfo fi in files) { + unowned string name = fi.get_name(); + if(match_case) { + if(!pattern.match_string(name)) { + allowed = false; + break; + } + } + else { + if(!pattern.match_string(name.casefold())) { + allowed = false; + break; + } + } + } + } + else { + foreach(unowned FileInfo fi in files) { + unowned string name = fi.get_name(); + if(match_case) { + if(allowed_base_name != name) { + allowed = false; + break; + } + } + else { + if(allowed_base_name.collate(name) != 0) { + allowed = false; + break; + } + } + } + } + return allowed; + } +#endif + + bool match_try_exec(const FileInfoList& files); + + bool match_show_if_registered(const FileInfoList& files); + + bool match_show_if_true(const FileInfoList& files); + + bool match_show_if_running(const FileInfoList& files); + + bool match_mime_type(const FileInfoList& files, const char* type, bool negated); + + bool match_mime_types(const FileInfoList& files); + + bool match_base_name(const FileInfoList& files, const char* base_name, bool negated); + + bool match_base_names(const FileInfoList& files); + + static bool match_scheme(const FileInfoList& files, const char* scheme, bool negated); + + bool match_schemes(const FileInfoList& files); + + static bool match_folder(const FileInfoList& files, const char* folder, bool negated); + + bool match_folders(const FileInfoList& files); + + bool match_selection_count(const FileInfoList &files); + + bool match_capabilities(const FileInfoList& files); + + bool match(const FileInfoList& files); + + CStrArrayPtr only_show_in; + CStrArrayPtr not_show_in; + CStrPtr try_exec; + CStrPtr show_if_registered; + CStrPtr show_if_true; + CStrPtr show_if_running; + CStrArrayPtr mime_types; + CStrArrayPtr base_names; + bool match_case; + char selection_count_cmp; + int selection_count; + CStrArrayPtr schemes; + CStrArrayPtr folders; + FileActionCapability capabilities; +}; + +} + +#endif // FILEACTIONCONDITION_H diff --git a/src/customactions/fileactionprofile.cpp b/src/customactions/fileactionprofile.cpp new file mode 100644 index 0000000..e356703 --- /dev/null +++ b/src/customactions/fileactionprofile.cpp @@ -0,0 +1,121 @@ +#include "fileactionprofile.h" +#include "fileaction.h" +#include + +using namespace std; + +namespace Fm { + +FileActionProfile::FileActionProfile(GKeyFile *kf, const char* profile_name) { + id = profile_name; + std::string group_name = "X-Action-Profile " + id; + name = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "Name", nullptr)}; + exec = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "Exec", nullptr)}; + // stdout.printf("id: %s, Exec: %s\n", id, exec); + + path = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "Path", nullptr)}; + auto s = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "ExecutionMode", nullptr)}; + if(s) { + if(strcmp(s.get(), "Normal") == 0) { + exec_mode = FileActionExecMode::NORMAL; + } + else if(strcmp(s.get(), "Terminal") == 0) { + exec_mode = FileActionExecMode::TERMINAL; + } + else if(strcmp(s.get(), "Embedded") == 0) { + exec_mode = FileActionExecMode::EMBEDDED; + } + else if(strcmp(s.get(), "DisplayOutput") == 0) { + exec_mode = FileActionExecMode::DISPLAY_OUTPUT; + } + else { + exec_mode = FileActionExecMode::NORMAL; + } + } + + startup_notify = g_key_file_get_boolean(kf, group_name.c_str(), "StartupNotify", nullptr); + startup_wm_class = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "StartupWMClass", nullptr)}; + exec_as = CStrPtr{g_key_file_get_string(kf, group_name.c_str(), "ExecuteAs", nullptr)}; + + condition = make_shared(kf, group_name.c_str()); +} + + +bool FileActionProfile::launch_once(GAppLaunchContext* /*ctx*/, std::shared_ptr first_file, const FileInfoList& files, CStrPtr& output) { + if(exec == nullptr) { + return false; + } + auto exec_cmd = FileActionObject::expand_str(exec.get(), files, false, first_file); + bool ret = false; + if(exec_mode == FileActionExecMode::DISPLAY_OUTPUT) { + int exit_status; + char* output_buf = nullptr; + ret = g_spawn_command_line_sync(exec_cmd.c_str(), &output_buf, nullptr, &exit_status, nullptr); + if(ret) { + ret = (exit_status == 0); + } + output = CStrPtr{output_buf}; + } + else { + /* + AppInfoCreateFlags flags = AppInfoCreateFlags.NONE; + if(startup_notify) + flags |= AppInfoCreateFlags.SUPPORTS_STARTUP_NOTIFICATION; + if(exec_mode == FileActionExecMode::TERMINAL || + exec_mode == FileActionExecMode::EMBEDDED) + flags |= AppInfoCreateFlags.NEEDS_TERMINAL; + GLib.AppInfo app = Fm.AppInfo.create_from_commandline(exec, nullptr, flags); + stdout.printf("Execute command line: %s\n\n", exec); + ret = app.launch(nullptr, ctx); + */ + + // NOTE: we cannot use GAppInfo here since GAppInfo does + // command line parsing which involving %u, %f, and other + // code defined in desktop entry spec. + // This may conflict with DES EMA parameters. + // FIXME: so how to handle this cleaner? + // Maybe we should leave all %% alone and don't translate + // them to %. Then GAppInfo will translate them to %, not + // codes specified in DES. + ret = g_spawn_command_line_async(exec_cmd.c_str(), nullptr); + } + return ret; +} + + +bool FileActionProfile::launch(GAppLaunchContext* ctx, const FileInfoList& files, CStrPtr& output) { + bool plural_form = FileActionObject::is_plural_exec(exec.get()); + bool ret; + if(plural_form) { // plural form command, handle all files at a time + ret = launch_once(ctx, files.front(), files, output); + } + else { // singular form command, run once for each file + GString* all_output = g_string_sized_new(1024); + bool show_output = false; + for(auto& fi: files) { + CStrPtr one_output; + launch_once(ctx, fi, files, one_output); + if(one_output) { + show_output = true; + // FIXME: how to handle multiple output std::strings properly? + g_string_append(all_output, one_output.get()); + g_string_append(all_output, "\n"); + } + } + if(show_output) { + output = CStrPtr{g_string_free(all_output, false)}; + } + else { + g_string_free(all_output, true); + } + ret = true; + } + return ret; +} + +bool FileActionProfile::match(FileInfoList files) { + // stdout.printf(" match profile: %s\n", id); + return condition->match(files); +} + +} diff --git a/src/customactions/fileactionprofile.h b/src/customactions/fileactionprofile.h new file mode 100644 index 0000000..18b959f --- /dev/null +++ b/src/customactions/fileactionprofile.h @@ -0,0 +1,45 @@ +#ifndef FILEACTIONPROFILE_H +#define FILEACTIONPROFILE_H + + +#include +#include +#include + +#include "../core/fileinfo.h" +#include "fileactioncondition.h" + +namespace Fm { + +enum class FileActionExecMode { + NORMAL, + TERMINAL, + EMBEDDED, + DISPLAY_OUTPUT +}; + +class FileActionProfile { +public: + explicit FileActionProfile(GKeyFile* kf, const char* profile_name); + + bool launch_once(GAppLaunchContext* ctx, std::shared_ptr first_file, const FileInfoList& files, CStrPtr& output); + + bool launch(GAppLaunchContext* ctx, const FileInfoList& files, CStrPtr& output); + + bool match(FileInfoList files); + + std::string id; + CStrPtr name; + CStrPtr exec; + CStrPtr path; + FileActionExecMode exec_mode; + bool startup_notify; + CStrPtr startup_wm_class; + CStrPtr exec_as; + + std::shared_ptr condition; +}; + +} // namespace Fm + +#endif // FILEACTIONPROFILE_H diff --git a/src/deepcountjob.h b/src/deepcountjob.h deleted file mode 100644 index 1eda2fe..0000000 --- a/src/deepcountjob.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_DEEP_COUNT_JOB_H__ -#define __LIBFM_QT_FM_DEEP_COUNT_JOB_H__ - -#include -#include -#include -#include "libfmqtglobals.h" -#include "job.h" - -namespace Fm { - - -class LIBFM_QT_API DeepCountJob: public Job { -public: - - - DeepCountJob(FmPathList* paths, FmDeepCountJobFlags flags) { - dataPtr_ = reinterpret_cast(fm_deep_count_job_new(paths, flags)); - } - - - // default constructor - DeepCountJob() { - dataPtr_ = nullptr; - } - - - DeepCountJob(FmDeepCountJob* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(g_object_ref(dataPtr)) : nullptr; - } - - - // copy constructor - DeepCountJob(const DeepCountJob& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - DeepCountJob(DeepCountJob&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - - // create a wrapper for the data pointer without increasing the reference count - static DeepCountJob wrapPtr(FmDeepCountJob* dataPtr) { - DeepCountJob obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmDeepCountJob* takeDataPtr() { - FmDeepCountJob* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmDeepCountJob* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmDeepCountJob*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - DeepCountJob& operator=(const DeepCountJob& other) { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - DeepCountJob& operator=(DeepCountJob&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - void setDest(dev_t dev, const char* fs_id) { - fm_deep_count_job_set_dest(dataPtr(), dev, fs_id); - } - - - -}; - - -} - -#endif // __LIBFM_QT_FM_DEEP_COUNT_JOB_H__ diff --git a/src/dirlistjob.h b/src/dirlistjob.h deleted file mode 100644 index 0c509a8..0000000 --- a/src/dirlistjob.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_DIR_LIST_JOB_H__ -#define __LIBFM_QT_FM_DIR_LIST_JOB_H__ - -#include -#include -#include -#include "libfmqtglobals.h" -#include "job.h" - -namespace Fm { - - -class LIBFM_QT_API DirListJob: public Job { -public: - - - DirListJob(FmPath* path, gboolean dir_only) { - dataPtr_ = reinterpret_cast(fm_dir_list_job_new(path, dir_only)); - } - - - // default constructor - DirListJob() { - dataPtr_ = nullptr; - } - - - DirListJob(FmDirListJob* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(g_object_ref(dataPtr)) : nullptr; - } - - - // copy constructor - DirListJob(const DirListJob& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - DirListJob(DirListJob&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - - // create a wrapper for the data pointer without increasing the reference count - static DirListJob wrapPtr(FmDirListJob* dataPtr) { - DirListJob obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmDirListJob* takeDataPtr() { - FmDirListJob* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmDirListJob* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmDirListJob*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - DirListJob& operator=(const DirListJob& other) { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - DirListJob& operator=(DirListJob&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - void addFoundFile(FmFileInfo* file) { - fm_dir_list_job_add_found_file(dataPtr(), file); - } - - - void setIncremental(gboolean set) { - fm_dir_list_job_set_incremental(dataPtr(), set); - } - - - FmFileInfoList* getFiles(void) { - return fm_dir_list_job_get_files(dataPtr()); - } - - - static DirListJob newForGfile(GFile* gf) { - return DirListJob::wrapPtr(fm_dir_list_job_new_for_gfile(gf)); - } - - - static DirListJob new2(FmPath* path, FmDirListJobFlags flags) { - return DirListJob::wrapPtr(fm_dir_list_job_new2(path, flags)); - } - - - -}; - - -} - -#endif // __LIBFM_QT_FM_DIR_LIST_JOB_H__ diff --git a/src/dirtreemodel.cpp b/src/dirtreemodel.cpp index 6947327..2d87de9 100644 --- a/src/dirtreemodel.cpp +++ b/src/dirtreemodel.cpp @@ -20,185 +20,213 @@ #include "dirtreemodel.h" #include "dirtreemodelitem.h" #include +#include "core/fileinfojob.h" namespace Fm { DirTreeModel::DirTreeModel(QObject* parent): - showHidden_(false) { + QAbstractItemModel(parent), + showHidden_(false) { } DirTreeModel::~DirTreeModel() { } +void DirTreeModel::addRoots(Fm::FilePathList rootPaths) { + auto job = new Fm::FileInfoJob{std::move(rootPaths)}; + job->setAutoDelete(true); + connect(job, &Fm::FileInfoJob::finished, this, &DirTreeModel::onFileInfoJobFinished, Qt::BlockingQueuedConnection); + job->runAsync(); +} + +void DirTreeModel::onFileInfoJobFinished() { + auto job = static_cast(sender()); + for(auto file: job->files()) { + addRoot(std::move(file)); + } +} + // QAbstractItemModel implementation Qt::ItemFlags DirTreeModel::flags(const QModelIndex& index) const { - DirTreeModelItem* item = itemFromIndex(index); - if(item && item->isPlaceHolder()) - return Qt::ItemIsEnabled; - return QAbstractItemModel::flags(index); + DirTreeModelItem* item = itemFromIndex(index); + if(item && item->isPlaceHolder()) { + return Qt::ItemIsEnabled; + } + return QAbstractItemModel::flags(index); } QVariant DirTreeModel::data(const QModelIndex& index, int role) const { - if(!index.isValid() || index.column() > 1) { - return QVariant(); - } - DirTreeModelItem* item = itemFromIndex(index); - if(item) { - FmFileInfo* info = item->fileInfo_; - switch(role) { - case Qt::ToolTipRole: - return QVariant(item->displayName_); - case Qt::DisplayRole: - return QVariant(item->displayName_); - case Qt::DecorationRole: - return QVariant(item->icon_); - case FileInfoRole: - return qVariantFromValue((void*)info); + if(!index.isValid() || index.column() > 1) { + return QVariant(); + } + DirTreeModelItem* item = itemFromIndex(index); + if(item) { + auto info = item->fileInfo_; + switch(role) { + case Qt::ToolTipRole: + return QVariant(item->displayName_); + case Qt::DisplayRole: + return QVariant(item->displayName_); + case Qt::DecorationRole: + return QVariant(item->icon_); + case FileInfoRole: { + QVariant v; + v.setValue(info); + return v; + } + } } - } - return QVariant(); + return QVariant(); } -int DirTreeModel::columnCount(const QModelIndex& parent) const { - return 1; +int DirTreeModel::columnCount(const QModelIndex& /*parent*/) const { + return 1; } int DirTreeModel::rowCount(const QModelIndex& parent) const { - if(!parent.isValid()) - return rootItems_.count(); - DirTreeModelItem* item = itemFromIndex(parent); - if(item) - return item->children_.count(); - return 0; + if(!parent.isValid()) { + return rootItems_.size(); + } + DirTreeModelItem* item = itemFromIndex(parent); + if(item) { + return item->children_.size(); + } + return 0; } QModelIndex DirTreeModel::parent(const QModelIndex& child) const { - DirTreeModelItem* item = itemFromIndex(child); - if(item && item->parent_) { - item = item->parent_; // go to parent item - if(item) { - const QList& items = item->parent_ ? item->parent_->children_ : rootItems_; - int row = items.indexOf(item); // this is Q(n) and may be slow :-( - if(row >= 0) - return createIndex(row, 0, (void*)item); + DirTreeModelItem* item = itemFromIndex(child); + if(item && item->parent_) { + item = item->parent_; // go to parent item + if(item) { + const auto& items = item->parent_ ? item->parent_->children_ : rootItems_; + auto it = std::find(items.cbegin(), items.cend(), item); + if(it != items.cend()) { + int row = it - items.cbegin(); + return createIndex(row, 0, (void*)item); + } + } } - } - return QModelIndex(); + return QModelIndex(); } QModelIndex DirTreeModel::index(int row, int column, const QModelIndex& parent) const { - if(row >= 0 && column >= 0 && column == 0) { - if(!parent.isValid()) { // root items - if(row < rootItems_.count()) { - const DirTreeModelItem* item = rootItems_.at(row); - return createIndex(row, column, (void*)item); - } + if(row >= 0 && column >= 0 && column == 0) { + if(!parent.isValid()) { // root items + if(static_cast(row) < rootItems_.size()) { + const DirTreeModelItem* item = rootItems_.at(row); + return createIndex(row, column, (void*)item); + } + } + else { // child items + DirTreeModelItem* parentItem = itemFromIndex(parent); + if(static_cast(row) < parentItem->children_.size()) { + const DirTreeModelItem* item = parentItem->children_.at(row); + return createIndex(row, column, (void*)item); + } + } } - else { // child items - DirTreeModelItem* parentItem = itemFromIndex(parent); - if(row < parentItem->children_.count()) { - const DirTreeModelItem* item = parentItem->children_.at(row); - return createIndex(row, column, (void*)item); - } - } - } - return QModelIndex(); // invalid index + return QModelIndex(); // invalid index } bool DirTreeModel::hasChildren(const QModelIndex& parent) const { - DirTreeModelItem* item = itemFromIndex(parent); - return item ? !item->isPlaceHolder() : true; + DirTreeModelItem* item = itemFromIndex(parent); + return item ? !item->isPlaceHolder() : true; } QModelIndex DirTreeModel::indexFromItem(DirTreeModelItem* item) const { - Q_ASSERT(item); - const QList& items = item->parent_ ? item->parent_->children_ : rootItems_; - int row = items.indexOf(item); - if(row >= 0) - return createIndex(row, 0, (void*)item); - return QModelIndex(); + Q_ASSERT(item); + const auto& items = item->parent_ ? item->parent_->children_ : rootItems_; + auto it = std::find(items.cbegin(), items.cend(), item); + if(it != items.cend()) { + int row = it - items.cbegin(); + return createIndex(row, 0, (void*)item); + } + return QModelIndex(); } // public APIs -QModelIndex DirTreeModel::addRoot(FmFileInfo* root) { - DirTreeModelItem* item = new DirTreeModelItem(root, this); - int row = rootItems_.count(); - beginInsertRows(QModelIndex(), row, row); - item->fileInfo_ = fm_file_info_ref(root); - rootItems_.append(item); - // add_place_holder_child_item(model, item_l, NULL, FALSE); - endInsertRows(); - return QModelIndex(); +QModelIndex DirTreeModel::addRoot(std::shared_ptr root) { + DirTreeModelItem* item = new DirTreeModelItem(std::move(root), this); + int row = rootItems_.size(); + beginInsertRows(QModelIndex(), row, row); + rootItems_.push_back(item); + // add_place_holder_child_item(model, item_l, nullptr, FALSE); + endInsertRows(); + return createIndex(row, 0, (void*)item); } DirTreeModelItem* DirTreeModel::itemFromIndex(const QModelIndex& index) const { - return reinterpret_cast(index.internalPointer()); + return reinterpret_cast(index.internalPointer()); } -QModelIndex DirTreeModel::indexFromPath(FmPath* path) const { - DirTreeModelItem* item = itemFromPath(path); - return item ? item->index() : QModelIndex(); +QModelIndex DirTreeModel::indexFromPath(const Fm::FilePath &path) const { + DirTreeModelItem* item = itemFromPath(path); + return item ? item->index() : QModelIndex(); } -DirTreeModelItem* DirTreeModel::itemFromPath(FmPath* path) const { - Q_FOREACH(DirTreeModelItem* item, rootItems_) { - if(item->fileInfo_ && fm_path_equal(path, fm_file_info_get_path(item->fileInfo_))) { - return item; - } - else { - DirTreeModelItem* child = item->childFromPath(path, true); - if(child) - return child; +DirTreeModelItem* DirTreeModel::itemFromPath(const Fm::FilePath &path) const { + Q_FOREACH(DirTreeModelItem* item, rootItems_) { + if(item->fileInfo_ && path == item->fileInfo_->path()) { + return item; + } + else { + DirTreeModelItem* child = item->childFromPath(path, true); + if(child) { + return child; + } + } } - } - return NULL; + return nullptr; } void DirTreeModel::loadRow(const QModelIndex& index) { - DirTreeModelItem* item = itemFromIndex(index); - Q_ASSERT(item); - if(item && !item->isPlaceHolder()) - item->loadFolder(); + DirTreeModelItem* item = itemFromIndex(index); + Q_ASSERT(item); + if(item && !item->isPlaceHolder()) { + item->loadFolder(); + } } void DirTreeModel::unloadRow(const QModelIndex& index) { - DirTreeModelItem* item = itemFromIndex(index); - if(item && !item->isPlaceHolder()) - item->unloadFolder(); + DirTreeModelItem* item = itemFromIndex(index); + if(item && !item->isPlaceHolder()) { + item->unloadFolder(); + } } bool DirTreeModel::isLoaded(const QModelIndex& index) { - DirTreeModelItem* item = itemFromIndex(index); - return item ? item->loaded_ : false; + DirTreeModelItem* item = itemFromIndex(index); + return item ? item->loaded_ : false; } QIcon DirTreeModel::icon(const QModelIndex& index) { - DirTreeModelItem* item = itemFromIndex(index); - return item ? item->icon_ : QIcon(); + DirTreeModelItem* item = itemFromIndex(index); + return item ? item->icon_ : QIcon(); } -FmFileInfo* DirTreeModel::fileInfo(const QModelIndex& index) { - DirTreeModelItem* item = itemFromIndex(index); - return item ? item->fileInfo_ : NULL; +std::shared_ptr DirTreeModel::fileInfo(const QModelIndex& index) { + DirTreeModelItem* item = itemFromIndex(index); + return item ? item->fileInfo_ : nullptr; } -FmPath* DirTreeModel::filePath(const QModelIndex& index) { - DirTreeModelItem* item = itemFromIndex(index); - return item && item->fileInfo_ ? fm_file_info_get_path(item->fileInfo_) : NULL; +Fm::FilePath DirTreeModel::filePath(const QModelIndex& index) { + DirTreeModelItem* item = itemFromIndex(index); + return (item && item->fileInfo_) ? item->fileInfo_->path() : Fm::FilePath{}; } QString DirTreeModel::dispName(const QModelIndex& index) { - DirTreeModelItem* item = itemFromIndex(index); - return item ? item->displayName_ : QString(); + DirTreeModelItem* item = itemFromIndex(index); + return item ? item->displayName_ : QString(); } void DirTreeModel::setShowHidden(bool show_hidden) { - showHidden_ = show_hidden; - Q_FOREACH(DirTreeModelItem* item, rootItems_) { - item->setShowHidden(show_hidden); - } + showHidden_ = show_hidden; + Q_FOREACH(DirTreeModelItem* item, rootItems_) { + item->setShowHidden(show_hidden); + } } diff --git a/src/dirtreemodel.h b/src/dirtreemodel.h index c82ff3b..c254713 100644 --- a/src/dirtreemodel.h +++ b/src/dirtreemodel.h @@ -27,6 +27,10 @@ #include #include #include +#include + +#include "core/fileinfo.h" +#include "core/filepath.h" namespace Fm { @@ -34,56 +38,63 @@ class DirTreeModelItem; class DirTreeView; class LIBFM_QT_API DirTreeModel : public QAbstractItemModel { - Q_OBJECT + Q_OBJECT public: - friend class DirTreeModelItem; // allow direct access of private members in DirTreeModelItem - friend class DirTreeView; // allow direct access of private members in DirTreeView + friend class DirTreeModelItem; // allow direct access of private members in DirTreeModelItem + friend class DirTreeView; // allow direct access of private members in DirTreeView - enum Role { - FileInfoRole = Qt::UserRole - }; + enum Role { + FileInfoRole = Qt::UserRole + }; - explicit DirTreeModel(QObject* parent); - ~DirTreeModel(); + explicit DirTreeModel(QObject* parent); + ~DirTreeModel(); - QModelIndex addRoot(FmFileInfo* root); - void loadRow(const QModelIndex& index); - void unloadRow(const QModelIndex& index); + void addRoots(Fm::FilePathList rootPaths); - bool isLoaded(const QModelIndex& index); - QIcon icon(const QModelIndex& index); - FmFileInfo* fileInfo(const QModelIndex& index); - FmPath* filePath(const QModelIndex& index); - QString dispName(const QModelIndex& index); + void loadRow(const QModelIndex& index); + void unloadRow(const QModelIndex& index); - void setShowHidden(bool show_hidden); - bool showHidden() const { - return showHidden_; - } + bool isLoaded(const QModelIndex& index); + QIcon icon(const QModelIndex& index); + std::shared_ptr fileInfo(const QModelIndex& index); + Fm::FilePath filePath(const QModelIndex& index); + QString dispName(const QModelIndex& index); - QModelIndex indexFromPath(FmPath* path) const; + void setShowHidden(bool show_hidden); + bool showHidden() const { + return showHidden_; + } - virtual Qt::ItemFlags flags(const QModelIndex& index) const; - virtual QVariant data(const QModelIndex& index, int role) const; - virtual int columnCount(const QModelIndex& parent) const; - virtual int rowCount(const QModelIndex& parent) const; - virtual QModelIndex parent(const QModelIndex& child) const; - virtual QModelIndex index(int row, int column, const QModelIndex& parent) const; - virtual bool hasChildren(const QModelIndex& parent = QModelIndex()) const; + QModelIndex indexFromPath(const Fm::FilePath& path) const; -private: - DirTreeModelItem* itemFromPath(FmPath* path) const; - DirTreeModelItem* itemFromIndex(const QModelIndex& index) const; - QModelIndex indexFromItem(DirTreeModelItem* item) const; + virtual Qt::ItemFlags flags(const QModelIndex& index) const; + virtual QVariant data(const QModelIndex& index, int role) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual int rowCount(const QModelIndex& parent) const; + virtual QModelIndex parent(const QModelIndex& child) const; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const; + virtual bool hasChildren(const QModelIndex& parent = QModelIndex()) const; Q_SIGNALS: - void rowLoaded(const QModelIndex& index); + void rowLoaded(const QModelIndex& index); + +private Q_SLOTS: + void onFileInfoJobFinished(); private: - bool showHidden_; - QList rootItems_; + QModelIndex addRoot(std::shared_ptr root); + + DirTreeModelItem* itemFromPath(const Fm::FilePath& path) const; + DirTreeModelItem* itemFromIndex(const QModelIndex& index) const; + QModelIndex indexFromItem(DirTreeModelItem* item) const; + +private: + bool showHidden_; + std::vector rootItems_; }; + } #endif // FM_DIRTREEMODEL_H diff --git a/src/dirtreemodelitem.cpp b/src/dirtreemodelitem.cpp index 602ff3d..52dc1dd 100644 --- a/src/dirtreemodelitem.cpp +++ b/src/dirtreemodelitem.cpp @@ -25,313 +25,393 @@ namespace Fm { DirTreeModelItem::DirTreeModelItem(): - fileInfo_(nullptr), - folder_(nullptr), - expanded_(false), - loaded_(false), - parent_(nullptr), - placeHolderChild_(nullptr), - model_(nullptr) { + fileInfo_(nullptr), + folder_(nullptr), + expanded_(false), + loaded_(false), + parent_(nullptr), + placeHolderChild_(nullptr), + model_(nullptr), + queuedForDeletion_(false) { } -DirTreeModelItem::DirTreeModelItem(FmFileInfo* info, DirTreeModel* model, DirTreeModelItem* parent): - fileInfo_(fm_file_info_ref(info)), - folder_(nullptr), - displayName_(QString::fromUtf8(fm_file_info_get_disp_name(info))), - icon_(IconTheme::icon(fm_file_info_get_icon(info))), - expanded_(false), - loaded_(false), - parent_(parent), - placeHolderChild_(nullptr), - model_(model) { - - if(info) - addPlaceHolderChild(); +DirTreeModelItem::DirTreeModelItem(std::shared_ptr info, DirTreeModel* model, DirTreeModelItem* parent): + fileInfo_{std::move(info)}, + expanded_(false), + loaded_(false), + parent_(parent), + placeHolderChild_(nullptr), + model_(model), + queuedForDeletion_(false) { + + if(fileInfo_) { + displayName_ = fileInfo_->displayName(); + icon_ = fileInfo_->icon()->qicon(); + addPlaceHolderChild(); + } } DirTreeModelItem::~DirTreeModelItem() { - if(fileInfo_) - fm_file_info_unref(fileInfo_); - - if(folder_) freeFolder(); - - // delete child items if needed - if(!children_.isEmpty()) { - Q_FOREACH(DirTreeModelItem* item, children_) { - delete item; + // delete child items if needed + if(!children_.empty()) { + Q_FOREACH(DirTreeModelItem* item, children_) { + delete item; + } } - } - if(!hiddenChildren_.isEmpty()) { - Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) { - delete item; + if(!hiddenChildren_.empty()) { + Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) { + delete item; + } } - } + /*if(queuedForDeletion_) + qDebug() << "queued deletion done";*/ } -void DirTreeModelItem::addPlaceHolderChild() { - placeHolderChild_ = new DirTreeModelItem(); - placeHolderChild_->parent_ = this; - placeHolderChild_->model_ = model_; - placeHolderChild_->displayName_ = DirTreeModel::tr("Loading..."); - children_.append(placeHolderChild_); +void DirTreeModelItem::freeFolder() { + if(folder_) { + QObject::disconnect(onFolderFinishLoadingConn_); + QObject::disconnect(onFolderFilesAddedConn_); + QObject::disconnect(onFolderFilesRemovedConn_); + QObject::disconnect(onFolderFilesChangedConn_); + folder_.reset(); + } } -void DirTreeModelItem::freeFolder() { - if(folder_) { - g_signal_handlers_disconnect_by_func(folder_, gpointer(onFolderFinishLoading), this); - g_signal_handlers_disconnect_by_func(folder_, gpointer(onFolderFilesAdded), this); - g_signal_handlers_disconnect_by_func(folder_, gpointer(onFolderFilesRemoved), this); - g_signal_handlers_disconnect_by_func(folder_, gpointer(onFolderFilesChanged), this); - g_object_unref(folder_); - folder_ = nullptr; - } +void DirTreeModelItem::addPlaceHolderChild() { + placeHolderChild_ = new DirTreeModelItem(); + placeHolderChild_->parent_ = this; + placeHolderChild_->model_ = model_; + placeHolderChild_->displayName_ = DirTreeModel::tr("Loading..."); + children_.push_back(placeHolderChild_); } void DirTreeModelItem::loadFolder() { - if(!expanded_) { - /* dynamically load content of the folder. */ - folder_ = fm_folder_from_path(fm_file_info_get_path(fileInfo_)); - /* g_debug("fm_dir_tree_model_load_row()"); */ - /* associate the data with loaded handler */ - g_signal_connect(folder_, "finish-loading", G_CALLBACK(onFolderFinishLoading), this); - g_signal_connect(folder_, "files-added", G_CALLBACK(onFolderFilesAdded), this); - g_signal_connect(folder_, "files-removed", G_CALLBACK(onFolderFilesRemoved), this); - g_signal_connect(folder_, "files-changed", G_CALLBACK(onFolderFilesChanged), this); - - /* set 'expanded' flag beforehand as callback may check it */ - expanded_ = true; - /* if the folder is already loaded, call "loaded" handler ourselves */ - if(fm_folder_is_loaded(folder_)) { // already loaded - GList* file_l; - FmFileInfoList* files = fm_folder_get_files(folder_); - for(file_l = fm_file_info_list_peek_head_link(files); file_l; file_l = file_l->next) { - FmFileInfo* fi = FM_FILE_INFO(file_l->data); - if(fm_file_info_is_dir(fi)) { - insertFileInfo(fi); + if(!expanded_) { + /* dynamically load content of the folder. */ + folder_ = Fm::Folder::fromPath(fileInfo_->path()); + /* g_debug("fm_dir_tree_model_load_row()"); */ + /* associate the data with loaded handler */ + + onFolderFinishLoadingConn_ = QObject::connect(folder_.get(), &Fm::Folder::finishLoading, model_, [=]() { + onFolderFinishLoading(); + }); + onFolderFilesAddedConn_ = QObject::connect(folder_.get(), &Fm::Folder::filesAdded, model_, [=](Fm::FileInfoList files) { + onFolderFilesAdded(files); + }); + onFolderFilesRemovedConn_ = QObject::connect(folder_.get(), &Fm::Folder::filesRemoved, model_, [=](Fm::FileInfoList files) { + onFolderFilesRemoved(files); + }); + onFolderFilesChangedConn_ = QObject::connect(folder_.get(), &Fm::Folder::filesChanged, model_, [=](std::vector& changes) { + onFolderFilesChanged(changes); + }); + + /* set 'expanded' flag beforehand as callback may check it */ + expanded_ = true; + /* if the folder is already loaded, call "loaded" handler ourselves */ + if(folder_->isLoaded()) { // already loaded + insertFiles(folder_->files()); + onFolderFinishLoading(); } - } - onFolderFinishLoading(folder_, this); } - } } void DirTreeModelItem::unloadFolder() { - if(expanded_) { /* do some cleanup */ - /* remove all children, and replace them with a dummy child - * item to keep expander in the tree view around. */ - - // delete all visible child items - model_->beginRemoveRows(index(), 0, children_.count() - 1); - if(!children_.isEmpty()) { - Q_FOREACH(DirTreeModelItem* item, children_) { - delete item; - } - children_.clear(); - } - model_->endRemoveRows(); - - // remove hidden children - if(!hiddenChildren_.isEmpty()) { - Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) { - delete item; - } - hiddenChildren_.clear(); - } + if(expanded_) { /* do some cleanup */ + /* remove all children, and replace them with a dummy child + * item to keep expander in the tree view around. */ + + // delete all visible child items + model_->beginRemoveRows(index(), 0, children_.size() - 1); + if(!children_.empty()) { + Q_FOREACH(DirTreeModelItem* item, children_) { + delete item; + } + children_.clear(); + } + model_->endRemoveRows(); + + // remove hidden children + if(!hiddenChildren_.empty()) { + Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) { + delete item; + } + hiddenChildren_.clear(); + } - /* now, we have no child since all child items are removed. - * So we add a place holder child item to keep the expander around. */ - addPlaceHolderChild(); - /* deactivate folder since it will be reactivated on expand */ - freeFolder(); - expanded_ = false; - loaded_ = false; - } + /* now, we have no child since all child items are removed. + * So we add a place holder child item to keep the expander around. */ + addPlaceHolderChild(); + /* deactivate folder since it will be reactivated on expand */ + freeFolder(); + expanded_ = false; + loaded_ = false; + } } QModelIndex DirTreeModelItem::index() { - Q_ASSERT(model_); - return model_->indexFromItem(this); + Q_ASSERT(model_); + return model_->indexFromItem(this); } -/* Add file info to parent node to proper position. - * GtkTreePath tp is the tree path of parent node. */ -DirTreeModelItem* DirTreeModelItem::insertFileInfo(FmFileInfo* fi) { - // qDebug() << "insertFileInfo: " << fm_file_info_get_disp_name(fi); - DirTreeModelItem* item = new DirTreeModelItem(fi, model_); - insertItem(item); - return item; +/* Add file info to parent node to proper position. */ +DirTreeModelItem* DirTreeModelItem::insertFile(std::shared_ptr fi) { + // qDebug() << "insertFileInfo: " << fm_file_info_get_disp_name(fi); + DirTreeModelItem* item = new DirTreeModelItem(std::move(fi), model_); + insertItem(item); + return item; +} + +/* Add file info to parent node to proper position. */ +void DirTreeModelItem::insertFiles(Fm::FileInfoList files) { + if(children_.size() == 1 && placeHolderChild_) { + // the list is empty, add them all at once and do sort + if(!model_->showHidden()) { // need to separate visible and hidden items + // insert hidden files into the "hiddenChildren_" list and remove them from "files" list + // WARNING: "std::remove_if" shouldn't be used to work on the "removed" items because, as + // docs say, the elements between the returned and the end iterators are in an unspecified + // state and, as far as I (@tsujan) have tested, some of them announce themselves as null. + for(auto it = files.begin(); it != files.end();) { + auto file = *it; + if(file->isHidden()) { + hiddenChildren_.push_back(new DirTreeModelItem{std::move(file), model_}); + it = files.erase(it); + } + else { + ++it; + } + } + + } + // sort the remaining visible files by name + std::sort(files.begin(), files.end(), [](const std::shared_ptr& a, const std::shared_ptr& b) { + return QString::localeAwareCompare(a->displayName(), b->displayName()) < 0; + }); + // insert the files into the visible children list at once + model_->beginInsertRows(index(), 1, files.size() + 1); // the first item is the placeholder item, so we start from row 1 + for(auto& file: files) { + if(file->isDir()) { + DirTreeModelItem* newItem = new DirTreeModelItem(std::move(file), model_); + newItem->parent_ = this; + children_.push_back(newItem); + } + } + model_->endInsertRows(); + + // remove the place holder if a folder is added + if(children_.size() > 1) { + auto it = std::find(children_.cbegin(), children_.cend(), placeHolderChild_); + if(it != children_.cend()) { + auto pos = it - children_.cbegin(); + model_->beginRemoveRows(index(), pos, pos); + children_.erase(it); + delete placeHolderChild_; + model_->endRemoveRows(); + placeHolderChild_ = nullptr; + } + } + } + else { + // the list already contain some items, insert new items one by one so they can be sorted. + for(auto& file: files) { + if(file->isDir()) { + insertFile(std::move(file)); + } + } + } } // find a good position to insert the new item +// FIXME: insert one item at a time is slow. Insert multiple items at once and then sort is faster. int DirTreeModelItem::insertItem(DirTreeModelItem* newItem) { - if(model_->showHidden() || !newItem->fileInfo_ || !fm_file_info_is_hidden(newItem->fileInfo_)) { - const char* new_key = fm_file_info_get_collate_key(newItem->fileInfo_); - int pos = 0; - QList::iterator it; - for(it = children_.begin(); it != children_.end(); ++it) { - DirTreeModelItem* child = *it; - if(G_UNLIKELY(!child->fileInfo_)) - continue; - const char* key = fm_file_info_get_collate_key(child->fileInfo_); - if(strcmp(new_key, key) <= 0) - break; - ++pos; + if(!newItem->fileInfo_ || !newItem->fileInfo_->isDir()) { + // don't insert placeholders or non-directory files + return -1; } - // inform the world that we're about to insert the item - model_->beginInsertRows(index(), pos, pos); - newItem->parent_ = this; - children_.insert(it, newItem); - model_->endInsertRows(); - return pos; - } - else { // hidden folder - hiddenChildren_.append(newItem); - } - return -1; + if(model_->showHidden() || !newItem->fileInfo_ || !newItem->fileInfo_->isHidden()) { + auto it = std::lower_bound(children_.cbegin(), children_.cend(), newItem, [=](const DirTreeModelItem* a, const DirTreeModelItem* b) { + if(Q_UNLIKELY(!a->fileInfo_)) { + return true; // this is a placeholder item which will be removed so the order doesn't matter. + } + if(Q_UNLIKELY(!b->fileInfo_)) { + return false; + } + return QString::localeAwareCompare(a->fileInfo_->displayName(), b->fileInfo_->displayName()) < 0; + }); + // inform the world that we're about to insert the item + auto position = it - children_.begin(); + model_->beginInsertRows(index(), position, position); + newItem->parent_ = this; + children_.insert(it, newItem); + model_->endInsertRows(); + return position; + } + else { // hidden folder + hiddenChildren_.push_back(newItem); + } + return -1; } // FmFolder signal handlers -// static -void DirTreeModelItem::onFolderFinishLoading(FmFolder* folder, gpointer user_data) { - DirTreeModelItem* _this = (DirTreeModelItem*)user_data; - DirTreeModel* model = _this->model_; - /* set 'loaded' flag beforehand as callback may check it */ - _this->loaded_ = true; - QModelIndex index = _this->index(); -qDebug() << "folder loaded"; - // remove the placeholder child if needed - if(_this->children_.count() == 1) { // we have no other child other than the place holder item, leave it - _this->placeHolderChild_->displayName_ = DirTreeModel::tr(""); - QModelIndex placeHolderIndex = _this->placeHolderChild_->index(); - // qDebug() << "placeHolderIndex: "<dataChanged(placeHolderIndex, placeHolderIndex); - } - else { - int pos = _this->children_.indexOf(_this->placeHolderChild_); - model->beginRemoveRows(index, pos, pos); - _this->children_.removeAt(pos); - delete _this->placeHolderChild_; - model->endRemoveRows(); - _this->placeHolderChild_ = nullptr; - } - - Q_EMIT model->rowLoaded(index); +void DirTreeModelItem::onFolderFinishLoading() { + DirTreeModel* model = model_; + /* set 'loaded' flag beforehand as callback may check it */ + loaded_ = true; + QModelIndex idx = index(); + //qDebug() << "folder loaded"; + // remove the placeholder child if needed + // (a check for its existence is necessary; see insertItem) + if(placeHolderChild_) { + if(children_.size() == 1) { // we have no other child other than the place holder item, leave it + placeHolderChild_->displayName_ = DirTreeModel::tr(""); + QModelIndex placeHolderIndex = placeHolderChild_->index(); + // qDebug() << "placeHolderIndex: "<dataChanged(placeHolderIndex, placeHolderIndex); + } + else { + auto it = std::find(children_.cbegin(), children_.cend(), placeHolderChild_); + if(it != children_.cend()) { + auto pos = it - children_.cbegin(); + model->beginRemoveRows(idx, pos, pos); + children_.erase(it); + delete placeHolderChild_; + model->endRemoveRows(); + placeHolderChild_ = nullptr; + } + } + } + + Q_EMIT model->rowLoaded(idx); } -// static -void DirTreeModelItem::onFolderFilesAdded(FmFolder* folder, GSList* files, gpointer user_data) { - GSList* l; - DirTreeModelItem* _this = (DirTreeModelItem*)user_data; - for(l = files; l; l = l->next) { - FmFileInfo* fi = FM_FILE_INFO(l->data); - if(fm_file_info_is_dir(fi)) { /* FIXME: maybe adding files can be allowed later */ - /* Ideally FmFolder should not emit files-added signals for files that - * already exists. So there is no need to check for duplication here. */ - _this->insertFileInfo(fi); - } - } +void DirTreeModelItem::onFolderFilesAdded(Fm::FileInfoList& files) { + insertFiles(files); } -// static -void DirTreeModelItem::onFolderFilesRemoved(FmFolder* folder, GSList* files, gpointer user_data) { - DirTreeModelItem* _this = (DirTreeModelItem*)user_data; - DirTreeModel* model = _this->model_; - - for(GSList* l = files; l; l = l->next) { - FmFileInfo* fi = FM_FILE_INFO(l->data); - int pos; - DirTreeModelItem* child = _this->childFromName(fm_file_info_get_name(fi), &pos); - if(child) { - model->beginRemoveRows(_this->index(), pos, pos); - _this->children_.removeAt(pos); - delete child; - model->endRemoveRows(); +void DirTreeModelItem::onFolderFilesRemoved(Fm::FileInfoList& files) { + DirTreeModel* model = model_; + + for(auto& fi: files) { + int pos; + DirTreeModelItem* child = childFromName(fi->name().c_str(), &pos); + if(child) { + // The item shouldn't be deleted now but after its row is removed from QTreeView; + // otherwise a freeze will happen when it has a child item (its row is expanded). + child->queuedForDeletion_ = true; + model->beginRemoveRows(index(), pos, pos); + children_.erase(children_.cbegin() + pos); + model->endRemoveRows(); + + } + } + + if(children_.empty()) { // no visible children, add a placeholder item to keep the row expanded + addPlaceHolderChild(); + placeHolderChild_->displayName_ = DirTreeModel::tr(""); } - } } -// static -void DirTreeModelItem::onFolderFilesChanged(FmFolder* folder, GSList* files, gpointer user_data) { - DirTreeModelItem* _this = (DirTreeModelItem*)user_data; - DirTreeModel* model = _this->model_; - - for(GSList* l = files; l; l = l->next) { - FmFileInfo* changedFile = FM_FILE_INFO(l->data); - int pos; - DirTreeModelItem* child = _this->childFromName(fm_file_info_get_name(changedFile), &pos); - if(child) { - QModelIndex childIndex = child->index(); - Q_EMIT model->dataChanged(childIndex, childIndex); +void DirTreeModelItem::onFolderFilesChanged(std::vector &changes) { + DirTreeModel* model = model_; + for(auto& changePair: changes) { + int pos; + auto& changedFile = changePair.first; + DirTreeModelItem* child = childFromName(changedFile->name().c_str(), &pos); + if(child) { + QModelIndex childIndex = child->index(); + Q_EMIT model->dataChanged(childIndex, childIndex); + } } - } } DirTreeModelItem* DirTreeModelItem::childFromName(const char* utf8_name, int* pos) { - int i = 0; - for (const auto item : children_) { - if(item->fileInfo_ && strcmp(fm_file_info_get_name(item->fileInfo_), utf8_name) == 0) { - if(pos) - *pos = i; - return item; + int i = 0; + for(const auto item : children_) { + if(item->fileInfo_ && item->fileInfo_->name() == utf8_name) { + if(pos) { + *pos = i; + } + return item; + } + ++i; } - ++i; - } - return nullptr; + return nullptr; } -DirTreeModelItem* DirTreeModelItem::childFromPath(FmPath* path, bool recursive) const { - Q_ASSERT(path != nullptr); - - Q_FOREACH(DirTreeModelItem* item, children_) { - // if(item->fileInfo_) - // qDebug() << "child: " << QString::fromUtf8(fm_file_info_get_disp_name(item->fileInfo_)); - if(item->fileInfo_ && fm_path_equal(fm_file_info_get_path(item->fileInfo_), path)) { - return item; - } else if(recursive) { - DirTreeModelItem* child = item->childFromPath(path, true); - if(child) - return child; +DirTreeModelItem* DirTreeModelItem::childFromPath(Fm::FilePath path, bool recursive) const { + Q_ASSERT(path != nullptr); + + Q_FOREACH(DirTreeModelItem* item, children_) { + // if(item->fileInfo_) + // qDebug() << "child: " << QString::fromUtf8(fm_file_info_get_disp_name(item->fileInfo_)); + if(item->fileInfo_ && item->fileInfo_->path() == path) { + return item; + } + else if(recursive) { + DirTreeModelItem* child = item->childFromPath(std::move(path), true); + if(child) { + return child; + } + } } - } - return nullptr; + return nullptr; } void DirTreeModelItem::setShowHidden(bool show) { - if(show) { - // move all hidden children to visible list - Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) { - insertItem(item); + if(show) { + // move all hidden children to visible list + for(auto item: hiddenChildren_) { + insertItem(item); + } + hiddenChildren_.clear(); + // remove the placeholder if needed + if(children_.size() > 1) { + auto it = std::find(children_.cbegin(), children_.cend(), placeHolderChild_); + if(it != children_.cend()) { + auto pos = it - children_.cbegin(); + model_->beginRemoveRows(index(), pos, pos); + children_.erase(it); + delete placeHolderChild_; + model_->endRemoveRows(); + placeHolderChild_ = nullptr; + } + } + // recursively show children of children, etc. + for(auto item: children_) { + item->setShowHidden(true); + } } - hiddenChildren_.clear(); - } - else { // hide hidden folders - QModelIndex _index = index(); - QList::iterator it, next; - int pos = 0; - for(it = children_.begin(); it != children_.end(); ++pos) { - DirTreeModelItem* item = *it; - next = it + 1; - if(item->fileInfo_) { - if(fm_file_info_is_hidden(item->fileInfo_)) { // hidden folder - // remove from the model and add to the hiddenChildren_ list - model_->beginRemoveRows(_index, pos, pos); - children_.erase(it); - hiddenChildren_.append(item); - model_->endRemoveRows(); + else { // hide hidden folders + QModelIndex _index = index(); + int pos = 0; + for(auto it = children_.begin(); it != children_.end(); ++pos) { + DirTreeModelItem* item = *it; + if(item->fileInfo_) { + if(item->fileInfo_->isHidden()) { // hidden folder + // remove from the model and add to the hiddenChildren_ list + model_->beginRemoveRows(_index, pos, pos); + it = children_.erase(it); + hiddenChildren_.push_back(item); + model_->endRemoveRows(); + } + else { // visible folder, recursively filter its children + item->setShowHidden(show); + ++it; + } + } + else { + ++it; + } } - else { // visible folder, recursively filter its children - item->setShowHidden(show); + if(children_.empty()) { // no visible children, add a placeholder item to keep the row expanded + addPlaceHolderChild(); + placeHolderChild_->displayName_ = DirTreeModel::tr(""); } - } - it = next; } - } } } // namespace Fm - diff --git a/src/dirtreemodelitem.h b/src/dirtreemodelitem.h index 4d77ffb..9b6e8d0 100644 --- a/src/dirtreemodelitem.h +++ b/src/dirtreemodelitem.h @@ -22,10 +22,13 @@ #include "libfmqtglobals.h" #include +#include #include -#include #include +#include "core/fileinfo.h" +#include "core/folder.h" + namespace Fm { class DirTreeModel; @@ -33,49 +36,61 @@ class DirTreeView; class LIBFM_QT_API DirTreeModelItem { public: - friend class DirTreeModel; // allow direct access of private members in DirTreeModel - friend class DirTreeView; // allow direct access of private members in DirTreeView + friend class DirTreeModel; // allow direct access of private members in DirTreeModel + friend class DirTreeView; // allow direct access of private members in DirTreeView + + explicit DirTreeModelItem(); + explicit DirTreeModelItem(std::shared_ptr info, DirTreeModel* model, DirTreeModelItem* parent = nullptr); + ~DirTreeModelItem(); - explicit DirTreeModelItem(); - explicit DirTreeModelItem(FmFileInfo* info, DirTreeModel* model, DirTreeModelItem* parent = nullptr); - ~DirTreeModelItem(); + void loadFolder(); + void unloadFolder(); - void loadFolder(); - void unloadFolder(); + inline bool isPlaceHolder() const { + return (fileInfo_ == nullptr); + } - inline bool isPlaceHolder() const { - return (fileInfo_ == nullptr); - } + void setShowHidden(bool show); - void setShowHidden(bool show); + bool isQueuedForDeletion() { + return queuedForDeletion_; + } + private: - void freeFolder(); - void addPlaceHolderChild(); - DirTreeModelItem* childFromName(const char* utf8_name, int* pos); - DirTreeModelItem* childFromPath(FmPath* path, bool recursive) const; + void freeFolder(); + void addPlaceHolderChild(); + DirTreeModelItem* childFromName(const char* utf8_name, int* pos); + DirTreeModelItem* childFromPath(Fm::FilePath path, bool recursive) const; - DirTreeModelItem* insertFileInfo(FmFileInfo* fi); - int insertItem(Fm::DirTreeModelItem* newItem); - QModelIndex index(); + DirTreeModelItem* insertFile(std::shared_ptr fi); + void insertFiles(Fm::FileInfoList files); + int insertItem(Fm::DirTreeModelItem* newItem); + QModelIndex index(); - static void onFolderFinishLoading(FmFolder* folder, gpointer user_data); - static void onFolderFilesAdded(FmFolder* folder, GSList* files, gpointer user_data); - static void onFolderFilesRemoved(FmFolder* folder, GSList* files, gpointer user_data); - static void onFolderFilesChanged(FmFolder* folder, GSList* files, gpointer user_data); + void onFolderFinishLoading(); + void onFolderFilesAdded(Fm::FileInfoList &files); + void onFolderFilesRemoved(Fm::FileInfoList &files); + void onFolderFilesChanged(std::vector& changes); private: - FmFileInfo* fileInfo_; - FmFolder* folder_; - QString displayName_ ; - QIcon icon_; - bool expanded_; - bool loaded_; - DirTreeModelItem* parent_; - DirTreeModelItem* placeHolderChild_; - QList children_; - QList hiddenChildren_; - DirTreeModel* model_; + std::shared_ptr fileInfo_; + std::shared_ptr folder_; + QString displayName_ ; + QIcon icon_; + bool expanded_; + bool loaded_; + DirTreeModelItem* parent_; + DirTreeModelItem* placeHolderChild_; + std::vector children_; + std::vector hiddenChildren_; + DirTreeModel* model_; + bool queuedForDeletion_; + // signal connections + QMetaObject::Connection onFolderFinishLoadingConn_; + QMetaObject::Connection onFolderFilesAddedConn_; + QMetaObject::Connection onFolderFilesRemovedConn_; + QMetaObject::Connection onFolderFilesChangedConn_; }; } diff --git a/src/dirtreeview.cpp b/src/dirtreeview.cpp index 8b58374..a16c7df 100644 --- a/src/dirtreeview.cpp +++ b/src/dirtreeview.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "dirtreemodel.h" #include "dirtreemodelitem.h" #include "filemenu.h" @@ -30,268 +31,309 @@ namespace Fm { DirTreeView::DirTreeView(QWidget* parent): - currentPath_(NULL), - currentExpandingItem_(NULL) { + QTreeView(parent), + currentExpandingItem_(nullptr) { - setSelectionMode(QAbstractItemView::SingleSelection); - setHeaderHidden(true); - setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); - header()->setStretchLastSection(false); + setSelectionMode(QAbstractItemView::SingleSelection); + setHeaderHidden(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + header()->setStretchLastSection(false); - connect(this, &DirTreeView::collapsed, this, &DirTreeView::onCollapsed); - connect(this, &DirTreeView::expanded, this, &DirTreeView::onExpanded); + connect(this, &DirTreeView::collapsed, this, &DirTreeView::onCollapsed); + connect(this, &DirTreeView::expanded, this, &DirTreeView::onExpanded); - setContextMenuPolicy(Qt::CustomContextMenu); - connect(this, &DirTreeView::customContextMenuRequested, - this, &DirTreeView::onCustomContextMenuRequested); + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &DirTreeView::customContextMenuRequested, + this, &DirTreeView::onCustomContextMenuRequested); } DirTreeView::~DirTreeView() { - if(currentPath_) - fm_path_unref(currentPath_); } void DirTreeView::cancelPendingChdir() { - if(!pathsToExpand_.isEmpty()) { - pathsToExpand_.clear(); - if(!currentExpandingItem_) - return; - DirTreeModel* _model = static_cast(model()); - disconnect(_model, &DirTreeModel::rowLoaded, this, &DirTreeView::onRowLoaded); - currentExpandingItem_ = NULL; - } + if(!pathsToExpand_.empty()) { + pathsToExpand_.clear(); + if(!currentExpandingItem_) { + return; + } + DirTreeModel* _model = static_cast(model()); + disconnect(_model, &DirTreeModel::rowLoaded, this, &DirTreeView::onRowLoaded); + currentExpandingItem_ = nullptr; + } } void DirTreeView::expandPendingPath() { - if(pathsToExpand_.isEmpty()) - return; - - FmPath* path = pathsToExpand_.first(); - // qDebug() << "expanding: " << Path(path).displayBasename(); - DirTreeModel* _model = static_cast(model()); - DirTreeModelItem* item = _model->itemFromPath(path); - // qDebug() << "findItem: " << item; - if(item) { - currentExpandingItem_ = item; - connect(_model, &DirTreeModel::rowLoaded, this, &DirTreeView::onRowLoaded); - if(item->loaded_) { // the node is already loaded - onRowLoaded(item->index()); + if(pathsToExpand_.empty()) { + return; + } + + auto path = pathsToExpand_.front(); + // qDebug() << "expanding: " << Path(path).displayBasename(); + DirTreeModel* _model = static_cast(model()); + DirTreeModelItem* item = _model->itemFromPath(path); + // qDebug() << "findItem: " << item; + if(item) { + currentExpandingItem_ = item; + connect(_model, &DirTreeModel::rowLoaded, this, &DirTreeView::onRowLoaded); + if(item->loaded_) { // the node is already loaded + onRowLoaded(item->index()); + } + else { + // _model->loadRow(item->index()); + item->loadFolder(); + } } else { - // _model->loadRow(item->index()); - item->loadFolder(); + selectionModel()->clear(); + /* since we never get it loaded we need to update cwd here */ + currentPath_ = path; + + cancelPendingChdir(); // FIXME: is this correct? this is not done in the gtk+ version of libfm. } - } - else { - selectionModel()->clear(); - /* since we never get it loaded we need to update cwd here */ - if(currentPath_) - fm_path_unref(currentPath_); - currentPath_ = fm_path_ref(path); - - cancelPendingChdir(); // FIXME: is this correct? this is not done in the gtk+ version of libfm. - } } void DirTreeView::onRowLoaded(const QModelIndex& index) { - DirTreeModel* _model = static_cast(model()); - if(!currentExpandingItem_) - return; - if(currentExpandingItem_ != _model->itemFromIndex(index)) { - return; - } - /* disconnect the handler since we only need it once */ - disconnect(_model, &DirTreeModel::rowLoaded, this, &DirTreeView::onRowLoaded); - - // DirTreeModelItem* item = _model->itemFromIndex(index); - // qDebug() << "row loaded: " << item->displayName_; - /* after the folder is loaded, the files should have been added to - * the tree model */ - expand(index); - - /* remove the expanded path from pending list */ - pathsToExpand_.removeFirst(); - if(pathsToExpand_.isEmpty()) { /* this is the last one and we're done, select the item */ - // qDebug() << "Done!"; - selectionModel()->select(index, QItemSelectionModel::SelectCurrent|QItemSelectionModel::Clear); - scrollTo(index, QAbstractItemView::EnsureVisible); - } - else { /* continue expanding next pending path */ - expandPendingPath(); - } + DirTreeModel* _model = static_cast(model()); + if(!currentExpandingItem_) { + return; + } + if(currentExpandingItem_ != _model->itemFromIndex(index)) { + return; + } + /* disconnect the handler since we only need it once */ + disconnect(_model, &DirTreeModel::rowLoaded, this, &DirTreeView::onRowLoaded); + + // DirTreeModelItem* item = _model->itemFromIndex(index); + // qDebug() << "row loaded: " << item->displayName_; + /* after the folder is loaded, the files should have been added to + * the tree model */ + expand(index); + + /* remove the expanded path from pending list */ + pathsToExpand_.erase(pathsToExpand_.begin()); + if(pathsToExpand_.empty()) { /* this is the last one and we're done, select the item */ + // qDebug() << "Done!"; + selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Clear); + scrollTo(index, QAbstractItemView::EnsureVisible); + } + else { /* continue expanding next pending path */ + expandPendingPath(); + } } -void DirTreeView::setCurrentPath(FmPath* path) { - DirTreeModel* _model = static_cast(model()); - if(!_model) - return; - int rowCount = _model->rowCount(QModelIndex()); - if(rowCount <= 0 || fm_path_equal(currentPath_, path)) - return; - - if(currentPath_) - fm_path_unref(currentPath_); - currentPath_ = fm_path_ref(path); - - // NOTE: The content of each node is loaded on demand dynamically. - // So, when we ask for a chdir operation, some nodes do not exists yet. - // We have to wait for the loading of child nodes and continue the - // pending chdir operation after the child nodes become available. - - // cancel previous pending tree expansion - cancelPendingChdir(); - - /* find a root item containing this path */ - FmPath* root; - for(int row = 0; row < rowCount; ++row) { - QModelIndex index = _model->index(row, 0, QModelIndex()); - root = _model->filePath(index); - if(fm_path_has_prefix(path, root)) - break; - root = NULL; - } - - if(root) { /* root item is found */ - do { /* add path elements one by one to a list */ - pathsToExpand_.prepend(path); - // qDebug() << "prepend path: " << Path(path).displayBasename(); - if(fm_path_equal(path, root)) - break; - path = fm_path_get_parent(path); +void DirTreeView::setCurrentPath(Fm::FilePath path) { + DirTreeModel* _model = static_cast(model()); + if(!_model) { + return; } - while(path); + int rowCount = _model->rowCount(QModelIndex()); + if(rowCount <= 0 || currentPath_ == path) { + return; + } + + currentPath_ = std::move(path); + + // NOTE: The content of each node is loaded on demand dynamically. + // So, when we ask for a chdir operation, some nodes do not exists yet. + // We have to wait for the loading of child nodes and continue the + // pending chdir operation after the child nodes become available. - expandPendingPath(); - } + // cancel previous pending tree expansion + cancelPendingChdir(); + + /* find a root item containing this path */ + Fm::FilePath root; + for(int row = 0; row < rowCount; ++row) { + QModelIndex index = _model->index(row, 0, QModelIndex()); + auto row_path = _model->filePath(index); + if(row_path.isPrefixOf(currentPath_)) { + root = row_path; + break; + } + } + + if(root) { /* root item is found */ + path = currentPath_; + do { /* add path elements one by one to a list */ + pathsToExpand_.insert(pathsToExpand_.cbegin(), path); + // qDebug() << "prepend path: " << Path(path).displayBasename(); + if(path == root) { + break; + } + path = path.parent(); + } + while(path); + + expandPendingPath(); + } } void DirTreeView::setModel(QAbstractItemModel* model) { - Q_ASSERT(model->inherits("Fm::DirTreeModel")); + Q_ASSERT(model->inherits("Fm::DirTreeModel")); - if(!pathsToExpand_.isEmpty()) // if a chdir request is in progress, cancel it - cancelPendingChdir(); + if(!pathsToExpand_.empty()) { // if a chdir request is in progress, cancel it + cancelPendingChdir(); + } - QTreeView::setModel(model); - header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &DirTreeView::onSelectionChanged); + QTreeView::setModel(model); + header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &DirTreeView::onSelectionChanged); } void DirTreeView::mousePressEvent(QMouseEvent* event) { - if(event && event->button() == Qt::RightButton && - event->type() == QEvent::MouseButtonPress) { - // Do not change the selection when the context menu is activated. - return; - } - QTreeView::mousePressEvent(event); + if(event && event->button() == Qt::RightButton && + event->type() == QEvent::MouseButtonPress) { + // Do not change the selection when the context menu is activated. + return; + } + QTreeView::mousePressEvent(event); } void DirTreeView::onCustomContextMenuRequested(const QPoint& pos) { - QModelIndex index = indexAt(pos); - if(index.isValid()) { - QVariant data = index.data(DirTreeModel::FileInfoRole); - FmFileInfo* fileInfo = reinterpret_cast(data.value()); - if(fileInfo) { - FmPath* path = fm_file_info_get_path(fileInfo); - FmFileInfoList* files = fm_file_info_list_new(); - fm_file_info_list_push_tail(files, fileInfo); - Fm::FileMenu* menu = new Fm::FileMenu(files, fileInfo, path); - // FIXME: apply some settings to the menu and set a proper file launcher to it - Q_EMIT prepareFileMenu(menu); - fm_file_info_list_unref(files); - QVariant pathData = qVariantFromValue(reinterpret_cast(path)); - QAction* action = menu->openAction(); - action->disconnect(); - action->setData(index); - connect(action, &QAction::triggered, this, &DirTreeView::onOpen); - action = new QAction(QIcon::fromTheme("window-new"), tr("Open in New T&ab"), menu); - action->setData(pathData); - connect(action, &QAction::triggered, this, &DirTreeView::onNewTab); - menu->insertAction(menu->separator1(), action); - action = new QAction(QIcon::fromTheme("window-new"), tr("Open in New Win&dow"), menu); - action->setData(pathData); - connect(action, &QAction::triggered, this, &DirTreeView::onNewWindow); - menu->insertAction(menu->separator1(), action); - if(fm_file_info_is_native(fileInfo)) { - action = new QAction(QIcon::fromTheme("utilities-terminal"), tr("Open in Termina&l"), menu); - action->setData(pathData); - connect(action, &QAction::triggered, this, &DirTreeView::onOpenInTerminal); - menu->insertAction(menu->separator1(), action); - } - menu->exec(mapToGlobal(pos)); - delete menu; + QModelIndex index = indexAt(pos); + if(index.isValid()) { + QVariant data = index.data(DirTreeModel::FileInfoRole); + auto fileInfo = data.value>(); + if(fileInfo) { + auto path = fileInfo->path(); + Fm::FileInfoList files ; + files.push_back(fileInfo); + Fm::FileMenu* menu = new Fm::FileMenu(files, fileInfo, path); + // FIXME: apply some settings to the menu and set a proper file launcher to it + Q_EMIT prepareFileMenu(menu); + + QVariant pathData = qVariantFromValue(path); + QAction* action = menu->openAction(); + action->disconnect(); + action->setData(index); + connect(action, &QAction::triggered, this, &DirTreeView::onOpen); + action = new QAction(QIcon::fromTheme("window-new"), tr("Open in New T&ab"), menu); + action->setData(pathData); + connect(action, &QAction::triggered, this, &DirTreeView::onNewTab); + menu->insertAction(menu->separator1(), action); + action = new QAction(QIcon::fromTheme("window-new"), tr("Open in New Win&dow"), menu); + action->setData(pathData); + connect(action, &QAction::triggered, this, &DirTreeView::onNewWindow); + menu->insertAction(menu->separator1(), action); + if(fileInfo->isNative()) { + action = new QAction(QIcon::fromTheme("utilities-terminal"), tr("Open in Termina&l"), menu); + action->setData(pathData); + connect(action, &QAction::triggered, this, &DirTreeView::onOpenInTerminal); + menu->insertAction(menu->separator1(), action); + } + menu->exec(mapToGlobal(pos)); + delete menu; + } } - } } void DirTreeView::onOpen() { - if(QAction* action = qobject_cast(sender())) { - setCurrentIndex(action->data().toModelIndex()); - } + if(QAction* action = qobject_cast(sender())) { + setCurrentIndex(action->data().toModelIndex()); + } } void DirTreeView::onNewWindow() { - if(QAction* action = qobject_cast(sender())) { - FmPath* path = reinterpret_cast(action->data().value()); - Q_EMIT openFolderInNewWindowRequested(path); - } + if(QAction* action = qobject_cast(sender())) { + auto path = action->data().value(); + Q_EMIT openFolderInNewWindowRequested(path); + } } void DirTreeView::onNewTab() { - if(QAction* action = qobject_cast(sender())) { - FmPath* path = reinterpret_cast(action->data().value()); - Q_EMIT openFolderInNewTabRequested(path); - } + if(QAction* action = qobject_cast(sender())) { + auto path = action->data().value(); + Q_EMIT openFolderInNewTabRequested(path); + } } void DirTreeView::onOpenInTerminal() { - if(QAction* action = qobject_cast(sender())) { - FmPath* path = reinterpret_cast(action->data().value()); - Q_EMIT openFolderInTerminalRequested(path); - } + if(QAction* action = qobject_cast(sender())) { + auto path = action->data().value(); + Q_EMIT openFolderInTerminalRequested(path); + } } void DirTreeView::onNewFolder() { - if(QAction* action = qobject_cast(sender())) { - FmPath* path = reinterpret_cast(action->data().value()); - Q_EMIT createNewFolderRequested(path); - } + if(QAction* action = qobject_cast(sender())) { + auto path = action->data().value(); + Q_EMIT createNewFolderRequested(path); + } } void DirTreeView::onCollapsed(const QModelIndex& index) { - DirTreeModel* treeModel = static_cast(model()); - if(treeModel) { - treeModel->unloadRow(index); - } + DirTreeModel* treeModel = static_cast(model()); + if(treeModel) { + treeModel->unloadRow(index); + } } void DirTreeView::onExpanded(const QModelIndex& index) { - DirTreeModel* treeModel = static_cast(model()); - if(treeModel) { - treeModel->loadRow(index); - } + DirTreeModel* treeModel = static_cast(model()); + if(treeModel) { + treeModel->loadRow(index); + } +} +void DirTreeView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { + // see if to-be-removed items are queued for deletion + // and also clear selection if one of them is selected (otherwise a freeze will occur) + QModelIndex selIndex; + if(selectionModel()->selectedRows().size() == 1) { + selIndex = selectionModel()->selectedRows().at(0); + } + for (int i = start; i <= end; ++i) { + QModelIndex index = parent.child(i, 0); + if(index.isValid()) { + if(index == selIndex) { + selectionModel()->clear(); + } + DirTreeModelItem* item = reinterpret_cast(index.internalPointer()); + if (item->isQueuedForDeletion()) { + queuedForDeletion_.push_back(item); + } + } + } + + QTreeView::rowsAboutToBeRemoved (parent, start, end); } -void DirTreeView::onSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected) { - if(!selected.isEmpty()) { - QModelIndex index = selected.first().topLeft(); - DirTreeModel* _model = static_cast(model()); - FmPath* path = _model->filePath(index); - if(path && currentPath_ && fm_path_equal(path, currentPath_)) - return; - cancelPendingChdir(); - if(!path) - return; - if(currentPath_) - fm_path_unref(currentPath_); - currentPath_ = fm_path_ref(path); - - // FIXME: use enums for type rather than hard-coded values 0 or 1 - int type = 0; - if(QGuiApplication::mouseButtons() & Qt::MiddleButton) - type = 1; - Q_EMIT chdirRequested(type, path); - } +void DirTreeView::rowsRemoved(const QModelIndex& parent, int start, int end) { + QTreeView::rowsRemoved (parent, start, end); + // do the queued deletions only after all rows are removed (otherwise a freeze might occur) + QTimer::singleShot(0, this, SLOT (doQueuedDeletions())); +} + +void DirTreeView::doQueuedDeletions() { + if(!queuedForDeletion_.empty()) { + Q_FOREACH(DirTreeModelItem* item, queuedForDeletion_) { + delete item; + } + queuedForDeletion_.clear(); + } +} + +void DirTreeView::onSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/) { + if(!selected.isEmpty()) { + QModelIndex index = selected.first().topLeft(); + DirTreeModel* _model = static_cast(model()); + auto path = _model->filePath(index); + if(path && currentPath_ && path == currentPath_) { + return; + } + cancelPendingChdir(); + if(!path) { + return; + } + currentPath_ = std::move(path); + + // FIXME: use enums for type rather than hard-coded values 0 or 1 + int type = 0; + if(QGuiApplication::mouseButtons() & Qt::MiddleButton) { + type = 1; + } + Q_EMIT chdirRequested(type, currentPath_); + } } diff --git a/src/dirtreeview.h b/src/dirtreeview.h index 44f4981..b32bd2e 100644 --- a/src/dirtreeview.h +++ b/src/dirtreeview.h @@ -16,14 +16,15 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ - + #ifndef FM_DIRTREEVIEW_H #define FM_DIRTREEVIEW_H #include "libfmqtglobals.h" #include #include -#include "path.h" + +#include "core/filepath.h" class QItemSelection; @@ -33,60 +34,59 @@ class FileMenu; class DirTreeModelItem; class LIBFM_QT_API DirTreeView : public QTreeView { - Q_OBJECT + Q_OBJECT public: - DirTreeView(QWidget* parent); - ~DirTreeView(); - - FmPath* currentPath() { - return currentPath_; - } + explicit DirTreeView(QWidget* parent); + ~DirTreeView(); - void setCurrentPath(FmPath* path); + const Fm::FilePath& currentPath() const { + return currentPath_; + } - // libfm-gtk compatible alias - FmPath* getCwd() { - return currentPath(); - } + void setCurrentPath(Fm::FilePath path); - void chdir(FmPath* path) { - setCurrentPath(path); - } + void chdir(Fm::FilePath path) { + setCurrentPath(std::move(path)); + } - virtual void setModel(QAbstractItemModel* model); + virtual void setModel(QAbstractItemModel* model); protected: - virtual void mousePressEvent(QMouseEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + virtual void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); private: - void cancelPendingChdir(); - void expandPendingPath(); + void cancelPendingChdir(); + void expandPendingPath(); Q_SIGNALS: - void chdirRequested(int type, FmPath* path); - void openFolderInNewWindowRequested(FmPath* path); - void openFolderInNewTabRequested(FmPath* path); - void openFolderInTerminalRequested(FmPath* path); - void createNewFolderRequested(FmPath* path); - void prepareFileMenu(Fm::FileMenu* menu); // emit before showing a Fm::FileMenu + void chdirRequested(int type, const Fm::FilePath& path); + void openFolderInNewWindowRequested(const Fm::FilePath& path); + void openFolderInNewTabRequested(const Fm::FilePath& path); + void openFolderInTerminalRequested(const Fm::FilePath& path); + void createNewFolderRequested(const Fm::FilePath& path); + void prepareFileMenu(Fm::FileMenu* menu); // emit before showing a Fm::FileMenu protected Q_SLOTS: - void onCollapsed(const QModelIndex & index); - void onExpanded(const QModelIndex & index); - void onRowLoaded(const QModelIndex& index); - void onSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected); - void onCustomContextMenuRequested(const QPoint& pos); - void onOpen(); - void onNewWindow(); - void onNewTab(); - void onOpenInTerminal(); - void onNewFolder(); + void onCollapsed(const QModelIndex& index); + void onExpanded(const QModelIndex& index); + void onRowLoaded(const QModelIndex& index); + void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void onCustomContextMenuRequested(const QPoint& pos); + void onOpen(); + void onNewWindow(); + void onNewTab(); + void onOpenInTerminal(); + void onNewFolder(); + void rowsRemoved(const QModelIndex& parent, int start, int end); + void doQueuedDeletions(); private: - FmPath* currentPath_; - QList pathsToExpand_; - DirTreeModelItem* currentExpandingItem_; + Fm::FilePath currentPath_; + Fm::FilePathList pathsToExpand_; + DirTreeModelItem* currentExpandingItem_; + std::vector queuedForDeletion_; }; } diff --git a/src/dndactionmenu.cpp b/src/dndactionmenu.cpp index 73625ee..2688376 100644 --- a/src/dndactionmenu.cpp +++ b/src/dndactionmenu.cpp @@ -23,20 +23,22 @@ namespace Fm { DndActionMenu::DndActionMenu(Qt::DropActions possibleActions, QWidget* parent) - : QMenu(parent) - , copyAction(nullptr) - , moveAction(nullptr) - , linkAction(nullptr) - , cancelAction(nullptr) -{ - if (possibleActions.testFlag(Qt::CopyAction)) - copyAction = addAction(QIcon::fromTheme("edit-copy"), tr("Copy here")); - if (possibleActions.testFlag(Qt::MoveAction)) - moveAction = addAction(tr("Move here")); - if (possibleActions.testFlag(Qt::LinkAction)) - linkAction = addAction(tr("Create symlink here")); - addSeparator(); - cancelAction = addAction(tr("Cancel")); + : QMenu(parent) + , copyAction(nullptr) + , moveAction(nullptr) + , linkAction(nullptr) + , cancelAction(nullptr) { + if(possibleActions.testFlag(Qt::CopyAction)) { + copyAction = addAction(QIcon::fromTheme("edit-copy"), tr("Copy here")); + } + if(possibleActions.testFlag(Qt::MoveAction)) { + moveAction = addAction(tr("Move here")); + } + if(possibleActions.testFlag(Qt::LinkAction)) { + linkAction = addAction(tr("Create symlink here")); + } + addSeparator(); + cancelAction = addAction(tr("Cancel")); } DndActionMenu::~DndActionMenu() { @@ -44,19 +46,21 @@ DndActionMenu::~DndActionMenu() { } Qt::DropAction DndActionMenu::askUser(Qt::DropActions possibleActions, QPoint pos) { - Qt::DropAction result = Qt::IgnoreAction; - DndActionMenu menu{possibleActions}; - QAction* action = menu.exec(pos); - if (nullptr != action) - { - if(action == menu.copyAction) - result = Qt::CopyAction; - else if(action == menu.moveAction) - result = Qt::MoveAction; - else if(action == menu.linkAction) - result = Qt::LinkAction; - } - return result; + Qt::DropAction result = Qt::IgnoreAction; + DndActionMenu menu{possibleActions}; + QAction* action = menu.exec(pos); + if(nullptr != action) { + if(action == menu.copyAction) { + result = Qt::CopyAction; + } + else if(action == menu.moveAction) { + result = Qt::MoveAction; + } + else if(action == menu.linkAction) { + result = Qt::LinkAction; + } + } + return result; } diff --git a/src/dndactionmenu.h b/src/dndactionmenu.h index 247aa1f..d4dd1bd 100644 --- a/src/dndactionmenu.h +++ b/src/dndactionmenu.h @@ -28,18 +28,18 @@ namespace Fm { class DndActionMenu : public QMenu { -Q_OBJECT + Q_OBJECT public: - explicit DndActionMenu(Qt::DropActions possibleActions, QWidget* parent = 0); - virtual ~DndActionMenu(); + explicit DndActionMenu(Qt::DropActions possibleActions, QWidget* parent = 0); + virtual ~DndActionMenu(); - static Qt::DropAction askUser(Qt::DropActions possibleActions, QPoint pos); + static Qt::DropAction askUser(Qt::DropActions possibleActions, QPoint pos); private: - QAction* copyAction; - QAction* moveAction; - QAction* linkAction; - QAction* cancelAction; + QAction* copyAction; + QAction* moveAction; + QAction* linkAction; + QAction* cancelAction; }; } diff --git a/src/dnddest.cpp b/src/dnddest.cpp index daf22f2..be20b66 100644 --- a/src/dnddest.cpp +++ b/src/dnddest.cpp @@ -41,7 +41,7 @@ bool DndDest::dropMimeData(const QMimeData* data, Qt::DropAction action) { // FIXME: should we put this in dropEvent handler of FolderView instead? if(data->hasUrls()) { qDebug("drop action: %d", action); - FmPathList* srcPaths = pathListFromQUrls(data->urls()); + auto srcPaths = pathListFromQUrls(data->urls()); switch(action) { case Qt::CopyAction: FileOperation::copyFiles(srcPaths, destPath_); @@ -52,20 +52,18 @@ bool DndDest::dropMimeData(const QMimeData* data, Qt::DropAction action) { case Qt::LinkAction: FileOperation::symlinkFiles(srcPaths, destPath_); default: - fm_path_list_unref(srcPaths); return false; } - fm_path_list_unref(srcPaths); return true; } return false; } -bool DndDest::isSupported(const QMimeData* data) { +bool DndDest::isSupported(const QMimeData* /*data*/) { return false; } -bool DndDest::isSupported(QString mimeType) { +bool DndDest::isSupported(QString /*mimeType*/) { return false; } diff --git a/src/dnddest.h b/src/dnddest.h index e292c3f..3efafd4 100644 --- a/src/dnddest.h +++ b/src/dnddest.h @@ -21,30 +21,30 @@ #define FM_DNDDEST_H #include -#include "path.h" +#include "core/filepath.h" namespace Fm { class DndDest { public: - DndDest(); - ~DndDest(); + explicit DndDest(); + ~DndDest(); - void setDestPath(FmPath* dest) { - destPath_ = dest; - } + void setDestPath(Fm::FilePath dest) { + destPath_ = std::move(dest); + } - const Path& destPath() { - return destPath_; - } + const Fm::FilePath& destPath() { + return destPath_; + } - bool isSupported(const QMimeData* data); - bool isSupported(QString mimeType); + bool isSupported(const QMimeData* data); + bool isSupported(QString mimeType); - bool dropMimeData(const QMimeData* data, Qt::DropAction action); + bool dropMimeData(const QMimeData* data, Qt::DropAction action); private: - Path destPath_; + Fm::FilePath destPath_; }; } diff --git a/src/dummymonitor.h b/src/dummymonitor.h deleted file mode 100644 index 1570250..0000000 --- a/src/dummymonitor.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_DUMMY_MONITOR_H__ -#define __LIBFM_QT_FM_DUMMY_MONITOR_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - - -} - -#endif // __LIBFM_QT_FM_DUMMY_MONITOR_H__ diff --git a/src/editbookmarksdialog.cpp b/src/editbookmarksdialog.cpp index 3fae409..36e0958 100644 --- a/src/editbookmarksdialog.cpp +++ b/src/editbookmarksdialog.cpp @@ -29,81 +29,83 @@ namespace Fm { EditBookmarksDialog::EditBookmarksDialog(FmBookmarks* bookmarks, QWidget* parent, Qt::WindowFlags f): - QDialog(parent, f), - ui(new Ui::EditBookmarksDialog()), - bookmarks_(FM_BOOKMARKS(g_object_ref(bookmarks))) { + QDialog(parent, f), + ui(new Ui::EditBookmarksDialog()), + bookmarks_(FM_BOOKMARKS(g_object_ref(bookmarks))) { - ui->setupUi(this); - setAttribute(Qt::WA_DeleteOnClose); // auto delete on close + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); // auto delete on close - // load bookmarks - GList* allBookmarks = fm_bookmarks_get_all(bookmarks_); - for(GList* l = allBookmarks; l; l = l->next) { - FmBookmarkItem* bookmark = reinterpret_cast(l->data); - QTreeWidgetItem* item = new QTreeWidgetItem(); - char* path_str = fm_path_display_name(bookmark->path, false); - item->setData(0, Qt::DisplayRole, QString::fromUtf8(bookmark->name)); - item->setData(1, Qt::DisplayRole, QString::fromUtf8(path_str)); - item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEditable|Qt::ItemIsDragEnabled|Qt::ItemIsEnabled); - g_free(path_str); - ui->treeWidget->addTopLevelItem(item); - } - g_list_free_full(allBookmarks, (GDestroyNotify)fm_bookmark_item_unref); + // load bookmarks + GList* allBookmarks = fm_bookmarks_get_all(bookmarks_); + for(GList* l = allBookmarks; l; l = l->next) { + FmBookmarkItem* bookmark = reinterpret_cast(l->data); + QTreeWidgetItem* item = new QTreeWidgetItem(); + char* path_str = fm_path_display_name(bookmark->path, false); + item->setData(0, Qt::DisplayRole, QString::fromUtf8(bookmark->name)); + item->setData(1, Qt::DisplayRole, QString::fromUtf8(path_str)); + item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled); + g_free(path_str); + ui->treeWidget->addTopLevelItem(item); + } + g_list_free_full(allBookmarks, (GDestroyNotify)fm_bookmark_item_unref); - connect(ui->addItem, &QPushButton::clicked, this, &EditBookmarksDialog::onAddItem); - connect(ui->removeItem, &QPushButton::clicked, this, &EditBookmarksDialog::onRemoveItem); + connect(ui->addItem, &QPushButton::clicked, this, &EditBookmarksDialog::onAddItem); + connect(ui->removeItem, &QPushButton::clicked, this, &EditBookmarksDialog::onRemoveItem); } EditBookmarksDialog::~EditBookmarksDialog() { - g_object_unref(bookmarks_); - delete ui; + g_object_unref(bookmarks_); + delete ui; } void EditBookmarksDialog::accept() { - // save bookmarks - // it's easier to recreate the whole bookmark file than - // to manipulate FmBookmarks object. So here we generate the file directly. - // FIXME: maybe in the future we should add a libfm API to easily replace all FmBookmarks. - // Here we use gtk+ 3.0 bookmarks rather than the gtk+ 2.0 one. - // Since gtk+ 2.24.12, gtk+2 reads gtk+3 bookmarks file if it exists. - // So it's safe to only save gtk+3 bookmarks file. - QString path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); - path += QLatin1String("/gtk-3.0"); - if(!QDir().mkpath(path)) - return; // fail to create ~/.config/gtk-3.0 dir - path += QLatin1String("/bookmarks"); - QSaveFile file(path); // use QSaveFile for atomic file operation - if(file.open(QIODevice::WriteOnly)){ - for(int row = 0; ; ++row) { - QTreeWidgetItem* item = ui->treeWidget->topLevelItem(row); - if(!item) - break; - QString name = item->data(0, Qt::DisplayRole).toString(); - QUrl url = QUrl::fromUserInput(item->data(1, Qt::DisplayRole).toString()); - file.write(url.toEncoded()); - file.write(" "); - file.write(name.toUtf8()); - file.write("\n"); + // save bookmarks + // it's easier to recreate the whole bookmark file than + // to manipulate FmBookmarks object. So here we generate the file directly. + // FIXME: maybe in the future we should add a libfm API to easily replace all FmBookmarks. + // Here we use gtk+ 3.0 bookmarks rather than the gtk+ 2.0 one. + // Since gtk+ 2.24.12, gtk+2 reads gtk+3 bookmarks file if it exists. + // So it's safe to only save gtk+3 bookmarks file. + QString path = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); + path += QLatin1String("/gtk-3.0"); + if(!QDir().mkpath(path)) { + return; // fail to create ~/.config/gtk-3.0 dir + } + path += QLatin1String("/bookmarks"); + QSaveFile file(path); // use QSaveFile for atomic file operation + if(file.open(QIODevice::WriteOnly)) { + for(int row = 0; ; ++row) { + QTreeWidgetItem* item = ui->treeWidget->topLevelItem(row); + if(!item) { + break; + } + QString name = item->data(0, Qt::DisplayRole).toString(); + QUrl url = QUrl::fromUserInput(item->data(1, Qt::DisplayRole).toString()); + file.write(url.toEncoded()); + file.write(" "); + file.write(name.toUtf8()); + file.write("\n"); + } + // FIXME: should we support Qt or KDE specific bookmarks in the future? + file.commit(); } - // FIXME: should we support Qt or KDE specific bookmarks in the future? - file.commit(); - } - QDialog::accept(); + QDialog::accept(); } void EditBookmarksDialog::onAddItem() { - QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setData(0, Qt::DisplayRole, tr("New bookmark")); - item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEditable|Qt::ItemIsDragEnabled|Qt::ItemIsEnabled); - ui->treeWidget->addTopLevelItem(item); - ui->treeWidget->editItem(item); + QTreeWidgetItem* item = new QTreeWidgetItem(); + item->setData(0, Qt::DisplayRole, tr("New bookmark")); + item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled); + ui->treeWidget->addTopLevelItem(item); + ui->treeWidget->editItem(item); } void EditBookmarksDialog::onRemoveItem() { - QList sels = ui->treeWidget->selectedItems(); - Q_FOREACH(QTreeWidgetItem* item, sels) { - delete item; - } + QList sels = ui->treeWidget->selectedItems(); + Q_FOREACH(QTreeWidgetItem* item, sels) { + delete item; + } } diff --git a/src/editbookmarksdialog.h b/src/editbookmarksdialog.h index bf76f9b..a3f2318 100644 --- a/src/editbookmarksdialog.h +++ b/src/editbookmarksdialog.h @@ -27,25 +27,25 @@ namespace Ui { class EditBookmarksDialog; -}; +} namespace Fm { class LIBFM_QT_API EditBookmarksDialog : public QDialog { -Q_OBJECT + Q_OBJECT public: - explicit EditBookmarksDialog(FmBookmarks* bookmarks, QWidget* parent = 0, Qt::WindowFlags f = 0); - virtual ~EditBookmarksDialog(); + explicit EditBookmarksDialog(FmBookmarks* bookmarks, QWidget* parent = 0, Qt::WindowFlags f = 0); + virtual ~EditBookmarksDialog(); - virtual void accept(); + virtual void accept(); private Q_SLOTS: - void onAddItem(); - void onRemoveItem(); + void onAddItem(); + void onRemoveItem(); private: - Ui::EditBookmarksDialog* ui; - FmBookmarks* bookmarks_; + Ui::EditBookmarksDialog* ui; + FmBookmarks* bookmarks_; }; } diff --git a/src/execfiledialog.cpp b/src/execfiledialog.cpp index 3759d0b..a1ec05f 100644 --- a/src/execfiledialog.cpp +++ b/src/execfiledialog.cpp @@ -20,50 +20,55 @@ #include "execfiledialog_p.h" #include "ui_exec-file.h" #include "icontheme.h" +#include "core/iconinfo.h" namespace Fm { ExecFileDialog::ExecFileDialog(FmFileInfo* file, QWidget* parent, Qt::WindowFlags f): - QDialog (parent, f), - ui(new Ui::ExecFileDialog()), - fileInfo_(fm_file_info_ref(file)), - result_(FM_FILE_LAUNCHER_EXEC_CANCEL) { + QDialog(parent, f), + ui(new Ui::ExecFileDialog()), + fileInfo_(fm_file_info_ref(file)), + result_(FM_FILE_LAUNCHER_EXEC_CANCEL) { - ui->setupUi(this); - // show file icon - FmIcon* icon = fm_file_info_get_icon(fileInfo_); - ui->icon->setPixmap(IconTheme::icon(icon).pixmap(QSize(48, 48))); + ui->setupUi(this); + // show file icon + GIcon* gicon = G_ICON(fm_file_info_get_icon(fileInfo_)); + ui->icon->setPixmap(Fm::IconInfo::fromGIcon(gicon)->qicon().pixmap(QSize(48, 48))); - QString msg; - if(fm_file_info_is_text(file)) { - msg = tr("This text file '%1' seems to be an executable script.\nWhat do you want to do with it?") - .arg(QString::fromUtf8(fm_file_info_get_disp_name(file))); - ui->execTerm->setDefault(true); - } - else { - msg= tr("This file '%1' is executable. Do you want to execute it?") - .arg(QString::fromUtf8(fm_file_info_get_disp_name(file))); - ui->exec->setDefault(true); - ui->open->hide(); - } - ui->msg->setText(msg); + QString msg; + if(fm_file_info_is_text(file)) { + msg = tr("This text file '%1' seems to be an executable script.\nWhat do you want to do with it?") + .arg(QString::fromUtf8(fm_file_info_get_disp_name(file))); + ui->execTerm->setDefault(true); + } + else { + msg = tr("This file '%1' is executable. Do you want to execute it?") + .arg(QString::fromUtf8(fm_file_info_get_disp_name(file))); + ui->exec->setDefault(true); + ui->open->hide(); + } + ui->msg->setText(msg); } ExecFileDialog::~ExecFileDialog() { - delete ui; - if(fileInfo_) - fm_file_info_unref(fileInfo_); + delete ui; + if(fileInfo_) { + fm_file_info_unref(fileInfo_); + } } void ExecFileDialog::accept() { - QObject* _sender = sender(); - if(_sender == ui->exec) - result_ = FM_FILE_LAUNCHER_EXEC; - else if(_sender == ui->execTerm) - result_ = FM_FILE_LAUNCHER_EXEC_IN_TERMINAL; - else if(_sender == ui->open) - result_ = FM_FILE_LAUNCHER_EXEC_OPEN; - QDialog::accept(); + QObject* _sender = sender(); + if(_sender == ui->exec) { + result_ = FM_FILE_LAUNCHER_EXEC; + } + else if(_sender == ui->execTerm) { + result_ = FM_FILE_LAUNCHER_EXEC_IN_TERMINAL; + } + else if(_sender == ui->open) { + result_ = FM_FILE_LAUNCHER_EXEC_OPEN; + } + QDialog::accept(); } } // namespace Fm diff --git a/src/file-operation-dialog.ui b/src/file-operation-dialog.ui index f81018e..f000afd 100644 --- a/src/file-operation-dialog.ui +++ b/src/file-operation-dialog.ui @@ -83,7 +83,7 @@ - + @@ -96,7 +96,7 @@ - + @@ -119,6 +119,20 @@ + + + + Data transferred: + + + + + + + + + + diff --git a/src/file.h b/src/file.h deleted file mode 100644 index 8902cda..0000000 --- a/src/file.h +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_FILE_H__ -#define __LIBFM_QT_FM_FILE_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - -class LIBFM_QT_API File { -public: - - - // default constructor - File() { - dataPtr_ = nullptr; - } - - - File(FmFile* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(g_object_ref(dataPtr)) : nullptr; - } - - - // copy constructor - File(const File& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - File(File&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - virtual ~File() { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static File wrapPtr(FmFile* dataPtr) { - File obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmFile* takeDataPtr() { - FmFile* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmFile* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmFile*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - File& operator=(const File& other) { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - File& operator=(File&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - static GFile* newForCommandlineArg(const char* arg) { - return fm_file_new_for_commandline_arg(arg); - } - - - static GFile* newForUri(const char* uri) { - return fm_file_new_for_uri(uri); - } - - - static bool wantsIncremental(GFile* file) { - return fm_file_wants_incremental(file); - } - - - static void addVfs(const char* name, FmFileInitTable* init) { - fm_file_add_vfs(name, init); - } - - - // automatic type casting for GObject - operator GObject*() { - return reinterpret_cast(dataPtr_); - } - - -protected: - GObject* dataPtr_; // data pointer for the underlying C struct - -}; - - -} - -#endif // __LIBFM_QT_FM_FILE_H__ diff --git a/src/filedialog.cpp b/src/filedialog.cpp new file mode 100644 index 0000000..3d05650 --- /dev/null +++ b/src/filedialog.cpp @@ -0,0 +1,967 @@ +#include "filedialog.h" +#include "cachedfoldermodel.h" +#include "proxyfoldermodel.h" +#include "utilities.h" +#include "core/fileinfojob.h" +#include "ui_filedialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Fm { + + +FileDialog::FileDialog(QWidget* parent, FilePath path) : + QDialog(parent), + ui{new Ui::FileDialog()}, + folderModel_{nullptr}, + proxyModel_{nullptr}, + folder_{nullptr}, + options_{0}, + viewMode_{FolderView::DetailedListMode}, + fileMode_{QFileDialog::AnyFile}, + acceptMode_{QFileDialog::AcceptOpen}, + confirmOverwrite_{true}, + modelFilter_{this} { + + ui->setupUi(this); + + // path bar + connect(ui->location, &PathBar::chdir, [this](const FilePath &path) { + setDirectoryPath(path); + }); + + // side pane + ui->sidePane->setMode(Fm::SidePane::ModePlaces); + connect(ui->sidePane, &SidePane::chdirRequested, [this](int /*type*/, const FilePath &path) { + setDirectoryPath(path); + }); + + // folder view + proxyModel_ = new ProxyFolderModel(this); + proxyModel_->sort(FolderModel::ColumnFileName, Qt::AscendingOrder); + proxyModel_->setThumbnailSize(64); + proxyModel_->setShowThumbnails(true); + + proxyModel_->addFilter(&modelFilter_); + + connect(ui->folderView, &FolderView::clicked, this, &FileDialog::onFileClicked); + ui->folderView->setModel(proxyModel_); + ui->folderView->setAutoSelectionDelay(0); + // set the completer + QCompleter* completer = new QCompleter(this); + completer->setModel(proxyModel_); + ui->fileName->setCompleter(completer); + connect(completer, static_cast(&QCompleter::activated), [this](const QString &text) { + ui->folderView->selectionModel()->clearSelection(); + selectFilePath(directoryPath_.child(text.toLocal8Bit().constData())); + }); + // select typed paths if it they exist + connect(ui->fileName, &QLineEdit::textEdited, [this](const QString& /*text*/) { + disconnect(ui->folderView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FileDialog::onSelectionChanged); + ui->folderView->selectionModel()->clearSelection(); + QStringList parsedNames = parseNames(); + for(auto& name: parsedNames) { + selectFilePath(directoryPath_.child(name.toLocal8Bit().constData())); + } + updateAcceptButtonState(); + updateSaveButtonText(false); + connect(ui->folderView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FileDialog::onSelectionChanged); + }); + // update selection mode for the view + updateSelectionMode(); + + // file type + connect(ui->fileTypeCombo, &QComboBox::currentTextChanged, [this](const QString& text) { + selectNameFilter(text); + }); + ui->fileTypeCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + ui->fileTypeCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + ui->fileTypeCombo->setCurrentIndex(0); + + QShortcut* shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_H), this); + connect(shortcut, &QShortcut::activated, [this]() { + proxyModel_->setShowHidden(!proxyModel_->showHidden()); + }); + + // setup toolbar buttons + auto toolbar = new QToolBar(this); + // back button + backAction_ = toolbar->addAction(QIcon::fromTheme("go-previous"), tr("Go Back")); + backAction_->setShortcut(QKeySequence(tr("Alt+Left", "Go Back"))); + connect(backAction_, &QAction::triggered, [this]() { + history_.backward(); + setDirectoryPath(history_.currentPath(), FilePath(), false); + }); + // forward button + forwardAction_ = toolbar->addAction(QIcon::fromTheme("go-next"), tr("Go Forward")); + forwardAction_->setShortcut(QKeySequence(tr("Alt+Right", "Go Forward"))); + connect(forwardAction_, &QAction::triggered, [this]() { + history_.forward(); + setDirectoryPath(history_.currentPath(), FilePath(), false); + }); + toolbar->addSeparator(); + // reload button + auto reloadAction = toolbar->addAction(QIcon::fromTheme("view-refresh"), tr("Reload")); + reloadAction->setShortcut(QKeySequence(tr("F5", "Reload"))); + connect(reloadAction, &QAction::triggered, [this]() { + if(folder_ && folder_->isLoaded()) { + QObject::disconnect(lambdaConnection_); + auto selFiles = ui->folderView->selectedFiles(); + ui->folderView->selectionModel()->clear(); + // reselect files on reloading + if(!selFiles.empty() + && selFiles.size() <= 50) { // otherwise senseless and CPU-intensive + lambdaConnection_ = QObject::connect(folder_.get(), &Fm::Folder::finishLoading, [this, selFiles]() { + selectFilesOnReload(selFiles); + }); + } + folder_->reload(); + } + }); + // new folder button + auto newFolderAction = toolbar->addAction(QIcon::fromTheme("folder-new"), tr("Create Folder")); + connect(newFolderAction, &QAction::triggered, this, &FileDialog::onNewFolder); + toolbar->addSeparator(); + // view buttons + auto viewModeGroup = new QActionGroup(this); + iconViewAction_ = toolbar->addAction(style()->standardIcon(QStyle::SP_FileDialogContentsView), tr("Icon View")); + iconViewAction_->setCheckable(true); + connect(iconViewAction_, &QAction::toggled, this, &FileDialog::onViewModeToggled); + viewModeGroup->addAction(iconViewAction_); + thumbnailViewAction_ = toolbar->addAction(style()->standardIcon(QStyle::SP_FileDialogInfoView), tr("Thumbnail View")); + thumbnailViewAction_->setCheckable(true); + connect(thumbnailViewAction_, &QAction::toggled, this, &FileDialog::onViewModeToggled); + viewModeGroup->addAction(thumbnailViewAction_); + compactViewAction_ = toolbar->addAction(style()->standardIcon(QStyle::SP_FileDialogListView), tr("Compact View")); + compactViewAction_->setCheckable(true); + connect(compactViewAction_, &QAction::toggled, this, &FileDialog::onViewModeToggled); + viewModeGroup->addAction(compactViewAction_); + detailedViewAction_ = toolbar->addAction(style()->standardIcon(QStyle::SP_FileDialogDetailedView), tr("Detailed List View")); + detailedViewAction_->setCheckable(true); + connect(detailedViewAction_, &QAction::toggled, this, &FileDialog::onViewModeToggled); + viewModeGroup->addAction(detailedViewAction_); + ui->toolbarLayout->addWidget(toolbar); + + setViewMode(viewMode_); + + // set the default splitter position + setSplitterPos(200); + + // browse to the directory + if(path.isValid()) { + setDirectoryPath(path); + } + else { + goHome(); + } + + // focus the text entry on showing the dialog + QTimer::singleShot(0, ui->fileName, SLOT(setFocus())); +} + +FileDialog::~FileDialog() { + freeFolder(); +} + +int FileDialog::splitterPos() const { + return ui->splitter->sizes().at(0); +} + +void FileDialog::setSplitterPos(int pos) { + QList sizes; + sizes.append(qMax(pos, 0)); + sizes.append(320); + ui->splitter->setSizes(sizes); +} + +// This should always be used instead of getting text directly from the entry. +QStringList FileDialog::parseNames() const { + // parse the file names from the text entry + QStringList parsedNames; + auto fileNames = ui->fileName->text(); + if(!fileNames.isEmpty()) { + /* check if there are multiple file names (containing "), + considering the fact that inside quotes were escaped by \ */ + auto firstQuote = fileNames.indexOf(QLatin1Char('\"')); + auto lastQuote = fileNames.lastIndexOf(QLatin1Char('\"')); + if(firstQuote != -1 && lastQuote != -1 + && firstQuote != lastQuote + && (firstQuote == 0 || fileNames.at(firstQuote - 1) != QLatin1Char('\\')) + && fileNames.at(lastQuote - 1) != QLatin1Char('\\')) { + // split the names + QRegExp sep{"\"\\s+\""}; // separated with " " + parsedNames = fileNames.mid(firstQuote + 1, lastQuote - firstQuote - 1).split(sep); + parsedNames.replaceInStrings(QLatin1String("\\\""), QLatin1String("\"")); + } + else { + parsedNames << fileNames.replace(QLatin1String("\\\""), QLatin1String("\"")); + } + } + return parsedNames; +} + +std::shared_ptr FileDialog::firstSelectedDir() const { + std::shared_ptr selectedFolder = nullptr; + auto list = ui->folderView->selectedFiles(); + for(auto it = list.cbegin(); it != list.cend(); ++it) { + auto& item = *it; + if(item->isDir()) { + selectedFolder = item; + break; + } + } + return selectedFolder; +} + +void FileDialog::accept() { + // handle selected filenames + selectedFiles_.clear(); + + // if a folder is selected in file mode, chdir into it (as QFileDialog does) + // by giving priority to the current index and, if it isn't a folder, + // to the first selected folder + if(fileMode_ != QFileDialog::Directory) { + std::shared_ptr selectedFolder = nullptr; + // check if the current index is a folder + QItemSelectionModel* selModel = ui->folderView->selectionModel(); + QModelIndex cur = selModel->currentIndex(); + if(cur.isValid() && selModel->isSelected(cur)) { + auto file = proxyModel_->fileInfoFromIndex(cur); + if(file && file->isDir()) { + selectedFolder = file; + } + } + if(!selectedFolder) { + selectedFolder = firstSelectedDir(); + } + if(selectedFolder) { + setDirectoryPath(selectedFolder->path()); + return; + } + } + + QStringList parsedNames = parseNames(); + if(parsedNames.isEmpty()) { + // when selecting a dir and the name is not provided, just select current dir in the view + if(fileMode_ == QFileDialog::Directory) { + auto localPath = directoryPath_.localPath(); + if(localPath) { + selectedFiles_.append(QUrl::fromLocalFile(localPath.get())); + } + else { + selectedFiles_.append(directory()); + } + } + else { + QMessageBox::critical(this, tr("Error"), tr("Please select a file")); + return; + } + } + else { + if(fileMode_ != QFileDialog::Directory) { + auto firstName = parsedNames.at(0); + auto childPath = directoryPath_.child(firstName.toLocal8Bit().constData()); + auto info = proxyModel_->fileInfoFromPath(childPath); + if(info) { + // if the typed name belongs to a (nonselected) directory, chdir into it + if(info->isDir()) { + setDirectoryPath(childPath); + return; + } + // overwrite prompt (as in QFileDialog::accept) + if(fileMode_ == QFileDialog::AnyFile + && acceptMode_ != QFileDialog::AcceptOpen + && confirmOverwrite_) { + if (QMessageBox::warning(this, windowTitle(), + tr("%1 already exists.\nDo you want to replace it?") + .arg(firstName), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + == QMessageBox::No) { + return; + } + } + } + } + + // get full paths for the filenames and convert them to URLs + for(auto& name: parsedNames) { + // add default filename extension as needed + if(!defaultSuffix_.isEmpty() && name.lastIndexOf('.') == -1) { + name += '.'; + name += defaultSuffix_; + } + auto fullPath = directoryPath_.child(name.toLocal8Bit().constData()); + auto localPath = fullPath.localPath(); + /* add the local path if it exists; otherwise, add the uri */ + if(localPath) { + selectedFiles_.append(QUrl::fromLocalFile(localPath.get())); + } + else { + selectedFiles_.append(QUrl::fromEncoded(fullPath.uri().get())); + } + } + } + + // check existence of the selected files and if their types are correct + // async operation, call doAccept() in the callback. + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + + auto pathList = pathListFromQUrls(selectedFiles_); + auto job = new FileInfoJob(pathList); + job->setAutoDelete(true); + connect(job, &Job::finished, this, &FileDialog::onFileInfoJobFinished); + job->runAsync(); +} + +void FileDialog::reject() { + QDialog::reject(); +} + +void FileDialog::setDirectory(const QUrl &directory) { + auto path = Fm::FilePath::fromUri(directory.toEncoded().constData()); + setDirectoryPath(path); +} + +// interface for QPlatformFileDialogHelper + +void FileDialog::freeFolder() { + if(folder_) { + QObject::disconnect(lambdaConnection_); // lambdaConnection_ can be invalid + disconnect(folder_.get(), nullptr, this, nullptr); + folder_ = nullptr; + } +} + +void FileDialog::goHome() { + setDirectoryPath(FilePath::homeDir()); +} + +void FileDialog::setDirectoryPath(FilePath directory, FilePath selectedPath, bool addHistory) { + if(!directory.isValid() || directoryPath_ == directory) { + updateAcceptButtonState(); // FIXME: is this needed? + return; + } + + if(folder_) { + if(folderModel_) { + proxyModel_->setSourceModel(nullptr); + folderModel_->unref(); // unref the cached model + folderModel_ = nullptr; + } + freeFolder(); + } + + directoryPath_ = std::move(directory); + + ui->location->setPath(directoryPath_); + ui->sidePane->chdir(directoryPath_); + if(addHistory) { + history_.add(directoryPath_); + } + backAction_->setEnabled(history_.canBackward()); + forwardAction_->setEnabled(history_.canForward()); + + folder_ = Fm::Folder::fromPath(directoryPath_); + folderModel_ = CachedFolderModel::modelFromFolder(folder_); + proxyModel_->setSourceModel(folderModel_); + + // no lambda in these connections for easy disconnection + connect(folder_.get(), &Fm::Folder::removed, this, &FileDialog::goHome); + connect(folder_.get(), &Fm::Folder::unmount, this, &FileDialog::goHome); + + QUrl uri = QUrl::fromEncoded(directory.uri().get()); + Q_EMIT directoryEntered(uri); + + // select the path if valid + if(selectedPath.isValid()) { + if(folder_->isLoaded()) { + selectFilePathWithDelay(selectedPath); + } + else { + lambdaConnection_ = QObject::connect(folder_.get(), &Fm::Folder::finishLoading, [this, selectedPath]() { + selectFilePathWithDelay(selectedPath); + }); + } + } + else { + updateAcceptButtonState(); + updateSaveButtonText(false); + } + +} + +void FileDialog::selectFilePath(const FilePath &path) { + auto idx = proxyModel_->indexFromPath(path); + if(!idx.isValid()) { + return; + } + + // FIXME: add a method to Fm::FolderView to select files + + // FIXME: need to add this for detailed list + QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Select; + if(viewMode_ == FolderView::DetailedListMode) { + flags |= QItemSelectionModel::Rows; + } + QItemSelectionModel* selModel = ui->folderView->selectionModel(); + selModel->select(idx, flags); + selModel->setCurrentIndex(idx, QItemSelectionModel::Current); + QTimer::singleShot(0, [this, idx]() { + ui->folderView->childView()->scrollTo(idx, QAbstractItemView::PositionAtCenter); + }); +} + +void FileDialog::selectFilePathWithDelay(const FilePath &path) { + QTimer::singleShot(0, [this, path]() { + if(acceptMode_ == QFileDialog::AcceptSave) { + // with a save dialog, always put the base name in line-edit, regardless of selection + ui->fileName->setText(path.baseName().get()); + } + // update "accept" button because there might be no selection later + updateAcceptButtonState(); + updateSaveButtonText(false); + // try to select path + selectFilePath(path); + }); +} + +void FileDialog::selectFilesOnReload(const Fm::FileInfoList& infos) { + QObject::disconnect(lambdaConnection_); + QTimer::singleShot(0, [this, infos]() { + for(auto& fileInfo: infos) { + selectFilePath(fileInfo->path()); + } + }); +} + +void FileDialog::onCurrentRowChanged(const QModelIndex ¤t, const QModelIndex& /*previous*/) { + // emit currentChanged signal + QUrl currentUrl; + if(current.isValid()) { + // emit changed siangl for newly selected items + auto fi = proxyModel_->fileInfoFromIndex(current); + if(fi) { + currentUrl = QUrl::fromEncoded(fi->path().uri().get()); + } + } + Q_EMIT currentChanged(currentUrl); +} + +void FileDialog::onSelectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { + auto selFiles = ui->folderView->selectedFiles(); + if(selFiles.empty()) { + updateAcceptButtonState(); + updateSaveButtonText(false); + return; + } + bool multiple(selFiles.size() > 1); + bool hasDir(false); + QString fileNames; + for(auto& fileInfo: selFiles) { + if(fileMode_ == QFileDialog::Directory) { + // if we want to select dir, ignore selected files + if(!fileInfo->isDir()) { + continue; + } + } + else if(fileInfo->isDir()) { + // if we want to select files, ignore selected dirs + hasDir = true; + continue; + } + + auto baseName = fileInfo->path().baseName(); + if(multiple) { + // support multiple selection + if(!fileNames.isEmpty()) { + fileNames += ' '; + } + fileNames += QLatin1Char('\"'); + // escape inside quotes with \ to distinguish between them + // and the quotes used for separating file names from each other + QString name(baseName.get()); + fileNames += name.replace(QLatin1String("\""), QLatin1String("\\\"")); + fileNames += QLatin1Char('\"'); + } + else { + // support single selection only + QString name(baseName.get()); + fileNames = name.replace(QLatin1String("\""), QLatin1String("\\\"")); + break; + } + } + // put the selection list in the text entry + if(!fileNames.isEmpty()) { + ui->fileName->setText(fileNames); + } + updateSaveButtonText(hasDir); + updateAcceptButtonState(); +} + +void FileDialog::onFileClicked(int type, const std::shared_ptr &file) { + bool canAccept = false; + if(file && type == FolderView::ActivatedClick) { + if(file->isDir()) { + if(fileMode_ == QFileDialog::Directory) { + ui->fileName->clear(); + } + // chdir into the activated dir + setDirectoryPath(file->path()); + } + else if(fileMode_ != QFileDialog::Directory) { + // select file(s) and a file item is activated + canAccept = true; + } + } + + if(canAccept) { + selectFilePath(file->path()); + accept(); + } +} + +void FileDialog::onNewFolder() { + createFileOrFolder(CreateNewFolder, directoryPath_, nullptr, this); +} + +void FileDialog::onViewModeToggled(bool active) { + if(active) { + auto action = static_cast(sender()); + FolderView::ViewMode newMode; + if(action == iconViewAction_) { + newMode = FolderView::IconMode; + } + else if(action == thumbnailViewAction_) { + newMode = FolderView::ThumbnailMode; + } + else if(action == compactViewAction_) { + newMode = FolderView::CompactMode; + } + else if(action == detailedViewAction_) { + newMode = FolderView::DetailedListMode; + } + else { + return; + } + setViewMode(newMode); + } +} + +void FileDialog::updateSelectionMode() { + // enable multiple selection? + ui->folderView->childView()->setSelectionMode(fileMode_ == QFileDialog::ExistingFiles ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection); +} + +void FileDialog::doAccept() { + + Q_EMIT filesSelected(selectedFiles_); + + if(selectedFiles_.size() == 1) { + Q_EMIT fileSelected(selectedFiles_[0]); + } + + QDialog::accept(); +} + +void FileDialog::onFileInfoJobFinished() { + auto job = static_cast(sender()); + if(job->isCancelled()) { + selectedFiles_.clear(); + reject(); + } + else { + QString error; + // check if the files exist and their types are correct + auto paths = job->paths(); + auto files = job->files(); + for(size_t i = 0; i < paths.size(); ++i) { + const auto& path = paths[i]; + if(i >= files.size() || files[i]->path() != path) { + // the file path is not found and does not have file info + if(fileMode_ != QFileDialog::AnyFile) { + // if we do not allow non-existent file, this is an error. + error = tr("Path \"%1\" does not exist").arg(path.displayName().get()); + break; + } + ++i; // skip the file + continue; + } + + // FIXME: currently, if a path is not found, FmFileInfoJob does not return its file info object. + // This is bad API design. We may return nullptr for the failed file info query instead. + const auto& file = files[i]; + // check if the file type is correct + if(fileMode_ == QFileDialog::Directory) { + if(!file->isDir()) { + // we're selecting dirs, but the selected file path does not point to a dir + error = tr("\"%1\" is not a directory").arg(path.displayName().get()); + break; + } + } + else if(file->isDir() || file->isShortcut()) { + // we're selecting files, but the selected file path refers to a dir or shortcut (such as computer:///) + error = tr("\"%1\" is not a file").arg(path.displayName().get());; + break; + } + } + + if(error.isEmpty()) { + // no error! + doAccept(); + } + else { + QMessageBox::critical(this, tr("Error"), error); + selectedFiles_.clear(); + } + } + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); +} + +QUrl FileDialog::directory() const { + QUrl url{directoryPath_.uri().get()}; + return url; +} + +void FileDialog::selectFile(const QUrl& filename) { + auto urlStr = filename.toEncoded(); + auto path = FilePath::fromUri(urlStr.constData()); + auto parent = path.parent(); + if(parent.isValid() && parent != directoryPath_) { + // chdir into file's parent if it isn't the current directory + setDirectoryPath(parent, path); + } + else { + selectFilePathWithDelay(path); + } +} + +QList FileDialog::selectedFiles() { + return selectedFiles_; +} + +void FileDialog::selectNameFilter(const QString& filter) { + if(filter != currentNameFilter_) { + currentNameFilter_ = filter; + ui->fileTypeCombo->setCurrentText(filter); + + modelFilter_.update(); + proxyModel_->invalidate(); + Q_EMIT filterSelected(filter); + } +} + +void FileDialog::selectMimeTypeFilter(const QString &filter) { + auto idx = mimeTypeFilters_.indexOf(filter); + if(idx != -1) { + ui->fileTypeCombo->setCurrentIndex(idx); + } +} + +QString FileDialog::selectedMimeTypeFilter() const { + QString filter; + auto idx = mimeTypeFilters_.indexOf(filter); + if(idx >= 0 && idx < mimeTypeFilters_.size()) { + filter = mimeTypeFilters_[idx]; + } + return filter; +} + +bool FileDialog::isSupportedUrl(const QUrl& url) { + auto scheme = url.scheme().toLocal8Bit(); + // FIXME: this is not reliable due to the bug of gvfs. + return Fm::isUriSchemeSupported(scheme.constData()); +} + + +// options + +void FileDialog::setFilter(QDir::Filters filters) { + filters_ = filters; + // TODO: +} + +void FileDialog::setViewMode(FolderView::ViewMode mode) { + viewMode_ = mode; + + // Since setModel() is called by FolderView::setViewMode(), the selectionModel will be replaced by one + // created by the view. So, we need to deal with selection changes again after setting the view mode. + disconnect(ui->folderView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &FileDialog::onCurrentRowChanged); + disconnect(ui->folderView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FileDialog::onSelectionChanged); + + ui->folderView->setViewMode(mode); + switch(mode) { + case FolderView::IconMode: + iconViewAction_->setChecked(true); + break; + case FolderView::ThumbnailMode: + thumbnailViewAction_->setChecked(true); + break; + case FolderView::CompactMode: + compactViewAction_->setChecked(true); + break; + case FolderView::DetailedListMode: + detailedViewAction_->setChecked(true); + break; + default: + break; + } + // selection changes + connect(ui->folderView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &FileDialog::onCurrentRowChanged); + connect(ui->folderView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FileDialog::onSelectionChanged); + // update selection mode for the view + updateSelectionMode(); +} + + +void FileDialog::setFileMode(QFileDialog::FileMode mode) { + if(mode == QFileDialog::DirectoryOnly) { + // directly only is deprecated and not allowed. + mode = QFileDialog::Directory; + } + fileMode_ = mode; + + // enable multiple selection? + updateSelectionMode(); +} + + +void FileDialog::setAcceptMode(QFileDialog::AcceptMode mode) { + acceptMode_ = mode; + // set "open/save" label if it isn't set explicitly + if(isLabelExplicitlySet(QFileDialog::Accept)) { + return; + } + if(acceptMode_ == QFileDialog::AcceptOpen) { + setLabelTextControl(QFileDialog::Accept, tr("&Open")); + } + else if(acceptMode_ == QFileDialog::AcceptSave) { + setLabelTextControl(QFileDialog::Accept, tr("&Save")); + } +} + +void FileDialog::setNameFilters(const QStringList& filters) { + if(filters.isEmpty()) { + // default filename pattern + nameFilters_ = (QStringList() << tr("All Files (*)")); + } + else { + nameFilters_ = filters; + } + ui->fileTypeCombo->clear(); + ui->fileTypeCombo->addItems(nameFilters_); +} + +void FileDialog::setMimeTypeFilters(const QStringList& filters) { + mimeTypeFilters_ = filters; + + QStringList nameFilters; + QMimeDatabase db; + for(const auto& filter: filters) { + auto mimeType = db.mimeTypeForName(filter); + auto nameFilter = mimeType.comment(); + if(!mimeType.suffixes().empty()) { + nameFilter + " ("; + for(const auto& suffix: mimeType.suffixes()) { + nameFilter += "*."; + nameFilter += suffix; + nameFilter += ' '; + } + nameFilter[nameFilter.length() - 1] = ')'; + } + nameFilters << nameFilter; + } + setNameFilters(nameFilters); +} + +void FileDialog::setLabelTextControl(QFileDialog::DialogLabel label, const QString& text) { + switch(label) { + case QFileDialog::LookIn: + ui->lookInLabel->setText(text); + break; + case QFileDialog::FileName: + ui->fileNameLabel->setText(text); + break; + case QFileDialog::FileType: + ui->fileTypeLabel->setText(text); + break; + case QFileDialog::Accept: + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(text); + break; + case QFileDialog::Reject: + ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(text); + break; + default: + break; + } +} + +void FileDialog::setLabelText(QFileDialog::DialogLabel label, const QString& text) { + setLabelExplicitly(label, text); + setLabelTextControl(label, text); +} + +QString FileDialog::labelText(QFileDialog::DialogLabel label) const { + QString text; + switch(label) { + case QFileDialog::LookIn: + text = ui->lookInLabel->text(); + break; + case QFileDialog::FileName: + text = ui->fileNameLabel->text(); + break; + case QFileDialog::FileType: + text = ui->fileTypeLabel->text(); + break; + case QFileDialog::Accept: + ui->buttonBox->button(QDialogButtonBox::Ok)->text(); + break; + case QFileDialog::Reject: + ui->buttonBox->button(QDialogButtonBox::Cancel)->text(); + break; + default: + break; + } + return text; +} + +void FileDialog::updateSaveButtonText(bool saveOnFolder) { + if(fileMode_ != QFileDialog::Directory + && acceptMode_ == QFileDialog::AcceptSave) { + // change save button to open button when there is a dir with the save name, + // otherwise restore it to a save button again + if(!saveOnFolder) { + QStringList parsedNames = parseNames(); + if(!parsedNames.isEmpty()) { + auto info = proxyModel_->fileInfoFromPath(directoryPath_.child(parsedNames.at(0).toLocal8Bit().constData())); + if(info && info->isDir()) { + saveOnFolder = true; + } + } + } + if(saveOnFolder) { + setLabelTextControl(QFileDialog::Accept, tr("&Open")); + } + else { + // restore save button text appropriately + if(isLabelExplicitlySet(QFileDialog::Accept)) { + setLabelTextControl(QFileDialog::Accept, explicitLabels_[QFileDialog::Accept]); + } + else { + setLabelTextControl(QFileDialog::Accept, tr("&Save")); + } + } + } +} + +void FileDialog::updateAcceptButtonState() { + bool enable(false); + if(fileMode_ != QFileDialog::Directory) { + if(acceptMode_ == QFileDialog::AcceptOpen) + { + if(firstSelectedDir()) { + // enable "open" button if a dir is selected + enable = true; + } + else { + // enable "open" button when there is a file whose name is listed + QStringList parsedNames = parseNames(); + for(auto& name: parsedNames) { + if(proxyModel_->indexFromPath(directoryPath_.child(name.toLocal8Bit().constData())).isValid()) { + enable = true; + break; + } + } + } + } + else if(acceptMode_ == QFileDialog::AcceptSave) { + // enable "save" button when there is a name or a dir selection + if(!ui->fileName->text().isEmpty()) { + enable = true; + } + else if(firstSelectedDir()) { + enable = true; + } + } + } + else if(fileMode_ == QFileDialog::Directory + && acceptMode_ != QFileDialog::AcceptSave) { + QStringList parsedNames = parseNames(); + if(parsedNames.isEmpty()) { + // in the dir mode, the current dir will be opened + // if no dir is selected and the name list is empty + enable = true; + } + else { + for(auto& name: parsedNames) { + auto info = proxyModel_->fileInfoFromPath(directoryPath_.child(name.toLocal8Bit().constData())); + if(info && info->isDir()) { + // the name of a dir is listed + enable = true; + break; + } + } + } + } + else { + enable = true; + } + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable); +} + +bool FileDialog::FileDialogFilter::filterAcceptsRow(const ProxyFolderModel* /*model*/, const std::shared_ptr &info) const { + if(dlg_->fileMode_ == QFileDialog::Directory) { + // we only want to select directories + if(!info->isDir()) { // not a dir + // NOTE: here we ignore dlg_->options_& QFileDialog::ShowDirsOnly option. + return false; + } + } + else { + // we want to select files, so all directories can be shown regardless of their names + if(info->isDir()) { + return true; + } + } + + bool nameMatched = false; + auto& name = info->displayName(); + for(const auto& pattern: patterns_) { + if(pattern.exactMatch(name)) { + nameMatched = true; + break; + } + } + return nameMatched; +} + +void FileDialog::FileDialogFilter::update() { + // update filename patterns + patterns_.clear(); + QString nameFilter = dlg_->currentNameFilter_; + // if the filter contains (...), only get the part between the parenthesis. + auto left = nameFilter.indexOf('('); + if(left != -1) { + ++left; + auto right = nameFilter.indexOf(')', left); + if(right == -1) { + right = nameFilter.length(); + } + nameFilter = nameFilter.mid(left, right - left); + } + // parse the "*.ext1 *.ext2 *.ext3 ..." list into QRegExp objects + auto globs = nameFilter.simplified().split(' '); + for(const auto& glob: globs) { + patterns_.emplace_back(QRegExp(glob, Qt::CaseInsensitive, QRegExp::Wildcard)); + } +} + +} // namespace Fm diff --git a/src/filedialog.h b/src/filedialog.h new file mode 100644 index 0000000..207d00d --- /dev/null +++ b/src/filedialog.h @@ -0,0 +1,213 @@ +#ifndef FM_FILEDIALOG_H +#define FM_FILEDIALOG_H + +#include "libfmqtglobals.h" +#include "core/filepath.h" + +#include +#include +#include +#include +#include "folderview.h" +#include "browsehistory.h" + +namespace Ui { +class FileDialog; +} + +namespace Fm { + +class CachedFolderModel; +class ProxyFolderModel; + +class LIBFM_QT_API FileDialog : public QDialog { + Q_OBJECT +public: + explicit FileDialog(QWidget *parent = 0, FilePath path = FilePath::homeDir()); + + ~FileDialog(); + + // Some QFileDialog compatible interface + void accept() override; + + void reject() override; + + QFileDialog::Options options() const { + return options_; + } + + void setOptions(QFileDialog::Options options) { + options_ = options; + } + + // interface for QPlatformFileDialogHelper + + void setDirectory(const QUrl &directory); + + QUrl directory() const; + + void selectFile(const QUrl &filename); + + QList selectedFiles(); + + void selectNameFilter(const QString &filter); + + QString selectedNameFilter() const { + return currentNameFilter_; + } + + void selectMimeTypeFilter(const QString &filter); + + QString selectedMimeTypeFilter() const; + + bool isSupportedUrl(const QUrl &url); + + // options + + // not yet supported + QDir::Filters filter() const { + return filters_; + } + // not yet supported + void setFilter(QDir::Filters filters); + + void setViewMode(FolderView::ViewMode mode); + FolderView::ViewMode viewMode() const { + return viewMode_; + } + + void setFileMode(QFileDialog::FileMode mode); + QFileDialog::FileMode fileMode() const { + return fileMode_; + } + + void setAcceptMode(QFileDialog::AcceptMode mode); + QFileDialog::AcceptMode acceptMode() const { + return acceptMode_; + } + + void setNameFilters(const QStringList &filters); + QStringList nameFilters() const { + return nameFilters_; + } + + void setMimeTypeFilters(const QStringList &filters); + QStringList mimeTypeFilters() const { + return mimeTypeFilters_; + } + + void setDefaultSuffix(const QString &suffix) { + if(!suffix.isEmpty() && suffix[0] == '.') { + // if the first char is dot, remove it. + defaultSuffix_ = suffix.mid(1); + } + else { + defaultSuffix_ = suffix; + } + } + QString defaultSuffix() const { + return defaultSuffix_; + } + + void setConfirmOverwrite(bool enabled) { + confirmOverwrite_ = enabled; + } + bool confirmOverwrite() const { + return confirmOverwrite_; + } + + void setLabelText(QFileDialog::DialogLabel label, const QString &text); + QString labelText(QFileDialog::DialogLabel label) const; + + int splitterPos() const; + void setSplitterPos(int pos); + +private Q_SLOTS: + void onCurrentRowChanged(const QModelIndex ¤t, const QModelIndex& /*previous*/); + void onSelectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/); + void onFileClicked(int type, const std::shared_ptr& file); + void onNewFolder(); + void onViewModeToggled(bool active); + void goHome(); + +Q_SIGNALS: + // emitted when the dialog is accepted and some files are selected + void fileSelected(const QUrl &file); + void filesSelected(const QList &files); + + // emitted whenever selection changes (including no selected files) + void currentChanged(const QUrl &path); + + void directoryEntered(const QUrl &directory); + void filterSelected(const QString &filter); + +private: + + class FileDialogFilter: public ProxyFolderModelFilter { + public: + FileDialogFilter(FileDialog* dlg): dlg_{dlg} {} + virtual bool filterAcceptsRow(const ProxyFolderModel* /*model*/, const std::shared_ptr& info) const override; + void update(); + + FileDialog* dlg_; + std::vector patterns_; + }; + + bool isLabelExplicitlySet(QFileDialog::DialogLabel label) const { + return !explicitLabels_[label].isEmpty(); + } + void setLabelExplicitly(QFileDialog::DialogLabel label, const QString& text) { + explicitLabels_[label] = text; + } + void setLabelTextControl(QFileDialog::DialogLabel label, const QString &text); + void updateSaveButtonText(bool saveOnFolder); + void updateAcceptButtonState(); + + std::shared_ptr firstSelectedDir() const; + void selectFilePath(const FilePath& path); + void selectFilePathWithDelay(const FilePath& path); + void selectFilesOnReload(const Fm::FileInfoList& infos); + void setDirectoryPath(FilePath directory, FilePath selectedPath = FilePath(), bool addHistory = true); + void updateSelectionMode(); + void doAccept(); + void onFileInfoJobFinished(); + void freeFolder(); + QStringList parseNames() const; + +private: + std::unique_ptr ui; + CachedFolderModel* folderModel_; + ProxyFolderModel* proxyModel_; + FilePath directoryPath_; + std::shared_ptr folder_; + Fm::BrowseHistory history_; + + QFileDialog::Options options_; + QDir::Filters filters_; + FolderView::ViewMode viewMode_; + QFileDialog::FileMode fileMode_; + QFileDialog::AcceptMode acceptMode_; + bool confirmOverwrite_; + QStringList nameFilters_; + QStringList mimeTypeFilters_; + QString defaultSuffix_; + FileDialogFilter modelFilter_; + QString currentNameFilter_; + QList selectedFiles_; + // view modes: + QAction* iconViewAction_; + QAction* thumbnailViewAction_; + QAction* compactViewAction_; + QAction* detailedViewAction_; + // back and forward buttons: + QAction* backAction_; + QAction* forwardAction_; + // dialog labels that can be set explicitly: + QString explicitLabels_[5]; + // needed for disconnecting Fm::Folder signal from lambda: + QMetaObject::Connection lambdaConnection_; +}; + + +} // namespace Fm +#endif // FM_FILEDIALOG_H diff --git a/src/filedialog.ui b/src/filedialog.ui new file mode 100644 index 0000000..cc7d65e --- /dev/null +++ b/src/filedialog.ui @@ -0,0 +1,151 @@ + + + FileDialog + + + + 0 + 0 + 700 + 500 + + + + + + + + + + + + Location: + + + + + + + + + + + + Qt::Horizontal + + + + + 0 + 0 + + + + + 120 + 0 + + + + + + + 1 + 0 + + + + + + + + + + + File name: + + + + + + + + + + File type: + + + + + + + + + + Qt::Vertical + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + Fm::SidePane + QWidget +
sidepane.h
+ 1 +
+ + Fm::FolderView + QWidget +
folderview.h
+ 1 +
+ + Fm::PathBar + QWidget +
pathbar.h
+ 1 +
+
+ + + + buttonBox + accepted() + FileDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + FileDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/fileinfo.h b/src/fileinfo.h deleted file mode 100644 index 3322cbd..0000000 --- a/src/fileinfo.h +++ /dev/null @@ -1,511 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_FILE_INFO_H__ -#define __LIBFM_QT_FM_FILE_INFO_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - -class LIBFM_QT_API FileInfoList { -public: - - - FileInfoList( ) { - dataPtr_ = reinterpret_cast(fm_file_info_list_new()); - } - - - FileInfoList(FmFileInfoList* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(fm_list_ref(FM_LIST(dataPtr))) : nullptr; - } - - - // copy constructor - FileInfoList(const FileInfoList& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(fm_list_ref(FM_LIST(other.dataPtr_))) : nullptr; - } - - - // move constructor - FileInfoList(FileInfoList&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - ~FileInfoList() { - if(dataPtr_ != nullptr) { - fm_list_unref(FM_LIST(dataPtr_)); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static FileInfoList wrapPtr(FmFileInfoList* dataPtr) { - FileInfoList obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmFileInfoList* takeDataPtr() { - FmFileInfoList* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmFileInfoList* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmFileInfoList*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - FileInfoList& operator=(const FileInfoList& other) { - if(dataPtr_ != nullptr) { - fm_list_unref(FM_LIST(dataPtr_)); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(fm_list_ref(FM_LIST(other.dataPtr_))) : nullptr; - return *this; - } - - - // move assignment - FileInfoList& operator=(FileInfoList&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - bool isSameFs(void) { - return fm_file_info_list_is_same_fs(dataPtr()); - } - - - bool isSameType(void) { - return fm_file_info_list_is_same_type(dataPtr()); - } - - - bool isEmpty() { - return fm_file_info_list_is_empty(dataPtr()); - } - - unsigned int getLength() { - return fm_file_info_list_get_length(dataPtr()); - } - - FmFileInfo* peekHead() { - return fm_file_info_list_peek_head(dataPtr()); - } - - GList* peekHeadLink() { - return fm_file_info_list_peek_head_link(dataPtr()); - } - - void pushTail(FmFileInfo* d) { - fm_file_info_list_push_tail(dataPtr(), d); - } - - void pushTailLink(GList* d) { - fm_file_info_list_push_tail_link(dataPtr(), d); - } - - FmFileInfo* popHead(){ - return fm_file_info_list_pop_head(dataPtr()); - } - - void deleteLink(GList* _l) { - fm_file_info_list_delete_link(dataPtr(), _l); - } - - void clear() { - fm_file_info_list_clear(dataPtr()); - } - - -private: - FmFileInfoList* dataPtr_; // data pointer for the underlying C struct - -}; - - - -class LIBFM_QT_API FileInfo { -public: - - - FileInfo( ) { - dataPtr_ = reinterpret_cast(fm_file_info_new()); - } - - - FileInfo(FmFileInfo* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(fm_file_info_ref(dataPtr)) : nullptr; - } - - - // copy constructor - FileInfo(const FileInfo& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(fm_file_info_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - FileInfo(FileInfo&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - ~FileInfo() { - if(dataPtr_ != nullptr) { - fm_file_info_unref(dataPtr_); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static FileInfo wrapPtr(FmFileInfo* dataPtr) { - FileInfo obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmFileInfo* takeDataPtr() { - FmFileInfo* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmFileInfo* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmFileInfo*() { - return dataPtr(); - } - - - // copy assignment - FileInfo& operator=(const FileInfo& other) { - if(dataPtr_ != nullptr) { - fm_file_info_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(fm_file_info_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - FileInfo& operator=(FileInfo&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - bool canSetHidden(void) { - return fm_file_info_can_set_hidden(dataPtr()); - } - - - bool canSetIcon(void) { - return fm_file_info_can_set_icon(dataPtr()); - } - - - bool canSetName(void) { - return fm_file_info_can_set_name(dataPtr()); - } - - - bool canThumbnail(void) { - return fm_file_info_can_thumbnail(dataPtr()); - } - - - dev_t getDev(void) { - return fm_file_info_get_dev(dataPtr()); - } - - - gid_t getGid(void) { - return fm_file_info_get_gid(dataPtr()); - } - - - uid_t getUid(void) { - return fm_file_info_get_uid(dataPtr()); - } - - const char* getDispGroup() { - return fm_file_info_get_disp_group(dataPtr()); - } - - const char* getFsId() { - return fm_file_info_get_fs_id(dataPtr()); - } - - FmIcon* getIcon(void) { - return fm_file_info_get_icon(dataPtr()); - } - - - time_t getCtime(void) { - return fm_file_info_get_ctime(dataPtr()); - } - - - time_t getAtime(void) { - return fm_file_info_get_atime(dataPtr()); - } - - - time_t getMtime(void) { - return fm_file_info_get_mtime(dataPtr()); - } - - - const char* getTarget() { - return fm_file_info_get_target(dataPtr()); - } - - const char* getCollateKey() { - return fm_file_info_get_collate_key(dataPtr()); - } - - const char* getCollateKeyNoCaseFold() { - return fm_file_info_get_collate_key_nocasefold(dataPtr()); - } - - const char* getDesc() { - return fm_file_info_get_desc(dataPtr()); - } - - const char* getDispMtime() { - return fm_file_info_get_disp_mtime(dataPtr()); - } - - bool isWritableDirectory(void) { - return fm_file_info_is_writable_directory(dataPtr()); - } - - - bool isAccessible(void) { - return fm_file_info_is_accessible(dataPtr()); - } - - - bool isExecutableType(void) { - return fm_file_info_is_executable_type(dataPtr()); - } - - - bool isBackup(void) { - return fm_file_info_is_backup(dataPtr()); - } - - - bool isHidden(void) { - return fm_file_info_is_hidden(dataPtr()); - } - - - bool isUnknownType(void) { - return fm_file_info_is_unknown_type(dataPtr()); - } - - - bool isDesktopEntry(void) { - return fm_file_info_is_desktop_entry(dataPtr()); - } - - - bool isText(void) { - return fm_file_info_is_text(dataPtr()); - } - - - bool isImage(void) { - return fm_file_info_is_image(dataPtr()); - } - - - bool isMountable(void) { - return fm_file_info_is_mountable(dataPtr()); - } - - - bool isShortcut(void) { - return fm_file_info_is_shortcut(dataPtr()); - } - - - bool isSymlink(void) { - return fm_file_info_is_symlink(dataPtr()); - } - - - bool isDir(void) { - return fm_file_info_is_dir(dataPtr()); - } - - - FmMimeType* getMimeType(void) { - return fm_file_info_get_mime_type(dataPtr()); - } - - - bool isNative(void) { - return fm_file_info_is_native(dataPtr()); - } - - - mode_t getMode(void) { - return fm_file_info_get_mode(dataPtr()); - } - - - goffset getBlocks(void) { - return fm_file_info_get_blocks(dataPtr()); - } - - - goffset getSize(void) { - return fm_file_info_get_size(dataPtr()); - } - - const char* getDispSize() { - return fm_file_info_get_disp_size(dataPtr()); - } - - void setIcon(GIcon* icon) { - fm_file_info_set_icon(dataPtr(), icon); - } - - - void setDispName(const char* name) { - fm_file_info_set_disp_name(dataPtr(), name); - } - - - void setPath(FmPath* path) { - fm_file_info_set_path(dataPtr(), path); - } - - - const char* getName() { - return fm_file_info_get_name(dataPtr()); - } - - - const char* getDispName() { - return fm_file_info_get_disp_name(dataPtr()); - } - - - FmPath* getPath(void) { - return fm_file_info_get_path(dataPtr()); - } - - - void update(FmFileInfo* src) { - fm_file_info_update(dataPtr(), src); - } - - - static FileInfo newFromNativeFile(FmPath* path, const char* path_str, GError** err) { - return FileInfo::wrapPtr(fm_file_info_new_from_native_file(path, path_str, err)); - } - - - bool setFromNativeFile(const char* path, GError** err) { - return fm_file_info_set_from_native_file(dataPtr(), path, err); - } - - - void setFromMenuCacheItem(struct _MenuCacheItem* item) { - fm_file_info_set_from_menu_cache_item(dataPtr(), item); - } - - - static FileInfo newFromMenuCacheItem(FmPath* path, struct _MenuCacheItem* item) { - return FileInfo::wrapPtr(fm_file_info_new_from_menu_cache_item(path, item)); - } - - - void setFromGFileData(GFile* gf, GFileInfo* inf) { - fm_file_info_set_from_g_file_data(dataPtr(), gf, inf); - } - - - static FileInfo newFromGFileData(GFile* gf, GFileInfo* inf, FmPath* path) { - return FileInfo::wrapPtr(fm_file_info_new_from_g_file_data(gf, inf, path)); - } - - - void setFromGfileinfo(GFileInfo* inf) { - fm_file_info_set_from_gfileinfo(dataPtr(), inf); - } - - - static FileInfo newFromGfileinfo(FmPath* path, GFileInfo* inf) { - return FileInfo::wrapPtr(fm_file_info_new_from_gfileinfo(path, inf)); - } - - -private: - FmFileInfo* dataPtr_; // data pointer for the underlying C struct - -}; - - -} - -#endif // __LIBFM_QT_FM_FILE_INFO_H__ diff --git a/src/fileinfojob.h b/src/fileinfojob.h deleted file mode 100644 index 396b254..0000000 --- a/src/fileinfojob.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_FILE_INFO_JOB_H__ -#define __LIBFM_QT_FM_FILE_INFO_JOB_H__ - -#include -#include -#include -#include "libfmqtglobals.h" -#include "job.h" - -namespace Fm { - - -class LIBFM_QT_API FileInfoJob: public Job { -public: - - - FileInfoJob(FmPathList* files_to_query, FmFileInfoJobFlags flags) { - dataPtr_ = reinterpret_cast(fm_file_info_job_new(files_to_query, flags)); - } - - - // default constructor - FileInfoJob() { - dataPtr_ = nullptr; - } - - - FileInfoJob(FmFileInfoJob* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(g_object_ref(dataPtr)) : nullptr; - } - - - // copy constructor - FileInfoJob(const FileInfoJob& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - FileInfoJob(FileInfoJob&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - - // create a wrapper for the data pointer without increasing the reference count - static FileInfoJob wrapPtr(FmFileInfoJob* dataPtr) { - FileInfoJob obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmFileInfoJob* takeDataPtr() { - FmFileInfoJob* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmFileInfoJob* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmFileInfoJob*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - FileInfoJob& operator=(const FileInfoJob& other) { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - FileInfoJob& operator=(FileInfoJob&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - FmPath* getCurrent(void) { - return fm_file_info_job_get_current(dataPtr()); - } - - - void addGfile(GFile* gf) { - fm_file_info_job_add_gfile(dataPtr(), gf); - } - - - void add(FmPath* path) { - fm_file_info_job_add(dataPtr(), path); - } - - - -}; - - -} - -#endif // __LIBFM_QT_FM_FILE_INFO_JOB_H__ diff --git a/src/filelauncher.cpp b/src/filelauncher.cpp index 1bee905..6131c01 100644 --- a/src/filelauncher.cpp +++ b/src/filelauncher.cpp @@ -29,94 +29,116 @@ namespace Fm { FmFileLauncher FileLauncher::funcs = { - FileLauncher::_getApp, - /* gboolean (*before_open)(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data); */ - (FmLaunchFolderFunc)FileLauncher::_openFolder, - FileLauncher::_execFile, - FileLauncher::_error, - FileLauncher::_ask + FileLauncher::_getApp, + /* gboolean (*before_open)(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data); */ + (FmLaunchFolderFunc)FileLauncher::_openFolder, + FileLauncher::_execFile, + FileLauncher::_error, + FileLauncher::_ask, + nullptr }; FileLauncher::FileLauncher(): - quickExec_(false) { + quickExec_(false) { } FileLauncher::~FileLauncher() { } -//static +bool FileLauncher::launchFiles(QWidget *parent, Fm::FileInfoList file_infos) { + // FIXME: rewrite + return launchPaths(parent, file_infos.paths()); +} + +bool FileLauncher::launchPaths(QWidget *parent, Fm::FilePathList paths) { + // FIXME: rewrite, port to new api + GList* tmp = nullptr; + for(auto& path: paths) { + auto fmpath = fm_path_new_for_gfile(path.gfile().get()); + tmp = g_list_prepend(tmp, fmpath); + } + tmp = g_list_reverse(tmp); + bool ret = launchPaths(parent, tmp); + g_list_free(tmp); + return ret; +} + bool FileLauncher::launchFiles(QWidget* parent, GList* file_infos) { - FmAppLaunchContext* context = fm_app_launch_context_new_for_widget(parent); - bool ret = fm_launch_files(G_APP_LAUNCH_CONTEXT(context), file_infos, &funcs, this); - g_object_unref(context); - return ret; + FmAppLaunchContext* context = fm_app_launch_context_new_for_widget(parent); + bool ret = fm_launch_files(G_APP_LAUNCH_CONTEXT(context), file_infos, &funcs, this); + g_object_unref(context); + return ret; } bool FileLauncher::launchPaths(QWidget* parent, GList* paths) { - FmAppLaunchContext* context = fm_app_launch_context_new_for_widget(parent); - bool ret = fm_launch_paths(G_APP_LAUNCH_CONTEXT(context), paths, &funcs, this); - g_object_unref(context); - return ret; + FmAppLaunchContext* context = fm_app_launch_context_new_for_widget(parent); + bool ret = fm_launch_paths(G_APP_LAUNCH_CONTEXT(context), paths, &funcs, this); + g_object_unref(context); + return ret; } -GAppInfo* FileLauncher::getApp(GList* file_infos, FmMimeType* mime_type, GError** err) { - AppChooserDialog dlg(NULL); - if(mime_type) - dlg.setMimeType(mime_type); - else - dlg.setCanSetDefault(false); - // FIXME: show error properly? - if(execModelessDialog(&dlg) == QDialog::Accepted) { - return dlg.selectedApp(); - } - return NULL; +GAppInfo* FileLauncher::getApp(GList* /*file_infos*/, FmMimeType* mime_type, GError** /*err*/) { + AppChooserDialog dlg(nullptr); + if(mime_type) { + dlg.setMimeType(Fm::MimeType::fromName(fm_mime_type_get_type(mime_type))); + } + else { + dlg.setCanSetDefault(false); + } + // FIXME: show error properly? + if(execModelessDialog(&dlg) == QDialog::Accepted) { + auto app = dlg.selectedApp(); + return app.release(); + } + return nullptr; } -bool FileLauncher::openFolder(GAppLaunchContext* ctx, GList* folder_infos, GError** err) { - for(GList* l = folder_infos; l; l = l->next) { - FmFileInfo* fi = FM_FILE_INFO(l->data); - qDebug() << " folder:" << QString::fromUtf8(fm_file_info_get_disp_name(fi)); - } - return false; +bool FileLauncher::openFolder(GAppLaunchContext* /*ctx*/, GList* folder_infos, GError** /*err*/) { + for(GList* l = folder_infos; l; l = l->next) { + FmFileInfo* fi = FM_FILE_INFO(l->data); + qDebug() << " folder:" << QString::fromUtf8(fm_file_info_get_disp_name(fi)); + } + return false; } FmFileLauncherExecAction FileLauncher::execFile(FmFileInfo* file) { - if (quickExec_) { - /* SF bug#838: open terminal for each script may be just a waste. - User should open a terminal and start the script there - in case if user wants to see the script output anyway. - if (fm_file_info_is_text(file)) - return FM_FILE_LAUNCHER_EXEC_IN_TERMINAL; */ - return FM_FILE_LAUNCHER_EXEC; - } - - FmFileLauncherExecAction res = FM_FILE_LAUNCHER_EXEC_CANCEL; - ExecFileDialog dlg(file); - if(execModelessDialog(&dlg) == QDialog::Accepted) { - res = dlg.result(); - } - return res; + if(quickExec_) { + /* SF bug#838: open terminal for each script may be just a waste. + User should open a terminal and start the script there + in case if user wants to see the script output anyway. + if (fm_file_info_is_text(file)) + return FM_FILE_LAUNCHER_EXEC_IN_TERMINAL; */ + return FM_FILE_LAUNCHER_EXEC; + } + + FmFileLauncherExecAction res = FM_FILE_LAUNCHER_EXEC_CANCEL; + ExecFileDialog dlg(file); + if(execModelessDialog(&dlg) == QDialog::Accepted) { + res = dlg.result(); + } + return res; } -int FileLauncher::ask(const char* msg, char* const* btn_labels, int default_btn) { - /* FIXME: set default button properly */ - // return fm_askv(data->parent, NULL, msg, btn_labels); - return -1; +int FileLauncher::ask(const char* /*msg*/, char* const* /*btn_labels*/, int /*default_btn*/) { + /* FIXME: set default button properly */ + // return fm_askv(data->parent, nullptr, msg, btn_labels); + return -1; } -bool FileLauncher::error(GAppLaunchContext* ctx, GError* err, FmPath* path) { - /* ask for mount if trying to launch unmounted path */ - if(err->domain == G_IO_ERROR) { - if(path && err->code == G_IO_ERROR_NOT_MOUNTED) { - //if(fm_mount_path(data->parent, path, TRUE)) - // return FALSE; /* ask to retry */ +bool FileLauncher::error(GAppLaunchContext* /*ctx*/, GError* err, FmPath* path) { + /* ask for mount if trying to launch unmounted path */ + if(err->domain == G_IO_ERROR) { + if(path && err->code == G_IO_ERROR_NOT_MOUNTED) { + //if(fm_mount_path(data->parent, path, TRUE)) + // return FALSE; /* ask to retry */ + } + else if(err->code == G_IO_ERROR_FAILED_HANDLED) { + return true; /* don't show error message */ + } } - else if(err->code == G_IO_ERROR_FAILED_HANDLED) - return true; /* don't show error message */ - } - QMessageBox dlg(QMessageBox::Critical, QObject::tr("Error"), QString::fromUtf8(err->message), QMessageBox::Ok); - execModelessDialog(&dlg); - return true; + QMessageBox dlg(QMessageBox::Critical, QObject::tr("Error"), QString::fromUtf8(err->message), QMessageBox::Ok); + execModelessDialog(&dlg); + return true; } diff --git a/src/filelauncher.h b/src/filelauncher.h index 81c1dc6..9ebcfe6 100644 --- a/src/filelauncher.h +++ b/src/filelauncher.h @@ -24,62 +24,59 @@ #include "libfmqtglobals.h" #include #include +#include "core/fileinfo.h" namespace Fm { class LIBFM_QT_API FileLauncher { public: - FileLauncher(); - virtual ~FileLauncher(); + explicit FileLauncher(); + virtual ~FileLauncher(); - bool launchFiles(QWidget* parent, FmFileInfoList* file_infos) { - GList* fileList = fm_file_info_list_peek_head_link(file_infos); - return launchFiles(parent, fileList); - } - bool launchPaths(QWidget* parent, FmPathList* paths) { - GList* pathList = fm_path_list_peek_head_link(paths); - return launchPaths(parent, pathList); - } + bool launchFiles(QWidget* parent, Fm::FileInfoList file_infos); - bool launchFiles(QWidget* parent, GList* file_infos); - bool launchPaths(QWidget* parent, GList* paths); + bool launchPaths(QWidget* parent, Fm::FilePathList paths); - bool quickExec() const { - return quickExec_; - } + bool quickExec() const { + return quickExec_; + } - void setQuickExec(bool value) { - quickExec_ = value; - } + void setQuickExec(bool value) { + quickExec_ = value; + } protected: - virtual GAppInfo* getApp(GList* file_infos, FmMimeType* mime_type, GError** err); - virtual bool openFolder(GAppLaunchContext* ctx, GList* folder_infos, GError** err); - virtual FmFileLauncherExecAction execFile(FmFileInfo* file); - virtual bool error(GAppLaunchContext* ctx, GError* err, FmPath* path); - virtual int ask(const char* msg, char* const* btn_labels, int default_btn); + virtual GAppInfo* getApp(GList* file_infos, FmMimeType* mime_type, GError** err); + virtual bool openFolder(GAppLaunchContext* ctx, GList* folder_infos, GError** err); + virtual FmFileLauncherExecAction execFile(FmFileInfo* file); + virtual bool error(GAppLaunchContext* ctx, GError* err, FmPath* path); + virtual int ask(const char* msg, char* const* btn_labels, int default_btn); private: - static GAppInfo* _getApp(GList* file_infos, FmMimeType* mime_type, gpointer user_data, GError** err) { - return reinterpret_cast(user_data)->getApp(file_infos, mime_type, err); - } - static gboolean _openFolder(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data, GError** err) { - return reinterpret_cast(user_data)->openFolder(ctx, folder_infos, err); - } - static FmFileLauncherExecAction _execFile(FmFileInfo* file, gpointer user_data) { - return reinterpret_cast(user_data)->execFile(file); - } - static gboolean _error(GAppLaunchContext* ctx, GError* err, FmPath* file, gpointer user_data) { - return reinterpret_cast(user_data)->error(ctx, err, file); - } - static int _ask(const char* msg, char* const* btn_labels, int default_btn, gpointer user_data) { - return reinterpret_cast(user_data)->ask(msg, btn_labels, default_btn); - } + bool launchFiles(QWidget* parent, GList* file_infos); + + bool launchPaths(QWidget* parent, GList* paths); + + static GAppInfo* _getApp(GList* file_infos, FmMimeType* mime_type, gpointer user_data, GError** err) { + return reinterpret_cast(user_data)->getApp(file_infos, mime_type, err); + } + static gboolean _openFolder(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data, GError** err) { + return reinterpret_cast(user_data)->openFolder(ctx, folder_infos, err); + } + static FmFileLauncherExecAction _execFile(FmFileInfo* file, gpointer user_data) { + return reinterpret_cast(user_data)->execFile(file); + } + static gboolean _error(GAppLaunchContext* ctx, GError* err, FmPath* file, gpointer user_data) { + return reinterpret_cast(user_data)->error(ctx, err, file); + } + static int _ask(const char* msg, char* const* btn_labels, int default_btn, gpointer user_data) { + return reinterpret_cast(user_data)->ask(msg, btn_labels, default_btn); + } private: - static FmFileLauncher funcs; - bool quickExec_; // Don't ask options on launch executable file + static FmFileLauncher funcs; + bool quickExec_; // Don't ask options on launch executable file }; } diff --git a/src/filemenu.cpp b/src/filemenu.cpp index 49bc6e2..e3942a3 100644 --- a/src/filemenu.cpp +++ b/src/filemenu.cpp @@ -26,383 +26,393 @@ #include "fileoperation.h" #include "filelauncher.h" #include "appchooserdialog.h" -#ifdef CUSTOM_ACTIONS -#include + +#include "customactions/fileaction.h" #include "customaction_p.h" -#endif + #include +#include #include #include "filemenu_p.h" -namespace Fm { - -FileMenu::FileMenu(FmFileInfoList* files, FmFileInfo* info, FmPath* cwd, QWidget* parent): - QMenu(parent), - fileLauncher_(NULL) { - createMenu(files, info, cwd); -} - -FileMenu::FileMenu(FmFileInfoList* files, FmFileInfo* info, FmPath* cwd, const QString& title, QWidget* parent): - QMenu(title, parent), - unTrashAction_(NULL), - fileLauncher_(NULL) { - createMenu(files, info, cwd); -} +#include "core/compat_p.h" -FileMenu::~FileMenu() { - if(files_) - fm_file_info_list_unref(files_); - if(info_) - fm_file_info_unref(info_); - if(cwd_) - fm_path_unref(cwd_); -} +namespace Fm { -void FileMenu::createMenu(FmFileInfoList* files, FmFileInfo* info, FmPath* cwd) { - useTrash_ = true; - confirmDelete_ = true; - confirmTrash_ = false; // Confirm before moving files into "trash can" - - openAction_ = NULL; - openWithMenuAction_ = NULL; - openWithAction_ = NULL; - separator1_ = NULL; - cutAction_ = NULL; - copyAction_ = NULL; - pasteAction_ = NULL; - deleteAction_ = NULL; - unTrashAction_ = NULL; - renameAction_ = NULL; - separator2_ = NULL; - propertiesAction_ = NULL; - - files_ = fm_file_info_list_ref(files); - info_ = info ? fm_file_info_ref(info) : NULL; - cwd_ = cwd ? fm_path_ref(cwd) : NULL; - - FmFileInfo* first = fm_file_info_list_peek_head(files); - FmMimeType* mime_type = fm_file_info_get_mime_type(first); - FmPath* path = fm_file_info_get_path(first); - // check if the files are of the same type - sameType_ = fm_file_info_list_is_same_type(files); - // check if the files are on the same filesystem - sameFilesystem_ = fm_file_info_list_is_same_fs(files); - // check if the files are all virtual - allVirtual_ = sameFilesystem_ && fm_path_is_virtual(path); - // check if the files are all in the trash can - allTrash_ = sameFilesystem_ && fm_path_is_trash(path); - - openAction_ = new QAction(QIcon::fromTheme("document-open"), tr("Open"), this); - connect(openAction_ , &QAction::triggered, this, &FileMenu::onOpenTriggered); - addAction(openAction_); - - openWithMenuAction_ = new QAction(tr("Open With..."), this); - addAction(openWithMenuAction_); - // create the "Open with..." sub menu - QMenu* menu = new QMenu(this); - openWithMenuAction_->setMenu(menu); - - if(sameType_) { /* add specific menu items for this mime type */ - if(mime_type && !allVirtual_) { /* the file has a valid mime-type and its not virtual */ - GList* apps = g_app_info_get_all_for_type(fm_mime_type_get_type(mime_type)); - GList* l; - for(l=apps;l;l=l->next) { - GAppInfo* app = G_APP_INFO(l->data); - - // check if the command really exists - gchar * program_path = g_find_program_in_path(g_app_info_get_executable(app)); - if (!program_path) - continue; - g_free(program_path); - - // create a QAction for the application. - AppInfoAction* action = new AppInfoAction(app, menu); - connect(action, &QAction::triggered, this, &FileMenu::onApplicationTriggered); - menu->addAction(action); - } - // unref GAppInfos here, they are still ref'ed in the AppInfoActions above - g_list_foreach(apps, (GFunc)g_object_unref, NULL); - g_list_free(apps); - } - } - menu->addSeparator(); - openWithAction_ = new QAction(tr("Other Applications"), this); - connect(openWithAction_ , &QAction::triggered, this, &FileMenu::onOpenWithTriggered); - menu->addAction(openWithAction_); - - separator1_ = addSeparator(); - - createAction_ = new QAction(tr("Create &New"), this); - FmPath* dirPath = fm_file_info_list_get_length(files) == 1 && fm_file_info_is_dir(first) - ? path : cwd_; - createAction_->setMenu(new CreateNewMenu(NULL, dirPath, this)); - addAction(createAction_); - - separator2_ = addSeparator(); - - if(allTrash_) { // all selected files are in trash:/// - bool can_restore = true; - /* only immediate children of trash:/// can be restored. */ - for(GList* l = fm_file_info_list_peek_head_link(files_); l; l=l->next) { - FmPath *trash_path = fm_file_info_get_path(FM_FILE_INFO(l->data)); - if(!fm_path_get_parent(trash_path) || - !fm_path_is_trash_root(fm_path_get_parent(trash_path))) { - can_restore = false; - break; +FileMenu::FileMenu(Fm::FileInfoList files, std::shared_ptr info, Fm::FilePath cwd, bool isWritableDir, const QString& title, QWidget* parent): + QMenu(title, parent), + files_{std::move(files)}, + info_{std::move(info)}, + cwd_{std::move(cwd)}, + unTrashAction_(nullptr), + fileLauncher_(nullptr) { + + useTrash_ = true; + confirmDelete_ = true; + confirmTrash_ = false; // Confirm before moving files into "trash can" + + openAction_ = nullptr; + openWithMenuAction_ = nullptr; + openWithAction_ = nullptr; + separator1_ = nullptr; + cutAction_ = nullptr; + copyAction_ = nullptr; + pasteAction_ = nullptr; + deleteAction_ = nullptr; + unTrashAction_ = nullptr; + renameAction_ = nullptr; + separator2_ = nullptr; + propertiesAction_ = nullptr; + + auto mime_type = info_->mimeType(); + Fm::FilePath path = info_->path(); + + // check if the files are of the same type + sameType_ = files_.isSameType(); + // check if the files are on the same filesystem + sameFilesystem_ = files_.isSameFilesystem(); + // check if the files are all virtual + + // FIXME: allVirtual_ = sameFilesystem_ && fm_path_is_virtual(path); + allVirtual_ = false; + + // check if the files are all in the trash can + allTrash_ = sameFilesystem_ && path.hasUriScheme("trash"); + + openAction_ = new QAction(QIcon::fromTheme("document-open"), tr("Open"), this); + connect(openAction_, &QAction::triggered, this, &FileMenu::onOpenTriggered); + addAction(openAction_); + + openWithMenuAction_ = new QAction(tr("Open With..."), this); + addAction(openWithMenuAction_); + // create the "Open with..." sub menu + QMenu* menu = new QMenu(this); + openWithMenuAction_->setMenu(menu); + + if(sameType_) { /* add specific menu items for this mime type */ + if(mime_type && !allVirtual_) { /* the file has a valid mime-type and its not virtual */ + GList* apps = g_app_info_get_all_for_type(mime_type->name()); + GList* l; + for(l = apps; l; l = l->next) { + Fm::GAppInfoPtr app{G_APP_INFO(l->data), false}; + // check if the command really exists + gchar* program_path = g_find_program_in_path(g_app_info_get_executable(app.get())); + if(!program_path) { + continue; + } + g_free(program_path); + + // create a QAction for the application. + AppInfoAction* action = new AppInfoAction(std::move(app), menu); + connect(action, &QAction::triggered, this, &FileMenu::onApplicationTriggered); + menu->addAction(action); + } + g_list_free(apps); } } - if(can_restore) { - unTrashAction_ = new QAction(tr("&Restore"), this); - connect(unTrashAction_, &QAction::triggered, this, &FileMenu::onUnTrashTriggered); - addAction(unTrashAction_); + menu->addSeparator(); + openWithAction_ = new QAction(tr("Other Applications"), this); + connect(openWithAction_, &QAction::triggered, this, &FileMenu::onOpenWithTriggered); + menu->addAction(openWithAction_); + + separator1_ = addSeparator(); + + createAction_ = new QAction(tr("Create &New"), this); + Fm::FilePath dirPath = files_.size() == 1 && info_->isDir() ? path : cwd_; + createAction_->setMenu(new CreateNewMenu(nullptr, dirPath, this)); + addAction(createAction_); + + separator2_ = addSeparator(); + + if(allTrash_) { // all selected files are in trash:/// + bool can_restore = true; + /* only immediate children of trash:/// can be restored. */ + auto trash_root = Fm::FilePath::fromUri("trash:///"); + for(auto& file: files_) { + Fm::FilePath trash_path = file->path(); + if(!trash_root.isParentOf(trash_path)) { + can_restore = false; + break; + } + } + if(can_restore) { + unTrashAction_ = new QAction(tr("&Restore"), this); + connect(unTrashAction_, &QAction::triggered, this, &FileMenu::onUnTrashTriggered); + addAction(unTrashAction_); + } } - } - else { // ordinary files - cutAction_ = new QAction(QIcon::fromTheme("edit-cut"), tr("Cut"), this); - connect(cutAction_, &QAction::triggered, this, &FileMenu::onCutTriggered); - addAction(cutAction_); - - copyAction_ = new QAction(QIcon::fromTheme("edit-copy"), tr("Copy"), this); - connect(copyAction_, &QAction::triggered, this, &FileMenu::onCopyTriggered); - addAction(copyAction_); - - pasteAction_ = new QAction(QIcon::fromTheme("edit-paste"), tr("Paste"), this); - connect(pasteAction_, &QAction::triggered, this, &FileMenu::onPasteTriggered); - addAction(pasteAction_); - - deleteAction_ = new QAction(QIcon::fromTheme("user-trash"), tr("&Move to Trash"), this); - connect(deleteAction_, &QAction::triggered, this, &FileMenu::onDeleteTriggered); - addAction(deleteAction_); - - renameAction_ = new QAction(tr("Rename"), this); - connect(renameAction_, &QAction::triggered, this, &FileMenu::onRenameTriggered); - addAction(renameAction_); - } - -#ifdef CUSTOM_ACTIONS - // DES-EMA custom actions integration - GList* files_list = fm_file_info_list_peek_head_link(files); - GList* items = fm_get_actions_for_files(files_list); - if(items) { - GList* l; - for(l=items; l; l=l->next) { - FmFileActionItem* item = FM_FILE_ACTION_ITEM(l->data); - if(l == items && item - && !(fm_file_action_item_is_action(item) - && !(fm_file_action_item_get_target(item) & FM_FILE_ACTION_TARGET_CONTEXT))) { - addSeparator(); // before all custom actions - } - addCustomActionItem(this, item); + else { // ordinary files + cutAction_ = new QAction(QIcon::fromTheme("edit-cut"), tr("Cut"), this); + connect(cutAction_, &QAction::triggered, this, &FileMenu::onCutTriggered); + addAction(cutAction_); + + copyAction_ = new QAction(QIcon::fromTheme("edit-copy"), tr("Copy"), this); + connect(copyAction_, &QAction::triggered, this, &FileMenu::onCopyTriggered); + addAction(copyAction_); + + pasteAction_ = new QAction(QIcon::fromTheme("edit-paste"), tr("Paste"), this); + connect(pasteAction_, &QAction::triggered, this, &FileMenu::onPasteTriggered); + addAction(pasteAction_); + + deleteAction_ = new QAction(QIcon::fromTheme("user-trash"), tr("&Move to Trash"), this); + connect(deleteAction_, &QAction::triggered, this, &FileMenu::onDeleteTriggered); + addAction(deleteAction_); + + renameAction_ = new QAction(tr("Rename"), this); + connect(renameAction_, &QAction::triggered, this, &FileMenu::onRenameTriggered); + addAction(renameAction_); + + // disable actons that can't be used + bool hasAccessible(false); + bool hasDeletable(false); + bool hasRenamable(false); + for(auto& file: files_) { + if(file->isAccessible()) { + hasAccessible = true; + } + if(file->isDeletable()) { + hasDeletable = true; + } + if(file->canSetName()) { + hasRenamable = true; + } + if (hasAccessible && hasDeletable && hasRenamable) { + break; + } + } + copyAction_->setEnabled(hasAccessible); + cutAction_->setEnabled(hasDeletable); + deleteAction_->setEnabled(hasDeletable); + renameAction_->setEnabled(hasRenamable); + if(!(sameType_ && info_->isDir() + && (files_.size() > 1 ? isWritableDir : info_->isWritable()))) { + pasteAction_->setEnabled(false); + } } - } - g_list_foreach(items, (GFunc)fm_file_action_item_unref, NULL); - g_list_free(items); -#endif - // archiver integration - // FIXME: we need to modify upstream libfm to include some Qt-based archiver programs. - if(!allVirtual_) { - if(sameType_) { - FmArchiver* archiver = fm_archiver_get_default(); - if(archiver) { - if(fm_archiver_is_mime_type_supported(archiver, fm_mime_type_get_type(mime_type))) { - if(cwd_ && archiver->extract_to_cmd) { - QAction* action = new QAction(tr("Extract to..."), this); - connect(action, &QAction::triggered, this, &FileMenu::onExtract); - addAction(action); - } - if(archiver->extract_cmd) { - QAction* action = new QAction(tr("Extract Here"), this); - connect(action, &QAction::triggered, this, &FileMenu::onExtractHere); - addAction(action); - } + + // DES-EMA custom actions integration + // FIXME: port these parts to Fm API + auto custom_actions = FileActionItem::get_actions_for_files(files_); + for(auto& item: custom_actions) { + if(item && !(item->get_target() & FILE_ACTION_TARGET_CONTEXT)) { + continue; // this item is not for context menu + } + if(item == custom_actions.front() && !item->is_action()) { + addSeparator(); // before all custom actions } - else { - QAction* action = new QAction(tr("Compress"), this); - connect(action, &QAction::triggered, this, &FileMenu::onCompress); - addAction(action); + addCustomActionItem(this, item); + } + + // archiver integration + // FIXME: we need to modify upstream libfm to include some Qt-based archiver programs. + if(!allVirtual_) { + if(sameType_) { + // FIXME: port these parts to Fm API + FmArchiver* archiver = fm_archiver_get_default(); + if(archiver) { + if(fm_archiver_is_mime_type_supported(archiver, mime_type->name())) { + QAction* archiverSeparator = nullptr; + if(cwd_ && archiver->extract_to_cmd) { + archiverSeparator = addSeparator(); + QAction* action = new QAction(tr("Extract to..."), this); + connect(action, &QAction::triggered, this, &FileMenu::onExtract); + addAction(action); + } + if(archiver->extract_cmd) { + if(!archiverSeparator) { + addSeparator(); + } + QAction* action = new QAction(tr("Extract Here"), this); + connect(action, &QAction::triggered, this, &FileMenu::onExtractHere); + addAction(action); + } + } + else { + addSeparator(); + QAction* action = new QAction(tr("Compress"), this); + connect(action, &QAction::triggered, this, &FileMenu::onCompress); + addAction(action); + } + } } - } } - } - separator3_ = addSeparator(); + separator3_ = addSeparator(); + + propertiesAction_ = new QAction(QIcon::fromTheme("document-properties"), tr("Properties"), this); + connect(propertiesAction_, &QAction::triggered, this, &FileMenu::onFilePropertiesTriggered); + addAction(propertiesAction_); +} - propertiesAction_ = new QAction(QIcon::fromTheme("document-properties"), tr("Properties"), this); - connect(propertiesAction_, &QAction::triggered, this, &FileMenu::onFilePropertiesTriggered); - addAction(propertiesAction_); +FileMenu::~FileMenu() { } -#ifdef CUSTOM_ACTIONS -void FileMenu::addCustomActionItem(QMenu* menu, FmFileActionItem* item) { - if(!item) { // separator - addSeparator(); - return; - } - - // this action is not for context menu - if(fm_file_action_item_is_action(item) && !(fm_file_action_item_get_target(item) & FM_FILE_ACTION_TARGET_CONTEXT)) - return; - - CustomAction* action = new CustomAction(item, menu); - menu->addAction(action); - if(fm_file_action_item_is_menu(item)) { - GList* subitems = fm_file_action_item_get_sub_items(item); - if (subitems != NULL) { - QMenu* submenu = new QMenu(menu); - for(GList* l = subitems; l; l = l->next) { - FmFileActionItem* subitem = FM_FILE_ACTION_ITEM(l->data); - addCustomActionItem(submenu, subitem); - } - action->setMenu(submenu); + +void FileMenu::addCustomActionItem(QMenu* menu, std::shared_ptr item) { + if(!item) { // separator + addSeparator(); + return; + } + + // this action is not for context menu + if(item->is_action() && !(item->get_target() & FILE_ACTION_TARGET_CONTEXT)) { + return; + } + + CustomAction* action = new CustomAction(item, menu); + menu->addAction(action); + if(item->is_menu()) { + auto& subitems = item->get_sub_items(); + if(!subitems.empty()) { + QMenu* submenu = new QMenu(menu); + for(auto& subitem: subitems) { + addCustomActionItem(submenu, subitem); + } + action->setMenu(submenu); + } + } + else if(item->is_action()) { + connect(action, &QAction::triggered, this, &FileMenu::onCustomActionTrigerred); } - } - else if(fm_file_action_item_is_action(item)) { - connect(action, &QAction::triggered, this, &FileMenu::onCustomActionTrigerred); - } } -#endif void FileMenu::onOpenTriggered() { - if(fileLauncher_) { - fileLauncher_->launchFiles(NULL, files_); - } - else { // use the default launcher - Fm::FileLauncher launcher; - launcher.launchFiles(NULL, files_); - } + if(fileLauncher_) { + fileLauncher_->launchFiles(nullptr, files_); + } + else { // use the default launcher + Fm::FileLauncher launcher; + launcher.launchFiles(nullptr, files_); + } } void FileMenu::onOpenWithTriggered() { - AppChooserDialog dlg(NULL); - if(sameType_) { - dlg.setMimeType(fm_file_info_get_mime_type(info_)); - } - else { // we can only set the selected app as default if all files are of the same type - dlg.setCanSetDefault(false); - } - - if(execModelessDialog(&dlg) == QDialog::Accepted) { - GAppInfo* app = dlg.selectedApp(); - if(app) { - openFilesWithApp(app); - g_object_unref(app); + AppChooserDialog dlg(nullptr); + if(sameType_) { + dlg.setMimeType(info_->mimeType()); + } + else { // we can only set the selected app as default if all files are of the same type + dlg.setCanSetDefault(false); + } + + if(execModelessDialog(&dlg) == QDialog::Accepted) { + auto app = dlg.selectedApp(); + if(app) { + openFilesWithApp(app.get()); + } } - } } void FileMenu::openFilesWithApp(GAppInfo* app) { - FmPathList* paths = fm_path_list_new_from_file_info_list(files_); - GList* uris = NULL; - for(GList* l = fm_path_list_peek_head_link(paths); l; l = l->next) { - FmPath* path = FM_PATH(l->data); - char* uri = fm_path_to_uri(path); - uris = g_list_prepend(uris, uri); - } - fm_path_list_unref(paths); - fm_app_info_launch_uris(app, uris, NULL, NULL); - g_list_foreach(uris, (GFunc)g_free, NULL); - g_list_free(uris); + GList* uris = nullptr; + for(auto& file: files_) { + auto uri = file->path().uri(); + uris = g_list_prepend(uris, uri.release()); + } + fm_app_info_launch_uris(app, uris, nullptr, nullptr); + g_list_foreach(uris, (GFunc)g_free, nullptr); + g_list_free(uris); } void FileMenu::onApplicationTriggered() { - AppInfoAction* action = static_cast(sender()); - openFilesWithApp(action->appInfo()); + AppInfoAction* action = static_cast(sender()); + openFilesWithApp(action->appInfo().get()); } -#ifdef CUSTOM_ACTIONS void FileMenu::onCustomActionTrigerred() { - CustomAction* action = static_cast(sender()); - FmFileActionItem* item = action->item(); - - GList* files = fm_file_info_list_peek_head_link(files_); - char* output = NULL; - /* g_debug("item: %s is activated, id:%s", fm_file_action_item_get_name(item), - fm_file_action_item_get_id(item)); */ - fm_file_action_item_launch(item, NULL, files, &output); - if(output) { - QMessageBox::information(this, tr("Output"), QString::fromUtf8(output)); - g_free(output); - } + CustomAction* action = static_cast(sender()); + auto& item = action->item(); + /* g_debug("item: %s is activated, id:%s", fm_file_action_item_get_name(item), + fm_file_action_item_get_id(item)); */ + CStrPtr output; + item->launch(nullptr, files_, output); + if(output) { + QMessageBox::information(this, tr("Output"), output.get()); + } } -#endif void FileMenu::onFilePropertiesTriggered() { - FilePropsDialog::showForFiles(files_); + FilePropsDialog::showForFiles(files_); } void FileMenu::onCopyTriggered() { - FmPathList* paths = fm_path_list_new_from_file_info_list(files_); - Fm::copyFilesToClipboard(paths); - fm_path_list_unref(paths); + Fm::copyFilesToClipboard(files_.paths()); } void FileMenu::onCutTriggered() { - FmPathList* paths = fm_path_list_new_from_file_info_list(files_); - Fm::cutFilesToClipboard(paths); - fm_path_list_unref(paths); + Fm::cutFilesToClipboard(files_.paths()); } void FileMenu::onDeleteTriggered() { - FmPathList* paths = fm_path_list_new_from_file_info_list(files_); - if(useTrash_) - FileOperation::trashFiles(paths, confirmTrash_); - else - FileOperation::deleteFiles(paths, confirmDelete_); - fm_path_list_unref(paths); + auto paths = files_.paths(); + if(useTrash_) { + FileOperation::trashFiles(paths, confirmTrash_); + } + else { + FileOperation::deleteFiles(paths, confirmDelete_); + } } void FileMenu::onUnTrashTriggered() { - FmPathList* paths = fm_path_list_new_from_file_info_list(files_); - FileOperation::unTrashFiles(paths); - fm_path_list_unref(paths); + FileOperation::unTrashFiles(files_.paths()); } void FileMenu::onPasteTriggered() { - Fm::pasteFilesFromClipboard(cwd_); + Fm::pasteFilesFromClipboard(cwd_); } void FileMenu::onRenameTriggered() { - for(GList* l = fm_file_info_list_peek_head_link(files_); l; l = l->next) { - FmFileInfo* info = FM_FILE_INFO(l->data); - Fm::renameFile(info, NULL); - } + // if there is a view and this is a single file, just edit the current index + if(files_.size() == 1) { + if (QAbstractItemView* view = qobject_cast(parentWidget())) { + QModelIndexList selIndexes = view->selectionModel()->selectedIndexes(); + if(selIndexes.size() > 1) { // in the detailed list mode, only the first index is editable + view->setCurrentIndex(selIndexes.at(0)); + } + if (view->currentIndex().isValid()) { + view->edit(view->currentIndex()); + return; + } + } + } + for(auto& info: files_) { + Fm::renameFile(info, nullptr); + } } void FileMenu::setUseTrash(bool trash) { - if(useTrash_ != trash) { - useTrash_ = trash; - if(deleteAction_) { - deleteAction_->setText(useTrash_ ? tr("&Move to Trash") : tr("&Delete")); - deleteAction_->setIcon(useTrash_ ? QIcon::fromTheme("user-trash") : QIcon::fromTheme("edit-delete")); + if(useTrash_ != trash) { + useTrash_ = trash; + if(deleteAction_) { + deleteAction_->setText(useTrash_ ? tr("&Move to Trash") : tr("&Delete")); + deleteAction_->setIcon(useTrash_ ? QIcon::fromTheme("user-trash") : QIcon::fromTheme("edit-delete")); + } } - } } void FileMenu::onCompress() { - FmArchiver* archiver = fm_archiver_get_default(); - if(archiver) { - FmPathList* paths = fm_path_list_new_from_file_info_list(files_); - fm_archiver_create_archive(archiver, NULL, paths); - fm_path_list_unref(paths); - } + FmArchiver* archiver = fm_archiver_get_default(); + if(archiver) { + auto paths = Fm::_convertPathList(files_.paths()); + fm_archiver_create_archive(archiver, nullptr, paths.dataPtr()); + } } void FileMenu::onExtract() { - FmArchiver* archiver = fm_archiver_get_default(); - if(archiver) { - FmPathList* paths = fm_path_list_new_from_file_info_list(files_); - fm_archiver_extract_archives(archiver, NULL, paths); - fm_path_list_unref(paths); - } + FmArchiver* archiver = fm_archiver_get_default(); + if(archiver) { + auto paths = Fm::_convertPathList(files_.paths()); + fm_archiver_extract_archives(archiver, nullptr, paths.dataPtr()); + } } void FileMenu::onExtractHere() { - FmArchiver* archiver = fm_archiver_get_default(); - if(archiver) { - FmPathList* paths = fm_path_list_new_from_file_info_list(files_); - fm_archiver_extract_archives_to(archiver, NULL, paths, cwd_); - fm_path_list_unref(paths); - } + FmArchiver* archiver = fm_archiver_get_default(); + if(archiver) { + auto paths = Fm::_convertPathList(files_.paths()); + auto cwd = Fm::_convertPath(cwd_); + fm_archiver_extract_archives_to(archiver, nullptr, paths.dataPtr(), cwd); + } } } // namespace Fm diff --git a/src/filemenu.h b/src/filemenu.h index 45e9e69..c683d84 100644 --- a/src/filemenu.h +++ b/src/filemenu.h @@ -25,192 +25,186 @@ #include #include #include +#include "core/fileinfo.h" class QAction; -struct _FmFileActionItem; - namespace Fm { class FileLauncher; +class FileActionItem; class LIBFM_QT_API FileMenu : public QMenu { -Q_OBJECT + Q_OBJECT public: - explicit FileMenu(FmFileInfoList* files, FmFileInfo* info, FmPath* cwd, QWidget* parent = 0); - explicit FileMenu(FmFileInfoList* files, FmFileInfo* info, FmPath* cwd, const QString& title, QWidget* parent = 0); - ~FileMenu(); + explicit FileMenu(Fm::FileInfoList files, std::shared_ptr info, Fm::FilePath cwd, bool isWritableDir = true, const QString& title = QString(), QWidget* parent = nullptr); + ~FileMenu(); - bool useTrash() { - return useTrash_; - } + bool useTrash() { + return useTrash_; + } - void setUseTrash(bool trash); + void setUseTrash(bool trash); - bool confirmDelete() { - return confirmDelete_; - } + bool confirmDelete() { + return confirmDelete_; + } - void setConfirmDelete(bool confirm) { - confirmDelete_ = confirm; - } + void setConfirmDelete(bool confirm) { + confirmDelete_ = confirm; + } - QAction* openAction() { - return openAction_; - } + QAction* openAction() { + return openAction_; + } - QAction* openWithMenuAction() { - return openWithMenuAction_; - } + QAction* openWithMenuAction() { + return openWithMenuAction_; + } - QAction* openWithAction() { - return openWithAction_; - } + QAction* openWithAction() { + return openWithAction_; + } - QAction* separator1() { - return separator1_; - } + QAction* separator1() { + return separator1_; + } - QAction* createAction() { - return createAction_; - } + QAction* createAction() { + return createAction_; + } - QAction* separator2() { - return separator2_; - } + QAction* separator2() { + return separator2_; + } - QAction* cutAction() { - return cutAction_; - } + QAction* cutAction() { + return cutAction_; + } - QAction* copyAction() { - return copyAction_; - } + QAction* copyAction() { + return copyAction_; + } - QAction* pasteAction() { - return pasteAction_; - } + QAction* pasteAction() { + return pasteAction_; + } - QAction* deleteAction() { - return deleteAction_; - } + QAction* deleteAction() { + return deleteAction_; + } - QAction* unTrashAction() { - return unTrashAction_; - } + QAction* unTrashAction() { + return unTrashAction_; + } - QAction* renameAction() { - return renameAction_; - } + QAction* renameAction() { + return renameAction_; + } - QAction* separator3() { - return separator3_; - } + QAction* separator3() { + return separator3_; + } - QAction* propertiesAction() { - return propertiesAction_; - } + QAction* propertiesAction() { + return propertiesAction_; + } - FmFileInfoList* files() { - return files_; - } + const Fm::FileInfoList& files() const { + return files_; + } - FmFileInfo* firstFile() { - return info_; - } + const std::shared_ptr& firstFile() const { + return info_; + } - FmPath* cwd() { - return cwd_; - } + const Fm::FilePath& cwd() const { + return cwd_; + } - void setFileLauncher(FileLauncher* launcher) { - fileLauncher_ = launcher; - } + void setFileLauncher(FileLauncher* launcher) { + fileLauncher_ = launcher; + } - FileLauncher* fileLauncher() { - return fileLauncher_; - } + FileLauncher* fileLauncher() { + return fileLauncher_; + } - bool sameType() const { - return sameType_; - } + bool sameType() const { + return sameType_; + } - bool sameFilesystem() const { - return sameFilesystem_; - } + bool sameFilesystem() const { + return sameFilesystem_; + } - bool allVirtual() const { - return allVirtual_; - } + bool allVirtual() const { + return allVirtual_; + } - bool allTrash() const { - return allTrash_; - } + bool allTrash() const { + return allTrash_; + } - bool confirmTrash() const { - return confirmTrash_; - } + bool confirmTrash() const { + return confirmTrash_; + } - void setConfirmTrash(bool value) { - confirmTrash_ = value; - } + void setConfirmTrash(bool value) { + confirmTrash_ = value; + } protected: - void createMenu(FmFileInfoList* files, FmFileInfo* info, FmPath* cwd); -#ifdef CUSTOM_ACTIONS - void addCustomActionItem(QMenu* menu, struct _FmFileActionItem* item); -#endif - void openFilesWithApp(GAppInfo* app); + void addCustomActionItem(QMenu* menu, std::shared_ptr item); + void openFilesWithApp(GAppInfo* app); protected Q_SLOTS: - void onOpenTriggered(); - void onOpenWithTriggered(); - void onFilePropertiesTriggered(); - void onApplicationTriggered(); -#ifdef CUSTOM_ACTIONS - void onCustomActionTrigerred(); -#endif - void onCompress(); - void onExtract(); - void onExtractHere(); - - void onCutTriggered(); - void onCopyTriggered(); - void onPasteTriggered(); - void onRenameTriggered(); - void onDeleteTriggered(); - void onUnTrashTriggered(); + void onOpenTriggered(); + void onOpenWithTriggered(); + void onFilePropertiesTriggered(); + void onApplicationTriggered(); + void onCustomActionTrigerred(); + void onCompress(); + void onExtract(); + void onExtractHere(); + + void onCutTriggered(); + void onCopyTriggered(); + void onPasteTriggered(); + void onRenameTriggered(); + void onDeleteTriggered(); + void onUnTrashTriggered(); private: - FmFileInfoList* files_; - FmFileInfo* info_; - FmPath* cwd_; - bool useTrash_; - bool confirmDelete_; - bool confirmTrash_; // Confirm before moving files into "trash can" - - bool sameType_; - bool sameFilesystem_; - bool allVirtual_; - bool allTrash_; - - QAction* openAction_; - QAction* openWithMenuAction_; - QAction* openWithAction_; - QAction* separator1_; - QAction* createAction_; - QAction* separator2_; - QAction* cutAction_; - QAction* copyAction_; - QAction* pasteAction_; - QAction* deleteAction_; - QAction* unTrashAction_; - QAction* renameAction_; - QAction* separator3_; - QAction* propertiesAction_; - - FileLauncher* fileLauncher_; + Fm::FileInfoList files_; + std::shared_ptr info_; + Fm::FilePath cwd_; + bool useTrash_; + bool confirmDelete_; + bool confirmTrash_; // Confirm before moving files into "trash can" + + bool sameType_; + bool sameFilesystem_; + bool allVirtual_; + bool allTrash_; + + QAction* openAction_; + QAction* openWithMenuAction_; + QAction* openWithAction_; + QAction* separator1_; + QAction* createAction_; + QAction* separator2_; + QAction* cutAction_; + QAction* copyAction_; + QAction* pasteAction_; + QAction* deleteAction_; + QAction* unTrashAction_; + QAction* renameAction_; + QAction* separator3_; + QAction* propertiesAction_; + + FileLauncher* fileLauncher_; }; } diff --git a/src/filemenu_p.h b/src/filemenu_p.h index 73a6831..c257dc0 100644 --- a/src/filemenu_p.h +++ b/src/filemenu_p.h @@ -22,32 +22,34 @@ #include "icontheme.h" #include +#include "core/gioptrs.h" +#include "core/iconinfo.h" namespace Fm { class AppInfoAction : public QAction { - Q_OBJECT + Q_OBJECT public: - explicit AppInfoAction(GAppInfo* app, QObject* parent = 0): - QAction(QString::fromUtf8(g_app_info_get_name(app)), parent), - appInfo_(G_APP_INFO(g_object_ref(app))) { - setToolTip(QString::fromUtf8(g_app_info_get_description(app))); - GIcon* gicon = g_app_info_get_icon(app); - QIcon icon = IconTheme::icon(gicon); - setIcon(icon); - } - - virtual ~AppInfoAction() { - if(appInfo_) - g_object_unref(appInfo_); - } - - GAppInfo* appInfo() const { - return appInfo_; - } + explicit AppInfoAction(Fm::GAppInfoPtr app, QObject* parent = 0): + QAction(QString::fromUtf8(g_app_info_get_name(app.get())), parent), + appInfo_{std::move(app)} { + setToolTip(QString::fromUtf8(g_app_info_get_description(appInfo_.get()))); + GIcon* gicon = g_app_info_get_icon(appInfo_.get()); + const auto icnInfo = Fm::IconInfo::fromGIcon(gicon); + if(icnInfo) { + setIcon(icnInfo->qicon()); + } + } + + virtual ~AppInfoAction() { + } + + const Fm::GAppInfoPtr& appInfo() const { + return appInfo_; + } private: - GAppInfo* appInfo_; + Fm::GAppInfoPtr appInfo_; }; } // namespace Fm diff --git a/src/fileoperation.cpp b/src/fileoperation.cpp index 0d63772..af0ad7c 100644 --- a/src/fileoperation.cpp +++ b/src/fileoperation.cpp @@ -24,283 +24,294 @@ #include #include #include +#include "path.h" + +#include "core/compat_p.h" namespace Fm { #define SHOW_DLG_DELAY 1000 -FileOperation::FileOperation(Type type, FmPathList* srcFiles, QObject* parent): - QObject(parent), - job_(fm_file_ops_job_new((FmFileOpType)type, srcFiles)), - dlg(NULL), - destPath(NULL), - srcPaths(fm_path_list_ref(srcFiles)), - uiTimer(NULL), - elapsedTimer_(NULL), - lastElapsed_(0), - updateRemainingTime_(true), - autoDestroy_(true) { - - g_signal_connect(job_, "ask", G_CALLBACK(onFileOpsJobAsk), this); - g_signal_connect(job_, "ask-rename", G_CALLBACK(onFileOpsJobAskRename), this); - g_signal_connect(job_, "error", G_CALLBACK(onFileOpsJobError), this); - g_signal_connect(job_, "prepared", G_CALLBACK(onFileOpsJobPrepared), this); - g_signal_connect(job_, "cur-file", G_CALLBACK(onFileOpsJobCurFile), this); - g_signal_connect(job_, "percent", G_CALLBACK(onFileOpsJobPercent), this); - g_signal_connect(job_, "finished", G_CALLBACK(onFileOpsJobFinished), this); - g_signal_connect(job_, "cancelled", G_CALLBACK(onFileOpsJobCancelled), this); +FileOperation::FileOperation(Type type, Fm::FilePathList srcFiles, QObject* parent): + QObject(parent), + job_{fm_file_ops_job_new((FmFileOpType)type, Fm::_convertPathList(srcFiles))}, + dlg{nullptr}, + srcPaths{std::move(srcFiles)}, + uiTimer(nullptr), + elapsedTimer_(nullptr), + lastElapsed_(0), + updateRemainingTime_(true), + autoDestroy_(true) { + + g_signal_connect(job_, "ask", G_CALLBACK(onFileOpsJobAsk), this); + g_signal_connect(job_, "ask-rename", G_CALLBACK(onFileOpsJobAskRename), this); + g_signal_connect(job_, "error", G_CALLBACK(onFileOpsJobError), this); + g_signal_connect(job_, "prepared", G_CALLBACK(onFileOpsJobPrepared), this); + g_signal_connect(job_, "cur-file", G_CALLBACK(onFileOpsJobCurFile), this); + g_signal_connect(job_, "percent", G_CALLBACK(onFileOpsJobPercent), this); + g_signal_connect(job_, "finished", G_CALLBACK(onFileOpsJobFinished), this); + g_signal_connect(job_, "cancelled", G_CALLBACK(onFileOpsJobCancelled), this); } void FileOperation::disconnectJob() { - g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobAsk), this); - g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobAskRename), this); - g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobError), this); - g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobPrepared), this); - g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobCurFile), this); - g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobPercent), this); - g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobFinished), this); - g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobCancelled), this); + g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobAsk), this); + g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobAskRename), this); + g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobError), this); + g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobPrepared), this); + g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobCurFile), this); + g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobPercent), this); + g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobFinished), this); + g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobCancelled), this); } FileOperation::~FileOperation() { - if(uiTimer) { - uiTimer->stop(); - delete uiTimer; - uiTimer = NULL; - } - if(elapsedTimer_) { - delete elapsedTimer_; - elapsedTimer_ = NULL; - } - - if(job_) { - disconnectJob(); - g_object_unref(job_); - } + if(uiTimer) { + uiTimer->stop(); + delete uiTimer; + uiTimer = nullptr; + } + if(elapsedTimer_) { + delete elapsedTimer_; + elapsedTimer_ = nullptr; + } - if(srcPaths) - fm_path_list_unref(srcPaths); + if(job_) { + disconnectJob(); + g_object_unref(job_); + } +} - if(destPath) - fm_path_unref(destPath); +void FileOperation::setDestination(Fm::FilePath dest) { + destPath = std::move(dest); + auto tmp = Fm::Path::newForGfile(dest.gfile().get()); + fm_file_ops_job_set_dest(job_, tmp.dataPtr()); } bool FileOperation::run() { - delete uiTimer; - // run the job - uiTimer = new QTimer(); - uiTimer->start(SHOW_DLG_DELAY); - connect(uiTimer, &QTimer::timeout, this, &FileOperation::onUiTimeout); + delete uiTimer; + // run the job + uiTimer = new QTimer(); + uiTimer->start(SHOW_DLG_DELAY); + connect(uiTimer, &QTimer::timeout, this, &FileOperation::onUiTimeout); - return fm_job_run_async(FM_JOB(job_)); + return fm_job_run_async(FM_JOB(job_)); } void FileOperation::onUiTimeout() { - if(dlg) { - dlg->setCurFile(curFile); - // estimate remaining time based on past history - // FIXME: avoid directly access data member of FmFileOpsJob - if(Q_LIKELY(job_->percent > 0 && updateRemainingTime_)) { - gint64 remaining = elapsedTime() * ((double(100 - job_->percent) / job_->percent) / 1000); - dlg->setRemainingTime(remaining); + if(dlg) { + dlg->setCurFile(curFile); + // estimate remaining time based on past history + // FIXME: avoid directly access data member of FmFileOpsJob + if(Q_LIKELY(job_->percent > 0 && updateRemainingTime_)) { + gint64 remaining = elapsedTime() * ((double(100 - job_->percent) / job_->percent) / 1000); + dlg->setRemainingTime(remaining); + } + // this timeout slot is called every 0.5 second. + // by adding this flag, we can update remaining time every 1 second. + updateRemainingTime_ = !updateRemainingTime_; + } + else { + showDialog(); } - // this timeout slot is called every 0.5 second. - // by adding this flag, we can update remaining time every 1 second. - updateRemainingTime_ = !updateRemainingTime_; - } - else{ - showDialog(); - } } void FileOperation::showDialog() { - if(!dlg) { - dlg = new FileOperationDialog(this); - dlg->setSourceFiles(srcPaths); - - if(destPath) - dlg->setDestPath(destPath); - - if(curFile.isEmpty()) { - dlg->setPrepared(); - dlg->setCurFile(curFile); + if(!dlg) { + dlg = new FileOperationDialog(this); + dlg->setSourceFiles(srcPaths); + + if(destPath) { + dlg->setDestPath(destPath); + } + + if(curFile.isEmpty()) { + dlg->setPrepared(); + dlg->setCurFile(curFile); + } + uiTimer->setInterval(500); // change the interval of the timer + // now the timer is used to update current file display + dlg->show(); } - uiTimer->setInterval(500); // change the interval of the timer - // now the timer is used to update current file display - dlg->show(); - } } -gint FileOperation::onFileOpsJobAsk(FmFileOpsJob* job, const char* question, char*const* options, FileOperation* pThis) { - pThis->pauseElapsedTimer(); - pThis->showDialog(); - int ret = pThis->dlg->ask(QString::fromUtf8(question), options); - pThis->resumeElapsedTimer(); - return ret; +gint FileOperation::onFileOpsJobAsk(FmFileOpsJob* /*job*/, const char* question, char* const* options, FileOperation* pThis) { + pThis->pauseElapsedTimer(); + pThis->showDialog(); + int ret = pThis->dlg->ask(QString::fromUtf8(question), options); + pThis->resumeElapsedTimer(); + return ret; } -gint FileOperation::onFileOpsJobAskRename(FmFileOpsJob* job, FmFileInfo* src, FmFileInfo* dest, char** new_name, FileOperation* pThis) { - pThis->pauseElapsedTimer(); - pThis->showDialog(); - QString newName; - int ret = pThis->dlg->askRename(src, dest, newName); - if(!newName.isEmpty()) { - *new_name = g_strdup(newName.toUtf8().constData()); - } - pThis->resumeElapsedTimer(); - return ret; +gint FileOperation::onFileOpsJobAskRename(FmFileOpsJob* /*job*/, FmFileInfo* src, FmFileInfo* dest, char** new_name, FileOperation* pThis) { + pThis->pauseElapsedTimer(); + pThis->showDialog(); + QString newName; + int ret = pThis->dlg->askRename(src, dest, newName); + if(!newName.isEmpty()) { + *new_name = g_strdup(newName.toUtf8().constData()); + } + pThis->resumeElapsedTimer(); + return ret; } -void FileOperation::onFileOpsJobCancelled(FmFileOpsJob* job, FileOperation* pThis) { - qDebug("file operation is cancelled!"); +void FileOperation::onFileOpsJobCancelled(FmFileOpsJob* /*job*/, FileOperation* /*pThis*/) { + qDebug("file operation is cancelled!"); } -void FileOperation::onFileOpsJobCurFile(FmFileOpsJob* job, const char* cur_file, FileOperation* pThis) { - pThis->curFile = QString::fromUtf8(cur_file); +void FileOperation::onFileOpsJobCurFile(FmFileOpsJob* /*job*/, const char* cur_file, FileOperation* pThis) { + pThis->curFile = QString::fromUtf8(cur_file); - // We update the current file name in a timeout slot because drawing a string - // in the UI is expansive. Updating the label text too often cause - // significant impact on performance. - // if(pThis->dlg) - // pThis->dlg->setCurFile(pThis->curFile); + // We update the current file name in a timeout slot because drawing a string + // in the UI is expansive. Updating the label text too often cause + // significant impact on performance. + // if(pThis->dlg) + // pThis->dlg->setCurFile(pThis->curFile); } -FmJobErrorAction FileOperation::onFileOpsJobError(FmFileOpsJob* job, GError* err, FmJobErrorSeverity severity, FileOperation* pThis) { - pThis->pauseElapsedTimer(); - pThis->showDialog(); - FmJobErrorAction act = pThis->dlg->error(err, severity); - pThis->resumeElapsedTimer(); - return act; +FmJobErrorAction FileOperation::onFileOpsJobError(FmFileOpsJob* /*job*/, GError* err, FmJobErrorSeverity severity, FileOperation* pThis) { + pThis->pauseElapsedTimer(); + pThis->showDialog(); + FmJobErrorAction act = pThis->dlg->error(err, severity); + pThis->resumeElapsedTimer(); + return act; } -void FileOperation::onFileOpsJobFinished(FmFileOpsJob* job, FileOperation* pThis) { - pThis->handleFinish(); +void FileOperation::onFileOpsJobFinished(FmFileOpsJob* /*job*/, FileOperation* pThis) { + pThis->handleFinish(); } void FileOperation::onFileOpsJobPercent(FmFileOpsJob* job, guint percent, FileOperation* pThis) { - if(pThis->dlg) { - pThis->dlg->setPercent(percent); - } + if(pThis->dlg) { + pThis->dlg->setPercent(percent); + pThis->dlg->setDataTransferred(job->finished, job->total); + } } -void FileOperation::onFileOpsJobPrepared(FmFileOpsJob* job, FileOperation* pThis) { - if(!pThis->elapsedTimer_) { - pThis->elapsedTimer_ = new QElapsedTimer(); - pThis->elapsedTimer_->start(); - } - if(pThis->dlg) { - pThis->dlg->setPrepared(); - } +void FileOperation::onFileOpsJobPrepared(FmFileOpsJob* /*job*/, FileOperation* pThis) { + if(!pThis->elapsedTimer_) { + pThis->elapsedTimer_ = new QElapsedTimer(); + pThis->elapsedTimer_->start(); + } + if(pThis->dlg) { + pThis->dlg->setPrepared(); + } } void FileOperation::handleFinish() { - disconnectJob(); + disconnectJob(); - if(uiTimer) { - uiTimer->stop(); - delete uiTimer; - uiTimer = NULL; - } - - if(dlg) { - dlg->done(QDialog::Accepted); - delete dlg; - dlg = NULL; - } - Q_EMIT finished(); - - /* sepcial handling for trash - * FIXME: need to refactor this to use a more elegant way later. */ - if(job_->type == FM_FILE_OP_TRASH) { /* FIXME: direct access to job struct! */ - FmPathList* unable_to_trash = static_cast(g_object_get_data(G_OBJECT(job_), "trash-unsupported")); - /* some files cannot be trashed because underlying filesystems don't support it. */ - if(unable_to_trash) { /* delete them instead */ - /* FIXME: parent window might be already destroyed! */ - QWidget* parent = NULL; // FIXME: currently, parent window is not set - if(QMessageBox::question(parent, tr("Error"), - tr("Some files cannot be moved to trash can because " - "the underlying file systems don't support this operation.\n" - "Do you want to delete them instead?")) == QMessageBox::Yes) { - deleteFiles(unable_to_trash, false); - } + if(uiTimer) { + uiTimer->stop(); + delete uiTimer; + uiTimer = nullptr; } - } - g_object_unref(job_); - job_ = NULL; - if(autoDestroy_) - delete this; + if(dlg) { + dlg->done(QDialog::Accepted); + delete dlg; + dlg = nullptr; + } + Q_EMIT finished(); + + /* sepcial handling for trash + * FIXME: need to refactor this to use a more elegant way later. */ + if(job_->type == FM_FILE_OP_TRASH) { /* FIXME: direct access to job struct! */ + auto unable_to_trash = static_cast(g_object_get_data(G_OBJECT(job_), "trash-unsupported")); + /* some files cannot be trashed because underlying filesystems don't support it. */ + if(unable_to_trash) { /* delete them instead */ + Fm::FilePathList filesToDel; + for(GList* l = fm_path_list_peek_head_link(unable_to_trash); l; l = l->next) { + filesToDel.push_back(Fm::FilePath{fm_path_to_gfile(FM_PATH(l->data)), false}); + } + /* FIXME: parent window might be already destroyed! */ + QWidget* parent = nullptr; // FIXME: currently, parent window is not set + if(QMessageBox::question(parent, tr("Error"), + tr("Some files cannot be moved to trash can because " + "the underlying file systems don't support this operation.\n" + "Do you want to delete them instead?")) == QMessageBox::Yes) { + deleteFiles(std::move(filesToDel), false); + } + } + } + g_object_unref(job_); + job_ = nullptr; + + if(autoDestroy_) { + delete this; + } } // static -FileOperation* FileOperation::copyFiles(FmPathList* srcFiles, FmPath* dest, QWidget* parent) { - FileOperation* op = new FileOperation(FileOperation::Copy, srcFiles); - op->setDestination(dest); - op->run(); - return op; +FileOperation* FileOperation::copyFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent) { + FileOperation* op = new FileOperation(FileOperation::Copy, std::move(srcFiles), parent); + op->setDestination(dest); + op->run(); + return op; } // static -FileOperation* FileOperation::moveFiles(FmPathList* srcFiles, FmPath* dest, QWidget* parent) { - FileOperation* op = new FileOperation(FileOperation::Move, srcFiles); - op->setDestination(dest); - op->run(); - return op; +FileOperation* FileOperation::moveFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent) { + FileOperation* op = new FileOperation(FileOperation::Move, std::move(srcFiles), parent); + op->setDestination(dest); + op->run(); + return op; } //static -FileOperation* FileOperation::symlinkFiles(FmPathList* srcFiles, FmPath* dest, QWidget* parent) { - FileOperation* op = new FileOperation(FileOperation::Link, srcFiles); - op->setDestination(dest); - op->run(); - return op; +FileOperation* FileOperation::symlinkFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent) { + FileOperation* op = new FileOperation(FileOperation::Link, std::move(srcFiles), parent); + op->setDestination(dest); + op->run(); + return op; } //static -FileOperation* FileOperation::deleteFiles(FmPathList* srcFiles, bool prompt, QWidget* parent) { - if(prompt) { - int result = QMessageBox::warning(parent, tr("Confirm"), - tr("Do you want to delete the selected files?"), - QMessageBox::Yes|QMessageBox::No, - QMessageBox::No); - if(result != QMessageBox::Yes) - return NULL; - } - - FileOperation* op = new FileOperation(FileOperation::Delete, srcFiles); - op->run(); - return op; +FileOperation* FileOperation::deleteFiles(Fm::FilePathList srcFiles, bool prompt, QWidget* parent) { + if(prompt) { + int result = QMessageBox::warning(parent, tr("Confirm"), + tr("Do you want to delete the selected files?"), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No); + if(result != QMessageBox::Yes) { + return nullptr; + } + } + + FileOperation* op = new FileOperation(FileOperation::Delete, std::move(srcFiles)); + op->run(); + return op; } //static -FileOperation* FileOperation::trashFiles(FmPathList* srcFiles, bool prompt, QWidget* parent) { - if(prompt) { - int result = QMessageBox::warning(parent, tr("Confirm"), - tr("Do you want to move the selected files to trash can?"), - QMessageBox::Yes|QMessageBox::No, - QMessageBox::No); - if(result != QMessageBox::Yes) - return NULL; - } - - FileOperation* op = new FileOperation(FileOperation::Trash, srcFiles); - op->run(); - return op; +FileOperation* FileOperation::trashFiles(Fm::FilePathList srcFiles, bool prompt, QWidget* parent) { + if(prompt) { + int result = QMessageBox::warning(parent, tr("Confirm"), + tr("Do you want to move the selected files to trash can?"), + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No); + if(result != QMessageBox::Yes) { + return nullptr; + } + } + + FileOperation* op = new FileOperation(FileOperation::Trash, std::move(srcFiles)); + op->run(); + return op; } //static -FileOperation* FileOperation::unTrashFiles(FmPathList* srcFiles, QWidget* parent) { - FileOperation* op = new FileOperation(FileOperation::UnTrash, srcFiles); - op->run(); - return op; +FileOperation* FileOperation::unTrashFiles(Fm::FilePathList srcFiles, QWidget* parent) { + FileOperation* op = new FileOperation(FileOperation::UnTrash, std::move(srcFiles), parent); + op->run(); + return op; } // static -FileOperation* FileOperation::changeAttrFiles(FmPathList* srcFiles, QWidget* parent) { - //TODO - FileOperation* op = new FileOperation(FileOperation::ChangeAttr, srcFiles); - op->run(); - return op; +FileOperation* FileOperation::changeAttrFiles(Fm::FilePathList srcFiles, QWidget* parent) { + //TODO + FileOperation* op = new FileOperation(FileOperation::ChangeAttr, std::move(srcFiles), parent); + op->run(); + return op; } diff --git a/src/fileoperation.h b/src/fileoperation.h index 7f8e67a..33f7fea 100644 --- a/src/fileoperation.h +++ b/src/fileoperation.h @@ -25,6 +25,7 @@ #include #include #include +#include "core/filepath.h" class QTimer; @@ -33,130 +34,128 @@ namespace Fm { class FileOperationDialog; class LIBFM_QT_API FileOperation : public QObject { -Q_OBJECT + Q_OBJECT public: - enum Type { - Copy = FM_FILE_OP_COPY, - Move = FM_FILE_OP_MOVE, - Link = FM_FILE_OP_LINK, - Delete = FM_FILE_OP_DELETE, - Trash = FM_FILE_OP_TRASH, - UnTrash = FM_FILE_OP_UNTRASH, - ChangeAttr = FM_FILE_OP_CHANGE_ATTR - }; + enum Type { + Copy = FM_FILE_OP_COPY, + Move = FM_FILE_OP_MOVE, + Link = FM_FILE_OP_LINK, + Delete = FM_FILE_OP_DELETE, + Trash = FM_FILE_OP_TRASH, + UnTrash = FM_FILE_OP_UNTRASH, + ChangeAttr = FM_FILE_OP_CHANGE_ATTR + }; public: - explicit FileOperation(Type type, FmPathList* srcFiles, QObject* parent = 0); - virtual ~FileOperation(); - - void setDestination(FmPath* dest) { - destPath = fm_path_ref(dest); - fm_file_ops_job_set_dest(job_, dest); - } - - void setChmod(mode_t newMode, mode_t newModeMask) { - fm_file_ops_job_set_chmod(job_, newMode, newModeMask); - } - - void setChown(gint uid, gint gid) { - fm_file_ops_job_set_chown(job_, uid, gid); - } - - // This only work for change attr jobs. - void setRecursiveChattr(bool recursive) { - fm_file_ops_job_set_recursive(job_, (gboolean)recursive); - } - - bool run(); - - void cancel() { - if(job_) - fm_job_cancel(FM_JOB(job_)); - } - - bool isRunning() const { - return job_ ? fm_job_is_running(FM_JOB(job_)) : false; - } - - bool isCancelled() const { - return job_ ? fm_job_is_cancelled(FM_JOB(job_)) : false; - } - - FmFileOpsJob* job() { - return job_; - } - - bool autoDestroy() { - return autoDestroy_; - } - void setAutoDestroy(bool destroy = true) { - autoDestroy_ = destroy; - } - - Type type() { - return (Type)job_->type; - } - - // convinient static functions - static FileOperation* copyFiles(FmPathList* srcFiles, FmPath* dest, QWidget* parent = 0); - static FileOperation* moveFiles(FmPathList* srcFiles, FmPath* dest, QWidget* parent = 0); - static FileOperation* symlinkFiles(FmPathList* srcFiles, FmPath* dest, QWidget* parent = 0); - static FileOperation* deleteFiles(FmPathList* srcFiles, bool promp = true, QWidget* parent = 0); - static FileOperation* trashFiles(FmPathList* srcFiles, bool promp = true, QWidget* parent = 0); - static FileOperation* unTrashFiles(FmPathList* srcFiles, QWidget* parent = 0); - static FileOperation* changeAttrFiles(FmPathList* srcFiles, QWidget* parent = 0); + explicit FileOperation(Type type, Fm::FilePathList srcFiles, QObject* parent = 0); + virtual ~FileOperation(); + + void setDestination(Fm::FilePath dest); + + void setChmod(mode_t newMode, mode_t newModeMask) { + fm_file_ops_job_set_chmod(job_, newMode, newModeMask); + } + + void setChown(gint uid, gint gid) { + fm_file_ops_job_set_chown(job_, uid, gid); + } + + // This only work for change attr jobs. + void setRecursiveChattr(bool recursive) { + fm_file_ops_job_set_recursive(job_, (gboolean)recursive); + } + + bool run(); + + void cancel() { + if(job_) { + fm_job_cancel(FM_JOB(job_)); + } + } + + bool isRunning() const { + return job_ ? fm_job_is_running(FM_JOB(job_)) : false; + } + + bool isCancelled() const { + return job_ ? fm_job_is_cancelled(FM_JOB(job_)) : false; + } + + FmFileOpsJob* job() { + return job_; + } + + bool autoDestroy() { + return autoDestroy_; + } + void setAutoDestroy(bool destroy = true) { + autoDestroy_ = destroy; + } + + Type type() { + return (Type)job_->type; + } + + // convinient static functions + static FileOperation* copyFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent = 0); + static FileOperation* moveFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent = 0); + static FileOperation* symlinkFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent = 0); + static FileOperation* deleteFiles(Fm::FilePathList srcFiles, bool promp = true, QWidget* parent = 0); + static FileOperation* trashFiles(Fm::FilePathList srcFiles, bool promp = true, QWidget* parent = 0); + static FileOperation* unTrashFiles(Fm::FilePathList srcFiles, QWidget* parent = 0); + static FileOperation* changeAttrFiles(Fm::FilePathList srcFiles, QWidget* parent = 0); Q_SIGNALS: - void finished(); + void finished(); private: - static gint onFileOpsJobAsk(FmFileOpsJob* job, const char* question, char* const* options, FileOperation* pThis); - static gint onFileOpsJobAskRename(FmFileOpsJob* job, FmFileInfo* src, FmFileInfo* dest, char** new_name, FileOperation* pThis); - static FmJobErrorAction onFileOpsJobError(FmFileOpsJob* job, GError* err, FmJobErrorSeverity severity, FileOperation* pThis); - static void onFileOpsJobPrepared(FmFileOpsJob* job, FileOperation* pThis); - static void onFileOpsJobCurFile(FmFileOpsJob* job, const char* cur_file, FileOperation* pThis); - static void onFileOpsJobPercent(FmFileOpsJob* job, guint percent, FileOperation* pThis); - static void onFileOpsJobFinished(FmFileOpsJob* job, FileOperation* pThis); - static void onFileOpsJobCancelled(FmFileOpsJob* job, FileOperation* pThis); - - void handleFinish(); - void disconnectJob(); - void showDialog(); - - void pauseElapsedTimer() { - if(Q_LIKELY(elapsedTimer_ != NULL)) { - lastElapsed_ += elapsedTimer_->elapsed(); - elapsedTimer_->invalidate(); + static gint onFileOpsJobAsk(FmFileOpsJob* job, const char* question, char* const* options, FileOperation* pThis); + static gint onFileOpsJobAskRename(FmFileOpsJob* job, FmFileInfo* src, FmFileInfo* dest, char** new_name, FileOperation* pThis); + static FmJobErrorAction onFileOpsJobError(FmFileOpsJob* job, GError* err, FmJobErrorSeverity severity, FileOperation* pThis); + static void onFileOpsJobPrepared(FmFileOpsJob* job, FileOperation* pThis); + static void onFileOpsJobCurFile(FmFileOpsJob* job, const char* cur_file, FileOperation* pThis); + static void onFileOpsJobPercent(FmFileOpsJob* job, guint percent, FileOperation* pThis); + static void onFileOpsJobFinished(FmFileOpsJob* job, FileOperation* pThis); + static void onFileOpsJobCancelled(FmFileOpsJob* job, FileOperation* pThis); + + void handleFinish(); + void disconnectJob(); + void showDialog(); + + void pauseElapsedTimer() { + if(Q_LIKELY(elapsedTimer_ != nullptr)) { + lastElapsed_ += elapsedTimer_->elapsed(); + elapsedTimer_->invalidate(); + } } - } - void resumeElapsedTimer() { - if(Q_LIKELY(elapsedTimer_ != NULL)) { - elapsedTimer_->start(); + void resumeElapsedTimer() { + if(Q_LIKELY(elapsedTimer_ != nullptr)) { + elapsedTimer_->start(); + } } - } - qint64 elapsedTime() { - if(Q_LIKELY(elapsedTimer_ != NULL)) { - return lastElapsed_ + elapsedTimer_->elapsed(); + qint64 elapsedTime() { + if(Q_LIKELY(elapsedTimer_ != nullptr)) { + return lastElapsed_ + elapsedTimer_->elapsed(); + } + return 0; } - return 0; - } private Q_SLOTS: - void onUiTimeout(); + void onUiTimeout(); private: - FmFileOpsJob* job_; - FileOperationDialog* dlg; - FmPath* destPath; - FmPathList* srcPaths; - QTimer* uiTimer; - QElapsedTimer* elapsedTimer_; - qint64 lastElapsed_; - bool updateRemainingTime_; - QString curFile; - bool autoDestroy_; + FmFileOpsJob* job_; + FileOperationDialog* dlg; + Fm::FilePath destPath; + Fm::FilePathList srcPaths; + QTimer* uiTimer; + QElapsedTimer* elapsedTimer_; + qint64 lastElapsed_; + bool updateRemainingTime_; + QString curFile; + bool autoDestroy_; }; } diff --git a/src/fileoperationdialog.cpp b/src/fileoperationdialog.cpp index 9aa7111..0b26fcd 100644 --- a/src/fileoperationdialog.cpp +++ b/src/fileoperationdialog.cpp @@ -21,157 +21,173 @@ #include "fileoperationdialog.h" #include "fileoperation.h" #include "renamedialog.h" +#include #include +#include +#include "utilities.h" #include "ui_file-operation-dialog.h" namespace Fm { FileOperationDialog::FileOperationDialog(FileOperation* _operation): - QDialog(NULL), - operation(_operation), - defaultOption(-1) { - - ui = new Ui::FileOperationDialog(); - ui->setupUi(this); - - QString title; - QString message; - switch(_operation->type()) { - case FM_FILE_OP_MOVE: - title = tr("Move files"); - message = tr("Moving the following files to destination folder:"); - break; - case FM_FILE_OP_COPY: - title = tr("Copy Files"); - message = tr("Copying the following files to destination folder:"); - break; - case FM_FILE_OP_TRASH: - title = tr("Trash Files"); - message = tr("Moving the following files to trash can:"); - break; - case FM_FILE_OP_DELETE: - title = tr("Delete Files"); - message = tr("Deleting the following files:"); - ui->dest->hide(); - ui->destLabel->hide(); - break; - case FM_FILE_OP_LINK: - title = tr("Create Symlinks"); - message = tr("Creating symlinks for the following files:"); - break; - case FM_FILE_OP_CHANGE_ATTR: - title = tr("Change Attributes"); - message = tr("Changing attributes of the following files:"); - ui->dest->hide(); - ui->destLabel->hide(); - break; - case FM_FILE_OP_UNTRASH: - title = tr("Restore Trashed Files"); - message = tr("Restoring the following files from trash can:"); - ui->dest->hide(); - ui->destLabel->hide(); - break; - } - ui->message->setText(message); - setWindowTitle(title); + QDialog(nullptr), + operation(_operation), + defaultOption(-1), + ignoreNonCriticalErrors_(false) { + + ui = new Ui::FileOperationDialog(); + ui->setupUi(this); + + QString title; + QString message; + switch(_operation->type()) { + case FM_FILE_OP_MOVE: + title = tr("Move files"); + message = tr("Moving the following files to destination folder:"); + break; + case FM_FILE_OP_COPY: + title = tr("Copy Files"); + message = tr("Copying the following files to destination folder:"); + break; + case FM_FILE_OP_TRASH: + title = tr("Trash Files"); + message = tr("Moving the following files to trash can:"); + break; + case FM_FILE_OP_DELETE: + title = tr("Delete Files"); + message = tr("Deleting the following files:"); + ui->dest->hide(); + ui->destLabel->hide(); + break; + case FM_FILE_OP_LINK: + title = tr("Create Symlinks"); + message = tr("Creating symlinks for the following files:"); + break; + case FM_FILE_OP_CHANGE_ATTR: + title = tr("Change Attributes"); + message = tr("Changing attributes of the following files:"); + ui->dest->hide(); + ui->destLabel->hide(); + break; + case FM_FILE_OP_UNTRASH: + title = tr("Restore Trashed Files"); + message = tr("Restoring the following files from trash can:"); + ui->dest->hide(); + ui->destLabel->hide(); + break; + } + ui->message->setText(message); + setWindowTitle(title); } FileOperationDialog::~FileOperationDialog() { - delete ui; + delete ui; } -void FileOperationDialog::setDestPath(FmPath* dest) { - char* pathStr = fm_path_display_name(dest, false); - ui->dest->setText(QString::fromUtf8(pathStr)); - g_free(pathStr); +void FileOperationDialog::setDestPath(const Fm::FilePath &dest) { + ui->dest->setText(dest.displayName().get()); } -void FileOperationDialog::setSourceFiles(FmPathList* srcFiles) { - GList* l; - for(l = fm_path_list_peek_head_link(srcFiles); l; l = l->next) { - FmPath* path = FM_PATH(l->data); - char* pathStr = fm_path_display_name(path, false); - ui->sourceFiles->addItem(QString::fromUtf8(pathStr)); - g_free(pathStr); - } +void FileOperationDialog::setSourceFiles(const Fm::FilePathList &srcFiles) { + for(auto& srcFile : srcFiles) { + ui->sourceFiles->addItem(srcFile.displayName().get()); + } } -int FileOperationDialog::ask(QString question, char*const* options) { - // TODO: implement FileOperationDialog::ask() - return 0; +int FileOperationDialog::ask(QString /*question*/, char* const* /*options*/) { + // TODO: implement FileOperationDialog::ask() + return 0; } int FileOperationDialog::askRename(FmFileInfo* src, FmFileInfo* dest, QString& new_name) { - int ret; - if(defaultOption == -1) { // default action is not set, ask the user - RenameDialog dlg(src, dest, this); - dlg.exec(); - switch(dlg.action()) { - case RenameDialog::ActionOverwrite: - ret = FM_FILE_OP_OVERWRITE; - if(dlg.applyToAll()) - defaultOption = ret; - break; - case RenameDialog::ActionRename: - ret = FM_FILE_OP_RENAME; - new_name = dlg.newName(); - break; - case RenameDialog::ActionIgnore: - ret = FM_FILE_OP_SKIP; - if(dlg.applyToAll()) - defaultOption = ret; - break; - default: - ret = FM_FILE_OP_CANCEL; - break; + int ret; + if(defaultOption == -1) { // default action is not set, ask the user + RenameDialog dlg(src, dest, this); + dlg.exec(); + switch(dlg.action()) { + case RenameDialog::ActionOverwrite: + ret = FM_FILE_OP_OVERWRITE; + if(dlg.applyToAll()) { + defaultOption = ret; + } + break; + case RenameDialog::ActionRename: + ret = FM_FILE_OP_RENAME; + new_name = dlg.newName(); + break; + case RenameDialog::ActionIgnore: + ret = FM_FILE_OP_SKIP; + if(dlg.applyToAll()) { + defaultOption = ret; + } + break; + default: + ret = FM_FILE_OP_CANCEL; + break; + } } - } - else - ret = defaultOption; - return ret; + else { + ret = defaultOption; + } + return ret; } FmJobErrorAction FileOperationDialog::error(GError* err, FmJobErrorSeverity severity) { - if(severity >= FM_JOB_ERROR_MODERATE) { - QMessageBox::critical(this, tr("Error"), QString::fromUtf8(err->message)); - if(severity == FM_JOB_ERROR_CRITICAL) - return FM_JOB_ABORT; - } - return FM_JOB_CONTINUE; + if(severity >= FM_JOB_ERROR_MODERATE) { + if(severity == FM_JOB_ERROR_CRITICAL) { + QMessageBox::critical(this, tr("Error"), QString::fromUtf8(err->message)); + return FM_JOB_ABORT; + } + if (ignoreNonCriticalErrors_) { + return FM_JOB_CONTINUE; + } + QMessageBox::StandardButton stb = QMessageBox::critical(this, tr("Error"), QString::fromUtf8(err->message), + QMessageBox::Ok | QMessageBox::Ignore); + if (stb == QMessageBox::Ignore) { + ignoreNonCriticalErrors_ = true; + } + } + return FM_JOB_CONTINUE; } void FileOperationDialog::setCurFile(QString cur_file) { - ui->curFile->setText(cur_file); + ui->curFile->setText(cur_file); +} + +void FileOperationDialog::setDataTransferred(uint64_t finishedSize, std::uint64_t totalSize) { + ui->dataTransferred->setText(QString("%1 / %2") + .arg(formatFileSize(finishedSize, fm_config->si_unit)) + .arg(formatFileSize(totalSize, fm_config->si_unit))); } void FileOperationDialog::setPercent(unsigned int percent) { - ui->progressBar->setValue(percent); + ui->progressBar->setValue(percent); } void FileOperationDialog::setRemainingTime(unsigned int sec) { - unsigned int min = 0; - unsigned int hr = 0; - if(sec > 60) { - min = sec / 60; - sec %= 60; - if(min > 60) { - hr = min / 60; - min %= 60; + unsigned int min = 0; + unsigned int hr = 0; + if(sec > 60) { + min = sec / 60; + sec %= 60; + if(min > 60) { + hr = min / 60; + min %= 60; + } } - } - ui->timeRemaining->setText(QString("%1:%2:%3") - .arg(hr, 2, 10, QChar('0')) - .arg(min, 2, 10, QChar('0')) - .arg(sec, 2, 10, QChar('0'))); + ui->timeRemaining->setText(QString("%1:%2:%3") + .arg(hr, 2, 10, QChar('0')) + .arg(min, 2, 10, QChar('0')) + .arg(sec, 2, 10, QChar('0'))); } void FileOperationDialog::setPrepared() { } void FileOperationDialog::reject() { - operation->cancel(); - QDialog::reject(); + operation->cancel(); + QDialog::reject(); } diff --git a/src/fileoperationdialog.h b/src/fileoperationdialog.h index 664f701..8f21493 100644 --- a/src/fileoperationdialog.h +++ b/src/fileoperationdialog.h @@ -22,40 +22,45 @@ #define FM_FILEOPERATIONDIALOG_H #include "libfmqtglobals.h" +#include #include #include +#include "core/filepath.h" +#include "core/fileinfo.h" namespace Ui { - class FileOperationDialog; -}; +class FileOperationDialog; +} namespace Fm { class FileOperation; class LIBFM_QT_API FileOperationDialog : public QDialog { -Q_OBJECT + Q_OBJECT public: - explicit FileOperationDialog(FileOperation* _operation); - virtual ~FileOperationDialog(); + explicit FileOperationDialog(FileOperation* _operation); + virtual ~FileOperationDialog(); - void setSourceFiles(FmPathList* srcFiles); - void setDestPath(FmPath* dest); + void setSourceFiles(const Fm::FilePathList& srcFiles); + void setDestPath(const Fm::FilePath& dest); - int ask(QString question, char* const* options); - int askRename(FmFileInfo* src, FmFileInfo* dest, QString& new_name); - FmJobErrorAction error(GError* err, FmJobErrorSeverity severity); - void setPrepared(); - void setCurFile(QString cur_file); - void setPercent(unsigned int percent); - void setRemainingTime(unsigned int sec); + int ask(QString question, char* const* options); + int askRename(FmFileInfo* src, FmFileInfo* dest, QString& new_name); + FmJobErrorAction error(GError* err, FmJobErrorSeverity severity); + void setPrepared(); + void setCurFile(QString cur_file); + void setPercent(unsigned int percent); + void setDataTransferred(std::uint64_t finishedSize, std::uint64_t totalSize); + void setRemainingTime(unsigned int sec); - virtual void reject(); + virtual void reject(); private: - Ui::FileOperationDialog* ui; - FileOperation* operation; - int defaultOption; + Ui::FileOperationDialog* ui; + FileOperation* operation; + int defaultOption; + bool ignoreNonCriticalErrors_; }; } diff --git a/src/fileopsjob.h b/src/fileopsjob.h deleted file mode 100644 index e2bbe71..0000000 --- a/src/fileopsjob.h +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_FILE_OPS_JOB_H__ -#define __LIBFM_QT_FM_FILE_OPS_JOB_H__ - -#include -#include -#include -#include "libfmqtglobals.h" -#include "job.h" - -namespace Fm { - - -class LIBFM_QT_API FileOpsJob: public Job { -public: - - - FileOpsJob(FmFileOpType type, FmPathList* files) { - dataPtr_ = reinterpret_cast(fm_file_ops_job_new(type, files)); - } - - - // default constructor - FileOpsJob() { - dataPtr_ = nullptr; - } - - - FileOpsJob(FmFileOpsJob* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(g_object_ref(dataPtr)) : nullptr; - } - - - // copy constructor - FileOpsJob(const FileOpsJob& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - FileOpsJob(FileOpsJob&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - - // create a wrapper for the data pointer without increasing the reference count - static FileOpsJob wrapPtr(FmFileOpsJob* dataPtr) { - FileOpsJob obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmFileOpsJob* takeDataPtr() { - FmFileOpsJob* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmFileOpsJob* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmFileOpsJob*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - FileOpsJob& operator=(const FileOpsJob& other) { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - FileOpsJob& operator=(FileOpsJob&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - FmFileOpOption getOptions(void) { - return fm_file_ops_job_get_options(dataPtr()); - } - - - FmFileOpOption askRename(GFile* src, GFileInfo* src_inf, GFile* dest, GFile** new_dest) { - return fm_file_ops_job_ask_rename(dataPtr(), src, src_inf, dest, new_dest); - } - - - void emitPercent(void) { - fm_file_ops_job_emit_percent(dataPtr()); - } - - - void emitCurFile(const char* cur_file) { - fm_file_ops_job_emit_cur_file(dataPtr(), cur_file); - } - - - void emitPrepared(void) { - fm_file_ops_job_emit_prepared(dataPtr()); - } - - - void setTarget(const char* url) { - fm_file_ops_job_set_target(dataPtr(), url); - } - - - void setHidden(gboolean hidden) { - fm_file_ops_job_set_hidden(dataPtr(), hidden); - } - - - void setIcon(GIcon* icon) { - fm_file_ops_job_set_icon(dataPtr(), icon); - } - - - void setDisplayName(const char* name) { - fm_file_ops_job_set_display_name(dataPtr(), name); - } - - - void setChown(gint uid, gint gid) { - fm_file_ops_job_set_chown(dataPtr(), uid, gid); - } - - - void setChmod(mode_t new_mode, mode_t new_mode_mask) { - fm_file_ops_job_set_chmod(dataPtr(), new_mode, new_mode_mask); - } - - - void setRecursive(gboolean recursive) { - fm_file_ops_job_set_recursive(dataPtr(), recursive); - } - - - FmPath* getDest(void) { - return fm_file_ops_job_get_dest(dataPtr()); - } - - - void setDest(FmPath* dest) { - fm_file_ops_job_set_dest(dataPtr(), dest); - } - - - -}; - - -} - -#endif // __LIBFM_QT_FM_FILE_OPS_JOB_H__ diff --git a/src/filepropsdialog.cpp b/src/filepropsdialog.cpp index f14e1c4..858aadf 100644 --- a/src/filepropsdialog.cpp +++ b/src/filepropsdialog.cpp @@ -26,9 +26,13 @@ #include #include #include -#include +#include +#include +#include #include #include +#include "core/totalsizejob.h" +#include "core/folder.h" #define DIFFERENT_UIDS ((uid)-1) #define DIFFERENT_GIDS ((gid)-1) @@ -37,439 +41,519 @@ namespace Fm { enum { - ACCESS_NO_CHANGE = 0, - ACCESS_READ_ONLY, - ACCESS_READ_WRITE, - ACCESS_FORBID + ACCESS_NO_CHANGE = 0, + ACCESS_READ_ONLY, + ACCESS_READ_WRITE, + ACCESS_FORBID }; -FilePropsDialog::FilePropsDialog(FmFileInfoList* files, QWidget* parent, Qt::WindowFlags f): - QDialog(parent, f), - fileInfos_(fm_file_info_list_ref(files)), - fileInfo(fm_file_info_list_peek_head(files)), - singleType(fm_file_info_list_is_same_type(files)), - singleFile(fm_file_info_list_get_length(files) == 1 ? true:false), - mimeType(NULL) { +FilePropsDialog::FilePropsDialog(Fm::FileInfoList files, QWidget* parent, Qt::WindowFlags f): + QDialog(parent, f), + fileInfos_{std::move(files)}, + fileInfo{fileInfos_.front()}, + singleType(fileInfos_.isSameType()), + singleFile(fileInfos_.size() == 1 ? true : false) { - setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_DeleteOnClose); - ui = new Ui::FilePropsDialog(); - ui->setupUi(this); + ui = new Ui::FilePropsDialog(); + ui->setupUi(this); - if(singleType) { - mimeType = fm_mime_type_ref(fm_file_info_get_mime_type(fileInfo)); - } + if(singleType) { + mimeType = fileInfo->mimeType(); + } - FmPathList* paths = fm_path_list_new_from_file_info_list(files); - deepCountJob = fm_deep_count_job_new(paths, FM_DC_JOB_DEFAULT); - fm_path_list_unref(paths); + totalSizeJob = new Fm::TotalSizeJob(fileInfos_.paths(), Fm::TotalSizeJob::DEFAULT); - initGeneralPage(); - initPermissionsPage(); + initGeneralPage(); + initPermissionsPage(); } FilePropsDialog::~FilePropsDialog() { - if(fileInfos_) - fm_file_info_list_unref(fileInfos_); - - // Stop the timer if it's still running - if(fileSizeTimer) { - fileSizeTimer->stop(); - delete fileSizeTimer; - fileSizeTimer = NULL; - } - - // Cancel the indexing job if it hasn't finished - if(deepCountJob) { - g_signal_handlers_disconnect_by_func(deepCountJob, (gpointer)G_CALLBACK(onDeepCountJobFinished), this); - fm_job_cancel(FM_JOB(deepCountJob)); - g_object_unref(deepCountJob); - deepCountJob = NULL; - } - - // And finally delete the dialog's UI - delete ui; + // Stop the timer if it's still running + if(fileSizeTimer) { + fileSizeTimer->stop(); + delete fileSizeTimer; + fileSizeTimer = nullptr; + } + + // Cancel the indexing job if it hasn't finished + if(totalSizeJob) { + totalSizeJob->cancel(); + totalSizeJob = nullptr; + } + + // And finally delete the dialog's UI + delete ui; } void FilePropsDialog::initApplications() { - if(singleType && mimeType && !fm_file_info_is_dir(fileInfo)) { - ui->openWith->setMimeType(mimeType); - } - else { - ui->openWith->hide(); - ui->openWithLabel->hide(); - } + if(singleType && mimeType && !fileInfo->isDir()) { + ui->openWith->setMimeType(mimeType); + } + else { + ui->openWith->hide(); + ui->openWithLabel->hide(); + } } void FilePropsDialog::initPermissionsPage() { - // ownership handling - // get owner/group and mode of the first file in the list - uid = fm_file_info_get_uid(fileInfo); - gid = fm_file_info_get_gid(fileInfo); - mode_t mode = fm_file_info_get_mode(fileInfo); - ownerPerm = (mode & (S_IRUSR|S_IWUSR|S_IXUSR)); - groupPerm = (mode & (S_IRGRP|S_IWGRP|S_IXGRP)); - otherPerm = (mode & (S_IROTH|S_IWOTH|S_IXOTH)); - execPerm = (mode & (S_IXUSR|S_IXGRP|S_IXOTH)); - allNative = fm_file_info_is_native(fileInfo); - hasDir = S_ISDIR(mode); - - // check if all selected files belongs to the same owner/group or have the same mode - // at the same time, check if all files are on native unix filesystems - GList* l; - for(l = fm_file_info_list_peek_head_link(fileInfos_)->next; l; l = l->next) { - FmFileInfo* fi = FM_FILE_INFO(l->data); - if(allNative && !fm_file_info_is_native(fi)) - allNative = false; // not all of the files are native - - mode_t fi_mode = fm_file_info_get_mode(fi); - if(S_ISDIR(fi_mode)) - hasDir = true; // the files list contains dir(s) - - if(uid != DIFFERENT_UIDS && uid != fm_file_info_get_uid(fi)) - uid = DIFFERENT_UIDS; // not all files have the same owner - if(gid != DIFFERENT_GIDS && gid != fm_file_info_get_gid(fi)) - gid = DIFFERENT_GIDS; // not all files have the same owner group - - if(ownerPerm != DIFFERENT_PERMS && ownerPerm != (fi_mode & (S_IRUSR|S_IWUSR|S_IXUSR))) - ownerPerm = DIFFERENT_PERMS; // not all files have the same permission for owner - if(groupPerm != DIFFERENT_PERMS && groupPerm != (fi_mode & (S_IRGRP|S_IWGRP|S_IXGRP))) - groupPerm = DIFFERENT_PERMS; // not all files have the same permission for grop - if(otherPerm != DIFFERENT_PERMS && otherPerm != (fi_mode & (S_IROTH|S_IWOTH|S_IXOTH))) - otherPerm = DIFFERENT_PERMS; // not all files have the same permission for other - if(execPerm != DIFFERENT_PERMS && execPerm != (fi_mode & (S_IXUSR|S_IXGRP|S_IXOTH))) - execPerm = DIFFERENT_PERMS; // not all files have the same executable permission - } - - // init owner/group - initOwner(); - - // if all files are of the same type, and some of them are dirs => all of the items are dirs - // rwx values have different meanings for dirs - // Let's make it clear for the users - // init combo boxes for file permissions here - QStringList comboItems; - comboItems.append("---"); // no change - if(singleType && hasDir) { // all files are dirs - comboItems.append(tr("View folder content")); - comboItems.append(tr("View and modify folder content")); - ui->executable->hide(); - } - else { //not all of the files are dirs - comboItems.append(tr("Read")); - comboItems.append(tr("Read and write")); - } - comboItems.append(tr("Forbidden")); - QStringListModel* comboModel = new QStringListModel(comboItems, this); - ui->ownerPerm->setModel(comboModel); - ui->groupPerm->setModel(comboModel); - ui->otherPerm->setModel(comboModel); - - // owner - ownerPermSel = ACCESS_NO_CHANGE; - if(ownerPerm != DIFFERENT_PERMS) { // permissions for owner are the same among all files - if(ownerPerm & S_IRUSR) { // can read - if(ownerPerm & S_IWUSR) // can write - ownerPermSel = ACCESS_READ_WRITE; - else - ownerPermSel = ACCESS_READ_ONLY; + // ownership handling + // get owner/group and mode of the first file in the list + uid = fileInfo->uid(); + gid = fileInfo->gid(); + mode_t mode = fileInfo->mode(); + ownerPerm = (mode & (S_IRUSR | S_IWUSR | S_IXUSR)); + groupPerm = (mode & (S_IRGRP | S_IWGRP | S_IXGRP)); + otherPerm = (mode & (S_IROTH | S_IWOTH | S_IXOTH)); + execPerm = (mode & (S_IXUSR | S_IXGRP | S_IXOTH)); + allNative = fileInfo->isNative(); + hasDir = S_ISDIR(mode); + + // check if all selected files belongs to the same owner/group or have the same mode + // at the same time, check if all files are on native unix filesystems + for(auto& fi: fileInfos_) { + if(allNative && !fi->isNative()) { + allNative = false; // not all of the files are native + } + + mode_t fi_mode = fi->mode(); + if(S_ISDIR(fi_mode)) { + hasDir = true; // the files list contains dir(s) + } + + if(uid != DIFFERENT_UIDS && static_cast(uid) != fi->uid()) { + uid = DIFFERENT_UIDS; // not all files have the same owner + } + if(gid != DIFFERENT_GIDS && static_cast(gid) != fi->gid()) { + gid = DIFFERENT_GIDS; // not all files have the same owner group + } + + if(ownerPerm != DIFFERENT_PERMS && ownerPerm != (fi_mode & (S_IRUSR | S_IWUSR | S_IXUSR))) { + ownerPerm = DIFFERENT_PERMS; // not all files have the same permission for owner + } + if(groupPerm != DIFFERENT_PERMS && groupPerm != (fi_mode & (S_IRGRP | S_IWGRP | S_IXGRP))) { + groupPerm = DIFFERENT_PERMS; // not all files have the same permission for grop + } + if(otherPerm != DIFFERENT_PERMS && otherPerm != (fi_mode & (S_IROTH | S_IWOTH | S_IXOTH))) { + otherPerm = DIFFERENT_PERMS; // not all files have the same permission for other + } + if(execPerm != DIFFERENT_PERMS && execPerm != (fi_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { + execPerm = DIFFERENT_PERMS; // not all files have the same executable permission + } } - else { - if((ownerPerm & S_IWUSR) == 0) // cannot read or write - ownerPermSel = ACCESS_FORBID; - } - } - ui->ownerPerm->setCurrentIndex(ownerPermSel); - - // owner and group - groupPermSel = ACCESS_NO_CHANGE; - if(groupPerm != DIFFERENT_PERMS) { // permissions for owner are the same among all files - if(groupPerm & S_IRGRP) { // can read - if(groupPerm & S_IWGRP) // can write - groupPermSel = ACCESS_READ_WRITE; - else - groupPermSel = ACCESS_READ_ONLY; + + // init owner/group + initOwner(); + + // if all files are of the same type, and some of them are dirs => all of the items are dirs + // rwx values have different meanings for dirs + // Let's make it clear for the users + // init combo boxes for file permissions here + QStringList comboItems; + comboItems.append("---"); // no change + if(singleType && hasDir) { // all files are dirs + comboItems.append(tr("View folder content")); + comboItems.append(tr("View and modify folder content")); + ui->executable->hide(); } - else { - if((groupPerm & S_IWGRP) == 0) // cannot read or write - groupPermSel = ACCESS_FORBID; + else { //not all of the files are dirs + comboItems.append(tr("Read")); + comboItems.append(tr("Read and write")); } - } - ui->groupPerm->setCurrentIndex(groupPermSel); - - // other - otherPermSel = ACCESS_NO_CHANGE; - if(otherPerm != DIFFERENT_PERMS) { // permissions for owner are the same among all files - if(otherPerm & S_IROTH) { // can read - if(otherPerm & S_IWOTH) // can write - otherPermSel = ACCESS_READ_WRITE; - else - otherPermSel = ACCESS_READ_ONLY; + comboItems.append(tr("Forbidden")); + QStringListModel* comboModel = new QStringListModel(comboItems, this); + ui->ownerPerm->setModel(comboModel); + ui->groupPerm->setModel(comboModel); + ui->otherPerm->setModel(comboModel); + + // owner + ownerPermSel = ACCESS_NO_CHANGE; + if(ownerPerm != DIFFERENT_PERMS) { // permissions for owner are the same among all files + if(ownerPerm & S_IRUSR) { // can read + if(ownerPerm & S_IWUSR) { // can write + ownerPermSel = ACCESS_READ_WRITE; + } + else { + ownerPermSel = ACCESS_READ_ONLY; + } + } + else { + if((ownerPerm & S_IWUSR) == 0) { // cannot read or write + ownerPermSel = ACCESS_FORBID; + } + } } - else { - if((otherPerm & S_IWOTH) == 0) // cannot read or write - otherPermSel = ACCESS_FORBID; + ui->ownerPerm->setCurrentIndex(ownerPermSel); + + // owner and group + groupPermSel = ACCESS_NO_CHANGE; + if(groupPerm != DIFFERENT_PERMS) { // permissions for owner are the same among all files + if(groupPerm & S_IRGRP) { // can read + if(groupPerm & S_IWGRP) { // can write + groupPermSel = ACCESS_READ_WRITE; + } + else { + groupPermSel = ACCESS_READ_ONLY; + } + } + else { + if((groupPerm & S_IWGRP) == 0) { // cannot read or write + groupPermSel = ACCESS_FORBID; + } + } } + ui->groupPerm->setCurrentIndex(groupPermSel); + + // other + otherPermSel = ACCESS_NO_CHANGE; + if(otherPerm != DIFFERENT_PERMS) { // permissions for owner are the same among all files + if(otherPerm & S_IROTH) { // can read + if(otherPerm & S_IWOTH) { // can write + otherPermSel = ACCESS_READ_WRITE; + } + else { + otherPermSel = ACCESS_READ_ONLY; + } + } + else { + if((otherPerm & S_IWOTH) == 0) { // cannot read or write + otherPermSel = ACCESS_FORBID; + } + } - } - ui->otherPerm->setCurrentIndex(otherPermSel); - - // set the checkbox to partially checked state - // when owner, group, and other have different executable flags set. - // some of them have exec, and others do not have. - execCheckState = Qt::PartiallyChecked; - if(execPerm != DIFFERENT_PERMS) { // if all files have the same executable permission - // check if the files are all executable - if((mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == (S_IXUSR|S_IXGRP|S_IXOTH)) { - // owner, group, and other all have exec permission. - ui->executable->setTristate(false); - execCheckState = Qt::Checked; } - else if((mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0) { - // owner, group, and other all have no exec permission - ui->executable->setTristate(false); - execCheckState = Qt::Unchecked; + ui->otherPerm->setCurrentIndex(otherPermSel); + + // set the checkbox to partially checked state + // when owner, group, and other have different executable flags set. + // some of them have exec, and others do not have. + execCheckState = Qt::PartiallyChecked; + if(execPerm != DIFFERENT_PERMS) { // if all files have the same executable permission + // check if the files are all executable + if((mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == (S_IXUSR | S_IXGRP | S_IXOTH)) { + // owner, group, and other all have exec permission. + ui->executable->setTristate(false); + execCheckState = Qt::Checked; + } + else if((mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) { + // owner, group, and other all have no exec permission + ui->executable->setTristate(false); + execCheckState = Qt::Unchecked; + } } - } - ui->executable->setCheckState(execCheckState); + ui->executable->setCheckState(execCheckState); } void FilePropsDialog::initGeneralPage() { - // update UI - if(singleType) { // all files are of the same mime-type - FmIcon* icon = NULL; - // FIXME: handle custom icons for some files - // FIXME: display special property pages for special files or - // some specified mime-types. - if(singleFile) { // only one file is selected. - icon = fm_file_info_get_icon(fileInfo); - } - if(mimeType) { - if(!icon) // get an icon from mime type if needed - icon = fm_mime_type_get_icon(mimeType); - ui->fileType->setText(QString::fromUtf8(fm_mime_type_get_desc(mimeType))); - ui->mimeType->setText(QString::fromUtf8(fm_mime_type_get_type(mimeType))); - } - if(icon) { - ui->iconButton->setIcon(IconTheme::icon(icon)); + // update UI + if(singleType) { // all files are of the same mime-type + std::shared_ptr icon; + // FIXME: handle custom icons for some files + // FIXME: display special property pages for special files or + // some specified mime-types. + if(singleFile) { // only one file is selected. + icon = fileInfo->icon(); + } + if(mimeType) { + if(!icon) { // get an icon from mime type if needed + icon = mimeType->icon(); + } + ui->fileType->setText(mimeType->desc()); + ui->mimeType->setText(mimeType->name()); + } + if(icon) { + ui->iconButton->setIcon(icon->qicon()); + } + + if(singleFile && fileInfo->isSymlink()) { + ui->target->setText(QString::fromStdString(fileInfo->target())); + } + else { + ui->target->hide(); + ui->targetLabel->hide(); + } + if(fileInfo->isDir() && fileInfo->isNative()) { // all files are native dirs + connect(ui->iconButton, &QAbstractButton::clicked, this, &FilePropsDialog::onIconButtonclicked); + } + } // end if(singleType) + else { // not singleType, multiple files are selected at the same time + ui->fileType->setText(tr("Files of different types")); + ui->target->hide(); + ui->targetLabel->hide(); } - if(singleFile && fm_file_info_is_symlink(fileInfo)) { - ui->target->setText(QString::fromUtf8(fm_file_info_get_target(fileInfo))); + // FIXME: check if all files has the same parent dir, mtime, or atime + if(singleFile) { // only one file is selected + auto parent_path = fileInfo->path().parent(); + auto parent_str = parent_path ? parent_path.displayName(): nullptr; + + ui->fileName->setText(fileInfo->displayName()); + if(parent_str) { + ui->location->setText(parent_str.get()); + } + else { + ui->location->clear(); + } + auto mtime = QDateTime::fromMSecsSinceEpoch(fileInfo->mtime() * 1000); + ui->lastModified->setText(mtime.toString(Qt::SystemLocaleShortDate)); + auto atime = QDateTime::fromMSecsSinceEpoch(fileInfo->atime() * 1000); + ui->lastAccessed->setText(atime.toString(Qt::SystemLocaleShortDate)); } else { - ui->target->hide(); - ui->targetLabel->hide(); - } - } // end if(singleType) - else { // not singleType, multiple files are selected at the same time - ui->fileType->setText(tr("Files of different types")); - ui->target->hide(); - ui->targetLabel->hide(); - } - - // FIXME: check if all files has the same parent dir, mtime, or atime - if(singleFile) { // only one file is selected - FmPath* parent_path = fm_path_get_parent(fm_file_info_get_path(fileInfo)); - char* parent_str = parent_path ? fm_path_display_name(parent_path, true) : NULL; - - ui->fileName->setText(QString::fromUtf8(fm_file_info_get_disp_name(fileInfo))); - if(parent_str) { - ui->location->setText(QString::fromUtf8(parent_str)); - g_free(parent_str); + ui->fileName->setText(tr("Multiple Files")); + ui->fileName->setEnabled(false); } - else - ui->location->clear(); - - ui->lastModified->setText(QString::fromUtf8(fm_file_info_get_disp_mtime(fileInfo))); - - // FIXME: need to encapsulate this in an libfm API. - time_t atime; - struct tm tm; - atime = fm_file_info_get_atime(fileInfo); - localtime_r(&atime, &tm); - char buf[128]; - strftime(buf, sizeof(buf), "%x %R", &tm); - ui->lastAccessed->setText(QString::fromUtf8(buf)); - } - else { - ui->fileName->setText(tr("Multiple Files")); - ui->fileName->setEnabled(false); - } - - initApplications(); // init applications combo box - - // calculate total file sizes - fileSizeTimer = new QTimer(this); - connect(fileSizeTimer, &QTimer::timeout, this, &FilePropsDialog::onFileSizeTimerTimeout); - fileSizeTimer->start(600); - g_signal_connect(deepCountJob, "finished", G_CALLBACK(onDeepCountJobFinished), this); - fm_job_run_async(FM_JOB(deepCountJob)); -} -/*static */ void FilePropsDialog::onDeepCountJobFinished(FmDeepCountJob* job, FilePropsDialog* pThis) { + initApplications(); // init applications combo box + + // calculate total file sizes + fileSizeTimer = new QTimer(this); + connect(fileSizeTimer, &QTimer::timeout, this, &FilePropsDialog::onFileSizeTimerTimeout); + fileSizeTimer->start(600); + + connect(totalSizeJob, &Fm::TotalSizeJob::finished, this, &FilePropsDialog::onDeepCountJobFinished, Qt::BlockingQueuedConnection); + totalSizeJob->setAutoDelete(true); + totalSizeJob->runAsync(); +} - pThis->onFileSizeTimerTimeout(); // update file size display +void FilePropsDialog::onDeepCountJobFinished() { + onFileSizeTimerTimeout(); // update file size display - // free the job - g_object_unref(pThis->deepCountJob); - pThis->deepCountJob = NULL; + totalSizeJob = nullptr; - // stop the timer - if(pThis->fileSizeTimer) { - pThis->fileSizeTimer->stop(); - delete pThis->fileSizeTimer; - pThis->fileSizeTimer = NULL; - } + // stop the timer + if(fileSizeTimer) { + fileSizeTimer->stop(); + delete fileSizeTimer; + fileSizeTimer = nullptr; + } } void FilePropsDialog::onFileSizeTimerTimeout() { - if(deepCountJob && !fm_job_is_cancelled(FM_JOB(deepCountJob))) { - char size_str[128]; - fm_file_size_to_str(size_str, sizeof(size_str), deepCountJob->total_size, - fm_config->si_unit); - // FIXME: - // OMG! It's really unbelievable that Qt developers only implement - // QObject::tr(... int n). GNU gettext developers are smarter and - // they use unsigned long instead of int. - // We cannot use Qt here to handle plural forms. So sad. :-( - QString str = QString::fromUtf8(size_str) % - QString(" (%1 B)").arg(deepCountJob->total_size); - // tr(" (%n) byte(s)", "", deepCountJob->total_size); - ui->fileSize->setText(str); - - fm_file_size_to_str(size_str, sizeof(size_str), deepCountJob->total_ondisk_size, - fm_config->si_unit); - str = QString::fromUtf8(size_str) % - QString(" (%1 B)").arg(deepCountJob->total_ondisk_size); - // tr(" (%n) byte(s)", "", deepCountJob->total_ondisk_size); - ui->onDiskSize->setText(str); - } + if(totalSizeJob && !totalSizeJob->isCancelled()) { + // FIXME: + // OMG! It's really unbelievable that Qt developers only implement + // QObject::tr(... int n). GNU gettext developers are smarter and + // they use unsigned long instead of int. + // We cannot use Qt here to handle plural forms. So sad. :-( + QString str = Fm::formatFileSize(totalSizeJob->totalSize(), fm_config->si_unit) % + QString(" (%1 B)").arg(totalSizeJob->totalSize()); + // tr(" (%n) byte(s)", "", deepCountJob->total_size); + ui->fileSize->setText(str); + + str = Fm::formatFileSize(totalSizeJob->totalOnDiskSize(), fm_config->si_unit) % + QString(" (%1 B)").arg(totalSizeJob->totalOnDiskSize()); + // tr(" (%n) byte(s)", "", deepCountJob->total_ondisk_size); + ui->onDiskSize->setText(str); + } +} + +void FilePropsDialog::onIconButtonclicked() { + QString iconDir; + QString iconThemeName = QIcon::themeName(); + QStringList icons = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, + "icons", + QStandardPaths::LocateDirectory); + for (QStringList::ConstIterator it = icons.constBegin(); it != icons.constEnd(); ++it) { + QString iconThemeFolder = *it + '/' + iconThemeName; + if (QDir(iconThemeFolder).exists() && QFileInfo(iconThemeFolder).permission(QFileDevice::ReadUser)) { + // give priority to the "places" folder + const QString places = iconThemeFolder + QLatin1String("/places"); + if (QDir(places).exists() && QFileInfo(places).permission(QFileDevice::ReadUser)) { + iconDir = places; + } + else { + iconDir = iconThemeFolder; + } + break; + } + } + if(iconDir.isEmpty()) { + iconDir = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + "icons", + QStandardPaths::LocateDirectory); + if(iconDir.isEmpty()) { + return; + } + } + const QString iconPath = QFileDialog::getOpenFileName(this, tr("Select an icon"), + iconDir, + tr("Images (*.png *.xpm *.svg *.svgz )")); + if(!iconPath.isEmpty()) { + QStringList parts = iconPath.split("/", QString::SkipEmptyParts); + if(!parts.isEmpty()) { + QString iconName = parts.at(parts.count() - 1); + int ln = iconName.lastIndexOf("."); + if(ln > -1) { + iconName.remove(ln, iconName.length() - ln); + customIcon = QIcon::fromTheme(iconName); + ui->iconButton->setIcon(customIcon); + } + } + } } void FilePropsDialog::accept() { - // applications - if(mimeType && ui->openWith->isChanged()) { - GAppInfo* currentApp = ui->openWith->selectedApp(); - g_app_info_set_as_default_for_type(currentApp, fm_mime_type_get_type(mimeType), NULL); - } - - // check if chown or chmod is needed - guint32 newUid = uidFromName(ui->owner->text()); - guint32 newGid = gidFromName(ui->ownerGroup->text()); - bool needChown = (newUid != -1 && newUid != uid) || (newGid != -1 && newGid != gid); - - int newOwnerPermSel = ui->ownerPerm->currentIndex(); - int newGroupPermSel = ui->groupPerm->currentIndex(); - int newOtherPermSel = ui->otherPerm->currentIndex(); - Qt::CheckState newExecCheckState = ui->executable->checkState(); - bool needChmod = ((newOwnerPermSel != ownerPermSel) || - (newGroupPermSel != groupPermSel) || - (newOtherPermSel != otherPermSel) || - (newExecCheckState != execCheckState)); - - if(needChmod || needChown) { - FmPathList* paths = fm_path_list_new_from_file_info_list(fileInfos_); - FileOperation* op = new FileOperation(FileOperation::ChangeAttr, paths); - fm_path_list_unref(paths); - if(needChown) { - // don't do chown if new uid/gid and the original ones are actually the same. - if(newUid == uid) - newUid = -1; - if(newGid == gid) - newGid = -1; - op->setChown(newUid, newGid); + // applications + if(mimeType && ui->openWith->isChanged()) { + auto currentApp = ui->openWith->selectedApp(); + g_app_info_set_as_default_for_type(currentApp.get(), mimeType->name(), nullptr); } - if(needChmod) { - mode_t newMode = 0; - mode_t newModeMask = 0; - // FIXME: we need to make sure that folders with "r" permission also have "x" - // at the same time. Otherwise, it's not able to browse the folder later. - if(newOwnerPermSel != ownerPermSel && newOwnerPermSel != ACCESS_NO_CHANGE) { - // owner permission changed - newModeMask |= (S_IRUSR|S_IWUSR); // affect user bits - if(newOwnerPermSel == ACCESS_READ_ONLY) - newMode |= S_IRUSR; - else if(newOwnerPermSel == ACCESS_READ_WRITE) - newMode |= (S_IRUSR|S_IWUSR); - } - if(newGroupPermSel != groupPermSel && newGroupPermSel != ACCESS_NO_CHANGE) { - qDebug("newGroupPermSel: %d", newGroupPermSel); - // group permission changed - newModeMask |= (S_IRGRP|S_IWGRP); // affect group bits - if(newGroupPermSel == ACCESS_READ_ONLY) - newMode |= S_IRGRP; - else if(newGroupPermSel == ACCESS_READ_WRITE) - newMode |= (S_IRGRP|S_IWGRP); - } - if(newOtherPermSel != otherPermSel && newOtherPermSel != ACCESS_NO_CHANGE) { - // other permission changed - newModeMask |= (S_IROTH|S_IWOTH); // affect other bits - if(newOtherPermSel == ACCESS_READ_ONLY) - newMode |= S_IROTH; - else if(newOtherPermSel == ACCESS_READ_WRITE) - newMode |= (S_IROTH|S_IWOTH); - } - if(newExecCheckState != execCheckState && newExecCheckState != Qt::PartiallyChecked) { - // executable state changed - newModeMask |= (S_IXUSR|S_IXGRP|S_IXOTH); - if(newExecCheckState == Qt::Checked) - newMode |= (S_IXUSR|S_IXGRP|S_IXOTH); - } - op->setChmod(newMode, newModeMask); - - if(hasDir) { // if there are some dirs in our selected files - QMessageBox::StandardButton r = QMessageBox::question(this, - tr("Apply changes"), - tr("Do you want to recursively apply these changes to all files and sub-folders?"), - QMessageBox::Yes|QMessageBox::No); - if(r == QMessageBox::Yes) - op->setRecursiveChattr(true); - } + + // check if chown or chmod is needed + gint32 newUid = uidFromName(ui->owner->text()); + gint32 newGid = gidFromName(ui->ownerGroup->text()); + bool needChown = (newUid != -1 && newUid != uid) || (newGid != -1 && newGid != gid); + + int newOwnerPermSel = ui->ownerPerm->currentIndex(); + int newGroupPermSel = ui->groupPerm->currentIndex(); + int newOtherPermSel = ui->otherPerm->currentIndex(); + Qt::CheckState newExecCheckState = ui->executable->checkState(); + bool needChmod = ((newOwnerPermSel != ownerPermSel) || + (newGroupPermSel != groupPermSel) || + (newOtherPermSel != otherPermSel) || + (newExecCheckState != execCheckState)); + + if(needChmod || needChown) { + FileOperation* op = new FileOperation(FileOperation::ChangeAttr, fileInfos_.paths()); + if(needChown) { + // don't do chown if new uid/gid and the original ones are actually the same. + if(newUid == uid) { + newUid = -1; + } + if(newGid == gid) { + newGid = -1; + } + op->setChown(newUid, newGid); + } + if(needChmod) { + mode_t newMode = 0; + mode_t newModeMask = 0; + // FIXME: we need to make sure that folders with "r" permission also have "x" + // at the same time. Otherwise, it's not able to browse the folder later. + if(newOwnerPermSel != ownerPermSel && newOwnerPermSel != ACCESS_NO_CHANGE) { + // owner permission changed + newModeMask |= (S_IRUSR | S_IWUSR); // affect user bits + if(newOwnerPermSel == ACCESS_READ_ONLY) { + newMode |= S_IRUSR; + } + else if(newOwnerPermSel == ACCESS_READ_WRITE) { + newMode |= (S_IRUSR | S_IWUSR); + } + } + if(newGroupPermSel != groupPermSel && newGroupPermSel != ACCESS_NO_CHANGE) { + qDebug("newGroupPermSel: %d", newGroupPermSel); + // group permission changed + newModeMask |= (S_IRGRP | S_IWGRP); // affect group bits + if(newGroupPermSel == ACCESS_READ_ONLY) { + newMode |= S_IRGRP; + } + else if(newGroupPermSel == ACCESS_READ_WRITE) { + newMode |= (S_IRGRP | S_IWGRP); + } + } + if(newOtherPermSel != otherPermSel && newOtherPermSel != ACCESS_NO_CHANGE) { + // other permission changed + newModeMask |= (S_IROTH | S_IWOTH); // affect other bits + if(newOtherPermSel == ACCESS_READ_ONLY) { + newMode |= S_IROTH; + } + else if(newOtherPermSel == ACCESS_READ_WRITE) { + newMode |= (S_IROTH | S_IWOTH); + } + } + if(newExecCheckState != execCheckState && newExecCheckState != Qt::PartiallyChecked) { + // executable state changed + newModeMask |= (S_IXUSR | S_IXGRP | S_IXOTH); + if(newExecCheckState == Qt::Checked) { + newMode |= (S_IXUSR | S_IXGRP | S_IXOTH); + } + } + op->setChmod(newMode, newModeMask); + + if(hasDir) { // if there are some dirs in our selected files + QMessageBox::StandardButton r = QMessageBox::question(this, + tr("Apply changes"), + tr("Do you want to recursively apply these changes to all files and sub-folders?"), + QMessageBox::Yes | QMessageBox::No); + if(r == QMessageBox::Yes) { + op->setRecursiveChattr(true); + } + } + } + op->setAutoDestroy(true); + op->run(); } - op->setAutoDestroy(true); - op->run(); - } - - // Renaming - if(singleFile) { - QString new_name = ui->fileName->text(); - if(QString::fromUtf8(fm_file_info_get_disp_name(fileInfo)) != new_name) { - FmPath* path = fm_file_info_get_path(fileInfo); - GFile* gf = fm_path_to_gfile(path); - GFile* parent_gf = g_file_get_parent(gf); - GFile* dest = g_file_get_child(G_FILE(parent_gf), new_name.toLocal8Bit().constData()); - g_object_unref(parent_gf); - GError* err = NULL; - if(!g_file_move(gf, dest, - GFileCopyFlags(G_FILE_COPY_ALL_METADATA | - G_FILE_COPY_NO_FALLBACK_FOR_MOVE | - G_FILE_COPY_NOFOLLOW_SYMLINKS), - NULL, NULL, NULL, &err)) { - QMessageBox::critical(this, QObject::tr("Error"), err->message); - g_error_free(err); - } - g_object_unref(dest); - g_object_unref(gf); + + // Renaming + if(singleFile) { + QString new_name = ui->fileName->text(); + if(fileInfo->displayName() != new_name) { + auto path = fileInfo->path(); + auto parent_path = path.parent(); + auto dest = parent_path.child(new_name.toLocal8Bit().constData()); + Fm::GErrorPtr err; + if(!g_file_move(path.gfile().get(), dest.gfile().get(), + GFileCopyFlags(G_FILE_COPY_ALL_METADATA | + G_FILE_COPY_NO_FALLBACK_FOR_MOVE | + G_FILE_COPY_NOFOLLOW_SYMLINKS), + nullptr, nullptr, nullptr, &err)) { + QMessageBox::critical(this, QObject::tr("Error"), err.message()); + } + } + } + + // Custom (folder) icon + if(!customIcon.isNull()) { + bool reloadNeeded(false); + QString iconNamne = customIcon.name(); + for(auto& fi: fileInfos_) { + std::shared_ptr icon = fi->icon(); + if (!fi->icon() || fi->icon()->qicon().name() != iconNamne) { + auto dot_dir = CStrPtr{g_build_filename(fi->path().localPath().get(), ".directory", nullptr)}; + GKeyFile* kf = g_key_file_new(); + g_key_file_set_string(kf, "Desktop Entry", "Icon", iconNamne.toLocal8Bit().constData()); + Fm::GErrorPtr err; + if (!g_key_file_save_to_file(kf, dot_dir.get(), &err)) { + QMessageBox::critical(this, QObject::tr("Custom Icon Error"), err.message()); + } + else { + reloadNeeded = true; + } + g_key_file_free(kf); + } + } + if(reloadNeeded) { + // since there can be only one parent dir, only one reload is needed + auto parent = fileInfo->path().parent(); + if(parent.isValid()) { + auto folder = Fm::Folder::fromPath(parent); + if(folder->isLoaded()) { + folder->reload(); + } + } + } } - } - QDialog::accept(); + QDialog::accept(); } void FilePropsDialog::initOwner() { - if(allNative) { - if(uid != DIFFERENT_UIDS) - ui->owner->setText(uidToName(uid)); - if(gid != DIFFERENT_GIDS) - ui->ownerGroup->setText(gidToName(gid)); - - if(geteuid() != 0) { // on local filesystems, only root can do chown. - ui->owner->setEnabled(false); - ui->ownerGroup->setEnabled(false); + if(allNative) { + if(uid != DIFFERENT_UIDS) { + ui->owner->setText(uidToName(uid)); + } + if(gid != DIFFERENT_GIDS) { + ui->ownerGroup->setText(gidToName(gid)); + } + + if(geteuid() != 0) { // on local filesystems, only root can do chown. + ui->owner->setEnabled(false); + ui->ownerGroup->setEnabled(false); + } } - } } diff --git a/src/filepropsdialog.h b/src/filepropsdialog.h index 257f926..d155c24 100644 --- a/src/filepropsdialog.h +++ b/src/filepropsdialog.h @@ -26,70 +26,73 @@ #include #include +#include "core/fileinfo.h" +#include "core/totalsizejob.h" + namespace Ui { - class FilePropsDialog; -}; +class FilePropsDialog; +} namespace Fm { class LIBFM_QT_API FilePropsDialog : public QDialog { -Q_OBJECT + Q_OBJECT public: - explicit FilePropsDialog(FmFileInfoList* files, QWidget* parent = 0, Qt::WindowFlags f = 0); - virtual ~FilePropsDialog(); + explicit FilePropsDialog(Fm::FileInfoList files, QWidget* parent = 0, Qt::WindowFlags f = 0); + virtual ~FilePropsDialog(); - virtual void accept(); + virtual void accept(); - static FilePropsDialog* showForFile(FmFileInfo* file, QWidget* parent = 0) { - FmFileInfoList* files = fm_file_info_list_new(); - fm_file_info_list_push_tail(files, file); - FilePropsDialog* dlg = showForFiles(files, parent); - fm_file_info_list_unref(files); - return dlg; - } + static FilePropsDialog* showForFile(std::shared_ptr file, QWidget* parent = 0) { + Fm::FileInfoList files; + files.push_back(std::move(file)); + FilePropsDialog* dlg = showForFiles(files, parent); + return dlg; + } - static FilePropsDialog* showForFiles(FmFileInfoList* files, QWidget* parent = 0) { - FilePropsDialog* dlg = new FilePropsDialog(files, parent); - dlg->show(); - return dlg; - } + static FilePropsDialog* showForFiles(Fm::FileInfoList files, QWidget* parent = 0) { + FilePropsDialog* dlg = new FilePropsDialog(std::move(files), parent); + dlg->show(); + return dlg; + } private: - void initGeneralPage(); - void initApplications(); - void initPermissionsPage(); - void initOwner(); - - static void onDeepCountJobFinished(FmDeepCountJob* job, FilePropsDialog* pThis); + void initGeneralPage(); + void initApplications(); + void initPermissionsPage(); + void initOwner(); private Q_SLOTS: - void onFileSizeTimerTimeout(); + void onDeepCountJobFinished(); + void onFileSizeTimerTimeout(); + void onIconButtonclicked(); private: - Ui::FilePropsDialog* ui; - FmFileInfoList* fileInfos_; // list of all file infos - FmFileInfo* fileInfo; // file info of the first file in the list - bool singleType; // all files are of the same type? - bool singleFile; // only one file is selected? - bool hasDir; // is there any dir in the files? - bool allNative; // all files are on native UNIX filesystems (not virtual or remote) - - FmMimeType* mimeType; // mime type of the files - - gint32 uid; // owner uid of the files, -1 means all files do not have the same uid - gint32 gid; // owner gid of the files, -1 means all files do not have the same uid - mode_t ownerPerm; // read permission of the files, -1 means not all files have the same value - int ownerPermSel; - mode_t groupPerm; // read permission of the files, -1 means not all files have the same value - int groupPermSel; - mode_t otherPerm; // read permission of the files, -1 means not all files have the same value - int otherPermSel; - mode_t execPerm; // exec permission of the files - Qt::CheckState execCheckState; - - FmDeepCountJob* deepCountJob; // job used to count total size - QTimer* fileSizeTimer; + Ui::FilePropsDialog* ui; + Fm::FileInfoList fileInfos_; // list of all file infos + std::shared_ptr fileInfo; // file info of the first file in the list + bool singleType; // all files are of the same type? + bool singleFile; // only one file is selected? + bool hasDir; // is there any dir in the files? + bool allNative; // all files are on native UNIX filesystems (not virtual or remote) + QIcon customIcon; // custom (folder) icon (wiil be checked for its nullity) + + std::shared_ptr mimeType; // mime type of the files + + gint32 uid; // owner uid of the files, -1 means all files do not have the same uid + gint32 gid; // owner gid of the files, -1 means all files do not have the same uid + mode_t ownerPerm; // read permission of the files, -1 means not all files have the same value + int ownerPermSel; + mode_t groupPerm; // read permission of the files, -1 means not all files have the same value + int groupPermSel; + mode_t otherPerm; // read permission of the files, -1 means not all files have the same value + int otherPermSel; + mode_t execPerm; // exec permission of the files + Qt::CheckState execCheckState; + + Fm::TotalSizeJob* totalSizeJob; // job used to count total size + QTimer* fileSizeTimer; }; } diff --git a/src/filesearch.ui b/src/filesearch.ui index 2919edb..d8c0e1a 100644 --- a/src/filesearch.ui +++ b/src/filesearch.ui @@ -54,6 +54,9 @@ Use regular expression + + true +
@@ -229,6 +232,9 @@ &Use regular expression + + true +
diff --git a/src/filesearchdialog.cpp b/src/filesearchdialog.cpp index 512aca2..9dbec7c 100644 --- a/src/filesearchdialog.cpp +++ b/src/filesearchdialog.cpp @@ -28,118 +28,173 @@ namespace Fm { FileSearchDialog::FileSearchDialog(QStringList paths, QWidget* parent, Qt::WindowFlags f): - QDialog(parent, f), - ui(new Ui::SearchDialog()) { - ui->setupUi(this); - ui->minSize->setMaximum(std::numeric_limits().max()); - ui->maxSize->setMaximum(std::numeric_limits().max()); - Q_FOREACH(const QString& path, paths) { - ui->listView->addItem(path); - } + QDialog(parent, f), + ui(new Ui::SearchDialog()) { + ui->setupUi(this); + ui->minSize->setMaximum(std::numeric_limits().max()); + ui->maxSize->setMaximum(std::numeric_limits().max()); + Q_FOREACH(const QString& path, paths) { + ui->listView->addItem(path); + } - ui->maxTime->setDate(QDate::currentDate()); - ui->minTime->setDate(QDate::currentDate()); + ui->maxTime->setDate(QDate::currentDate()); + ui->minTime->setDate(QDate::currentDate()); - connect(ui->addPath, &QPushButton::clicked, this, &FileSearchDialog::onAddPath); - connect(ui->removePath, &QPushButton::clicked, this, &FileSearchDialog::onRemovePath); + connect(ui->addPath, &QPushButton::clicked, this, &FileSearchDialog::onAddPath); + connect(ui->removePath, &QPushButton::clicked, this, &FileSearchDialog::onRemovePath); - ui->namePatterns->setFocus(); + ui->namePatterns->setFocus(); } FileSearchDialog::~FileSearchDialog() { - delete ui; + delete ui; } void FileSearchDialog::accept() { - // build the search:/// uri - int n = ui->listView->count(); - if(n > 0) { - FmSearch* search = fm_search_new(); - for(int i = 0; i < n; ++i) { // add directories - QListWidgetItem* item = ui->listView->item(i); - fm_search_add_dir(search, item->text().toLocal8Bit().constData()); + // build the search:/// uri + int n = ui->listView->count(); + if(n > 0) { + FmSearch* search = fm_search_new(); + for(int i = 0; i < n; ++i) { // add directories + QListWidgetItem* item = ui->listView->item(i); + fm_search_add_dir(search, item->text().toLocal8Bit().constData()); + } + + fm_search_set_recursive(search, ui->recursiveSearch->isChecked()); + fm_search_set_show_hidden(search, ui->searchHidden->isChecked()); + fm_search_set_name_patterns(search, ui->namePatterns->text().toUtf8().constData()); + fm_search_set_name_ci(search, ui->nameCaseInsensitive->isChecked()); + fm_search_set_name_regex(search, ui->nameRegExp->isChecked()); + + fm_search_set_content_pattern(search, ui->contentPattern->text().toUtf8().constData()); + fm_search_set_content_ci(search, ui->contentCaseInsensitive->isChecked()); + fm_search_set_content_regex(search, ui->contentRegExp->isChecked()); + + // search for the files of specific mime-types + if(ui->searchTextFiles->isChecked()) { + fm_search_add_mime_type(search, "text/plain"); + } + if(ui->searchImages->isChecked()) { + fm_search_add_mime_type(search, "image/*"); + } + if(ui->searchAudio->isChecked()) { + fm_search_add_mime_type(search, "audio/*"); + } + if(ui->searchVideo->isChecked()) { + fm_search_add_mime_type(search, "video/*"); + } + if(ui->searchFolders->isChecked()) { + fm_search_add_mime_type(search, "inode/directory"); + } + if(ui->searchDocuments->isChecked()) { + const char* doc_types[] = { + "application/pdf", + /* "text/html;" */ + "application/vnd.oasis.opendocument.*", + "application/vnd.openxmlformats-officedocument.*", + "application/msword;application/vnd.ms-word", + "application/msexcel;application/vnd.ms-excel" + }; + for(unsigned int i = 0; i < sizeof(doc_types) / sizeof(char*); ++i) { + fm_search_add_mime_type(search, doc_types[i]); + } + } + + // search based on file size + const unsigned int unit_bytes[] = {1, (1024), (1024 * 1024), (1024 * 1024 * 1024)}; + if(ui->largerThan->isChecked()) { + guint64 size = ui->minSize->value() * unit_bytes[ui->minSizeUnit->currentIndex()]; + fm_search_set_min_size(search, size); + } + + if(ui->smallerThan->isChecked()) { + guint64 size = ui->maxSize->value() * unit_bytes[ui->maxSizeUnit->currentIndex()]; + fm_search_set_max_size(search, size); + } + + // search based on file mtime (we only support date in YYYY-MM-DD format) + if(ui->earlierThan->isChecked()) { + fm_search_set_max_mtime(search, ui->maxTime->date().toString(QStringLiteral("yyyy-MM-dd")).toUtf8().constData()); + } + if(ui->laterThan->isChecked()) { + fm_search_set_min_mtime(search, ui->minTime->date().toString(QStringLiteral("yyyy-MM-dd")).toUtf8().constData()); + } + + searchUri_ = Path::wrapPtr(fm_search_dup_path(search)); + + fm_search_free(search); } - - fm_search_set_recursive(search, ui->recursiveSearch->isChecked()); - fm_search_set_show_hidden(search, ui->searchHidden->isChecked()); - fm_search_set_name_patterns(search, ui->namePatterns->text().toUtf8().constData()); - fm_search_set_name_ci(search, ui->nameCaseInsensitive->isChecked()); - fm_search_set_name_regex(search, ui->nameRegExp->isChecked()); - - fm_search_set_content_pattern(search, ui->contentPattern->text().toUtf8().constData()); - fm_search_set_content_ci(search, ui->contentCaseInsensitive->isChecked()); - fm_search_set_content_regex(search, ui->contentRegExp->isChecked()); - - // search for the files of specific mime-types - if(ui->searchTextFiles->isChecked()) - fm_search_add_mime_type(search, "text/plain"); - if(ui->searchImages->isChecked()) - fm_search_add_mime_type(search, "image/*"); - if(ui->searchAudio->isChecked()) - fm_search_add_mime_type(search, "audio/*"); - if(ui->searchVideo->isChecked()) - fm_search_add_mime_type(search, "video/*"); - if(ui->searchFolders->isChecked()) - fm_search_add_mime_type(search, "inode/directory"); - if(ui->searchDocuments->isChecked()) { - const char* doc_types[] = { - "application/pdf", - /* "text/html;" */ - "application/vnd.oasis.opendocument.*", - "application/vnd.openxmlformats-officedocument.*", - "application/msword;application/vnd.ms-word", - "application/msexcel;application/vnd.ms-excel" - }; - for(unsigned int i = 0; i < sizeof(doc_types)/sizeof(char*); ++i) - fm_search_add_mime_type(search, doc_types[i]); + else { + QMessageBox::critical(this, tr("Error"), tr("You should add at least one directory to search.")); + return; } + QDialog::accept(); +} - // search based on file size - const unsigned int unit_bytes[] = {1, (1024), (1024*1024), (1024*1024*1024)}; - if(ui->largerThan->isChecked()) { - guint64 size = ui->minSize->value() * unit_bytes[ui->minSizeUnit->currentIndex()]; - fm_search_set_min_size(search, size); +void FileSearchDialog::onAddPath() { + QString dir = QFileDialog::getExistingDirectory(this, tr("Select a folder")); + if(dir.isEmpty()) { + return; } - - if(ui->smallerThan->isChecked()) { - guint64 size = ui->maxSize->value() * unit_bytes[ui->maxSizeUnit->currentIndex()]; - fm_search_set_max_size(search, size); + // avoid adding duplicated items + if(ui->listView->findItems(dir, Qt::MatchFixedString | Qt::MatchCaseSensitive).isEmpty()) { + ui->listView->addItem(dir); } +} - // search based on file mtime (we only support date in YYYY-MM-DD format) - if(ui->earlierThan->isChecked()) { - fm_search_set_max_mtime(search, ui->maxTime->date().toString(QStringLiteral("yyyy-MM-dd")).toUtf8().constData()); - } - if(ui->laterThan->isChecked()) { - fm_search_set_min_mtime(search, ui->minTime->date().toString(QStringLiteral("yyyy-MM-dd")).toUtf8().constData()); +void FileSearchDialog::onRemovePath() { + // remove selected items + Q_FOREACH(QListWidgetItem* item, ui->listView->selectedItems()) { + delete item; } +} - searchUri_ = std::move(Path::wrapPtr(fm_search_dup_path(search))); +void FileSearchDialog::setNameCaseInsensitive(bool caseInsensitive) { + ui->nameCaseInsensitive->setChecked(caseInsensitive); +} - fm_search_free(search); - } - else { - QMessageBox::critical(this, tr("Error"), tr("You should add at least one directory to search.")); - return; - } - QDialog::accept(); +void FileSearchDialog::setContentCaseInsensitive(bool caseInsensitive) { + ui->contentCaseInsensitive->setChecked(caseInsensitive); } -void FileSearchDialog::onAddPath() { - QString dir = QFileDialog::getExistingDirectory(this, tr("Select a folder")); - if(dir.isEmpty()) - return; - // avoid adding duplicated items - if(ui->listView->findItems(dir, Qt::MatchFixedString|Qt::MatchCaseSensitive).isEmpty()) { - ui->listView->addItem(dir); - } +void FileSearchDialog::setNameRegexp(bool reg) { + ui->nameRegExp->setChecked(reg); } -void FileSearchDialog::onRemovePath() { - // remove selected items - Q_FOREACH(QListWidgetItem* item, ui->listView->selectedItems()) { - delete item; - } +void FileSearchDialog::setContentRegexp(bool reg) { + ui->contentRegExp->setChecked(reg); +} + +void FileSearchDialog::setRecursive(bool rec) { + ui->recursiveSearch->setChecked(rec); +} + +void FileSearchDialog::setSearchhHidden(bool hidden) { + ui->searchHidden->setChecked(hidden); +} + +bool FileSearchDialog::nameCaseInsensitive() const { + return ui->nameCaseInsensitive->isChecked(); +} + +bool FileSearchDialog::contentCaseInsensitive() const { + return ui->contentCaseInsensitive->isChecked(); +} + +bool FileSearchDialog::nameRegexp() const { + return ui->nameRegExp->isChecked(); +} + +bool FileSearchDialog::contentRegexp() const { + return ui->contentRegExp->isChecked(); +} + +bool FileSearchDialog::recursive() const { + return ui->recursiveSearch->isChecked(); +} + +bool FileSearchDialog::searchhHidden() const { + return ui->searchHidden->isChecked(); } } diff --git a/src/filesearchdialog.h b/src/filesearchdialog.h index dc6dc08..1ca6632 100644 --- a/src/filesearchdialog.h +++ b/src/filesearchdialog.h @@ -1,20 +1,20 @@ /* * Copyright (C) 2015 Hong Jen Yee (PCMan) - * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * + * */ #ifndef FM_FILESEARCHDIALOG_H @@ -25,30 +25,47 @@ #include "path.h" namespace Ui { - class SearchDialog; +class SearchDialog; } namespace Fm { -class LIBFM_QT_API FileSearchDialog : public QDialog -{ +class LIBFM_QT_API FileSearchDialog : public QDialog { public: - FileSearchDialog(QStringList paths = QStringList(), QWidget * parent = 0, Qt::WindowFlags f = 0); - ~FileSearchDialog(); + explicit FileSearchDialog(QStringList paths = QStringList(), QWidget* parent = 0, Qt::WindowFlags f = 0); + ~FileSearchDialog(); - Path searchUri() const { - return searchUri_; - } + Path searchUri() const { + return searchUri_; + } - virtual void accept(); + virtual void accept(); + + bool nameCaseInsensitive() const; + void setNameCaseInsensitive(bool caseInsensitive); + + bool contentCaseInsensitive() const; + void setContentCaseInsensitive(bool caseInsensitive); + + bool nameRegexp() const; + void setNameRegexp(bool reg); + + bool contentRegexp() const; + void setContentRegexp(bool reg); + + bool recursive() const; + void setRecursive(bool rec); + + bool searchhHidden() const; + void setSearchhHidden(bool hidden); private Q_SLOTS: - void onAddPath(); - void onRemovePath(); + void onAddPath(); + void onRemovePath(); private: - Ui::SearchDialog* ui; - Path searchUri_; + Ui::SearchDialog* ui; + Path searchUri_; }; } diff --git a/src/folder.h b/src/folder.h deleted file mode 100644 index 355b6d7..0000000 --- a/src/folder.h +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_FOLDER_H__ -#define __LIBFM_QT_FM_FOLDER_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - -class LIBFM_QT_API Folder { -public: - - - // default constructor - Folder() { - dataPtr_ = nullptr; - } - - - Folder(FmFolder* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(g_object_ref(dataPtr)) : nullptr; - } - - - // copy constructor - Folder(const Folder& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - Folder(Folder&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - virtual ~Folder() { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static Folder wrapPtr(FmFolder* dataPtr) { - Folder obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmFolder* takeDataPtr() { - FmFolder* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmFolder* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmFolder*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - Folder& operator=(const Folder& other) { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - Folder& operator=(Folder&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - bool makeDirectory(const char* name, GError** error) { - return fm_folder_make_directory(dataPtr(), name, error); - } - - - void queryFilesystemInfo(void) { - fm_folder_query_filesystem_info(dataPtr()); - } - - - bool getFilesystemInfo(guint64* total_size, guint64* free_size) { - return fm_folder_get_filesystem_info(dataPtr(), total_size, free_size); - } - - - void reload(void) { - fm_folder_reload(dataPtr()); - } - - - bool isIncremental(void) { - return fm_folder_is_incremental(dataPtr()); - } - - - bool isValid(void) { - return fm_folder_is_valid(dataPtr()); - } - - - bool isLoaded(void) { - return fm_folder_is_loaded(dataPtr()); - } - - - FmFileInfo* getFileByName(const char* name) { - return fm_folder_get_file_by_name(dataPtr(), name); - } - - - bool isEmpty(void) { - return fm_folder_is_empty(dataPtr()); - } - - - FmFileInfoList* getFiles(void) { - return fm_folder_get_files(dataPtr()); - } - - - FmPath* getPath(void) { - return fm_folder_get_path(dataPtr()); - } - - - FmFileInfo* getInfo(void) { - return fm_folder_get_info(dataPtr()); - } - - - void unblockUpdates(void) { - fm_folder_unblock_updates(dataPtr()); - } - - - void blockUpdates(void) { - fm_folder_block_updates(dataPtr()); - } - - - static Folder findByPath(FmPath* path) { - return Folder::wrapPtr(fm_folder_find_by_path(path)); - } - - - static Folder fromUri(const char* uri) { - return Folder::wrapPtr(fm_folder_from_uri(uri)); - } - - - static Folder fromPathName(const char* path) { - return Folder::wrapPtr(fm_folder_from_path_name(path)); - } - - - static Folder fromGfile(GFile* gf) { - return Folder::wrapPtr(fm_folder_from_gfile(gf)); - } - - - static Folder fromPath(FmPath* path) { - return Folder::wrapPtr(fm_folder_from_path(path)); - } - - - // automatic type casting for GObject - operator GObject*() { - return reinterpret_cast(dataPtr_); - } - - -protected: - GObject* dataPtr_; // data pointer for the underlying C struct - -}; - - -} - -#endif // __LIBFM_QT_FM_FOLDER_H__ diff --git a/src/folderconfig.h b/src/folderconfig.h index 61ebb40..3a0843c 100644 --- a/src/folderconfig.h +++ b/src/folderconfig.h @@ -25,170 +25,173 @@ #include #include "libfmqtglobals.h" +#include "core/filepath.h" namespace Fm { +// FIXME: port to the new API and drop libfm dependency class LIBFM_QT_API FolderConfig { public: - - FolderConfig(FmPath* path) { - dataPtr_ = reinterpret_cast(fm_folder_config_open(path)); - } + FolderConfig(const Fm::FilePath& path) { + FmPath* fmpath = fm_path_new_for_gfile(path.gfile().get()); + dataPtr_ = reinterpret_cast(fm_folder_config_open(fmpath)); + fm_path_unref(fmpath); + } - // default constructor - FolderConfig() { - dataPtr_ = nullptr; - } + // default constructor + FolderConfig() { + dataPtr_ = nullptr; + } - // move constructor - FolderConfig(FolderConfig&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } + // move constructor + FolderConfig(FolderConfig&& other) noexcept { + dataPtr_ = reinterpret_cast(other.takeDataPtr()); + } - // destructor - ~FolderConfig() { - if(dataPtr_ != nullptr) { - fm_folder_config_close(dataPtr_, nullptr); + // destructor + ~FolderConfig() { + if(dataPtr_ != nullptr) { + fm_folder_config_close(dataPtr_, nullptr); + } } - } - // create a wrapper for the data pointer without increasing the reference count - static FolderConfig wrapPtr(FmFolderConfig* dataPtr) { - FolderConfig obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } + // create a wrapper for the data pointer without increasing the reference count + static FolderConfig wrapPtr(FmFolderConfig* dataPtr) { + FolderConfig obj; + obj.dataPtr_ = reinterpret_cast(dataPtr); + return obj; + } - // disown the managed data pointer - FmFolderConfig* takeDataPtr() { - FmFolderConfig* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } + // disown the managed data pointer + FmFolderConfig* takeDataPtr() { + FmFolderConfig* data = reinterpret_cast(dataPtr_); + dataPtr_ = nullptr; + return data; + } - // get the raw pointer wrapped - FmFolderConfig* dataPtr() { - return reinterpret_cast(dataPtr_); - } + // get the raw pointer wrapped + FmFolderConfig* dataPtr() { + return reinterpret_cast(dataPtr_); + } - // automatic type casting - operator FmFolderConfig*() { - return dataPtr(); - } + // automatic type casting + operator FmFolderConfig* () { + return dataPtr(); + } - // automatic type casting - operator void*() { - return dataPtr(); - } + // automatic type casting + operator void* () { + return dataPtr(); + } - // move assignment - FolderConfig& operator=(FolderConfig&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } + // move assignment + FolderConfig& operator=(FolderConfig&& other) noexcept { + dataPtr_ = reinterpret_cast(other.takeDataPtr()); + return *this; + } - bool isNull() { - return (dataPtr_ == nullptr); - } + bool isNull() { + return (dataPtr_ == nullptr); + } - // methods + // methods - static void saveCache(void ) { - fm_folder_config_save_cache(); - } + static void saveCache(void) { + fm_folder_config_save_cache(); + } - void purge(void) { - fm_folder_config_purge(dataPtr()); - } + void purge(void) { + fm_folder_config_purge(dataPtr()); + } - void removeKey(const char* key) { - fm_folder_config_remove_key(dataPtr(), key); - } + void removeKey(const char* key) { + fm_folder_config_remove_key(dataPtr(), key); + } - void setStringList(const char* key, const gchar * const list[], gsize length) { - fm_folder_config_set_string_list(dataPtr(), key, list, length); - } + void setStringList(const char* key, const gchar* const list[], gsize length) { + fm_folder_config_set_string_list(dataPtr(), key, list, length); + } - void setString(const char* key, const char* string) { - fm_folder_config_set_string(dataPtr(), key, string); - } + void setString(const char* key, const char* string) { + fm_folder_config_set_string(dataPtr(), key, string); + } - void setBoolean(const char* key, gboolean val) { - fm_folder_config_set_boolean(dataPtr(), key, val); - } + void setBoolean(const char* key, gboolean val) { + fm_folder_config_set_boolean(dataPtr(), key, val); + } - void setDouble(const char* key, gdouble val) { - fm_folder_config_set_double(dataPtr(), key, val); - } + void setDouble(const char* key, gdouble val) { + fm_folder_config_set_double(dataPtr(), key, val); + } - void setUint64(const char* key, guint64 val) { - fm_folder_config_set_uint64(dataPtr(), key, val); - } + void setUint64(const char* key, guint64 val) { + fm_folder_config_set_uint64(dataPtr(), key, val); + } - void setInteger(const char* key, gint val) { - fm_folder_config_set_integer(dataPtr(), key, val); - } + void setInteger(const char* key, gint val) { + fm_folder_config_set_integer(dataPtr(), key, val); + } - char** getStringList(const char* key, gsize* length) { - return fm_folder_config_get_string_list(dataPtr(), key, length); - } + char** getStringList(const char* key, gsize* length) { + return fm_folder_config_get_string_list(dataPtr(), key, length); + } - char* getString(const char* key) { - return fm_folder_config_get_string(dataPtr(), key); - } + char* getString(const char* key) { + return fm_folder_config_get_string(dataPtr(), key); + } - bool getBoolean(const char* key, gboolean* val) { - return fm_folder_config_get_boolean(dataPtr(), key, val); - } + bool getBoolean(const char* key, gboolean* val) { + return fm_folder_config_get_boolean(dataPtr(), key, val); + } - bool getDouble(const char* key, gdouble* val) { - return fm_folder_config_get_double(dataPtr(), key, val); - } + bool getDouble(const char* key, gdouble* val) { + return fm_folder_config_get_double(dataPtr(), key, val); + } - bool getUint64(const char* key, guint64* val) { - return fm_folder_config_get_uint64(dataPtr(), key, val); - } + bool getUint64(const char* key, guint64* val) { + return fm_folder_config_get_uint64(dataPtr(), key, val); + } - bool getInteger(const char* key, gint* val) { - return fm_folder_config_get_integer(dataPtr(), key, val); - } + bool getInteger(const char* key, gint* val) { + return fm_folder_config_get_integer(dataPtr(), key, val); + } - bool isEmpty(void) { - return fm_folder_config_is_empty(dataPtr()); - } + bool isEmpty(void) { + return fm_folder_config_is_empty(dataPtr()); + } // the wrapped object cannot be copied. private: - FolderConfig(const FolderConfig& other) = delete; - FolderConfig& operator=(const FolderConfig& other) = delete; + FolderConfig(const FolderConfig& other) = delete; + FolderConfig& operator=(const FolderConfig& other) = delete; private: - FmFolderConfig* dataPtr_; // data pointer for the underlying C struct + FmFolderConfig* dataPtr_; // data pointer for the underlying C struct }; diff --git a/src/folderitemdelegate.cpp b/src/folderitemdelegate.cpp index be8c01b..62e1dbc 100644 --- a/src/folderitemdelegate.cpp +++ b/src/folderitemdelegate.cpp @@ -22,232 +22,409 @@ #include "foldermodel.h" #include #include +#include #include #include #include #include #include #include +#include +#include +#include #include namespace Fm { FolderItemDelegate::FolderItemDelegate(QAbstractItemView* view, QObject* parent): - QStyledItemDelegate(parent ? parent : view), - view_(view), - symlinkIcon_(QIcon::fromTheme("emblem-symbolic-link")), - fileInfoRole_(Fm::FolderModel::FileInfoRole), - fmIconRole_(-1) { + QStyledItemDelegate(parent ? parent : view), + symlinkIcon_(QIcon::fromTheme("emblem-symbolic-link")), + fileInfoRole_(Fm::FolderModel::FileInfoRole), + iconInfoRole_(-1), + margins_(QSize(3, 3)), + hasEditor_(false) { + connect(this, &QAbstractItemDelegate::closeEditor, [=]{hasEditor_ = false;}); } FolderItemDelegate::~FolderItemDelegate() { } +QSize FolderItemDelegate::iconViewTextSize(const QModelIndex& index) const { + QStyleOptionViewItem opt; + initStyleOption(&opt, index); + opt.decorationSize = iconSize_.isValid() ? iconSize_ : QSize(0, 0); + opt.decorationAlignment = Qt::AlignHCenter | Qt::AlignTop; + opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; + QRectF textRect(0, 0, + itemSize_.width() - 2 * margins_.width(), + itemSize_.height() - 2 * margins_.height() - opt.decorationSize.height()); + drawText(nullptr, opt, textRect); // passing nullptr for painter will calculate the bounding rect only + return textRect.toRect().size(); +} + QSize FolderItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { - QVariant value = index.data(Qt::SizeHintRole); - if(value.isValid()) - return qvariant_cast(value); - if(option.decorationPosition == QStyleOptionViewItem::Top || - option.decorationPosition == QStyleOptionViewItem::Bottom) { + QVariant value = index.data(Qt::SizeHintRole); + if(value.isValid()) { + // no further processing if the size is specified by the data model + return qvariant_cast(value); + } + if(option.decorationPosition == QStyleOptionViewItem::Top || + option.decorationPosition == QStyleOptionViewItem::Bottom) { + // we handle vertical layout just by returning our item size + return itemSize_; + } + + // The default size hint of the horizontal layout isn't reliable + // because Qt calculates the row size based on the real icon size, + // which may not be equal to the requested icon size on various occasions. + // So, we do as in QStyledItemDelegate::sizeHint() but use the requested size. QStyleOptionViewItem opt = option; initStyleOption(&opt, index); - opt.decorationAlignment = Qt::AlignHCenter|Qt::AlignTop; - opt.displayAlignment = Qt::AlignTop|Qt::AlignHCenter; - - // "opt.decorationSize" may be smaller than the requested size because - // "QStyledItemDelegate::initStyleOption()" uses "QIcon::actualSize()" to set it - // (see Qt -> qstyleditemdelegate.cpp). So, we always get decorationSize from "option". - Q_ASSERT(gridSize_ != QSize()); - QRectF textRect(0, 0, gridSize_.width(), gridSize_.height() - option.decorationSize.height()); - drawText(nullptr, opt, textRect); // passing NULL for painter will calculate the bounding rect only. - int width = qMax((int)textRect.width(), option.decorationSize.width()); - int height = option.decorationSize.height() + textRect.height(); - return QSize(width, height); - } - return QStyledItemDelegate::sizeHint(option, index); + opt.decorationSize = option.decorationSize; // requested by the view + const QWidget* widget = option.widget; + QStyle* style = widget ? widget->style() : QApplication::style(); + return style->sizeFromContents(QStyle::CT_ItemViewItem, &opt, QSize(), widget); } QIcon::Mode FolderItemDelegate::iconModeFromState(const QStyle::State state) { - if(state & QStyle::State_Enabled) - return (state & QStyle::State_Selected) ? QIcon::Selected : QIcon::Normal; + if(state & QStyle::State_Enabled) { + return (state & QStyle::State_Selected) ? QIcon::Selected : QIcon::Normal; + } - return QIcon::Disabled; + return QIcon::Disabled; } -// special thanks to Razor-qt developer Alec Moskvin(amoskvin) for providing the fix! void FolderItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - Q_ASSERT(index.isValid()); - FmFileInfo* file = static_cast(index.data(fileInfoRole_).value()); - FmIcon* fmicon = static_cast(index.data(fmIconRole_).value()); - if(fmicon == nullptr && file != nullptr) { - fmicon = fm_file_info_get_icon(file); - } - QList emblems = fmicon != nullptr ? IconTheme::emblems(fmicon) : QList(); - bool isSymlink = file && fm_file_info_is_symlink(file); - if(option.decorationPosition == QStyleOptionViewItem::Top || - option.decorationPosition == QStyleOptionViewItem::Bottom) { - painter->save(); - painter->setClipRect(option.rect); + if(!index.isValid()) + return; - QStyleOptionViewItem opt = option; - initStyleOption(&opt, index); - opt.decorationAlignment = Qt::AlignHCenter|Qt::AlignTop; - opt.displayAlignment = Qt::AlignTop|Qt::AlignHCenter; - - // draw the icon - QIcon::Mode iconMode = iconModeFromState(opt.state); - QPoint iconPos(opt.rect.x() + (opt.rect.width() - option.decorationSize.width()) / 2, opt.rect.y()); - QPixmap pixmap = opt.icon.pixmap(option.decorationSize, iconMode); - // in case the pixmap is smaller than the requested size - QSize margin = ((option.decorationSize - pixmap.size()) / 2).expandedTo(QSize(0, 0)); - painter->drawPixmap(iconPos + QPoint(margin.width(), margin.height()), pixmap); - - // draw some emblems for the item if needed - if(isSymlink) - painter->drawPixmap(iconPos, symlinkIcon_.pixmap(option.decorationSize / 2, iconMode)); - if(!emblems.isEmpty()) { - QPoint emblemPos(opt.rect.x() + opt.rect.width() / 2, opt.rect.y() + option.decorationSize.height() / 2); - QIcon emblem = IconTheme::icon(emblems.first().dataPtr()); - painter->drawPixmap(emblemPos, emblem.pixmap(option.decorationSize / 2, iconMode)); - } - - // draw the text - // The text rect dimensions should be exactly as they were in sizeHint() - QRectF textRect(opt.rect.x() - (gridSize_.width() - opt.rect.width()) / 2, - opt.rect.y() + option.decorationSize.height(), - gridSize_.width(), - gridSize_.height() - option.decorationSize.height()); - drawText(painter, opt, textRect); - painter->restore(); - } - else { - // let QStyledItemDelegate does its default painting - QStyledItemDelegate::paint(painter, option, index); - - // draw emblems if needed - if(isSymlink || !emblems.isEmpty()) { - QStyleOptionViewItem opt = option; - initStyleOption(&opt, index); - QIcon::Mode iconMode = iconModeFromState(opt.state); - // draw some emblems for the item if needed - if(isSymlink) { - QPoint iconPos(opt.rect.x(), opt.rect.y() + (opt.rect.height() - option.decorationSize.height()) / 2); - painter->drawPixmap(iconPos, symlinkIcon_.pixmap(option.decorationSize / 2, iconMode)); - } - else { - QPoint iconPos(opt.rect.x() + option.decorationSize.width() / 2, opt.rect.y() + opt.rect.height() / 2); - QIcon emblem = IconTheme::icon(emblems.first().dataPtr()); - painter->drawPixmap(iconPos, emblem.pixmap(option.decorationSize / 2, iconMode)); - } - } - } + // get emblems for this icon + std::forward_list> icon_emblems; + auto fmicon = index.data(iconInfoRole_).value>(); + if(fmicon) { + icon_emblems = fmicon->emblems(); + } + // get file info for the item + auto file = index.data(fileInfoRole_).value>(); + const auto& emblems = file ? file->emblems() : icon_emblems; + + bool isSymlink = file && file->isSymlink(); + // vertical layout (icon mode, thumbnail mode) + if(option.decorationPosition == QStyleOptionViewItem::Top || + option.decorationPosition == QStyleOptionViewItem::Bottom) { + painter->save(); + painter->setClipRect(option.rect); + + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + opt.decorationAlignment = Qt::AlignHCenter | Qt::AlignTop; + opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; + + // draw the icon + QIcon::Mode iconMode = iconModeFromState(opt.state); + QPoint iconPos(opt.rect.x() + (opt.rect.width() - option.decorationSize.width()) / 2, opt.rect.y() + margins_.height()); + QPixmap pixmap = opt.icon.pixmap(option.decorationSize, iconMode); + // in case the pixmap is smaller than the requested size + QSize margin = ((option.decorationSize - pixmap.size()) / 2).expandedTo(QSize(0, 0)); + bool isCut = index.data(FolderModel::FileIsCutRole).toBool(); + if(isCut) { + painter->save(); + painter->setOpacity(0.45); + } + painter->drawPixmap(iconPos + QPoint(margin.width(), margin.height()), pixmap); + if(isCut) { + painter->restore(); + } + + // draw some emblems for the item if needed + if(isSymlink) { + // draw the emblem for symlinks + painter->drawPixmap(iconPos, symlinkIcon_.pixmap(option.decorationSize / 2, iconMode)); + } + + // draw other emblems if there's any + if(!emblems.empty()) { + // FIXME: we only support one emblem now + QPoint emblemPos(opt.rect.x() + opt.rect.width() / 2, opt.rect.y() + option.decorationSize.height() / 2); + QIcon emblem = emblems.front()->qicon(); + painter->drawPixmap(emblemPos, emblem.pixmap(option.decorationSize / 2, iconMode)); + } + + // draw the text + QSize drawAreaSize = itemSize_ - 2 * margins_; + // The text rect dimensions should be exactly as they were in sizeHint() + QRectF textRect(opt.rect.x() + (opt.rect.width() - drawAreaSize.width()) / 2, + opt.rect.y() + margins_.height() + option.decorationSize.height(), + drawAreaSize.width(), + drawAreaSize.height() - option.decorationSize.height()); + drawText(painter, opt, textRect); + painter->restore(); + } + else { // horizontal layout (list view) + + // let QStyledItemDelegate does its default painting + // FIXME: For better text alignment, here we should increase + // the icon width if it's smaller that the requested size + QStyledItemDelegate::paint(painter, option, index); + + // draw emblems if needed + if(isSymlink || !emblems.empty()) { + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + QIcon::Mode iconMode = iconModeFromState(opt.state); + // draw some emblems for the item if needed + if(isSymlink) { + QPoint iconPos(opt.rect.x(), opt.rect.y() + (opt.rect.height() - option.decorationSize.height()) / 2); + painter->drawPixmap(iconPos, symlinkIcon_.pixmap(option.decorationSize / 2, iconMode)); + } + else { + // FIXME: we only support one emblem now + QPoint iconPos(opt.rect.x() + option.decorationSize.width() / 2, opt.rect.y() + opt.rect.height() / 2); + QIcon emblem = emblems.front()->qicon(); + painter->drawPixmap(iconPos, emblem.pixmap(option.decorationSize / 2, iconMode)); + } + } + } } // if painter is nullptr, the method calculate the bounding rectangle of the text and save it to textRect void FolderItemDelegate::drawText(QPainter* painter, QStyleOptionViewItem& opt, QRectF& textRect) const { - QTextLayout layout(opt.text, opt.font); - QTextOption textOption; - textOption.setAlignment(opt.displayAlignment); - textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - if (opt.text.isRightToLeft()) - textOption.setTextDirection(Qt::RightToLeft); - else - textOption.setTextDirection(Qt::LeftToRight); - layout.setTextOption(textOption); - qreal height = 0; - qreal width = 0; - int visibleLines = 0; - layout.beginLayout(); - QString elidedText; - textRect.adjust(2, 2, -2, -2); // a 2-px margin is considered at FolderView::updateGridSize() - for(;;) { - QTextLine line = layout.createLine(); - if(!line.isValid()) - break; - line.setLineWidth(textRect.width()); - height += opt.fontMetrics.leading(); - line.setPosition(QPointF(0, height)); - if((height + line.height() + textRect.y()) > textRect.bottom()) { - // if part of this line falls outside the textRect, ignore it and quit. - QTextLine lastLine = layout.lineAt(visibleLines - 1); - elidedText = opt.text.mid(lastLine.textStart()); - elidedText = opt.fontMetrics.elidedText(elidedText, opt.textElideMode, textRect.width()); - if(visibleLines == 1) // this is the only visible line - width = textRect.width(); - break; - } - height += line.height(); - width = qMax(width, line.naturalTextWidth()); - ++ visibleLines; - } - layout.endLayout(); - width = qMax(width, (qreal)opt.fontMetrics.width(elidedText)); - - // draw background for selected item - QRectF boundRect = layout.boundingRect(); - //qDebug() << "bound rect: " << boundRect << "width: " << width; - boundRect.setWidth(width); - boundRect.setHeight(height); - boundRect.moveTo(textRect.x() + (textRect.width() - width)/2, textRect.y()); - - QRectF selRect = boundRect.adjusted(-2, -2, 2, 2); - - if(!painter) { // no painter, calculate the bounding rect only - textRect = selRect; - return; - } - - QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; - if(opt.state & QStyle::State_Selected) { - if(!opt.widget) - painter->fillRect(selRect, opt.palette.highlight()); - painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); - } - else - painter->setPen(opt.palette.color(cg, QPalette::Text)); - - if (opt.state & QStyle::State_Selected || opt.state & QStyle::State_MouseOver) { - if (const QWidget* widget = opt.widget) { // let the style engine do it - QStyle* style = widget->style() ? widget->style() : qApp->style(); - QStyleOptionViewItem o(opt); - o.text = QString(); - o.rect = selRect.toAlignedRect().intersected(opt.rect); // due to clipping and rounding, we might lose 1px - o.showDecorationSelected = true; - style->drawPrimitive(QStyle::PE_PanelItemViewItem, &o, painter, widget); - } - } - - // draw text - for(int i = 0; i < visibleLines; ++i) { - QTextLine line = layout.lineAt(i); - if(i == (visibleLines - 1) && !elidedText.isEmpty()) { // the last line, draw elided text - QPointF pos(boundRect.x() + line.position().x(), boundRect.y() + line.y() + line.ascent()); - painter->drawText(pos, elidedText); + QTextLayout layout(opt.text, opt.font); + QTextOption textOption; + textOption.setAlignment(opt.displayAlignment); + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + // FIXME: textOption.setTextDirection(opt.direction); ? + if(opt.text.isRightToLeft()) { + textOption.setTextDirection(Qt::RightToLeft); } else { - line.draw(painter, textRect.topLeft()); + textOption.setTextDirection(Qt::LeftToRight); + } + layout.setTextOption(textOption); + qreal height = 0; + qreal width = 0; + int visibleLines = 0; + layout.beginLayout(); + QString elidedText; + textRect.adjust(2, 2, -2, -2); // a 2-px margin is considered at FolderView::updateGridSize() + for(;;) { + QTextLine line = layout.createLine(); + if(!line.isValid()) { + break; + } + line.setLineWidth(textRect.width()); + height += opt.fontMetrics.leading(); + line.setPosition(QPointF(0, height)); + if((height + line.height() + textRect.y()) > textRect.bottom()) { + // if part of this line falls outside the textRect, ignore it and quit. + QTextLine lastLine = layout.lineAt(visibleLines - 1); + elidedText = opt.text.mid(lastLine.textStart()); + elidedText = opt.fontMetrics.elidedText(elidedText, opt.textElideMode, textRect.width()); + if(visibleLines == 1) { // this is the only visible line + width = textRect.width(); + } + break; + } + height += line.height(); + width = qMax(width, line.naturalTextWidth()); + ++ visibleLines; } - } + layout.endLayout(); + width = qMax(width, (qreal)opt.fontMetrics.width(elidedText)); + + // draw background for selected item + QRectF boundRect = layout.boundingRect(); + //qDebug() << "bound rect: " << boundRect << "width: " << width; + boundRect.setWidth(width); + boundRect.setHeight(height); + boundRect.moveTo(textRect.x() + (textRect.width() - width) / 2, textRect.y()); + + QRectF selRect = boundRect.adjusted(-2, -2, 2, 2); - if(opt.state & QStyle::State_HasFocus) { - // draw focus rect - QStyleOptionFocusRect o; - o.QStyleOption::operator=(opt); - o.rect = selRect.toRect(); // subElementRect(SE_ItemViewItemFocusRect, vopt, widget); - o.state |= QStyle::State_KeyboardFocusChange; - o.state |= QStyle::State_Item; + if(!painter) { // no painter, calculate the bounding rect only + textRect = selRect; + return; + } + + // ????? QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled) - ? QPalette::Normal : QPalette::Disabled; - o.backgroundColor = opt.palette.color(cg, (opt.state & QStyle::State_Selected) - ? QPalette::Highlight : QPalette::Window); - if (const QWidget* widget = opt.widget) { - QStyle* style = widget->style() ? widget->style() : qApp->style(); - style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, widget); - } - } + ? (opt.state & QStyle::State_Active) + ? QPalette::Active + : QPalette::Inactive + : QPalette::Disabled; + if(opt.state & QStyle::State_Selected) { + if(!opt.widget) { + painter->fillRect(selRect, opt.palette.highlight()); + } + painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); + } + else { + painter->setPen(opt.palette.color(cg, QPalette::Text)); + } + + if(opt.state & QStyle::State_Selected || opt.state & QStyle::State_MouseOver) { + if(const QWidget* widget = opt.widget) { // let the style engine do it + QStyle* style = widget->style() ? widget->style() : qApp->style(); + QStyleOptionViewItem o(opt); + o.text = QString(); + o.rect = selRect.toAlignedRect().intersected(opt.rect); // due to clipping and rounding, we might lose 1px + o.showDecorationSelected = true; + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &o, painter, widget); + } + } + + // draw shadow for text if the item is not selected and a shadow color is set + if(!(opt.state & QStyle::State_Selected) && shadowColor_.isValid()) { + QPen prevPen = painter->pen(); + painter->setPen(QPen(shadowColor_)); + for(int i = 0; i < visibleLines; ++i) { + QTextLine line = layout.lineAt(i); + if(i == (visibleLines - 1) && !elidedText.isEmpty()) { // the last line, draw elided text + QPointF pos(boundRect.x() + line.position().x() + 1, boundRect.y() + line.y() + line.ascent() + 1); + painter->drawText(pos, elidedText); + } + else { + line.draw(painter, textRect.topLeft() + QPointF(1, 1)); + } + } + painter->setPen(prevPen); + } + + // draw text + for(int i = 0; i < visibleLines; ++i) { + QTextLine line = layout.lineAt(i); + if(i == (visibleLines - 1) && !elidedText.isEmpty()) { // the last line, draw elided text + QPointF pos(boundRect.x() + line.position().x(), boundRect.y() + line.y() + line.ascent()); + painter->drawText(pos, elidedText); + } + else { + line.draw(painter, textRect.topLeft()); + } + } + + if(opt.state & QStyle::State_HasFocus) { + // draw focus rect + QStyleOptionFocusRect o; + o.QStyleOption::operator=(opt); + o.rect = selRect.toRect(); // subElementRect(SE_ItemViewItemFocusRect, vopt, widget); + o.state |= QStyle::State_KeyboardFocusChange; + o.state |= QStyle::State_Item; + QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled) + ? QPalette::Normal : QPalette::Disabled; + o.backgroundColor = opt.palette.color(cg, (opt.state & QStyle::State_Selected) + ? QPalette::Highlight : QPalette::Window); + if(const QWidget* widget = opt.widget) { + QStyle* style = widget->style() ? widget->style() : qApp->style(); + style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, widget); + } + } +} + +/* + * The following methods are for inline renaming. + */ + +QWidget* FolderItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { + hasEditor_ = true; + if (option.decorationPosition == QStyleOptionViewItem::Top + || option.decorationPosition == QStyleOptionViewItem::Bottom) + { + // in icon view, we use QTextEdit as the editor (and not QPlainTextEdit + // because the latter always shows an empty space at the bottom) + QTextEdit *textEdit = new QTextEdit(parent); + textEdit->setAcceptRichText(false); + + // Since the text color on desktop is inherited from desktop foreground color, + // it may not be suitable. So, we reset it by using the app palette. + QPalette p = textEdit->palette(); + p.setColor(QPalette::Text, qApp->palette().text().color()); + textEdit->setPalette(p); + + textEdit->ensureCursorVisible(); + textEdit->setFocusPolicy(Qt::StrongFocus); + textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + textEdit->setContentsMargins(0, 0, 0, 0); + return textEdit; + } + else { + // return the default line-edit in compact view + return QStyledItemDelegate::createEditor(parent, option, index); + } +} + +void FolderItemDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const { + if (!index.isValid()) { + return; + } + const QString currentName = index.data(Qt::EditRole).toString(); + + if (QTextEdit* textEdit = qobject_cast(editor)) { + textEdit->setPlainText(currentName); + textEdit->setUndoRedoEnabled(false); + textEdit->setAlignment(Qt::AlignCenter); + textEdit->setUndoRedoEnabled(true); + // select text appropriately + QTextCursor cur = textEdit->textCursor(); + int end; + if (index.data(Fm::FolderModel::FileIsDirRole).toBool() || !currentName.contains(".")) { + end = currentName.size(); + } + else { + end = currentName.lastIndexOf("."); + } + cur.setPosition(end, QTextCursor::KeepAnchor); + textEdit->setTextCursor(cur); + } + else if (QLineEdit* lineEdit = qobject_cast(editor)) { + lineEdit->setText(currentName); + if (!index.data(Fm::FolderModel::FileIsDirRole).toBool() && currentName.contains(".")) + { + /* Qt will call QLineEdit::selectAll() after calling setEditorData() in + qabstractitemview.cpp -> QAbstractItemViewPrivate::editor(). Therefore, + we cannot select a part of the text in the usual way here. */ + QTimer::singleShot(0, [lineEdit]() { + int length = lineEdit->text().lastIndexOf("."); + lineEdit->setSelection(0, length); + }); + } + } +} + +bool FolderItemDelegate::eventFilter(QObject* object, QEvent* event) { + QWidget *editor = qobject_cast(object); + if (editor && event->type() == QEvent::KeyPress) { + int k = static_cast(event)->key(); + if (k == Qt::Key_Return || k == Qt::Key_Enter) { + Q_EMIT QAbstractItemDelegate::commitData(editor); + Q_EMIT QAbstractItemDelegate::closeEditor(editor, QAbstractItemDelegate::NoHint); + return true; + } + } + return QStyledItemDelegate::eventFilter(object, event); +} + +void FolderItemDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const { + if (option.decorationPosition == QStyleOptionViewItem::Top + || option.decorationPosition == QStyleOptionViewItem::Bottom) { + // give all of the available space to the editor + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + opt.decorationAlignment = Qt::AlignHCenter|Qt::AlignTop; + opt.displayAlignment = Qt::AlignTop|Qt::AlignHCenter; + QRect textRect(opt.rect.x(), + opt.rect.y() + margins_.height() + option.decorationSize.height(), + itemSize_.width(), + itemSize_.height() - margins_.height() - option.decorationSize.height()); + int frame = editor->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option, editor); + editor->setGeometry(textRect.adjusted(-frame, -frame, frame, frame)); + } + else { + // use the default editor geometry in compact view + QStyledItemDelegate::updateEditorGeometry(editor, option, index); + } } diff --git a/src/folderitemdelegate.h b/src/folderitemdelegate.h index 9bf06a7..1886e39 100644 --- a/src/folderitemdelegate.h +++ b/src/folderitemdelegate.h @@ -23,53 +23,100 @@ #include "libfmqtglobals.h" #include -#include +class QAbstractItemView; namespace Fm { class LIBFM_QT_API FolderItemDelegate : public QStyledItemDelegate { - Q_OBJECT + Q_OBJECT public: - explicit FolderItemDelegate(QAbstractItemView* view, QObject* parent = nullptr); - virtual ~FolderItemDelegate(); + explicit FolderItemDelegate(QAbstractItemView* view, QObject* parent = nullptr); - inline void setGridSize(QSize size) { - gridSize_ = size; - } + virtual ~FolderItemDelegate(); - inline QSize gridSize() const { - return gridSize_; - } + inline void setItemSize(QSize size) { + itemSize_ = size; + } - int fileInfoRole() { - return fileInfoRole_; - } + inline QSize itemSize() const { + return itemSize_; + } - void setFileInfoRole(int role) { - fileInfoRole_ = role; - } + inline void setIconSize(QSize size) { + iconSize_ = size; + } - int fmIconRole() { - return fmIconRole_; - } + inline QSize iconSize() const { + return iconSize_; + } - void setFmIconRole(int role) { - fmIconRole_ = role; - } + int fileInfoRole() { + return fileInfoRole_; + } - virtual QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const; - virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + void setFileInfoRole(int role) { + fileInfoRole_ = role; + } + + int iconInfoRole() { + return iconInfoRole_; + } + + void setIconInfoRole(int role) { + iconInfoRole_ = role; + } + + // only support vertical layout (icon view mode: text below icon) + void setShadowColor(const QColor& shadowColor) { + shadowColor_ = shadowColor; + } + + // only support vertical layout (icon view mode: text below icon) + const QColor& shadowColor() const { + return shadowColor_; + } + + // only support vertical layout (icon view mode: text below icon) + void setMargins(QSize margins) { + margins_ = margins.expandedTo(QSize(0, 0)); + } + + QSize getMargins() const { + return margins_; + } + + bool hasEditor() const { + return hasEditor_; + } + + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; + + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + + virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const; + + virtual void setEditorData(QWidget* editor, const QModelIndex& index) const; + + virtual bool eventFilter(QObject* object, QEvent* event); + + virtual void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const; + + QSize iconViewTextSize(const QModelIndex& index) const; private: - void drawText(QPainter* painter, QStyleOptionViewItem& opt, QRectF& textRect) const; - static QIcon::Mode iconModeFromState(QStyle::State state); + void drawText(QPainter* painter, QStyleOptionViewItem& opt, QRectF& textRect) const; + + static QIcon::Mode iconModeFromState(QStyle::State state); private: - QAbstractItemView* view_; - QIcon symlinkIcon_; - QSize gridSize_; - int fileInfoRole_; - int fmIconRole_; + QIcon symlinkIcon_; + QSize iconSize_; + QSize itemSize_; + int fileInfoRole_; + int iconInfoRole_; + QColor shadowColor_; + QSize margins_; + mutable bool hasEditor_; }; } diff --git a/src/foldermenu.cpp b/src/foldermenu.cpp index a5fb5b7..1a87dd0 100644 --- a/src/foldermenu.cpp +++ b/src/foldermenu.cpp @@ -25,277 +25,278 @@ #include "folderview.h" #include "utilities.h" #include // for memset -#ifdef CUSTOM_ACTIONS +#include #include "customaction_p.h" +#include "customactions/fileaction.h" #include -#endif namespace Fm { FolderMenu::FolderMenu(FolderView* view, QWidget* parent): - QMenu(parent), - view_(view) { - - ProxyFolderModel* model = view_->model(); - - createAction_ = new QAction(tr("Create &New"), this); - addAction(createAction_); - - createAction_->setMenu(new CreateNewMenu(view_, view_->path(), this)); - - separator1_ = addSeparator(); - - pasteAction_ = new QAction(QIcon::fromTheme("edit-paste"), tr("&Paste"), this); - addAction(pasteAction_); - connect(pasteAction_, &QAction::triggered, this, &FolderMenu::onPasteActionTriggered); - - separator2_ = addSeparator(); - - selectAllAction_ = new QAction(tr("Select &All"), this); - addAction(selectAllAction_); - connect(selectAllAction_, &QAction::triggered, this, &FolderMenu::onSelectAllActionTriggered); - - invertSelectionAction_ = new QAction(tr("Invert Selection"), this); - addAction(invertSelectionAction_); - connect(invertSelectionAction_, &QAction::triggered, this, &FolderMenu::onInvertSelectionActionTriggered); - - separator3_ = addSeparator(); - - sortAction_ = new QAction(tr("Sorting"), this); - addAction(sortAction_); - createSortMenu(); - sortAction_->setMenu(sortMenu_); - - showHiddenAction_ = new QAction(tr("Show Hidden"), this); - addAction(showHiddenAction_); - showHiddenAction_->setCheckable(true); - showHiddenAction_->setChecked(model->showHidden()); - connect(showHiddenAction_, &QAction::triggered, this, &FolderMenu::onShowHiddenActionTriggered); - -#ifdef CUSTOM_ACTIONS - FmFileInfo* folderInfo = view_->folderInfo(); - if(folderInfo) { - GList *single_list = NULL; - single_list = g_list_prepend(single_list, (GList*)folderInfo); - GList* items = fm_get_actions_for_files(single_list); - if(items) { - GList* l; - for(l=items; l; l=l->next) { - FmFileActionItem* item = FM_FILE_ACTION_ITEM(l->data); - if(l == items && item - && !(fm_file_action_item_is_action(item) - && !(fm_file_action_item_get_target(item) & FM_FILE_ACTION_TARGET_CONTEXT))) { - addSeparator(); // before all custom actions + QMenu(parent), + view_(view) { + + ProxyFolderModel* model = view_->model(); + + createAction_ = new QAction(tr("Create &New"), this); + addAction(createAction_); + + createAction_->setMenu(new CreateNewMenu(view_, view_->path(), this)); + + separator1_ = addSeparator(); + + pasteAction_ = new QAction(QIcon::fromTheme("edit-paste"), tr("&Paste"), this); + addAction(pasteAction_); + connect(pasteAction_, &QAction::triggered, this, &FolderMenu::onPasteActionTriggered); + + separator2_ = addSeparator(); + + selectAllAction_ = new QAction(tr("Select &All"), this); + addAction(selectAllAction_); + connect(selectAllAction_, &QAction::triggered, this, &FolderMenu::onSelectAllActionTriggered); + + invertSelectionAction_ = new QAction(tr("Invert Selection"), this); + addAction(invertSelectionAction_); + connect(invertSelectionAction_, &QAction::triggered, this, &FolderMenu::onInvertSelectionActionTriggered); + + separator3_ = addSeparator(); + + sortAction_ = new QAction(tr("Sorting"), this); + addAction(sortAction_); + createSortMenu(); + sortAction_->setMenu(sortMenu_); + + showHiddenAction_ = new QAction(tr("Show Hidden"), this); + addAction(showHiddenAction_); + showHiddenAction_->setCheckable(true); + showHiddenAction_->setChecked(model->showHidden()); + connect(showHiddenAction_, &QAction::triggered, this, &FolderMenu::onShowHiddenActionTriggered); + + auto folderInfo = view_->folderInfo(); + if(folderInfo) { // should never be null (see FolderView::onFileClicked) + // DES-EMA custom actions integration + FileInfoList files; + files.push_back(folderInfo); + auto custom_actions = FileActionItem::get_actions_for_files(files); + for(auto& item: custom_actions) { + if(item && !(item->get_target() & FILE_ACTION_TARGET_CONTEXT)) { + continue; // this item is not for context menu + } + if(item == custom_actions.front() && item && !item->is_action()) { + addSeparator(); // before all custom actions + } + addCustomActionItem(this, item); } - addCustomActionItem(this, item); - } + + // disable paste acton if it can't be used + pasteAction_->setEnabled(folderInfo->isWritable()); } - g_list_foreach(items, (GFunc)fm_file_action_item_unref, NULL); - g_list_free(items); - } -#endif - separator4_ = addSeparator(); + separator4_ = addSeparator(); - propertiesAction_ = new QAction(tr("Folder Pr&operties"), this); - addAction(propertiesAction_); - connect(propertiesAction_, &QAction::triggered, this, &FolderMenu::onPropertiesActionTriggered); + propertiesAction_ = new QAction(tr("Folder Pr&operties"), this); + addAction(propertiesAction_); + connect(propertiesAction_, &QAction::triggered, this, &FolderMenu::onPropertiesActionTriggered); } FolderMenu::~FolderMenu() { } -#ifdef CUSTOM_ACTIONS -void FolderMenu::addCustomActionItem(QMenu* menu, FmFileActionItem* item) { - if(!item) return; - if(fm_file_action_item_is_action(item) && !(fm_file_action_item_get_target(item) & FM_FILE_ACTION_TARGET_CONTEXT)) - return; - - CustomAction* action = new CustomAction(item, menu); - menu->addAction(action); - if(fm_file_action_item_is_menu(item)) { - GList* subitems = fm_file_action_item_get_sub_items(item); - if (subitems != NULL) { - QMenu* submenu = new QMenu(menu); - for(GList* l = subitems; l; l = l->next) { - FmFileActionItem* subitem = FM_FILE_ACTION_ITEM(l->data); - addCustomActionItem(submenu, subitem); - } - action->setMenu(submenu); +void FolderMenu::addCustomActionItem(QMenu* menu, std::shared_ptr item) { + if(!item) { + return; + } + if(item->is_action() && !(item->get_target() & FILE_ACTION_TARGET_CONTEXT)) { + return; + } + + CustomAction* action = new CustomAction(item, menu); + menu->addAction(action); + if(item->is_menu()) { + auto& subitems = item->get_sub_items(); + if(!subitems.empty()) { + QMenu* submenu = new QMenu(menu); + for(auto& subitem: subitems) { + addCustomActionItem(submenu, subitem); + } + action->setMenu(submenu); + } + } + else if(item->is_action()) { + connect(action, &QAction::triggered, this, &FolderMenu::onCustomActionTrigerred); } - } - else if(fm_file_action_item_is_action(item)) { - connect(action, &QAction::triggered, this, &FolderMenu::onCustomActionTrigerred); - } } void FolderMenu::onCustomActionTrigerred() { - CustomAction* action = static_cast(sender()); - FmFileActionItem* item = action->item(); - - FmFileInfo* folderInfo = view_->folderInfo(); - if(folderInfo) { - GList *single_list = NULL; - single_list = g_list_prepend(single_list, (GList*)folderInfo); - char* output = NULL; - fm_file_action_item_launch(item, NULL, single_list, &output); - if(output) { - QMessageBox::information(this, tr("Output"), QString::fromUtf8(output)); - g_free(output); + CustomAction* action = static_cast(sender()); + auto& item = action->item(); + auto folderInfo = view_->folderInfo(); + if(folderInfo) { + CStrPtr output; + FileInfoList file_list; + file_list.push_back(folderInfo); + item->launch(nullptr, file_list, output); + if(output) { + QMessageBox::information(this, tr("Output"), output.get()); + } } - } } -#endif void FolderMenu::addSortMenuItem(QString title, int id) { - QAction* action = new QAction(title, this); - sortMenu_->addAction(action); - action->setCheckable(true); - sortActionGroup_->addAction(action); - connect(action, &QAction::triggered, this, &FolderMenu::onSortActionTriggered); - sortActions_[id] = action; + QAction* action = new QAction(title, this); + sortMenu_->addAction(action); + action->setCheckable(true); + sortActionGroup_->addAction(action); + connect(action, &QAction::triggered, this, &FolderMenu::onSortActionTriggered); + sortActions_[id] = action; } void FolderMenu::createSortMenu() { - ProxyFolderModel* model = view_->model(); + ProxyFolderModel* model = view_->model(); - sortMenu_ = new QMenu(this); - sortActionGroup_ = new QActionGroup(sortMenu_); - sortActionGroup_->setExclusive(true); + sortMenu_ = new QMenu(this); + sortActionGroup_ = new QActionGroup(sortMenu_); + sortActionGroup_->setExclusive(true); - std::memset(sortActions_, 0, sizeof(sortActions_)); + std::memset(sortActions_, 0, sizeof(sortActions_)); - addSortMenuItem(tr("By File Name"), FolderModel::ColumnFileName); - addSortMenuItem(tr("By Modification Time"), FolderModel::ColumnFileMTime); - addSortMenuItem(tr("By File Size"), FolderModel::ColumnFileSize); - addSortMenuItem(tr("By File Type"), FolderModel::ColumnFileType); - addSortMenuItem(tr("By File Owner"), FolderModel::ColumnFileOwner); + addSortMenuItem(tr("By File Name"), FolderModel::ColumnFileName); + addSortMenuItem(tr("By Modification Time"), FolderModel::ColumnFileMTime); + addSortMenuItem(tr("By File Size"), FolderModel::ColumnFileSize); + addSortMenuItem(tr("By File Type"), FolderModel::ColumnFileType); + addSortMenuItem(tr("By File Owner"), FolderModel::ColumnFileOwner); - int col = model->sortColumn(); + int col = model->sortColumn(); - if(col >= 0 && col < FolderModel::NumOfColumns) { - sortActions_[col]->setChecked(true);; - } + if(col >= 0 && col < FolderModel::NumOfColumns) { + sortActions_[col]->setChecked(true);; + } - sortMenu_->addSeparator(); + sortMenu_->addSeparator(); - QActionGroup* group = new QActionGroup(this); - group->setExclusive(true); - actionAscending_ = new QAction(tr("Ascending"), this); - actionAscending_->setCheckable(true); - sortMenu_->addAction(actionAscending_); - group->addAction(actionAscending_); + QActionGroup* group = new QActionGroup(this); + group->setExclusive(true); + actionAscending_ = new QAction(tr("Ascending"), this); + actionAscending_->setCheckable(true); + sortMenu_->addAction(actionAscending_); + group->addAction(actionAscending_); - actionDescending_ = new QAction(tr("Descending"), this); - actionDescending_->setCheckable(true); - sortMenu_->addAction(actionDescending_); - group->addAction(actionDescending_); + actionDescending_ = new QAction(tr("Descending"), this); + actionDescending_->setCheckable(true); + sortMenu_->addAction(actionDescending_); + group->addAction(actionDescending_); - if(model->sortOrder() == Qt::AscendingOrder) - actionAscending_->setChecked(true); - else - actionDescending_->setChecked(true); + if(model->sortOrder() == Qt::AscendingOrder) { + actionAscending_->setChecked(true); + } + else { + actionDescending_->setChecked(true); + } - connect(actionAscending_, &QAction::triggered, this, &FolderMenu::onSortOrderActionTriggered); - connect(actionDescending_, &QAction::triggered, this, &FolderMenu::onSortOrderActionTriggered); + connect(actionAscending_, &QAction::triggered, this, &FolderMenu::onSortOrderActionTriggered); + connect(actionDescending_, &QAction::triggered, this, &FolderMenu::onSortOrderActionTriggered); - sortMenu_->addSeparator(); + sortMenu_->addSeparator(); - QAction* actionFolderFirst = new QAction(tr("Folder First"), this); - sortMenu_->addAction(actionFolderFirst); - actionFolderFirst->setCheckable(true); + QAction* actionFolderFirst = new QAction(tr("Folder First"), this); + sortMenu_->addAction(actionFolderFirst); + actionFolderFirst->setCheckable(true); - if(model->folderFirst()) - actionFolderFirst->setChecked(true); + if(model->folderFirst()) { + actionFolderFirst->setChecked(true); + } - connect(actionFolderFirst, &QAction::triggered, this, &FolderMenu::onFolderFirstActionTriggered); + connect(actionFolderFirst, &QAction::triggered, this, &FolderMenu::onFolderFirstActionTriggered); - QAction* actionCaseSensitive = new QAction(tr("Case Sensitive"), this); - sortMenu_->addAction(actionCaseSensitive); - actionCaseSensitive->setCheckable(true); + QAction* actionCaseSensitive = new QAction(tr("Case Sensitive"), this); + sortMenu_->addAction(actionCaseSensitive); + actionCaseSensitive->setCheckable(true); - if(model->sortCaseSensitivity() == Qt::CaseSensitive) - actionCaseSensitive->setChecked(true); + if(model->sortCaseSensitivity() == Qt::CaseSensitive) { + actionCaseSensitive->setChecked(true); + } - connect(actionCaseSensitive, &QAction::triggered, this, &FolderMenu::onCaseSensitiveActionTriggered); + connect(actionCaseSensitive, &QAction::triggered, this, &FolderMenu::onCaseSensitiveActionTriggered); } void FolderMenu::onPasteActionTriggered() { - FmPath* folderPath = view_->path(); - - if(folderPath) - pasteFilesFromClipboard(folderPath); + auto folderPath = view_->path(); + if(folderPath) { + pasteFilesFromClipboard(folderPath); + } } void FolderMenu::onSelectAllActionTriggered() { - view_->selectAll(); + view_->selectAll(); } void FolderMenu::onInvertSelectionActionTriggered() { - view_->invertSelection(); + view_->invertSelection(); } -void FolderMenu::onSortActionTriggered(bool checked) { - ProxyFolderModel* model = view_->model(); +void FolderMenu::onSortActionTriggered(bool /*checked*/) { + ProxyFolderModel* model = view_->model(); - if(model) { - QAction* action = static_cast(sender()); + if(model) { + QAction* action = static_cast(sender()); - for(int col = 0; col < FolderModel::NumOfColumns; ++col) { - if(action == sortActions_[col]) { - model->sort(col, model->sortOrder()); - break; - } + for(int col = 0; col < FolderModel::NumOfColumns; ++col) { + if(action == sortActions_[col]) { + model->sort(col, model->sortOrder()); + break; + } + } } - } } -void FolderMenu::onSortOrderActionTriggered(bool checked) { - ProxyFolderModel* model = view_->model(); +void FolderMenu::onSortOrderActionTriggered(bool /*checked*/) { + ProxyFolderModel* model = view_->model(); - if(model) { - QAction* action = static_cast(sender()); - Qt::SortOrder order; + if(model) { + QAction* action = static_cast(sender()); + Qt::SortOrder order; - if(action == actionAscending_) - order = Qt::AscendingOrder; - else - order = Qt::DescendingOrder; + if(action == actionAscending_) { + order = Qt::AscendingOrder; + } + else { + order = Qt::DescendingOrder; + } - model->sort(model->sortColumn(), order); - } + model->sort(model->sortColumn(), order); + } } void FolderMenu::onShowHiddenActionTriggered(bool checked) { - ProxyFolderModel* model = view_->model(); + ProxyFolderModel* model = view_->model(); - if(model) { - qDebug("show hidden: %d", checked); - model->setShowHidden(checked); - } + if(model) { + qDebug("show hidden: %d", checked); + model->setShowHidden(checked); + } } void FolderMenu::onCaseSensitiveActionTriggered(bool checked) { - ProxyFolderModel* model = view_->model(); + ProxyFolderModel* model = view_->model(); - if(model) { - model->setSortCaseSensitivity(checked ? Qt::CaseSensitive : Qt::CaseInsensitive); - } + if(model) { + model->setSortCaseSensitivity(checked ? Qt::CaseSensitive : Qt::CaseInsensitive); + } } void FolderMenu::onFolderFirstActionTriggered(bool checked) { - ProxyFolderModel* model = view_->model(); + ProxyFolderModel* model = view_->model(); - if(model) { - model->setFolderFirst(checked); - } + if(model) { + model->setFolderFirst(checked); + } } void FolderMenu::onPropertiesActionTriggered() { - FmFileInfo* folderInfo = view_->folderInfo(); - - if(folderInfo) - FilePropsDialog::showForFile(folderInfo); + auto folderInfo = view_->folderInfo(); + if(folderInfo) { + FilePropsDialog::showForFile(folderInfo); + } } } // namespace Fm diff --git a/src/foldermenu.h b/src/foldermenu.h index 8308153..70d1e25 100644 --- a/src/foldermenu.h +++ b/src/foldermenu.h @@ -25,112 +25,106 @@ #include #include #include "foldermodel.h" -#ifdef CUSTOM_ACTIONS -#include -#endif class QAction; namespace Fm { class FolderView; +class FileActionItem; class LIBFM_QT_API FolderMenu : public QMenu { -Q_OBJECT + Q_OBJECT public: - explicit FolderMenu(FolderView* view, QWidget* parent = 0); - virtual ~FolderMenu(); + explicit FolderMenu(FolderView* view, QWidget* parent = 0); + virtual ~FolderMenu(); - QAction* createAction() { - return createAction_; - } + QAction* createAction() { + return createAction_; + } - QAction* separator1() { - return separator1_; - } + QAction* separator1() { + return separator1_; + } - QAction* pasteAction() { - return pasteAction_; - } + QAction* pasteAction() { + return pasteAction_; + } - QAction* separator2() { - return separator2_; - } + QAction* separator2() { + return separator2_; + } - QAction* selectAllAction() { - return selectAllAction_; - } + QAction* selectAllAction() { + return selectAllAction_; + } - QAction* invertSelectionAction() { - return invertSelectionAction_; - } + QAction* invertSelectionAction() { + return invertSelectionAction_; + } - QAction* separator3() { - return separator3_; - } + QAction* separator3() { + return separator3_; + } - QAction* sortAction() { - return sortAction_; - } + QAction* sortAction() { + return sortAction_; + } - QAction* showHiddenAction() { - return showHiddenAction_; - } + QAction* showHiddenAction() { + return showHiddenAction_; + } - QAction* separator4() { - return separator4_; - } + QAction* separator4() { + return separator4_; + } - QAction* propertiesAction() { - return propertiesAction_; - } + QAction* propertiesAction() { + return propertiesAction_; + } - FolderView* view() { - return view_; - } + FolderView* view() { + return view_; + } protected: -#ifdef CUSTOM_ACTIONS - void addCustomActionItem(QMenu* menu, FmFileActionItem* item); -#endif + void addCustomActionItem(QMenu* menu, std::shared_ptr item); protected Q_SLOTS: - void onPasteActionTriggered(); - void onSelectAllActionTriggered(); - void onInvertSelectionActionTriggered(); - void onSortActionTriggered(bool checked); - void onSortOrderActionTriggered(bool checked); - void onShowHiddenActionTriggered(bool checked); - void onCaseSensitiveActionTriggered(bool checked); - void onFolderFirstActionTriggered(bool checked); - void onPropertiesActionTriggered(); -#ifdef CUSTOM_ACTIONS - void onCustomActionTrigerred(); -#endif + void onPasteActionTriggered(); + void onSelectAllActionTriggered(); + void onInvertSelectionActionTriggered(); + void onSortActionTriggered(bool checked); + void onSortOrderActionTriggered(bool checked); + void onShowHiddenActionTriggered(bool checked); + void onCaseSensitiveActionTriggered(bool checked); + void onFolderFirstActionTriggered(bool checked); + void onPropertiesActionTriggered(); + void onCustomActionTrigerred(); private: - void createSortMenu(); - void addSortMenuItem(QString title, int id); + void createSortMenu(); + void addSortMenuItem(QString title, int id); private: - FolderView* view_; - QAction* createAction_; - QAction* separator1_; - QAction* pasteAction_; - QAction* separator2_; - QAction* selectAllAction_; - QAction* invertSelectionAction_; - QAction* separator3_; - QAction* sortAction_; - QActionGroup* sortActionGroup_; - QMenu* sortMenu_; - QAction* sortActions_[FolderModel::NumOfColumns]; - QAction* actionAscending_; - QAction* actionDescending_; - QAction* showHiddenAction_; - QAction* separator4_; - QAction* propertiesAction_; + FolderView* view_; + QAction* createAction_; + QAction* separator1_; + QAction* pasteAction_; + QAction* separator2_; + QAction* selectAllAction_; + QAction* invertSelectionAction_; + QAction* separator3_; + QAction* sortAction_; + QActionGroup* sortActionGroup_; + QMenu* sortMenu_; + QAction* sortActions_[FolderModel::NumOfColumns]; + QAction* actionAscending_; + QAction* actionDescending_; + QAction* showHiddenAction_; + QAction* separator4_; + QAction* propertiesAction_; }; } diff --git a/src/foldermodel.cpp b/src/foldermodel.cpp index 8f6f603..3437ca4 100644 --- a/src/foldermodel.cpp +++ b/src/foldermodel.cpp @@ -21,6 +21,7 @@ #include "foldermodel.h" #include "icontheme.h" #include +#include #include #include #include @@ -28,533 +29,517 @@ #include #include #include +#include #include "utilities.h" #include "fileoperation.h" -#include "thumbnailloader.h" namespace Fm { -FolderModel::FolderModel() : - folder_(nullptr) { -/* - ColumnIcon, - ColumnName, - ColumnFileType, - ColumnMTime, - NumOfColumns -*/ - thumbnailRefCounts.reserve(4); - - // reload all icons when the icon theme is changed - connect(IconTheme::instance(), &IconTheme::changed, this, &FolderModel::updateIcons); +FolderModel::FolderModel(): + hasPendingThumbnailHandler_{false} { } FolderModel::~FolderModel() { - qDebug("delete FolderModel"); - - if(folder_) - setFolder(nullptr); - - // if the thumbnail requests list is not empty, cancel them - if(!thumbnailResults.empty()) { - Q_FOREACH(FmThumbnailLoader* res, thumbnailResults) { - ThumbnailLoader::cancel(res); - } - } -} - -void FolderModel::setFolder(FmFolder* new_folder) { - if(folder_) { - removeAll(); // remove old items - g_signal_handlers_disconnect_by_func(folder_, gpointer(onStartLoading), this); - g_signal_handlers_disconnect_by_func(folder_, gpointer(onFinishLoading), this); - g_signal_handlers_disconnect_by_func(folder_, gpointer(onFilesAdded), this); - g_signal_handlers_disconnect_by_func(folder_, gpointer(onFilesChanged), this); - g_signal_handlers_disconnect_by_func(folder_, gpointer(onFilesRemoved), this); - g_object_unref(folder_); - } - if(new_folder) { - folder_ = FM_FOLDER(g_object_ref(new_folder)); - g_signal_connect(folder_, "start-loading", G_CALLBACK(onStartLoading), this); - g_signal_connect(folder_, "finish-loading", G_CALLBACK(onFinishLoading), this); - g_signal_connect(folder_, "files-added", G_CALLBACK(onFilesAdded), this); - g_signal_connect(folder_, "files-changed", G_CALLBACK(onFilesChanged), this); - g_signal_connect(folder_, "files-removed", G_CALLBACK(onFilesRemoved), this); - // handle the case if the folder is already loaded - if(fm_folder_is_loaded(folder_)) - insertFiles(0, fm_folder_get_files(folder_)); - } - else - folder_ = nullptr; -} - -void FolderModel::onStartLoading(FmFolder* folder, gpointer user_data) { - FolderModel* model = static_cast(user_data); - // remove all items - model->removeAll(); -} - -void FolderModel::onFinishLoading(FmFolder* folder, gpointer user_data) { - Q_UNUSED(folder) - Q_UNUSED(user_data) -} - -void FolderModel::onFilesAdded(FmFolder* folder, GSList* files, gpointer user_data) { - FolderModel* model = static_cast(user_data); - int n_files = g_slist_length(files); - model->beginInsertRows(QModelIndex(), model->items.count(), model->items.count() + n_files - 1); - for(GSList* l = files; l; l = l->next) { - FmFileInfo* info = FM_FILE_INFO(l->data); - FolderModelItem item(info); -/* - if(fm_file_info_is_hidden(info)) { - model->hiddenItems.append(item); - continue; + qDebug("delete FolderModel"); + // if the thumbnail requests list is not empty, cancel them + for(auto job: pendingThumbnailJobs_) { + job->cancel(); } -*/ - model->items.append(item); - } - model->endInsertRows(); } -//static -void FolderModel::onFilesChanged(FmFolder* folder, GSList* files, gpointer user_data) { - FolderModel* model = static_cast(user_data); - for(GSList* l = files; l; l = l->next) { - FmFileInfo* info = FM_FILE_INFO(l->data); - int row; - QList::iterator it = model->findItemByFileInfo(info, &row); - if(it != model->items.end()) { - FolderModelItem& item = *it; - // try to update the item - item.displayName = QString::fromUtf8(fm_file_info_get_disp_name(info)); - item.updateIcon(); - item.thumbnails.clear(); - QModelIndex index = model->createIndex(row, 0, &item); - Q_EMIT model->dataChanged(index, index); - } - } -} - -//static -void FolderModel::onFilesRemoved(FmFolder* folder, GSList* files, gpointer user_data) { - FolderModel* model = static_cast(user_data); - for(GSList* l = files; l; l = l->next) { - FmFileInfo* info = FM_FILE_INFO(l->data); - const char* name = fm_file_info_get_name(info); - int row; - QList::iterator it = model->findItemByName(name, &row); - if(it != model->items.end()) { - model->beginRemoveRows(QModelIndex(), row, row); - model->items.erase(it); - model->endRemoveRows(); +void FolderModel::setFolder(const std::shared_ptr& new_folder) { + if(folder_) { + removeAll(); // remove old items + } + if(new_folder) { + folder_ = new_folder; + connect(folder_.get(), &Fm::Folder::startLoading, this, &FolderModel::onStartLoading); + connect(folder_.get(), &Fm::Folder::finishLoading, this, &FolderModel::onFinishLoading); + connect(folder_.get(), &Fm::Folder::filesAdded, this, &FolderModel::onFilesAdded); + connect(folder_.get(), &Fm::Folder::filesChanged, this, &FolderModel::onFilesChanged); + connect(folder_.get(), &Fm::Folder::filesRemoved, this, &FolderModel::onFilesRemoved); + // handle the case if the folder is already loaded + if(folder_->isLoaded()) { + insertFiles(0, folder_->files()); + } + } +} + +void FolderModel::onStartLoading() { + // remove all items + removeAll(); +} + +void FolderModel::onFinishLoading() { +} + +void FolderModel::onFilesAdded(const Fm::FileInfoList& files) { + int n_files = files.size(); + beginInsertRows(QModelIndex(), items.count(), items.count() + n_files - 1); + for(auto& info : files) { + FolderModelItem item(info); + /* + if(fm_file_info_is_hidden(info)) { + model->hiddenItems.append(item); + continue; + } + */ + items.append(item); + } + endInsertRows(); +} + +void FolderModel::onFilesChanged(std::vector& files) { + for(auto& change : files) { + int row; + auto& oldInfo = change.first; + auto& newInfo = change.second; + QList::iterator it = findItemByFileInfo(oldInfo.get(), &row); + if(it != items.end()) { + FolderModelItem& item = *it; + // try to update the item + item.info = newInfo; + item.thumbnails.clear(); + QModelIndex index = createIndex(row, 0, &item); + Q_EMIT dataChanged(index, index); + if(oldInfo->size() != newInfo->size()) { + Q_EMIT fileSizeChanged(index); + } + } + } +} + +void FolderModel::onFilesRemoved(const Fm::FileInfoList& files) { + for(auto& info : files) { + int row; + QList::iterator it = findItemByName(info->name().c_str(), &row); + if(it != items.end()) { + beginRemoveRows(QModelIndex(), row, row); + items.erase(it); + endRemoveRows(); + } + } +} + +void FolderModel::loadPendingThumbnails() { + hasPendingThumbnailHandler_ = false; + for(auto& item: thumbnailData_) { + if(!item.pendingThumbnails_.empty()) { + auto job = new Fm::ThumbnailJob(std::move(item.pendingThumbnails_), item.size_); + pendingThumbnailJobs_.push_back(job); + job->setAutoDelete(true); + connect(job, &Fm::ThumbnailJob::thumbnailLoaded, this, &FolderModel::onThumbnailLoaded, Qt::BlockingQueuedConnection); + connect(job, &Fm::ThumbnailJob::finished, this, &FolderModel::onThumbnailJobFinished, Qt::BlockingQueuedConnection); + Fm::ThumbnailJob::threadPool()->start(job); + } + } +} + +void FolderModel::queueLoadThumbnail(const std::shared_ptr& file, int size) { + auto it = std::find_if(thumbnailData_.begin(), thumbnailData_.end(), [size](ThumbnailData& item){return item.size_ == size;}); + if(it != thumbnailData_.end()) { + it->pendingThumbnails_.push_back(file); + if(!hasPendingThumbnailHandler_) { + QTimer::singleShot(0, this, &FolderModel::loadPendingThumbnails); + hasPendingThumbnailHandler_ = true; + } + } +} + +void FolderModel::insertFiles(int row, const Fm::FileInfoList& files) { + int n_files = files.size(); + beginInsertRows(QModelIndex(), row, row + n_files - 1); + for(auto& info : files) { + FolderModelItem item(info); + items.append(item); } - } + endInsertRows(); } -void FolderModel::insertFiles(int row, FmFileInfoList* files) { - int n_files = fm_file_info_list_get_length(files); - beginInsertRows(QModelIndex(), row, row + n_files - 1); - for(GList* l = fm_file_info_list_peek_head_link(files); l; l = l->next) { - FolderModelItem item(FM_FILE_INFO(l->data)); - items.append(item); - } - endInsertRows(); +void FolderModel::setCutFiles(const QItemSelection& selection) { + if(folder_) { + if(!selection.isEmpty()) { + auto cutFilesHashSet = std::make_shared(); + folder_->setCutFiles(cutFilesHashSet); + for(const auto& index : selection.indexes()) { + auto item = itemFromIndex(index); + item->bindCutFiles(cutFilesHashSet); + cutFilesHashSet->insert(item->info->path().hash()); + } + } + Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0)); + } } void FolderModel::removeAll() { - if(items.empty()) - return; - beginRemoveRows(QModelIndex(), 0, items.size() - 1); - items.clear(); - endRemoveRows(); + if(items.empty()) { + return; + } + beginRemoveRows(QModelIndex(), 0, items.size() - 1); + items.clear(); + endRemoveRows(); } -int FolderModel::rowCount(const QModelIndex & parent) const { - if(parent.isValid()) - return 0; - return items.size(); +int FolderModel::rowCount(const QModelIndex& parent) const { + if(parent.isValid()) { + return 0; + } + return items.size(); } -int FolderModel::columnCount (const QModelIndex & parent = QModelIndex()) const { - if(parent.isValid()) - return 0; - return NumOfColumns; +int FolderModel::columnCount(const QModelIndex& parent = QModelIndex()) const { + if(parent.isValid()) { + return 0; + } + return NumOfColumns; } FolderModelItem* FolderModel::itemFromIndex(const QModelIndex& index) const { - return reinterpret_cast(index.internalPointer()); + return reinterpret_cast(index.internalPointer()); } -FmFileInfo* FolderModel::fileInfoFromIndex(const QModelIndex& index) const { - FolderModelItem* item = itemFromIndex(index); - return item ? item->info : nullptr; +std::shared_ptr FolderModel::fileInfoFromIndex(const QModelIndex& index) const { + FolderModelItem* item = itemFromIndex(index); + return item ? item->info : nullptr; } -QVariant FolderModel::data(const QModelIndex & index, int role/* = Qt::DisplayRole*/) const { - if(!index.isValid() || index.row() > items.size() || index.column() >= NumOfColumns) { - return QVariant(); - } - FolderModelItem* item = itemFromIndex(index); - FmFileInfo* info = item->info; +QVariant FolderModel::data(const QModelIndex& index, int role/* = Qt::DisplayRole*/) const { + if(!index.isValid() || index.row() > items.size() || index.column() >= NumOfColumns) { + return QVariant(); + } + FolderModelItem* item = itemFromIndex(index); + auto info = item->info; + + bool isCut = false; + if(folder_ && Q_UNLIKELY(folder_->hasCutFiles())) { + isCut = item->isCut(); + } - switch(role) { + switch(role) { case Qt::ToolTipRole: - return QVariant(item->displayName); + return QVariant(item->displayName()); case Qt::DisplayRole: { - switch(index.column()) { - case ColumnFileName: { - return QVariant(item->displayName); - } - case ColumnFileType: { - FmMimeType* mime = fm_file_info_get_mime_type(info); - const char* desc = fm_mime_type_get_desc(mime); - return QString::fromUtf8(desc); - } - case ColumnFileMTime: { - const char* name = fm_file_info_get_disp_mtime(info); - return QString::fromUtf8(name); - } - case ColumnFileSize: { - const char* name = fm_file_info_get_disp_size(info); - return QString::fromUtf8(name); - } - case ColumnFileOwner: { - const char* name = fm_file_info_get_disp_owner(info); - return QString::fromUtf8(name); + switch(index.column()) { + case ColumnFileName: + return item->displayName(); + case ColumnFileType: + return QString(info->mimeType()->desc()); + case ColumnFileMTime: + return item->displayMtime(); + case ColumnFileSize: + return item->displaySize(); + case ColumnFileOwner: + return item->ownerName(); } - } + break; } case Qt::DecorationRole: { - if(index.column() == 0) { - // QPixmap pix = IconTheme::loadIcon(fm_file_info_get_icon(info), iconSize_); - return QVariant(item->icon); - // return QVariant(pix); - } - break; + if(index.column() == 0) { + return QVariant(item->icon(isCut)); + } + break; + } + case Qt::EditRole: { + if(index.column() == 0) { + return QString::fromStdString(info->name()); + } + break; } case FileInfoRole: - return qVariantFromValue((void*)info); - } - return QVariant(); + return QVariant::fromValue(info); + case FileIsDirRole: + return QVariant(info->isDir()); + case FileIsCutRole: + return isCut; + } + return QVariant(); } QVariant FolderModel::headerData(int section, Qt::Orientation orientation, int role/* = Qt::DisplayRole*/) const { - if(role == Qt::DisplayRole) { - if(orientation == Qt::Horizontal) { - QString title; - switch(section) { - case ColumnFileName: - title = tr("Name"); - break; - case ColumnFileType: - title = tr("Type"); - break; - case ColumnFileSize: - title = tr("Size"); - break; - case ColumnFileMTime: - title = tr("Modified"); - break; - case ColumnFileOwner: - title = tr("Owner"); - break; - } - return QVariant(title); + if(role == Qt::DisplayRole) { + if(orientation == Qt::Horizontal) { + QString title; + switch(section) { + case ColumnFileName: + title = tr("Name"); + break; + case ColumnFileType: + title = tr("Type"); + break; + case ColumnFileSize: + title = tr("Size"); + break; + case ColumnFileMTime: + title = tr("Modified"); + break; + case ColumnFileOwner: + title = tr("Owner"); + break; + } + return QVariant(title); + } } - } - return QVariant(); + return QVariant(); } -QModelIndex FolderModel::index(int row, int column, const QModelIndex & parent) const { - if(row <0 || row >= items.size() || column < 0 || column >= NumOfColumns) - return QModelIndex(); - const FolderModelItem& item = items.at(row); - return createIndex(row, column, (void*)&item); +QModelIndex FolderModel::index(int row, int column, const QModelIndex& /*parent*/) const { + if(row < 0 || row >= items.size() || column < 0 || column >= NumOfColumns) { + return QModelIndex(); + } + const FolderModelItem& item = items.at(row); + return createIndex(row, column, (void*)&item); } -QModelIndex FolderModel::parent(const QModelIndex & index) const { - return QModelIndex(); +QModelIndex FolderModel::parent(const QModelIndex& /*index*/) const { + return QModelIndex(); } Qt::ItemFlags FolderModel::flags(const QModelIndex& index) const { - // FIXME: should not return same flags unconditionally for all columns - Qt::ItemFlags flags; - if(index.isValid()) { - flags = Qt::ItemIsEnabled|Qt::ItemIsSelectable; - if(index.column() == ColumnFileName) - flags |= (Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled); - } - else { - flags = Qt::ItemIsDropEnabled; - } - return flags; + // FIXME: should not return same flags unconditionally for all columns + Qt::ItemFlags flags; + if(index.isValid()) { + flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + if(index.column() == ColumnFileName) { + flags |= (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled + | Qt::ItemIsEditable); // inline renaming); + } + } + else { + flags = Qt::ItemIsDropEnabled; + } + return flags; } // FIXME: this is very inefficient and should be replaced with a // more reasonable implementation later. -QList::iterator FolderModel::findItemByPath(FmPath* path, int* row) { - QList::iterator it = items.begin(); - int i = 0; - while(it != items.end()) { - FolderModelItem& item = *it; - FmPath* item_path = fm_file_info_get_path(item.info); - if(fm_path_equal(item_path, path)) { - *row = i; - return it; - } - ++it; - ++i; - } - return items.end(); +QList::iterator FolderModel::findItemByPath(const Fm::FilePath& path, int* row) { + QList::iterator it = items.begin(); + int i = 0; + while(it != items.end()) { + FolderModelItem& item = *it; + auto item_path = item.info->path(); + if(item_path == path) { + *row = i; + return it; + } + ++it; + ++i; + } + return items.end(); } // FIXME: this is very inefficient and should be replaced with a // more reasonable implementation later. QList::iterator FolderModel::findItemByName(const char* name, int* row) { - QList::iterator it = items.begin(); - int i = 0; - while(it != items.end()) { - FolderModelItem& item = *it; - const char* item_name = fm_file_info_get_name(item.info); - if(strcmp(name, item_name) == 0) { - *row = i; - return it; - } - ++it; - ++i; - } - return items.end(); -} - -QList< FolderModelItem >::iterator FolderModel::findItemByFileInfo(FmFileInfo* info, int* row) { - QList::iterator it = items.begin(); - int i = 0; - while(it != items.end()) { - FolderModelItem& item = *it; - if(item.info == info) { - *row = i; - return it; - } - ++it; - ++i; - } - return items.end(); + QList::iterator it = items.begin(); + int i = 0; + while(it != items.end()) { + FolderModelItem& item = *it; + if(item.info->name() == name) { + *row = i; + return it; + } + ++it; + ++i; + } + return items.end(); } -QStringList FolderModel::mimeTypes() const { - qDebug("FolderModel::mimeTypes"); - QStringList types = QAbstractItemModel::mimeTypes(); - // now types contains "application/x-qabstractitemmodeldatalist" +QList< FolderModelItem >::iterator FolderModel::findItemByFileInfo(const Fm::FileInfo* info, int* row) { + QList::iterator it = items.begin(); + int i = 0; + while(it != items.end()) { + FolderModelItem& item = *it; + if(item.info.get() == info) { + *row = i; + return it; + } + ++it; + ++i; + } + return items.end(); +} - // add support for freedesktop Xdnd direct save (XDS) protocol. - // http://www.freedesktop.org/wiki/Specifications/XDS/#index4h2 - // the real implementation is in FolderView::childDropEvent(). - types << "XdndDirectSave0"; - types << "text/uri-list"; - // types << "x-special/gnome-copied-files"; - return types; +QStringList FolderModel::mimeTypes() const { + qDebug("FolderModel::mimeTypes"); + QStringList types = QAbstractItemModel::mimeTypes(); + // now types contains "application/x-qabstractitemmodeldatalist" + + // add support for freedesktop Xdnd direct save (XDS) protocol. + // http://www.freedesktop.org/wiki/Specifications/XDS/#index4h2 + // the real implementation is in FolderView::childDropEvent(). + types << "XdndDirectSave0"; + types << "text/uri-list"; + // types << "x-special/gnome-copied-files"; + return types; } QMimeData* FolderModel::mimeData(const QModelIndexList& indexes) const { - QMimeData* data = QAbstractItemModel::mimeData(indexes); - qDebug("FolderModel::mimeData"); - // build a uri list - QByteArray urilist; - urilist.reserve(4096); - - for(const auto &index : indexes) { - FolderModelItem* item = itemFromIndex(index); - if(item && item->info) { - FmPath* path = fm_file_info_get_path(item->info); - if(path) { - char* uri = fm_path_to_uri(path); - urilist.append(uri); - urilist.append('\n'); - g_free(uri); - } + QMimeData* data = QAbstractItemModel::mimeData(indexes); + qDebug("FolderModel::mimeData"); + // build a uri list + QByteArray urilist; + urilist.reserve(4096); + + for(const auto& index : indexes) { + FolderModelItem* item = itemFromIndex(index); + if(item && item->info) { + auto path = item->info->path(); + if(path.isValid()) { + auto uri = path.uri(); + urilist.append(uri.get()); + urilist.append('\n'); + } + } } - } - data->setData("text/uri-list", urilist); + data->setData("text/uri-list", urilist); - return data; + return data; } bool FolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { - qDebug("FolderModel::dropMimeData"); - if(!folder_) - return false; - FmPath* destPath; - if(parent.isValid()) { // drop on an item - FmFileInfo* info; - if(row == -1 && column == -1) - info = fileInfoFromIndex(parent); - else { - QModelIndex itemIndex = parent.child(row, column); - info = fileInfoFromIndex(itemIndex); - } - if(info) - destPath = fm_file_info_get_path(info); - else - return false; - } - else { // drop on blank area of the folder - destPath = path(); - } - - // FIXME: should we put this in dropEvent handler of FolderView instead? - if(data->hasUrls()) { - qDebug("drop action: %d", action); - FmPathList* srcPaths = pathListFromQUrls(data->urls()); - switch(action) { - case Qt::CopyAction: - FileOperation::copyFiles(srcPaths, destPath); - break; - case Qt::MoveAction: - FileOperation::moveFiles(srcPaths, destPath); - break; - case Qt::LinkAction: - FileOperation::symlinkFiles(srcPaths, destPath); - default: - fm_path_list_unref(srcPaths); + qDebug("FolderModel::dropMimeData"); + if(!folder_ || !data) { return false; } - fm_path_list_unref(srcPaths); - return true; - } - else if(data->hasFormat("application/x-qabstractitemmodeldatalist")) { - return true; - } - return QAbstractListModel::dropMimeData(data, action, row, column, parent); + Fm::FilePath destPath; + if(parent.isValid()) { // drop on an item + std::shared_ptr info; + if(row == -1 && column == -1) { + info = fileInfoFromIndex(parent); + } + else { + QModelIndex itemIndex = parent.child(row, column); + info = fileInfoFromIndex(itemIndex); + } + if(info) { + destPath = info->path(); + } + else { + return false; + } + } + else { // drop on blank area of the folder + destPath = path(); + } + + // FIXME: should we put this in dropEvent handler of FolderView instead? + if(data->hasUrls()) { + qDebug("drop action: %d", action); + auto srcPaths = pathListFromQUrls(data->urls()); + switch(action) { + case Qt::CopyAction: + FileOperation::copyFiles(srcPaths, destPath); + break; + case Qt::MoveAction: + FileOperation::moveFiles(srcPaths, destPath); + break; + case Qt::LinkAction: + FileOperation::symlinkFiles(srcPaths, destPath); + default: + return false; + } + return true; + } + else if(data->hasFormat("application/x-qabstractitemmodeldatalist")) { + return true; + } + return QAbstractListModel::dropMimeData(data, action, row, column, parent); } Qt::DropActions FolderModel::supportedDropActions() const { - qDebug("FolderModel::supportedDropActions"); - return Qt::CopyAction|Qt::MoveAction|Qt::LinkAction; + qDebug("FolderModel::supportedDropActions"); + return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; } // ask the model to load thumbnails of the specified size void FolderModel::cacheThumbnails(const int size) { - QVector>::iterator it = thumbnailRefCounts.begin(); - while (it != thumbnailRefCounts.end()) { - if (it->first == size) { - ++it->second; - return; - } else ++it; - } - thumbnailRefCounts.append(QPair(size, 1)); + auto it = std::find_if(thumbnailData_.begin(), thumbnailData_.end(), [size](ThumbnailData& item){return item.size_ == size;}); + if(it != thumbnailData_.cend()) { + ++it->refCount_; + } + else { + thumbnailData_.push_front(ThumbnailData(size)); + } } // ask the model to free cached thumbnails of the specified size void FolderModel::releaseThumbnails(int size) { - QVector >::iterator it; - for(it = thumbnailRefCounts.begin(); it != thumbnailRefCounts.end(); ++it) { - if(it->first == size) { - break; - } - } - if(it != thumbnailRefCounts.end()) { - --it->second; - if(it->second == 0) { - thumbnailRefCounts.erase(it); - - // remove thumbnails that ara queued for loading from thumbnailResults - QLinkedList::iterator it; - for(it = thumbnailResults.begin(); it != thumbnailResults.end();) { - QLinkedList::iterator next = it + 1; - FmThumbnailLoader* res = *it; - if(ThumbnailLoader::size(res) == size) { - ThumbnailLoader::cancel(res); - thumbnailResults.erase(it); + auto prev = thumbnailData_.before_begin(); + for(auto it = thumbnailData_.begin(); it != thumbnailData_.end(); ++it) { + if(it->size_ == size) { + --it->refCount_; + if(it->refCount_ == 0) { + thumbnailData_.erase_after(prev); + } + + // remove all cached thumbnails of the specified size + QList::iterator itemIt; + for(itemIt = items.begin(); itemIt != items.end(); ++itemIt) { + FolderModelItem& item = *itemIt; + item.removeThumbnail(size); + } + break; } - it = next; - } - - // remove all cached thumbnails of the specified size - QList::iterator itemIt; - for(itemIt = items.begin(); itemIt != items.end(); ++itemIt) { - FolderModelItem& item = *itemIt; - item.removeThumbnail(size); - } - } - } -} - -void FolderModel::onThumbnailLoaded(FmThumbnailLoader* res, gpointer user_data) { - FolderModel* pThis = reinterpret_cast(user_data); - QLinkedList::iterator it; - for(it = pThis->thumbnailResults.begin(); it != pThis->thumbnailResults.end(); ++it) { - if(*it == res) { // the thumbnail result is in our list - pThis->thumbnailResults.erase(it); // remove it from the list - FmFileInfo* info = ThumbnailLoader::fileInfo(res); - int row = -1; - // find the model item this thumbnail belongs to - QList::iterator it = pThis->findItemByFileInfo(info, &row); - if(it != pThis->items.end()) { + prev = it; + } +} + +void FolderModel::onThumbnailJobFinished() { + Fm::ThumbnailJob* job = static_cast(sender()); + auto it = std::find(pendingThumbnailJobs_.cbegin(), pendingThumbnailJobs_.cend(), job); + if(it != pendingThumbnailJobs_.end()) { + pendingThumbnailJobs_.erase(it); + } +} + +void FolderModel::onThumbnailLoaded(const std::shared_ptr& file, int size, const QImage& image) { + // find the model item this thumbnail belongs to + int row; + QList::iterator it = findItemByFileInfo(file.get(), &row); + if(it != items.end()) { // the file is found in our model FolderModelItem& item = *it; - QModelIndex index = pThis->createIndex(row, 0, (void*)&item); + QModelIndex index = createIndex(row, 0, (void*)&item); // store the image in the folder model item. - int size = ThumbnailLoader::size(res); - QImage image = ThumbnailLoader::image(res); - FolderModelItem::Thumbnail* thumbnail = item.findThumbnail(size); + FolderModelItem::Thumbnail* thumbnail = item.findThumbnail(size, false); thumbnail->image = image; + thumbnail->transparent = false; // qDebug("thumbnail loaded for: %s, size: %d", item.displayName.toUtf8().constData(), size); - if(image.isNull()) - thumbnail->status = FolderModelItem::ThumbnailFailed; + if(image.isNull()) { + thumbnail->status = FolderModelItem::ThumbnailFailed; + } else { - thumbnail->status = FolderModelItem::ThumbnailLoaded; - // FIXME: due to bugs in Qt's QStyledItemDelegate, if the image width and height - // are not the same, painting errors will happen. It's quite unfortunate. - // Let's do some padding to make its width and height equals. - // This greatly decrease performance :-( - // Later if we can re-implement our own item delegate, this can be avoided. - QPixmap pixmap = QPixmap(size, size); - pixmap.fill(QColor(0, 0, 0, 0)); // fill the pixmap with transparent color (alpha:0) - QPainter painter(&pixmap); - int x = (size - image.width()) / 2; - int y = (size - image.height()) / 2; - painter.drawImage(QPoint(x, y), image); // draw the image to the pixmap at center. - // FIXME: should we cache QPixmap instead for performance reason? - thumbnail->image = pixmap.toImage(); // convert it back to image - - // tell the world that we have the thumbnail loaded - Q_EMIT pThis->thumbnailLoaded(index, size); + thumbnail->status = FolderModelItem::ThumbnailLoaded; + thumbnail->image = image; + + // tell the world that we have the thumbnail loaded + Q_EMIT thumbnailLoaded(index, size); } - } - break; } - } } // get a thumbnail of size at the index // if a thumbnail is not yet loaded, this will initiate loading of the thumbnail. QImage FolderModel::thumbnailFromIndex(const QModelIndex& index, int size) { - FolderModelItem* item = itemFromIndex(index); - if(item) { - FolderModelItem::Thumbnail* thumbnail = item->findThumbnail(size); - // qDebug("FolderModel::thumbnailFromIndex: %d, %s", thumbnail->status, item->displayName.toUtf8().constData()); - switch(thumbnail->status) { - case FolderModelItem::ThumbnailNotChecked: { - // load the thumbnail - FmThumbnailLoader* res = ThumbnailLoader::load(item->info, size, onThumbnailLoaded, this); - thumbnailResults.push_back(res); - thumbnail->status = FolderModelItem::ThumbnailLoading; - break; - } - case FolderModelItem::ThumbnailLoaded: - return thumbnail->image; - default:; + FolderModelItem* item = itemFromIndex(index); + if(item) { + FolderModelItem::Thumbnail* thumbnail = item->findThumbnail(size, item->isCut()); + // qDebug("FolderModel::thumbnailFromIndex: %d, %s", thumbnail->status, item->displayName.toUtf8().data()); + switch(thumbnail->status) { + case FolderModelItem::ThumbnailNotChecked: { + // load the thumbnail + queueLoadThumbnail(item->info, size); + thumbnail->status = FolderModelItem::ThumbnailLoading; + break; + } + case FolderModelItem::ThumbnailLoaded: + return thumbnail->image; + default: + ; + } } - } - return QImage(); -} - -void FolderModel::updateIcons() { - QList::iterator it = items.begin(); - for(;it != items.end(); ++it) { - (*it).updateIcon(); - } + return QImage(); } diff --git a/src/foldermodel.h b/src/foldermodel.h index 4ae0edb..3fd34c7 100644 --- a/src/foldermodel.h +++ b/src/foldermodel.h @@ -23,97 +23,121 @@ #include "libfmqtglobals.h" #include +#include #include #include #include #include -#include -#include -#include +#include +#include +#include #include "foldermodelitem.h" +#include "core/folder.h" +#include "core/thumbnailjob.h" + namespace Fm { class LIBFM_QT_API FolderModel : public QAbstractListModel { -Q_OBJECT + Q_OBJECT public: - enum Role { - FileInfoRole = Qt::UserRole - }; - - enum ColumnId { - ColumnFileName, - ColumnFileType, - ColumnFileSize, - ColumnFileMTime, - ColumnFileOwner, - NumOfColumns - }; + enum Role { + FileInfoRole = Qt::UserRole, + FileIsDirRole, + FileIsCutRole + }; + + enum ColumnId { + ColumnFileName, + ColumnFileType, + ColumnFileSize, + ColumnFileMTime, + ColumnFileOwner, + NumOfColumns + }; public: - FolderModel(); - virtual ~FolderModel(); + explicit FolderModel(); + virtual ~FolderModel(); + + const std::shared_ptr& folder() const { + return folder_; + } - FmFolder* folder() { - return folder_; - } - void setFolder(FmFolder* new_folder); + void setFolder(const std::shared_ptr& new_folder); - FmPath* path() { - return folder_ ? fm_folder_get_path(folder_) : NULL; - } + Fm::FilePath path() { + return folder_ ? folder_->path() : Fm::FilePath(); + } - int rowCount(const QModelIndex & parent = QModelIndex()) const; - int columnCount (const QModelIndex & parent) const; - QVariant data(const QModelIndex & index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role) const; - QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; - QModelIndex parent( const QModelIndex & index ) const; - // void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + int rowCount(const QModelIndex& parent = QModelIndex()) const; + int columnCount(const QModelIndex& parent) const; + QVariant data(const QModelIndex& index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex& index) const; + // void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); - Qt::ItemFlags flags(const QModelIndex & index) const; + Qt::ItemFlags flags(const QModelIndex& index) const; - virtual QStringList mimeTypes() const; - virtual QMimeData* mimeData(const QModelIndexList & indexes) const; - virtual Qt::DropActions supportedDropActions() const; - virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + virtual QStringList mimeTypes() const; + virtual QMimeData* mimeData(const QModelIndexList& indexes) const; + virtual Qt::DropActions supportedDropActions() const; + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); - FmFileInfo* fileInfoFromIndex(const QModelIndex& index) const; - FolderModelItem* itemFromIndex(const QModelIndex& index) const; - QImage thumbnailFromIndex(const QModelIndex& index, int size); + std::shared_ptr fileInfoFromIndex(const QModelIndex& index) const; + FolderModelItem* itemFromIndex(const QModelIndex& index) const; + QImage thumbnailFromIndex(const QModelIndex& index, int size); - void cacheThumbnails(int size); - void releaseThumbnails(int size); + void cacheThumbnails(int size); + void releaseThumbnails(int size); + + void setCutFiles(const QItemSelection& selection); Q_SIGNALS: - void thumbnailLoaded(const QModelIndex& index, int size); + void thumbnailLoaded(const QModelIndex& index, int size); + void fileSizeChanged(const QModelIndex& index); + +protected Q_SLOTS: -public Q_SLOTS: - void updateIcons(); + void onStartLoading(); + void onFinishLoading(); + void onFilesAdded(const Fm::FileInfoList& files); + void onFilesChanged(std::vector& files); + void onFilesRemoved(const Fm::FileInfoList& files); + + void onThumbnailLoaded(const std::shared_ptr& file, int size, const QImage& image); + void onThumbnailJobFinished(); + void loadPendingThumbnails(); protected: - static void onStartLoading(FmFolder* folder, gpointer user_data); - static void onFinishLoading(FmFolder* folder, gpointer user_data); - static void onFilesAdded(FmFolder* folder, GSList* files, gpointer user_data); - static void onFilesChanged(FmFolder* folder, GSList* files, gpointer user_data); - static void onFilesRemoved(FmFolder* folder, GSList* files, gpointer user_data); - static void onThumbnailLoaded(FmThumbnailLoader *res, gpointer user_data); - - void insertFiles(int row, FmFileInfoList* files); - void removeAll(); - QList::iterator findItemByPath(FmPath* path, int* row); - QList::iterator findItemByName(const char* name, int* row); - QList::iterator findItemByFileInfo(FmFileInfo* info, int* row); + void queueLoadThumbnail(const std::shared_ptr& file, int size); + void insertFiles(int row, const Fm::FileInfoList& files); + void removeAll(); + QList::iterator findItemByPath(const Fm::FilePath& path, int* row); + QList::iterator findItemByName(const char* name, int* row); + QList::iterator findItemByFileInfo(const Fm::FileInfo* info, int* row); private: - FmFolder* folder_; - // FIXME: should we use a hash table here so item lookup becomes much faster? - QList items; - // record what size of thumbnails we should cache in an array of pairs. - QVector > thumbnailRefCounts; - QLinkedList thumbnailResults; + struct ThumbnailData { + ThumbnailData(int size): + size_{size}, + refCount_{1} { + } + + int size_; + int refCount_; + Fm::FileInfoList pendingThumbnails_; + }; + + std::shared_ptr folder_; + QList items; + + bool hasPendingThumbnailHandler_; + std::vector pendingThumbnailJobs_; + std::forward_list thumbnailData_; }; } diff --git a/src/foldermodelitem.cpp b/src/foldermodelitem.cpp index ef1a24b..273fca1 100644 --- a/src/foldermodelitem.cpp +++ b/src/foldermodelitem.cpp @@ -19,78 +19,131 @@ #include "foldermodelitem.h" +#include +#include +#include "utilities.h" +#include "core/userinfocache.h" namespace Fm { -FolderModelItem::FolderModelItem(FmFileInfo* _info): - info(fm_file_info_ref(_info)) { - displayName = QString::fromUtf8(fm_file_info_get_disp_name(info)); - icon = IconTheme::icon(fm_file_info_get_icon(_info)); - thumbnails.reserve(2); +FolderModelItem::FolderModelItem(const std::shared_ptr& _info): + info{_info} { + thumbnails.reserve(2); } -FolderModelItem::FolderModelItem(const FolderModelItem& other) { - info = other.info ? fm_file_info_ref(other.info) : NULL; - displayName = QString::fromUtf8(fm_file_info_get_disp_name(info)); - icon = other.icon; - thumbnails = other.thumbnails; +FolderModelItem::FolderModelItem(const FolderModelItem& other): + info{other.info}, + thumbnails{other.thumbnails} { } FolderModelItem::~FolderModelItem() { - if(info) - fm_file_info_unref(info); +} + +QString FolderModelItem::ownerName() const { + QString name; + auto user = Fm::UserInfoCache::globalInstance()->userFromId(info->uid()); + if(user) { + name = user->realName(); + if(name.isEmpty()) { + name = user->name(); + } + } + return name; +} + +QString FolderModelItem::ownerGroup() const { + auto group = Fm::UserInfoCache::globalInstance()->groupFromId(info->gid()); + return group ? group->name() : QString(); +} + +const QString &FolderModelItem::displayMtime() const { + if(dispMtime_.isEmpty()) { + auto mtime = QDateTime::fromMSecsSinceEpoch(info->mtime() * 1000); + dispMtime_ = mtime.toString(Qt::SystemLocaleShortDate); + } + return dispMtime_; +} + +const QString& FolderModelItem::displaySize() const { + if(!info->isDir()) { + // FIXME: choose IEC or SI units + dispSize_ = Fm::formatFileSize(info->size(), false); + } + return dispSize_; +} + +bool FolderModelItem::isCut() const { + return !cutFilesHashSet_.expired() || info->isCut(); +} + +void FolderModelItem::bindCutFiles(const std::shared_ptr& cutFilesHashSet) { + cutFilesHashSet_ = cutFilesHashSet; } // find thumbnail of the specified size // The returned thumbnail item is temporary and short-lived // If you need to use the struct later, copy it to your own struct to keep it. -FolderModelItem::Thumbnail* FolderModelItem::findThumbnail(int size) { - QVector::iterator it; - for(it = thumbnails.begin(); it != thumbnails.end(); ++it) { - if(it->size == size) { // an image of the same size is found - return it; +FolderModelItem::Thumbnail* FolderModelItem::findThumbnail(int size, bool transparent) { + QVector::iterator it; + Thumbnail* transThumb = nullptr; + for(it = thumbnails.begin(); it != thumbnails.end(); ++it) { + if(it->size == size) { + if(it->status != ThumbnailLoaded) { + return it; + } + else { // it->status == ThumbnailLoaded + if(it->transparent == false && transparent == true + && size < 48 /* (dirty) needed only for 'compact' and 'details list' view */ ) { + transThumb = it; // save thumb to add transparency later + } + else { + return it; // an image of the same size and transparency is found + } + } + } } - } - if(it == thumbnails.end()) { - Thumbnail thumbnail; - thumbnail.status = ThumbnailNotChecked; - thumbnail.size = size; - thumbnails.append(thumbnail); - } - return &thumbnails.back(); -} + if(transThumb) { + QImage image(transThumb->image); -// remove cached thumbnail of the specified size -void FolderModelItem::removeThumbnail(int size) { - QVector::iterator it; - for(it = thumbnails.begin(); it != thumbnails.end(); ++it) { - if(it->size == size) { // an image of the same size is found - thumbnails.erase(it); - break; + if(!image.hasAlphaChannel()) { + image = image.convertToFormat(QImage::Format_ARGB32); + } + + // add transparency to image + QPainter p; + p.begin(&image); + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + p.fillRect(image.rect(), QColor(0, 0, 0, 115 /* alpha 45% */)); + p.end(); + + // add image to thumbnails + Thumbnail thumbnail; + thumbnail.status = ThumbnailLoaded; + thumbnail.image = image; + thumbnail.size = size; + thumbnail.transparent = true; + thumbnails.append(thumbnail); + } + else if(it == thumbnails.end()) { + Thumbnail thumbnail; + thumbnail.status = ThumbnailNotChecked; + thumbnail.size = size; + thumbnail.transparent = false; + thumbnails.append(thumbnail); } - } + return &thumbnails.back(); } -#if 0 -// cache the thumbnail of the specified size in the folder item -void FolderModelItem::setThumbnail(int size, QImage image) { - QVector::iterator it; - for(it = thumbnails.begin(); it != thumbnails.end(); ++it) { - if(it->size == size) { // an image of the same size already exists - it->image = image; // replace it - it->status = ThumbnailLoaded; - break; +// remove cached thumbnail of the specified size +void FolderModelItem::removeThumbnail(int size) { + QVector::iterator it; + for(it = thumbnails.begin(); it != thumbnails.end(); ++it) { + if(it->size == size) { // an image of the same size is found + thumbnails.erase(it); + break; + } } - } - if(it == thumbnails.end()) { // the image is not found - Thumbnail thumbnail; - thumbnail.size = size; - thumbnail.status = ThumbnailLoaded; - thumbnail.image = image; - thumbnails.append(thumbnail); // add a new entry - } } -#endif } // namespace Fm diff --git a/src/foldermodelitem.h b/src/foldermodelitem.h index 7274057..d0bcee6 100644 --- a/src/foldermodelitem.h +++ b/src/foldermodelitem.h @@ -29,41 +29,62 @@ #include #include "icontheme.h" +#include "core/folder.h" + namespace Fm { class LIBFM_QT_API FolderModelItem { public: - enum ThumbnailStatus { - ThumbnailNotChecked, - ThumbnailLoading, - ThumbnailLoaded, - ThumbnailFailed - }; + enum ThumbnailStatus { + ThumbnailNotChecked, + ThumbnailLoading, + ThumbnailLoaded, + ThumbnailFailed + }; - struct Thumbnail { - int size; - ThumbnailStatus status; - QImage image; - }; + struct Thumbnail { + int size; + bool transparent; + ThumbnailStatus status; + QImage image; + }; public: - FolderModelItem(FmFileInfo* _info); - FolderModelItem(const FolderModelItem& other); - virtual ~FolderModelItem(); - - Thumbnail* findThumbnail(int size); - // void setThumbnail(int size, QImage image); - void removeThumbnail(int size); - - void updateIcon() { - icon = IconTheme::icon(fm_file_info_get_icon(info)); - } - - QString displayName; - QIcon icon; - FmFileInfo* info; - QVector thumbnails; + explicit FolderModelItem(const std::shared_ptr& _info); + FolderModelItem(const FolderModelItem& other); + virtual ~FolderModelItem(); + + const QString& displayName() const { + return info->displayName(); + } + + QIcon icon(bool transparent = false) const { + const auto i = info->icon(); + return i ? i->qicon(transparent) : QIcon{}; + } + + QString ownerName() const; + + QString ownerGroup() const; + + const QString& displayMtime() const; + + const QString &displaySize() const; + + bool isCut() const; + + void bindCutFiles(const std::shared_ptr& cutFilesHashSet); + + Thumbnail* findThumbnail(int size, bool transparent); + + void removeThumbnail(int size); + + std::shared_ptr info; + mutable QString dispMtime_; + mutable QString dispSize_; + std::weak_ptr cutFilesHashSet_; + QVector thumbnails; }; } diff --git a/src/folderview.cpp b/src/folderview.cpp index 7f1f642..c2166f9 100644 --- a/src/folderview.cpp +++ b/src/folderview.cpp @@ -29,75 +29,98 @@ #include "filemenu.h" #include "foldermenu.h" #include "filelauncher.h" +#include "utilities.h" #include #include #include +#include #include #include #include #include #include +#include +#include +#include #include // for XDS support #include // for XDS support #include "xdndworkaround.h" // for XDS support #include "path.h" #include "folderview_p.h" +#include "utilities.h" Q_DECLARE_OPAQUE_POINTER(FmFileInfo*) using namespace Fm; FolderViewListView::FolderViewListView(QWidget* parent): - QListView(parent), - activationAllowed_(true) { - connect(this, &QListView::activated, this, &FolderViewListView::activation); + QListView(parent), + activationAllowed_(true) { + connect(this, &QListView::activated, this, &FolderViewListView::activation); + // inline renaming + setEditTriggers(QAbstractItemView::NoEditTriggers); } FolderViewListView::~FolderViewListView() { } void FolderViewListView::startDrag(Qt::DropActions supportedActions) { - if(movement() != Static) - QListView::startDrag(supportedActions); - else - QAbstractItemView::startDrag(supportedActions); + if(movement() != Static) { + QListView::startDrag(supportedActions); + } + else { + QAbstractItemView::startDrag(supportedActions); + } } void FolderViewListView::mousePressEvent(QMouseEvent* event) { - QListView::mousePressEvent(event); - static_cast(parent())->childMousePressEvent(event); + QListView::mousePressEvent(event); + static_cast(parent())->childMousePressEvent(event); +} + +void FolderViewListView::mouseMoveEvent(QMouseEvent* event) { + // NOTE: Filter the BACK & FORWARD buttons to not Drag & Drop with them. + // (by default Qt views drag with any button) + if (event->buttons() == Qt::NoButton || event->buttons() & ~(Qt::BackButton | Qt::ForwardButton)) + QListView::mouseMoveEvent(event); } QModelIndex FolderViewListView::indexAt(const QPoint& point) const { - QModelIndex index = QListView::indexAt(point); - // NOTE: QListView has a severe design flaw here. It does hit-testing based on the - // total bound rect of the item. The width of an item is determined by max(icon_width, text_width). - // So if the text label is much wider than the icon, when you click outside the icon but - // the point is still within the outer bound rect, the item is still selected. - // This results in very poor usability. Let's do precise hit-testing here. - // An item is hit only when the point is in the icon or text label. - // If the point is in the bound rectangle but outside the icon or text, it should not be selected. - if(viewMode() == QListView::IconMode && index.isValid()) { - // FIXME: this hack only improves the usability partially. We still need more precise sizeHint handling. - // FolderItemDelegate* delegate = static_cast(itemDelegateForColumn(FolderModel::ColumnFileName)); - // Q_ASSERT(delegate != nullptr); - // We use the grid size - (2, 2) as the size of the bounding rectangle of the whole item. - // The width of the text label hence is gridSize.width - 2, and the width and height of the icon is from iconSize(). - QRect visRect = visualRect(index); // visibal area on the screen - QSize itemSize = gridSize(); - itemSize.setWidth(itemSize.width() - 2); - itemSize.setHeight(itemSize.height() - 2); - QSize _iconSize = iconSize(); - int textHeight = itemSize.height() - _iconSize.height(); - if(point.y() < visRect.bottom() - textHeight) { - // the point is in the icon area, not over the text label - int iconXMargin = (itemSize.width() - _iconSize.width()) / 2; - if(point.x() < (visRect.left() + iconXMargin) || point.x() > (visRect.right() - iconXMargin)) - return QModelIndex(); - } - // qDebug() << "visualRect: " << visRect << "point:" << point; - } - return index; + QModelIndex index = QListView::indexAt(point); + // NOTE: QListView has a severe design flaw here. It does hit-testing based on the + // total bound rect of the item. The width of an item is determined by max(icon_width, text_width). + // So if the text label is much wider than the icon, when you click outside the icon but + // the point is still within the outer bound rect, the item is still selected. + // This results in very poor usability. Let's do precise hit-testing here. + // An item is hit only when the point is in the icon or text label. + // If the point is in the bound rectangle but outside the icon or text, it should not be selected. + if(viewMode() == QListView::IconMode && index.isValid()) { + QRect visRect = visualRect(index); // visible area on the screen + FolderItemDelegate* delegate = static_cast(itemDelegateForColumn(FolderModel::ColumnFileName)); + QSize margins = delegate->getMargins(); + QSize _iconSize = iconSize(); + if(point.y() < visRect.top() + margins.height()) { // above icon + return QModelIndex(); + } + else if(point.y() < visRect.top() + margins.height() + _iconSize.height()) { // on the icon area + int iconXMargin = (visRect.width() - _iconSize.width()) / 2; + if(point.x() < (visRect.left() + iconXMargin) || point.x() > (visRect.right() + 1 - iconXMargin)) { + // to the left or right of the icon + return QModelIndex(); + } + } + else { + QSize _textSize = delegate->iconViewTextSize(index); + int textHMargin = (visRect.width() - _textSize.width()) / 2; + if(point.y() > visRect.top() + margins.height() + _iconSize.height() + _textSize.height() // below text + // on the text area but to the left or right of the text + || point.x() < visRect.left() + textHMargin || point.x() > visRect.right() + 1 - textHMargin) { + return QModelIndex(); + } + } + // qDebug() << "visualRect: " << visRect << "point:" << point; + } + return index; } @@ -115,925 +138,1105 @@ QModelIndex FolderViewListView::indexAt(const QPoint& point) const { // TODO: I really should file a bug report to Qt developers. void FolderViewListView::dragEnterEvent(QDragEnterEvent* event) { - if(movement() != Static) - QListView::dragEnterEvent(event); - else - QAbstractItemView::dragEnterEvent(event); - qDebug("dragEnterEvent"); - //static_cast(parent())->childDragEnterEvent(event); + if(movement() != Static) { + QListView::dragEnterEvent(event); + } + else { + QAbstractItemView::dragEnterEvent(event); + } + qDebug("dragEnterEvent"); + //static_cast(parent())->childDragEnterEvent(event); } void FolderViewListView::dragLeaveEvent(QDragLeaveEvent* e) { - if(movement() != Static) - QListView::dragLeaveEvent(e); - else - QAbstractItemView::dragLeaveEvent(e); - static_cast(parent())->childDragLeaveEvent(e); + if(movement() != Static) { + QListView::dragLeaveEvent(e); + } + else { + QAbstractItemView::dragLeaveEvent(e); + } + static_cast(parent())->childDragLeaveEvent(e); } void FolderViewListView::dragMoveEvent(QDragMoveEvent* e) { - if(movement() != Static) - QListView::dragMoveEvent(e); - else - QAbstractItemView::dragMoveEvent(e); - static_cast(parent())->childDragMoveEvent(e); + if(movement() != Static) { + QListView::dragMoveEvent(e); + } + else { + QAbstractItemView::dragMoveEvent(e); + } + static_cast(parent())->childDragMoveEvent(e); } void FolderViewListView::dropEvent(QDropEvent* e) { - static_cast(parent())->childDropEvent(e); + static_cast(parent())->childDropEvent(e); - if(movement() != Static) - QListView::dropEvent(e); - else - QAbstractItemView::dropEvent(e); + if(movement() != Static) { + QListView::dropEvent(e); + } + else { + QAbstractItemView::dropEvent(e); + } } void FolderViewListView::mouseReleaseEvent(QMouseEvent* event) { - bool activationWasAllowed = activationAllowed_; - if ((!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this)) || (event->button() != Qt::LeftButton)) { - activationAllowed_ = false; - } + bool activationWasAllowed = activationAllowed_; + if((!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this)) || (event->button() != Qt::LeftButton)) { + activationAllowed_ = false; + } - QListView::mouseReleaseEvent(event); + QListView::mouseReleaseEvent(event); - activationAllowed_ = activationWasAllowed; + activationAllowed_ = activationWasAllowed; } void FolderViewListView::mouseDoubleClickEvent(QMouseEvent* event) { - bool activationWasAllowed = activationAllowed_; - if ((style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this)) || (event->button() != Qt::LeftButton)) { - activationAllowed_ = false; - } + bool activationWasAllowed = activationAllowed_; + if((style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this)) || (event->button() != Qt::LeftButton)) { + activationAllowed_ = false; + } - QListView::mouseDoubleClickEvent(event); + QListView::mouseDoubleClickEvent(event); - activationAllowed_ = activationWasAllowed; + activationAllowed_ = activationWasAllowed; } -void FolderViewListView::activation(const QModelIndex &index) { - if (activationAllowed_) { - Q_EMIT activatedFiltered(index); - } +QModelIndex FolderViewListView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) { + QAbstractItemModel* model_ = model(); + + if(model_ && currentIndex().isValid()) { + FolderView::ViewMode viewMode = static_cast(parent())->viewMode(); + if((viewMode == FolderView::IconMode) || (viewMode == FolderView::ThumbnailMode)) { + int next = (layoutDirection() == Qt::RightToLeft) ? - 1 : 1; + + if(cursorAction == QAbstractItemView::MoveRight) { + return model_->index(currentIndex().row() + next, 0); + } + else if(cursorAction == QAbstractItemView::MoveLeft) { + return model_->index(currentIndex().row() - next, 0); + } + } + } + + return QListView::moveCursor(cursorAction, modifiers); +} + +void FolderViewListView::activation(const QModelIndex& index) { + if(activationAllowed_) { + Q_EMIT activatedFiltered(index); + } } //----------------------------------------------------------------------------- FolderViewTreeView::FolderViewTreeView(QWidget* parent): - QTreeView(parent), - doingLayout_(false), - layoutTimer_(nullptr), - activationAllowed_(true) { + QTreeView(parent), + doingLayout_(false), + layoutTimer_(nullptr), + activationAllowed_(true) { - header()->setStretchLastSection(true); - setIndentation(0); + header()->setStretchLastSection(true); + setIndentation(0); + /* the default true value may cause a crash on entering a folder + by double clicking because of the viewport update done by + QTreeView::mouseDoubleClickEvent() (a Qt bug?) */ + setExpandsOnDoubleClick(false); - connect(this, &QTreeView::activated, this, &FolderViewTreeView::activation); + connect(this, &QTreeView::activated, this, &FolderViewTreeView::activation); + // don't open editor on double clicking + setEditTriggers(QAbstractItemView::NoEditTriggers); } FolderViewTreeView::~FolderViewTreeView() { - if(layoutTimer_) - delete layoutTimer_; + if(layoutTimer_) { + delete layoutTimer_; + } } void FolderViewTreeView::setModel(QAbstractItemModel* model) { - QTreeView::setModel(model); - layoutColumns(); - if(ProxyFolderModel* proxyModel = qobject_cast(model)) { - connect(proxyModel, &ProxyFolderModel::sortFilterChanged, this, &FolderViewTreeView::onSortFilterChanged, - Qt::UniqueConnection); - onSortFilterChanged(); - } + QTreeView::setModel(model); + layoutColumns(); + if(ProxyFolderModel* proxyModel = qobject_cast(model)) { + connect(proxyModel, &ProxyFolderModel::sortFilterChanged, this, &FolderViewTreeView::onSortFilterChanged, + Qt::UniqueConnection); + onSortFilterChanged(); + } } void FolderViewTreeView::mousePressEvent(QMouseEvent* event) { - QTreeView::mousePressEvent(event); - static_cast(parent())->childMousePressEvent(event); + QTreeView::mousePressEvent(event); + static_cast(parent())->childMousePressEvent(event); +} + +void FolderViewTreeView::mouseMoveEvent(QMouseEvent* event) { + // NOTE: Filter the BACK & FORWARD buttons to not Drag & Drop with them. + // (by default Qt views drag with any button) + if (event->buttons() == Qt::NoButton || event->buttons() & ~(Qt::BackButton | Qt::ForwardButton)) + QTreeView::mouseMoveEvent(event); } void FolderViewTreeView::dragEnterEvent(QDragEnterEvent* event) { - QTreeView::dragEnterEvent(event); - static_cast(parent())->childDragEnterEvent(event); + QTreeView::dragEnterEvent(event); + //static_cast(parent())->childDragEnterEvent(event); } void FolderViewTreeView::dragLeaveEvent(QDragLeaveEvent* e) { - QTreeView::dragLeaveEvent(e); - static_cast(parent())->childDragLeaveEvent(e); + QTreeView::dragLeaveEvent(e); + static_cast(parent())->childDragLeaveEvent(e); } void FolderViewTreeView::dragMoveEvent(QDragMoveEvent* e) { - QTreeView::dragMoveEvent(e); - static_cast(parent())->childDragMoveEvent(e); + QTreeView::dragMoveEvent(e); + static_cast(parent())->childDragMoveEvent(e); } void FolderViewTreeView::dropEvent(QDropEvent* e) { - static_cast(parent())->childDropEvent(e); - QTreeView::dropEvent(e); + static_cast(parent())->childDropEvent(e); + QTreeView::dropEvent(e); } // the default list mode of QListView handles column widths // very badly (worse than gtk+) and it's not very flexible. // so, let's handle column widths outselves. void FolderViewTreeView::layoutColumns() { - // qDebug("layoutColumns"); - if(!model()) - return; - doingLayout_ = true; - QHeaderView* headerView = header(); - // the width that's available for showing the columns. - int availWidth = viewport()->contentsRect().width(); - int desiredWidth = 0; - - // get the width that every column want - int numCols = headerView->count(); - if(numCols > 0) { - int* widths = new int[numCols]; // array to store the widths every column needs - int column; - for(column = 0; column < numCols; ++column) { - int columnId = headerView->logicalIndex(column); - // get the size that the column needs - widths[column] = sizeHintForColumn(columnId); - // compute the total width needed - desiredWidth += widths[column]; - } - - int filenameColumn = headerView->visualIndex(FolderModel::ColumnFileName); - // if the total witdh we want exceeds the available space - if(desiredWidth > availWidth) { - // Compute the width available for the filename column - int filenameAvailWidth = availWidth - desiredWidth + widths[filenameColumn]; - - // Compute the minimum acceptable width for the filename column - int filenameMinWidth = qMin(200, sizeHintForColumn(filenameColumn)); - - if (filenameAvailWidth > filenameMinWidth) { - // Shrink the filename column to the available width - widths[filenameColumn] = filenameAvailWidth; - } - else { - // Set the filename column to its minimum width - widths[filenameColumn] = filenameMinWidth; - } - } - else { - // Fill the extra available space with the filename column - widths[filenameColumn] += availWidth - desiredWidth; + // qDebug("layoutColumns"); + if(!model()) { + return; } + doingLayout_ = true; + QHeaderView* headerView = header(); + // the width that's available for showing the columns. + int availWidth = viewport()->contentsRect().width(); + + // get the width that every column want + int numCols = headerView->count(); + if(numCols > 0) { + int desiredWidth = 0; + int* widths = new int[numCols]; // array to store the widths every column needs + QStyleOptionHeader opt; + opt.initFrom(headerView); + opt.fontMetrics = QFontMetrics(font()); + if (headerView->isSortIndicatorShown()) { + opt.sortIndicator = QStyleOptionHeader::SortDown; + } + QAbstractItemModel* model_ = model(); + int column; + for(column = 0; column < numCols; ++column) { + int columnId = headerView->logicalIndex(column); + // get the size that the column needs + if(model_) { + QVariant data = model_->headerData(columnId, Qt::Horizontal, Qt::DisplayRole); + if(data.isValid()) { + opt.text = data.isValid() ? data.toString() : QString(); + } + } + opt.section = columnId; + widths[column] = qMax(sizeHintForColumn(columnId), + style()->sizeFromContents(QStyle::CT_HeaderSection, &opt, QSize(), headerView).width()); + // compute the total width needed + desiredWidth += widths[column]; + } - // really do the resizing for every column - for(int column = 0; column < numCols; ++column) { - headerView->resizeSection(column, widths[column]); + int filenameColumn = headerView->visualIndex(FolderModel::ColumnFileName); + // if the total witdh we want exceeds the available space + if(desiredWidth > availWidth) { + // Compute the width available for the filename column + int filenameAvailWidth = availWidth - desiredWidth + widths[filenameColumn]; + + // Compute the minimum acceptable width for the filename column + int filenameMinWidth = qMin(200, sizeHintForColumn(filenameColumn)); + + if(filenameAvailWidth > filenameMinWidth) { + // Shrink the filename column to the available width + widths[filenameColumn] = filenameAvailWidth; + } + else { + // Set the filename column to its minimum width + widths[filenameColumn] = filenameMinWidth; + } + } + else { + // Fill the extra available space with the filename column + widths[filenameColumn] += availWidth - desiredWidth; + } + + // really do the resizing for every column + for(int column = 0; column < numCols; ++column) { + headerView->resizeSection(headerView->logicalIndex(column), widths[column]); + } + delete []widths; } - delete []widths; - } - doingLayout_ = false; + doingLayout_ = false; - if(layoutTimer_) { - delete layoutTimer_; - layoutTimer_ = nullptr; - } + if(layoutTimer_) { + delete layoutTimer_; + layoutTimer_ = nullptr; + } } void FolderViewTreeView::resizeEvent(QResizeEvent* event) { - QAbstractItemView::resizeEvent(event); - // prevent endless recursion. - // When manually resizing columns, at the point where a horizontal scroll - // bar has to be inserted or removed, the vertical size changes, a resize - // event occurs and the column headers are flickering badly if the column - // layout is modified at this point. Therefore only layout the columns if - // the horizontal size changes. - if(!doingLayout_ && event->size().width() != event->oldSize().width()) - layoutColumns(); // layoutColumns() also triggers resizeEvent + QAbstractItemView::resizeEvent(event); + // prevent endless recursion. + // When manually resizing columns, at the point where a horizontal scroll + // bar has to be inserted or removed, the vertical size changes, a resize + // event occurs and the column headers are flickering badly if the column + // layout is modified at this point. Therefore only layout the columns if + // the horizontal size changes. + if(!doingLayout_ && event->size().width() != event->oldSize().width()) { + layoutColumns(); // layoutColumns() also triggers resizeEvent + } } void FolderViewTreeView::rowsInserted(const QModelIndex& parent, int start, int end) { - QTreeView::rowsInserted(parent, start, end); - queueLayoutColumns(); + QTreeView::rowsInserted(parent, start, end); + queueLayoutColumns(); } void FolderViewTreeView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { - QTreeView::rowsAboutToBeRemoved(parent, start, end); - queueLayoutColumns(); + QTreeView::rowsAboutToBeRemoved(parent, start, end); + queueLayoutColumns(); } -void FolderViewTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { - QTreeView::dataChanged(topLeft, bottomRight); - // FIXME: this will be very inefficient - // queueLayoutColumns(); +void FolderViewTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles /*= QVector{}*/) { + QTreeView::dataChanged(topLeft, bottomRight, roles); + // FIXME: this will be very inefficient + // queueLayoutColumns(); } void FolderViewTreeView::reset() { - // Sometimes when the content of the model is radically changed, Qt does reset() - // on the model rather than doing large amount of insertion and deletion. - // This is for performance reason so in this case rowsInserted() and rowsAboutToBeRemoved() - // might not be called. Hence we also have to re-layout the columns when the model is reset. - // This fixes bug #190 - // https://github.com/lxde/pcmanfm-qt/issues/190 - QTreeView::reset(); - queueLayoutColumns(); + // Sometimes when the content of the model is radically changed, Qt does reset() + // on the model rather than doing large amount of insertion and deletion. + // This is for performance reason so in this case rowsInserted() and rowsAboutToBeRemoved() + // might not be called. Hence we also have to re-layout the columns when the model is reset. + // This fixes bug #190 + // https://github.com/lxde/pcmanfm-qt/issues/190 + QTreeView::reset(); + queueLayoutColumns(); } void FolderViewTreeView::queueLayoutColumns() { - // qDebug("queueLayoutColumns"); - if(!layoutTimer_) { - layoutTimer_ = new QTimer(); - layoutTimer_->setSingleShot(true); - layoutTimer_->setInterval(0); - connect(layoutTimer_, &QTimer::timeout, this, &FolderViewTreeView::layoutColumns); - } - layoutTimer_->start(); + // qDebug("queueLayoutColumns"); + if(!layoutTimer_) { + layoutTimer_ = new QTimer(); + layoutTimer_->setSingleShot(true); + layoutTimer_->setInterval(0); + connect(layoutTimer_, &QTimer::timeout, this, &FolderViewTreeView::layoutColumns); + } + layoutTimer_->start(); } void FolderViewTreeView::mouseReleaseEvent(QMouseEvent* event) { - bool activationWasAllowed = activationAllowed_; - if ((!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this)) || (event->button() != Qt::LeftButton)) { - activationAllowed_ = false; - } + bool activationWasAllowed = activationAllowed_; + if((!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this)) || (event->button() != Qt::LeftButton)) { + activationAllowed_ = false; + } - QTreeView::mouseReleaseEvent(event); + QTreeView::mouseReleaseEvent(event); - activationAllowed_ = activationWasAllowed; + activationAllowed_ = activationWasAllowed; } void FolderViewTreeView::mouseDoubleClickEvent(QMouseEvent* event) { - bool activationWasAllowed = activationAllowed_; - if ((style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this)) || (event->button() != Qt::LeftButton)) { - activationAllowed_ = false; - } + bool activationWasAllowed = activationAllowed_; + if((style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this)) || (event->button() != Qt::LeftButton)) { + activationAllowed_ = false; + } - QTreeView::mouseDoubleClickEvent(event); + QTreeView::mouseDoubleClickEvent(event); - activationAllowed_ = activationWasAllowed; + activationAllowed_ = activationWasAllowed; } -void FolderViewTreeView::activation(const QModelIndex &index) { - if (activationAllowed_) { - Q_EMIT activatedFiltered(index); - } +void FolderViewTreeView::activation(const QModelIndex& index) { + if(activationAllowed_) { + Q_EMIT activatedFiltered(index); + } } void FolderViewTreeView::onSortFilterChanged() { - if(QSortFilterProxyModel* proxyModel = qobject_cast(model())) { - header()->setSortIndicatorShown(true); - header()->setSortIndicator(proxyModel->sortColumn(), proxyModel->sortOrder()); - if (!isSortingEnabled()) { - setSortingEnabled(true); + if(QSortFilterProxyModel* proxyModel = qobject_cast(model())) { + header()->setSortIndicatorShown(true); + header()->setSortIndicator(proxyModel->sortColumn(), proxyModel->sortOrder()); + if(!isSortingEnabled()) { + setSortingEnabled(true); + } } - } } //----------------------------------------------------------------------------- -FolderView::FolderView(ViewMode _mode, QWidget* parent): - QWidget(parent), - view(nullptr), - model_(nullptr), - mode((ViewMode)0), - fileLauncher_(nullptr), - autoSelectionDelay_(600), - autoSelectionTimer_(nullptr), - selChangedTimer_(nullptr), - itemDelegateMargins_(QSize(3, 3)) { +FolderView::FolderView(FolderView::ViewMode _mode, QWidget *parent): + QWidget(parent), + view(nullptr), + model_(nullptr), + mode((ViewMode)0), + fileLauncher_(nullptr), + autoSelectionDelay_(600), + autoSelectionTimer_(nullptr), + selChangedTimer_(nullptr), + itemDelegateMargins_(QSize(3, 3)) { - iconSize_[IconMode - FirstViewMode] = QSize(48, 48); - iconSize_[CompactMode - FirstViewMode] = QSize(24, 24); - iconSize_[ThumbnailMode - FirstViewMode] = QSize(128, 128); - iconSize_[DetailedListMode - FirstViewMode] = QSize(24, 24); + iconSize_[IconMode - FirstViewMode] = QSize(48, 48); + iconSize_[CompactMode - FirstViewMode] = QSize(24, 24); + iconSize_[ThumbnailMode - FirstViewMode] = QSize(128, 128); + iconSize_[DetailedListMode - FirstViewMode] = QSize(24, 24); - QVBoxLayout* layout = new QVBoxLayout(); - layout->setMargin(0); - setLayout(layout); + QVBoxLayout* layout = new QVBoxLayout(); + layout->setMargin(0); + setLayout(layout); - setViewMode(_mode); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setViewMode(_mode); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - connect(this, &FolderView::clicked, this, &FolderView::onFileClicked); + connect(this, &FolderView::clicked, this, &FolderView::onFileClicked); + connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &FolderView::onClipboardDataChange); } FolderView::~FolderView() { } void FolderView::onItemActivated(QModelIndex index) { - if(index.isValid() && index.model()) { - QVariant data = index.model()->data(index, FolderModel::FileInfoRole); - FmFileInfo* info = (FmFileInfo*)data.value(); - if(info) { - if (!(QApplication::keyboardModifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier))) { - Q_EMIT clicked(ActivatedClick, info); - } + if(index.isValid() && index.model()) { + QVariant data = index.model()->data(index, FolderModel::FileInfoRole); + auto info = data.value>(); + if(info) { + if(!(QApplication::keyboardModifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier))) { + Q_EMIT clicked(ActivatedClick, info); + } + } } - } } void FolderView::onSelChangedTimeout() { - selChangedTimer_->deleteLater(); - selChangedTimer_ = nullptr; - - QItemSelectionModel* selModel = selectionModel(); - int nSel = 0; - if(viewMode() == DetailedListMode) - nSel = selModel->selectedRows().count(); - else { - nSel = selModel->selectedIndexes().count(); - } - // qDebug()<<"selected:" << nSel; - Q_EMIT selChanged(nSel); // FIXME: this is inefficient + selChangedTimer_->deleteLater(); + selChangedTimer_ = nullptr; + // qDebug()<<"selected:" << nSel; + Q_EMIT selChanged(); +} + +void FolderView::onSelectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { + // It's possible that the selected items change too often and this slot gets called for thousands of times. + // For example, when you select thousands of files and delete them, we will get one selectionChanged() event + // for every deleted file. So, we use a timer to delay the handling to avoid too frequent updates of the UI. + if(!selChangedTimer_) { + selChangedTimer_ = new QTimer(this); + selChangedTimer_->setSingleShot(true); + connect(selChangedTimer_, &QTimer::timeout, this, &FolderView::onSelChangedTimeout); + selChangedTimer_->start(200); + } } -void FolderView::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { - // It's possible that the selected items change too often and this slot gets called for thousands of times. - // For example, when you select thousands of files and delete them, we will get one selectionChanged() event - // for every deleted file. So, we use a timer to delay the handling to avoid too frequent updates of the UI. - if(!selChangedTimer_) { - selChangedTimer_ = new QTimer(this); - selChangedTimer_->setSingleShot(true); - connect(selChangedTimer_, &QTimer::timeout, this, &FolderView::onSelChangedTimeout); - selChangedTimer_->start(200); - } +void FolderView::onClosingEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint) { + if (hint != QAbstractItemDelegate::NoHint) { + // we set the hint to NoHint in FolderItemDelegate::eventFilter() + return; + } + QString newName; + if (qobject_cast(editor)) { // icon and thumbnail view + newName = qobject_cast(editor)->toPlainText(); + } + else if (qobject_cast(editor)) { // compact view + newName = qobject_cast(editor)->text(); + } + if (newName.isEmpty()) { + return; + } + // the editor will be deleted by QAbstractItemDelegate::destroyEditor() when no longer needed + + QModelIndex index = view->selectionModel()->currentIndex(); + if(index.isValid() && index.model()) { + QVariant data = index.model()->data(index, FolderModel::FileInfoRole); + auto info = data.value>(); + if (info) { + auto oldName = QString::fromStdString(info->name()); + if(newName == oldName) { + return; + } + QWidget* parent = window(); + if (window() == this) { // supposedly desktop, in case it uses this + parent = nullptr; + } + changeFileName(info->path(), newName, parent); + } + } } - void FolderView::setViewMode(ViewMode _mode) { - if(_mode == mode) // if it's the same more, ignore - return; - // FIXME: retain old selection - - // since only detailed list mode uses QTreeView, and others - // all use QListView, it's wise to preserve QListView when possible. - bool recreateView = false; - if(view && (mode == DetailedListMode || _mode == DetailedListMode)) { - delete view; // FIXME: no virtual dtor? - view = nullptr; - recreateView = true; - } - mode = _mode; - QSize iconSize = iconSize_[mode - FirstViewMode]; - - if(mode == DetailedListMode) { - FolderViewTreeView* treeView = new FolderViewTreeView(this); - connect(treeView, &FolderViewTreeView::activatedFiltered, this, &FolderView::onItemActivated); - setFocusProxy(treeView); - - view = treeView; - treeView->setItemsExpandable(false); - treeView->setRootIsDecorated(false); - treeView->setAllColumnsShowFocus(false); - - // set our own custom delegate - FolderItemDelegate* delegate = new FolderItemDelegate(treeView); - treeView->setItemDelegateForColumn(FolderModel::ColumnFileName, delegate); - } - else { - FolderViewListView* listView; - if(view) - listView = static_cast(view); + if(_mode == mode) { // if it's the same more, ignore + return; + } + // FIXME: retain old selection + + // since only detailed list mode uses QTreeView, and others + // all use QListView, it's wise to preserve QListView when possible. + bool recreateView = false; + if(view && (mode == DetailedListMode || _mode == DetailedListMode)) { + delete view; // FIXME: no virtual dtor? + view = nullptr; + recreateView = true; + } + mode = _mode; + QSize iconSize = iconSize_[mode - FirstViewMode]; + + FolderItemDelegate* delegate = nullptr; + if(mode == DetailedListMode) { + FolderViewTreeView* treeView = new FolderViewTreeView(this); + connect(treeView, &FolderViewTreeView::activatedFiltered, this, &FolderView::onItemActivated); + setFocusProxy(treeView); + + view = treeView; + treeView->setItemsExpandable(false); + treeView->setRootIsDecorated(false); + treeView->setAllColumnsShowFocus(false); + + // set our own custom delegate + delegate = new FolderItemDelegate(treeView); + treeView->setItemDelegateForColumn(FolderModel::ColumnFileName, delegate); + } else { - listView = new FolderViewListView(this); - connect(listView, &FolderViewListView::activatedFiltered, this, &FolderView::onItemActivated); - view = listView; - } - setFocusProxy(listView); - - // set our own custom delegate - FolderItemDelegate* delegate = new FolderItemDelegate(listView); - listView->setItemDelegateForColumn(FolderModel::ColumnFileName, delegate); - // FIXME: should we expose the delegate? - listView->setMovement(QListView::Static); - /* If listView is already visible, setMovement() will lay out items again with delay - (see Qt, QListView::setMovement(), d->doDelayedItemsLayout()) and thus drop events - will remain disabled for the viewport. So, we should re-enable drop events here. */ - if(listView->viewport()->isVisible()) - listView->viewport()->setAcceptDrops(true); - listView->setResizeMode(QListView::Adjust); - listView->setWrapping(true); - switch(mode) { - case IconMode: { - listView->setViewMode(QListView::IconMode); - listView->setWordWrap(true); - listView->setFlow(QListView::LeftToRight); - break; - } - case CompactMode: { - listView->setViewMode(QListView::ListMode); - listView->setWordWrap(false); - listView->setFlow(QListView::QListView::TopToBottom); - break; - } - case ThumbnailMode: { - listView->setViewMode(QListView::IconMode); - listView->setWordWrap(true); - listView->setFlow(QListView::LeftToRight); - break; - } - default:; - } - updateGridSize(); - } - if(view) { - // we have to install the event filter on the viewport instead of the view itself. - view->viewport()->installEventFilter(this); - // we want the QEvent::HoverMove event for single click + auto-selection support - view->viewport()->setAttribute(Qt::WA_Hover, true); - view->setContextMenuPolicy(Qt::NoContextMenu); // defer the context menu handling to parent widgets - view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - view->setIconSize(iconSize); - - view->setSelectionMode(QAbstractItemView::ExtendedSelection); - layout()->addWidget(view); - - // enable dnd - view->setDragEnabled(true); - view->setAcceptDrops(true); - view->setDragDropMode(QAbstractItemView::DragDrop); - view->setDropIndicatorShown(true); + FolderViewListView* listView; + if(view) { + listView = static_cast(view); + } + else { + listView = new FolderViewListView(this); + connect(listView, &FolderViewListView::activatedFiltered, this, &FolderView::onItemActivated); + view = listView; + } + setFocusProxy(listView); + + // set our own custom delegate + delegate = new FolderItemDelegate(listView); + listView->setItemDelegateForColumn(FolderModel::ColumnFileName, delegate); + // FIXME: should we expose the delegate? + listView->setMovement(QListView::Static); + /* If listView is already visible, setMovement() will lay out items again with delay + (see Qt, QListView::setMovement(), d->doDelayedItemsLayout()) and thus drop events + will remain disabled for the viewport. So, we should re-enable drop events here. */ + if(listView->viewport()->isVisible()) { + listView->viewport()->setAcceptDrops(true); + } + listView->setResizeMode(QListView::Adjust); + listView->setWrapping(true); + switch(mode) { + case IconMode: { + listView->setViewMode(QListView::IconMode); + listView->setWordWrap(true); + listView->setFlow(QListView::LeftToRight); + break; + } + case CompactMode: { + listView->setViewMode(QListView::ListMode); + listView->setWordWrap(false); + listView->setFlow(QListView::QListView::TopToBottom); + break; + } + case ThumbnailMode: { + listView->setViewMode(QListView::IconMode); + listView->setWordWrap(true); + listView->setFlow(QListView::LeftToRight); + break; + } + default: + ; + } + updateGridSize(); + } + if(view) { + // we have to install the event filter on the viewport instead of the view itself. + view->viewport()->installEventFilter(this); + // we want the QEvent::HoverMove event for single click + auto-selection support + view->viewport()->setAttribute(Qt::WA_Hover, true); + view->setContextMenuPolicy(Qt::NoContextMenu); // defer the context menu handling to parent widgets + view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + view->setIconSize(iconSize); + + view->setSelectionMode(QAbstractItemView::ExtendedSelection); + layout()->addWidget(view); + + // enable dnd + view->setDragEnabled(true); + view->setAcceptDrops(true); + view->setDragDropMode(QAbstractItemView::DragDrop); + view->setDropIndicatorShown(true); + + // inline renaming + if(delegate) { + connect(delegate, &QAbstractItemDelegate::closeEditor, this, &FolderView::onClosingEditor); + } - if(model_) { - // FIXME: preserve selections - model_->setThumbnailSize(iconSize.width()); - view->setModel(model_); - if(recreateView) - connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FolderView::onSelectionChanged); + if(model_) { + // FIXME: preserve selections + model_->setThumbnailSize(iconSize.width()); + view->setModel(model_); + if(recreateView) { + connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FolderView::onSelectionChanged); + } + } } - } } // set proper grid size for the QListView based on current view mode, icon size, and font size. void FolderView::updateGridSize() { - if(mode == DetailedListMode || !view) - return; - FolderViewListView* listView = static_cast(view); - QSize icon = iconSize(mode); // size of the icon - QFontMetrics fm = fontMetrics(); // size of current font - QSize grid; // the final grid size - switch(mode) { + if(mode == DetailedListMode || !view) { + return; + } + FolderViewListView* listView = static_cast(view); + QSize icon = iconSize(mode); // size of the icon + QFontMetrics fm = fontMetrics(); // size of current font + QSize grid; // the final grid size + switch(mode) { case IconMode: case ThumbnailMode: { - // NOTE by PCMan about finding the optimal text label size: - // The average filename length on my root filesystem is roughly 18-20 chars. - // So, a reasonable size for the text label is about 10 chars each line since string of this length - // can be shown in two lines. If you consider word wrap, then the result is around 10 chars per word. - // In average, 10 char per line should be enough to display a "word" in the filename without breaking. - // The values can be estimated with this command: - // > find / | xargs basename -a | sed -e s'/[_-]/ /g' | wc -mcw - // However, this average only applies to English. For some Asian characters, such as Chinese chars, - // each char actually takes doubled space. To be safe, we use 13 chars per line x average char width - // to get a nearly optimal width for the text label. As most of the filenames have less than 40 chars - // 13 chars x 3 lines should be enough to show the full filenames for most files. - int textWidth = fm.averageCharWidth() * 13; - int textHeight = fm.lineSpacing() * 3; - grid.setWidth(qMax(icon.width(), textWidth) + 4); // a margin of 2 px for selection rects - grid.setHeight(icon.height() + textHeight + 4); // a margin of 2 px for selection rects - break; + // NOTE by PCMan about finding the optimal text label size: + // The average filename length on my root filesystem is roughly 18-20 chars. + // So, a reasonable size for the text label is about 10 chars each line since string of this length + // can be shown in two lines. If you consider word wrap, then the result is around 10 chars per word. + // In average, 10 char per line should be enough to display a "word" in the filename without breaking. + // The values can be estimated with this command: + // > find / | xargs basename -a | sed -e s'/[_-]/ /g' | wc -mcw + // However, this average only applies to English. For some Asian characters, such as Chinese chars, + // each char actually takes doubled space. To be safe, we use 13 chars per line x average char width + // to get a nearly optimal width for the text label. As most of the filenames have less than 40 chars + // 13 chars x 3 lines should be enough to show the full filenames for most files. + int textWidth = fm.averageCharWidth() * 13; + int textHeight = fm.lineSpacing() * 3; + grid.setWidth(qMax(icon.width(), textWidth) + 4); // a margin of 2 px for selection rects + grid.setHeight(icon.height() + textHeight + 4); // a margin of 2 px for selection rects + // grow to include margins + grid += 2*itemDelegateMargins_; + // let horizontal and vertical spacings be set only by itemDelegateMargins_ + listView->setSpacing(0); + + break; } default: - ; // do not use grid size - } - if(mode == IconMode || mode == ThumbnailMode) - listView->setGridSize(grid + 2 * itemDelegateMargins_); // the default spacing is 6(=2x3) px - else - listView->setGridSize(grid); - FolderItemDelegate* delegate = static_cast(listView->itemDelegateForColumn(FolderModel::ColumnFileName)); - delegate->setGridSize(grid); + // FIXME: set proper item size + listView->setSpacing(2); + ; // do not use grid size + } + + FolderItemDelegate* delegate = static_cast(listView->itemDelegateForColumn(FolderModel::ColumnFileName)); + delegate->setItemSize(grid); + delegate->setIconSize(icon); + delegate->setMargins(itemDelegateMargins_); } void FolderView::setIconSize(ViewMode mode, QSize size) { - Q_ASSERT(mode >= FirstViewMode && mode <= LastViewMode); - iconSize_[mode - FirstViewMode] = size; - if(viewMode() == mode) { - view->setIconSize(size); - if(model_) - model_->setThumbnailSize(size.width()); - updateGridSize(); - } + Q_ASSERT(mode >= FirstViewMode && mode <= LastViewMode); + iconSize_[mode - FirstViewMode] = size; + if(viewMode() == mode) { + view->setIconSize(size); + if(model_) { + model_->setThumbnailSize(size.width()); + } + updateGridSize(); + } } QSize FolderView::iconSize(ViewMode mode) const { - Q_ASSERT(mode >= FirstViewMode && mode <= LastViewMode); - return iconSize_[mode - FirstViewMode]; + Q_ASSERT(mode >= FirstViewMode && mode <= LastViewMode); + return iconSize_[mode - FirstViewMode]; } void FolderView::setMargins(QSize size) { - if (itemDelegateMargins_ != size.expandedTo(QSize(0, 0))) { - itemDelegateMargins_ = size.expandedTo(QSize(0, 0)); - updateGridSize(); - } + if(itemDelegateMargins_ != size.expandedTo(QSize(0, 0))) { + itemDelegateMargins_ = size.expandedTo(QSize(0, 0)); + updateGridSize(); + } } FolderView::ViewMode FolderView::viewMode() const { - return mode; + return mode; } void FolderView::setAutoSelectionDelay(int delay) { - autoSelectionDelay_ = delay; + autoSelectionDelay_ = delay; } QAbstractItemView* FolderView::childView() const { - return view; + return view; } ProxyFolderModel* FolderView::model() const { - return model_; + return model_; } void FolderView::setModel(ProxyFolderModel* model) { - if(view) { - view->setModel(model); - QSize iconSize = iconSize_[mode - FirstViewMode]; - model->setThumbnailSize(iconSize.width()); - if(view->selectionModel()) - connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FolderView::onSelectionChanged); - } - if(model_) - delete model_; - model_ = model; + if(view) { + view->setModel(model); + QSize iconSize = iconSize_[mode - FirstViewMode]; + model->setThumbnailSize(iconSize.width()); + if(view->selectionModel()) { + connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FolderView::onSelectionChanged); + } + } + if(model_) { + delete model_; + } + model_ = model; } bool FolderView::event(QEvent* event) { - switch(event->type()) { + switch(event->type()) { case QEvent::StyleChange: - break; + break; case QEvent::FontChange: - updateGridSize(); - break; - default: break; - } - return QWidget::event(event); + updateGridSize(); + break; + default: + break; + } + return QWidget::event(event); } void FolderView::contextMenuEvent(QContextMenuEvent* event) { - QWidget::contextMenuEvent(event); - QPoint pos = event->pos(); - QPoint view_pos = view->mapFromParent(pos); - QPoint viewport_pos = view->viewport()->mapFromParent(view_pos); - emitClickedAt(ContextMenuClick, viewport_pos); + QWidget::contextMenuEvent(event); + QPoint pos = event->pos(); + QPoint view_pos = view->mapFromParent(pos); + QPoint viewport_pos = view->viewport()->mapFromParent(view_pos); + emitClickedAt(ContextMenuClick, viewport_pos); } void FolderView::childMousePressEvent(QMouseEvent* event) { - // called from mousePressEvent() of child view - Qt::MouseButton button = event->button(); - if(button == Qt::MiddleButton) { - emitClickedAt(MiddleClick, event->pos()); - } else if (button == Qt::BackButton) { - Q_EMIT clickedBack(); - } else if (button == Qt::ForwardButton) { - Q_EMIT clickedForward(); - } + // called from mousePressEvent() of child view + Qt::MouseButton button = event->button(); + if(button == Qt::MiddleButton) { + emitClickedAt(MiddleClick, event->pos()); + } + else if(button == Qt::BackButton) { + Q_EMIT clickedBack(); + } + else if(button == Qt::ForwardButton) { + Q_EMIT clickedForward(); + } } void FolderView::emitClickedAt(ClickType type, const QPoint& pos) { - // indexAt() needs a point in "viewport" coordinates. - QModelIndex index = view->indexAt(pos); - if(index.isValid()) { - QVariant data = index.data(FolderModel::FileInfoRole); - FmFileInfo* info = reinterpret_cast(data.value()); - Q_EMIT clicked(type, info); - } - else { - // FIXME: should we show popup menu for the selected files instead - // if there are selected files? - if(type == ContextMenuClick) { - // clear current selection if clicked outside selected files - view->clearSelection(); - Q_EMIT clicked(type, nullptr); - } - } + // indexAt() needs a point in "viewport" coordinates. + QModelIndex index = view->indexAt(pos); + if(index.isValid()) { + QVariant data = index.data(FolderModel::FileInfoRole); + auto info = data.value>(); + Q_EMIT clicked(type, info); + } + else { + // FIXME: should we show popup menu for the selected files instead + // if there are selected files? + if(type == ContextMenuClick) { + // clear current selection if clicked outside selected files + view->clearSelection(); + Q_EMIT clicked(type, nullptr); + } + } } QModelIndexList FolderView::selectedRows(int column) const { - QItemSelectionModel* selModel = selectionModel(); - if(selModel) { - return selModel->selectedRows(column); - } - return QModelIndexList(); + QItemSelectionModel* selModel = selectionModel(); + if(selModel) { + return selModel->selectedRows(column); + } + return QModelIndexList(); } // This returns all selected "cells", which means all cells of the same row are returned. QModelIndexList FolderView::selectedIndexes() const { - QItemSelectionModel* selModel = selectionModel(); - if(selModel) { - return selModel->selectedIndexes(); - } - return QModelIndexList(); + QItemSelectionModel* selModel = selectionModel(); + if(selModel) { + return selModel->selectedIndexes(); + } + return QModelIndexList(); } QItemSelectionModel* FolderView::selectionModel() const { - return view ? view->selectionModel() : nullptr; -} - -Fm::PathList FolderView::selectedFilePaths() const { - if(model_) { - QModelIndexList selIndexes = mode == DetailedListMode ? selectedRows() : selectedIndexes(); - if(!selIndexes.isEmpty()) { - PathList paths; - QModelIndexList::const_iterator it; - for(it = selIndexes.begin(); it != selIndexes.end(); ++it) { - FmFileInfo* file = model_->fileInfoFromIndex(*it); - paths.pushTail(fm_file_info_get_path(file)); - } - return paths; - } - } - return nullptr; -} - -QModelIndex FolderView::indexFromFolderPath(FmPath* folderPath) const { - if(!model_ || !folderPath) return QModelIndex(); - QModelIndex index; - int count = model_->rowCount(); - for(int row = 0; row < count; ++row) { - index = model_->index(row, 0); - FmFileInfo* info = model_->fileInfoFromIndex(index); - if(info && fm_file_info_is_dir(info) && fm_path_equal(folderPath,fm_file_info_get_path(info))) - return index; - } - return QModelIndex(); + return view ? view->selectionModel() : nullptr; +} + +Fm::FilePathList FolderView::selectedFilePaths() const { + if(model_) { + QModelIndexList selIndexes = mode == DetailedListMode ? selectedRows() : selectedIndexes(); + if(!selIndexes.isEmpty()) { + Fm::FilePathList paths; + QModelIndexList::const_iterator it; + for(it = selIndexes.constBegin(); it != selIndexes.constEnd(); ++it) { + auto file = model_->fileInfoFromIndex(*it); + paths.push_back(file->path()); + } + return paths; + } + } + return Fm::FilePathList(); +} + +bool FolderView::hasSelection() const { + QItemSelectionModel* selModel = selectionModel(); + return selModel ? selModel->hasSelection() : false; +} + +QModelIndex FolderView::indexFromFolderPath(const Fm::FilePath& folderPath) const { + if(!model_ || !folderPath.isValid()) { + return QModelIndex(); + } + QModelIndex index; + int count = model_->rowCount(); + for(int row = 0; row < count; ++row) { + index = model_->index(row, 0); + auto info = model_->fileInfoFromIndex(index); + if(info && info->isDir() && folderPath == info->path()) { + return index; + } + } + return QModelIndex(); } Fm::FileInfoList FolderView::selectedFiles() const { - if(model_) { - QModelIndexList selIndexes = mode == DetailedListMode ? selectedRows() : selectedIndexes(); - if(!selIndexes.isEmpty()) { - FileInfoList files; - QModelIndexList::const_iterator it; - for(it = selIndexes.constBegin(); it != selIndexes.constEnd(); ++it) { - FmFileInfo* file = model_->fileInfoFromIndex(*it); - files.pushTail(file); - } - return files; + if(model_) { + QModelIndexList selIndexes = mode == DetailedListMode ? selectedRows() : selectedIndexes(); + if(!selIndexes.isEmpty()) { + Fm::FileInfoList files; + QModelIndexList::const_iterator it; + for(it = selIndexes.constBegin(); it != selIndexes.constEnd(); ++it) { + auto file = model_->fileInfoFromIndex(*it); + files.push_back(file); + } + return files; + } } - } - return nullptr; + return Fm::FileInfoList(); } void FolderView::selectAll() { - if(mode == DetailedListMode) - view->selectAll(); - else { - // NOTE: By default QListView::selectAll() selects all columns in the model. - // However, QListView only show the first column. Normal selection by mouse - // can only select the first column of every row. I consider this discripancy yet - // another design flaw of Qt. To make them consistent, we do it ourselves by only - // selecting the first column of every row and do not select all columns as Qt does. - // This will trigger one selectionChanged event per row, which is very inefficient, - // but we have no other choices to workaround the Qt bug. - // I'll report a Qt bug for this later. - if(model_) { - int rowCount = model_->rowCount(); - for(int row = 0; row < rowCount; ++row) { - QModelIndex index = model_->index(row, 0); - selectionModel()->select(index, QItemSelectionModel::Select); - } + if(mode == DetailedListMode) { + view->selectAll(); + } + else { + // NOTE: By default QListView::selectAll() selects all columns in the model. + // However, QListView only show the first column. Normal selection by mouse + // can only select the first column of every row. I consider this discripancy yet + // another design flaw of Qt. To make them consistent, we do it ourselves by only + // selecting the first column of every row and do not select all columns as Qt does. + // I'll report a Qt bug for this later. + if(model_) { + const QItemSelection sel{model_->index(0, 0), model_->index(model_->rowCount() - 1, 0)}; + selectionModel()->select(sel, QItemSelectionModel::Select); + } } - } } void FolderView::invertSelection() { - if(model_) { - QItemSelectionModel* selModel = view->selectionModel(); - int rows = model_->rowCount(); - QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Toggle; - if(mode == DetailedListMode) - flags |= QItemSelectionModel::Rows; - for(int row = 0; row < rows; ++row) { - QModelIndex index = model_->index(row, 0); - selModel->select(index, flags); + if(model_) { + QItemSelectionModel* selModel = view->selectionModel(); + int rows = model_->rowCount(); + QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Toggle; + if(mode == DetailedListMode) { + flags |= QItemSelectionModel::Rows; + } + for(int row = 0; row < rows; ++row) { + QModelIndex index = model_->index(row, 0); + selModel->select(index, flags); + } } - } } void FolderView::childDragEnterEvent(QDragEnterEvent* event) { - qDebug("drag enter"); - if(event->mimeData()->hasFormat("text/uri-list")) { - event->accept(); - } - else - event->ignore(); + qDebug("drag enter"); + if(event->mimeData()->hasFormat("text/uri-list")) { + event->accept(); + } + else { + event->ignore(); + } } void FolderView::childDragLeaveEvent(QDragLeaveEvent* e) { - qDebug("drag leave"); - e->accept(); + qDebug("drag leave"); + e->accept(); } -void FolderView::childDragMoveEvent(QDragMoveEvent* e) { - qDebug("drag move"); +void FolderView::childDragMoveEvent(QDragMoveEvent* /*e*/) { + qDebug("drag move"); } void FolderView::childDropEvent(QDropEvent* e) { - // qDebug("drop"); - // Try to support XDS - // NOTE: in theory, it's not possible to implement XDS with pure Qt. - // We achieved this with some dirty XCB/XDND workarounds. - // Please refer to XdndWorkaround::clientMessage() in xdndworkaround.cpp for details. - if(QX11Info::isPlatformX11() && e->mimeData()->hasFormat("XdndDirectSave0")) { - e->setDropAction(Qt::CopyAction); - const QWidget* targetWidget = childView()->viewport(); - // these are dynamic QObject property set by our XDND workarounds in xworkaround.cpp. - xcb_window_t dndSource = xcb_window_t(targetWidget->property("xdnd::lastDragSource").toUInt()); - //xcb_timestamp_t dropTimestamp = (xcb_timestamp_t)targetWidget->property("xdnd::lastDropTime").toUInt(); - // qDebug() << "XDS: source window" << dndSource << dropTimestamp; - if(dndSource != 0) { - xcb_atom_t XdndDirectSaveAtom = XdndWorkaround::internAtom("XdndDirectSave0", 15); - xcb_atom_t textAtom = XdndWorkaround::internAtom("text/plain", 10); - - // 1. get the filename from XdndDirectSave property of the source window - QByteArray basename = XdndWorkaround::windowProperty(dndSource, XdndDirectSaveAtom, textAtom, 1024); - - // 2. construct the fill URI for the file, and update the source window property. - Path filePath = Path(path()).newChild(basename); - QByteArray fileUri = filePath.toUri(); - XdndWorkaround::setWindowProperty(dndSource, XdndDirectSaveAtom, textAtom, (void*)fileUri.constData(), fileUri.length()); - - // 3. send to XDS selection data request with type "XdndDirectSave" to the source window and - // receive result from the source window. (S: success, E: error, or F: failure) - QByteArray result = e->mimeData()->data("XdndDirectSave0"); - // NOTE: there seems to be some bugs in file-roller so it always replies with "E" even if the - // file extraction is finished successfully. Anyways, we ignore any error at the moment. - } - e->accept(); // yeah! we've done with XDS so stop Qt from further event propagation. - return; - } - - if(e->keyboardModifiers() == Qt::NoModifier) { - // if no key modifiers are used, popup a menu - // to ask the user for the action he/she wants to perform. - Qt::DropAction action = DndActionMenu::askUser(e->possibleActions(), QCursor::pos()); - e->setDropAction(action); - } + // qDebug("drop"); + // Try to support XDS + // NOTE: in theory, it's not possible to implement XDS with pure Qt. + // We achieved this with some dirty XCB/XDND workarounds. + // Please refer to XdndWorkaround::clientMessage() in xdndworkaround.cpp for details. + if(QX11Info::isPlatformX11() && e->mimeData()->hasFormat("XdndDirectSave0")) { + e->setDropAction(Qt::CopyAction); + const QWidget* targetWidget = childView()->viewport(); + // these are dynamic QObject property set by our XDND workarounds in xdndworkaround.cpp. + xcb_window_t dndSource = xcb_window_t(targetWidget->property("xdnd::lastDragSource").toUInt()); + //xcb_timestamp_t dropTimestamp = (xcb_timestamp_t)targetWidget->property("xdnd::lastDropTime").toUInt(); + // qDebug() << "XDS: source window" << dndSource << dropTimestamp; + if(dndSource != 0) { + xcb_atom_t XdndDirectSaveAtom = XdndWorkaround::internAtom("XdndDirectSave0", 15); + xcb_atom_t textAtom = XdndWorkaround::internAtom("text/plain", 10); + + // 1. get the filename from XdndDirectSave property of the source window + QByteArray basename = XdndWorkaround::windowProperty(dndSource, XdndDirectSaveAtom, textAtom, 1024); + + // 2. construct the fill URI for the file, and update the source window property. + Fm::FilePath filePath; + if(model_) { + QModelIndex index = view->indexAt(e->pos()); + auto info = model_->fileInfoFromIndex(index); + if(info && info->isDir()) { + filePath = info->path().child(basename); + } + } + if(!filePath.isValid()) { + filePath = path().child(basename); + } + QByteArray fileUri = filePath.uri().get(); + XdndWorkaround::setWindowProperty(dndSource, XdndDirectSaveAtom, textAtom, (void*)fileUri.constData(), fileUri.length()); + + // 3. send to XDS selection data request with type "XdndDirectSave" to the source window and + // receive result from the source window. (S: success, E: error, or F: failure) + QByteArray result = e->mimeData()->data("XdndDirectSave0"); + // NOTE: there seems to be some bugs in file-roller so it always replies with "E" even if the + // file extraction is finished successfully. Anyways, we ignore any error at the moment. + } + e->accept(); // yeah! we've done with XDS so stop Qt from further event propagation. + return; + } + + if(e->keyboardModifiers() == Qt::NoModifier) { + // if no key modifiers are used, popup a menu + // to ask the user for the action he/she wants to perform. + Qt::DropAction action = DndActionMenu::askUser(e->possibleActions(), QCursor::pos()); + e->setDropAction(action); + } } bool FolderView::eventFilter(QObject* watched, QEvent* event) { - // NOTE: Instead of simply filtering the drag and drop events of the child view in - // the event filter, we overrided each event handler virtual methods in - // both QListView and QTreeView and added some childXXXEvent() callbacks. - // We did this because of a design flaw of Qt. - // All QAbstractScrollArea derived widgets, including QAbstractItemView - // contains an internal child widget, which is called a viewport. - // The events actually comes from the child viewport, not the parent view itself. - // Qt redirects the events of viewport to the viewportEvent() method of - // QAbstractScrollArea and let the parent widget handle the events. - // Qt implemented this using a event filter installed on the child viewport widget. - // That means, when we try to install an event filter on the viewport, - // there is already a filter installed by Qt which will be called before ours. - // So we can never intercept the event handling of QAbstractItemView by using a filter. - // That's why we override respective virtual methods for different events. - if(view && watched == view->viewport()) { - switch(event->type()) { - case QEvent::HoverMove: - // activate items on single click - if(style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) { - QHoverEvent* hoverEvent = static_cast(event); - QModelIndex index = view->indexAt(hoverEvent->pos()); // find out the hovered item - if(index.isValid()) { // change the cursor to a hand when hovering on an item - setCursor(Qt::PointingHandCursor); - if(!selectionModel()->hasSelection()) - selectionModel()->setCurrentIndex(index, QItemSelectionModel::Current); - } - else - setCursor(Qt::ArrowCursor); - // turn on auto-selection for hovered item when single click mode is used. - if(autoSelectionDelay_ > 0 && model_) { - if(!autoSelectionTimer_) { - autoSelectionTimer_ = new QTimer(this); - connect(autoSelectionTimer_, &QTimer::timeout, this, &FolderView::onAutoSelectionTimeout); - lastAutoSelectionIndex_ = QModelIndex(); - } - autoSelectionTimer_->start(autoSelectionDelay_); + // NOTE: Instead of simply filtering the drag and drop events of the child view in + // the event filter, we overrided each event handler virtual methods in + // both QListView and QTreeView and added some childXXXEvent() callbacks. + // We did this because of a design flaw of Qt. + // All QAbstractScrollArea derived widgets, including QAbstractItemView + // contains an internal child widget, which is called a viewport. + // The events actually comes from the child viewport, not the parent view itself. + // Qt redirects the events of viewport to the viewportEvent() method of + // QAbstractScrollArea and let the parent widget handle the events. + // Qt implemented this using a event filter installed on the child viewport widget. + // That means, when we try to install an event filter on the viewport, + // there is already a filter installed by Qt which will be called before ours. + // So we can never intercept the event handling of QAbstractItemView by using a filter. + // That's why we override respective virtual methods for different events. + if(view && watched == view->viewport()) { + switch(event->type()) { + case QEvent::HoverMove: + // activate items on single click + if(style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) { + QHoverEvent* hoverEvent = static_cast(event); + QModelIndex index = view->indexAt(hoverEvent->pos()); // find out the hovered item + if(index.isValid()) { // change the cursor to a hand when hovering on an item + setCursor(Qt::PointingHandCursor); + } + else { + setCursor(Qt::ArrowCursor); + } + // turn on auto-selection for hovered item when single click mode is used. + if(autoSelectionDelay_ > 0 && model_) { + if(!autoSelectionTimer_) { + autoSelectionTimer_ = new QTimer(this); + connect(autoSelectionTimer_, &QTimer::timeout, this, &FolderView::onAutoSelectionTimeout); + lastAutoSelectionIndex_ = QModelIndex(); + } + autoSelectionTimer_->start(autoSelectionDelay_); + } + break; + } + case QEvent::HoverLeave: + if(style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) { + setCursor(Qt::ArrowCursor); + } + break; + case QEvent::Wheel: + // don't let the view scroll during an inline renaming + if (view) { + FolderItemDelegate* delegate = nullptr; + if(mode == DetailedListMode) { + FolderViewTreeView* treeView = static_cast(view); + delegate = static_cast(treeView->itemDelegateForColumn(FolderModel::ColumnFileName)); + } + else { + FolderViewListView* listView = static_cast(view); + delegate = static_cast(listView->itemDelegateForColumn(FolderModel::ColumnFileName)); + } + if (delegate && delegate->hasEditor()) { + return true; + } + } + // This is to fix #85: Scrolling doesn't work in compact view + // Actually, I think it's the bug of Qt, not ours. + // When in compact mode, only the horizontal scroll bar is used and the vertical one is hidden. + // So, when a user scroll his mouse wheel, it's reasonable to scroll the horizontal scollbar. + // Qt does not implement such a simple feature, unfortunately. + // We do it by forwarding the scroll event in the viewport to the horizontal scrollbar. + // FIXME: if someday Qt supports this, we have to disable the workaround. + if(mode == CompactMode) { + QScrollBar* scroll = view->horizontalScrollBar(); + if(scroll) { + QApplication::sendEvent(scroll, event); + return true; + } + } + break; + default: + break; } - break; - } - case QEvent::HoverLeave: - if(style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) - setCursor(Qt::ArrowCursor); - break; - case QEvent::Wheel: - // This is to fix #85: Scrolling doesn't work in compact view - // Actually, I think it's the bug of Qt, not ours. - // When in compact mode, only the horizontal scroll bar is used and the vertical one is hidden. - // So, when a user scroll his mouse wheel, it's reasonable to scroll the horizontal scollbar. - // Qt does not implement such a simple feature, unfortunately. - // We do it by forwarding the scroll event in the viewport to the horizontal scrollbar. - // FIXME: if someday Qt supports this, we have to disable the workaround. - if(mode == CompactMode) { - QScrollBar* scroll = view->horizontalScrollBar(); - if(scroll) { - QApplication::sendEvent(scroll, event); - return true; - } - } - break; - default: break; } - } - return QObject::eventFilter(watched, event); + return QObject::eventFilter(watched, event); } // this slot handles auto-selection of items. void FolderView::onAutoSelectionTimeout() { - if(QApplication::mouseButtons() != Qt::NoButton) - return; + if(QApplication::mouseButtons() != Qt::NoButton) { + return; + } - Qt::KeyboardModifiers mods = QApplication::keyboardModifiers(); - QPoint pos = view->viewport()->mapFromGlobal(QCursor::pos()); // convert to viewport coordinates - QModelIndex index = view->indexAt(pos); // find out the hovered item - QItemSelectionModel::SelectionFlags flags = (mode == DetailedListMode ? QItemSelectionModel::Rows : QItemSelectionModel::NoUpdate); - QItemSelectionModel* selModel = view->selectionModel(); + Qt::KeyboardModifiers mods = QApplication::keyboardModifiers(); + QPoint pos = view->viewport()->mapFromGlobal(QCursor::pos()); // convert to viewport coordinates + QModelIndex index = view->indexAt(pos); // find out the hovered item + QItemSelectionModel::SelectionFlags flags = (mode == DetailedListMode ? QItemSelectionModel::Rows : QItemSelectionModel::NoUpdate); + QItemSelectionModel* selModel = view->selectionModel(); - if(mods & Qt::ControlModifier) { // Ctrl key is pressed - if(selModel->isSelected(index) && index != lastAutoSelectionIndex_) { - // unselect a previously selected item - selModel->select(index, flags|QItemSelectionModel::Deselect); - lastAutoSelectionIndex_ = QModelIndex(); + if(mods & Qt::ControlModifier) { // Ctrl key is pressed + if(selModel->isSelected(index) && index != lastAutoSelectionIndex_) { + // unselect a previously selected item + selModel->select(index, flags | QItemSelectionModel::Deselect); + lastAutoSelectionIndex_ = QModelIndex(); + } + else { + // select an unselected item + selModel->select(index, flags | QItemSelectionModel::Select); + lastAutoSelectionIndex_ = index; + } + selModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); // move the cursor } - else { - // select an unselected item - selModel->select(index, flags|QItemSelectionModel::Select); - lastAutoSelectionIndex_ = index; - } - selModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); // move the cursor - } - else if(mods & Qt::ShiftModifier) { // Shift key is pressed - // select all items between current index and the hovered index. - QModelIndex current = selModel->currentIndex(); - if(selModel->hasSelection() && current.isValid()) { - selModel->clear(); // clear old selection - selModel->setCurrentIndex(current, QItemSelectionModel::NoUpdate); - int begin = current.row(); - int end = index.row(); - if(begin > end) - qSwap(begin, end); - for(int row = begin; row <= end; ++row) { - QModelIndex sel = model_->index(row, 0); - selModel->select(sel, flags|QItemSelectionModel::Select); - } - } - else { // no items are selected, select the hovered item. - if(index.isValid()) { - selModel->select(index, flags|QItemSelectionModel::SelectCurrent); - selModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); - } - } - lastAutoSelectionIndex_ = index; - } - else if(mods == Qt::NoModifier) { // no modifier keys are pressed. - if(index.isValid()) { - // select the hovered item - view->clearSelection(); - selModel->select(index, flags|QItemSelectionModel::SelectCurrent); - selModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); - } - lastAutoSelectionIndex_ = index; - } - - autoSelectionTimer_->deleteLater(); - autoSelectionTimer_ = nullptr; -} - -void FolderView::onFileClicked(int type, FmFileInfo* fileInfo) { - if(type == ActivatedClick) { - if(fileLauncher_) { - GList* files = g_list_append(nullptr, fileInfo); - fileLauncher_->launchFiles(nullptr, files); - g_list_free(files); - } - } - else if(type == ContextMenuClick) { - FmPath* folderPath = nullptr; - FileInfoList files = selectedFiles(); - if (!files.isNull()) { - FmFileInfo* first = files.peekHead(); - if (files.getLength() == 1 && fm_file_info_is_dir(first)) - folderPath = fm_file_info_get_path(first); - } - if (!folderPath) - folderPath = path(); - QMenu* menu = nullptr; - if(fileInfo) { - // show context menu - FileInfoList files = selectedFiles(); - if (!files.isNull()) { - Fm::FileMenu* fileMenu = new Fm::FileMenu(files.dataPtr(), fileInfo, folderPath); - fileMenu->setFileLauncher(fileLauncher_); - prepareFileMenu(fileMenu); - menu = fileMenu; - } + else if(mods & Qt::ShiftModifier) { // Shift key is pressed + // select all items between current index and the hovered index. + QModelIndex current = selModel->currentIndex(); + if(selModel->hasSelection() && current.isValid()) { + selModel->clear(); // clear old selection + selModel->setCurrentIndex(current, QItemSelectionModel::NoUpdate); + int begin = current.row(); + int end = index.row(); + if(begin > end) { + qSwap(begin, end); + } + for(int row = begin; row <= end; ++row) { + QModelIndex sel = model_->index(row, 0); + selModel->select(sel, flags | QItemSelectionModel::Select); + } + } + else { // no items are selected, select the hovered item. + if(index.isValid()) { + selModel->select(index, flags | QItemSelectionModel::SelectCurrent); + selModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + } + } + lastAutoSelectionIndex_ = index; } - else { - Fm::FolderMenu* folderMenu = new Fm::FolderMenu(this); - prepareFolderMenu(folderMenu); - menu = folderMenu; + else if(mods == Qt::NoModifier) { // no modifier keys are pressed. + if(index.isValid()) { + // select the hovered item + view->clearSelection(); + selModel->select(index, flags | QItemSelectionModel::SelectCurrent); + selModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate); + } + lastAutoSelectionIndex_ = index; } - if (menu) { - menu->exec(QCursor::pos()); - delete menu; + + autoSelectionTimer_->deleteLater(); + autoSelectionTimer_ = nullptr; +} + +void FolderView::onFileClicked(int type, const std::shared_ptr &fileInfo) { + if(type == ActivatedClick) { + if(fileLauncher_) { + Fm::FileInfoList files; + files.push_back(fileInfo); + fileLauncher_->launchFiles(nullptr, std::move(files)); + } + } + else if(type == ContextMenuClick) { + Fm::FilePath folderPath; + bool isWritableDir(true); + auto files = selectedFiles(); + if(!files.empty()) { + auto& first = files.front(); + if(files.size() == 1 && first->isDir()) { + folderPath = first->path(); + isWritableDir = first->isWritable(); + } + } + if(!folderPath.isValid()) { + folderPath = path(); + if(auto info = folderInfo()) { + isWritableDir = info->isWritable(); + } + } + QMenu* menu = nullptr; + if(fileInfo) { + // show context menu + auto files = selectedFiles(); + if(!files.empty()) { + QModelIndexList selIndexes = mode == DetailedListMode ? selectedRows() : selectedIndexes(); + Fm::FileMenu* fileMenu = (view && selIndexes.size() == 1) + ? new Fm::FileMenu(files, fileInfo, folderPath, isWritableDir, QString(), view) + : new Fm::FileMenu(files, fileInfo, folderPath, isWritableDir); + fileMenu->setFileLauncher(fileLauncher_); + prepareFileMenu(fileMenu); + menu = fileMenu; + } + } + else if (folderInfo()) { + Fm::FolderMenu* folderMenu = new Fm::FolderMenu(this); + prepareFolderMenu(folderMenu); + menu = folderMenu; + } + if(menu) { + menu->exec(QCursor::pos()); + delete menu; + } } - } } -void FolderView::prepareFileMenu(FileMenu* menu) { +void FolderView::onClipboardDataChange() { + if(model_) { + const QClipboard* clipboard = QApplication::clipboard(); + const QMimeData* data = clipboard->mimeData(); + Fm::FilePathList paths; + bool isCutSelection; + std::tie(paths, isCutSelection) = Fm::parseClipboardData(*data); + if(!folder()->path().hasUriScheme("search") // skip for search results + && isCutSelection + && Fm::isCurrentPidClipboardData(*data)) { // set cut files only with this app + auto cutDirPath = paths.size() > 0 ? paths[0].parent(): FilePath(); + if(folder()->path() == cutDirPath) { + model_->setCutFiles(selectionModel()->selection()); + } + else if(folder()->hadCutFilesUnset() || folder()->hasCutFiles()) { + model_->setCutFiles(QItemSelection()); + } + return; + } + + folder()->setCutFiles(std::make_shared()); // clean Folder::cutFilesHashSet_ + if(folder()->hadCutFilesUnset()) { + model_->setCutFiles(QItemSelection()); // update indexes if there were cut files here + } + } } -void FolderView::prepareFolderMenu(FolderMenu* menu) { +void FolderView::prepareFileMenu(FileMenu* /*menu*/) { } +void FolderView::prepareFolderMenu(FolderMenu* /*menu*/) { +} diff --git a/src/folderview.h b/src/folderview.h index 5173d94..5e36433 100644 --- a/src/folderview.h +++ b/src/folderview.h @@ -29,9 +29,10 @@ #include #include "foldermodel.h" #include "proxyfoldermodel.h" -#include "fileinfo.h" #include "path.h" +#include "core/folder.h" + class QTimer; namespace Fm { @@ -42,138 +43,144 @@ class FileLauncher; class FolderViewStyle; class LIBFM_QT_API FolderView : public QWidget { - Q_OBJECT + Q_OBJECT public: - enum ViewMode { - FirstViewMode = 1, - IconMode = FirstViewMode, - CompactMode, - DetailedListMode, - ThumbnailMode, - LastViewMode = ThumbnailMode, - NumViewModes = (LastViewMode - FirstViewMode + 1) - }; + enum ViewMode { + FirstViewMode = 1, + IconMode = FirstViewMode, + CompactMode, + DetailedListMode, + ThumbnailMode, + LastViewMode = ThumbnailMode, + NumViewModes = (LastViewMode - FirstViewMode + 1) + }; + + enum ClickType { + ActivatedClick, + MiddleClick, + ContextMenuClick + }; + + friend class FolderViewTreeView; + friend class FolderViewListView; - enum ClickType { - ActivatedClick, - MiddleClick, - ContextMenuClick - }; + explicit FolderView(ViewMode _mode = IconMode, QWidget* parent = 0); - friend class FolderViewTreeView; - friend class FolderViewListView; + explicit FolderView(QWidget* parent): FolderView{IconMode, parent} {} - explicit FolderView(ViewMode _mode = IconMode, QWidget* parent = 0); - virtual ~FolderView(); + virtual ~FolderView(); - void setViewMode(ViewMode _mode); - ViewMode viewMode() const; + void setViewMode(ViewMode _mode); + ViewMode viewMode() const; - void setIconSize(ViewMode mode, QSize size); - QSize iconSize(ViewMode mode) const; + void setIconSize(ViewMode mode, QSize size); + QSize iconSize(ViewMode mode) const; - QAbstractItemView* childView() const; + QAbstractItemView* childView() const; - ProxyFolderModel* model() const; - void setModel(ProxyFolderModel* _model); + ProxyFolderModel* model() const; + void setModel(ProxyFolderModel* _model); - FmFolder* folder() { - return model_ ? static_cast(model_->sourceModel())->folder() : NULL; - } + std::shared_ptr folder() const { + return model_ ? static_cast(model_->sourceModel())->folder() : nullptr; + } - FmFileInfo* folderInfo() { - FmFolder* _folder = folder(); - return _folder ? fm_folder_get_info(_folder) : NULL; - } + std::shared_ptr folderInfo() const { + auto _folder = folder(); + return _folder ? _folder->info() : nullptr; + } - FmPath* path() { - FmFolder* _folder = folder(); - return _folder ? fm_folder_get_path(_folder) : NULL; - } + Fm::FilePath path() { + auto _folder = folder(); + return _folder ? _folder->path() : Fm::FilePath(); + } - QItemSelectionModel* selectionModel() const; - Fm::FileInfoList selectedFiles() const; - Fm::PathList selectedFilePaths() const; - QModelIndex indexFromFolderPath(FmPath* folderPath) const; + QItemSelectionModel* selectionModel() const; + Fm::FileInfoList selectedFiles() const; + Fm::FilePathList selectedFilePaths() const; + bool hasSelection() const; + QModelIndex indexFromFolderPath(const Fm::FilePath& folderPath) const; - void selectAll(); + void selectAll(); - void invertSelection(); + void invertSelection(); - void setFileLauncher(FileLauncher* launcher) { - fileLauncher_ = launcher; - } + void setFileLauncher(FileLauncher* launcher) { + fileLauncher_ = launcher; + } - FileLauncher* fileLauncher() { - return fileLauncher_; - } + FileLauncher* fileLauncher() { + return fileLauncher_; + } - int autoSelectionDelay() const { - return autoSelectionDelay_; - } + int autoSelectionDelay() const { + return autoSelectionDelay_; + } - void setAutoSelectionDelay(int delay); + void setAutoSelectionDelay(int delay); protected: - virtual bool event(QEvent* event); - virtual void contextMenuEvent(QContextMenuEvent* event); - virtual void childMousePressEvent(QMouseEvent* event); - virtual void childDragEnterEvent(QDragEnterEvent* event); - virtual void childDragMoveEvent(QDragMoveEvent* e); - virtual void childDragLeaveEvent(QDragLeaveEvent* e); - virtual void childDropEvent(QDropEvent* e); + virtual bool event(QEvent* event); + virtual void contextMenuEvent(QContextMenuEvent* event); + virtual void childMousePressEvent(QMouseEvent* event); + virtual void childDragEnterEvent(QDragEnterEvent* event); + virtual void childDragMoveEvent(QDragMoveEvent* e); + virtual void childDragLeaveEvent(QDragLeaveEvent* e); + virtual void childDropEvent(QDropEvent* e); - void emitClickedAt(ClickType type, const QPoint& pos); + void emitClickedAt(ClickType type, const QPoint& pos); - QModelIndexList selectedRows ( int column = 0 ) const; - QModelIndexList selectedIndexes() const; + QModelIndexList selectedRows(int column = 0) const; + QModelIndexList selectedIndexes() const; - virtual void prepareFileMenu(Fm::FileMenu* menu); - virtual void prepareFolderMenu(Fm::FolderMenu* menu); + virtual void prepareFileMenu(Fm::FileMenu* menu); + virtual void prepareFolderMenu(Fm::FolderMenu* menu); - virtual bool eventFilter(QObject* watched, QEvent* event); + virtual bool eventFilter(QObject* watched, QEvent* event); - void updateGridSize(); // called when view mode, icon size, font size or cell margin is changed + void updateGridSize(); // called when view mode, icon size, font size or cell margin is changed - QSize getMargins() const { - return itemDelegateMargins_; - } + QSize getMargins() const { + return itemDelegateMargins_; + } - // sets the cell margins in the icon and thumbnail modes - // and calls updateGridSize() when needed - void setMargins(QSize size); + // sets the cell margins in the icon and thumbnail modes + // and calls updateGridSize() when needed + void setMargins(QSize size); public Q_SLOTS: - void onItemActivated(QModelIndex index); - void onSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected); - virtual void onFileClicked(int type, FmFileInfo* fileInfo); + void onItemActivated(QModelIndex index); + void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + virtual void onFileClicked(int type, const std::shared_ptr& fileInfo); + void onClipboardDataChange(); private Q_SLOTS: - void onAutoSelectionTimeout(); - void onSelChangedTimeout(); + void onAutoSelectionTimeout(); + void onSelChangedTimeout(); + void onClosingEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint); Q_SIGNALS: - void clicked(int type, FmFileInfo* file); - void clickedBack(); - void clickedForward(); - void selChanged(int n_sel); - void sortChanged(); + void clicked(int type, const std::shared_ptr& file); + void clickedBack(); + void clickedForward(); + void selChanged(); + void sortChanged(); private: - QAbstractItemView* view; - ProxyFolderModel* model_; - ViewMode mode; - QSize iconSize_[NumViewModes]; - FileLauncher* fileLauncher_; - int autoSelectionDelay_; - QTimer* autoSelectionTimer_; - QModelIndex lastAutoSelectionIndex_; - QTimer* selChangedTimer_; - // the cell margins in the icon and thumbnail modes - QSize itemDelegateMargins_; + QAbstractItemView* view; + ProxyFolderModel* model_; + ViewMode mode; + QSize iconSize_[NumViewModes]; + FileLauncher* fileLauncher_; + int autoSelectionDelay_; + QTimer* autoSelectionTimer_; + QModelIndex lastAutoSelectionIndex_; + QTimer* selChangedTimer_; + // the cell margins in the icon and thumbnail modes + QSize itemDelegateMargins_; }; } diff --git a/src/folderview_p.h b/src/folderview_p.h index 91c6b81..769bb38 100644 --- a/src/folderview_p.h +++ b/src/folderview_p.h @@ -39,6 +39,7 @@ public: virtual ~FolderViewListView(); virtual void startDrag(Qt::DropActions supportedActions); virtual void mousePressEvent(QMouseEvent* event); + virtual void mouseMoveEvent(QMouseEvent* event); virtual void mouseReleaseEvent(QMouseEvent* event); virtual void mouseDoubleClickEvent(QMouseEvent* event); virtual void dragEnterEvent(QDragEnterEvent* event); @@ -63,6 +64,9 @@ public: Q_SIGNALS: void activatedFiltered(const QModelIndex &index); +protected: + virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + private Q_SLOTS: void activation(const QModelIndex &index); @@ -78,6 +82,7 @@ public: virtual ~FolderViewTreeView(); virtual void setModel(QAbstractItemModel* model); virtual void mousePressEvent(QMouseEvent* event); + virtual void mouseMoveEvent(QMouseEvent* event); virtual void mouseReleaseEvent(QMouseEvent* event); virtual void mouseDoubleClickEvent(QMouseEvent* event); virtual void dragEnterEvent(QDragEnterEvent* event); @@ -87,7 +92,7 @@ public: virtual void rowsInserted(const QModelIndex& parent,int start, int end); virtual void rowsAboutToBeRemoved(const QModelIndex& parent,int start, int end); - virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles = QVector{}); virtual void reset(); virtual void resizeEvent(QResizeEvent* event); diff --git a/src/fontbutton.cpp b/src/fontbutton.cpp index f4599de..f3b21a6 100644 --- a/src/fontbutton.cpp +++ b/src/fontbutton.cpp @@ -25,33 +25,33 @@ namespace Fm { FontButton::FontButton(QWidget* parent): QPushButton(parent) { - connect(this, &QPushButton::clicked, this, &FontButton::onClicked); + connect(this, &QPushButton::clicked, this, &FontButton::onClicked); } FontButton::~FontButton() { } void FontButton::onClicked() { - QFontDialog dlg(font_); - if(dlg.exec() == QDialog::Accepted) { - setFont(dlg.selectedFont()); - } + QFontDialog dlg(font_); + if(dlg.exec() == QDialog::Accepted) { + setFont(dlg.selectedFont()); + } } void FontButton::setFont(QFont font) { - font_ = font; - QString text = font.family(); - if(font.bold()) { - text += " "; - text += tr("Bold"); - } - if(font.italic()) { - text += " "; - text += tr("Italic"); - } - text += QString(" %1").arg(font.pointSize()); - setText(text); - Q_EMIT changed(); + font_ = font; + QString text = font.family(); + if(font.bold()) { + text += " "; + text += tr("Bold"); + } + if(font.italic()) { + text += " "; + text += tr("Italic"); + } + text += QString(" %1").arg(font.pointSize()); + setText(text); + Q_EMIT changed(); } diff --git a/src/fontbutton.h b/src/fontbutton.h index 1caf19b..4dee7b2 100644 --- a/src/fontbutton.h +++ b/src/fontbutton.h @@ -28,25 +28,25 @@ namespace Fm { class LIBFM_QT_API FontButton : public QPushButton { -Q_OBJECT + Q_OBJECT public: - explicit FontButton(QWidget* parent = 0); - virtual ~FontButton(); + explicit FontButton(QWidget* parent = 0); + virtual ~FontButton(); - QFont font() { - return font_; - } + QFont font() { + return font_; + } - void setFont(QFont font); + void setFont(QFont font); Q_SIGNALS: - void changed(); + void changed(); private Q_SLOTS: - void onClicked(); + void onClicked(); private: - QFont font_; + QFont font_; }; } diff --git a/src/icon.h b/src/icon.h deleted file mode 100644 index 794fdc9..0000000 --- a/src/icon.h +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_ICON_H__ -#define __LIBFM_QT_FM_ICON_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - -class LIBFM_QT_API Icon { -public: - - - // default constructor - Icon() { - dataPtr_ = nullptr; - } - - - Icon(FmIcon* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(fm_icon_ref(dataPtr)) : nullptr; - } - - - // copy constructor - Icon(const Icon& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(fm_icon_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - Icon(Icon&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - ~Icon() { - if(dataPtr_ != nullptr) { - fm_icon_unref(dataPtr_); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static Icon wrapPtr(FmIcon* dataPtr) { - Icon obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmIcon* takeDataPtr() { - FmIcon* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmIcon* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmIcon*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - Icon& operator=(const Icon& other) { - if(dataPtr_ != nullptr) { - fm_icon_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(fm_icon_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - Icon& operator=(Icon&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - static void unloadCache( ) { - fm_icon_unload_cache(); - } - - - static void resetUserDataCache(GQuark quark) { - fm_icon_reset_user_data_cache(quark); - } - - - static void unloadUserDataCache( ) { - fm_icon_unload_user_data_cache(); - } - - - static void setUserDataDestroy(GDestroyNotify func) { - fm_icon_set_user_data_destroy(func); - } - - - void setUserData(gpointer user_data) { - fm_icon_set_user_data(dataPtr(), user_data); - } - - - gpointer getUserData(void) { - return fm_icon_get_user_data(dataPtr()); - } - - - static Icon fromName(const char* name) { - return Icon::wrapPtr(fm_icon_from_name(name)); - } - - - static Icon fromGicon(GIcon* gicon) { - return Icon::wrapPtr(fm_icon_from_gicon(gicon)); - } - - - -private: - FmIcon* dataPtr_; // data pointer for the underlying C struct - -}; - - -} - -#endif // __LIBFM_QT_FM_ICON_H__ diff --git a/src/icontheme.cpp b/src/icontheme.cpp index 36542b9..55d1f28 100644 --- a/src/icontheme.cpp +++ b/src/icontheme.cpp @@ -26,157 +26,54 @@ #include #include -namespace Fm { - -class IconCacheData { -public: - QIcon qicon; - QList emblems; -}; +#include "core/iconinfo.h" -static IconTheme* theIconTheme = NULL; // the global single instance of IconTheme. -static const char* fallbackNames[] = {"unknown", "application-octet-stream", NULL}; +namespace Fm { -static void fmIconDataDestroy(gpointer user_data) { - IconCacheData* data = reinterpret_cast(user_data); - delete data; -} +static IconTheme* theIconTheme = nullptr; // the global single instance of IconTheme. IconTheme::IconTheme(): - currentThemeName_(QIcon::themeName()) { - // NOTE: only one instance is allowed - Q_ASSERT(theIconTheme == NULL); - Q_ASSERT(qApp != NULL); // QApplication should exists before contructing IconTheme. - - theIconTheme = this; - fm_icon_set_user_data_destroy(reinterpret_cast(fmIconDataDestroy)); - fallbackIcon_ = iconFromNames(fallbackNames); - - // We need to get notified when there is a QEvent::StyleChange event so - // we can check if the current icon theme name is changed. - // To do this, we can filter QApplication object itself to intercept - // signals of all widgets, but this may be too inefficient. - // So, we only filter the events on QDesktopWidget instead. - qApp->desktop()->installEventFilter(this); + currentThemeName_(QIcon::themeName()) { + // NOTE: only one instance is allowed + Q_ASSERT(theIconTheme == nullptr); + Q_ASSERT(qApp != nullptr); // QApplication should exists before contructing IconTheme. + + theIconTheme = this; + + // We need to get notified when there is a QEvent::StyleChange event so + // we can check if the current icon theme name is changed. + // To do this, we can filter QApplication object itself to intercept + // signals of all widgets, but this may be too inefficient. + // So, we only filter the events on QDesktopWidget instead. + qApp->desktop()->installEventFilter(this); } IconTheme::~IconTheme() { } IconTheme* IconTheme::instance() { - return theIconTheme; + return theIconTheme; } // check if the icon theme name is changed and emit "changed()" signal if any change is detected. void IconTheme::checkChanged() { - if(QIcon::themeName() != theIconTheme->currentThemeName_) { - // if the icon theme is changed - theIconTheme->currentThemeName_ = QIcon::themeName(); - // invalidate the cached data - fm_icon_reset_user_data_cache(fm_qdata_id); - - theIconTheme->fallbackIcon_ = iconFromNames(fallbackNames); - Q_EMIT theIconTheme->changed(); - } -} - -QIcon IconTheme::iconFromNames(const char* const* names) { - const gchar* const* name; - // qDebug("names: %p", names); - for(name = names; *name; ++name) { - // qDebug("icon name=%s", *name); - QString qname = *name; - QIcon qicon = QIcon::fromTheme(qname); - if(!qicon.isNull()) { - return qicon; - } - } - return QIcon(); -} - -QIcon IconTheme::convertFromGIconWithoutEmblems(GIcon* gicon) { - if(G_IS_THEMED_ICON(gicon)) { - const gchar * const * names = g_themed_icon_get_names(G_THEMED_ICON(gicon)); - QIcon icon = iconFromNames(names); - if(!icon.isNull()) - return icon; - } - else if(G_IS_FILE_ICON(gicon)) { - GFile* file = g_file_icon_get_file(G_FILE_ICON(gicon)); - char* fpath = g_file_get_path(file); - QString path = fpath; - g_free(fpath); - return QIcon(path); - } - return theIconTheme->fallbackIcon_; -} - - -// static -IconCacheData* IconTheme::ensureCacheData(FmIcon* fmicon) { - IconCacheData* data = reinterpret_cast(fm_icon_get_user_data(fmicon)); - if(!data) { // we don't have a cache yet - data = new IconCacheData(); - GIcon* gicon = G_ICON(fmicon); - if(G_IS_EMBLEMED_ICON(gicon)) { // special handling for emblemed icon - GList* emblems = g_emblemed_icon_get_emblems(G_EMBLEMED_ICON(gicon)); - for(GList* l = emblems; l; l = l->next) { - GIcon* emblem_gicon = g_emblem_get_icon(G_EMBLEM(l->data)); - data->emblems.append(Icon::fromGicon(emblem_gicon)); - } - gicon = g_emblemed_icon_get_icon(G_EMBLEMED_ICON(gicon)); // get an emblemless GIcon + if(QIcon::themeName() != theIconTheme->currentThemeName_) { + // if the icon theme is changed + theIconTheme->currentThemeName_ = QIcon::themeName(); + // invalidate the cached data + Fm::IconInfo::updateQIcons(); + Q_EMIT theIconTheme->changed(); } - data->qicon = convertFromGIconWithoutEmblems(gicon); - fm_icon_set_user_data(fmicon, data); // store it in FmIcon - } - return data; -} - -//static -QIcon IconTheme::icon(FmIcon* fmicon) { - IconCacheData* data = ensureCacheData(fmicon); - return data->qicon; -} - -//static -QIcon IconTheme::icon(GIcon* gicon) { - if(G_IS_EMBLEMED_ICON(gicon)) // get an emblemless GIcon - gicon = g_emblemed_icon_get_icon(G_EMBLEMED_ICON(gicon)); - if(G_IS_THEMED_ICON(gicon)) { - FmIcon* fmicon = fm_icon_from_gicon(gicon); - QIcon qicon = icon(fmicon); - fm_icon_unref(fmicon); - return qicon; - } - else if(G_IS_FILE_ICON(gicon)) { - // we do not map GFileIcon to FmIcon deliberately. - return convertFromGIconWithoutEmblems(gicon); - } - return theIconTheme->fallbackIcon_; -} - -// static -QList IconTheme::emblems(FmIcon* fmicon) { - IconCacheData* data = ensureCacheData(fmicon); - return data->emblems; -} - -//static -QList IconTheme::emblems(GIcon* gicon) { - if(G_IS_EMBLEMED_ICON(gicon)) { // if this gicon contains emblems - Icon fmicon = Icon::fromGicon(gicon); - return emblems(fmicon.dataPtr()); - } - return QList(); } // this method is called whenever there is an event on the QDesktopWidget object. bool IconTheme::eventFilter(QObject* obj, QEvent* event) { - // we're only interested in the StyleChange event. - if(event->type() == QEvent::StyleChange) { - checkChanged(); // check if the icon theme is changed - } - return QObject::eventFilter(obj, event); + // we're only interested in the StyleChange event. + // FIXME: QEvent::ThemeChange seems to be interal to Qt 5 and is not documented + if(event->type() == QEvent::StyleChange || event->type() == QEvent::ThemeChange) { + checkChanged(); // check if the icon theme is changed + } + return QObject::eventFilter(obj, event); } diff --git a/src/icontheme.h b/src/icontheme.h index df613ef..8337e8b 100644 --- a/src/icontheme.h +++ b/src/icontheme.h @@ -25,49 +25,27 @@ #include #include #include "libfm/fm.h" -#include "icon.h" namespace Fm { -// NOTE: -// Qt seems to has its own QIcon pixmap caching mechanism internally. -// Besides, it also caches QIcon objects created by QIcon::fromTheme(). -// So maybe we should not duplicate the work. -// See http://qt.gitorious.org/qt/qt/blobs/4.8/src/gui/image/qicon.cpp -// QPixmap QPixmapIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state). -// In addition, QPixmap is actually stored in X11 server, not client side. -// Hence maybe we should not cache too many pixmaps, I guess? -// Let's have Qt do its work and only translate GIcon to QIcon here. - -// Nice article about QPixmap from KDE: http://techbase.kde.org/Development/Tutorials/Graphics/Performance - -class IconCacheData; - class LIBFM_QT_API IconTheme: public QObject { - Q_OBJECT + Q_OBJECT public: - IconTheme(); - ~IconTheme(); + IconTheme(); + ~IconTheme(); - static IconTheme* instance(); - static QIcon icon(FmIcon* fmicon); - static QIcon icon(GIcon* gicon); - static QList emblems(FmIcon* fmicon); - static QList emblems(GIcon* gicon); + static IconTheme* instance(); + + static void checkChanged(); // check if current icon theme name is changed - static void checkChanged(); // check if current icon theme name is changed Q_SIGNALS: - void changed(); // emitted when the name of current icon theme is changed + void changed(); // emitted when the name of current icon theme is changed protected: - bool eventFilter(QObject *obj, QEvent *event); - static QIcon convertFromGIconWithoutEmblems(GIcon* gicon); - static QIcon iconFromNames(const char * const * names); - static IconCacheData* ensureCacheData(FmIcon* fmicon); + bool eventFilter(QObject* obj, QEvent* event); -protected: - QIcon fallbackIcon_; - QString currentThemeName_; +private: + QString currentThemeName_; }; } diff --git a/src/job.h b/src/job.h deleted file mode 100644 index 12e337b..0000000 --- a/src/job.h +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_JOB_H__ -#define __LIBFM_QT_FM_JOB_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - -class LIBFM_QT_API Job { -public: - - - // default constructor - Job() { - dataPtr_ = nullptr; - } - - - Job(FmJob* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(g_object_ref(dataPtr)) : nullptr; - } - - - // copy constructor - Job(const Job& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - Job(Job&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - virtual ~Job() { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static Job wrapPtr(FmJob* dataPtr) { - Job obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmJob* takeDataPtr() { - FmJob* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmJob* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmJob*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - Job& operator=(const Job& other) { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - Job& operator=(Job&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - void resume(void) { - fm_job_resume(dataPtr()); - } - - - bool pause(void) { - return fm_job_pause(dataPtr()); - } - - - int askValist(const char* question, va_list options) { - return fm_job_ask_valist(dataPtr(), question, options); - } - - - int askv(const char* question, gchar* const* options) { - return fm_job_askv(dataPtr(), question, options); - } - - - int ask(const char* question, ... ) { - - int ret; - va_list args; - va_start (args, question); - ret = fm_job_ask_valist(dataPtr(), question, args); - va_end (args); - return ret; - - } - - - FmJobErrorAction emitError(GError* err, FmJobErrorSeverity severity) { - return fm_job_emit_error(dataPtr(), err, severity); - } - - - void finish(void) { - fm_job_finish(dataPtr()); - } - - - void setCancellable(GCancellable* cancellable) { - fm_job_set_cancellable(dataPtr(), cancellable); - } - - - GCancellable* getCancellable(void) { - return fm_job_get_cancellable(dataPtr()); - } - - - void initCancellable(void) { - fm_job_init_cancellable(dataPtr()); - } - - - gpointer callMainThread(FmJobCallMainThreadFunc func, gpointer user_data) { - return fm_job_call_main_thread(dataPtr(), func, user_data); - } - - - void cancel(void) { - fm_job_cancel(dataPtr()); - } - - - bool runSyncWithMainloop(void) { - return fm_job_run_sync_with_mainloop(dataPtr()); - } - - - bool runSync(void) { - return fm_job_run_sync(dataPtr()); - } - - - bool runAsync(void) { - return fm_job_run_async(dataPtr()); - } - - - bool isRunning(void) { - return fm_job_is_running(dataPtr()); - } - - - bool isCancelled(void) { - return fm_job_is_cancelled(dataPtr()); - } - - - // automatic type casting for GObject - operator GObject*() { - return reinterpret_cast(dataPtr_); - } - - -protected: - GObject* dataPtr_; // data pointer for the underlying C struct - -}; - - -} - -#endif // __LIBFM_QT_FM_JOB_H__ diff --git a/src/libfmqt.cpp b/src/libfmqt.cpp index 63127c2..756f76b 100644 --- a/src/libfmqt.cpp +++ b/src/libfmqt.cpp @@ -20,62 +20,76 @@ #include #include "libfmqt.h" #include +#include #include "icontheme.h" -#include "thumbnailloader.h" +#include "core/thumbnailer.h" #include "xdndworkaround.h" namespace Fm { struct LibFmQtData { - LibFmQtData(); - ~LibFmQtData(); + LibFmQtData(); + ~LibFmQtData(); - IconTheme* iconTheme; - ThumbnailLoader* thumbnailLoader; - QTranslator translator; - XdndWorkaround workaround; - int refCount; - Q_DISABLE_COPY(LibFmQtData) + IconTheme* iconTheme; + QTranslator translator; + XdndWorkaround workaround; + int refCount; + Q_DISABLE_COPY(LibFmQtData) }; -static LibFmQtData* theLibFmData = NULL; +static LibFmQtData* theLibFmData = nullptr; + +static GFile* lookupCustomUri(GVfs * /*vfs*/, const char *identifier, gpointer /*user_data*/) { + GFile* gf = fm_file_new_for_uri(identifier); + return gf; +} LibFmQtData::LibFmQtData(): refCount(1) { #if !GLIB_CHECK_VERSION(2, 36, 0) - g_type_init(); + g_type_init(); #endif - fm_init(NULL); - // turn on glib debug message - // g_setenv("G_MESSAGES_DEBUG", "all", true); - iconTheme = new IconTheme(); - thumbnailLoader = new ThumbnailLoader(); - translator.load("libfm-qt_" + QLocale::system().name(), LIBFM_QT_DATA_DIR "/translations"); + fm_init(nullptr); + // turn on glib debug message + // g_setenv("G_MESSAGES_DEBUG", "all", true); + iconTheme = new IconTheme(); + Fm::Thumbnailer::loadAll(); + translator.load("libfm-qt_" + QLocale::system().name(), LIBFM_QT_DATA_DIR "/translations"); + + // register some URI schemes implemented by libfm + // FIXME: move these implementations into libfm-qt to avoid linking with libfm. + GVfs* vfs = g_vfs_get_default(); + g_vfs_register_uri_scheme(vfs, "menu", lookupCustomUri, nullptr, nullptr, lookupCustomUri, nullptr, nullptr); + g_vfs_register_uri_scheme(vfs, "search", lookupCustomUri, nullptr, nullptr, lookupCustomUri, nullptr, nullptr); } LibFmQtData::~LibFmQtData() { - delete iconTheme; - delete thumbnailLoader; - fm_finalize(); + GVfs* vfs = g_vfs_get_default(); + g_vfs_unregister_uri_scheme(vfs, "menu"); + g_vfs_unregister_uri_scheme(vfs, "search"); + delete iconTheme; + fm_finalize(); } LibFmQt::LibFmQt() { - if(!theLibFmData) { - theLibFmData = new LibFmQtData(); - } - else - ++theLibFmData->refCount; - d = theLibFmData; + if(!theLibFmData) { + theLibFmData = new LibFmQtData(); + } + else { + ++theLibFmData->refCount; + } + d = theLibFmData; } LibFmQt::~LibFmQt() { - if(--d->refCount == 0) { - delete d; - theLibFmData = NULL; - } + if(--d->refCount == 0) { + delete d; + theLibFmData = nullptr; + } } QTranslator* LibFmQt::translator() { - return &d->translator; + return &d->translator; } } // namespace Fm diff --git a/src/libfmqt.h b/src/libfmqt.h index 83ffd8e..e63e2db 100644 --- a/src/libfmqt.h +++ b/src/libfmqt.h @@ -32,14 +32,14 @@ struct LibFmQtData; class LIBFM_QT_API LibFmQt { public: - LibFmQt(); - ~LibFmQt(); + explicit LibFmQt(); + ~LibFmQt(); - QTranslator* translator(); + QTranslator* translator(); private: - LibFmQt(LibFmQt& other); // disable copy - LibFmQtData* d; + LibFmQt(LibFmQt& other); // disable copy + LibFmQtData* d; }; } diff --git a/src/list.h b/src/list.h deleted file mode 100644 index e207004..0000000 --- a/src/list.h +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_LIST_H__ -#define __LIBFM_QT_FM_LIST_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - -class LIBFM_QT_API List { -public: - - - List(FmListFuncs* funcs) { - dataPtr_ = reinterpret_cast(fm_list_new(funcs)); - } - - - // default constructor - List() { - dataPtr_ = nullptr; - } - - - List(FmList* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(fm_list_ref(dataPtr)) : nullptr; - } - - - // copy constructor - List(const List& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(fm_list_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - List(List&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - ~List() { - if(dataPtr_ != nullptr) { - fm_list_unref(dataPtr_); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static List wrapPtr(FmList* dataPtr) { - List obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmList* takeDataPtr() { - FmList* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmList* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmList*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - List& operator=(const List& other) { - if(dataPtr_ != nullptr) { - fm_list_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(fm_list_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - List& operator=(List&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - void deleteLink(GList* l_) { - fm_list_delete_link(dataPtr(), l_); - } - - - void removeAll(gpointer data) { - fm_list_remove_all(dataPtr(), data); - } - - - void remove(gpointer data) { - fm_list_remove(dataPtr(), data); - } - - - void clear(void) { - fm_list_clear(dataPtr()); - } - - - -private: - FmList* dataPtr_; // data pointer for the underlying C struct - -}; - - -} - -#endif // __LIBFM_QT_FM_LIST_H__ diff --git a/src/mimetype.h b/src/mimetype.h deleted file mode 100644 index 5cf402d..0000000 --- a/src/mimetype.h +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_MIME_TYPE_H__ -#define __LIBFM_QT_FM_MIME_TYPE_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - -class LIBFM_QT_API MimeType { -public: - - - // default constructor - MimeType() { - dataPtr_ = nullptr; - } - - - MimeType(FmMimeType* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(fm_mime_type_ref(dataPtr)) : nullptr; - } - - - // copy constructor - MimeType(const MimeType& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(fm_mime_type_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - MimeType(MimeType&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - ~MimeType() { - if(dataPtr_ != nullptr) { - fm_mime_type_unref(dataPtr_); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static MimeType wrapPtr(FmMimeType* dataPtr) { - MimeType obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmMimeType* takeDataPtr() { - FmMimeType* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmMimeType* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmMimeType*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - MimeType& operator=(const MimeType& other) { - if(dataPtr_ != nullptr) { - fm_mime_type_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(fm_mime_type_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - MimeType& operator=(MimeType&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - void removeThumbnailer(gpointer thumbnailer) { - fm_mime_type_remove_thumbnailer(dataPtr(), thumbnailer); - } - - - void addThumbnailer(gpointer thumbnailer) { - fm_mime_type_add_thumbnailer(dataPtr(), thumbnailer); - } - - - GList* getThumbnailersList(void) { - return fm_mime_type_get_thumbnailers_list(dataPtr()); - } - - - FmIcon* getIcon(void) { - return fm_mime_type_get_icon(dataPtr()); - } - - - static MimeType fromName(const char* type) { - return MimeType::wrapPtr(fm_mime_type_from_name(type)); - } - - - static MimeType fromNativeFile(const char* file_path, const char* base_name, struct stat* pstat) { - return MimeType::wrapPtr(fm_mime_type_from_native_file(file_path, base_name, pstat)); - } - - - static MimeType fromFileName(const char* ufile_name) { - return MimeType::wrapPtr(fm_mime_type_from_file_name(ufile_name)); - } - - - -private: - FmMimeType* dataPtr_; // data pointer for the underlying C struct - -}; - - -} - -#endif // __LIBFM_QT_FM_MIME_TYPE_H__ diff --git a/src/mountoperation.cpp b/src/mountoperation.cpp index b7fb9cc..f9a1a5b 100644 --- a/src/mountoperation.cpp +++ b/src/mountoperation.cpp @@ -30,76 +30,76 @@ namespace Fm { MountOperation::MountOperation(bool interactive, QWidget* parent): - QObject(parent), - op(g_mount_operation_new()), - cancellable_(g_cancellable_new()), - running(false), - interactive_(interactive), - eventLoop(NULL), - autoDestroy_(true) { - - g_signal_connect(op, "ask-password", G_CALLBACK(onAskPassword), this); - g_signal_connect(op, "ask-question", G_CALLBACK(onAskQuestion), this); - // g_signal_connect(op, "reply", G_CALLBACK(onReply), this); + QObject(parent), + op(g_mount_operation_new()), + cancellable_(g_cancellable_new()), + running(false), + interactive_(interactive), + eventLoop(nullptr), + autoDestroy_(true) { + + g_signal_connect(op, "ask-password", G_CALLBACK(onAskPassword), this); + g_signal_connect(op, "ask-question", G_CALLBACK(onAskQuestion), this); + // g_signal_connect(op, "reply", G_CALLBACK(onReply), this); #if GLIB_CHECK_VERSION(2, 20, 0) - g_signal_connect(op, "aborted", G_CALLBACK(onAbort), this); + g_signal_connect(op, "aborted", G_CALLBACK(onAbort), this); #endif #if GLIB_CHECK_VERSION(2, 22, 0) - g_signal_connect(op, "show-processes", G_CALLBACK(onShowProcesses), this); + g_signal_connect(op, "show-processes", G_CALLBACK(onShowProcesses), this); #endif #if GLIB_CHECK_VERSION(2, 34, 0) - g_signal_connect(op, "show-unmount-progress", G_CALLBACK(onShowUnmountProgress), this); + g_signal_connect(op, "show-unmount-progress", G_CALLBACK(onShowUnmountProgress), this); #endif } MountOperation::~MountOperation() { - qDebug("delete MountOperation"); - if(cancellable_) { - cancel(); - g_object_unref(cancellable_); - } - - if(eventLoop) { // if wait() is called to block the main loop, but the event loop is still running - // NOTE: is this possible? - eventLoop->exit(1); - } - - if(op) { - g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onAskPassword), this); - g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onAskQuestion), this); + qDebug("delete MountOperation"); + if(cancellable_) { + cancel(); + g_object_unref(cancellable_); + } + + if(eventLoop) { // if wait() is called to block the main loop, but the event loop is still running + // NOTE: is this possible? + eventLoop->exit(1); + } + + if(op) { + g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onAskPassword), this); + g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onAskQuestion), this); #if GLIB_CHECK_VERSION(2, 20, 0) - g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onAbort), this); + g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onAbort), this); #endif #if GLIB_CHECK_VERSION(2, 22, 0) - g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onShowProcesses), this); + g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onShowProcesses), this); #endif #if GLIB_CHECK_VERSION(2, 34, 0) - g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onShowUnmountProgress), this); + g_signal_handlers_disconnect_by_func(op, (gpointer)G_CALLBACK(onShowUnmountProgress), this); #endif - g_object_unref(op); - } - // qDebug("MountOperation deleted"); + g_object_unref(op); + } + // qDebug("MountOperation deleted"); } -void MountOperation::onAbort(GMountOperation* _op, MountOperation* pThis) { +void MountOperation::onAbort(GMountOperation* /*_op*/, MountOperation* /*pThis*/) { } -void MountOperation::onAskPassword(GMountOperation* _op, gchar* message, gchar* default_user, gchar* default_domain, GAskPasswordFlags flags, MountOperation* pThis) { - qDebug("ask password"); - MountOperationPasswordDialog dlg(pThis, flags); - dlg.setMessage(QString::fromUtf8(message)); - dlg.setDefaultUser(QString::fromUtf8(default_user)); - dlg.setDefaultDomain(QString::fromUtf8(default_domain)); - dlg.exec(); +void MountOperation::onAskPassword(GMountOperation* /*_op*/, gchar* message, gchar* default_user, gchar* default_domain, GAskPasswordFlags flags, MountOperation* pThis) { + qDebug("ask password"); + MountOperationPasswordDialog dlg(pThis, flags); + dlg.setMessage(QString::fromUtf8(message)); + dlg.setDefaultUser(QString::fromUtf8(default_user)); + dlg.setDefaultDomain(QString::fromUtf8(default_domain)); + dlg.exec(); } -void MountOperation::onAskQuestion(GMountOperation* _op, gchar* message, GStrv choices, MountOperation* pThis) { - qDebug("ask question"); - MountOperationQuestionDialog dialog(pThis, message, choices); - dialog.exec(); +void MountOperation::onAskQuestion(GMountOperation* /*_op*/, gchar* message, GStrv choices, MountOperation* pThis) { + qDebug("ask question"); + MountOperationQuestionDialog dialog(pThis, message, choices); + dialog.exec(); } /* @@ -108,120 +108,125 @@ void MountOperation::onReply(GMountOperation* _op, GMountOperationResult result, } */ -void MountOperation::onShowProcesses(GMountOperation* _op, gchar* message, GArray* processes, GStrv choices, MountOperation* pThis) { - qDebug("show processes"); +void MountOperation::onShowProcesses(GMountOperation* /*_op*/, gchar* /*message*/, GArray* /*processes*/, GStrv /*choices*/, MountOperation* /*pThis*/) { + qDebug("show processes"); } -void MountOperation::onShowUnmountProgress(GMountOperation* _op, gchar* message, gint64 time_left, gint64 bytes_left, MountOperation* pThis) { - qDebug("show unmount progress"); +void MountOperation::onShowUnmountProgress(GMountOperation* /*_op*/, gchar* /*message*/, gint64 /*time_left*/, gint64 /*bytes_left*/, MountOperation* /*pThis*/) { + qDebug("show unmount progress"); } void MountOperation::onEjectMountFinished(GMount* mount, GAsyncResult* res, QPointer< MountOperation >* pThis) { - if(*pThis) { - GError* error = NULL; - g_mount_eject_with_operation_finish(mount, res, &error); - (*pThis)->handleFinish(error); - } - delete pThis; + if(*pThis) { + GError* error = nullptr; + g_mount_eject_with_operation_finish(mount, res, &error); + (*pThis)->handleFinish(error); + } + delete pThis; } void MountOperation::onEjectVolumeFinished(GVolume* volume, GAsyncResult* res, QPointer< MountOperation >* pThis) { - if(*pThis) { - GError* error = NULL; - g_volume_eject_with_operation_finish(volume, res, &error); - (*pThis)->handleFinish(error); - } - delete pThis; + if(*pThis) { + GError* error = nullptr; + g_volume_eject_with_operation_finish(volume, res, &error); + (*pThis)->handleFinish(error); + } + delete pThis; } void MountOperation::onMountFileFinished(GFile* file, GAsyncResult* res, QPointer< MountOperation >* pThis) { - if(*pThis) { - GError* error = NULL; - g_file_mount_enclosing_volume_finish(file, res, &error); - (*pThis)->handleFinish(error); - } - delete pThis; + if(*pThis) { + GError* error = nullptr; + g_file_mount_enclosing_volume_finish(file, res, &error); + (*pThis)->handleFinish(error); + } + delete pThis; } void MountOperation::onMountVolumeFinished(GVolume* volume, GAsyncResult* res, QPointer< MountOperation >* pThis) { - if(*pThis) { - GError* error = NULL; - g_volume_mount_finish(volume, res, &error); - (*pThis)->handleFinish(error); - } - delete pThis; + if(*pThis) { + GError* error = nullptr; + g_volume_mount_finish(volume, res, &error); + (*pThis)->handleFinish(error); + } + delete pThis; } void MountOperation::onUnmountMountFinished(GMount* mount, GAsyncResult* res, QPointer< MountOperation >* pThis) { - if(*pThis) { - GError* error = NULL; - g_mount_unmount_with_operation_finish(mount, res, &error); - (*pThis)->handleFinish(error); - } - delete pThis; + if(*pThis) { + GError* error = nullptr; + g_mount_unmount_with_operation_finish(mount, res, &error); + (*pThis)->handleFinish(error); + } + delete pThis; } void MountOperation::handleFinish(GError* error) { - qDebug("operation finished: %p", error); - if(error) { - bool showError = interactive_; - if(error->domain == G_IO_ERROR) { - if(error->code == G_IO_ERROR_FAILED) { - // Generate a more human-readable error message instead of using a gvfs one. - // The original error message is something like: - // Error unmounting: umount exited with exit code 1: - // helper failed with: umount: only root can unmount - // UUID=18cbf00c-e65f-445a-bccc-11964bdea05d from /media/sda4 */ - // Why they pass this back to us? This is not human-readable for the users at all. - if(strstr(error->message, "only root can ")) { - g_free(error->message); - error->message = g_strdup(_("Only system administrators have the permission to do this.")); + qDebug("operation finished: %p", static_cast(error)); + if(error) { + bool showError = interactive_; + if(error->domain == G_IO_ERROR) { + if(error->code == G_IO_ERROR_FAILED) { + // Generate a more human-readable error message instead of using a gvfs one. + // The original error message is something like: + // Error unmounting: umount exited with exit code 1: + // helper failed with: umount: only root can unmount + // UUID=18cbf00c-e65f-445a-bccc-11964bdea05d from /media/sda4 */ + // Why they pass this back to us? This is not human-readable for the users at all. + if(strstr(error->message, "only root can ")) { + g_free(error->message); + error->message = g_strdup(_("Only system administrators have the permission to do this.")); + } + } + else if(error->code == G_IO_ERROR_FAILED_HANDLED) { + showError = false; + } + } + if(showError) { + QMessageBox::critical(nullptr, QObject::tr("Error"), QString::fromUtf8(error->message)); } - } - else if(error->code == G_IO_ERROR_FAILED_HANDLED) - showError = false; } - if(showError) - QMessageBox::critical(NULL, QObject::tr("Error"), QString::fromUtf8(error->message)); - } - Q_EMIT finished(error); + Q_EMIT finished(error); - if(eventLoop) { // if wait() is called to block the main loop - eventLoop->exit(error != NULL ? 1 : 0); - eventLoop = NULL; - } + if(eventLoop) { // if wait() is called to block the main loop + eventLoop->exit(error != nullptr ? 1 : 0); + eventLoop = nullptr; + } - if(error) - g_error_free(error); + if(error) { + g_error_free(error); + } - // free ourself here!! - if(autoDestroy_) - deleteLater(); + // free ourself here!! + if(autoDestroy_) { + deleteLater(); + } } void MountOperation::prepareUnmount(GMount* mount) { - /* ensure that CWD is not on the mounted filesystem. */ - char* cwd_str = g_get_current_dir(); - GFile* cwd = g_file_new_for_path(cwd_str); - GFile* root = g_mount_get_root(mount); - g_free(cwd_str); - /* FIXME: This cannot cover 100% cases since symlinks are not checked. - * There may be other cases that cwd is actually under mount root - * but checking prefix is not enough. We already did our best, though. */ - if(g_file_has_prefix(cwd, root)) - g_chdir("/"); - g_object_unref(cwd); - g_object_unref(root); + /* ensure that CWD is not on the mounted filesystem. */ + char* cwd_str = g_get_current_dir(); + GFile* cwd = g_file_new_for_path(cwd_str); + GFile* root = g_mount_get_root(mount); + g_free(cwd_str); + /* FIXME: This cannot cover 100% cases since symlinks are not checked. + * There may be other cases that cwd is actually under mount root + * but checking prefix is not enough. We already did our best, though. */ + if(g_file_has_prefix(cwd, root)) { + g_chdir("/"); + } + g_object_unref(cwd); + g_object_unref(root); } // block the operation used an internal QEventLoop and returns // only after the whole operation is finished. bool MountOperation::wait() { - QEventLoop loop; - eventLoop = &loop; - int exitCode = loop.exec(); - return exitCode == 0 ? true : false; + QEventLoop loop; + eventLoop = &loop; + int exitCode = loop.exec(); + return exitCode == 0 ? true : false; } } // namespace Fm diff --git a/src/mountoperation.h b/src/mountoperation.h index 00b5849..97e558f 100644 --- a/src/mountoperation.h +++ b/src/mountoperation.h @@ -28,6 +28,8 @@ #include #include +#include "core/filepath.h" + class QEventLoop; namespace Fm { @@ -40,114 +42,114 @@ namespace Fm { // indeed causes some problems. :-( class LIBFM_QT_API MountOperation: public QObject { -Q_OBJECT + Q_OBJECT public: - explicit MountOperation(bool interactive = true, QWidget* parent = 0); - ~MountOperation(); - - void mount(FmPath* path) { - GFile* gf = fm_path_to_gfile(path); - g_file_mount_enclosing_volume(gf, G_MOUNT_MOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onMountFileFinished, new QPointer(this)); - g_object_unref(gf); - } - - void mount(GVolume* volume) { - g_volume_mount(volume, G_MOUNT_MOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onMountVolumeFinished, new QPointer(this)); - } - - void unmount(GMount* mount) { - prepareUnmount(mount); - g_mount_unmount_with_operation(mount, G_MOUNT_UNMOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onUnmountMountFinished, new QPointer(this)); - } - - void unmount(GVolume* volume) { - GMount* mount = g_volume_get_mount(volume); - if(!mount) - return; - unmount(mount); - g_object_unref(mount); - } - - void eject(GMount* mount) { - prepareUnmount(mount); - g_mount_eject_with_operation(mount, G_MOUNT_UNMOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onEjectMountFinished, new QPointer(this)); - } - - void eject(GVolume* volume) { - GMount* mnt = g_volume_get_mount(volume); - prepareUnmount(mnt); - g_object_unref(mnt); - g_volume_eject_with_operation(volume, G_MOUNT_UNMOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onEjectVolumeFinished, new QPointer(this)); - } - - QWidget* parent() const { - return parent_; - } - - void setParent(QWidget* parent) { - parent_ = parent; - } - - GCancellable* cancellable() const { - return cancellable_; - } - - GMountOperation* mountOperation() { - return op; - } - - void cancel() { - g_cancellable_cancel(cancellable_); - } - - bool isRunning() const { - return running; - } - - // block the operation used an internal QEventLoop and returns - // only after the whole operation is finished. - bool wait(); - - bool autoDestroy() { - return autoDestroy_; - } - - void setAutoDestroy(bool destroy = true) { - autoDestroy_ = destroy; - } + explicit MountOperation(bool interactive = true, QWidget* parent = 0); + ~MountOperation(); + + void mount(const Fm::FilePath& path) { + g_file_mount_enclosing_volume(path.gfile().get(), G_MOUNT_MOUNT_NONE, op, cancellable_, + (GAsyncReadyCallback)onMountFileFinished, new QPointer(this)); + } + + void mount(GVolume* volume) { + g_volume_mount(volume, G_MOUNT_MOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onMountVolumeFinished, new QPointer(this)); + } + + void unmount(GMount* mount) { + prepareUnmount(mount); + g_mount_unmount_with_operation(mount, G_MOUNT_UNMOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onUnmountMountFinished, new QPointer(this)); + } + + void unmount(GVolume* volume) { + GMount* mount = g_volume_get_mount(volume); + if(!mount) { + return; + } + unmount(mount); + g_object_unref(mount); + } + + void eject(GMount* mount) { + prepareUnmount(mount); + g_mount_eject_with_operation(mount, G_MOUNT_UNMOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onEjectMountFinished, new QPointer(this)); + } + + void eject(GVolume* volume) { + GMount* mnt = g_volume_get_mount(volume); + prepareUnmount(mnt); + g_object_unref(mnt); + g_volume_eject_with_operation(volume, G_MOUNT_UNMOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onEjectVolumeFinished, new QPointer(this)); + } + + QWidget* parent() const { + return parent_; + } + + void setParent(QWidget* parent) { + parent_ = parent; + } + + GCancellable* cancellable() const { + return cancellable_; + } + + GMountOperation* mountOperation() { + return op; + } + + void cancel() { + g_cancellable_cancel(cancellable_); + } + + bool isRunning() const { + return running; + } + + // block the operation used an internal QEventLoop and returns + // only after the whole operation is finished. + bool wait(); + + bool autoDestroy() { + return autoDestroy_; + } + + void setAutoDestroy(bool destroy = true) { + autoDestroy_ = destroy; + } Q_SIGNALS: - void finished(GError* error = NULL); + void finished(GError* error = nullptr); private: - void prepareUnmount(GMount* mount); + void prepareUnmount(GMount* mount); - static void onAskPassword(GMountOperation *_op, gchar* message, gchar* default_user, gchar* default_domain, GAskPasswordFlags flags, MountOperation* pThis); - static void onAskQuestion(GMountOperation *_op, gchar* message, GStrv choices, MountOperation* pThis); - // static void onReply(GMountOperation *_op, GMountOperationResult result, MountOperation* pThis); + static void onAskPassword(GMountOperation* _op, gchar* message, gchar* default_user, gchar* default_domain, GAskPasswordFlags flags, MountOperation* pThis); + static void onAskQuestion(GMountOperation* _op, gchar* message, GStrv choices, MountOperation* pThis); + // static void onReply(GMountOperation *_op, GMountOperationResult result, MountOperation* pThis); - static void onAbort(GMountOperation *_op, MountOperation* pThis); - static void onShowProcesses(GMountOperation *_op, gchar* message, GArray* processes, GStrv choices, MountOperation* pThis); - static void onShowUnmountProgress(GMountOperation *_op, gchar* message, gint64 time_left, gint64 bytes_left, MountOperation* pThis); + static void onAbort(GMountOperation* _op, MountOperation* pThis); + static void onShowProcesses(GMountOperation* _op, gchar* message, GArray* processes, GStrv choices, MountOperation* pThis); + static void onShowUnmountProgress(GMountOperation* _op, gchar* message, gint64 time_left, gint64 bytes_left, MountOperation* pThis); - // it's possible that this object is freed when the callback is called by gio, so guarding with QPointer is needed here. - static void onMountFileFinished(GFile* file, GAsyncResult *res, QPointer* pThis); - static void onMountVolumeFinished(GVolume* volume, GAsyncResult *res, QPointer* pThis); - static void onUnmountMountFinished(GMount* mount, GAsyncResult *res, QPointer* pThis); - static void onEjectMountFinished(GMount* mount, GAsyncResult *res, QPointer* pThis); - static void onEjectVolumeFinished(GVolume* volume, GAsyncResult *res, QPointer* pThis); + // it's possible that this object is freed when the callback is called by gio, so guarding with QPointer is needed here. + static void onMountFileFinished(GFile* file, GAsyncResult* res, QPointer* pThis); + static void onMountVolumeFinished(GVolume* volume, GAsyncResult* res, QPointer* pThis); + static void onUnmountMountFinished(GMount* mount, GAsyncResult* res, QPointer* pThis); + static void onEjectMountFinished(GMount* mount, GAsyncResult* res, QPointer* pThis); + static void onEjectVolumeFinished(GVolume* volume, GAsyncResult* res, QPointer* pThis); - void handleFinish(GError* error); + void handleFinish(GError* error); private: - GMountOperation* op; - GCancellable* cancellable_; - QWidget* parent_; - bool running; - bool interactive_; - QEventLoop* eventLoop; - bool autoDestroy_; + GMountOperation* op; + GCancellable* cancellable_; + QWidget* parent_; + bool running; + bool interactive_; + QEventLoop* eventLoop; + bool autoDestroy_; }; } diff --git a/src/mountoperationpassworddialog.cpp b/src/mountoperationpassworddialog.cpp index f2783e3..4e7d850 100644 --- a/src/mountoperationpassworddialog.cpp +++ b/src/mountoperationpassworddialog.cpp @@ -25,101 +25,114 @@ namespace Fm { MountOperationPasswordDialog::MountOperationPasswordDialog(MountOperation* op, GAskPasswordFlags flags): - QDialog(), - mountOperation(op), - needPassword(flags & G_ASK_PASSWORD_NEED_PASSWORD ? true : false), - needUserName(flags & G_ASK_PASSWORD_NEED_USERNAME ? true : false), - needDomain(flags & G_ASK_PASSWORD_NEED_DOMAIN ? true : false), - canSavePassword(flags & G_ASK_PASSWORD_SAVING_SUPPORTED ? true : false), - canAnonymous(flags & G_ASK_PASSWORD_ANONYMOUS_SUPPORTED ? true : false) { - - ui = new Ui::MountOperationPasswordDialog(); - ui->setupUi(this); - - // change the text of Ok button to Connect - ui->buttonBox->buttons().first()->setText(tr("&Connect")); - connect(ui->Anonymous, &QAbstractButton::toggled, this, &MountOperationPasswordDialog::onAnonymousToggled); - - if(canAnonymous) { - // select ananymous by default if applicable. - ui->Anonymous->setChecked(true); - } - else { - ui->Anonymous->setEnabled(false); - } - if(!needUserName) { - ui->username->setEnabled(false); - } - if(!needPassword) { - ui->password->setEnabled(false); - } - if(!needDomain) { - ui->domain->hide(); - ui->domainLabel->hide(); - } - if(canSavePassword) { - ui->sessionPassword->setChecked(true); - } - else { - ui->storePassword->setEnabled(false); - ui->sessionPassword->setEnabled(false); - ui->forgetPassword->setChecked(true); - } + QDialog(), + mountOperation(op), + needPassword(flags & G_ASK_PASSWORD_NEED_PASSWORD ? true : false), + needUserName(flags & G_ASK_PASSWORD_NEED_USERNAME ? true : false), + needDomain(flags & G_ASK_PASSWORD_NEED_DOMAIN ? true : false), + canSavePassword(flags & G_ASK_PASSWORD_SAVING_SUPPORTED ? true : false), + canAnonymous(flags & G_ASK_PASSWORD_ANONYMOUS_SUPPORTED ? true : false) { + + ui = new Ui::MountOperationPasswordDialog(); + ui->setupUi(this); + + // change the text of Ok button to Connect + ui->buttonBox->buttons().constFirst()->setText(tr("&Connect")); + connect(ui->Anonymous, &QAbstractButton::toggled, this, &MountOperationPasswordDialog::onAnonymousToggled); + + if(canAnonymous) { + // select ananymous by default if applicable. + ui->Anonymous->setChecked(true); + } + else { + ui->Anonymous->setEnabled(false); + ui->asUser->setChecked(true); + } + if(!needUserName) { + ui->username->setEnabled(false); + } + if(needPassword) { + if(!needUserName) { + ui->password->setFocus(); + } + } + else { + ui->password->setEnabled(false); + } + if(!needDomain) { + ui->domain->hide(); + ui->domainLabel->hide(); + } + if(canSavePassword) { + ui->sessionPassword->setChecked(true); + } + else { + ui->storePassword->setEnabled(false); + ui->sessionPassword->setEnabled(false); + ui->forgetPassword->setChecked(true); + } } MountOperationPasswordDialog::~MountOperationPasswordDialog() { - delete ui; + delete ui; } void MountOperationPasswordDialog::onAnonymousToggled(bool checked) { - // disable username/password entries if anonymous mode is used - bool useUserPassword = !checked; - if(needUserName) - ui->username->setEnabled(useUserPassword); - if(needPassword) - ui->password->setEnabled(useUserPassword); - if(needDomain) - ui->domain->setEnabled(useUserPassword); - - if(canSavePassword) { - ui->forgetPassword->setEnabled(useUserPassword); - ui->sessionPassword->setEnabled(useUserPassword); - ui->storePassword->setEnabled(useUserPassword); - } + // disable username/password entries if anonymous mode is used + bool useUserPassword = !checked; + if(needUserName) { + ui->username->setEnabled(useUserPassword); + } + if(needPassword) { + ui->password->setEnabled(useUserPassword); + } + if(needDomain) { + ui->domain->setEnabled(useUserPassword); + } + + if(canSavePassword) { + ui->forgetPassword->setEnabled(useUserPassword); + ui->sessionPassword->setEnabled(useUserPassword); + ui->storePassword->setEnabled(useUserPassword); + } } void MountOperationPasswordDialog::setMessage(QString message) { - ui->message->setText(message); + ui->message->setText(message); } void MountOperationPasswordDialog::setDefaultDomain(QString domain) { - ui->domain->setText(domain); + ui->domain->setText(domain); } void MountOperationPasswordDialog::setDefaultUser(QString user) { - ui->username->setText(user); + ui->username->setText(user); } void MountOperationPasswordDialog::done(int r) { - GMountOperation* gmop = mountOperation->mountOperation(); - - if(r == QDialog::Accepted) { - - if(needUserName) - g_mount_operation_set_username(gmop, ui->username->text().toUtf8()); - if(needDomain) - g_mount_operation_set_domain(gmop, ui->domain->text().toUtf8()); - if(needPassword) - g_mount_operation_set_password(gmop, ui->password->text().toUtf8()); - if(canAnonymous) - g_mount_operation_set_anonymous(gmop, ui->Anonymous->isChecked()); - - g_mount_operation_reply(gmop, G_MOUNT_OPERATION_HANDLED); - } - else { - g_mount_operation_reply(gmop, G_MOUNT_OPERATION_ABORTED); - } - QDialog::done(r); + GMountOperation* gmop = mountOperation->mountOperation(); + + if(r == QDialog::Accepted) { + + if(needUserName) { + g_mount_operation_set_username(gmop, ui->username->text().toUtf8()); + } + if(needDomain) { + g_mount_operation_set_domain(gmop, ui->domain->text().toUtf8()); + } + if(needPassword) { + g_mount_operation_set_password(gmop, ui->password->text().toUtf8()); + } + if(canAnonymous) { + g_mount_operation_set_anonymous(gmop, ui->Anonymous->isChecked()); + } + + g_mount_operation_reply(gmop, G_MOUNT_OPERATION_HANDLED); + } + else { + g_mount_operation_reply(gmop, G_MOUNT_OPERATION_ABORTED); + } + QDialog::done(r); } } // namespace Fm diff --git a/src/mountoperationpassworddialog_p.h b/src/mountoperationpassworddialog_p.h index 10ff58b..1fd6cd1 100644 --- a/src/mountoperationpassworddialog_p.h +++ b/src/mountoperationpassworddialog_p.h @@ -26,37 +26,37 @@ #include namespace Ui { - class MountOperationPasswordDialog; -}; +class MountOperationPasswordDialog; +} namespace Fm { class MountOperation; class MountOperationPasswordDialog : public QDialog { -Q_OBJECT + Q_OBJECT public: - explicit MountOperationPasswordDialog(MountOperation* op, GAskPasswordFlags flags); - virtual ~MountOperationPasswordDialog(); + explicit MountOperationPasswordDialog(MountOperation* op, GAskPasswordFlags flags); + virtual ~MountOperationPasswordDialog(); - void setMessage(QString message); - void setDefaultUser(QString user); - void setDefaultDomain(QString domain); + void setMessage(QString message); + void setDefaultUser(QString user); + void setDefaultDomain(QString domain); - virtual void done(int r); + virtual void done(int r); private Q_SLOTS: - void onAnonymousToggled(bool checked); + void onAnonymousToggled(bool checked); private: - Ui::MountOperationPasswordDialog* ui; - MountOperation* mountOperation; - bool needPassword; - bool needUserName; - bool needDomain; - bool canSavePassword; - bool canAnonymous; + Ui::MountOperationPasswordDialog* ui; + MountOperation* mountOperation; + bool needPassword; + bool needUserName; + bool needDomain; + bool canSavePassword; + bool canAnonymous; }; } diff --git a/src/mountoperationquestiondialog.cpp b/src/mountoperationquestiondialog.cpp index a409e28..3679740 100644 --- a/src/mountoperationquestiondialog.cpp +++ b/src/mountoperationquestiondialog.cpp @@ -25,47 +25,44 @@ namespace Fm { MountOperationQuestionDialog::MountOperationQuestionDialog(MountOperation* op, gchar* message, GStrv choices): - QMessageBox(), - mountOperation(op) { + QMessageBox(), + mountOperation(op) { - setIcon(QMessageBox::Question); - setText(QString::fromUtf8(message)); + setIcon(QMessageBox::Question); + setText(QString::fromUtf8(message)); - choiceCount = g_strv_length(choices); - choiceButtons = new QAbstractButton*[choiceCount]; - for(int i = 0; i < choiceCount; ++i) { - // It's not allowed to add custom buttons without standard roles - // to QMessageBox. So we set role of all buttons to AcceptRole and - // handle their clicked() signals in our own slots. - // When anyone of the buttons is clicked, exec() always returns "accept". - QPushButton* button = new QPushButton(QString::fromUtf8(choices[i])); - addButton(button, QMessageBox::AcceptRole); - choiceButtons[i] = button; - } - connect(this, &MountOperationQuestionDialog::buttonClicked, this, &MountOperationQuestionDialog::onButtonClicked); + choiceCount = g_strv_length(choices); + choiceButtons = new QAbstractButton*[choiceCount]; + for(int i = 0; i < choiceCount; ++i) { + // It's not allowed to add custom buttons without standard roles + // to QMessageBox. So we set role of all buttons to AcceptRole. + // When any of the set buttons is clicked, exec() always returns "accept". + QPushButton* button = new QPushButton(QString::fromUtf8(choices[i])); + addButton(button, QMessageBox::AcceptRole); + choiceButtons[i] = button; + } } MountOperationQuestionDialog::~MountOperationQuestionDialog() { - delete []choiceButtons; + delete []choiceButtons; } void MountOperationQuestionDialog::done(int r) { - if(r != QDialog::Accepted) { GMountOperation* op = mountOperation->mountOperation(); - g_mount_operation_reply(op, G_MOUNT_OPERATION_ABORTED); - } - QDialog::done(r); + + g_mount_operation_set_choice(op, r); + g_mount_operation_reply(op, G_MOUNT_OPERATION_HANDLED); + + QDialog::done(r); } -void MountOperationQuestionDialog::onButtonClicked(QAbstractButton* button) { - GMountOperation* op = mountOperation->mountOperation(); - for(int i = 0; i < choiceCount; ++i) { - if(choiceButtons[i] == button) { - g_mount_operation_set_choice(op, i); - g_mount_operation_reply(op, G_MOUNT_OPERATION_HANDLED); - break; - } - } +void MountOperationQuestionDialog::closeEvent(QCloseEvent *event) +{ + GMountOperation* op = mountOperation->mountOperation(); + + g_mount_operation_reply(op, G_MOUNT_OPERATION_ABORTED); + + event->accept(); } } // namespace Fm diff --git a/src/mountoperationquestiondialog_p.h b/src/mountoperationquestiondialog_p.h index b0a4bdb..8c6a810 100644 --- a/src/mountoperationquestiondialog_p.h +++ b/src/mountoperationquestiondialog_p.h @@ -22,6 +22,7 @@ #define FM_MOUNTOPERATIONQUESTIONDIALOG_H #include "libfmqtglobals.h" +#include #include #include @@ -30,20 +31,18 @@ namespace Fm { class MountOperation; class MountOperationQuestionDialog : public QMessageBox { -Q_OBJECT + Q_OBJECT public: - MountOperationQuestionDialog(MountOperation* op, gchar* message, GStrv choices); - virtual ~MountOperationQuestionDialog(); + MountOperationQuestionDialog(MountOperation* op, gchar* message, GStrv choices); + virtual ~MountOperationQuestionDialog(); - virtual void done(int r); - -private Q_SLOTS: - void onButtonClicked(QAbstractButton* button); + virtual void done(int r); + virtual void closeEvent(QCloseEvent *event); private: - MountOperation* mountOperation; - QAbstractButton** choiceButtons; - int choiceCount; + MountOperation* mountOperation; + QAbstractButton** choiceButtons; + int choiceCount; }; } diff --git a/src/navhistory.h b/src/navhistory.h deleted file mode 100644 index f88ef4c..0000000 --- a/src/navhistory.h +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_NAV_HISTORY_H__ -#define __LIBFM_QT_FM_NAV_HISTORY_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - -class LIBFM_QT_API NavHistory { -public: - - - NavHistory(void ) { - dataPtr_ = reinterpret_cast(fm_nav_history_new()); - } - - - NavHistory(FmNavHistory* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(g_object_ref(dataPtr)) : nullptr; - } - - - // copy constructor - NavHistory(const NavHistory& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - NavHistory(NavHistory&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - virtual ~NavHistory() { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static NavHistory wrapPtr(FmNavHistory* dataPtr) { - NavHistory obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmNavHistory* takeDataPtr() { - FmNavHistory* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmNavHistory* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmNavHistory*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - NavHistory& operator=(const NavHistory& other) { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - NavHistory& operator=(NavHistory&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - void setMax(guint num) { - fm_nav_history_set_max(dataPtr(), num); - } - - - void clear(void) { - fm_nav_history_clear(dataPtr()); - } - - - void chdir(FmPath* path, gint old_scroll_pos) { - fm_nav_history_chdir(dataPtr(), path, old_scroll_pos); - } - - - bool canBack(void) { - return fm_nav_history_can_back(dataPtr()); - } - - - int getScrollPos(void) { - return fm_nav_history_get_scroll_pos(dataPtr()); - } - - - FmPath* goTo(guint n, gint old_scroll_pos) { - return fm_nav_history_go_to(dataPtr(), n, old_scroll_pos); - } - - - FmPath* getNthPath(guint n) { - return fm_nav_history_get_nth_path(dataPtr(), n); - } - - - unsigned int getCurIndex(void) { - return fm_nav_history_get_cur_index(dataPtr()); - } - - - void jump(GList* l, int old_scroll_pos) { - fm_nav_history_jump(dataPtr(), l, old_scroll_pos); - } - - - void forward(int old_scroll_pos) { - fm_nav_history_forward(dataPtr(), old_scroll_pos); - } - - - bool canForward(void) { - return fm_nav_history_can_forward(dataPtr()); - } - - - void back(int old_scroll_pos) { - fm_nav_history_back(dataPtr(), old_scroll_pos); - } - - - // automatic type casting for GObject - operator GObject*() { - return reinterpret_cast(dataPtr_); - } - - -protected: - GObject* dataPtr_; // data pointer for the underlying C struct - -}; - - -} - -#endif // __LIBFM_QT_FM_NAV_HISTORY_H__ diff --git a/src/pathbar.cpp b/src/pathbar.cpp index e5d9a34..3ddb250 100644 --- a/src/pathbar.cpp +++ b/src/pathbar.cpp @@ -20,7 +20,6 @@ #include "pathbar.h" #include "pathbar_p.h" #include -#include #include #include #include @@ -36,260 +35,316 @@ namespace Fm { -PathBar::PathBar(QWidget *parent): - QWidget(parent), - tempPathEdit_(nullptr) { - - QHBoxLayout* topLayout = new QHBoxLayout(this); - topLayout->setContentsMargins(0, 0, 0, 0); - topLayout->setSpacing(0); - bool rtl(layoutDirection() == Qt::RightToLeft); - - // the arrow button used to scroll to start of the path - scrollToStart_ = new QToolButton(this); - scrollToStart_->setArrowType(rtl ? Qt::RightArrow : Qt::LeftArrow); - scrollToStart_->setAutoRepeat(true); - scrollToStart_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); - connect(scrollToStart_, &QToolButton::clicked, this, &PathBar::onScrollButtonClicked); - topLayout->addWidget(scrollToStart_); - - // there might be too many buttons when the path is long, so make it scrollable. - scrollArea_ = new QScrollArea(this); - scrollArea_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - scrollArea_->setFrameShape(QFrame::NoFrame); - scrollArea_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); - scrollArea_->verticalScrollBar()->setDisabled(true); - connect(scrollArea_->horizontalScrollBar(), &QAbstractSlider::valueChanged, this, &PathBar::setArrowEnabledState); - topLayout->addWidget(scrollArea_, 1); // stretch factor=1, make it expandable - - // the arrow button used to scroll to end of the path - scrollToEnd_ = new QToolButton(this); - scrollToEnd_->setArrowType(rtl ? Qt::LeftArrow : Qt::RightArrow); - scrollToEnd_->setAutoRepeat(true); - scrollToEnd_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); - connect(scrollToEnd_, &QToolButton::clicked, this, &PathBar::onScrollButtonClicked); - topLayout->addWidget(scrollToEnd_); - - // container widget of the path buttons - buttonsWidget_ = new QWidget(this); - buttonsWidget_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - - buttonsLayout_ = new QHBoxLayout(buttonsWidget_); - buttonsLayout_->setContentsMargins(0, 0, 0, 0); - buttonsLayout_->setSpacing(0); - buttonsLayout_->setSizeConstraint(QLayout::SetFixedSize); // required when added to scroll area according to QScrollArea doc. - scrollArea_->setWidget(buttonsWidget_); // make the buttons widget scrollable if the path is too long +PathBar::PathBar(QWidget* parent): + QWidget(parent), + tempPathEdit_(nullptr) { + + QHBoxLayout* topLayout = new QHBoxLayout(this); + topLayout->setContentsMargins(0, 0, 0, 0); + topLayout->setSpacing(0); + bool rtl(layoutDirection() == Qt::RightToLeft); + + // the arrow button used to scroll to start of the path + scrollToStart_ = new QToolButton(this); + scrollToStart_->setArrowType(rtl ? Qt::RightArrow : Qt::LeftArrow); + scrollToStart_->setAutoRepeat(true); + scrollToStart_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); + connect(scrollToStart_, &QToolButton::clicked, this, &PathBar::onScrollButtonClicked); + topLayout->addWidget(scrollToStart_); + + // there might be too many buttons when the path is long, so make it scrollable. + scrollArea_ = new QScrollArea(this); + scrollArea_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + scrollArea_->setFrameShape(QFrame::NoFrame); + scrollArea_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + scrollArea_->verticalScrollBar()->setDisabled(true); + connect(scrollArea_->horizontalScrollBar(), &QAbstractSlider::valueChanged, this, &PathBar::setArrowEnabledState); + topLayout->addWidget(scrollArea_, 1); // stretch factor=1, make it expandable + + // the arrow button used to scroll to end of the path + scrollToEnd_ = new QToolButton(this); + scrollToEnd_->setArrowType(rtl ? Qt::LeftArrow : Qt::RightArrow); + scrollToEnd_->setAutoRepeat(true); + scrollToEnd_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); + connect(scrollToEnd_, &QToolButton::clicked, this, &PathBar::onScrollButtonClicked); + topLayout->addWidget(scrollToEnd_); + + // container widget of the path buttons + buttonsWidget_ = new QWidget(this); + buttonsWidget_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + buttonsLayout_ = new QHBoxLayout(buttonsWidget_); + buttonsLayout_->setContentsMargins(0, 0, 0, 0); + buttonsLayout_->setSpacing(0); + buttonsLayout_->setSizeConstraint(QLayout::SetFixedSize); // required when added to scroll area according to QScrollArea doc. + scrollArea_->setWidget(buttonsWidget_); // make the buttons widget scrollable if the path is too long } void PathBar::resizeEvent(QResizeEvent* event) { - QWidget::resizeEvent(event); - updateScrollButtonVisibility(); + QWidget::resizeEvent(event); + updateScrollButtonVisibility(); } void PathBar::wheelEvent(QWheelEvent* event) { - QWidget::wheelEvent(event); - QAbstractSlider::SliderAction action = QAbstractSlider::SliderNoAction; - int vDelta = event->angleDelta().y(); - if(vDelta > 0) { - if(scrollToStart_->isEnabled()) - action = QAbstractSlider::SliderSingleStepSub; - } - else if(vDelta < 0) { - if(scrollToEnd_->isEnabled()) - action = QAbstractSlider::SliderSingleStepAdd; - } - scrollArea_->horizontalScrollBar()->triggerAction(action); + QWidget::wheelEvent(event); + QAbstractSlider::SliderAction action = QAbstractSlider::SliderNoAction; + int vDelta = event->angleDelta().y(); + if(vDelta > 0) { + if(scrollToStart_->isEnabled()) { + action = QAbstractSlider::SliderSingleStepSub; + } + } + else if(vDelta < 0) { + if(scrollToEnd_->isEnabled()) { + action = QAbstractSlider::SliderSingleStepAdd; + } + } + scrollArea_->horizontalScrollBar()->triggerAction(action); } -void PathBar::mousePressEvent(QMouseEvent *event) { - QWidget::mousePressEvent(event); - if(event->button() == Qt::LeftButton) { - openEditor(); - } - else if(event->button() == Qt::MiddleButton) { - PathButton* btn = qobject_cast(childAt(event->x(), event->y())); - if(btn != nullptr) { - scrollArea_->ensureWidgetVisible(btn, 0); - Q_EMIT middleClickChdir(btn->pathElement().dataPtr()); - } - } +void PathBar::mousePressEvent(QMouseEvent* event) { + QWidget::mousePressEvent(event); + if(event->button() == Qt::LeftButton) { + openEditor(); + } + else if(event->button() == Qt::MiddleButton) { + PathButton* btn = qobject_cast(childAt(event->x(), event->y())); + if(btn != nullptr) { + scrollArea_->ensureWidgetVisible(btn, + 1); // a harmless compensation for a miscalculation in Qt + Q_EMIT middleClickChdir(pathForButton(btn)); + } + } } -void PathBar::contextMenuEvent(QContextMenuEvent *event) { - QMenu* menu = new QMenu(this); - connect(menu, &QMenu::aboutToHide, menu, &QMenu::deleteLater); +void PathBar::contextMenuEvent(QContextMenuEvent* event) { + QMenu* menu = new QMenu(this); + connect(menu, &QMenu::aboutToHide, menu, &QMenu::deleteLater); - QAction* action = menu->addAction(tr("&Edit Path")); - connect(action, &QAction::triggered, this, &PathBar::openEditor); + QAction* action = menu->addAction(tr("&Edit Path")); + connect(action, &QAction::triggered, this, &PathBar::openEditor); - action = menu->addAction(tr("&Copy Path")); - connect(action, &QAction::triggered, this, &PathBar::copyPath); + action = menu->addAction(tr("&Copy Path")); + connect(action, &QAction::triggered, this, &PathBar::copyPath); - menu->popup(mapToGlobal(event->pos())); + menu->popup(mapToGlobal(event->pos())); } void PathBar::updateScrollButtonVisibility() { - // Wait for the horizontal scrollbar to be completely shaped. - // Without this, the enabled state of arrow buttons might be - // wrong when the pathbar is created for the first time. - QTimer::singleShot(0, this, SLOT(setScrollButtonVisibility())); + // Wait for the horizontal scrollbar to be completely shaped. + // Without this, the enabled state of arrow buttons might be + // wrong when the pathbar is created for the first time. + QTimer::singleShot(0, this, SLOT(setScrollButtonVisibility())); } void PathBar::setScrollButtonVisibility() { - bool showScrollers; - if(tempPathEdit_ != nullptr) { - showScrollers = false; - } - else { - showScrollers = (buttonsLayout_->sizeHint().width() > width()); - } - scrollToStart_->setVisible(showScrollers); - scrollToEnd_->setVisible(showScrollers); - if(showScrollers) { - QScrollBar* sb = scrollArea_->horizontalScrollBar(); - int value = sb->value(); - scrollToStart_->setEnabled(value != sb->minimum()); - scrollToEnd_->setEnabled(value != sb->maximum()); - } + bool showScrollers; + if(tempPathEdit_ != nullptr) { + showScrollers = false; + } + else { + showScrollers = (buttonsLayout_->sizeHint().width() > width()); + } + scrollToStart_->setVisible(showScrollers); + scrollToEnd_->setVisible(showScrollers); + if(showScrollers) { + QScrollBar* sb = scrollArea_->horizontalScrollBar(); + int value = sb->value(); + scrollToStart_->setEnabled(value != sb->minimum()); + scrollToEnd_->setEnabled(value != sb->maximum()); + } +} + +Fm::FilePath PathBar::pathForButton(PathButton* btn) { + std::string fullPath; + int buttonCount = buttonsLayout_->count() - 1; // the last item is a spacer + for(int i = 0; i < buttonCount; ++i) { + if(!fullPath.empty() && fullPath.back() != '/') { + fullPath += '/'; + } + PathButton* elem = static_cast(buttonsLayout_->itemAt(i)->widget()); + fullPath += elem->name(); + if(elem == btn) + break; + } + return Fm::FilePath::fromPathStr(fullPath.c_str()); } void PathBar::onButtonToggled(bool checked) { - if(checked) { - PathButton* btn = static_cast(sender()); - scrollArea_->ensureWidgetVisible(btn, 0); // make the button visible - - currentPath_ = btn->pathElement(); - // qDebug("chdir: %s", currentPath_.displayName(false)); - Q_EMIT chdir(currentPath_.dataPtr()); - } + if(checked) { + PathButton* btn = static_cast(sender()); + currentPath_ = pathForButton(btn); + Q_EMIT chdir(currentPath_); + + // since scrolling to the toggled buton will happen correctly only when the + // layout is updated and because the update is disabled on creating buttons + // in setPath(), the update status can be used as a sign to know when to wait + if(updatesEnabled()) { + scrollArea_->ensureWidgetVisible(btn, 1); + } + else { + QTimer::singleShot(0, this, SLOT(ensureToggledVisible())); + } + } } +void PathBar::ensureToggledVisible() { + int buttonCount = buttonsLayout_->count() - 1; // the last item is a spacer + for(int i = buttonCount - 1; i >= 0; --i) { + if(auto btn = static_cast(buttonsLayout_->itemAt(i)->widget())) { + if(btn->isChecked()) { + scrollArea_->ensureWidgetVisible(btn, 1); + return; + } + } + } +} void PathBar::onScrollButtonClicked() { - QToolButton* btn = static_cast(sender()); - QAbstractSlider::SliderAction action = QAbstractSlider::SliderNoAction; - if(btn == scrollToEnd_) - action = QAbstractSlider::SliderSingleStepAdd; - else if (btn == scrollToStart_) - action = QAbstractSlider::SliderSingleStepSub; - scrollArea_->horizontalScrollBar()->triggerAction(action); + QToolButton* btn = static_cast(sender()); + QAbstractSlider::SliderAction action = QAbstractSlider::SliderNoAction; + if(btn == scrollToEnd_) { + action = QAbstractSlider::SliderSingleStepAdd; + } + else if(btn == scrollToStart_) { + action = QAbstractSlider::SliderSingleStepSub; + } + scrollArea_->horizontalScrollBar()->triggerAction(action); } -void PathBar::setPath(Path path) { - if(!currentPath_.isNull() && !path.isNull() && currentPath_ == path) // same path, do nothing - return; - - currentPath_ = path; - int buttonCount = buttonsLayout_->count() - 1; // the last item is a spacer - // check if we already have a button for this path (FIXME: this loop is inefficient) - for(int i = buttonCount - 1; i >= 0; --i) { - PathButton* btn = static_cast(buttonsLayout_->itemAt(i)->widget()); - if(btn->pathElement() == path) { // we have a button for this path - btn->setChecked(true); // toggle the button - /* we don't need to emit chdir signal here since later - * toggled signal will be triggered on the button, which - * in turns emit chdir. */ - return; +void PathBar::setPath(Fm::FilePath path) { + if(currentPath_ == path) { // same path, do nothing + return; } - } - - /* FIXME: if the new path is the subdir of our full path, actually - * we can append several new buttons rather than re-create - * all of the buttons. */ - - setUpdatesEnabled(false); - // we do not have the path in the buttons list - // destroy existing path element buttons and the spacer - QLayoutItem* item; - while((item = buttonsLayout_->takeAt(0)) != nullptr) { - delete item->widget(); - delete item; - } - - Path pathElement = path; - // create new buttons for the new path - while(!pathElement.isNull()) { - // qDebug("%s", pathElement.displayName(false)); - PathButton* btn = new PathButton(pathElement, buttonsWidget_); - btn->show(); - connect(btn, &QPushButton::toggled, this, &PathBar::onButtonToggled); - pathElement = pathElement.getParent(); - buttonsLayout_->insertWidget(0, btn); - } - buttonCount = buttonsLayout_->count(); - if(buttonCount) { - PathButton* lastBtn = static_cast(buttonsLayout_->itemAt(buttonCount - 1)->widget()); - // we don't have to emit the chdir signal since the "onButtonToggled()" slot will be triggered by this. - lastBtn->setChecked(true); - } - buttonsLayout_->addStretch(1); - - // we don't want to scroll vertically. make the scroll area fit the height of the buttons - // FIXME: this is a little bit hackish :-( - scrollArea_->setFixedHeight(buttonsLayout_->sizeHint().height()); - updateScrollButtonVisibility(); - setUpdatesEnabled(true); + + auto oldPath = std::move(currentPath_); + currentPath_ = std::move(path); + // check if we already have a button for this path + int buttonCount = buttonsLayout_->count() - 1; // the last item is a spacer + if(oldPath && currentPath_.isPrefixOf(oldPath)) { + for(int i = buttonCount - 1; i >= 0; --i) { + auto btn = static_cast(buttonsLayout_->itemAt(i)->widget()); + if(pathForButton(btn) == currentPath_) { + btn->setChecked(true); // toggle the button + /* we don't need to emit chdir signal here since later + * toggled signal will be triggered on the button, which + * in turns emit chdir. */ + return; + } + } + } + + /* FIXME: if the new path is the subdir of our full path, actually + * we can append several new buttons rather than re-create + * all of the buttons. This can reduce flickers. */ + + setUpdatesEnabled(false); + // we do not have the path in the buttons list + // destroy existing path element buttons and the spacer + QLayoutItem* item; + while((item = buttonsLayout_->takeAt(0)) != nullptr) { + delete item->widget(); + delete item; + } + + // create new buttons for the new path + auto btnPath = currentPath_; + while(btnPath) { + Fm::CStrPtr name; + Fm::CStrPtr displayName; + auto parent = btnPath.parent(); + // FIXME: some buggy uri types, such as menu://, fail to return NULL when there is no parent path. + // Instead, the path itself is returned. So we check if the parent path is the same as current path. + auto isRoot = !parent.isValid() || parent == btnPath; + if(isRoot) { + displayName = btnPath.displayName(); + name = btnPath.toString(); + } + else { + name = btnPath.baseName(); + } + auto btn = new PathButton(name.get(), displayName ? displayName.get() : name.get(), isRoot, buttonsWidget_); + btn->show(); + connect(btn, &QAbstractButton::toggled, this, &PathBar::onButtonToggled); + buttonsLayout_->insertWidget(0, btn); + if(isRoot) { // this is the root element of the path + break; + } + btnPath = parent; + } + buttonsLayout_->addStretch(1); // add a spacer at the tail of the buttons + + // we don't want to scroll vertically. make the scroll area fit the height of the buttons + // FIXME: this is a little bit hackish :-( + scrollArea_->setFixedHeight(buttonsLayout_->sizeHint().height()); + updateScrollButtonVisibility(); + + // to guarantee that the button will be scrolled to correctly, + // it should be toggled only after the layout update starts above + buttonCount = buttonsLayout_->count() - 1; + if(buttonCount > 0) { + PathButton* lastBtn = static_cast(buttonsLayout_->itemAt(buttonCount - 1)->widget()); + // we don't have to emit the chdir signal since the "onButtonToggled()" slot will be triggered by this. + lastBtn->setChecked(true); + } + + setUpdatesEnabled(true); } void PathBar::openEditor() { - if(tempPathEdit_ == nullptr) { - tempPathEdit_ = new PathEdit(this); - layout()->replaceWidget(scrollArea_, tempPathEdit_, Qt::FindDirectChildrenOnly); - scrollArea_->hide(); - scrollToStart_->setVisible(false); - scrollToEnd_->setVisible(false); - char* pathStr = currentPath_.toStr(); - tempPathEdit_->setText(pathStr); - g_free(pathStr); - - connect(tempPathEdit_, &PathEdit::returnPressed, this, &PathBar::onReturnPressed); - connect(tempPathEdit_, &PathEdit::editingFinished, this, &PathBar::closeEditor); - } - tempPathEdit_->setFocus(); - tempPathEdit_->selectAll(); + if(tempPathEdit_ == nullptr) { + tempPathEdit_ = new PathEdit(this); + delete layout()->replaceWidget(scrollArea_, tempPathEdit_, Qt::FindDirectChildrenOnly); + scrollArea_->hide(); + scrollToStart_->setVisible(false); + scrollToEnd_->setVisible(false); + tempPathEdit_->setText(currentPath_.toString().get()); + + connect(tempPathEdit_, &PathEdit::returnPressed, this, &PathBar::onReturnPressed); + connect(tempPathEdit_, &PathEdit::editingFinished, this, &PathBar::closeEditor); + } + tempPathEdit_->setFocus(); + tempPathEdit_->selectAll(); } void PathBar::closeEditor() { - if(tempPathEdit_ == nullptr) - return; - // If a menu has popped up synchronously (with QMenu::exec), the path buttons may be drawn - // but the path-edit may not disappear until the menu is closed. So, we hide it here. - tempPathEdit_->setVisible(false); - layout()->replaceWidget(tempPathEdit_, scrollArea_, Qt::FindDirectChildrenOnly); - scrollArea_->show(); - if(buttonsLayout_->sizeHint().width() > width()) { - scrollToStart_->setVisible(true); - scrollToEnd_->setVisible(true); - } - - tempPathEdit_->deleteLater(); - tempPathEdit_ = nullptr; - updateScrollButtonVisibility(); - - Q_EMIT editingFinished(); + if(tempPathEdit_ == nullptr) { + return; + } + // If a menu has popped up synchronously (with QMenu::exec), the path buttons may be drawn + // but the path-edit may not disappear until the menu is closed. So, we hide it here. + tempPathEdit_->setVisible(false); + delete layout()->replaceWidget(tempPathEdit_, scrollArea_, Qt::FindDirectChildrenOnly); + scrollArea_->show(); + if(buttonsLayout_->sizeHint().width() > width()) { + scrollToStart_->setVisible(true); + scrollToEnd_->setVisible(true); + } + + tempPathEdit_->deleteLater(); + tempPathEdit_ = nullptr; + updateScrollButtonVisibility(); + + Q_EMIT editingFinished(); } void PathBar::copyPath() { - char* pathStr = currentPath_.toStr(); - QApplication::clipboard()->setText(pathStr); - g_free(pathStr); + QApplication::clipboard()->setText(currentPath_.toString().get()); } void PathBar::onReturnPressed() { - QByteArray pathStr = tempPathEdit_->text().toLocal8Bit(); - Path path = Path::newForDisplayName(pathStr.constData()); - setPath(path); + QByteArray pathStr = tempPathEdit_->text().toLocal8Bit(); + setPath(Fm::FilePath::fromPathStr(pathStr.constData())); } void PathBar::setArrowEnabledState(int value) { - if(buttonsLayout_->sizeHint().width() > width()) { - QScrollBar* sb = scrollArea_->horizontalScrollBar(); - scrollToStart_->setEnabled(value != sb->minimum()); - scrollToEnd_->setEnabled(value != sb->maximum()); - } + if(buttonsLayout_->sizeHint().width() > width()) { + QScrollBar* sb = scrollArea_->horizontalScrollBar(); + scrollToStart_->setEnabled(value != sb->minimum()); + scrollToEnd_->setEnabled(value != sb->maximum()); + } } - } // namespace Fm diff --git a/src/pathbar.h b/src/pathbar.h index 0ef163e..34609fd 100644 --- a/src/pathbar.h +++ b/src/pathbar.h @@ -22,7 +22,7 @@ #include "libfmqtglobals.h" #include -#include "path.h" +#include "core/filepath.h" class QToolButton; class QScrollArea; @@ -32,27 +32,28 @@ class QHBoxLayout; namespace Fm { class PathEdit; +class PathButton; class LIBFM_QT_API PathBar: public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit PathBar(QWidget *parent = 0); + explicit PathBar(QWidget* parent = 0); - Path path() { - return currentPath_; - } + const Fm::FilePath& path() { + return currentPath_; + } - void setPath(Path path); + void setPath(Fm::FilePath path); Q_SIGNALS: - void chdir(FmPath* path); - void middleClickChdir(FmPath* path); - void editingFinished(); + void chdir(const Fm::FilePath& path); + void middleClickChdir(const Fm::FilePath& path); + void editingFinished(); public Q_SLOTS: - void openEditor(); - void closeEditor(); - void copyPath(); + void openEditor(); + void closeEditor(); + void copyPath(); private Q_SLOTS: void onButtonToggled(bool checked); @@ -60,25 +61,27 @@ private Q_SLOTS: void onReturnPressed(); void setArrowEnabledState(int value); void setScrollButtonVisibility(); + void ensureToggledVisible(); protected: - void resizeEvent(QResizeEvent* event); - void wheelEvent (QWheelEvent* event); - void mousePressEvent(QMouseEvent *event); - void contextMenuEvent(QContextMenuEvent *event); + void resizeEvent(QResizeEvent* event); + void wheelEvent(QWheelEvent* event); + void mousePressEvent(QMouseEvent* event); + void contextMenuEvent(QContextMenuEvent* event); private: - void updateScrollButtonVisibility(); + void updateScrollButtonVisibility(); + Fm::FilePath pathForButton(PathButton* btn); private: - QToolButton* scrollToStart_; - QToolButton* scrollToEnd_; - QScrollArea* scrollArea_; - QWidget* buttonsWidget_; - QHBoxLayout* buttonsLayout_; - PathEdit* tempPathEdit_; - - Path currentPath_; // currently active path + QToolButton* scrollToStart_; + QToolButton* scrollToEnd_; + QScrollArea* scrollArea_; + QWidget* buttonsWidget_; + QHBoxLayout* buttonsLayout_; + PathEdit* tempPathEdit_; + + Fm::FilePath currentPath_; // currently active path }; } // namespace Fm diff --git a/src/pathbar_p.h b/src/pathbar_p.h index 6d2298f..6a64b98 100644 --- a/src/pathbar_p.h +++ b/src/pathbar_p.h @@ -24,53 +24,53 @@ #include #include #include -#include "path.h" +#include +#include namespace Fm { class PathButton: public QToolButton { - Q_OBJECT + Q_OBJECT public: - PathButton(Fm::Path pathElement, QWidget* parent = nullptr): - QToolButton(parent), - pathElement_(pathElement) { + PathButton(std::string name, QString displayName, bool isRoot = false, QWidget* parent = nullptr): + QToolButton(parent), + name_{name} { - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding); - setCheckable(true); - setAutoExclusive(true); - setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - /* respect the toolbar icon size (can be set with some styles) */ - int icnSize = style()->pixelMetric(QStyle::PM_ToolBarIconSize); - setIconSize(QSize(icnSize, icnSize)); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding); + setCheckable(true); + setAutoExclusive(true); + setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + /* respect the toolbar icon size (can be set with some styles) */ + int icnSize = style()->pixelMetric(QStyle::PM_ToolBarIconSize); + setIconSize(QSize(icnSize, icnSize)); - char* label = pathElement.displayBasename(); - setText(label); - g_free(label); + setText(displayName); - if(pathElement.getParent().isNull()) { /* this element is root */ - QIcon icon = QIcon::fromTheme("drive-harddisk"); - setIcon(icon); - } - } + if(isRoot) { /* this element is root */ + QIcon icon = QIcon::fromTheme("drive-harddisk"); + setIcon(icon); + } + } - Path pathElement() { - return pathElement_; - } + void changeEvent(QEvent* event) override { + QToolButton::changeEvent(event); + if(event->type() == QEvent::StyleChange) { + int icnSize = style()->pixelMetric(QStyle::PM_ToolBarIconSize); + setIconSize(QSize(icnSize, icnSize)); + } + } - void setPathElement(Path pathElement) { - pathElement_ = pathElement; - } + std::string name() const { + return name_; + } - void changeEvent(QEvent* event) override { - QToolButton::changeEvent(event); - if(event->type() == QEvent::StyleChange) { - int icnSize = style()->pixelMetric(QStyle::PM_ToolBarIconSize); - setIconSize(QSize(icnSize, icnSize)); + void setName(const std::string& name) { + name_ = name; } - } private: - Path pathElement_; + QString displayName_; + std::string name_; }; } // namespace Fm diff --git a/src/pathedit.cpp b/src/pathedit.cpp index 50b866d..27b3a2a 100644 --- a/src/pathedit.cpp +++ b/src/pathedit.cpp @@ -26,193 +26,220 @@ #include #include #include +#include #include namespace Fm { void PathEditJob::runJob() { - GError *err = NULL; - GFileEnumerator* enu = g_file_enumerate_children(dirName, - // G_FILE_ATTRIBUTE_STANDARD_NAME"," - G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME"," - G_FILE_ATTRIBUTE_STANDARD_TYPE, - G_FILE_QUERY_INFO_NONE, cancellable, - &err); - if(enu) { - while(!g_cancellable_is_cancelled(cancellable)) { - GFileInfo* inf = g_file_enumerator_next_file(enu, cancellable, &err); - if(inf) { - GFileType type = g_file_info_get_file_type(inf); - if(type == G_FILE_TYPE_DIRECTORY) { - const char* name = g_file_info_get_display_name(inf); - // FIXME: encoding conversion here? - subDirs.append(QString::fromUtf8(name)); + GError* err = nullptr; + GFileEnumerator* enu = g_file_enumerate_children(dirName, + // G_FILE_ATTRIBUTE_STANDARD_NAME"," + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME"," + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, cancellable, + &err); + if(enu) { + while(!g_cancellable_is_cancelled(cancellable)) { + GFileInfo* inf = g_file_enumerator_next_file(enu, cancellable, &err); + if(inf) { + GFileType type = g_file_info_get_file_type(inf); + if(type == G_FILE_TYPE_DIRECTORY) { + const char* name = g_file_info_get_display_name(inf); + // FIXME: encoding conversion here? + subDirs.append(QString::fromUtf8(name)); + } + g_object_unref(inf); + } + else { + if(err) { + g_error_free(err); + err = nullptr; + } + else { /* EOF */ + break; + } + } } - g_object_unref(inf); - } - else { - if(err) { - g_error_free(err); - err = NULL; - } - else /* EOF */ - break; - } + g_file_enumerator_close(enu, cancellable, nullptr); + g_object_unref(enu); } - g_file_enumerator_close(enu, cancellable, NULL); - g_object_unref(enu); - } - // finished! let's update the UI in the main thread - Q_EMIT finished(); + // finished! let's update the UI in the main thread + Q_EMIT finished(); + QThread::currentThread()->quit(); } PathEdit::PathEdit(QWidget* parent): - QLineEdit(parent), - completer_(new QCompleter()), - model_(new QStringListModel()), - cancellable_(NULL) { - setCompleter(completer_); - completer_->setModel(model_); - connect(this, &PathEdit::textChanged, this, &PathEdit::onTextChanged); + QLineEdit(parent), + completer_(new QCompleter()), + model_(new QStringListModel()), + cancellable_(nullptr) { + setCompleter(completer_); + completer_->setModel(model_); + connect(this, &PathEdit::textChanged, this, &PathEdit::onTextChanged); + connect(this, &PathEdit::textEdited, this, &PathEdit::onTextEdited); } PathEdit::~PathEdit() { - delete completer_; - if(model_) - delete model_; - if(cancellable_) { - g_cancellable_cancel(cancellable_); - g_object_unref(cancellable_); - } + delete completer_; + if(model_) { + delete model_; + } + if(cancellable_) { + g_cancellable_cancel(cancellable_); + g_object_unref(cancellable_); + } } void PathEdit::focusInEvent(QFocusEvent* e) { - QLineEdit::focusInEvent(e); - // build the completion list only when we have the keyboard focus - reloadCompleter(true); + QLineEdit::focusInEvent(e); + // build the completion list only when we have the keyboard focus + reloadCompleter(true); } void PathEdit::focusOutEvent(QFocusEvent* e) { - QLineEdit::focusOutEvent(e); - // free the completion list since we don't need it anymore - freeCompleter(); + QLineEdit::focusOutEvent(e); + // free the completion list since we don't need it anymore + freeCompleter(); } bool PathEdit::event(QEvent* e) { - // Stop Qt from moving the keyboard focus to the next widget when "Tab" is pressed. - // Instead, we need to do auto-completion in this case. - if(e->type() == QEvent::KeyPress) { - QKeyEvent* keyEvent = static_cast(e); - if(keyEvent->key() == Qt::Key_Tab && keyEvent->modifiers() == Qt::NoModifier) { // Tab key is pressed - e->accept(); - // do auto-completion when the user press the Tab key. - // This fixes #201: https://github.com/lxde/pcmanfm-qt/issues/201 - autoComplete(); - return true; + // Stop Qt from moving the keyboard focus to the next widget when "Tab" is pressed. + // Instead, we need to do auto-completion in this case. + if(e->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(e); + if(keyEvent->key() == Qt::Key_Tab && keyEvent->modifiers() == Qt::NoModifier) { // Tab key is pressed + e->accept(); + // do auto-completion when the user press the Tab key. + // This fixes #201: https://github.com/lxde/pcmanfm-qt/issues/201 + autoComplete(); + return true; + } + } + return QLineEdit::event(e); +} + +void PathEdit::onTextEdited(const QString& text) { + // just replace start tilde with home path if text is changed by user + if(text == QLatin1String("~") || text.startsWith(QLatin1String("~/"))) { + QString txt(text); + txt.replace(0, 1, QDir::homePath()); + setText(txt); // emits textChanged() + return; } - } - return QLineEdit::event(e); } void PathEdit::onTextChanged(const QString& text) { - int pos = text.lastIndexOf('/'); - if(pos >= 0) - ++pos; - else - pos = text.length(); - QString newPrefix = text.left(pos); - if(currentPrefix_ != newPrefix) { - currentPrefix_ = newPrefix; - // only build the completion list if we have the keyboard focus - // if we don't have the focus now, then we'll rebuild the completion list - // when focusInEvent happens. this avoid unnecessary dir loading. - if(hasFocus()) - reloadCompleter(false); - } + if(text == QLatin1String("~") || text.startsWith(QLatin1String("~/"))) { + // do nothing with a start tilde because neither Fm::FilePath nor autocompletion + // understands it; instead, wait until textChanged() is emitted again without it + // WARNING: replacing tilde may not be safe here + return; + } + int pos = text.lastIndexOf('/'); + if(pos >= 0) { + ++pos; + } + else { + pos = text.length(); + } + QString newPrefix = text.left(pos); + if(currentPrefix_ != newPrefix) { + currentPrefix_ = newPrefix; + // only build the completion list if we have the keyboard focus + // if we don't have the focus now, then we'll rebuild the completion list + // when focusInEvent happens. this avoid unnecessary dir loading. + if(hasFocus()) { + reloadCompleter(false); + } + } } void PathEdit::autoComplete() { - // find longest common prefix of the strings currently shown in the candidate list - QAbstractItemModel* model = completer_->completionModel(); - if(model->rowCount() > 0) { - int minLen = text().length(); - QString commonPrefix = model->data(model->index(0, 0)).toString(); - for(int row = 1; row < model->rowCount() && commonPrefix.length() > minLen; ++row) { - QModelIndex index = model->index(row, 0); - QString rowText = model->data(index).toString(); - int prefixLen = 0; - while(prefixLen < rowText.length() && prefixLen < commonPrefix.length() && rowText[prefixLen] == commonPrefix[prefixLen]) { - ++prefixLen; - } - commonPrefix.truncate(prefixLen); - } - if(commonPrefix.length() > minLen) { - setText(commonPrefix); + // find longest common prefix of the strings currently shown in the candidate list + QAbstractItemModel* model = completer_->completionModel(); + if(model->rowCount() > 0) { + int minLen = text().length(); + QString commonPrefix = model->data(model->index(0, 0)).toString(); + for(int row = 1; row < model->rowCount() && commonPrefix.length() > minLen; ++row) { + QModelIndex index = model->index(row, 0); + QString rowText = model->data(index).toString(); + int prefixLen = 0; + while(prefixLen < rowText.length() && prefixLen < commonPrefix.length() && rowText[prefixLen] == commonPrefix[prefixLen]) { + ++prefixLen; + } + commonPrefix.truncate(prefixLen); + } + if(commonPrefix.length() > minLen) { + setText(commonPrefix); + } } - } } void PathEdit::reloadCompleter(bool triggeredByFocusInEvent) { - // parent dir has been changed, reload dir list - // if(currentPrefix_[0] == "~") { // special case for home dir - // cancel running dir-listing jobs, if there's any - if(cancellable_) { - g_cancellable_cancel(cancellable_); - g_object_unref(cancellable_); - } - - // create a new job to do dir listing - PathEditJob* job = new PathEditJob(); - job->edit = this; - job->triggeredByFocusInEvent = triggeredByFocusInEvent; - // need to use fm_file_new_for_commandline_arg() rather than g_file_new_for_commandline_arg(). - // otherwise, our own vfs, such as menu://, won't be loaded. - job->dirName = fm_file_new_for_commandline_arg(currentPrefix_.toLocal8Bit().constData()); - // qDebug("load: %s", g_file_get_uri(data->dirName)); - cancellable_ = g_cancellable_new(); - job->cancellable = (GCancellable*)g_object_ref(cancellable_); - - // launch a new worker thread to handle the job - QThread* thread = new QThread(); - job->moveToThread(thread); - connect(thread, &QThread::started, job, &PathEditJob::runJob); - connect(thread, &QThread::finished, thread, &QObject::deleteLater); - connect(thread, &QThread::finished, job, &QObject::deleteLater); - connect(job, &PathEditJob::finished, this, &PathEdit::onJobFinished); - thread->start(QThread::LowPriority); + // parent dir has been changed, reload dir list + // if(currentPrefix_[0] == "~") { // special case for home dir + // cancel running dir-listing jobs, if there's any + if(cancellable_) { + g_cancellable_cancel(cancellable_); + g_object_unref(cancellable_); + } + + // create a new job to do dir listing + PathEditJob* job = new PathEditJob(); + job->edit = this; + job->triggeredByFocusInEvent = triggeredByFocusInEvent; + // need to use fm_file_new_for_commandline_arg() rather than g_file_new_for_commandline_arg(). + // otherwise, our own vfs, such as menu://, won't be loaded. + job->dirName = fm_file_new_for_commandline_arg(currentPrefix_.toLocal8Bit().constData()); + // qDebug("load: %s", g_file_get_uri(data->dirName)); + cancellable_ = g_cancellable_new(); + job->cancellable = (GCancellable*)g_object_ref(cancellable_); + + // launch a new worker thread to handle the job + QThread* thread = new QThread(); + job->moveToThread(thread); + connect(job, &PathEditJob::finished, this, &PathEdit::onJobFinished, Qt::BlockingQueuedConnection); + // connect(job, &PathEditJob::finished, thread, &QThread::quit); + connect(thread, &QThread::started, job, &PathEditJob::runJob); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + connect(thread, &QThread::finished, job, &QObject::deleteLater); + thread->start(QThread::LowPriority); } void PathEdit::freeCompleter() { - if(cancellable_) { - g_cancellable_cancel(cancellable_); - g_object_unref(cancellable_); - cancellable_ = NULL; - } - model_->setStringList(QStringList()); + if(cancellable_) { + g_cancellable_cancel(cancellable_); + g_object_unref(cancellable_); + cancellable_ = nullptr; + } + model_->setStringList(QStringList()); } // This slot is called from main thread so it's safe to access the GUI void PathEdit::onJobFinished() { - PathEditJob* data = static_cast(sender()); - if(!g_cancellable_is_cancelled(data->cancellable)) { - // update the completer only if the job is not cancelled - QStringList::iterator it; - for(it = data->subDirs.begin(); it != data->subDirs.end(); ++it) { - // qDebug("%s", it->toUtf8().constData()); - *it = (currentPrefix_ % *it); + PathEditJob* data = static_cast(sender()); + if(!g_cancellable_is_cancelled(data->cancellable)) { + // update the completer only if the job is not cancelled + QStringList::iterator it; + for(it = data->subDirs.begin(); it != data->subDirs.end(); ++it) { + // qDebug("%s", it->toUtf8().constData()); + *it = (currentPrefix_ % *it); + } + model_->setStringList(data->subDirs); + // trigger completion manually + if(hasFocus() && !data->triggeredByFocusInEvent) { + completer_->complete(); + } + } + else { + model_->setStringList(QStringList()); + } + if(cancellable_) { + g_object_unref(cancellable_); + cancellable_ = nullptr; } - model_->setStringList(data->subDirs); - // trigger completion manually - if(hasFocus() && !data->triggeredByFocusInEvent) - completer_->complete(); - } - else - model_->setStringList(QStringList()); - if(cancellable_) { - g_object_unref(cancellable_); - cancellable_ = NULL; - } } } // namespace Fm diff --git a/src/pathedit.h b/src/pathedit.h index 950bcf6..6385ddf 100644 --- a/src/pathedit.h +++ b/src/pathedit.h @@ -33,30 +33,31 @@ namespace Fm { class PathEditJob; class LIBFM_QT_API PathEdit : public QLineEdit { -Q_OBJECT + Q_OBJECT public: - explicit PathEdit(QWidget* parent = 0); - virtual ~PathEdit(); + explicit PathEdit(QWidget* parent = 0); + virtual ~PathEdit(); protected: - virtual void focusInEvent(QFocusEvent* e); - virtual void focusOutEvent(QFocusEvent* e); - virtual bool event(QEvent* e); + virtual void focusInEvent(QFocusEvent* e); + virtual void focusOutEvent(QFocusEvent* e); + virtual bool event(QEvent* e); private Q_SLOTS: - void onTextChanged(const QString & text); + void onTextChanged(const QString& text); + void onTextEdited(const QString& text); private: - void autoComplete(); - void reloadCompleter(bool triggeredByFocusInEvent = false); - void freeCompleter(); - void onJobFinished(); + void autoComplete(); + void reloadCompleter(bool triggeredByFocusInEvent = false); + void freeCompleter(); + void onJobFinished(); private: - QCompleter* completer_; - QStringListModel* model_; - QString currentPrefix_; - GCancellable* cancellable_; + QCompleter* completer_; + QStringListModel* model_; + QString currentPrefix_; + GCancellable* cancellable_; }; } diff --git a/src/pathedit_p.h b/src/pathedit_p.h index b91629c..ce0e8f7 100644 --- a/src/pathedit_p.h +++ b/src/pathedit_p.h @@ -29,24 +29,24 @@ namespace Fm { class PathEdit; class PathEditJob : public QObject { - Q_OBJECT + Q_OBJECT public: - GCancellable* cancellable; - GFile* dirName; - QStringList subDirs; - PathEdit* edit; - bool triggeredByFocusInEvent; + GCancellable* cancellable; + GFile* dirName; + QStringList subDirs; + PathEdit* edit; + bool triggeredByFocusInEvent; - ~PathEditJob() { - g_object_unref(dirName); - g_object_unref(cancellable); - } + ~PathEditJob() { + g_object_unref(dirName); + g_object_unref(cancellable); + } Q_SIGNALS: - void finished(); + void finished(); public Q_SLOTS: - void runJob(); + void runJob(); }; diff --git a/src/placesmodel.cpp b/src/placesmodel.cpp index d5b0ac7..d6d3776 100644 --- a/src/placesmodel.cpp +++ b/src/placesmodel.cpp @@ -25,599 +25,587 @@ #include #include #include +#include #include "utilities.h" #include "placesmodelitem.h" namespace Fm { +std::weak_ptr PlacesModel::globalInstance_; + PlacesModel::PlacesModel(QObject* parent): - QStandardItemModel(parent), - showApplications_(true), - showDesktop_(true), - ejectIcon_(QIcon::fromTheme("media-eject")) { - setColumnCount(2); - - placesRoot = new QStandardItem(tr("Places")); - placesRoot->setSelectable(false); - placesRoot->setColumnCount(2); - appendRow(placesRoot); - - homeItem = new PlacesModelItem("user-home", g_get_user_name(), fm_path_get_home()); - placesRoot->appendRow(homeItem); - - desktopItem = new PlacesModelItem("user-desktop", tr("Desktop"), fm_path_get_desktop()); - placesRoot->appendRow(desktopItem); - - createTrashItem(); - - FmPath* path; - // FIXME: add an option to hide network:/// - if(true) { - path = fm_path_new_for_uri("computer:///"); - computerItem = new PlacesModelItem("computer", tr("Computer"), path); - fm_path_unref(path); - placesRoot->appendRow(computerItem); - } - else - computerItem = NULL; - - // FIXME: add an option to hide applications:/// - const char* applicaion_icon_names[] = {"system-software-install", "applications-accessories", "application-x-executable"}; - // NOTE: g_themed_icon_new_from_names() accepts char**, but actually const char** is OK. - GIcon* gicon = g_themed_icon_new_from_names((char**)applicaion_icon_names, G_N_ELEMENTS(applicaion_icon_names)); - FmIcon* fmicon = fm_icon_from_gicon(gicon); - g_object_unref(gicon); - applicationsItem = new PlacesModelItem(fmicon, tr("Applications"), fm_path_get_apps_menu()); - fm_icon_unref(fmicon); - placesRoot->appendRow(applicationsItem); - - // FIXME: add an option to hide network:/// - if(true) { - const char* network_icon_names[] = {"network", "folder-network", "folder"}; + QStandardItemModel(parent), + showApplications_(true), + showDesktop_(true), + // FIXME: this seems to be broken when porting to new API. + ejectIcon_(QIcon::fromTheme("media-eject")) { + setColumnCount(2); + + placesRoot = new QStandardItem(tr("Places")); + placesRoot->setSelectable(false); + placesRoot->setColumnCount(2); + appendRow(placesRoot); + + homeItem = new PlacesModelItem("user-home", g_get_user_name(), Fm::FilePath::homeDir()); + placesRoot->appendRow(homeItem); + + desktopItem = new PlacesModelItem("user-desktop", tr("Desktop"), + Fm::FilePath::fromLocalPath(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toLocal8Bit().constData())); + placesRoot->appendRow(desktopItem); + + createTrashItem(); + + // FIXME: add an option to hide network:/// + if(true) { + computerItem = new PlacesModelItem("computer", tr("Computer"), Fm::FilePath::fromUri("computer:///")); + placesRoot->appendRow(computerItem); + } + else { + computerItem = nullptr; + } + + // FIXME: add an option to hide applications:/// + const char* applicaion_icon_names[] = {"system-software-install", "applications-accessories", "application-x-executable"}; // NOTE: g_themed_icon_new_from_names() accepts char**, but actually const char** is OK. - gicon = g_themed_icon_new_from_names((char**)network_icon_names, G_N_ELEMENTS(network_icon_names)); - fmicon = fm_icon_from_gicon(gicon); - g_object_unref(gicon); - path = fm_path_new_for_uri("network:///"); - networkItem = new PlacesModelItem(fmicon, tr("Network"), path); - fm_icon_unref(fmicon); - fm_path_unref(path); - placesRoot->appendRow(networkItem); - } - else - networkItem = NULL; - - devicesRoot = new QStandardItem(tr("Devices")); - devicesRoot->setSelectable(false); - devicesRoot->setColumnCount(2); - appendRow(devicesRoot); - - // volumes - volumeMonitor = g_volume_monitor_get(); - if(volumeMonitor) { - g_signal_connect(volumeMonitor, "volume-added", G_CALLBACK(onVolumeAdded), this); - g_signal_connect(volumeMonitor, "volume-removed", G_CALLBACK(onVolumeRemoved), this); - g_signal_connect(volumeMonitor, "volume-changed", G_CALLBACK(onVolumeChanged), this); - g_signal_connect(volumeMonitor, "mount-added", G_CALLBACK(onMountAdded), this); - g_signal_connect(volumeMonitor, "mount-changed", G_CALLBACK(onMountChanged), this); - g_signal_connect(volumeMonitor, "mount-removed", G_CALLBACK(onMountRemoved), this); - - // add volumes to side-pane - GList* vols = g_volume_monitor_get_volumes(volumeMonitor); - GList* l; - for(l = vols; l; l = l->next) { - GVolume* volume = G_VOLUME(l->data); - onVolumeAdded(volumeMonitor, volume, this); - g_object_unref(volume); - } - g_list_free(vols); - - /* add mounts to side-pane */ - vols = g_volume_monitor_get_mounts(volumeMonitor); - for(l = vols; l; l = l->next) { - GMount* mount = G_MOUNT(l->data); - GVolume* volume = g_mount_get_volume(mount); - if(volume) - g_object_unref(volume); - else { /* network mounts or others */ - gboolean shadowed = FALSE; + Fm::GIconPtr gicon{g_themed_icon_new_from_names((char**)applicaion_icon_names, G_N_ELEMENTS(applicaion_icon_names)), false}; + auto fmicon = Fm::IconInfo::fromGIcon(std::move(gicon)); + applicationsItem = new PlacesModelItem(fmicon, tr("Applications"), Fm::FilePath::fromUri("menu:///applications/")); + placesRoot->appendRow(applicationsItem); + + // FIXME: add an option to hide network:/// + if(true) { + const char* network_icon_names[] = {"network", "folder-network", "folder"}; + // NOTE: g_themed_icon_new_from_names() accepts char**, but actually const char** is OK. + Fm::GIconPtr gicon{g_themed_icon_new_from_names((char**)network_icon_names, G_N_ELEMENTS(network_icon_names)), false}; + auto fmicon = Fm::IconInfo::fromGIcon(std::move(gicon)); + networkItem = new PlacesModelItem(fmicon, tr("Network"), Fm::FilePath::fromUri("network:///")); + placesRoot->appendRow(networkItem); + } + else { + networkItem = nullptr; + } + + devicesRoot = new QStandardItem(tr("Devices")); + devicesRoot->setSelectable(false); + devicesRoot->setColumnCount(2); + appendRow(devicesRoot); + + // volumes + volumeMonitor = g_volume_monitor_get(); + if(volumeMonitor) { + g_signal_connect(volumeMonitor, "volume-added", G_CALLBACK(onVolumeAdded), this); + g_signal_connect(volumeMonitor, "volume-removed", G_CALLBACK(onVolumeRemoved), this); + g_signal_connect(volumeMonitor, "volume-changed", G_CALLBACK(onVolumeChanged), this); + g_signal_connect(volumeMonitor, "mount-added", G_CALLBACK(onMountAdded), this); + g_signal_connect(volumeMonitor, "mount-changed", G_CALLBACK(onMountChanged), this); + g_signal_connect(volumeMonitor, "mount-removed", G_CALLBACK(onMountRemoved), this); + + // add volumes to side-pane + GList* vols = g_volume_monitor_get_volumes(volumeMonitor); + GList* l; + for(l = vols; l; l = l->next) { + GVolume* volume = G_VOLUME(l->data); + onVolumeAdded(volumeMonitor, volume, this); + g_object_unref(volume); + } + g_list_free(vols); + + /* add mounts to side-pane */ + vols = g_volume_monitor_get_mounts(volumeMonitor); + for(l = vols; l; l = l->next) { + GMount* mount = G_MOUNT(l->data); + GVolume* volume = g_mount_get_volume(mount); + if(volume) { + g_object_unref(volume); + } + else { /* network mounts or others */ + gboolean shadowed = FALSE; #if GLIB_CHECK_VERSION(2, 20, 0) - shadowed = g_mount_is_shadowed(mount); + shadowed = g_mount_is_shadowed(mount); #endif - // according to gio API doc, a shadowed mount should not be visible to the user - if(shadowed) { - shadowedMounts_.push_back(mount); - continue; - } - else { - PlacesModelItem* item = new PlacesModelMountItem(mount); - devicesRoot->appendRow(item); - } + // according to gio API doc, a shadowed mount should not be visible to the user + if(shadowed) { + shadowedMounts_.push_back(mount); + continue; + } + else { + PlacesModelItem* item = new PlacesModelMountItem(mount); + devicesRoot->appendRow(item); + } + } + g_object_unref(mount); } - g_object_unref(mount); + g_list_free(vols); } - g_list_free(vols); - } - // bookmarks - bookmarksRoot = new QStandardItem(tr("Bookmarks")); - bookmarksRoot->setSelectable(false); - bookmarksRoot->setColumnCount(2); - appendRow(bookmarksRoot); + // bookmarks + bookmarksRoot = new QStandardItem(tr("Bookmarks")); + bookmarksRoot->setSelectable(false); + bookmarksRoot->setColumnCount(2); + appendRow(bookmarksRoot); - bookmarks = fm_bookmarks_dup(); - loadBookmarks(); - g_signal_connect(bookmarks, "changed", G_CALLBACK(onBookmarksChanged), this); - - // update some icons when the icon theme is changed - connect(IconTheme::instance(), &IconTheme::changed, this, &PlacesModel::updateIcons); + bookmarks = Fm::Bookmarks::globalInstance(); + loadBookmarks(); + connect(bookmarks.get(), &Fm::Bookmarks::changed, this, &PlacesModel::onBookmarksChanged); } void PlacesModel::loadBookmarks() { - GList* allBookmarks = fm_bookmarks_get_all(bookmarks); - for(GList* l = allBookmarks; l; l = l->next) { - FmBookmarkItem* bm_item = (FmBookmarkItem*)l->data; - PlacesModelBookmarkItem* item = new PlacesModelBookmarkItem(bm_item); - bookmarksRoot->appendRow(item); - } - g_list_free_full(allBookmarks, (GDestroyNotify)fm_bookmark_item_unref); + for(auto& bm_item: bookmarks->items()) { + PlacesModelBookmarkItem* item = new PlacesModelBookmarkItem(bm_item); + bookmarksRoot->appendRow(item); + } } PlacesModel::~PlacesModel() { - if(bookmarks) { - g_signal_handlers_disconnect_by_func(bookmarks, (gpointer)onBookmarksChanged, this); - g_object_unref(bookmarks); - } - if(volumeMonitor) { - g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onVolumeAdded), this); - g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onVolumeRemoved), this); - g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onVolumeChanged), this); - g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onMountAdded), this); - g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onMountChanged), this); - g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onMountRemoved), this); - g_object_unref(volumeMonitor); - } - if(trashMonitor_) { - g_signal_handlers_disconnect_by_func(trashMonitor_, (gpointer)G_CALLBACK(onTrashChanged), this); - g_object_unref(trashMonitor_); - } - - Q_FOREACH(GMount* mount, shadowedMounts_) { - g_object_unref(mount); - } + if(volumeMonitor) { + g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onVolumeAdded), this); + g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onVolumeRemoved), this); + g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onVolumeChanged), this); + g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onMountAdded), this); + g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onMountChanged), this); + g_signal_handlers_disconnect_by_func(volumeMonitor, (gpointer)G_CALLBACK(onMountRemoved), this); + g_object_unref(volumeMonitor); + } + if(trashMonitor_) { + g_signal_handlers_disconnect_by_func(trashMonitor_, (gpointer)G_CALLBACK(onTrashChanged), this); + g_object_unref(trashMonitor_); + } + + Q_FOREACH(GMount* mount, shadowedMounts_) { + g_object_unref(mount); + } } // static -void PlacesModel::onTrashChanged(GFileMonitor* monitor, GFile* gf, GFile* other, GFileMonitorEvent evt, PlacesModel* pThis) { - QTimer::singleShot(0, pThis, SLOT(updateTrash())); +void PlacesModel::onTrashChanged(GFileMonitor* /*monitor*/, GFile* /*gf*/, GFile* /*other*/, GFileMonitorEvent /*evt*/, PlacesModel* pThis) { + QTimer::singleShot(0, pThis, SLOT(updateTrash())); } void PlacesModel::updateTrash() { - struct UpdateTrashData { - QPointer model; - GFile* gf; - UpdateTrashData(PlacesModel* _model) : model(_model) { - gf = fm_file_new_for_uri("trash:///"); - } - ~UpdateTrashData() { - g_object_unref(gf); - } - }; - - if(trashItem_) { - UpdateTrashData* data = new UpdateTrashData(this); - g_file_query_info_async(data->gf, G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT, G_FILE_QUERY_INFO_NONE, G_PRIORITY_LOW, NULL, - [](GObject *source_object, GAsyncResult *res, gpointer user_data) { - // the callback lambda function is called when the asyn query operation is finished - UpdateTrashData* data = reinterpret_cast(user_data); - PlacesModel* _this = data->model.data(); - if(_this != nullptr) { // ensure that our model object is not deleted yet - GFileInfo* inf = g_file_query_info_finish(data->gf, res, NULL); - if(inf) { - if(_this->trashItem_ != nullptr) { // it's possible that when we finish, the trash item is removed - guint32 n = g_file_info_get_attribute_uint32(inf, G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT); - const char* icon_name = n > 0 ? "user-trash-full" : "user-trash"; - FmIcon* icon = fm_icon_from_name(icon_name); - _this->trashItem_->setIcon(icon); - fm_icon_unref(icon); - } - g_object_unref(inf); - } + struct UpdateTrashData { + QPointer model; + GFile* gf; + UpdateTrashData(PlacesModel* _model) : model(_model) { + gf = fm_file_new_for_uri("trash:///"); + } + ~UpdateTrashData() { + g_object_unref(gf); } - delete data; // free the data used for this async operation. - }, data); - } + }; + + if(trashItem_) { + UpdateTrashData* data = new UpdateTrashData(this); + g_file_query_info_async(data->gf, G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT, G_FILE_QUERY_INFO_NONE, G_PRIORITY_LOW, nullptr, + [](GObject * /*source_object*/, GAsyncResult * res, gpointer user_data) { + // the callback lambda function is called when the asyn query operation is finished + UpdateTrashData* data = reinterpret_cast(user_data); + PlacesModel* _this = data->model.data(); + if(_this != nullptr) { // ensure that our model object is not deleted yet + Fm::GFileInfoPtr inf{g_file_query_info_finish(data->gf, res, nullptr), false}; + if(inf) { + if(_this->trashItem_ != nullptr) { // it's possible that when we finish, the trash item is removed + guint32 n = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT); + const char* icon_name = n > 0 ? "user-trash-full" : "user-trash"; + auto icon = Fm::IconInfo::fromName(icon_name); + _this->trashItem_->setIcon(std::move(icon)); + } + } + } + delete data; // free the data used for this async operation. + }, data); + } } void PlacesModel::createTrashItem() { - GFile* gf; - gf = fm_file_new_for_uri("trash:///"); - // check if trash is supported by the current vfs - // if gvfs is not installed, this can be unavailable. - if(!g_file_query_exists(gf, NULL)) { + GFile* gf; + gf = fm_file_new_for_uri("trash:///"); + // check if trash is supported by the current vfs + // if gvfs is not installed, this can be unavailable. + if(!g_file_query_exists(gf, nullptr)) { + g_object_unref(gf); + trashItem_ = nullptr; + trashMonitor_ = nullptr; + return; + } + trashItem_ = new PlacesModelItem("user-trash", tr("Trash"), Fm::FilePath::fromUri("trash:///")); + + trashMonitor_ = fm_monitor_directory(gf, nullptr); + if(trashMonitor_) { + g_signal_connect(trashMonitor_, "changed", G_CALLBACK(onTrashChanged), this); + } g_object_unref(gf); - trashItem_ = NULL; - trashMonitor_ = NULL; - return; - } - trashItem_ = new PlacesModelItem("user-trash", tr("Trash"), fm_path_get_trash()); - - trashMonitor_ = fm_monitor_directory(gf, NULL); - if(trashMonitor_) - g_signal_connect(trashMonitor_, "changed", G_CALLBACK(onTrashChanged), this); - g_object_unref(gf); - - placesRoot->insertRow(desktopItem->row() + 1, trashItem_); - QTimer::singleShot(0, this, SLOT(updateTrash())); + + placesRoot->insertRow(desktopItem->row() + 1, trashItem_); + QTimer::singleShot(0, this, SLOT(updateTrash())); } void PlacesModel::setShowApplications(bool show) { - if(showApplications_ != show) { - showApplications_ = show; - } + if(showApplications_ != show) { + showApplications_ = show; + } } void PlacesModel::setShowDesktop(bool show) { - if(showDesktop_ != show) { - showDesktop_ = show; - } + if(showDesktop_ != show) { + showDesktop_ = show; + } } void PlacesModel::setShowTrash(bool show) { - if(show) { - if(!trashItem_) - createTrashItem(); - } - else { - if(trashItem_) { - if(trashMonitor_) { - g_signal_handlers_disconnect_by_func(trashMonitor_, (gpointer)G_CALLBACK(onTrashChanged), this); - g_object_unref(trashMonitor_); - trashMonitor_ = NULL; - } - placesRoot->removeRow(trashItem_->row()); // delete trashItem_; - trashItem_ = NULL; + if(show) { + if(!trashItem_) { + createTrashItem(); + } + } + else { + if(trashItem_) { + if(trashMonitor_) { + g_signal_handlers_disconnect_by_func(trashMonitor_, (gpointer)G_CALLBACK(onTrashChanged), this); + g_object_unref(trashMonitor_); + trashMonitor_ = nullptr; + } + placesRoot->removeRow(trashItem_->row()); // delete trashItem_; + trashItem_ = nullptr; + } } - } } -PlacesModelItem* PlacesModel::itemFromPath(FmPath* path) { - PlacesModelItem* item = itemFromPath(placesRoot, path); - if(!item) - item = itemFromPath(devicesRoot, path); - if(!item) - item = itemFromPath(bookmarksRoot, path); - return item; +PlacesModelItem* PlacesModel::itemFromPath(const Fm::FilePath &path) { + PlacesModelItem* item = itemFromPath(placesRoot, path); + if(!item) { + item = itemFromPath(devicesRoot, path); + } + if(!item) { + item = itemFromPath(bookmarksRoot, path); + } + return item; } -PlacesModelItem* PlacesModel::itemFromPath(QStandardItem* rootItem, FmPath* path) { - int rowCount = rootItem->rowCount(); - for(int i = 0; i < rowCount; ++i) { - PlacesModelItem* item = static_cast(rootItem->child(i, 0)); - if(fm_path_equal(item->path(), path)) - return item; - } - return NULL; +PlacesModelItem* PlacesModel::itemFromPath(QStandardItem* rootItem, const Fm::FilePath &path) { + int rowCount = rootItem->rowCount(); + for(int i = 0; i < rowCount; ++i) { + PlacesModelItem* item = static_cast(rootItem->child(i, 0)); + if(item->path() == path) { + return item; + } + } + return nullptr; } PlacesModelVolumeItem* PlacesModel::itemFromVolume(GVolume* volume) { - int rowCount = devicesRoot->rowCount(); - for(int i = 0; i < rowCount; ++i) { - PlacesModelItem* item = static_cast(devicesRoot->child(i, 0)); - if(item->type() == PlacesModelItem::Volume) { - PlacesModelVolumeItem* volumeItem = static_cast(item); - if(volumeItem->volume() == volume) - return volumeItem; - } - } - return NULL; + int rowCount = devicesRoot->rowCount(); + for(int i = 0; i < rowCount; ++i) { + PlacesModelItem* item = static_cast(devicesRoot->child(i, 0)); + if(item->type() == PlacesModelItem::Volume) { + PlacesModelVolumeItem* volumeItem = static_cast(item); + if(volumeItem->volume() == volume) { + return volumeItem; + } + } + } + return nullptr; } PlacesModelMountItem* PlacesModel::itemFromMount(GMount* mount) { - int rowCount = devicesRoot->rowCount(); - for(int i = 0; i < rowCount; ++i) { - PlacesModelItem* item = static_cast(devicesRoot->child(i, 0)); - if(item->type() == PlacesModelItem::Mount) { - PlacesModelMountItem* mountItem = static_cast(item); - if(mountItem->mount() == mount) - return mountItem; - } - } - return NULL; + int rowCount = devicesRoot->rowCount(); + for(int i = 0; i < rowCount; ++i) { + PlacesModelItem* item = static_cast(devicesRoot->child(i, 0)); + if(item->type() == PlacesModelItem::Mount) { + PlacesModelMountItem* mountItem = static_cast(item); + if(mountItem->mount() == mount) { + return mountItem; + } + } + } + return nullptr; } -PlacesModelBookmarkItem* PlacesModel::itemFromBookmark(FmBookmarkItem* bkitem) { - int rowCount = bookmarksRoot->rowCount(); - for(int i = 0; i < rowCount; ++i) { - PlacesModelBookmarkItem* item = static_cast(bookmarksRoot->child(i, 0)); - if(item->bookmark() == bkitem) - return item; - } - return NULL; +PlacesModelBookmarkItem* PlacesModel::itemFromBookmark(std::shared_ptr bkitem) { + int rowCount = bookmarksRoot->rowCount(); + for(int i = 0; i < rowCount; ++i) { + PlacesModelBookmarkItem* item = static_cast(bookmarksRoot->child(i, 0)); + if(item->bookmark() == bkitem) { + return item; + } + } + return nullptr; } -void PlacesModel::onMountAdded(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis) { - // according to gio API doc, a shadowed mount should not be visible to the user +void PlacesModel::onMountAdded(GVolumeMonitor* /*monitor*/, GMount* mount, PlacesModel* pThis) { + // according to gio API doc, a shadowed mount should not be visible to the user #if GLIB_CHECK_VERSION(2, 20, 0) if(g_mount_is_shadowed(mount)) { - if(pThis->shadowedMounts_.indexOf(mount) == -1) - pThis->shadowedMounts_.push_back(G_MOUNT(g_object_ref(mount))); - return; + if(pThis->shadowedMounts_.indexOf(mount) == -1) { + pThis->shadowedMounts_.push_back(G_MOUNT(g_object_ref(mount))); + } + return; } #endif - GVolume* vol = g_mount_get_volume(mount); - if(vol) { // mount-added is also emitted when a volume is newly mounted. - PlacesModelVolumeItem* item = pThis->itemFromVolume(vol); - if(item && !item->path()) { - // update the mounted volume and show a button for eject. - GFile* gf = g_mount_get_root(mount); - FmPath* path = fm_path_new_for_gfile(gf); - g_object_unref(gf); - item->setPath(path); - if(path) - fm_path_unref(path); - // update the mount indicator (eject button) - QStandardItem* ejectBtn = item->parent()->child(item->row(), 1); - Q_ASSERT(ejectBtn); - ejectBtn->setIcon(pThis->ejectIcon_); - } - g_object_unref(vol); - } - else { // network mounts and others - PlacesModelMountItem* item = pThis->itemFromMount(mount); - /* for some unknown reasons, sometimes we get repeated mount-added - * signals and added a device more than one. So, make a sanity check here. */ - if(!item) { - item = new PlacesModelMountItem(mount); - QStandardItem* eject_btn = new QStandardItem(pThis->ejectIcon_, QString()); - pThis->devicesRoot->appendRow(QList() << item << eject_btn); + GVolume* vol = g_mount_get_volume(mount); + if(vol) { // mount-added is also emitted when a volume is newly mounted. + PlacesModelVolumeItem* item = pThis->itemFromVolume(vol); + if(item && !item->path()) { + // update the mounted volume and show a button for eject. + Fm::FilePath path{g_mount_get_root(mount), false}; + item->setPath(path); + // update the mount indicator (eject button) + QStandardItem* ejectBtn = item->parent()->child(item->row(), 1); + Q_ASSERT(ejectBtn); + ejectBtn->setIcon(pThis->ejectIcon_); + } + g_object_unref(vol); + } + else { // network mounts and others + PlacesModelMountItem* item = pThis->itemFromMount(mount); + /* for some unknown reasons, sometimes we get repeated mount-added + * signals and added a device more than one. So, make a sanity check here. */ + if(!item) { + item = new PlacesModelMountItem(mount); + QStandardItem* eject_btn = new QStandardItem(pThis->ejectIcon_, QString()); + pThis->devicesRoot->appendRow(QList() << item << eject_btn); + } } - } } void PlacesModel::onMountChanged(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis) { - gboolean shadowed = FALSE; - // according to gio API doc, a shadowed mount should not be visible to the user + gboolean shadowed = FALSE; + // according to gio API doc, a shadowed mount should not be visible to the user #if GLIB_CHECK_VERSION(2, 20, 0) - shadowed = g_mount_is_shadowed(mount); - // qDebug() << "changed:" << mount << shadowed; + shadowed = g_mount_is_shadowed(mount); + // qDebug() << "changed:" << mount << shadowed; #endif - PlacesModelMountItem* item = pThis->itemFromMount(mount); - if(item) { - if(shadowed) { // if a visible item becomes shadowed, remove it from the model - pThis->shadowedMounts_.push_back(G_MOUNT(g_object_ref(mount))); // remember the shadowed mount - pThis->devicesRoot->removeRow(item->row()); - } - else { // otherwise, update its status - item->update(); + PlacesModelMountItem* item = pThis->itemFromMount(mount); + if(item) { + if(shadowed) { // if a visible item becomes shadowed, remove it from the model + pThis->shadowedMounts_.push_back(G_MOUNT(g_object_ref(mount))); // remember the shadowed mount + pThis->devicesRoot->removeRow(item->row()); + } + else { // otherwise, update its status + item->update(); + } } - } - else { + else { #if GLIB_CHECK_VERSION(2, 20, 0) - if(!shadowed) { // if a mount is unshadowed - int i = pThis->shadowedMounts_.indexOf(mount); - if(i != -1) { // a previously shadowed mount is unshadowed - pThis->shadowedMounts_.removeAt(i); - onMountAdded(monitor, mount, pThis); // add it to our model again - } - } + if(!shadowed) { // if a mount is unshadowed + int i = pThis->shadowedMounts_.indexOf(mount); + if(i != -1) { // a previously shadowed mount is unshadowed + pThis->shadowedMounts_.removeAt(i); + onMountAdded(monitor, mount, pThis); // add it to our model again + } + } #endif - } + } } void PlacesModel::onMountRemoved(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis) { - GVolume* vol = g_mount_get_volume(mount); - // qDebug() << "mount removed" << mount << "volume umounted: " << vol; - if(vol) { - // a volume is being unmounted - // NOTE: Due to some problems of gvfs, sometimes the volume does not receive - // "change" signal so it does not update the eject button. Let's workaround - // this by calling onVolumeChanged() handler manually. (This is needed for mtp://) - onVolumeChanged(monitor, vol, pThis); - g_object_unref(vol); - } - else { // network mounts and others - PlacesModelMountItem* item = pThis->itemFromMount(mount); - if(item) { - pThis->devicesRoot->removeRow(item->row()); + GVolume* vol = g_mount_get_volume(mount); + // qDebug() << "mount removed" << mount << "volume umounted: " << vol; + if(vol) { + // a volume is being unmounted + // NOTE: Due to some problems of gvfs, sometimes the volume does not receive + // "change" signal so it does not update the eject button. Let's workaround + // this by calling onVolumeChanged() handler manually. (This is needed for mtp://) + onVolumeChanged(monitor, vol, pThis); + g_object_unref(vol); + } + else { // network mounts and others + PlacesModelMountItem* item = pThis->itemFromMount(mount); + if(item) { + pThis->devicesRoot->removeRow(item->row()); + } } - } #if GLIB_CHECK_VERSION(2, 20, 0) - // NOTE: g_mount_is_shadowed() sometimes returns FALSE here even if the mount is shadowed. - // I don't know whether this is a bug in gvfs or not. - // So let's check if its in our list instead. - if(pThis->shadowedMounts_.removeOne(mount)) { - // if this is a shadowed mount - // qDebug() << "remove shadow mount"; - g_object_unref(mount); - } + // NOTE: g_mount_is_shadowed() sometimes returns FALSE here even if the mount is shadowed. + // I don't know whether this is a bug in gvfs or not. + // So let's check if its in our list instead. + if(pThis->shadowedMounts_.removeOne(mount)) { + // if this is a shadowed mount + // qDebug() << "remove shadow mount"; + g_object_unref(mount); + } #endif } -void PlacesModel::onVolumeAdded(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis) { - // for some unknown reasons, sometimes we get repeated volume-added - // signals and added a device more than one. So, make a sanity check here. - PlacesModelVolumeItem* volumeItem = pThis->itemFromVolume(volume); - if(!volumeItem) { - volumeItem = new PlacesModelVolumeItem(volume); - QStandardItem* ejectBtn = new QStandardItem(); - if(volumeItem->isMounted()) - ejectBtn->setIcon(pThis->ejectIcon_); - pThis->devicesRoot->appendRow(QList() << volumeItem << ejectBtn); - } -} - -void PlacesModel::onVolumeChanged(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis) { - PlacesModelVolumeItem* item = pThis->itemFromVolume(volume); - if(item) { - item->update(); - if(!item->isMounted()) { // the volume is unmounted, remove the eject button if needed - // remove the eject button for the volume (at column 1 of the same row) - QStandardItem* ejectBtn = item->parent()->child(item->row(), 1); - Q_ASSERT(ejectBtn); - ejectBtn->setIcon(QIcon()); - } - } +void PlacesModel::onVolumeAdded(GVolumeMonitor* /*monitor*/, GVolume* volume, PlacesModel* pThis) { + // for some unknown reasons, sometimes we get repeated volume-added + // signals and added a device more than one. So, make a sanity check here. + PlacesModelVolumeItem* volumeItem = pThis->itemFromVolume(volume); + if(!volumeItem) { + volumeItem = new PlacesModelVolumeItem(volume); + QStandardItem* ejectBtn = new QStandardItem(); + if(volumeItem->isMounted()) { + ejectBtn->setIcon(pThis->ejectIcon_); + } + pThis->devicesRoot->appendRow(QList() << volumeItem << ejectBtn); + } } -void PlacesModel::onVolumeRemoved(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis) { - PlacesModelVolumeItem* item = pThis->itemFromVolume(volume); - if(item) { - pThis->devicesRoot->removeRow(item->row()); - } +void PlacesModel::onVolumeChanged(GVolumeMonitor* /*monitor*/, GVolume* volume, PlacesModel* pThis) { + PlacesModelVolumeItem* item = pThis->itemFromVolume(volume); + if(item) { + item->update(); + if(!item->isMounted()) { // the volume is unmounted, remove the eject button if needed + // remove the eject button for the volume (at column 1 of the same row) + QStandardItem* ejectBtn = item->parent()->child(item->row(), 1); + Q_ASSERT(ejectBtn); + ejectBtn->setIcon(QIcon()); + } + } } -void PlacesModel::onBookmarksChanged(FmBookmarks* bookmarks, PlacesModel* pThis) { - // remove all items - pThis->bookmarksRoot->removeRows(0, pThis->bookmarksRoot->rowCount()); - pThis->loadBookmarks(); +void PlacesModel::onVolumeRemoved(GVolumeMonitor* /*monitor*/, GVolume* volume, PlacesModel* pThis) { + PlacesModelVolumeItem* item = pThis->itemFromVolume(volume); + if(item) { + pThis->devicesRoot->removeRow(item->row()); + } } -void PlacesModel::updateIcons() { - // the icon theme is changed and we need to update the icons - PlacesModelItem* item; - int row; - int n = placesRoot->rowCount(); - for(row = 0; row < n; ++row) { - item = static_cast(placesRoot->child(row)); - item->updateIcon(); - } - n = devicesRoot->rowCount(); - for(row = 0; row < n; ++row) { - item = static_cast(devicesRoot->child(row)); - item->updateIcon(); - } +void PlacesModel::onBookmarksChanged() { + // remove all items + bookmarksRoot->removeRows(0, bookmarksRoot->rowCount()); + loadBookmarks(); } Qt::ItemFlags PlacesModel::flags(const QModelIndex& index) const { - if(index.column() == 1) // make 2nd column of every row selectable. - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; - if(!index.parent().isValid()) { // root items - if(index.row() == 2) // bookmarks root - return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; - else - return Qt::ItemIsEnabled; - } - return QStandardItemModel::flags(index); + if(index.column() == 1) { // make 2nd column of every row selectable. + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; + } + if(!index.parent().isValid()) { // root items + if(index.row() == 2) { // bookmarks root + return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; + } + else { + return Qt::ItemIsEnabled; + } + } + return QStandardItemModel::flags(index); } -QVariant PlacesModel::data(const QModelIndex &index, int role) const { - if(index.column() == 0 && index.parent().isValid()) { - PlacesModelItem* item = static_cast(QStandardItemModel::itemFromIndex(index)); - if(item != nullptr) { - switch(role) { - case FileInfoRole: - return QVariant::fromValue(item->fileInfo()); - case FmIconRole: - return QVariant::fromValue(item->icon()); - } +QVariant PlacesModel::data(const QModelIndex& index, int role) const { + if(index.column() == 0 && index.parent().isValid()) { + PlacesModelItem* item = static_cast(QStandardItemModel::itemFromIndex(index)); + if(item != nullptr) { + switch(role) { + case FileInfoRole: + return QVariant::fromValue(item->fileInfo()); + case FmIconRole: + return QVariant::fromValue(item->icon()); + } + } + } + return QStandardItemModel::data(index, role); +} + +std::shared_ptr PlacesModel::globalInstance() { + auto model = globalInstance_.lock(); + if(!model) { + model = std::make_shared(); + globalInstance_ = model; } - } - return QStandardItemModel::data(index, role); + return model; } -bool PlacesModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { - QStandardItem* item = itemFromIndex(parent); - if(data->hasFormat("application/x-bookmark-row")) { // the data being dopped is a bookmark row - // decode it and do bookmark reordering - QByteArray buf = data->data("application/x-bookmark-row"); - QDataStream stream(&buf, QIODevice::ReadOnly); - int oldPos = -1; - char* pathStr = NULL; - stream >> oldPos >> pathStr; - // find the source bookmark item being dragged - GList* allBookmarks = fm_bookmarks_get_all(bookmarks); - FmBookmarkItem* draggedItem = static_cast(g_list_nth_data(allBookmarks, oldPos)); - // If we cannot find the dragged bookmark item at position , or we find an item, - // but the path of the item is not the same as what we expected, than it's the wrong item. - // This means that the bookmarks are changed during our dnd processing, which is an extremely rare case. - if(!draggedItem || !fm_path_equal_str(draggedItem->path, pathStr, -1)) { - delete []pathStr; - return false; - } - delete []pathStr; - - int newPos = -1; - if(row == -1 && column == -1) { // drop on an item - // we only allow dropping on an bookmark item - if(item && item->parent() == bookmarksRoot) - newPos = parent.row(); - } - else { // drop on a position between items - if(item == bookmarksRoot) // we only allow dropping on a bookmark item - newPos = row; - } - if(newPos != -1 && newPos != oldPos) // reorder the bookmark item - fm_bookmarks_reorder(bookmarks, draggedItem, newPos); - } - else if(data->hasUrls()) { // files uris are dropped - if(row == -1 && column == -1) { // drop uris on an item - if(item && item->parent()) { // need to be a child item - PlacesModelItem* placesItem = static_cast(item); - if(placesItem->path()) { - qDebug() << "dropped dest:" << placesItem->text(); - // TODO: copy or move the dragged files to the dir pointed by the item. - qDebug() << "drop on" << item->text(); +bool PlacesModel::dropMimeData(const QMimeData* data, Qt::DropAction /*action*/, int row, int column, const QModelIndex& parent) { + QStandardItem* item = itemFromIndex(parent); + if(data->hasFormat("application/x-bookmark-row")) { // the data being dopped is a bookmark row + // decode it and do bookmark reordering + QByteArray buf = data->data("application/x-bookmark-row"); + QDataStream stream(&buf, QIODevice::ReadOnly); + int oldPos = -1; + char* pathStr = nullptr; + stream >> oldPos >> pathStr; + // find the source bookmark item being dragged + auto allBookmarks = bookmarks->items(); + auto& draggedItem = allBookmarks[oldPos]; + // If we cannot find the dragged bookmark item at position , or we find an item, + // but the path of the item is not the same as what we expected, than it's the wrong item. + // This means that the bookmarks are changed during our dnd processing, which is an extremely rare case. + auto draggedPath = Fm::FilePath::fromPathStr(pathStr); + if(!draggedItem || draggedItem->path() != draggedPath) { + delete []pathStr; + return false; } - } - } - else { // drop uris on a position between items - if(item == bookmarksRoot) { // we only allow dropping on blank row of bookmarks section - FmPathList* paths = pathListFromQUrls(data->urls()); - for(GList* l = fm_path_list_peek_head_link(paths); l; l = l->next) { - FmPath* path = FM_PATH(l->data); - GFile* gf = fm_path_to_gfile(path); - // FIXME: this is a blocking call - if(g_file_query_file_type(gf, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - NULL) == G_FILE_TYPE_DIRECTORY) { - char* disp_name = fm_path_display_basename(path); - fm_bookmarks_insert(bookmarks, path, disp_name, row); - g_free(disp_name); - } - g_object_unref(gf); - return true; + delete []pathStr; + + int newPos = -1; + if(row == -1 && column == -1) { // drop on an item + // we only allow dropping on an bookmark item + if(item && item->parent() == bookmarksRoot) { + newPos = parent.row(); + } + } + else { // drop on a position between items + if(item == bookmarksRoot) { // we only allow dropping on a bookmark item + newPos = row; + } + } + if(newPos != -1 && newPos != oldPos) { // reorder the bookmark item + bookmarks->reorder(draggedItem, newPos); } - } } - } - return false; + else if(data->hasUrls()) { // files uris are dropped + if(row == -1 && column == -1) { // drop uris on an item + if(item && item->parent()) { // need to be a child item + PlacesModelItem* placesItem = static_cast(item); + if(placesItem->path()) { + qDebug() << "dropped dest:" << placesItem->text(); + // TODO: copy or move the dragged files to the dir pointed by the item. + qDebug() << "drop on" << item->text(); + } + } + } + else { // drop uris on a position between items + if(item == bookmarksRoot) { // we only allow dropping on blank row of bookmarks section + auto paths = pathListFromQUrls(data->urls()); + for(auto& path: paths) { + // FIXME: this is a blocking call + if(g_file_query_file_type(path.gfile().get(), G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + nullptr) == G_FILE_TYPE_DIRECTORY) { + auto disp_name = path.baseName(); + bookmarks->insert(path, disp_name.get(), row); + } + return true; + } + } + } + } + return false; } // we only support dragging bookmark items and use our own // custom pseudo-mime-type: application/x-bookmark-row QMimeData* PlacesModel::mimeData(const QModelIndexList& indexes) const { - if(!indexes.isEmpty()) { - // we only allow dragging one bookmark item at a time, so handle the first index only. - QModelIndex index = indexes.first(); - QStandardItem* item = itemFromIndex(index); - // ensure that it's really a bookmark item - if(item && item->parent() == bookmarksRoot) { - PlacesModelBookmarkItem* bookmarkItem = static_cast(item); - QMimeData* mime = new QMimeData(); - QByteArray data; - QDataStream stream(&data, QIODevice::WriteOnly); - // There is no safe and cross-process way to store a reference of a row. - // Let's store the pos, name, and path of the bookmark item instead. - char* pathStr = fm_path_to_str(bookmarkItem->path()); - stream << index.row() << pathStr; - g_free(pathStr); - mime->setData("application/x-bookmark-row", data); - return mime; - } - } - return NULL; + if(!indexes.isEmpty()) { + // we only allow dragging one bookmark item at a time, so handle the first index only. + QModelIndex index = indexes.first(); + QStandardItem* item = itemFromIndex(index); + // ensure that it's really a bookmark item + if(item && item->parent() == bookmarksRoot) { + PlacesModelBookmarkItem* bookmarkItem = static_cast(item); + QMimeData* mime = new QMimeData(); + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + // There is no safe and cross-process way to store a reference of a row. + // Let's store the pos, name, and path of the bookmark item instead. + auto pathStr = bookmarkItem->path().toString(); + stream << index.row() << pathStr.get(); + mime->setData("application/x-bookmark-row", data); + return mime; + } + } + return nullptr; } QStringList PlacesModel::mimeTypes() const { - return QStringList() << "application/x-bookmark-row" << "text/uri-list"; + return QStringList() << "application/x-bookmark-row" << "text/uri-list"; } Qt::DropActions PlacesModel::supportedDropActions() const { - return QStandardItemModel::supportedDropActions(); + return QStandardItemModel::supportedDropActions(); } diff --git a/src/placesmodel.h b/src/placesmodel.h index 4aba709..08d0a5a 100644 --- a/src/placesmodel.h +++ b/src/placesmodel.h @@ -28,6 +28,11 @@ #include #include +#include + +#include "core/filepath.h" +#include "core/bookmarks.h" + namespace Fm { class PlacesModelItem; @@ -36,103 +41,105 @@ class PlacesModelMountItem; class PlacesModelBookmarkItem; class LIBFM_QT_API PlacesModel : public QStandardItemModel { -Q_OBJECT -friend class PlacesView; + Q_OBJECT + friend class PlacesView; public: - enum { - FileInfoRole = Qt::UserRole, - FmIconRole - }; - - // QAction used for popup menus - class ItemAction : public QAction { - public: - ItemAction(const QModelIndex& index, QString text, QObject* parent = 0): - QAction(text, parent), - index_(index) { - } - - QPersistentModelIndex& index() { - return index_; - } - private: - QPersistentModelIndex index_; - }; + enum { + FileInfoRole = Qt::UserRole, + FmIconRole + }; + + // QAction used for popup menus + class ItemAction : public QAction { + public: + explicit ItemAction(const QModelIndex& index, QString text, QObject* parent = 0): + QAction(text, parent), + index_(index) { + } + + QPersistentModelIndex& index() { + return index_; + } + private: + QPersistentModelIndex index_; + }; public: - explicit PlacesModel(QObject* parent = 0); - virtual ~PlacesModel(); + explicit PlacesModel(QObject* parent = 0); + virtual ~PlacesModel(); - bool showTrash() { - return trashItem_ != NULL; - } - void setShowTrash(bool show); + bool showTrash() { + return trashItem_ != nullptr; + } + void setShowTrash(bool show); - bool showApplications() { - return showApplications_; - } - void setShowApplications(bool show); + bool showApplications() { + return showApplications_; + } + void setShowApplications(bool show); - bool showDesktop() { - return showDesktop_; - } - void setShowDesktop(bool show); + bool showDesktop() { + return showDesktop_; + } + void setShowDesktop(bool show); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + + static std::shared_ptr globalInstance(); public Q_SLOTS: - void updateIcons(); - void updateTrash(); + void updateTrash(); + void onBookmarksChanged(); protected: - PlacesModelItem* itemFromPath(FmPath* path); - PlacesModelItem* itemFromPath(QStandardItem* rootItem, FmPath* path); - PlacesModelVolumeItem* itemFromVolume(GVolume* volume); - PlacesModelMountItem* itemFromMount(GMount* mount); - PlacesModelBookmarkItem* itemFromBookmark(FmBookmarkItem* bkitem); + PlacesModelItem* itemFromPath(const Fm::FilePath& path); + PlacesModelItem* itemFromPath(QStandardItem* rootItem, const Fm::FilePath & path); + PlacesModelVolumeItem* itemFromVolume(GVolume* volume); + PlacesModelMountItem* itemFromMount(GMount* mount); + PlacesModelBookmarkItem* itemFromBookmark(std::shared_ptr bkitem); - virtual Qt::ItemFlags flags(const QModelIndex& index) const; - virtual QStringList mimeTypes() const; - virtual QMimeData* mimeData(const QModelIndexList& indexes) const; - virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); - Qt::DropActions supportedDropActions() const; + virtual Qt::ItemFlags flags(const QModelIndex& index) const; + virtual QStringList mimeTypes() const; + virtual QMimeData* mimeData(const QModelIndexList& indexes) const; + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + Qt::DropActions supportedDropActions() const; - void createTrashItem(); + void createTrashItem(); private: - void loadBookmarks(); - - static void onVolumeAdded(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis); - static void onVolumeRemoved(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis); - static void onVolumeChanged(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis); - static void onMountAdded(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis); - static void onMountRemoved(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis); - static void onMountChanged(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis); + void loadBookmarks(); - static void onBookmarksChanged(FmBookmarks* bookmarks, PlacesModel* pThis); + static void onVolumeAdded(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis); + static void onVolumeRemoved(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis); + static void onVolumeChanged(GVolumeMonitor* monitor, GVolume* volume, PlacesModel* pThis); + static void onMountAdded(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis); + static void onMountRemoved(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis); + static void onMountChanged(GVolumeMonitor* monitor, GMount* mount, PlacesModel* pThis); - static void onTrashChanged(GFileMonitor *monitor, GFile *gf, GFile *other, GFileMonitorEvent evt, PlacesModel* pThis); + static void onTrashChanged(GFileMonitor* monitor, GFile* gf, GFile* other, GFileMonitorEvent evt, PlacesModel* pThis); private: - FmBookmarks* bookmarks; - GVolumeMonitor* volumeMonitor; - QList jobs; - bool showApplications_; - bool showDesktop_; - QStandardItem* placesRoot; - QStandardItem* devicesRoot; - QStandardItem* bookmarksRoot; - PlacesModelItem* trashItem_; - GFileMonitor* trashMonitor_; - PlacesModelItem* desktopItem; - PlacesModelItem* homeItem; - PlacesModelItem* computerItem; - PlacesModelItem* networkItem; - PlacesModelItem* applicationsItem; - QIcon ejectIcon_; - QList shadowedMounts_; + std::shared_ptr bookmarks; + GVolumeMonitor* volumeMonitor; + QList jobs; + bool showApplications_; + bool showDesktop_; + QStandardItem* placesRoot; + QStandardItem* devicesRoot; + QStandardItem* bookmarksRoot; + PlacesModelItem* trashItem_; + GFileMonitor* trashMonitor_; + PlacesModelItem* desktopItem; + PlacesModelItem* homeItem; + PlacesModelItem* computerItem; + PlacesModelItem* networkItem; + PlacesModelItem* applicationsItem; + QIcon ejectIcon_; + QList shadowedMounts_; + + static std::weak_ptr globalInstance_; }; } diff --git a/src/placesmodelitem.cpp b/src/placesmodelitem.cpp index 1de2471..cc1b2e3 100644 --- a/src/placesmodelitem.cpp +++ b/src/placesmodelitem.cpp @@ -26,166 +26,128 @@ namespace Fm { PlacesModelItem::PlacesModelItem(): - QStandardItem(), - path_(NULL), - fileInfo_(NULL), - icon_(NULL) { + QStandardItem(), + fileInfo_(nullptr), + icon_(nullptr) { } -PlacesModelItem::PlacesModelItem(const char* iconName, QString title, FmPath* path): - QStandardItem(title), - path_(path ? fm_path_ref(path) : NULL), - fileInfo_(NULL), - icon_(fm_icon_from_name(iconName)) { - if(icon_) - QStandardItem::setIcon(IconTheme::icon(icon_)); - setEditable(false); +PlacesModelItem::PlacesModelItem(const char* iconName, QString title, Fm::FilePath path): + QStandardItem(title), + path_{std::move(path)}, + icon_(Fm::IconInfo::fromName(iconName)) { + if(icon_) { + QStandardItem::setIcon(icon_->qicon()); + } + setEditable(false); } -PlacesModelItem::PlacesModelItem(FmIcon* icon, QString title, FmPath* path): - QStandardItem(title), - path_(path ? fm_path_ref(path) : NULL), - fileInfo_(NULL), - icon_(icon ? fm_icon_ref(icon) : NULL) { - if(icon_) - QStandardItem::setIcon(IconTheme::icon(icon)); - setEditable(false); +PlacesModelItem::PlacesModelItem(std::shared_ptr icon, QString title, Fm::FilePath path): + QStandardItem(title), + path_{std::move(path)}, + icon_{std::move(icon)} { + if(icon_) { + QStandardItem::setIcon(icon_->qicon()); + } + setEditable(false); } -PlacesModelItem::PlacesModelItem(QIcon icon, QString title, FmPath* path): - QStandardItem(icon, title), - path_(path ? fm_path_ref(path) : NULL), - fileInfo_(NULL), - icon_(NULL) { - setEditable(false); +PlacesModelItem::PlacesModelItem(QIcon icon, QString title, Fm::FilePath path): + QStandardItem(icon, title), + path_{std::move(path)} { + setEditable(false); } PlacesModelItem::~PlacesModelItem() { - if(path_) - fm_path_unref(path_); - if(fileInfo_) - g_object_unref(fileInfo_); - if(icon_) - fm_icon_unref(icon_); } -void PlacesModelItem::setPath(FmPath* path) { - if(path_) - fm_path_unref(path_); - path_ = path ? fm_path_ref(path) : NULL; -} -void PlacesModelItem::setIcon(FmIcon* icon) { - if(icon_) - fm_icon_unref(icon_); - if(icon) { - icon_ = fm_icon_ref(icon); - QStandardItem::setIcon(IconTheme::icon(icon_)); - } - else { - icon_ = NULL; - QStandardItem::setIcon(QIcon()); - } +void PlacesModelItem::setIcon(std::shared_ptr icon) { + icon_= std::move(icon); + if(icon_) { + QStandardItem::setIcon(icon_->qicon()); + } + else { + QStandardItem::setIcon(QIcon()); + } } void PlacesModelItem::setIcon(GIcon* gicon) { - FmIcon* icon = gicon ? fm_icon_from_gicon(gicon) : NULL; - setIcon(icon); - fm_icon_unref(icon); + setIcon(Fm::IconInfo::fromGIcon(Fm::GIconPtr{gicon, true})); } void PlacesModelItem::updateIcon() { - if(icon_) - QStandardItem::setIcon(IconTheme::icon(icon_)); + if(icon_) { + QStandardItem::setIcon(icon_->qicon()); + } } QVariant PlacesModelItem::data(int role) const { - // we use a QPixmap from FmIcon cache rather than QIcon object for decoration role. - return QStandardItem::data(role); -} - -void PlacesModelItem::setFileInfo(FmFileInfo* fileInfo) { - // FIXME: how can we correctly update icon? - if(fileInfo_) - fm_file_info_unref(fileInfo_); - - if(fileInfo) { - fileInfo_ = fm_file_info_ref(fileInfo); - } - else - fileInfo_ = NULL; + // we use a QPixmap from FmIcon cache rather than QIcon object for decoration role. + return QStandardItem::data(role); } -PlacesModelBookmarkItem::PlacesModelBookmarkItem(FmBookmarkItem* bm_item): - PlacesModelItem(QIcon::fromTheme("folder"), QString::fromUtf8(bm_item->name), bm_item->path), - bookmarkItem_(fm_bookmark_item_ref(bm_item)) { - setEditable(true); +PlacesModelBookmarkItem::PlacesModelBookmarkItem(std::shared_ptr bm_item): + PlacesModelItem{Fm::IconInfo::fromName("folder"), bm_item->name(), bm_item->path()}, + bookmarkItem_{std::move(bm_item)} { + setEditable(true); } PlacesModelVolumeItem::PlacesModelVolumeItem(GVolume* volume): - PlacesModelItem(), - volume_(reinterpret_cast(g_object_ref(volume))) { - update(); - setEditable(false); + PlacesModelItem(), + volume_(reinterpret_cast(g_object_ref(volume))) { + update(); + setEditable(false); } void PlacesModelVolumeItem::update() { - // set title - char* volumeName = g_volume_get_name(volume_); - setText(QString::fromUtf8(volumeName)); - g_free(volumeName); - - // set icon - GIcon* gicon = g_volume_get_icon(volume_); - setIcon(gicon); - g_object_unref(gicon); - - // set dir path - GMount* mount = g_volume_get_mount(volume_); - if(mount) { - GFile* mount_root = g_mount_get_root(mount); - FmPath* mount_path = fm_path_new_for_gfile(mount_root); - setPath(mount_path); - fm_path_unref(mount_path); - g_object_unref(mount_root); - g_object_unref(mount); - } - else { - setPath(NULL); - } + // set title + char* volumeName = g_volume_get_name(volume_); + setText(QString::fromUtf8(volumeName)); + g_free(volumeName); + + // set icon + Fm::GIconPtr gicon{g_volume_get_icon(volume_), false}; + setIcon(gicon.get()); + + // set dir path + Fm::GMountPtr mount{g_volume_get_mount(volume_), false}; + if(mount) { + Fm::FilePath mount_root{g_mount_get_root(mount.get()), false}; + setPath(mount_root); + } + else { + setPath(Fm::FilePath{}); + } } bool PlacesModelVolumeItem::isMounted() { - GMount* mount = g_volume_get_mount(volume_); - if(mount) - g_object_unref(mount); - return mount != NULL ? true : false; + GMount* mount = g_volume_get_mount(volume_); + if(mount) { + g_object_unref(mount); + } + return mount != nullptr ? true : false; } PlacesModelMountItem::PlacesModelMountItem(GMount* mount): - PlacesModelItem(), - mount_(reinterpret_cast(mount)) { - update(); - setEditable(false); + PlacesModelItem(), + mount_(reinterpret_cast(mount)) { + update(); + setEditable(false); } void PlacesModelMountItem::update() { - // set title - setText(QString::fromUtf8(g_mount_get_name(mount_))); - - // set path - GFile* mount_root = g_mount_get_root(mount_); - FmPath* mount_path = fm_path_new_for_gfile(mount_root); - setPath(mount_path); - fm_path_unref(mount_path); - g_object_unref(mount_root); - - // set icon - GIcon* gicon = g_mount_get_icon(mount_); - setIcon(gicon); - g_object_unref(gicon); + // set title + setText(QString::fromUtf8(g_mount_get_name(mount_))); + + // set path + Fm::FilePath mount_root{g_mount_get_root(mount_), false}; + setPath(mount_root); + + // set icon + Fm::GIconPtr gicon{g_mount_get_icon(mount_), false}; + setIcon(gicon.get()); } } diff --git a/src/placesmodelitem.h b/src/placesmodelitem.h index d788bbb..cc5bd57 100644 --- a/src/placesmodelitem.h +++ b/src/placesmodelitem.h @@ -28,101 +28,105 @@ #include #include +#include "core/fileinfo.h" +#include "core/filepath.h" +#include "core/bookmarks.h" + namespace Fm { // model item class LIBFM_QT_API PlacesModelItem : public QStandardItem { public: - enum Type { - Places = QStandardItem::UserType + 1, - Volume, - Mount, - Bookmark - }; + enum Type { + Places = QStandardItem::UserType + 1, + Volume, + Mount, + Bookmark + }; public: - PlacesModelItem(); - PlacesModelItem(QIcon icon, QString title, FmPath* path = NULL); - PlacesModelItem(const char* iconName, QString title, FmPath* path = NULL); - PlacesModelItem(FmIcon* icon, QString title, FmPath* path = NULL); - ~PlacesModelItem(); - - FmFileInfo* fileInfo() { - return fileInfo_; - } - void setFileInfo(FmFileInfo* fileInfo); - - FmPath* path() { - return path_; - } - void setPath(FmPath* path); - - FmIcon* icon() { - return icon_; - } - void setIcon(FmIcon* icon); - void setIcon(GIcon* gicon); - void updateIcon(); - - QVariant data(int role = Qt::UserRole + 1) const; - - virtual int type() const { - return Places; - } + explicit PlacesModelItem(); + explicit PlacesModelItem(QIcon icon, QString title, Fm::FilePath path = Fm::FilePath{}); + explicit PlacesModelItem(const char* iconName, QString title, Fm::FilePath path = Fm::FilePath{}); + explicit PlacesModelItem(std::shared_ptr icon, QString title, Fm::FilePath path = Fm::FilePath{}); + ~PlacesModelItem(); + + const std::shared_ptr& fileInfo() const { + return fileInfo_; + } + void setFileInfo(std::shared_ptr fileInfo) { + fileInfo_ = std::move(fileInfo); + } + + const Fm::FilePath& path() const { + return path_; + } + void setPath(Fm::FilePath path) { + path_ = std::move(path); + } + + const std::shared_ptr& icon() const { + return icon_; + } + void setIcon(std::shared_ptr icon); + void setIcon(GIcon* gicon); + void updateIcon(); + + QVariant data(int role = Qt::UserRole + 1) const; + + virtual int type() const { + return Places; + } private: - FmPath* path_; - FmFileInfo* fileInfo_; - FmIcon* icon_; + Fm::FilePath path_; + std::shared_ptr fileInfo_; + std::shared_ptr icon_; }; class LIBFM_QT_API PlacesModelVolumeItem : public PlacesModelItem { public: - PlacesModelVolumeItem(GVolume* volume); - bool isMounted(); - bool canEject() { - return g_volume_can_eject(volume_); - } - virtual int type() const { - return Volume; - } - GVolume* volume() { - return volume_; - } - void update(); + PlacesModelVolumeItem(GVolume* volume); + bool isMounted(); + bool canEject() { + return g_volume_can_eject(volume_); + } + virtual int type() const { + return Volume; + } + GVolume* volume() { + return volume_; + } + void update(); private: - GVolume* volume_; + GVolume* volume_; }; class LIBFM_QT_API PlacesModelMountItem : public PlacesModelItem { public: - PlacesModelMountItem(GMount* mount); - virtual int type() const { - return Mount; - } - GMount* mount() const { - return mount_; - } - void update(); + PlacesModelMountItem(GMount* mount); + virtual int type() const { + return Mount; + } + GMount* mount() const { + return mount_; + } + void update(); private: - GMount* mount_; + GMount* mount_; }; class LIBFM_QT_API PlacesModelBookmarkItem : public PlacesModelItem { public: - virtual int type() const { - return Bookmark; - } - PlacesModelBookmarkItem(FmBookmarkItem* bm_item); - virtual ~PlacesModelBookmarkItem() { - if(bookmarkItem_) - fm_bookmark_item_unref(bookmarkItem_); - } - FmBookmarkItem* bookmark() const { - return bookmarkItem_; - } + virtual int type() const { + return Bookmark; + } + PlacesModelBookmarkItem(std::shared_ptr bm_item); + const std::shared_ptr& bookmark() const { + return bookmarkItem_; + } private: - FmBookmarkItem* bookmarkItem_; + std::shared_ptr bookmarkItem_; }; } diff --git a/src/placesview.cpp b/src/placesview.cpp index d38abe9..8fa3609 100644 --- a/src/placesview.cpp +++ b/src/placesview.cpp @@ -33,398 +33,401 @@ namespace Fm { PlacesView::PlacesView(QWidget* parent): - QTreeView(parent), - currentPath_(NULL) { - setRootIsDecorated(false); - setHeaderHidden(true); - setIndentation(12); - - connect(this, &QTreeView::clicked, this, &PlacesView::onClicked); - connect(this, &QTreeView::pressed, this, &PlacesView::onPressed); - - setIconSize(QSize(24, 24)); - - FolderItemDelegate* delegate = new FolderItemDelegate(this, this); - delegate->setFileInfoRole(PlacesModel::FileInfoRole); - delegate->setFmIconRole(PlacesModel::FmIconRole); - setItemDelegateForColumn(0, delegate); - - // FIXME: we may share this model amont all views - model_ = new PlacesModel(this); - setModel(model_); - - QHeaderView* headerView = header(); - headerView->setSectionResizeMode(0, QHeaderView::Stretch); - headerView->setSectionResizeMode(1, QHeaderView::Fixed); - headerView->setStretchLastSection(false); - expandAll(); - - // FIXME: is there any better way to make the first column span the whole row? - setFirstColumnSpanned(0, QModelIndex(), true); // places root - setFirstColumnSpanned(1, QModelIndex(), true); // devices root - setFirstColumnSpanned(2, QModelIndex(), true); // bookmarks root - - // the 2nd column is for the eject buttons - setSelectionBehavior(QAbstractItemView::SelectRows); // FIXME: why this does not work? - setAllColumnsShowFocus(false); - - setAcceptDrops(true); - setDragEnabled(true); - - // update the umount button's column width based on icon size - onIconSizeChanged(iconSize()); + QTreeView(parent) { + setRootIsDecorated(false); + setHeaderHidden(true); + setIndentation(12); + + connect(this, &QTreeView::clicked, this, &PlacesView::onClicked); + connect(this, &QTreeView::pressed, this, &PlacesView::onPressed); + + setIconSize(QSize(24, 24)); + + FolderItemDelegate* delegate = new FolderItemDelegate(this, this); + delegate->setFileInfoRole(PlacesModel::FileInfoRole); + delegate->setIconInfoRole(PlacesModel::FmIconRole); + setItemDelegateForColumn(0, delegate); + + model_ = PlacesModel::globalInstance(); + setModel(model_.get()); + + QHeaderView* headerView = header(); + headerView->setSectionResizeMode(0, QHeaderView::Stretch); + headerView->setSectionResizeMode(1, QHeaderView::Fixed); + headerView->setStretchLastSection(false); + expandAll(); + + // FIXME: is there any better way to make the first column span the whole row? + setFirstColumnSpanned(0, QModelIndex(), true); // places root + setFirstColumnSpanned(1, QModelIndex(), true); // devices root + setFirstColumnSpanned(2, QModelIndex(), true); // bookmarks root + + // the 2nd column is for the eject buttons + setSelectionBehavior(QAbstractItemView::SelectRows); // FIXME: why this does not work? + setAllColumnsShowFocus(false); + + setAcceptDrops(true); + setDragEnabled(true); + + // update the umount button's column width based on icon size + onIconSizeChanged(iconSize()); #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) // this signal requires Qt >= 5.5 - connect(this, &QAbstractItemView::iconSizeChanged, this, &PlacesView::onIconSizeChanged); + connect(this, &QAbstractItemView::iconSizeChanged, this, &PlacesView::onIconSizeChanged); #endif } PlacesView::~PlacesView() { - if(currentPath_) - fm_path_unref(currentPath_); - // qDebug("delete PlacesView"); + // qDebug("delete PlacesView"); } void PlacesView::activateRow(int type, const QModelIndex& index) { - if(!index.parent().isValid()) // ignore root items - return; - PlacesModelItem* item = static_cast(model_->itemFromIndex(index)); - if(item) { - FmPath* path = item->path(); - if(!path) { - // check if mounting volumes is needed - if(item->type() == PlacesModelItem::Volume) { - PlacesModelVolumeItem* volumeItem = static_cast(item); - if(!volumeItem->isMounted()) { - // Mount the volume - GVolume* volume = volumeItem->volume(); - MountOperation* op = new MountOperation(true, this); - op->mount(volume); - // connect(op, SIGNAL(finished(GError*)), SLOT(onMountOperationFinished(GError*))); - // blocking here until the mount operation is finished? - - // FIXME: update status of the volume after mount is finished!! - if(!op->wait()) - return; - path = item->path(); - } - } + if(!index.parent().isValid()) { // ignore root items + return; } - if(path) { - Q_EMIT chdirRequested(type, path); + PlacesModelItem* item = static_cast(model_->itemFromIndex(index)); + if(item) { + auto path = item->path(); + if(!path) { + // check if mounting volumes is needed + if(item->type() == PlacesModelItem::Volume) { + PlacesModelVolumeItem* volumeItem = static_cast(item); + if(!volumeItem->isMounted()) { + // Mount the volume + GVolume* volume = volumeItem->volume(); + MountOperation* op = new MountOperation(true, this); + op->mount(volume); + // connect(op, SIGNAL(finished(GError*)), SLOT(onMountOperationFinished(GError*))); + // blocking here until the mount operation is finished? + + // FIXME: update status of the volume after mount is finished!! + if(!op->wait()) { + return; + } + path = item->path(); + } + } + } + if(path) { + Q_EMIT chdirRequested(type, path); + } } - } } // mouse button pressed void PlacesView::onPressed(const QModelIndex& index) { - // if middle button is pressed - if(QGuiApplication::mouseButtons() & Qt::MiddleButton) { - // the real item is at column 0 - activateRow(1, 0 == index.column() ? index : index.sibling(index.row(), 0)); - } + // if middle button is pressed + if(QGuiApplication::mouseButtons() & Qt::MiddleButton) { + // the real item is at column 0 + activateRow(1, 0 == index.column() ? index : index.sibling(index.row(), 0)); + } } void PlacesView::onIconSizeChanged(const QSize& size) { - setColumnWidth(1, size.width() + 5); + setColumnWidth(1, size.width() + 5); } void PlacesView::onEjectButtonClicked(PlacesModelItem* item) { - // The eject button is clicked for a device item (volume or mount) - if(item->type() == PlacesModelItem::Volume) { - PlacesModelVolumeItem* volumeItem = static_cast(item); - MountOperation* op = new MountOperation(true, this); - if(volumeItem->canEject()) // do eject if applicable - op->eject(volumeItem->volume()); - else // otherwise, do unmount instead - op->unmount(volumeItem->volume()); - } - else if(item->type() == PlacesModelItem::Mount) { - PlacesModelMountItem* mountItem = static_cast(item); - MountOperation* op = new MountOperation(true, this); - op->unmount(mountItem->mount()); - } - qDebug("PlacesView::onEjectButtonClicked"); + // The eject button is clicked for a device item (volume or mount) + if(item->type() == PlacesModelItem::Volume) { + PlacesModelVolumeItem* volumeItem = static_cast(item); + MountOperation* op = new MountOperation(true, this); + if(volumeItem->canEject()) { // do eject if applicable + op->eject(volumeItem->volume()); + } + else { // otherwise, do unmount instead + op->unmount(volumeItem->volume()); + } + } + else if(item->type() == PlacesModelItem::Mount) { + PlacesModelMountItem* mountItem = static_cast(item); + MountOperation* op = new MountOperation(true, this); + op->unmount(mountItem->mount()); + } + qDebug("PlacesView::onEjectButtonClicked"); } void PlacesView::onClicked(const QModelIndex& index) { - if(!index.parent().isValid()) // ignore root items - return; - - if(index.column() == 0) { - activateRow(0, index); - } - else if(index.column() == 1) { // column 1 contains eject buttons of the mounted devices - if(index.parent() == model_->devicesRoot->index()) { // this is a mounted device - // the eject button is clicked - QModelIndex itemIndex = index.sibling(index.row(), 0); // the real item is at column 0 - PlacesModelItem* item = static_cast(model_->itemFromIndex(itemIndex)); - if(item) { - // eject the volume or the mount - onEjectButtonClicked(item); - } + if(!index.parent().isValid()) { // ignore root items + return; + } + + if(index.column() == 0) { + activateRow(0, index); + } + else if(index.column() == 1) { // column 1 contains eject buttons of the mounted devices + if(index.parent() == model_->devicesRoot->index()) { // this is a mounted device + // the eject button is clicked + QModelIndex itemIndex = index.sibling(index.row(), 0); // the real item is at column 0 + PlacesModelItem* item = static_cast(model_->itemFromIndex(itemIndex)); + if(item) { + // eject the volume or the mount + onEjectButtonClicked(item); + } + } + else { + activateRow(0, index.sibling(index.row(), 0)); + } } - else - activateRow(0, index.sibling(index.row(), 0)); - } } -void PlacesView::setCurrentPath(FmPath* path) { - if(currentPath_) - fm_path_unref(currentPath_); - if(path) { - currentPath_ = fm_path_ref(path); - // TODO: search for item with the path in model_ and select it. - PlacesModelItem* item = model_->itemFromPath(currentPath_); - if(item) { - selectionModel()->select(item->index(), QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows); +void PlacesView::setCurrentPath(Fm::FilePath path) { + currentPath_ = std::move(path); + if(currentPath_) { + // TODO: search for item with the path in model_ and select it. + PlacesModelItem* item = model_->itemFromPath(currentPath_); + if(item) { + selectionModel()->select(item->index(), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + } + else { + clearSelection(); + } + } + else { + clearSelection(); } - else - clearSelection(); - } - else { - currentPath_ = NULL; - clearSelection(); - } } void PlacesView::dragMoveEvent(QDragMoveEvent* event) { - QTreeView::dragMoveEvent(event); - /* - QModelIndex index = indexAt(event->pos()); - if(event->isAccepted() && index.isValid() && index.parent() == model_->bookmarksRoot->index()) { - if(dropIndicatorPosition() != OnItem) { - event->setDropAction(Qt::LinkAction); - event->accept(); + QTreeView::dragMoveEvent(event); + /* + QModelIndex index = indexAt(event->pos()); + if(event->isAccepted() && index.isValid() && index.parent() == model_->bookmarksRoot->index()) { + if(dropIndicatorPosition() != OnItem) { + event->setDropAction(Qt::LinkAction); + event->accept(); + } } - } - */ + */ } void PlacesView::dropEvent(QDropEvent* event) { - QTreeView::dropEvent(event); + QTreeView::dropEvent(event); } void PlacesView::onEmptyTrash() { - FmPathList* files = fm_path_list_new(); - fm_path_list_push_tail(files, fm_path_get_trash()); - Fm::FileOperation::deleteFiles(files); - fm_path_list_unref(files); + Fm::FilePathList files; + files.push_back(Fm::FilePath::fromUri("trash:///")); + Fm::FileOperation::deleteFiles(std::move(files)); } -void PlacesView::onMoveBookmarkUp() -{ - PlacesModel::ItemAction* action = static_cast(sender()); - if(!action->index().isValid()) - return; - PlacesModelBookmarkItem* item = static_cast(model_->itemFromIndex(action->index())); - - int row = item->row(); - if(row > 0) { - FmBookmarkItem* bookmarkItem = item->bookmark(); - FmBookmarks* bookmarks = fm_bookmarks_dup(); - fm_bookmarks_reorder(bookmarks, bookmarkItem, row - 1); - g_object_unref(bookmarks); - } +void PlacesView::onMoveBookmarkUp() { + PlacesModel::ItemAction* action = static_cast(sender()); + if(!action->index().isValid()) { + return; + } + PlacesModelBookmarkItem* item = static_cast(model_->itemFromIndex(action->index())); + + int row = item->row(); + if(row > 0) { + auto bookmarkItem = item->bookmark(); + Fm::Bookmarks::globalInstance()->reorder(bookmarkItem, row - 1); + } } -void PlacesView::onMoveBookmarkDown() -{ - PlacesModel::ItemAction* action = static_cast(sender()); - if(!action->index().isValid()) - return; - PlacesModelBookmarkItem* item = static_cast(model_->itemFromIndex(action->index())); - - int row = item->row(); - if(row < model_->rowCount()) { - FmBookmarkItem* bookmarkItem = item->bookmark(); - FmBookmarks* bookmarks = fm_bookmarks_dup(); - fm_bookmarks_reorder(bookmarks, bookmarkItem, row + 1); - g_object_unref(bookmarks); - } +void PlacesView::onMoveBookmarkDown() { + PlacesModel::ItemAction* action = static_cast(sender()); + if(!action->index().isValid()) { + return; + } + PlacesModelBookmarkItem* item = static_cast(model_->itemFromIndex(action->index())); + + int row = item->row(); + if(row < model_->rowCount()) { + auto bookmarkItem = item->bookmark(); + Fm::Bookmarks::globalInstance()->reorder(bookmarkItem, row + 1); + } } void PlacesView::onDeleteBookmark() { - PlacesModel::ItemAction* action = static_cast(sender()); - if(!action->index().isValid()) - return; - PlacesModelBookmarkItem* item = static_cast(model_->itemFromIndex(action->index())); - FmBookmarkItem* bookmarkItem = item->bookmark(); - FmBookmarks* bookmarks = fm_bookmarks_dup(); - fm_bookmarks_remove(bookmarks, bookmarkItem); - g_object_unref(bookmarks); + PlacesModel::ItemAction* action = static_cast(sender()); + if(!action->index().isValid()) { + return; + } + PlacesModelBookmarkItem* item = static_cast(model_->itemFromIndex(action->index())); + auto bookmarkItem = item->bookmark(); + Fm::Bookmarks::globalInstance()->remove(bookmarkItem); } // virtual -void PlacesView::commitData(QWidget * editor) { - QTreeView::commitData(editor); - PlacesModelBookmarkItem* item = static_cast(model_->itemFromIndex(currentIndex())); - FmBookmarkItem* bookmarkItem = item->bookmark(); - FmBookmarks* bookmarks = fm_bookmarks_dup(); - // rename bookmark - fm_bookmarks_rename(bookmarks, bookmarkItem, item->text().toUtf8().constData()); - g_object_unref(bookmarks); +void PlacesView::commitData(QWidget* editor) { + QTreeView::commitData(editor); + PlacesModelBookmarkItem* item = static_cast(model_->itemFromIndex(currentIndex())); + auto bookmarkItem = item->bookmark(); + // rename bookmark + Fm::Bookmarks::globalInstance()->rename(bookmarkItem, item->text()); } -void PlacesView::onOpenNewTab() -{ - PlacesModel::ItemAction* action = static_cast(sender()); - if(!action->index().isValid()) - return; - PlacesModelItem* item = static_cast(model_->itemFromIndex(action->index())); - if(item) - Q_EMIT chdirRequested(1, item->path()); +void PlacesView::onOpenNewTab() { + PlacesModel::ItemAction* action = static_cast(sender()); + if(!action->index().isValid()) { + return; + } + PlacesModelItem* item = static_cast(model_->itemFromIndex(action->index())); + if(item) { + Q_EMIT chdirRequested(1, item->path()); + } } -void PlacesView::onOpenNewWindow() -{ - PlacesModel::ItemAction* action = static_cast(sender()); - if(!action->index().isValid()) - return; - PlacesModelItem* item = static_cast(model_->itemFromIndex(action->index())); - if(item) - Q_EMIT chdirRequested(2, item->path()); +void PlacesView::onOpenNewWindow() { + PlacesModel::ItemAction* action = static_cast(sender()); + if(!action->index().isValid()) { + return; + } + PlacesModelItem* item = static_cast(model_->itemFromIndex(action->index())); + if(item) { + Q_EMIT chdirRequested(2, item->path()); + } } void PlacesView::onRenameBookmark() { - PlacesModel::ItemAction* action = static_cast(sender()); - if(!action->index().isValid()) - return; - PlacesModelBookmarkItem* item = static_cast(model_->itemFromIndex(action->index())); - setFocus(); - setCurrentIndex(item->index()); - edit(item->index()); + PlacesModel::ItemAction* action = static_cast(sender()); + if(!action->index().isValid()) { + return; + } + PlacesModelBookmarkItem* item = static_cast(model_->itemFromIndex(action->index())); + setFocus(); + setCurrentIndex(item->index()); + edit(item->index()); } void PlacesView::onMountVolume() { - PlacesModel::ItemAction* action = static_cast(sender()); - if(!action->index().isValid()) - return; - PlacesModelVolumeItem* item = static_cast(model_->itemFromIndex(action->index())); - MountOperation* op = new MountOperation(true, this); - op->mount(item->volume()); - op->wait(); + PlacesModel::ItemAction* action = static_cast(sender()); + if(!action->index().isValid()) { + return; + } + PlacesModelVolumeItem* item = static_cast(model_->itemFromIndex(action->index())); + MountOperation* op = new MountOperation(true, this); + op->mount(item->volume()); + op->wait(); } void PlacesView::onUnmountVolume() { - PlacesModel::ItemAction* action = static_cast(sender()); - if(!action->index().isValid()) - return; - PlacesModelVolumeItem* item = static_cast(model_->itemFromIndex(action->index())); - MountOperation* op = new MountOperation(true, this); - op->unmount(item->volume()); - op->wait(); + PlacesModel::ItemAction* action = static_cast(sender()); + if(!action->index().isValid()) { + return; + } + PlacesModelVolumeItem* item = static_cast(model_->itemFromIndex(action->index())); + MountOperation* op = new MountOperation(true, this); + op->unmount(item->volume()); + op->wait(); } void PlacesView::onUnmountMount() { - PlacesModel::ItemAction* action = static_cast(sender()); - if(!action->index().isValid()) - return; - PlacesModelMountItem* item = static_cast(model_->itemFromIndex(action->index())); - GMount* mount = item->mount(); - MountOperation* op = new MountOperation(true, this); - op->unmount(mount); - op->wait(); + PlacesModel::ItemAction* action = static_cast(sender()); + if(!action->index().isValid()) { + return; + } + PlacesModelMountItem* item = static_cast(model_->itemFromIndex(action->index())); + GMount* mount = item->mount(); + MountOperation* op = new MountOperation(true, this); + op->unmount(mount); + op->wait(); } void PlacesView::onEjectVolume() { - PlacesModel::ItemAction* action = static_cast(sender()); - if(!action->index().isValid()) - return; - PlacesModelVolumeItem* item = static_cast(model_->itemFromIndex(action->index())); - MountOperation* op = new MountOperation(true, this); - op->eject(item->volume()); - op->wait(); + PlacesModel::ItemAction* action = static_cast(sender()); + if(!action->index().isValid()) { + return; + } + PlacesModelVolumeItem* item = static_cast(model_->itemFromIndex(action->index())); + MountOperation* op = new MountOperation(true, this); + op->eject(item->volume()); + op->wait(); } void PlacesView::contextMenuEvent(QContextMenuEvent* event) { - QModelIndex index = indexAt(event->pos()); - if(index.isValid() && index.parent().isValid()) { - if(index.column() != 0) // the real item is at column 0 - index = index.sibling(index.row(), 0); - - // Do not take the ownership of the menu since - // it will be deleted with deleteLater() upon hidden. - // This is possibly related to #145 - https://github.com/lxde/pcmanfm-qt/issues/145 - QMenu* menu = new QMenu(); - QAction* action; - PlacesModelItem* item = static_cast(model_->itemFromIndex(index)); + QModelIndex index = indexAt(event->pos()); + if(index.isValid() && index.parent().isValid()) { + if(index.column() != 0) { // the real item is at column 0 + index = index.sibling(index.row(), 0); + } - if(item->type() != PlacesModelItem::Mount - && (item->type() != PlacesModelItem::Volume - || static_cast(item)->isMounted())) { - action = new PlacesModel::ItemAction(item->index(), tr("Open in New Tab"), menu); - connect(action, &QAction::triggered, this, &PlacesView::onOpenNewTab); - menu->addAction(action); - action = new PlacesModel::ItemAction(item->index(), tr("Open in New Window"), menu); - connect(action, &QAction::triggered, this, &PlacesView::onOpenNewWindow); - menu->addAction(action); - } + // Do not take the ownership of the menu since + // it will be deleted with deleteLater() upon hidden. + // This is possibly related to #145 - https://github.com/lxde/pcmanfm-qt/issues/145 + QMenu* menu = new QMenu(); + QAction* action; + PlacesModelItem* item = static_cast(model_->itemFromIndex(index)); + + if(item->type() != PlacesModelItem::Mount + && (item->type() != PlacesModelItem::Volume + || static_cast(item)->isMounted())) { + action = new PlacesModel::ItemAction(item->index(), tr("Open in New Tab"), menu); + connect(action, &QAction::triggered, this, &PlacesView::onOpenNewTab); + menu->addAction(action); + action = new PlacesModel::ItemAction(item->index(), tr("Open in New Window"), menu); + connect(action, &QAction::triggered, this, &PlacesView::onOpenNewWindow); + menu->addAction(action); + } - switch(item->type()) { - case PlacesModelItem::Places: { - FmPath* path = item->path(); - if(path && fm_path_equal(fm_path_get_trash(), path)) { - action = new PlacesModel::ItemAction(item->index(), tr("Empty Trash"), menu); - connect(action, &QAction::triggered, this, &PlacesView::onEmptyTrash); - menu->addAction(action); + switch(item->type()) { + case PlacesModelItem::Places: { + auto path = item->path(); + auto path_str = path.toString(); + // FIXME: inefficient + if(path && strcmp(path_str.get(), "trash:///") == 0) { + action = new PlacesModel::ItemAction(item->index(), tr("Empty Trash"), menu); + connect(action, &QAction::triggered, this, &PlacesView::onEmptyTrash); + menu->addAction(action); + } + break; } - break; - } - case PlacesModelItem::Bookmark: { - // create context menu for bookmark item - if(item->index().row() > 0) { - action = new PlacesModel::ItemAction(item->index(), tr("Move Bookmark Up"), menu); - connect(action, &QAction::triggered, this, &PlacesView::onMoveBookmarkUp); - menu->addAction(action); + case PlacesModelItem::Bookmark: { + // create context menu for bookmark item + if(item->index().row() > 0) { + action = new PlacesModel::ItemAction(item->index(), tr("Move Bookmark Up"), menu); + connect(action, &QAction::triggered, this, &PlacesView::onMoveBookmarkUp); + menu->addAction(action); + } + if(item->index().row() < model_->rowCount()) { + action = new PlacesModel::ItemAction(item->index(), tr("Move Bookmark Down"), menu); + connect(action, &QAction::triggered, this, &PlacesView::onMoveBookmarkDown); + menu->addAction(action); + } + action = new PlacesModel::ItemAction(item->index(), tr("Rename Bookmark"), menu); + connect(action, &QAction::triggered, this, &PlacesView::onRenameBookmark); + menu->addAction(action); + action = new PlacesModel::ItemAction(item->index(), tr("Remove Bookmark"), menu); + connect(action, &QAction::triggered, this, &PlacesView::onDeleteBookmark); + menu->addAction(action); + break; } - if(item->index().row() < model_->rowCount()) { - action = new PlacesModel::ItemAction(item->index(), tr("Move Bookmark Down"), menu); - connect(action, &QAction::triggered, this, &PlacesView::onMoveBookmarkDown); - menu->addAction(action); + case PlacesModelItem::Volume: { + PlacesModelVolumeItem* volumeItem = static_cast(item); + + if(volumeItem->isMounted()) { + action = new PlacesModel::ItemAction(item->index(), tr("Unmount"), menu); + connect(action, &QAction::triggered, this, &PlacesView::onUnmountVolume); + } + else { + action = new PlacesModel::ItemAction(item->index(), tr("Mount"), menu); + connect(action, &QAction::triggered, this, &PlacesView::onMountVolume); + } + menu->addAction(action); + + if(volumeItem->canEject()) { + action = new PlacesModel::ItemAction(item->index(), tr("Eject"), menu); + connect(action, &QAction::triggered, this, &PlacesView::onEjectVolume); + menu->addAction(action); + } + break; } - action = new PlacesModel::ItemAction(item->index(), tr("Rename Bookmark"), menu); - connect(action, &QAction::triggered, this, &PlacesView::onRenameBookmark); - menu->addAction(action); - action = new PlacesModel::ItemAction(item->index(), tr("Remove Bookmark"), menu); - connect(action, &QAction::triggered, this, &PlacesView::onDeleteBookmark); - menu->addAction(action); - break; - } - case PlacesModelItem::Volume: { - PlacesModelVolumeItem* volumeItem = static_cast(item); - - if(volumeItem->isMounted()) { - action = new PlacesModel::ItemAction(item->index(), tr("Unmount"), menu); - connect(action, &QAction::triggered, this, &PlacesView::onUnmountVolume); + case PlacesModelItem::Mount: { + action = new PlacesModel::ItemAction(item->index(), tr("Unmount"), menu); + connect(action, &QAction::triggered, this, &PlacesView::onUnmountMount); + menu->addAction(action); + break; } - else { - action = new PlacesModel::ItemAction(item->index(), tr("Mount"), menu); - connect(action, &QAction::triggered, this, &PlacesView::onMountVolume); } - menu->addAction(action); - - if(volumeItem->canEject()) { - action = new PlacesModel::ItemAction(item->index(), tr("Eject"), menu); - connect(action, &QAction::triggered, this, &PlacesView::onEjectVolume); - menu->addAction(action); + if(menu->actions().size()) { + menu->popup(mapToGlobal(event->pos())); + connect(menu, &QMenu::aboutToHide, menu, &QMenu::deleteLater); + } + else { + menu->deleteLater(); } - break; - } - case PlacesModelItem::Mount: { - action = new PlacesModel::ItemAction(item->index(), tr("Unmount"), menu); - connect(action, &QAction::triggered, this, &PlacesView::onUnmountMount); - menu->addAction(action); - break; - } - } - if(menu->actions().size()) { - menu->popup(mapToGlobal(event->pos())); - connect(menu, &QMenu::aboutToHide, menu, &QMenu::deleteLater); - } else { - menu->deleteLater(); } - } } diff --git a/src/placesview.h b/src/placesview.h index 902ac3d..ccb7fa9 100644 --- a/src/placesview.h +++ b/src/placesview.h @@ -25,83 +25,82 @@ #include #include +#include +#include "core/filepath.h" + namespace Fm { class PlacesModel; class PlacesModelItem; class LIBFM_QT_API PlacesView : public QTreeView { -Q_OBJECT + Q_OBJECT public: - explicit PlacesView(QWidget* parent = 0); - virtual ~PlacesView(); + explicit PlacesView(QWidget* parent = 0); + virtual ~PlacesView(); - void setCurrentPath(FmPath* path); - FmPath* currentPath() { - return currentPath_; - } + void setCurrentPath(Fm::FilePath path); - // libfm-gtk compatible alias - FmPath* getCwd() { - return currentPath(); - } + const Fm::FilePath& currentPath() const { + return currentPath_; + } - void chdir(FmPath* path) { - setCurrentPath(path); - } + void chdir(Fm::FilePath path) { + setCurrentPath(std::move(path)); + } #if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) - void setIconSize(const QSize &size) { - // The signal QAbstractItemView::iconSizeChanged is only available after Qt 5.5. - // To simulate the effect for older Qt versions, we override setIconSize(). - QAbstractItemView::setIconSize(size); - onIconSizeChanged(size); - } + void setIconSize(const QSize& size) { + // The signal QAbstractItemView::iconSizeChanged is only available after Qt 5.5. + // To simulate the effect for older Qt versions, we override setIconSize(). + QAbstractItemView::setIconSize(size); + onIconSizeChanged(size); + } #endif Q_SIGNALS: - void chdirRequested(int type, FmPath* path); + void chdirRequested(int type, const Fm::FilePath& path); protected Q_SLOTS: - void onClicked(const QModelIndex & index); - void onPressed(const QModelIndex & index); - void onIconSizeChanged(const QSize & size); - // void onMountOperationFinished(GError* error); + void onClicked(const QModelIndex& index); + void onPressed(const QModelIndex& index); + void onIconSizeChanged(const QSize& size); + // void onMountOperationFinished(GError* error); - void onOpenNewTab(); - void onOpenNewWindow(); + void onOpenNewTab(); + void onOpenNewWindow(); - void onEmptyTrash(); + void onEmptyTrash(); - void onMountVolume(); - void onUnmountVolume(); - void onEjectVolume(); - void onUnmountMount(); + void onMountVolume(); + void onUnmountVolume(); + void onEjectVolume(); + void onUnmountMount(); - void onMoveBookmarkUp(); - void onMoveBookmarkDown(); - void onDeleteBookmark(); - void onRenameBookmark(); + void onMoveBookmarkUp(); + void onMoveBookmarkDown(); + void onDeleteBookmark(); + void onRenameBookmark(); protected: - void drawBranches ( QPainter * painter, const QRect & rect, const QModelIndex & index ) const { - // override this method to inhibit drawing of the branch grid lines by Qt. - } + void drawBranches(QPainter* /*painter*/, const QRect& /*rect*/, const QModelIndex& /*index*/) const { + // override this method to inhibit drawing of the branch grid lines by Qt. + } - virtual void dragMoveEvent(QDragMoveEvent* event); - virtual void dropEvent(QDropEvent* event); - virtual void contextMenuEvent(QContextMenuEvent* event); + virtual void dragMoveEvent(QDragMoveEvent* event); + virtual void dropEvent(QDropEvent* event); + virtual void contextMenuEvent(QContextMenuEvent* event); - virtual void commitData(QWidget * editor); + virtual void commitData(QWidget* editor); private: - void onEjectButtonClicked(PlacesModelItem* item); - void activateRow(int type, const QModelIndex& index); + void onEjectButtonClicked(PlacesModelItem* item); + void activateRow(int type, const QModelIndex& index); private: - PlacesModel* model_; - FmPath* currentPath_; + std::shared_ptr model_; + Fm::FilePath currentPath_; }; } diff --git a/src/proxyfoldermodel.cpp b/src/proxyfoldermodel.cpp index 54d1a19..106e846 100644 --- a/src/proxyfoldermodel.cpp +++ b/src/proxyfoldermodel.cpp @@ -24,256 +24,278 @@ namespace Fm { -ProxyFolderModel::ProxyFolderModel(QObject * parent): - QSortFilterProxyModel(parent), - showHidden_(false), - folderFirst_(true), - showThumbnails_(false), - thumbnailSize_(0) { - setDynamicSortFilter(true); - setSortCaseSensitivity(Qt::CaseInsensitive); +ProxyFolderModel::ProxyFolderModel(QObject* parent): + QSortFilterProxyModel(parent), + showHidden_(false), + folderFirst_(true), + showThumbnails_(false), + thumbnailSize_(0) { + + setDynamicSortFilter(true); + setSortCaseSensitivity(Qt::CaseInsensitive); + + collator_.setNumericMode(true); } ProxyFolderModel::~ProxyFolderModel() { - qDebug("delete ProxyFolderModel"); + qDebug("delete ProxyFolderModel"); - if(showThumbnails_ && thumbnailSize_ != 0) { - FolderModel* srcModel = static_cast(sourceModel()); - // tell the source model that we don't need the thumnails anymore - if(srcModel) { - srcModel->releaseThumbnails(thumbnailSize_); - disconnect(srcModel, SIGNAL(thumbnailLoaded(QModelIndex,int))); + if(showThumbnails_ && thumbnailSize_ != 0) { + FolderModel* srcModel = static_cast(sourceModel()); + // tell the source model that we don't need the thumnails anymore + if(srcModel) { + srcModel->releaseThumbnails(thumbnailSize_); + disconnect(srcModel, SIGNAL(thumbnailLoaded(QModelIndex, int))); + } } - } } void ProxyFolderModel::setSourceModel(QAbstractItemModel* model) { - if(model) { - // we only support Fm::FolderModel - Q_ASSERT(model->inherits("Fm::FolderModel")); - - if(showThumbnails_ && thumbnailSize_ != 0) { // if we're showing thumbnails - FolderModel* oldSrcModel = static_cast(sourceModel()); - FolderModel* newSrcModel = static_cast(model); - if(oldSrcModel) { // we need to release cached thumbnails for the old source model - oldSrcModel->releaseThumbnails(thumbnailSize_); - disconnect(oldSrcModel, SIGNAL(thumbnailLoaded(QModelIndex,int))); - } - if(newSrcModel) { // tell the new source model that we want thumbnails of this size - newSrcModel->cacheThumbnails(thumbnailSize_); - connect(newSrcModel, &FolderModel::thumbnailLoaded, this, &ProxyFolderModel::onThumbnailLoaded); - } + if(model == sourceModel()) // avoid setting the same model twice + return; + if(model) { + // we only support Fm::FolderModel + Q_ASSERT(model->inherits("Fm::FolderModel")); + + if(showThumbnails_ && thumbnailSize_ != 0) { // if we're showing thumbnails + FolderModel* oldSrcModel = static_cast(sourceModel()); + FolderModel* newSrcModel = static_cast(model); + if(oldSrcModel) { // we need to release cached thumbnails for the old source model + oldSrcModel->releaseThumbnails(thumbnailSize_); + disconnect(oldSrcModel, SIGNAL(thumbnailLoaded(QModelIndex, int))); + } + if(newSrcModel) { // tell the new source model that we want thumbnails of this size + newSrcModel->cacheThumbnails(thumbnailSize_); + connect(newSrcModel, &FolderModel::thumbnailLoaded, this, &ProxyFolderModel::onThumbnailLoaded); + } + } } - } - QSortFilterProxyModel::setSourceModel(model); + QSortFilterProxyModel::setSourceModel(model); } void ProxyFolderModel::sort(int column, Qt::SortOrder order) { - int oldColumn = sortColumn(); - Qt::SortOrder oldOrder = sortOrder(); - QSortFilterProxyModel::sort(column, order); - if(column != oldColumn || order != oldOrder) { - Q_EMIT sortFilterChanged(); - } + int oldColumn = sortColumn(); + Qt::SortOrder oldOrder = sortOrder(); + QSortFilterProxyModel::sort(column, order); + if(column != oldColumn || order != oldOrder) { + Q_EMIT sortFilterChanged(); + } } void ProxyFolderModel::setShowHidden(bool show) { - if(show != showHidden_) { - showHidden_ = show; - invalidateFilter(); - Q_EMIT sortFilterChanged(); - } + if(show != showHidden_) { + showHidden_ = show; + invalidateFilter(); + Q_EMIT sortFilterChanged(); + } } // need to call invalidateFilter() manually. void ProxyFolderModel::setFolderFirst(bool folderFirst) { - if(folderFirst != folderFirst_) { - folderFirst_ = folderFirst; + if(folderFirst != folderFirst_) { + folderFirst_ = folderFirst; + invalidate(); + Q_EMIT sortFilterChanged(); + } +} + +void ProxyFolderModel::setSortCaseSensitivity(Qt::CaseSensitivity cs) { + collator_.setCaseSensitivity(cs); + QSortFilterProxyModel::setSortCaseSensitivity(cs); invalidate(); Q_EMIT sortFilterChanged(); - } } -bool ProxyFolderModel::filterAcceptsRow(int source_row, const QModelIndex & source_parent) const { - if(!showHidden_) { - QAbstractItemModel* srcModel = sourceModel(); - QString name = srcModel->data(srcModel->index(source_row, 0, source_parent)).toString(); - if(name.startsWith(".") || name.endsWith("~")) - return false; - } - // apply additional filters if there're any - Q_FOREACH(ProxyFolderModelFilter* filter, filters_) { - FolderModel* srcModel = static_cast(sourceModel()); - FmFileInfo* fileInfo = srcModel->fileInfoFromIndex(srcModel->index(source_row, 0, source_parent)); - if(!filter->filterAcceptsRow(this, fileInfo)) - return false; - } - return true; +bool ProxyFolderModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { + if(!showHidden_) { + QAbstractItemModel* srcModel = sourceModel(); + QString name = srcModel->data(srcModel->index(source_row, 0, source_parent)).toString(); + if(name.startsWith(".") || name.endsWith("~")) { + return false; + } + } + // apply additional filters if there're any + Q_FOREACH(ProxyFolderModelFilter* filter, filters_) { + FolderModel* srcModel = static_cast(sourceModel()); + auto fileInfo = srcModel->fileInfoFromIndex(srcModel->index(source_row, 0, source_parent)); + if(!filter->filterAcceptsRow(this, fileInfo)) { + return false; + } + } + return true; } bool ProxyFolderModel::lessThan(const QModelIndex& left, const QModelIndex& right) const { - FolderModel* srcModel = static_cast(sourceModel()); - // left and right are indexes of source model, not the proxy model. - if(srcModel) { - FmFileInfo* leftInfo = srcModel->fileInfoFromIndex(left); - FmFileInfo* rightInfo = srcModel->fileInfoFromIndex(right); - - if(Q_UNLIKELY(!leftInfo || !rightInfo)) { - // In theory, this should not happen, but it's safer to add the null check. - // This is reported in https://github.com/lxde/pcmanfm-qt/issues/205 - return false; - } + FolderModel* srcModel = static_cast(sourceModel()); + // left and right are indexes of source model, not the proxy model. + if(srcModel) { + auto leftInfo = srcModel->fileInfoFromIndex(left); + auto rightInfo = srcModel->fileInfoFromIndex(right); + + if(folderFirst_) { + bool leftIsFolder = leftInfo->isDir(); + bool rightIsFolder = rightInfo->isDir(); + if(leftIsFolder != rightIsFolder) { + return sortOrder() == Qt::AscendingOrder ? leftIsFolder : rightIsFolder; + } + } - if(folderFirst_) { - bool leftIsFolder = (bool)fm_file_info_is_dir(leftInfo); - bool rightIsFolder = (bool)fm_file_info_is_dir(rightInfo); - if(leftIsFolder != rightIsFolder) - return sortOrder() == Qt::AscendingOrder ? leftIsFolder : rightIsFolder; + switch(sortColumn()) { + case FolderModel::ColumnFileMTime: + return leftInfo->mtime() < rightInfo->mtime(); + case FolderModel::ColumnFileSize: + return leftInfo->size() < rightInfo->size(); + default: { + QString leftText = left.data(Qt::DisplayRole).toString(); + QString rightText = right.data(Qt::DisplayRole).toString(); + return collator_.compare(leftText, rightText) < 0; + } + } } + return QSortFilterProxyModel::lessThan(left, right); +} - switch(sortColumn()) { - case FolderModel::ColumnFileName: - if(sortCaseSensitivity() == Qt::CaseSensitive) { - // fm_file_info_get_collate_key_nocasefold() uses g_utf8_casefold() from glib internally, which - // is only an approximation not working correctly in some locales. - // FIXME: we may use QCollator (since Qt 5.2) for this, but the performance impact is unknown - return strcmp(fm_file_info_get_collate_key_nocasefold(leftInfo), fm_file_info_get_collate_key_nocasefold(rightInfo)) < 0; - /* - QCollator coll; - coll.setCaseSensitivity(Qt::CaseSensitive); - coll.setIgnorePunctuation(false); - coll.setNumericMode(true); - return coll.compare(QString::fromUtf8(fm_file_info_get_disp_name(leftInfo)), QString::fromUtf8(fm_file_info_get_disp_name(rightInfo))) < 0; - */ +std::shared_ptr ProxyFolderModel::fileInfoFromIndex(const QModelIndex& index) const { + if(index.isValid()) { + FolderModel* srcModel = static_cast(sourceModel()); + if(srcModel) { + QModelIndex srcIndex = mapToSource(index); + return srcModel->fileInfoFromIndex(srcIndex); } - else { - // linguistic case insensitive ordering - return strcmp(fm_file_info_get_collate_key(leftInfo), fm_file_info_get_collate_key(rightInfo)) < 0; + } + return nullptr; +} + +QModelIndex ProxyFolderModel::indexFromPath(const FilePath &path) const { + QModelIndex ret; + int n_rows = rowCount(); + for(int row = 0; row < n_rows; ++row) { + auto idx = index(row, FolderModel::ColumnFileName, QModelIndex()); + auto fi = fileInfoFromIndex(idx); + if(fi && fi->path() == path) { // found the item + ret = idx; + break; } - case FolderModel::ColumnFileMTime: - return fm_file_info_get_mtime(leftInfo) < fm_file_info_get_mtime(rightInfo); - case FolderModel::ColumnFileSize: - return fm_file_info_get_size(leftInfo) < fm_file_info_get_size(rightInfo); - case FolderModel::ColumnFileOwner: - // TODO: sort by owner - break; - case FolderModel::ColumnFileType: - break; } - } - return QSortFilterProxyModel::lessThan(left, right); + return ret; } -FmFileInfo* ProxyFolderModel::fileInfoFromIndex(const QModelIndex& index) const { - FolderModel* srcModel = static_cast(sourceModel()); - if(srcModel) { - QModelIndex srcIndex = mapToSource(index); - return srcModel->fileInfoFromIndex(srcIndex); - } - return NULL; +std::shared_ptr ProxyFolderModel::fileInfoFromPath(const FilePath &path) const { + return fileInfoFromIndex(indexFromPath(path)); } -void ProxyFolderModel::setShowThumbnails(bool show) { - if(show != showThumbnails_) { - showThumbnails_ = show; +void ProxyFolderModel::setCutFiles(const QItemSelection& selection) { FolderModel* srcModel = static_cast(sourceModel()); - if(srcModel && thumbnailSize_ != 0) { - if(show) { - // ask for cache of thumbnails of the new size in source model - srcModel->cacheThumbnails(thumbnailSize_); - // connect to the srcModel so we can be notified when a thumbnail is loaded. - connect(srcModel, &FolderModel::thumbnailLoaded, this, &ProxyFolderModel::onThumbnailLoaded); - } - else { // turn off thumbnails - // free cached old thumbnails in souce model - srcModel->releaseThumbnails(thumbnailSize_); - disconnect(srcModel, SIGNAL(thumbnailLoaded(QModelIndex,int))); - } - // reload all items, FIXME: can we only update items previously having thumbnails - Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0)); + if(srcModel) { + srcModel->setCutFiles(mapSelectionToSource(selection)); } - } } -void ProxyFolderModel::setThumbnailSize(int size) { - if(size != thumbnailSize_) { - FolderModel* srcModel = static_cast(sourceModel()); - if(showThumbnails_ && srcModel) { - // free cached thumbnails of the old size - if(thumbnailSize_ != 0) - srcModel->releaseThumbnails(thumbnailSize_); - else { - // if the old thumbnail size is 0, we did not turn on thumbnail initially - connect(srcModel, &FolderModel::thumbnailLoaded, this, &ProxyFolderModel::onThumbnailLoaded); - } - // ask for cache of thumbnails of the new size in source model - srcModel->cacheThumbnails(size); - // reload all items, FIXME: can we only update items previously having thumbnails - Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0)); +void ProxyFolderModel::setShowThumbnails(bool show) { + if(show != showThumbnails_) { + showThumbnails_ = show; + FolderModel* srcModel = static_cast(sourceModel()); + if(srcModel && thumbnailSize_ != 0) { + if(show) { + // ask for cache of thumbnails of the new size in source model + srcModel->cacheThumbnails(thumbnailSize_); + // connect to the srcModel so we can be notified when a thumbnail is loaded. + connect(srcModel, &FolderModel::thumbnailLoaded, this, &ProxyFolderModel::onThumbnailLoaded); + } + else { // turn off thumbnails + // free cached old thumbnails in souce model + srcModel->releaseThumbnails(thumbnailSize_); + disconnect(srcModel, SIGNAL(thumbnailLoaded(QModelIndex, int))); + } + // reload all items, FIXME: can we only update items previously having thumbnails + Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0)); + } } +} - thumbnailSize_ = size; - } +void ProxyFolderModel::setThumbnailSize(int size) { + if(size != thumbnailSize_) { + FolderModel* srcModel = static_cast(sourceModel()); + if(showThumbnails_ && srcModel) { + // free cached thumbnails of the old size + if(thumbnailSize_ != 0) { + srcModel->releaseThumbnails(thumbnailSize_); + } + else { + // if the old thumbnail size is 0, we did not turn on thumbnail initially + connect(srcModel, &FolderModel::thumbnailLoaded, this, &ProxyFolderModel::onThumbnailLoaded); + } + // ask for cache of thumbnails of the new size in source model + srcModel->cacheThumbnails(size); + // reload all items, FIXME: can we only update items previously having thumbnails + Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0)); + } + + thumbnailSize_ = size; + } } QVariant ProxyFolderModel::data(const QModelIndex& index, int role) const { - if(index.column() == 0) { // only show the decoration role for the first column - if(role == Qt::DecorationRole && showThumbnails_ && thumbnailSize_) { - // we need to show thumbnails instead of icons - FolderModel* srcModel = static_cast(sourceModel()); - QModelIndex srcIndex = mapToSource(index); - QImage image = srcModel->thumbnailFromIndex(srcIndex, thumbnailSize_); - if(!image.isNull()) // if we got a thumbnail of the desired size, use it - return QVariant(image); + if(index.column() == 0) { // only show the decoration role for the first column + if(role == Qt::DecorationRole && showThumbnails_ && thumbnailSize_) { + // we need to show thumbnails instead of icons + FolderModel* srcModel = static_cast(sourceModel()); + QModelIndex srcIndex = mapToSource(index); + QImage image = srcModel->thumbnailFromIndex(srcIndex, thumbnailSize_); + if(!image.isNull()) { // if we got a thumbnail of the desired size, use it + return QVariant(image); + } + } } - } - // fallback to icons if thumbnails are not available - return QSortFilterProxyModel::data(index, role); + // fallback to icons if thumbnails are not available + return QSortFilterProxyModel::data(index, role); } void ProxyFolderModel::onThumbnailLoaded(const QModelIndex& srcIndex, int size) { - // FolderModel* srcModel = static_cast(sourceModel()); - // FolderModelItem* item = srcModel->itemFromIndex(srcIndex); - // qDebug("ProxyFolderModel::onThumbnailLoaded: %d, %s", size, item->displayName.toUtf8().constData()); - - if(size == thumbnailSize_) { // if a thumbnail of the size we want is loaded - QModelIndex index = mapFromSource(srcIndex); - Q_EMIT dataChanged(index, index); - } + // FolderModel* srcModel = static_cast(sourceModel()); + // FolderModelItem* item = srcModel->itemFromIndex(srcIndex); + // qDebug("ProxyFolderModel::onThumbnailLoaded: %d, %s", size, item->displayName.toUtf8().data()); + + if(size == thumbnailSize_ // if a thumbnail of the size we want is loaded + && srcIndex.model() == sourceModel()) { // check if the sourse model contains the index item + QModelIndex index = mapFromSource(srcIndex); + Q_EMIT dataChanged(index, index); + } } void ProxyFolderModel::addFilter(ProxyFolderModelFilter* filter) { - filters_.append(filter); - invalidateFilter(); - Q_EMIT sortFilterChanged(); + filters_.append(filter); + invalidateFilter(); + Q_EMIT sortFilterChanged(); } void ProxyFolderModel::removeFilter(ProxyFolderModelFilter* filter) { - filters_.removeOne(filter); - invalidateFilter(); - Q_EMIT sortFilterChanged(); + filters_.removeOne(filter); + invalidateFilter(); + Q_EMIT sortFilterChanged(); } void ProxyFolderModel::updateFilters() { - invalidate(); - Q_EMIT sortFilterChanged(); + invalidate(); + Q_EMIT sortFilterChanged(); } #if 0 void ProxyFolderModel::reloadAllThumbnails() { - // reload all thumbnails and update UI - FolderModel* srcModel = static_cast(sourceModel()); - if(srcModel) { - int rows= rowCount(); - for(int row = 0; row < rows; ++row) { - QModelIndex index = this->index(row, 0); - QModelIndex srcIndex = mapToSource(index); - QImage image = srcModel->thumbnailFromIndex(srcIndex, size); - // tell the world that the item is changed to trigger a UI update - if(!image.isNull()) - Q_EMIT dataChanged(index, index); + // reload all thumbnails and update UI + FolderModel* srcModel = static_cast(sourceModel()); + if(srcModel) { + int rows = rowCount(); + for(int row = 0; row < rows; ++row) { + QModelIndex index = this->index(row, 0); + QModelIndex srcIndex = mapToSource(index); + QImage image = srcModel->thumbnailFromIndex(srcIndex, size); + // tell the world that the item is changed to trigger a UI update + if(!image.isNull()) { + Q_EMIT dataChanged(index, index); + } + } } - } } #endif diff --git a/src/proxyfoldermodel.h b/src/proxyfoldermodel.h index ed4dec3..9e29239 100644 --- a/src/proxyfoldermodel.h +++ b/src/proxyfoldermodel.h @@ -25,6 +25,9 @@ #include #include #include +#include + +#include "core/fileinfo.h" namespace Fm { @@ -35,73 +38,75 @@ class ProxyFolderModel; class LIBFM_QT_API ProxyFolderModelFilter { public: - virtual bool filterAcceptsRow(const ProxyFolderModel* model, FmFileInfo* info) const = 0; - virtual ~ProxyFolderModelFilter() {} + virtual bool filterAcceptsRow(const ProxyFolderModel* model, const std::shared_ptr& info) const = 0; + virtual ~ProxyFolderModelFilter() {} }; class LIBFM_QT_API ProxyFolderModel : public QSortFilterProxyModel { - Q_OBJECT + Q_OBJECT public: - explicit ProxyFolderModel(QObject * parent = 0); - virtual ~ProxyFolderModel(); + explicit ProxyFolderModel(QObject* parent = 0); + virtual ~ProxyFolderModel(); + + // only Fm::FolderModel is allowed for being sourceModel + virtual void setSourceModel(QAbstractItemModel* model); + + void setShowHidden(bool show); + bool showHidden() const { + return showHidden_; + } - // only Fm::FolderModel is allowed for being sourceModel - virtual void setSourceModel(QAbstractItemModel* model); + void setFolderFirst(bool folderFirst); + bool folderFirst() { + return folderFirst_; + } - void setShowHidden(bool show); - bool showHidden() const { - return showHidden_; - } + void setSortCaseSensitivity(Qt::CaseSensitivity cs); - void setFolderFirst(bool folderFirst); - bool folderFirst() { - return folderFirst_; - } + void setCutFiles(const QItemSelection& selection); - void setSortCaseSensitivity(Qt::CaseSensitivity cs) { - QSortFilterProxyModel::setSortCaseSensitivity(cs); - Q_EMIT sortFilterChanged(); - } + bool showThumbnails() { + return showThumbnails_; + } + void setShowThumbnails(bool show); - bool showThumbnails() { - return showThumbnails_; - } - void setShowThumbnails(bool show); + int thumbnailSize() { + return thumbnailSize_; + } + void setThumbnailSize(int size); - int thumbnailSize() { - return thumbnailSize_; - } - void setThumbnailSize(int size); + std::shared_ptr fileInfoFromIndex(const QModelIndex& index) const; - FmFileInfo* fileInfoFromIndex(const QModelIndex& index) const; + std::shared_ptr fileInfoFromPath(const FilePath& path) const; - virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); - virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const; + QModelIndex indexFromPath(const FilePath& path) const; - void addFilter(ProxyFolderModelFilter* filter); - void removeFilter(ProxyFolderModelFilter* filter); - void updateFilters(); + virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + + void addFilter(ProxyFolderModelFilter* filter); + void removeFilter(ProxyFolderModelFilter* filter); + void updateFilters(); Q_SIGNALS: - void sortFilterChanged(); + void sortFilterChanged(); protected Q_SLOTS: - void onThumbnailLoaded(const QModelIndex& srcIndex, int size); + void onThumbnailLoaded(const QModelIndex& srcIndex, int size); protected: - bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const; - bool lessThan(const QModelIndex & left, const QModelIndex & right) const; - // void reloadAllThumbnails(); - -private: + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; + bool lessThan(const QModelIndex& left, const QModelIndex& right) const; + // void reloadAllThumbnails(); private: - bool showHidden_; - bool folderFirst_; - bool showThumbnails_; - int thumbnailSize_; - QList filters_; + QCollator collator_; + bool showHidden_; + bool folderFirst_; + bool showThumbnails_; + int thumbnailSize_; + QList filters_; }; } diff --git a/src/renamedialog.cpp b/src/renamedialog.cpp index 054752b..4dc17b8 100644 --- a/src/renamedialog.cpp +++ b/src/renamedialog.cpp @@ -22,118 +22,118 @@ #include "ui_rename-dialog.h" #include #include -#include "icontheme.h" +#include "core/iconinfo.h" namespace Fm { RenameDialog::RenameDialog(FmFileInfo* src, FmFileInfo* dest, QWidget* parent, Qt::WindowFlags f): - QDialog(parent, f), - action_(ActionIgnore), - applyToAll_(false) { - - ui = new Ui::RenameDialog(); - ui->setupUi(this); - - FmPath* path = fm_file_info_get_path(dest); - FmIcon* srcIcon = fm_file_info_get_icon(src); - FmIcon* destIcon = fm_file_info_get_icon(dest); - - // show info for the source file - QIcon icon = IconTheme::icon(srcIcon); - QSize iconSize(fm_config->big_icon_size, fm_config->big_icon_size); - QPixmap pixmap = icon.pixmap(iconSize); - ui->srcIcon->setPixmap(pixmap); - - QString infoStr; - const char* disp_size = fm_file_info_get_disp_size(src); - if(disp_size) { - infoStr = QString(tr("Type: %1\nSize: %2\nModified: %3")) - .arg(QString::fromUtf8(fm_file_info_get_desc(src))) - .arg(QString::fromUtf8(disp_size)) - .arg(QString::fromUtf8(fm_file_info_get_disp_mtime(src))); - } - else { - infoStr = QString(tr("Type: %1\nModified: %2")) - .arg(QString::fromUtf8(fm_file_info_get_desc(src))) - .arg(QString::fromUtf8(fm_file_info_get_disp_mtime(src))); - } - ui->srcInfo->setText(infoStr); - - // show info for the dest file - icon = IconTheme::icon(destIcon); - pixmap = icon.pixmap(iconSize); - ui->destIcon->setPixmap(pixmap); - - disp_size = fm_file_info_get_disp_size(dest); - if(disp_size) { - infoStr = QString(tr("Type: %1\nSize: %2\nModified: %3")) - .arg(QString::fromUtf8(fm_file_info_get_desc(dest))) - .arg(QString::fromUtf8(disp_size)) - .arg(QString::fromUtf8(fm_file_info_get_disp_mtime(dest))); - } - else { - infoStr = QString(tr("Type: %1\nModified: %2")) - .arg(QString::fromUtf8(fm_file_info_get_desc(dest))) - .arg(QString::fromUtf8(fm_file_info_get_disp_mtime(dest))); - } - ui->destInfo->setText(infoStr); - - char* basename = fm_path_display_basename(path); - ui->fileName->setText(QString::fromUtf8(basename)); - oldName_ = basename; - g_free(basename); - connect(ui->fileName, &QLineEdit::textChanged, this, &RenameDialog::onFileNameChanged); - - // add "Rename" button - QAbstractButton* button = ui->buttonBox->button(QDialogButtonBox::Ok); - button->setText(tr("&Overwrite")); - // FIXME: there seems to be no way to place the Rename button next to the overwrite one. - renameButton_ = ui->buttonBox->addButton(tr("&Rename"), QDialogButtonBox::ActionRole); - connect(renameButton_, &QPushButton::clicked, this, &RenameDialog::onRenameClicked); - renameButton_->setEnabled(false); // disabled by default - - button = ui->buttonBox->button(QDialogButtonBox::Ignore); - connect(button, &QPushButton::clicked, this, &RenameDialog::onIgnoreClicked); + QDialog(parent, f), + action_(ActionIgnore), + applyToAll_(false) { + + ui = new Ui::RenameDialog(); + ui->setupUi(this); + + FmPath* path = fm_file_info_get_path(dest); + FmIcon* srcIcon = fm_file_info_get_icon(src); + FmIcon* destIcon = fm_file_info_get_icon(dest); + + // show info for the source file + QIcon icon = Fm::IconInfo::fromGIcon(G_ICON(srcIcon))->qicon(); + QSize iconSize(fm_config->big_icon_size, fm_config->big_icon_size); + QPixmap pixmap = icon.pixmap(iconSize); + ui->srcIcon->setPixmap(pixmap); + + QString infoStr; + const char* disp_size = fm_file_info_get_disp_size(src); + if(disp_size) { + infoStr = QString(tr("Type: %1\nSize: %2\nModified: %3")) + .arg(QString::fromUtf8(fm_file_info_get_desc(src))) + .arg(QString::fromUtf8(disp_size)) + .arg(QString::fromUtf8(fm_file_info_get_disp_mtime(src))); + } + else { + infoStr = QString(tr("Type: %1\nModified: %2")) + .arg(QString::fromUtf8(fm_file_info_get_desc(src))) + .arg(QString::fromUtf8(fm_file_info_get_disp_mtime(src))); + } + ui->srcInfo->setText(infoStr); + + // show info for the dest file + icon = Fm::IconInfo::fromGIcon(G_ICON(destIcon))->qicon(); + pixmap = icon.pixmap(iconSize); + ui->destIcon->setPixmap(pixmap); + + disp_size = fm_file_info_get_disp_size(dest); + if(disp_size) { + infoStr = QString(tr("Type: %1\nSize: %2\nModified: %3")) + .arg(QString::fromUtf8(fm_file_info_get_desc(dest))) + .arg(QString::fromUtf8(disp_size)) + .arg(QString::fromUtf8(fm_file_info_get_disp_mtime(dest))); + } + else { + infoStr = QString(tr("Type: %1\nModified: %2")) + .arg(QString::fromUtf8(fm_file_info_get_desc(dest))) + .arg(QString::fromUtf8(fm_file_info_get_disp_mtime(dest))); + } + ui->destInfo->setText(infoStr); + + char* basename = fm_path_display_basename(path); + ui->fileName->setText(QString::fromUtf8(basename)); + oldName_ = basename; + g_free(basename); + connect(ui->fileName, &QLineEdit::textChanged, this, &RenameDialog::onFileNameChanged); + + // add "Rename" button + QAbstractButton* button = ui->buttonBox->button(QDialogButtonBox::Ok); + button->setText(tr("&Overwrite")); + // FIXME: there seems to be no way to place the Rename button next to the overwrite one. + renameButton_ = ui->buttonBox->addButton(tr("&Rename"), QDialogButtonBox::ActionRole); + connect(renameButton_, &QPushButton::clicked, this, &RenameDialog::onRenameClicked); + renameButton_->setEnabled(false); // disabled by default + + button = ui->buttonBox->button(QDialogButtonBox::Ignore); + connect(button, &QPushButton::clicked, this, &RenameDialog::onIgnoreClicked); } RenameDialog::~RenameDialog() { - delete ui; + delete ui; } void RenameDialog::onRenameClicked() { - action_ = ActionRename; - QDialog::done(QDialog::Accepted); + action_ = ActionRename; + QDialog::done(QDialog::Accepted); } void RenameDialog::onIgnoreClicked() { - action_ = ActionIgnore; + action_ = ActionIgnore; } // the overwrite button void RenameDialog::accept() { - action_ = ActionOverwrite; - applyToAll_ = ui->applyToAll->isChecked(); - QDialog::accept(); + action_ = ActionOverwrite; + applyToAll_ = ui->applyToAll->isChecked(); + QDialog::accept(); } // cancel, or close the dialog void RenameDialog::reject() { - action_ = ActionCancel; - QDialog::reject(); + action_ = ActionCancel; + QDialog::reject(); } void RenameDialog::onFileNameChanged(QString newName) { - newName_ = newName; - // FIXME: check if the name already exists in the current dir - bool hasNewName = (newName_ != oldName_); - renameButton_->setEnabled(hasNewName); - renameButton_->setDefault(hasNewName); - - // change default button to rename rather than overwrire - // if the user typed a new filename - QPushButton* overwriteButton = static_cast(ui->buttonBox->button(QDialogButtonBox::Ok)); - overwriteButton->setEnabled(!hasNewName); - overwriteButton->setDefault(!hasNewName); + newName_ = newName; + // FIXME: check if the name already exists in the current dir + bool hasNewName = (newName_ != oldName_); + renameButton_->setEnabled(hasNewName); + renameButton_->setDefault(hasNewName); + + // change default button to rename rather than overwrire + // if the user typed a new filename + QPushButton* overwriteButton = static_cast(ui->buttonBox->button(QDialogButtonBox::Ok)); + overwriteButton->setEnabled(!hasNewName); + overwriteButton->setDefault(!hasNewName); } diff --git a/src/renamedialog.h b/src/renamedialog.h index f979584..994c21a 100644 --- a/src/renamedialog.h +++ b/src/renamedialog.h @@ -26,56 +26,56 @@ #include namespace Ui { - class RenameDialog; -}; +class RenameDialog; +} class QPushButton; namespace Fm { class LIBFM_QT_API RenameDialog : public QDialog { -Q_OBJECT + Q_OBJECT public: - enum Action { - ActionCancel, - ActionRename, - ActionOverwrite, - ActionIgnore - }; + enum Action { + ActionCancel, + ActionRename, + ActionOverwrite, + ActionIgnore + }; public: - explicit RenameDialog(FmFileInfo* src, FmFileInfo* dest, QWidget* parent = 0, Qt::WindowFlags f = 0); - virtual ~RenameDialog(); + explicit RenameDialog(FmFileInfo* src, FmFileInfo* dest, QWidget* parent = 0, Qt::WindowFlags f = 0); + virtual ~RenameDialog(); - Action action() { - return action_; - } + Action action() { + return action_; + } - bool applyToAll() { - return applyToAll_; - } + bool applyToAll() { + return applyToAll_; + } - QString newName() { - return newName_; - } + QString newName() { + return newName_; + } protected Q_SLOTS: - void onRenameClicked(); - void onIgnoreClicked(); - void onFileNameChanged(QString newName); + void onRenameClicked(); + void onIgnoreClicked(); + void onFileNameChanged(QString newName); protected: - void accept(); - void reject(); + void accept(); + void reject(); private: - Ui::RenameDialog* ui; - QPushButton* renameButton_; - Action action_; - bool applyToAll_; - QString oldName_; - QString newName_; + Ui::RenameDialog* ui; + QPushButton* renameButton_; + Action action_; + bool applyToAll_; + QString oldName_; + QString newName_; }; } diff --git a/src/sidepane.cpp b/src/sidepane.cpp index 959245d..717594f 100644 --- a/src/sidepane.cpp +++ b/src/sidepane.cpp @@ -31,217 +31,181 @@ namespace Fm { SidePane::SidePane(QWidget* parent): - QWidget(parent), - currentPath_(NULL), - view_(NULL), - combo_(NULL), - iconSize_(24, 24), - mode_(ModeNone), - showHidden_(false) { - - verticalLayout = new QVBoxLayout(this); - verticalLayout->setContentsMargins(0, 0, 0, 0); - - combo_ = new QComboBox(this); - combo_->setFrame(false); - combo_->addItem(tr("Places")); - combo_->addItem(tr("Directory Tree")); - connect(combo_, static_cast(&QComboBox::currentIndexChanged), this, &SidePane::onComboCurrentIndexChanged); - verticalLayout->addWidget(combo_); + QWidget(parent), + view_(nullptr), + combo_(nullptr), + iconSize_(24, 24), + mode_(ModeNone), + showHidden_(false) { + + verticalLayout = new QVBoxLayout(this); + verticalLayout->setContentsMargins(0, 0, 0, 0); + + combo_ = new QComboBox(this); + combo_->setFrame(false); + combo_->addItem(tr("Places")); + combo_->addItem(tr("Directory Tree")); + connect(combo_, static_cast(&QComboBox::currentIndexChanged), this, &SidePane::onComboCurrentIndexChanged); + verticalLayout->addWidget(combo_); } SidePane::~SidePane() { - if(currentPath_) - fm_path_unref(currentPath_); - // qDebug("delete SidePane"); -} - -void SidePane::onPlacesViewChdirRequested(int type, FmPath* path) { - Q_EMIT chdirRequested(type, path); -} - -void SidePane::onDirTreeViewChdirRequested(int type, FmPath* path) { - Q_EMIT chdirRequested(type, path); + // qDebug("delete SidePane"); } void SidePane::onComboCurrentIndexChanged(int current) { - if(current != mode_) { - setMode(Mode(current)); - } + if(current != mode_) { + setMode(Mode(current)); + } } void SidePane::setIconSize(QSize size) { - iconSize_ = size; - switch(mode_) { + iconSize_ = size; + switch(mode_) { case ModePlaces: - static_cast(view_)->setIconSize(size); + static_cast(view_)->setIconSize(size); case ModeDirTree: - static_cast(view_)->setIconSize(size); - break; - default:; - } + static_cast(view_)->setIconSize(size); + break; + default: + ; + } } -void SidePane::setCurrentPath(FmPath* path) { - Q_ASSERT(path != NULL); - if(currentPath_) - fm_path_unref(currentPath_); - currentPath_ = fm_path_ref(path); - switch(mode_) { +void SidePane::setCurrentPath(Fm::FilePath path) { + Q_ASSERT(path); + currentPath_ = std::move(path); + switch(mode_) { case ModePlaces: - static_cast(view_)->setCurrentPath(path); - break; + static_cast(view_)->setCurrentPath(currentPath_); + break; case ModeDirTree: - static_cast(view_)->setCurrentPath(path); - break; - default:; - } + static_cast(view_)->setCurrentPath(currentPath_); + break; + default: + ; + } } SidePane::Mode SidePane::modeByName(const char* str) { - if(str == NULL) + if(str == nullptr) { + return ModeNone; + } + if(strcmp(str, "places") == 0) { + return ModePlaces; + } + if(strcmp(str, "dirtree") == 0) { + return ModeDirTree; + } return ModeNone; - if(strcmp(str, "places") == 0) - return ModePlaces; - if(strcmp(str, "dirtree") == 0) - return ModeDirTree; - return ModeNone; } const char* SidePane::modeName(SidePane::Mode mode) { - switch(mode) { - case ModePlaces: - return "places"; - case ModeDirTree: - return "dirtree"; - default: - return NULL; - } -} - -#if 0 // FIXME: are these APIs from libfm-qt needed? - -QString SidePane::modeLabel(SidePane::Mode mode) { - switch(mode) { - case ModePlaces: - return tr("Places"); - case ModeDirTree: - return tr("Directory Tree"); - } - return QString(); -} - -QString SidePane::modeTooltip(SidePane::Mode mode) { - switch(mode) { - case ModePlaces: - return tr("Shows list of common places, devices, and bookmarks in sidebar"); - case ModeDirTree: - return tr("Shows tree of directories in sidebar"); - } - return QString(); + switch(mode) { + case ModePlaces: + return "places"; + case ModeDirTree: + return "dirtree"; + default: + return nullptr; + } } -#endif -bool SidePane::setHomeDir(const char* home_dir) { - if(view_ == NULL) - return false; - // TODO: SidePane::setHomeDir +bool SidePane::setHomeDir(const char* /*home_dir*/) { + if(view_ == nullptr) { + return false; + } + // TODO: SidePane::setHomeDir - switch(mode_) { - case ModePlaces: - // static_cast(view_); - return true; - case ModeDirTree: - // static_cast(view_); + switch(mode_) { + case ModePlaces: + // static_cast(view_); + return true; + case ModeDirTree: + // static_cast(view_); + return true; + default: + ; + } return true; - default:; - } - return true; } void SidePane::initDirTree() { - // TODO - DirTreeModel* model = new DirTreeModel(view_); - FmFileInfoJob* job = fm_file_info_job_new(NULL, FM_FILE_INFO_JOB_NONE); - model->setShowHidden(showHidden_); - - GList* l; - /* query FmFileInfo for home dir and root dir, and then, - * add them to dir tree model */ - fm_file_info_job_add(job, fm_path_get_home()); - fm_file_info_job_add(job, fm_path_get_root()); - /* FIXME: maybe it's cleaner to use run_async here? */ - fm_job_run_sync_with_mainloop(FM_JOB(job)); - for(l = fm_file_info_list_peek_head_link(job->file_infos); l; l = l->next) { - FmFileInfo* fi = FM_FILE_INFO(l->data); - model->addRoot(fi); - } - g_object_unref(job); - - static_cast(view_)->setModel(model); + DirTreeModel* model = new DirTreeModel(view_); + model->setShowHidden(showHidden_); + + Fm::FilePathList rootPaths; + rootPaths.emplace_back(Fm::FilePath::homeDir()); + rootPaths.emplace_back(Fm::FilePath::fromLocalPath("/")); + model->addRoots(std::move(rootPaths)); + static_cast(view_)->setModel(model); } void SidePane::setMode(Mode mode) { - if(mode == mode_) - return; - - if(view_) { - delete view_; - view_ = NULL; - //if(sp->update_popup) - // g_signal_handlers_disconnect_by_func(sp->view, on_item_popup, sp); - } - mode_ = mode; - - combo_->setCurrentIndex(mode); - switch(mode) { - case ModePlaces: { - PlacesView* placesView = new Fm::PlacesView(this); - view_ = placesView; - placesView->setIconSize(iconSize_); - placesView->setCurrentPath(currentPath_); - connect(placesView, &PlacesView::chdirRequested, this, &SidePane::onPlacesViewChdirRequested); - break; - } - case ModeDirTree: { - DirTreeView* dirTreeView = new Fm::DirTreeView(this); - view_ = dirTreeView; - initDirTree(); - dirTreeView->setIconSize(iconSize_); - dirTreeView->setCurrentPath(currentPath_); - connect(dirTreeView, &DirTreeView::chdirRequested, this, &SidePane::onDirTreeViewChdirRequested); - connect(dirTreeView, &DirTreeView::openFolderInNewWindowRequested, - this, &SidePane::openFolderInNewWindowRequested); - connect(dirTreeView, &DirTreeView::openFolderInNewTabRequested, - this, &SidePane::openFolderInNewTabRequested); - connect(dirTreeView, &DirTreeView::openFolderInTerminalRequested, - this, &SidePane::openFolderInTerminalRequested); - connect(dirTreeView, &DirTreeView::createNewFolderRequested, - this, &SidePane::createNewFolderRequested); - connect(dirTreeView, &DirTreeView::prepareFileMenu, - this, &SidePane::prepareFileMenu); - break; - } - default:; - } - if(view_) { - // if(sp->update_popup) - // g_signal_connect(sp->view, "item-popup", G_CALLBACK(on_item_popup), sp); - verticalLayout->addWidget(view_); - } - Q_EMIT modeChanged(mode); + if(mode == mode_) { + return; + } + + if(view_) { + delete view_; + view_ = nullptr; + //if(sp->update_popup) + // g_signal_handlers_disconnect_by_func(sp->view, on_item_popup, sp); + } + mode_ = mode; + + combo_->setCurrentIndex(mode); + switch(mode) { + case ModePlaces: { + PlacesView* placesView = new Fm::PlacesView(this); + view_ = placesView; + placesView->setIconSize(iconSize_); + placesView->setCurrentPath(currentPath_); + connect(placesView, &PlacesView::chdirRequested, this, &SidePane::chdirRequested); + break; + } + case ModeDirTree: { + DirTreeView* dirTreeView = new Fm::DirTreeView(this); + view_ = dirTreeView; + initDirTree(); + dirTreeView->setIconSize(iconSize_); + dirTreeView->setCurrentPath(currentPath_); + connect(dirTreeView, &DirTreeView::chdirRequested, this, &SidePane::chdirRequested); + connect(dirTreeView, &DirTreeView::openFolderInNewWindowRequested, + this, &SidePane::openFolderInNewWindowRequested); + connect(dirTreeView, &DirTreeView::openFolderInNewTabRequested, + this, &SidePane::openFolderInNewTabRequested); + connect(dirTreeView, &DirTreeView::openFolderInTerminalRequested, + this, &SidePane::openFolderInTerminalRequested); + connect(dirTreeView, &DirTreeView::createNewFolderRequested, + this, &SidePane::createNewFolderRequested); + connect(dirTreeView, &DirTreeView::prepareFileMenu, + this, &SidePane::prepareFileMenu); + break; + } + default: + ; + } + if(view_) { + // if(sp->update_popup) + // g_signal_connect(sp->view, "item-popup", G_CALLBACK(on_item_popup), sp); + verticalLayout->addWidget(view_); + } + Q_EMIT modeChanged(mode); } void SidePane::setShowHidden(bool show_hidden) { - if(view_ == NULL || show_hidden == showHidden_) - return; - showHidden_ = show_hidden; - if(mode_ == ModeDirTree) { - DirTreeView* dirTreeView = static_cast(view_); - DirTreeModel* model = static_cast( dirTreeView->model()); - if(model) - model->setShowHidden(showHidden_); - } + if(view_ == nullptr || show_hidden == showHidden_) { + return; + } + showHidden_ = show_hidden; + if(mode_ == ModeDirTree) { + DirTreeView* dirTreeView = static_cast(view_); + DirTreeModel* model = static_cast(dirTreeView->model()); + if(model) { + model->setShowHidden(showHidden_); + } + } } } // namespace Fm diff --git a/src/sidepane.h b/src/sidepane.h index 3657413..04215ca 100644 --- a/src/sidepane.h +++ b/src/sidepane.h @@ -25,6 +25,8 @@ #include #include +#include "core/filepath.h" + class QComboBox; class QVBoxLayout; class QWidget; @@ -34,99 +36,92 @@ namespace Fm { class FileMenu; class LIBFM_QT_API SidePane : public QWidget { - Q_OBJECT + Q_OBJECT public: - enum Mode { - ModeNone = -1, - ModePlaces = 0, - ModeDirTree, - NumModes - }; + enum Mode { + ModeNone = -1, + ModePlaces = 0, + ModeDirTree, + NumModes + }; public: - explicit SidePane(QWidget* parent = 0); - virtual ~SidePane(); + explicit SidePane(QWidget* parent = 0); + virtual ~SidePane(); - QSize iconSize() { - return iconSize_; - } + QSize iconSize() const { + return iconSize_; + } - void setIconSize(QSize size); + void setIconSize(QSize size); - FmPath* currentPath() { - return currentPath_; - } + const Fm::FilePath& currentPath() const { + return currentPath_; + } - void setCurrentPath(FmPath* path); + void setCurrentPath(Fm::FilePath path); - void setMode(Mode mode); + void setMode(Mode mode); - Mode mode() { - return mode_; - } + Mode mode() const { + return mode_; + } - QWidget* view() { - return view_; - } + QWidget* view() const { + return view_; + } - const char *modeName(Mode mode); + static const char* modeName(Mode mode); - Mode modeByName(const char *str); + static Mode modeByName(const char* str); #if 0 // FIXME: are these APIs from libfm-qt needed? - int modeCount(void) { - return NumModes; - } + int modeCount(void) { + return NumModes; + } - QString modeLabel(Mode mode); + QString modeLabel(Mode mode); - QString modeTooltip(Mode mode); + QString modeTooltip(Mode mode); #endif - void setShowHidden(bool show_hidden); - - bool showHidden() { - return showHidden_; - } + void setShowHidden(bool show_hidden); - bool setHomeDir(const char *home_dir); + bool showHidden() const { + return showHidden_; + } - // libfm-gtk compatible alias - FmPath* getCwd() { - return currentPath(); - } + bool setHomeDir(const char* home_dir); - void chdir(FmPath* path) { - setCurrentPath(path); - } + void chdir(Fm::FilePath path) { + setCurrentPath(std::move(path)); + } Q_SIGNALS: - void chdirRequested(int type, FmPath* path); - void openFolderInNewWindowRequested(FmPath* path); - void openFolderInNewTabRequested(FmPath* path); - void openFolderInTerminalRequested(FmPath* path); - void createNewFolderRequested(FmPath* path); - void modeChanged(Fm::SidePane::Mode mode); + void chdirRequested(int type, const Fm::FilePath& path); + void openFolderInNewWindowRequested(const Fm::FilePath& path); + void openFolderInNewTabRequested(const Fm::FilePath& path); + void openFolderInTerminalRequested(const Fm::FilePath& path); + void createNewFolderRequested(const Fm::FilePath& path); + void modeChanged(Fm::SidePane::Mode mode); - void prepareFileMenu(Fm::FileMenu* menu); // emit before showing a Fm::FileMenu + void prepareFileMenu(Fm::FileMenu* menu); // emit before showing a Fm::FileMenu protected Q_SLOTS: - void onPlacesViewChdirRequested(int type, FmPath* path); - void onDirTreeViewChdirRequested(int type, FmPath* path); - void onComboCurrentIndexChanged(int current); + void onComboCurrentIndexChanged(int current); private: - void initDirTree(); + void initDirTree(); private: - FmPath* currentPath_; - QWidget* view_; - QComboBox* combo_; - QVBoxLayout* verticalLayout; - QSize iconSize_; - Mode mode_; - bool showHidden_; + Fm::FilePath currentPath_; + QWidget* view_; + QComboBox* combo_; + QVBoxLayout* verticalLayout; + QSize iconSize_; + Mode mode_; + bool showHidden_; }; } diff --git a/src/templates.h b/src/templates.h index fa55979..bb7fbed 100644 --- a/src/templates.h +++ b/src/templates.h @@ -51,7 +51,7 @@ public: // move constructor - Template(Template&& other) { + Template(Template&& other) noexcept { dataPtr_ = reinterpret_cast(other.takeDataPtr()); } @@ -105,7 +105,7 @@ public: // move assignment - Template& operator=(Template&& other) { + Template& operator=(Template&& other) noexcept { dataPtr_ = reinterpret_cast(other.takeDataPtr()); return *this; } diff --git a/src/terminal.h b/src/terminal.h deleted file mode 100644 index cd7a31c..0000000 --- a/src/terminal.h +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_TERMINAL_H__ -#define __LIBFM_QT_FM_TERMINAL_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - -class LIBFM_QT_API Terminal { -public: - - - // default constructor - Terminal() { - dataPtr_ = nullptr; - } - - - Terminal(FmTerminal* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(g_object_ref(dataPtr)) : nullptr; - } - - - // copy constructor - Terminal(const Terminal& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - Terminal(Terminal&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - virtual ~Terminal() { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static Terminal wrapPtr(FmTerminal* dataPtr) { - Terminal obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmTerminal* takeDataPtr() { - FmTerminal* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmTerminal* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmTerminal*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - Terminal& operator=(const Terminal& other) { - if(dataPtr_ != nullptr) { - g_object_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(g_object_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - Terminal& operator=(Terminal&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - static bool launch(const gchar* dir, GError** error) { - return fm_terminal_launch(dir, error); - } - - - static Terminal dupDefault(GError** error) { - return Terminal::wrapPtr(fm_terminal_dup_default(error)); - } - - - // automatic type casting for GObject - operator GObject*() { - return reinterpret_cast(dataPtr_); - } - - -protected: - GObject* dataPtr_; // data pointer for the underlying C struct - -}; - - -} - -#endif // __LIBFM_QT_FM_TERMINAL_H__ diff --git a/src/tests/test-filedialog.cpp b/src/tests/test-filedialog.cpp new file mode 100644 index 0000000..ccd1809 --- /dev/null +++ b/src/tests/test-filedialog.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include "../core/folder.h" +#include "../foldermodel.h" +#include "../folderview.h" +#include "../cachedfoldermodel.h" +#include "../proxyfoldermodel.h" +#include "../pathedit.h" +#include "../filedialog.h" +#include "libfmqt.h" + + +int main(int argc, char** argv) { + QApplication app(argc, argv); + + Fm::LibFmQt contex; + + /* + QFileDialog dlg0; + dlg0.setFileMode(QFileDialog::ExistingFiles); + + dlg0.setNameFilters(QStringList() << "Txt (*.txt)"); + QObject::connect(&dlg0, &QFileDialog::currentChanged, [](const QString& path) { + qDebug() << "currentChanged:" << path; + }); + QObject::connect(&dlg0, &QFileDialog::fileSelected, [](const QString& path) { + qDebug() << "fileSelected:" << path; + }); + QObject::connect(&dlg0, &QFileDialog::filesSelected, [](const QStringList& paths) { + qDebug() << "filesSelected:" << paths; + }); + + dlg0.exec(); + */ + + Fm::FileDialog dlg; + // dlg.setFileMode(QFileDialog::ExistingFile); + dlg.setFileMode(QFileDialog::ExistingFiles); + // dlg.setFileMode(QFileDialog::Directory); + dlg.setNameFilters(QStringList() << "All (*)" << "Text (*.txt)" << "Images (*.gif *.jpeg *.jpg)"); + + dlg.exec(); + qDebug() << "selected files:" << dlg.selectedFiles(); + + return 0; +} diff --git a/src/tests/test-folder.cpp b/src/tests/test-folder.cpp new file mode 100644 index 0000000..45af775 --- /dev/null +++ b/src/tests/test-folder.cpp @@ -0,0 +1,44 @@ +#include +#include +#include "../core/folder.h" + +int main(int argc, char** argv) { + QApplication app(argc, argv); + + auto home = Fm::FilePath::homeDir(); + auto folder = Fm::Folder::fromPath(home); + + QObject::connect(folder.get(), &Fm::Folder::startLoading, [=]() { + qDebug("start loading"); + }); + QObject::connect(folder.get(), &Fm::Folder::finishLoading, [=]() { + qDebug("finish loading"); + }); + + QObject::connect(folder.get(), &Fm::Folder::filesAdded, [=](Fm::FileInfoList& files) { + qDebug("files added"); + for(auto& item: files) { + qDebug() << item->displayName(); + } + }); + QObject::connect(folder.get(), &Fm::Folder::filesRemoved, [=](Fm::FileInfoList& files) { + qDebug("files removed"); + for(auto& item: files) { + qDebug() << item->displayName(); + } + }); + QObject::connect(folder.get(), &Fm::Folder::filesChanged, [=](std::vector& file_pairs) { + qDebug("files changed"); + for(auto& pair: file_pairs) { + auto& item = pair.second; + qDebug() << item->displayName(); + } + }); + + for(auto& item: folder->files()) { + qDebug() << item->displayName(); + } + qDebug() << "here"; + + return app.exec(); +} diff --git a/src/tests/test-folderview.cpp b/src/tests/test-folderview.cpp new file mode 100644 index 0000000..427f1e5 --- /dev/null +++ b/src/tests/test-folderview.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include "../core/folder.h" +#include "../foldermodel.h" +#include "../folderview.h" +#include "../cachedfoldermodel.h" +#include "../proxyfoldermodel.h" +#include "../pathedit.h" +#include "libfmqt.h" + +int main(int argc, char** argv) { + QApplication app(argc, argv); + + Fm::LibFmQt contex; + QMainWindow win; + + Fm::FolderView folder_view; + win.setCentralWidget(&folder_view); + + auto home = Fm::FilePath::homeDir(); + Fm::CachedFolderModel* model = Fm::CachedFolderModel::modelFromPath(home); + auto proxy_model = new Fm::ProxyFolderModel(); + proxy_model->sort(Fm::FolderModel::ColumnFileName, Qt::AscendingOrder); + proxy_model->setSourceModel(model); + + proxy_model->setThumbnailSize(64); + proxy_model->setShowThumbnails(true); + + folder_view.setModel(proxy_model); + + QToolBar toolbar; + win.addToolBar(Qt::TopToolBarArea, &toolbar); + Fm::PathEdit edit; + edit.setText(home.toString().get()); + toolbar.addWidget(&edit); + auto action = new QAction("Go", nullptr); + toolbar.addAction(action); + QObject::connect(action, &QAction::triggered, [&]() { + auto path = Fm::FilePath::fromPathStr(edit.text().toLocal8Bit().constData()); + auto new_model = Fm::CachedFolderModel::modelFromPath(path); + proxy_model->setSourceModel(new_model); + }); + + win.show(); + return app.exec(); +} diff --git a/src/tests/test-placesview.cpp b/src/tests/test-placesview.cpp new file mode 100644 index 0000000..ddfe213 --- /dev/null +++ b/src/tests/test-placesview.cpp @@ -0,0 +1,20 @@ +#include +#include +#include +#include +#include +#include "../placesview.h" +#include "libfmqt.h" + +int main(int argc, char** argv) { + QApplication app(argc, argv); + + Fm::LibFmQt contex; + QMainWindow win; + + Fm::PlacesView view; + win.setCentralWidget(&view); + + win.show(); + return app.exec(); +} diff --git a/src/tests/test-volumemanager.cpp b/src/tests/test-volumemanager.cpp new file mode 100644 index 0000000..7b6438a --- /dev/null +++ b/src/tests/test-volumemanager.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include "../core/volumemanager.h" + +int main(int argc, char** argv) { + QApplication app(argc, argv); + + auto vm = Fm::VolumeManager::globalInstance(); + + QObject::connect(vm.get(), &Fm::VolumeManager::volumeAdded, [=](const Fm::Volume& vol) { + qDebug() << "volume added:" << vol.name().get(); + }); + QObject::connect(vm.get(), &Fm::VolumeManager::volumeRemoved, [=](const Fm::Volume& vol) { + qDebug() << "volume removed:" << vol.name().get(); + }); + + for(auto& item: vm->volumes()) { + auto name = item.name(); + qDebug() << "list volume:" << name.get(); + } + + return app.exec(); +} diff --git a/src/thumbnailer.h b/src/thumbnailer.h deleted file mode 100644 index bbf3db2..0000000 --- a/src/thumbnailer.h +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2016 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef __LIBFM_QT_FM_THUMBNAILER_H__ -#define __LIBFM_QT_FM_THUMBNAILER_H__ - -#include -#include -#include -#include "libfmqtglobals.h" - - -namespace Fm { - - -class LIBFM_QT_API Thumbnailer { -public: - - - // default constructor - Thumbnailer() { - dataPtr_ = nullptr; - } - - - Thumbnailer(FmThumbnailer* dataPtr){ - dataPtr_ = dataPtr != nullptr ? reinterpret_cast(fm_thumbnailer_ref(dataPtr)) : nullptr; - } - - - // copy constructor - Thumbnailer(const Thumbnailer& other) { - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(fm_thumbnailer_ref(other.dataPtr_)) : nullptr; - } - - - // move constructor - Thumbnailer(Thumbnailer&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - } - - - // destructor - ~Thumbnailer() { - if(dataPtr_ != nullptr) { - fm_thumbnailer_unref(dataPtr_); - } - } - - - // create a wrapper for the data pointer without increasing the reference count - static Thumbnailer wrapPtr(FmThumbnailer* dataPtr) { - Thumbnailer obj; - obj.dataPtr_ = reinterpret_cast(dataPtr); - return obj; - } - - // disown the managed data pointer - FmThumbnailer* takeDataPtr() { - FmThumbnailer* data = reinterpret_cast(dataPtr_); - dataPtr_ = nullptr; - return data; - } - - // get the raw pointer wrapped - FmThumbnailer* dataPtr() { - return reinterpret_cast(dataPtr_); - } - - // automatic type casting - operator FmThumbnailer*() { - return dataPtr(); - } - - // automatic type casting - operator void*() { - return dataPtr(); - } - - - // copy assignment - Thumbnailer& operator=(const Thumbnailer& other) { - if(dataPtr_ != nullptr) { - fm_thumbnailer_unref(dataPtr_); - } - dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast(fm_thumbnailer_ref(other.dataPtr_)) : nullptr; - return *this; - } - - - // move assignment - Thumbnailer& operator=(Thumbnailer&& other) { - dataPtr_ = reinterpret_cast(other.takeDataPtr()); - return *this; - } - - bool isNull() { - return (dataPtr_ == nullptr); - } - - // methods - - static void checkUpdate( ) { - fm_thumbnailer_check_update(); - } - - - void free(void) { - fm_thumbnailer_free(dataPtr()); - } - - - bool launchForUri(const char* uri, const char* output_file, guint size) { - return fm_thumbnailer_launch_for_uri(dataPtr(), uri, output_file, size); - } - - - GPid launchForUriAsync(const char* uri, const char* output_file, guint size, GError** error) { - return fm_thumbnailer_launch_for_uri_async(dataPtr(), uri, output_file, size, error); - } - - - char* commandForUri(const char* uri, const char* output_file, guint size) { - return fm_thumbnailer_command_for_uri(dataPtr(), uri, output_file, size); - } - - - static Thumbnailer newFromKeyfile(const char* id, GKeyFile* kf) { - return Thumbnailer::wrapPtr(fm_thumbnailer_new_from_keyfile(id, kf)); - } - - - -private: - FmThumbnailer* dataPtr_; // data pointer for the underlying C struct - -}; - - -} - -#endif // __LIBFM_QT_FM_THUMBNAILER_H__ diff --git a/src/thumbnailloader.cpp b/src/thumbnailloader.cpp deleted file mode 100644 index 8b46cdb..0000000 --- a/src/thumbnailloader.cpp +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - - -#include "thumbnailloader.h" -#include -#include -#include - -namespace Fm { - -// FmQImageWrapper is a GObject used to wrap QImage objects and use in glib-based libfm -#define FM_TYPE_QIMAGE_WRAPPER (fm_qimage_wrapper_get_type()) -#define FM_QIMAGE_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ -FM_TYPE_QIMAGE_WRAPPER, FmQImageWrapper)) -#define FM_QIMAGE_WRAPPER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),\ -FM_TYPE_QIMAGE_WRAPPER, FmQImageWrapperClass)) -#define FM_IS_QIMAGE_WRAPPER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),\ -FM_TYPE_QIMAGE_WRAPPER)) -#define FM_IS_QIMAGE_WRAPPER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),\ -FM_TYPE_QIMAGE_WRAPPER)) -#define FM_QIMAGE_WRAPPER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),\ -FM_TYPE_QIMAGE_WRAPPER, FmQImageWrapperClass)) - -typedef struct _FmQImageWrapper FmQImageWrapper; -typedef struct _FmQImageWrapperClass FmQImageWrapperClass; - -struct _FmQImageWrapper { - GObject parent; - QImage image; -}; - -struct _FmQImageWrapperClass { - GObjectClass parent_class; -}; - -GType fm_qimage_wrapper_get_type(void); -GObject* fm_qimage_wrapper_new(void); -static void fm_qimage_wrapper_finalize(GObject *self); - -G_DEFINE_TYPE(FmQImageWrapper, fm_qimage_wrapper, G_TYPE_OBJECT) - -static void fm_qimage_wrapper_class_init(FmQImageWrapperClass *klass) { - GObjectClass* object_class = G_OBJECT_CLASS(klass); - object_class->finalize = fm_qimage_wrapper_finalize; -} - -static void fm_qimage_wrapper_init(FmQImageWrapper *self) { - // placement new for QImage - new(&self->image) QImage(); -} - -static void fm_qimage_wrapper_finalize(GObject *self) { - FmQImageWrapper *wrapper = FM_QIMAGE_WRAPPER(self); - // placement delete - wrapper->image.~QImage(); -} - -GObject *fm_qimage_wrapper_new(QImage& image) { - FmQImageWrapper *wrapper = (FmQImageWrapper*)g_object_new(FM_TYPE_QIMAGE_WRAPPER, NULL); - wrapper->image = image; - return (GObject*)wrapper; -} - -ThumbnailLoader* ThumbnailLoader::theThumbnailLoader = NULL; -bool ThumbnailLoader::localFilesOnly_ = true; -int ThumbnailLoader::maxThumbnailFileSize_ = 0; - -ThumbnailLoader::ThumbnailLoader() { - // apply the settings to libfm - fm_config->thumbnail_local = localFilesOnly_; - fm_config->thumbnail_max = maxThumbnailFileSize_; - - FmThumbnailLoaderBackend qt_backend = { - readImageFromFile, - readImageFromStream, - writeImage, - scaleImage, - rotateImage, - getImageWidth, - getImageHeight, - getImageText, - setImageText - }; - fm_thumbnail_loader_set_backend(&qt_backend); -} - -ThumbnailLoader::~ThumbnailLoader() { - -} - -GObject* ThumbnailLoader::readImageFromFile(const char* filename) { - QImage image; - image.load(QString(filename)); - // qDebug("readImageFromFile: %s, %d", filename, image.isNull()); - return image.isNull() ? NULL : fm_qimage_wrapper_new(image); -} - -GObject* ThumbnailLoader::readImageFromStream(GInputStream* stream, guint64 len, GCancellable* cancellable) { - // qDebug("readImageFromStream: %p, %llu", stream, len); - // FIXME: should we set a limit here? Otherwise if len is too large, we can run out of memory. - QScopedArrayPointer buffer(new unsigned char[len]); // allocate enough buffer - unsigned char* pbuffer = buffer.data(); - unsigned int totalReadSize = 0; - while(!g_cancellable_is_cancelled(cancellable) && totalReadSize < len) { - int bytesToRead = totalReadSize + 4096 > len ? len - totalReadSize : 4096; - gssize readSize = g_input_stream_read(stream, pbuffer, bytesToRead, cancellable, NULL); - if(readSize == 0) // end of file - break; - else if(readSize == -1) // error - return NULL; - totalReadSize += readSize; - pbuffer += readSize; - } - QImage image; - image.loadFromData(buffer.data(), totalReadSize); - return image.isNull() ? NULL : fm_qimage_wrapper_new(image); -} - -gboolean ThumbnailLoader::writeImage(GObject* image, const char* filename) { - FmQImageWrapper* wrapper = FM_QIMAGE_WRAPPER(image); - if(wrapper == NULL || wrapper->image.isNull()) - return FALSE; - return (gboolean)wrapper->image.save(filename, "PNG"); -} - -GObject* ThumbnailLoader::scaleImage(GObject* ori_pix, int new_width, int new_height) { - // qDebug("scaleImage: %d, %d", new_width, new_height); - FmQImageWrapper* ori_wrapper = FM_QIMAGE_WRAPPER(ori_pix); - QImage scaled = ori_wrapper->image.scaled(new_width, new_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - return scaled.isNull() ? NULL : fm_qimage_wrapper_new(scaled); -} - -GObject* ThumbnailLoader::rotateImage(GObject* image, int degree) { - FmQImageWrapper* wrapper = FM_QIMAGE_WRAPPER(image); - // degree values are 0, 90, 180, and 270 counterclockwise. - // In Qt, QMatrix does rotation counterclockwise as well. - // However, because the y axis of widget coordinate system is downward, - // the real effect of the coordinate transformation becomes clockwise rotation. - // So we need to use (360 - degree) here. - // Quote from QMatrix API doc: - // Note that if you apply a QMatrix to a point defined in widget - // coordinates, the direction of the rotation will be clockwise because - // the y-axis points downwards. - QImage rotated = wrapper->image.transformed(QMatrix().rotate(360 - degree)); - return rotated.isNull() ? NULL : fm_qimage_wrapper_new(rotated); -} - -int ThumbnailLoader::getImageWidth(GObject* image) { - FmQImageWrapper* wrapper = FM_QIMAGE_WRAPPER(image); - return wrapper->image.width(); -} - -int ThumbnailLoader::getImageHeight(GObject* image) { - FmQImageWrapper* wrapper = FM_QIMAGE_WRAPPER(image); - return wrapper->image.height(); -} - -char* ThumbnailLoader::getImageText(GObject* image, const char* key) { - FmQImageWrapper* wrapper = FM_QIMAGE_WRAPPER(image); - QByteArray text = wrapper->image.text(key).toLatin1(); - return (char*)g_memdup(text.constData(), text.length()); -} - -gboolean ThumbnailLoader::setImageText(GObject* image, const char* key, const char* val) { - FmQImageWrapper* wrapper = FM_QIMAGE_WRAPPER(image); - // NOTE: we might receive image=NULL sometimes with older versions of libfm. - if(Q_LIKELY(wrapper != NULL)) { - wrapper->image.setText(key, val); - } - return TRUE; -} - -QImage ThumbnailLoader::image(FmThumbnailLoader* result) { - FmQImageWrapper* wrapper = FM_QIMAGE_WRAPPER(fm_thumbnail_loader_get_data(result)); - if(wrapper) { - return wrapper->image; - } - return QImage(); -} - - -} // namespace Fm diff --git a/src/thumbnailloader.h b/src/thumbnailloader.h deleted file mode 100644 index 37a8960..0000000 --- a/src/thumbnailloader.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef FM_THUMBNAILLOADER_H -#define FM_THUMBNAILLOADER_H - -#include "libfmqtglobals.h" -#include -#include -#include - -namespace Fm { - -class LIBFM_QT_API ThumbnailLoader { - -public: - ThumbnailLoader(); - virtual ~ThumbnailLoader(); - - static ThumbnailLoader* instance() { - return theThumbnailLoader; - } - - static FmThumbnailLoader* load(FmFileInfo* fileInfo, int size, FmThumbnailLoaderCallback callback, gpointer user_data) { - // qDebug("load thumbnail: %s", fm_file_info_get_disp_name(fileInfo)); - return fm_thumbnail_loader_load(fileInfo, size, callback, user_data); - } - - static FmFileInfo* fileInfo(FmThumbnailLoader* result) { - return fm_thumbnail_loader_get_file_info(result); - } - - static void cancel(FmThumbnailLoader* result) { - fm_thumbnail_loader_cancel(result); - } - - static QImage image(FmThumbnailLoader* result); - - static int size(FmThumbnailLoader* result) { - return fm_thumbnail_loader_get_size(result); - } - - static void setLocalFilesOnly(bool value) { - localFilesOnly_ = value; - if(fm_config) - fm_config->thumbnail_local = localFilesOnly_; - } - - static bool localFilesOnly() { - return localFilesOnly_; - } - - static int maxThumbnailFileSize() { - return maxThumbnailFileSize_; - } - - static void setMaxThumbnailFileSize(int size) { - maxThumbnailFileSize_ = size; - if(fm_config) - fm_config->thumbnail_max = maxThumbnailFileSize_; - } - -private: - static GObject* readImageFromFile(const char* filename); - static GObject* readImageFromStream(GInputStream* stream, guint64 len, GCancellable* cancellable); - static gboolean writeImage(GObject* image, const char* filename); - static GObject* scaleImage(GObject* ori_pix, int new_width, int new_height); - static int getImageWidth(GObject* image); - static int getImageHeight(GObject* image); - static char* getImageText(GObject* image, const char* key); - static gboolean setImageText(GObject* image, const char* key, const char* val); - static GObject* rotateImage(GObject* image, int degree); - -private: - static ThumbnailLoader* theThumbnailLoader; - static bool localFilesOnly_; - static int maxThumbnailFileSize_; -}; - -} - -#endif // FM_THUMBNAILLOADER_H diff --git a/src/utilities.cpp b/src/utilities.cpp index 5aa2fd0..c4351b2 100644 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -36,262 +36,299 @@ namespace Fm { -FmPathList* pathListFromQUrls(QList urls) { - QList::const_iterator it; - FmPathList* pathList = fm_path_list_new(); - - for(it = urls.begin(); it != urls.end(); ++it) { - QUrl url = *it; - FmPath* path = fm_path_new_for_uri(url.toString().toUtf8()); - fm_path_list_push_tail(pathList, path); - fm_path_unref(path); - } - - return pathList; +Fm::FilePathList pathListFromUriList(const char* uriList) { + Fm::FilePathList pathList; + char** uris = g_strsplit_set(uriList, "\r\n", -1); + for(char** uri = uris; *uri; ++uri) { + if(**uri != '\0') { + pathList.push_back(Fm::FilePath::fromUri(*uri)); + } + } + g_strfreev(uris); + return pathList; } -void pasteFilesFromClipboard(FmPath* destPath, QWidget* parent) { - QClipboard* clipboard = QApplication::clipboard(); - const QMimeData* data = clipboard->mimeData(); - bool isCut = false; - FmPathList* paths = NULL; +QByteArray pathListToUriList(const Fm::FilePathList& paths) { + QByteArray uriList; + for(auto& path: paths) { + uriList += path.uri().get(); + uriList += "\r\n"; + } + return uriList; +} - if(data->hasFormat("x-special/gnome-copied-files")) { - // Gnome, LXDE, and XFCE - QByteArray gnomeData = data->data("x-special/gnome-copied-files"); - char* pdata = gnomeData.data(); - char* eol = strchr(pdata, '\n'); - - if(eol) { - *eol = '\0'; - isCut = (strcmp(pdata, "cut") == 0 ? true : false); - paths = fm_path_list_new_from_uri_list(eol + 1); +Fm::FilePathList pathListFromQUrls(QList urls) { + Fm::FilePathList pathList; + for(auto it = urls.cbegin(); it != urls.cend(); ++it) { + auto path = Fm::FilePath::fromUri(it->toString().toUtf8().constData()); + pathList.push_back(std::move(path)); } - } + return pathList; +} - if(!paths && data->hasUrls()) { - // The KDE way - paths = Fm::pathListFromQUrls(data->urls()); - QByteArray cut = data->data("x-kde-cut-selection"); +std::pair parseClipboardData(const QMimeData& data) { + bool isCut = false; + Fm::FilePathList paths; + + if(data.hasFormat("x-special/gnome-copied-files")) { + // Gnome, LXDE, and XFCE + QByteArray gnomeData = data.data("x-special/gnome-copied-files"); + char* pdata = gnomeData.data(); + char* eol = strchr(pdata, '\n'); + + if(eol) { + *eol = '\0'; + isCut = (strcmp(pdata, "cut") == 0 ? true : false); + paths = pathListFromUriList(eol + 1); + } + } - if(!cut.isEmpty() && cut.at(0) == '1') - isCut = true; - } + if(paths.empty() && data.hasUrls()) { + // The KDE way + paths = Fm::pathListFromQUrls(data.urls()); + QByteArray cut = data.data(QStringLiteral("application/x-kde-cutselection")); + if(!cut.isEmpty() && QChar::fromLatin1(cut.at(0)) == QLatin1Char('1')) { + isCut = true; + } + } - if(paths) { - if(isCut) - FileOperation::moveFiles(paths, destPath, parent); - else - FileOperation::copyFiles(paths, destPath, parent); + return std::make_pair(paths, isCut); +} - fm_path_list_unref(paths); - } +void pasteFilesFromClipboard(const Fm::FilePath& destPath, QWidget* parent) { + QClipboard* clipboard = QApplication::clipboard(); + const QMimeData* data = clipboard->mimeData(); + Fm::FilePathList paths; + bool isCut = false; + + std::tie(paths, isCut) = parseClipboardData(*data); + + if(!paths.empty()) { + if(isCut) { + FileOperation::moveFiles(paths, destPath, parent); + clipboard->clear(QClipboard::Clipboard); + } + else { + FileOperation::copyFiles(paths, destPath, parent); + } + } } -void copyFilesToClipboard(FmPathList* files) { - QClipboard* clipboard = QApplication::clipboard(); - QMimeData* data = new QMimeData(); - char* urilist = fm_path_list_to_uri_list(files); - // Gnome, LXDE, and XFCE - data->setData("x-special/gnome-copied-files", QByteArray("copy\n") + QByteArray(urilist)); - // The KDE way - data->setData("text/uri-list", urilist); - // data.setData("x-kde-cut-selection", "0"); - g_free(urilist); - clipboard->setMimeData(data); +void copyFilesToClipboard(const Fm::FilePathList& files) { + QClipboard* clipboard = QApplication::clipboard(); + QMimeData* data = new QMimeData(); + QByteArray ba; + auto urilist = pathListToUriList(files); + + // Add current pid to trace cut/copy operations to current app + data->setData(QStringLiteral("text/x-libfmqt-pid"), ba.setNum(QCoreApplication::applicationPid())); + // Gnome, LXDE, and XFCE + // Note: the standard text/urilist format uses CRLF for line breaks, but gnome format uses LF only + data->setData("x-special/gnome-copied-files", QByteArray("copy\n") + urilist.replace("\r\n", "\n")); + // The KDE way + data->setData("text/uri-list", urilist); + // data->setData(QStringLiteral("application/x-kde-cutselection"), QByteArrayLiteral("0")); + clipboard->setMimeData(data); } -void cutFilesToClipboard(FmPathList* files) { - QClipboard* clipboard = QApplication::clipboard(); - QMimeData* data = new QMimeData(); - char* urilist = fm_path_list_to_uri_list(files); - // Gnome, LXDE, and XFCE - data->setData("x-special/gnome-copied-files", QByteArray("cut\n") + QByteArray(urilist)); - // The KDE way - data->setData("text/uri-list", urilist); - data->setData("x-kde-cut-selection", "1"); - g_free(urilist); - clipboard->setMimeData(data); +void cutFilesToClipboard(const Fm::FilePathList& files) { + QClipboard* clipboard = QApplication::clipboard(); + QMimeData* data = new QMimeData(); + QByteArray ba; + auto urilist = pathListToUriList(files); + + // Add current pid to trace cut/copy operations to current app + data->setData(QStringLiteral("text/x-libfmqt-pid"), ba.setNum(QCoreApplication::applicationPid())); + // Gnome, LXDE, and XFCE + // Note: the standard text/urilist format uses CRLF for line breaks, but gnome format uses LF only + data->setData("x-special/gnome-copied-files", QByteArray("cut\n") + urilist.replace("\r\n", "\n")); + // The KDE way + data->setData("text/uri-list", urilist); + data->setData(QStringLiteral("application/x-kde-cutselection"), QByteArrayLiteral("1")); + clipboard->setMimeData(data); } -void renameFile(FmFileInfo *file, QWidget *parent) { - FmPath* path = fm_file_info_get_path(file); - FilenameDialog dlg(parent); - dlg.setWindowTitle(QObject::tr("Rename File")); - dlg.setLabelText(QObject::tr("Please enter a new name:")); - // FIXME: what's the best way to handle non-UTF8 filename encoding here? - QString old_name = QString::fromLocal8Bit(fm_path_get_basename(path)); - dlg.setTextValue(old_name); - - if(fm_file_info_is_dir(file)) // select filename extension for directories - dlg.setSelectExtension(true); - - if(dlg.exec() != QDialog::Accepted) - return; - - QString new_name = dlg.textValue(); - - if(new_name == old_name) - return; - - GFile* gf = fm_path_to_gfile(path); - GFile* parent_gf = g_file_get_parent(gf); - GFile* dest = g_file_get_child(G_FILE(parent_gf), new_name.toLocal8Bit().constData()); - g_object_unref(parent_gf); - - GError* err = NULL; - if(!g_file_move(gf, dest, - GFileCopyFlags(G_FILE_COPY_ALL_METADATA | - G_FILE_COPY_NO_FALLBACK_FOR_MOVE | - G_FILE_COPY_NOFOLLOW_SYMLINKS), - NULL, /* make this cancellable later. */ - NULL, NULL, &err)) { - QMessageBox::critical(parent, QObject::tr("Error"), err->message); - g_error_free(err); - } - - g_object_unref(dest); - g_object_unref(gf); +bool isCurrentPidClipboardData(const QMimeData& data) { + QByteArray clip_pid = data.data(QStringLiteral("text/x-libfmqt-pid")); + QByteArray curr_pid; + curr_pid.setNum(QCoreApplication::applicationPid()); + + return !clip_pid.isEmpty() && clip_pid == curr_pid; } -// templateFile is a file path used as a template of the new file. -void createFileOrFolder(CreateFileType type, FmPath* parentDir, FmTemplate* templ, QWidget* parent) { - QString defaultNewName; - QString prompt; - QString dialogTitle = type == CreateNewFolder ? QObject::tr("Create Folder") - : QObject::tr("Create File"); - - switch(type) { - case CreateNewTextFile: - prompt = QObject::tr("Please enter a new file name:"); - defaultNewName = QObject::tr("New text file"); - break; +void changeFileName(const Fm::FilePath& filePath, const QString& newName, QWidget* parent) { + auto dest = filePath.parent().child(newName.toLocal8Bit().constData()); + Fm::GErrorPtr err; + if(!g_file_move(filePath.gfile().get(), dest.gfile().get(), + GFileCopyFlags(G_FILE_COPY_ALL_METADATA | + G_FILE_COPY_NO_FALLBACK_FOR_MOVE | + G_FILE_COPY_NOFOLLOW_SYMLINKS), + nullptr, /* make this cancellable later. */ + nullptr, nullptr, &err)) { + QMessageBox::critical(parent, QObject::tr("Error"), err.message()); + } +} - case CreateNewFolder: - prompt = QObject::tr("Please enter a new folder name:"); - defaultNewName = QObject::tr("New folder"); - break; +void renameFile(std::shared_ptr file, QWidget* parent) { + FilenameDialog dlg(parent); + dlg.setWindowTitle(QObject::tr("Rename File")); + dlg.setLabelText(QObject::tr("Please enter a new name:")); + // FIXME: what's the best way to handle non-UTF8 filename encoding here? + auto old_name = QString::fromStdString(file->name()); + dlg.setTextValue(old_name); - case CreateWithTemplate: { - FmMimeType* mime = fm_template_get_mime_type(templ); - prompt = QObject::tr("Enter a name for the new %1:").arg(QString::fromUtf8(fm_mime_type_get_desc(mime))); - defaultNewName = QString::fromUtf8(fm_template_get_name(templ, NULL)); - } - break; - } + if(file->isDir()) { // select filename extension for directories + dlg.setSelectExtension(true); + } -_retry: - // ask the user to input a file name - bool ok; - QString new_name = QInputDialog::getText(parent, dialogTitle, - prompt, - QLineEdit::Normal, - defaultNewName, - &ok); - - if(!ok) - return; - - GFile* parent_gf = fm_path_to_gfile(parentDir); - GFile* dest_gf = g_file_get_child(G_FILE(parent_gf), new_name.toLocal8Bit().constData()); - g_object_unref(parent_gf); - - GError* err = NULL; - switch(type) { - case CreateNewTextFile: { - GFileOutputStream* f = g_file_create(dest_gf, G_FILE_CREATE_NONE, NULL, &err); - if(f) { - g_output_stream_close(G_OUTPUT_STREAM(f), NULL, NULL); - g_object_unref(f); + if(dlg.exec() != QDialog::Accepted) { + return; + } + + QString new_name = dlg.textValue(); + if(new_name == old_name) { + return; + } + changeFileName(file->path(), new_name, parent); +} + +// templateFile is a file path used as a template of the new file. +void createFileOrFolder(CreateFileType type, Fm::FilePath parentDir, FmTemplate* templ, QWidget* parent) { + QString defaultNewName; + QString prompt; + QString dialogTitle = type == CreateNewFolder ? QObject::tr("Create Folder") + : QObject::tr("Create File"); + + switch(type) { + case CreateNewTextFile: + prompt = QObject::tr("Please enter a new file name:"); + defaultNewName = QObject::tr("New text file"); + break; + + case CreateNewFolder: + prompt = QObject::tr("Please enter a new folder name:"); + defaultNewName = QObject::tr("New folder"); + break; + + case CreateWithTemplate: { + FmMimeType* mime = fm_template_get_mime_type(templ); + prompt = QObject::tr("Enter a name for the new %1:").arg(QString::fromUtf8(fm_mime_type_get_desc(mime))); + defaultNewName = QString::fromUtf8(fm_template_get_name(templ, nullptr)); } break; - } - case CreateNewFolder: - g_file_make_directory(dest_gf, NULL, &err); - break; - case CreateWithTemplate: - fm_template_create_file(templ, dest_gf, &err, false); - break; - } - g_object_unref(dest_gf); - - if(err) { - if(err->domain == G_IO_ERROR && err->code == G_IO_ERROR_EXISTS) { - g_error_free(err); - err = NULL; - goto _retry; } - QMessageBox::critical(parent, QObject::tr("Error"), err->message); - g_error_free(err); - } +_retry: + // ask the user to input a file name + bool ok; + QString new_name = QInputDialog::getText(parent, dialogTitle, + prompt, + QLineEdit::Normal, + defaultNewName, + &ok); + + if(!ok) { + return; + } + + auto dest = parentDir.child(new_name.toLocal8Bit().data()); + Fm::GErrorPtr err; + switch(type) { + case CreateNewTextFile: { + Fm::GFileOutputStreamPtr f{g_file_create(dest.gfile().get(), G_FILE_CREATE_NONE, nullptr, &err), false}; + if(f) { + g_output_stream_close(G_OUTPUT_STREAM(f.get()), nullptr, nullptr); + } + break; + } + case CreateNewFolder: + g_file_make_directory(dest.gfile().get(), nullptr, &err); + break; + case CreateWithTemplate: + fm_template_create_file(templ, dest.gfile().get(), &err, false); + break; + } + if(err) { + if(err.domain() == G_IO_ERROR && err.code() == G_IO_ERROR_EXISTS) { + err.reset(); + goto _retry; + } + + QMessageBox::critical(parent, QObject::tr("Error"), err.message()); + } } uid_t uidFromName(QString name) { - uid_t ret; - if(name.isEmpty()) - return -1; - if(name.at(0).digitValue() != -1) { - ret = uid_t(name.toUInt()); - } - else { - struct passwd* pw = getpwnam(name.toLatin1()); - // FIXME: use getpwnam_r instead later to make it reentrant - ret = pw ? pw->pw_uid : -1; - } - - return ret; + uid_t ret; + if(name.isEmpty()) { + return -1; + } + if(name.at(0).digitValue() != -1) { + ret = uid_t(name.toUInt()); + } + else { + struct passwd* pw = getpwnam(name.toLatin1()); + // FIXME: use getpwnam_r instead later to make it reentrant + ret = pw ? pw->pw_uid : -1; + } + + return ret; } QString uidToName(uid_t uid) { - QString ret; - struct passwd* pw = getpwuid(uid); + QString ret; + struct passwd* pw = getpwuid(uid); - if(pw) - ret = pw->pw_name; - else - ret = QString::number(uid); + if(pw) { + ret = pw->pw_name; + } + else { + ret = QString::number(uid); + } - return ret; + return ret; } gid_t gidFromName(QString name) { - gid_t ret; - if(name.isEmpty()) - return -1; - if(name.at(0).digitValue() != -1) { - ret = gid_t(name.toUInt()); - } - else { - // FIXME: use getgrnam_r instead later to make it reentrant - struct group* grp = getgrnam(name.toLatin1()); - ret = grp ? grp->gr_gid : -1; - } - - return ret; + gid_t ret; + if(name.isEmpty()) { + return -1; + } + if(name.at(0).digitValue() != -1) { + ret = gid_t(name.toUInt()); + } + else { + // FIXME: use getgrnam_r instead later to make it reentrant + struct group* grp = getgrnam(name.toLatin1()); + ret = grp ? grp->gr_gid : -1; + } + + return ret; } QString gidToName(gid_t gid) { - QString ret; - struct group* grp = getgrgid(gid); + QString ret; + struct group* grp = getgrgid(gid); - if(grp) - ret = grp->gr_name; - else - ret = QString::number(gid); + if(grp) { + ret = grp->gr_name; + } + else { + ret = QString::number(gid); + } - return ret; + return ret; } int execModelessDialog(QDialog* dlg) { - // FIXME: this does much less than QDialog::exec(). Will this work flawlessly? - QEventLoop loop; - QObject::connect(dlg, &QDialog::finished, &loop, &QEventLoop::quit); - // DialogExec does not seem to be documented in the Qt API doc? - // However, in the source code of QDialog::exec(), it's used so let's use it too. - dlg->show(); - (void)loop.exec(QEventLoop::DialogExec); - return dlg->result(); + // FIXME: this does much less than QDialog::exec(). Will this work flawlessly? + QEventLoop loop; + QObject::connect(dlg, &QDialog::finished, &loop, &QEventLoop::quit); + // DialogExec does not seem to be documented in the Qt API doc? + // However, in the source code of QDialog::exec(), it's used so let's use it too. + dlg->show(); + (void)loop.exec(QEventLoop::DialogExec); + return dlg->result(); } // check if GVFS can support this uri scheme (lower case) @@ -299,13 +336,15 @@ int execModelessDialog(QDialog* dlg) { // https://github.com/lxde/lxqt/issues/512 // Use uriExists() whenever possible. bool isUriSchemeSupported(const char* uriScheme) { - const gchar * const * schemes = g_vfs_get_supported_uri_schemes(g_vfs_get_default()); - if(Q_UNLIKELY(schemes == NULL)) + const gchar* const* schemes = g_vfs_get_supported_uri_schemes(g_vfs_get_default()); + if(Q_UNLIKELY(schemes == nullptr)) { + return false; + } + for(const gchar * const* scheme = schemes; *scheme; ++scheme) + if(strcmp(uriScheme, *scheme) == 0) { + return true; + } return false; - for(const gchar * const * scheme = schemes; *scheme; ++scheme) - if(strcmp(uriScheme, *scheme) == 0) - return true; - return false; } // check if the URI exists. @@ -314,11 +353,15 @@ bool isUriSchemeSupported(const char* uriScheme) { // Avoid calling this on a slow filesystem. // Checking "network:///" is very slow, for example. bool uriExists(const char* uri) { - GFile* gf = g_file_new_for_uri(uri); - bool ret = (bool)g_file_query_exists(gf, NULL); - g_object_unref(gf); - return ret; + GFile* gf = g_file_new_for_uri(uri); + bool ret = (bool)g_file_query_exists(gf, nullptr); + g_object_unref(gf); + return ret; } +QString formatFileSize(uint64_t size, bool useSI) { + Fm::CStrPtr str{g_format_size_full(size, useSI ? G_FORMAT_SIZE_DEFAULT : G_FORMAT_SIZE_IEC_UNITS)}; + return QString(str.get()); +} } // namespace Fm diff --git a/src/utilities.h b/src/utilities.h index b6a3b35..e6bf715 100644 --- a/src/utilities.h +++ b/src/utilities.h @@ -21,33 +21,49 @@ #define FM_UTILITIES_H #include "libfmqtglobals.h" -#include #include #include +#include #include #include +#include +#include + +#include "core/filepath.h" +#include "core/fileinfo.h" + class QDialog; namespace Fm { -LIBFM_QT_API FmPathList* pathListFromQUrls(QList urls); +LIBFM_QT_API Fm::FilePathList pathListFromUriList(const char* uriList); + +LIBFM_QT_API QByteArray pathListToUriList(const Fm::FilePathList& paths); + +LIBFM_QT_API Fm::FilePathList pathListFromQUrls(QList urls); -LIBFM_QT_API void pasteFilesFromClipboard(FmPath* destPath, QWidget* parent = 0); +LIBFM_QT_API std::pair parseClipboardData(const QMimeData& data); -LIBFM_QT_API void copyFilesToClipboard(FmPathList* files); +LIBFM_QT_API void pasteFilesFromClipboard(const Fm::FilePath& destPath, QWidget* parent = 0); -LIBFM_QT_API void cutFilesToClipboard(FmPathList* files); +LIBFM_QT_API void copyFilesToClipboard(const Fm::FilePathList& files); -LIBFM_QT_API void renameFile(FmFileInfo* file, QWidget* parent = 0); +LIBFM_QT_API void cutFilesToClipboard(const Fm::FilePathList& files); + +LIBFM_QT_API bool isCurrentPidClipboardData(const QMimeData& data); + +LIBFM_QT_API void changeFileName(const Fm::FilePath& path, const QString& newName, QWidget* parent); + +LIBFM_QT_API void renameFile(std::shared_ptr file, QWidget* parent = 0); enum CreateFileType { - CreateNewFolder, - CreateNewTextFile, - CreateWithTemplate + CreateNewFolder, + CreateNewTextFile, + CreateWithTemplate }; -LIBFM_QT_API void createFileOrFolder(CreateFileType type, FmPath* parentDir, FmTemplate* templ = NULL, QWidget* parent = 0); +LIBFM_QT_API void createFileOrFolder(CreateFileType type, Fm::FilePath parentDir, FmTemplate* templ = nullptr, QWidget* parent = 0); LIBFM_QT_API uid_t uidFromName(QString name); @@ -65,6 +81,8 @@ LIBFM_QT_API bool isUriSchemeSupported(const char* uriScheme); LIBFM_QT_API bool uriExists(const char* uri); +LIBFM_QT_API QString formatFileSize(std::uint64_t size, bool useSI = false); + } #endif // FM_UTILITIES_H diff --git a/src/xdndworkaround.cpp b/src/xdndworkaround.cpp index ef8238e..e60feac 100644 --- a/src/xdndworkaround.cpp +++ b/src/xdndworkaround.cpp @@ -44,241 +44,249 @@ #endif // (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) XdndWorkaround::XdndWorkaround() { - if(!QX11Info::isPlatformX11()) - return; + if(!QX11Info::isPlatformX11()) { + return; + } - // we need to filter all X11 events - qApp->installNativeEventFilter(this); + // we need to filter all X11 events + qApp->installNativeEventFilter(this); // This part is for Qt >= 5.4 only #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) - lastDrag_ = nullptr; + lastDrag_ = nullptr; - // initialize xinput2 since newer versions of Qt5 uses it. - static char xi_name[] = "XInputExtension"; - xcb_connection_t* conn = QX11Info::connection(); - xcb_query_extension_cookie_t cookie = xcb_query_extension(conn, strlen(xi_name), xi_name); - xcb_generic_error_t* err = nullptr; - xcb_query_extension_reply_t* reply = xcb_query_extension_reply(conn, cookie, &err); - if(err == nullptr) { - xinput2Enabled_ = true; - xinputOpCode_ = reply->major_opcode; - xinputEventBase_ = reply->first_event; - xinputErrorBase_ = reply->first_error; - // qDebug() << "xinput: " << m_xi2Enabled << m_xiOpCode << m_xiEventBase; - } - else { - xinput2Enabled_ = false; - free(err); - } - free(reply); + // initialize xinput2 since newer versions of Qt5 uses it. + static char xi_name[] = "XInputExtension"; + xcb_connection_t* conn = QX11Info::connection(); + xcb_query_extension_cookie_t cookie = xcb_query_extension(conn, strlen(xi_name), xi_name); + xcb_generic_error_t* err = nullptr; + xcb_query_extension_reply_t* reply = xcb_query_extension_reply(conn, cookie, &err); + if(err == nullptr) { + xinput2Enabled_ = true; + xinputOpCode_ = reply->major_opcode; + xinputEventBase_ = reply->first_event; + xinputErrorBase_ = reply->first_error; + // qDebug() << "xinput: " << m_xi2Enabled << m_xiOpCode << m_xiEventBase; + } + else { + xinput2Enabled_ = false; + free(err); + } + free(reply); #endif // (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) } XdndWorkaround::~XdndWorkaround() { - if(!QX11Info::isPlatformX11()) - return; - qApp->removeNativeEventFilter(this); + if(!QX11Info::isPlatformX11()) { + return; + } + qApp->removeNativeEventFilter(this); } -bool XdndWorkaround::nativeEventFilter(const QByteArray & eventType, void * message, long * result) { - if(Q_LIKELY(eventType == "xcb_generic_event_t")) { - xcb_generic_event_t* event = static_cast(message); - switch(event->response_type & ~0x80) { - case XCB_CLIENT_MESSAGE: - return clientMessage(reinterpret_cast(event)); - case XCB_SELECTION_NOTIFY: - return selectionNotify(reinterpret_cast(event)); +bool XdndWorkaround::nativeEventFilter(const QByteArray& eventType, void* message, long* /*result*/) { + if(Q_LIKELY(eventType == "xcb_generic_event_t")) { + xcb_generic_event_t* event = static_cast(message); + switch(event->response_type & ~0x80) { + case XCB_CLIENT_MESSAGE: + return clientMessage(reinterpret_cast(event)); + case XCB_SELECTION_NOTIFY: + return selectionNotify(reinterpret_cast(event)); // This part is for Qt >= 5.4 only #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) - case XCB_SELECTION_REQUEST: - return selectionRequest(reinterpret_cast(event)); - case XCB_GE_GENERIC: - // newer versions of Qt5 supports xinput2, which sends its mouse events via XGE. - return genericEvent(reinterpret_cast(event)); - case XCB_BUTTON_RELEASE: - // older versions of Qt5 receive mouse events via old XCB events. - buttonRelease(); - break; + case XCB_SELECTION_REQUEST: + return selectionRequest(reinterpret_cast(event)); + case XCB_GE_GENERIC: + // newer versions of Qt5 supports xinput2, which sends its mouse events via XGE. + return genericEvent(reinterpret_cast(event)); + case XCB_BUTTON_RELEASE: + // older versions of Qt5 receive mouse events via old XCB events. + buttonRelease(); + break; #endif // Qt >= 5.4 - default: - break; + default: + break; + } } - } - return false; + return false; } // static QByteArray XdndWorkaround::atomName(xcb_atom_t atom) { - QByteArray name; - xcb_connection_t* conn = QX11Info::connection(); - xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name(conn, atom); - xcb_get_atom_name_reply_t* reply = xcb_get_atom_name_reply(conn, cookie, NULL); - int len = xcb_get_atom_name_name_length(reply); - if(len > 0) { - name.append(xcb_get_atom_name_name(reply), len); - } - free(reply); - return name; + QByteArray name; + xcb_connection_t* conn = QX11Info::connection(); + xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name(conn, atom); + xcb_get_atom_name_reply_t* reply = xcb_get_atom_name_reply(conn, cookie, nullptr); + int len = xcb_get_atom_name_name_length(reply); + if(len > 0) { + name.append(xcb_get_atom_name_name(reply), len); + } + free(reply); + return name; } // static xcb_atom_t XdndWorkaround::internAtom(const char* name, int len) { - xcb_atom_t atom = 0; - if(len == -1) - len = strlen(name); - xcb_connection_t* conn = QX11Info::connection(); - xcb_intern_atom_cookie_t cookie = xcb_intern_atom(conn, false, len, name); - xcb_generic_error_t* err = nullptr; - xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(conn, cookie, &err); - if(reply != nullptr) { - atom = reply->atom; - free(reply); - } - if(err != nullptr) - free(err); - return atom; + xcb_atom_t atom = 0; + if(len == -1) { + len = strlen(name); + } + xcb_connection_t* conn = QX11Info::connection(); + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(conn, false, len, name); + xcb_generic_error_t* err = nullptr; + xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(conn, cookie, &err); + if(reply != nullptr) { + atom = reply->atom; + free(reply); + } + if(err != nullptr) { + free(err); + } + return atom; } // static QByteArray XdndWorkaround::windowProperty(xcb_window_t window, xcb_atom_t propAtom, xcb_atom_t typeAtom, int len) { - QByteArray data; - xcb_connection_t* conn = QX11Info::connection(); - xcb_get_property_cookie_t cookie = xcb_get_property(conn, false, window, propAtom, typeAtom, 0, len); - xcb_generic_error_t* err = nullptr; - xcb_get_property_reply_t* reply = xcb_get_property_reply(conn, cookie, &err); - if(reply != nullptr) { - len = xcb_get_property_value_length(reply); - const char* buf = (const char*)xcb_get_property_value(reply); - data.append(buf, len); - free(reply); - } - if(err != nullptr) { - free(err); - } - return data; + QByteArray data; + xcb_connection_t* conn = QX11Info::connection(); + xcb_get_property_cookie_t cookie = xcb_get_property(conn, false, window, propAtom, typeAtom, 0, len); + xcb_generic_error_t* err = nullptr; + xcb_get_property_reply_t* reply = xcb_get_property_reply(conn, cookie, &err); + if(reply != nullptr) { + len = xcb_get_property_value_length(reply); + const char* buf = (const char*)xcb_get_property_value(reply); + data.append(buf, len); + free(reply); + } + if(err != nullptr) { + free(err); + } + return data; } // static void XdndWorkaround::setWindowProperty(xcb_window_t window, xcb_atom_t propAtom, xcb_atom_t typeAtom, void* data, int len, int format) { - xcb_connection_t* conn = QX11Info::connection(); - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, propAtom, typeAtom, format, len, data); + xcb_connection_t* conn = QX11Info::connection(); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, propAtom, typeAtom, format, len, data); } bool XdndWorkaround::clientMessage(xcb_client_message_event_t* event) { - QByteArray event_type = atomName(event->type); - // qDebug() << "client message:" << event_type; + QByteArray event_type = atomName(event->type); + // qDebug() << "client message:" << event_type; - // NOTE: Because of the limitation of Qt, this hack is required to provide - // Xdnd direct save (XDS) protocol support. - // http://www.freedesktop.org/wiki/Specifications/XDS/#index4h2 - // - // XDS requires that the drop target should get and set the window property of the - // drag source to pass the file path, but in Qt there is NO way to know the - // window ID of the drag source so it's not possible to implement XDS with Qt alone. - // Here is a simple hack. We get the drag source window ID with raw XCB code. - // Then, save it on the drop target widget using QObject dynamic property. - // So in the drop event handler of the target widget, it can obtain the - // window ID of the drag source with QObject::property(). - // This hack works 99.99% of the time, but it's not bullet-proof. - // In theory, there is one corner case for which this will not work. - // That is, when you drag multiple XDS sources at the same time and drop - // all of them on the same widget. (Does XDND support doing this?) - // I do not think that any app at the moment support this. - // Even if somebody is using it, X11 will die and we should solve this in Wayland instead. - // - if(event_type == "XdndDrop") { - // data.l[0] contains the XID of the source window. - // data.l[1] is reserved for future use (flags). - // data.l[2] contains the time stamp for retrieving the data. (new in version 1) - QWidget* target = QWidget::find(event->window); - if(target != nullptr) { // drop on our widget - target = qApp->widgetAt(QCursor::pos()); // get the exact child widget that receives the drop - if(target != nullptr) { - target->setProperty("xdnd::lastDragSource", event->data.data32[0]); - target->setProperty("xdnd::lastDropTime", event->data.data32[2]); - } + // NOTE: Because of the limitation of Qt, this hack is required to provide + // Xdnd direct save (XDS) protocol support. + // http://www.freedesktop.org/wiki/Specifications/XDS/#index4h2 + // + // XDS requires that the drop target should get and set the window property of the + // drag source to pass the file path, but in Qt there is NO way to know the + // window ID of the drag source so it's not possible to implement XDS with Qt alone. + // Here is a simple hack. We get the drag source window ID with raw XCB code. + // Then, save it on the drop target widget using QObject dynamic property. + // So in the drop event handler of the target widget, it can obtain the + // window ID of the drag source with QObject::property(). + // This hack works 99.99% of the time, but it's not bullet-proof. + // In theory, there is one corner case for which this will not work. + // That is, when you drag multiple XDS sources at the same time and drop + // all of them on the same widget. (Does XDND support doing this?) + // I do not think that any app at the moment support this. + // Even if somebody is using it, X11 will die and we should solve this in Wayland instead. + // + if(event_type == "XdndDrop") { + // data.l[0] contains the XID of the source window. + // data.l[1] is reserved for future use (flags). + // data.l[2] contains the time stamp for retrieving the data. (new in version 1) + QWidget* target = QWidget::find(event->window); + if(target != nullptr) { // drop on our widget + target = qApp->widgetAt(QCursor::pos()); // get the exact child widget that receives the drop + if(target != nullptr) { + target->setProperty("xdnd::lastDragSource", event->data.data32[0]); + target->setProperty("xdnd::lastDropTime", event->data.data32[2]); + } + } } - } - // This part is for Qt >= 5.4 only - #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) - else if(event_type == "XdndFinished") { - lastDrag_ = nullptr; - } - #endif // Qt >= 5.4 - return false; + // This part is for Qt >= 5.4 only +#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) + else if(event_type == "XdndFinished") { + lastDrag_ = nullptr; + } +#endif // Qt >= 5.4 + return false; } bool XdndWorkaround::selectionNotify(xcb_selection_notify_event_t* event) { - qDebug() << "selection notify" << atomName(event->selection); - return false; + qDebug() << "selection notify" << atomName(event->selection); + return false; } // This part is for Qt >= 5.4 only #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) bool XdndWorkaround::selectionRequest(xcb_selection_request_event_t* event) { - xcb_connection_t* conn = QX11Info::connection(); - if(event->property == XCB_ATOM_PRIMARY || event->property == XCB_ATOM_SECONDARY) - return false; // we only touch selection requests related to XDnd - QByteArray prop_name = atomName(event->property); - if(prop_name == "CLIPBOARD") - return false; // we do not touch clipboard, either + xcb_connection_t* conn = QX11Info::connection(); + if(event->property == XCB_ATOM_PRIMARY || event->property == XCB_ATOM_SECONDARY) { + return false; // we only touch selection requests related to XDnd + } + QByteArray prop_name = atomName(event->property); + if(prop_name == "CLIPBOARD") { + return false; // we do not touch clipboard, either + } - xcb_atom_t atomFormat = event->target; - QByteArray type_name = atomName(atomFormat); - // qDebug() << "selection request" << prop_name << type_name; - // We only want to handle text/x-moz-url and text/uri-list - if(type_name == "text/x-moz-url" || type_name.startsWith("text/uri-list")) { - QDragManager* mgr = QDragManager::self(); - QDrag* drag = mgr->object(); - if(drag == nullptr) - drag = lastDrag_; - QMimeData* mime = drag ? drag->mimeData() : nullptr; - if(mime != nullptr && mime->hasUrls()) { - QByteArray data; - QList uris = mime->urls(); - if(type_name == "text/x-moz-url") { - QString mozurl = uris.at(0).toString(QUrl::FullyEncoded); - data.append((const char*)mozurl.utf16(), mozurl.length() * 2); - } - else { // text/uri-list - for(const QUrl& uri : uris) { - data.append(uri.toString(QUrl::FullyEncoded)); - data.append("\r\n"); + xcb_atom_t atomFormat = event->target; + QByteArray type_name = atomName(atomFormat); + // qDebug() << "selection request" << prop_name << type_name; + // We only want to handle text/x-moz-url and text/uri-list + if(type_name == "text/x-moz-url" || type_name.startsWith("text/uri-list")) { + QDragManager* mgr = QDragManager::self(); + QDrag* drag = mgr->object(); + if(drag == nullptr) { + drag = lastDrag_; + } + QMimeData* mime = drag ? drag->mimeData() : nullptr; + if(mime != nullptr && mime->hasUrls()) { + QByteArray data; + QList uris = mime->urls(); + if(type_name == "text/x-moz-url") { + QString mozurl = uris.at(0).toString(QUrl::FullyEncoded); + data.append((const char*)mozurl.utf16(), mozurl.length() * 2); + } + else { // text/uri-list + for(const QUrl& uri : uris) { + data.append(uri.toString(QUrl::FullyEncoded)); + data.append("\r\n"); + } + } + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, event->requestor, event->property, + atomFormat, 8, data.size(), (const void*)data.constData()); + xcb_selection_notify_event_t notify; + notify.response_type = XCB_SELECTION_NOTIFY; + notify.requestor = event->requestor; + notify.selection = event->selection; + notify.time = event->time; + notify.property = event->property; + notify.target = atomFormat; + xcb_window_t proxy_target = event->requestor; + xcb_send_event(conn, false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char*)¬ify); + return true; // stop Qt 5 from touching the event } - } - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, event->requestor, event->property, - atomFormat, 8, data.size(), (const void*)data.constData()); - xcb_selection_notify_event_t notify; - notify.response_type = XCB_SELECTION_NOTIFY; - notify.requestor = event->requestor; - notify.selection = event->selection; - notify.time = event->time; - notify.property = event->property; - notify.target = atomFormat; - xcb_window_t proxy_target = event->requestor; - xcb_send_event(conn, false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)¬ify); - return true; // stop Qt 5 from touching the event } - } - return false; // let Qt handle this + return false; // let Qt handle this } bool XdndWorkaround::genericEvent(xcb_ge_generic_event_t* event) { - // check this is an xinput event - if(xinput2Enabled_ && event->extension == xinputOpCode_) { - if(event->event_type == XI_ButtonRelease) - buttonRelease(); - } - return false; + // check this is an xinput event + if(xinput2Enabled_ && event->extension == xinputOpCode_) { + if(event->event_type == XI_ButtonRelease) { + buttonRelease(); + } + } + return false; } void XdndWorkaround::buttonRelease() { - QDragManager* mgr = QDragManager::self(); - lastDrag_ = mgr->object(); - // qDebug() << "BUTTON RELEASE!!!!" << xcbDrag()->canDrop() << lastDrag_; + QDragManager* mgr = QDragManager::self(); + lastDrag_ = mgr->object(); + // qDebug() << "BUTTON RELEASE!!!!" << xcbDrag()->canDrop() << lastDrag_; } #endif // QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) diff --git a/src/xdndworkaround.h b/src/xdndworkaround.h index 1a05d79..e5a3f51 100644 --- a/src/xdndworkaround.h +++ b/src/xdndworkaround.h @@ -55,35 +55,34 @@ class QDrag; -class XdndWorkaround : public QAbstractNativeEventFilter -{ +class XdndWorkaround : public QAbstractNativeEventFilter { public: - XdndWorkaround(); - ~XdndWorkaround(); - bool nativeEventFilter(const QByteArray & eventType, void * message, long * result) override; - static QByteArray atomName(xcb_atom_t atom); - static xcb_atom_t internAtom(const char* name, int len = -1); - static QByteArray windowProperty(xcb_window_t window, xcb_atom_t propAtom, xcb_atom_t typeAtom, int len); - static void setWindowProperty(xcb_window_t window, xcb_atom_t propAtom, xcb_atom_t typeAtom, void* data, int len, int format = 8); + explicit XdndWorkaround(); + ~XdndWorkaround(); + bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override; + static QByteArray atomName(xcb_atom_t atom); + static xcb_atom_t internAtom(const char* name, int len = -1); + static QByteArray windowProperty(xcb_window_t window, xcb_atom_t propAtom, xcb_atom_t typeAtom, int len); + static void setWindowProperty(xcb_window_t window, xcb_atom_t propAtom, xcb_atom_t typeAtom, void* data, int len, int format = 8); private: - bool clientMessage(xcb_client_message_event_t* event); - bool selectionNotify(xcb_selection_notify_event_t* event); + bool clientMessage(xcb_client_message_event_t* event); + bool selectionNotify(xcb_selection_notify_event_t* event); // This part is for Qt >= 5.4 only #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) private: - bool selectionRequest(xcb_selection_request_event_t* event); - bool genericEvent(xcb_ge_generic_event_t *event); - // _QBasicDrag* xcbDrag() const; - void buttonRelease(); + bool selectionRequest(xcb_selection_request_event_t* event); + bool genericEvent(xcb_ge_generic_event_t* event); + // _QBasicDrag* xcbDrag() const; + void buttonRelease(); - QDrag* lastDrag_; - // xinput related - bool xinput2Enabled_; - int xinputOpCode_; - int xinputEventBase_; - int xinputErrorBase_; + QDrag* lastDrag_; + // xinput related + bool xinput2Enabled_; + int xinputOpCode_; + int xinputEventBase_; + int xinputErrorBase_; #endif // Qt >= 5.4 };