parent
30fcfd50cc
commit
52954c35b8
@ -1,6 +0,0 @@
|
||||
Upstream Authors:
|
||||
LXQt team: https://lxqt.org
|
||||
Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
|
||||
Copyright:
|
||||
Copyright (c) 2013-2018 LXQt team
|
@ -1,678 +0,0 @@
|
||||
|
||||
libfm-qt-0.13.1 / 2018-06-02
|
||||
============================
|
||||
|
||||
* Bump patch version to 1
|
||||
* It's supposedly fixed in Qt-5.11.1
|
||||
* Disconnect old source model
|
||||
|
||||
0.13.0 / 2018-05-21
|
||||
===================
|
||||
|
||||
* Release 0.13.0: Update changelog
|
||||
* Bumped minor version to 13
|
||||
* Fixed shortcut detection
|
||||
* Delete CachedFolderModel immediately on unreffing it
|
||||
* Fix crashes in new templates code
|
||||
* Remove the C++ wrapper for the old FmPath struct in libfm.
|
||||
* Avoid using FmPath from libfm in FmSearch.
|
||||
* Removed redundant code
|
||||
* Modify Fm::BasicFileLauncher APIs to use Fm::FileInfoPtr consistently.
|
||||
* Add typedef FileInfoPtr for std::shared_ptr<const FileInfo> since it's so frequently used.
|
||||
* Correctly handle the mounting of mountable paths and correctly parse the target URIs of shortcuts.
|
||||
* Add new APIs Fm::MountOperation::mountEnclosingVolume() and Fm::MountOperation::mountMountable() and deprecate Fm::MountOperation::mount().
|
||||
* Also cover local shortcuts
|
||||
* Added "reject" to exec dialog
|
||||
* Delete some libfm compatibility wrappers.
|
||||
* Delete the unused old FmTemplate wrapper.
|
||||
* Create the template items in the "Create New" menu with the new C++ file template APIs.
|
||||
* Port template support to C++ (Fm::Templates class).
|
||||
* Add convinient functions in Fm::FileOperation to set dest paths for file transfer jobs.
|
||||
* Made job async again
|
||||
* Fixed launching of desktop files
|
||||
* Cleanup
|
||||
* CMake: Prevent in-source builds
|
||||
* Fix Fm::FileInfo::isDir() and isExecutableType() to make its behavior consistent with libfm.
|
||||
* Port file lancher to C++ and Implement Fm::BasicFileLauncher base class. * Migrate Fm::FileLauncher to the new C++ implementation.
|
||||
* Port archiver integration to C++ (#182)
|
||||
* Fully port all file operations to C++ 11 (#181)
|
||||
* Add kitty to terminals.list
|
||||
* Italic font for hidden items
|
||||
* Select multiple files
|
||||
* No visible cursor in File Properties labels
|
||||
* Just corrected a misspelling
|
||||
* Elided labels for file operation dialog
|
||||
* fix namespace, fixes lxqt/libfm-qt/issues/174
|
||||
* Misc link fixes
|
||||
* Fixes some pathes after repo move
|
||||
* Fix a cause of crash in `AppChooserComboBox`
|
||||
* build: Bump version
|
||||
* Drop Fm::IconTheme
|
||||
* iconinfo: Properly handle multiple names
|
||||
* Some code cleanup
|
||||
* Fixed custom action execution mode
|
||||
* Don't drop on files
|
||||
* Prevent possible c++11 range-loop container detach
|
||||
* See `.bak` and `.old` as backup extensions; also follow GLib
|
||||
* Ensure that rename editor has opaque background
|
||||
* Use special/custom folder icons for bookmarks
|
||||
* Added two missing cases of `mapFromSource()`
|
||||
* Support hiding items in Places side-pane
|
||||
* Fixed sorting by type/owner
|
||||
* Fix lambda connections in filedialog (#159)
|
||||
* Drop Q_FOREACH
|
||||
* Fix memory leak in thumbnail loading (#150)
|
||||
* Be more tolerant
|
||||
* Fix the "Folders" key in custom actions
|
||||
* Add Group column and don't use owner's full name
|
||||
* Fix comparison of integers of different signs
|
||||
* Guarantee 64-bit time attributes (#148)
|
||||
* Added a proxy setting for backup as hidden (#145)
|
||||
* Fixed the logic of queued deletion
|
||||
* Track folders containing cut files only with QString
|
||||
* Copy selected pathbar text to selection clipboard
|
||||
* Smooth scrolling for icon and thumbnail views
|
||||
* cmake: Handle CMP0071
|
||||
* Prepare libfm-qt for bulk rename
|
||||
* No change queue of files in the deletion queue
|
||||
* FileInfo: Fix potential SEGFAULT
|
||||
* Fix two devices for one mount
|
||||
* Always wait for the folder to load before selecting
|
||||
* Really cancel multiple renaming on cancelling
|
||||
* Prevent a potential crash in xdndworkaround
|
||||
* Fix wrong gray-out of cut files
|
||||
* Merge side-pane with its surroundings
|
||||
* Prompt dialog specifically for desktop files
|
||||
* Remove unnecessary noise
|
||||
|
||||
0.12.0 / 2017-10-21
|
||||
===================
|
||||
|
||||
* Release 0.12.0: Update changelog
|
||||
* 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)
|
||||
* Updates lxqt-build-tools required version
|
||||
* Bump ABI so version numbers preparing for a new release.
|
||||
* Fix Pathbar Paint on Menu Pop-Up
|
||||
* Code cleanup and refactor for Fm::PathBar.
|
||||
* Added another condition
|
||||
* Added a missing condition (typo)
|
||||
* Scroll Pathbar with Mouse Wheel
|
||||
* Reduct flickering of the path bar when creating path buttons.
|
||||
* Code simplification by removing unnecessary signal/slot usage.
|
||||
* Path Button Middle Click
|
||||
* Enable auto-repeat for pathbar scroll buttons.
|
||||
* Make the path bar buttons aware of style changes.
|
||||
* Use widget style instead of app style
|
||||
* Align Path Buttons
|
||||
* Move FindXCB.cmake to lxqt-build-tools
|
||||
* Adds superbuild/intree support
|
||||
* Removes not needed dependency check
|
||||
* Set CMP0024 policy usage to NEW
|
||||
* Updates target_include_directories() stuff
|
||||
* Drops GLib library detection
|
||||
* Use the new FindMenuCache CMake module
|
||||
* Use the new FindFm CMake module
|
||||
* Check for FolderModelItem info (and FmPath)
|
||||
* Add Fm::PathBar::editingFinished() signal.
|
||||
* Select the current path when editing the path bar.
|
||||
* Enable path editing and popup menu for the button-style path bar.
|
||||
* Properly set styles of path buttons.
|
||||
* Remove unnecessary debug messages.
|
||||
* Try to implement the Fm::PathBar which shows a filesystem path as buttons.
|
||||
* Adds Build PROJECT_NAME with Qt version message
|
||||
* Move LIBFM_DATA_DIR to pcmanfm repo.
|
||||
* Refactors CUSTOM_ACTIONS compile definition
|
||||
* Refactors LIBFM_DATA_DIR compile definition
|
||||
* Drop add_definitions. Use target_compile_definitions.
|
||||
* Removes duplicated symbols visibility settings
|
||||
* README.md: Add build dependency lxqt-build-tools
|
||||
* Use the new lxqt-build-tools package
|
||||
* Restore symlink emblem
|
||||
* Remove empty files
|
||||
* Try to refactor the emblemed icon support in a more generalized way. Reuse FolderItemDelegate to paint the emblemed icons in Fm::PlacesView to prevent code duplication. APIs changes: * Add Fm::IconTheme::emblems() and cache emblem info in the cache. * Add Fm::FolderItemDelegate::setFileInfoRole() and Fm::FolderItemDelegate::setFmIconRole() * Cache multiple emblems rather than getting the first one only (but only paint the first one now). * Remove icon sizes from Fm::PlacesModel and Fm::PlacesModelItems to maintain MVC design pattern and backward incompatibility. * Expose two role IDs in Fm::PlacesModel: FileInfoRole and FmIconRole so the views can access these data.
|
||||
* Show File Emblems
|
||||
* Emblem For (Encrypted) Volume Icons
|
||||
* Remove cpack (#44)
|
||||
* Also Consider GEmblemedIcon (#41)
|
||||
|
||||
0.11.1 / 2016-09-24
|
||||
===================
|
||||
|
||||
* Release 0.11.1: Add changelog
|
||||
* Bump version to 0.11.1 (#39)
|
||||
* Fix Custom Actions Submenu (#38)
|
||||
* Extend README.md
|
||||
* Add C++ wrappers for libfm C APIs.
|
||||
* Correct positioning of small icons in icon/thumbnail mode (#32)
|
||||
* Silence new compiler warnings (#36)
|
||||
* Adapt to QT_USE_QSTRINGBUILDER macro
|
||||
* Fixes xcb linking error
|
||||
* Use LXQtCompilerSettings cmake module
|
||||
* Replaces deprecated QStyleOptionViewItemV4
|
||||
* Fix item text direction with RTL layout (#33)
|
||||
* Set tabstops for mount operation password dialog (#31)
|
||||
* Fix memory leak in file open menu (#29)
|
||||
* Fixes https://github.com/lxde/pcmanfm-qt/issues/351. (#27)
|
||||
* build: Use external translations
|
||||
* ts-file removal (#26)
|
||||
* Fix memory leaks in file menu (#25)
|
||||
* Merge pull request #24 from philippwiesemann/fix-folder-menu-leak
|
||||
* translations: fix four typos in messages (#23)
|
||||
* Fix memory leak if restoring files from trash
|
||||
* Fix rename dialog displaying source info twice
|
||||
* Enable renaming in Properties dialog Fixes https://github.com/lxde/pcmanfm-qt/issues/324.
|
||||
* Cancel indexing job when closing filepropsdialog
|
||||
* Bump year
|
||||
|
||||
0.11.0 / 2016-02-27
|
||||
===================
|
||||
|
||||
* Bump version number and ABI version and preparing for the initial release.
|
||||
* Redirect focus operation on the folderview to the actual widget
|
||||
* Use grid layout to have proper properties alignment
|
||||
* Focus automatically on the first field of the filesearch dialog
|
||||
* Add (folder) custom actions to foldermenu.
|
||||
* Update czech translation (by Petr Balíček <pbalicek@seznam.cz>)
|
||||
* Fix compiling with Qt < 5.4
|
||||
* Add supports for freedesktop Xdnd direct save (XDS) protocol (closes #pcmanfm-qt/298). * Move Xdnd workarounds from pcmanfm-qt to libfm-qt.
|
||||
* Protected FolderView methods for getting and setting item delegate margins
|
||||
* Perform auto-completion for the path bar when the user press Tab key. This closes pcmanfm-qt/#201.
|
||||
* Disable unused options in file search dialog
|
||||
* Let the style engine draw the text selection rectangle.
|
||||
* Fix file search with smaller than option
|
||||
* Fix target language in Lithuanian translation file
|
||||
* Fix memory leak if reading stream for image failed
|
||||
* Update German translation
|
||||
* Polish translation updated
|
||||
* Polish translation updated
|
||||
* Add a missing type casting to fix a compiler warning.
|
||||
* Relicense libfm-qt to LGPL v.2.1.
|
||||
* Avoid using blocking calls when initializing the trash can to speed up startup.
|
||||
* Updated Russian translation Removed ru_RU file
|
||||
* Create DnD menu with supported actions only
|
||||
* make createAction public so can be hidden from view
|
||||
* Adds Runtime and Devel install COMPONENT
|
||||
* Quote everything that could break due to whitespaces
|
||||
* Use CMake GenerateExportHeader to generate ABI header file
|
||||
* Use no exceptions also with Clang
|
||||
* Uses CMAKE_VISIBILITY and CMAKE_CXX_VISIBILITY flags
|
||||
* Use QString() instead of ""
|
||||
* Fix duplicated mtp mounts in the side pane by adding workarounds for potential bugs of gvfs.
|
||||
* Replace g_io_scheduler which is deprecated with QThread in Fm::PathEdit. * Fix compiler warnings.
|
||||
* Adds .gitignore
|
||||
* Makes Release the default BUILD_TYPE
|
||||
* Adds INSTALL package configuration file
|
||||
* Removes XCB from the dependencies
|
||||
* Adds target_include_directories() for the INSTALL_INTERFACE.
|
||||
* Removes CMAKE_CURRENT_BINARY_DIR usage
|
||||
* Removes QTX_INCLUDE_DIRS
|
||||
* Removes Qt Creator .user file
|
||||
* Updates libraries dependencies
|
||||
* Adds REQUIRED_PACKAGE_VERSION variables
|
||||
* Adds generation of TARGETS CMake file
|
||||
* Creates and installs an package version file
|
||||
* Renames and moves LIBRARY_NAME variable to the top CMakeLists
|
||||
* Removes sub-project
|
||||
* Rename the library to libfm-qt and fix installed header files.
|
||||
* Split pcmanfm-qt into two parts and move libfm-qt to its own repository.
|
||||
* Update French translation
|
||||
* Italian translation updates
|
||||
* Fix a crash triggered when unmounting a volume from the side pane.
|
||||
* Avoid duplicated volumes and mounts in the side panel. (This fixes the mtp:// problem.)
|
||||
* Fix missing null pointer check in Fm::Path::parent() and use nullptr instead of NULL in path.h.
|
||||
* Code cleanup, «using» directive only if necessary
|
||||
* Upgrade of pcmanfm-qt to C++11
|
||||
* hu translations updated
|
||||
* Fixed several problems with item selection and alignment
|
||||
* Remove unnecessary qDebug traces
|
||||
* Update translations.
|
||||
* The signal QAbstractItemView::iconSizeChanged is only available after Qt 5.5. Add workarounds for older Qt versions.
|
||||
* Add more null pointer checks in the thumbnail loader to avoid crashes caused by older versions of libfm.
|
||||
* Update translations
|
||||
* Avoid the column resizing tricks for the detailed list view when the view contains no columns.
|
||||
* Improve the column width computation for the detailed view
|
||||
* PlacesView: activate on click on the second column
|
||||
* SidePane: reduce size of button's column width
|
||||
* Added a filterbar + Handle virtually hidden files
|
||||
* Russian translation update
|
||||
* Update cs_CZ translation with the strings provided by petrbal in pull request #218.
|
||||
* Allow adding or removing paths in the file search dialog. Fix bugs in searching for documents.
|
||||
* Try to implement file searching by using the new Fm::FileSearchDialog class.
|
||||
* Fix a incorrecy free() in fm_search_free() API.
|
||||
* Add Fm::Path::take() API to take the ownership of a raw FmPath pointer.
|
||||
* Add class Fm::FileSearchDialog used to show a dialog for searching files.
|
||||
* Add FmSearch API which is used to build a search:// URI. (implemented in C and might become part of libfm later).
|
||||
* Fix #195 - Crash when rightclick on item in trash.
|
||||
* Add a null check for FmFileInfo in Fm::ProxyFolderModel::lessThan(). This closes #205.
|
||||
* Fix (workaround) for right-click crash in placesview.
|
||||
* Russian translation: update
|
||||
* Italian translation: add desktop entry files, adjust TS files
|
||||
* placesview: middle-click correct item to activate (fix of segfault)
|
||||
* Check for null pointers.
|
||||
* Select the folder from where we have gone up.
|
||||
* Paste into folder from its context menu.
|
||||
* libfm-qt: updated german translation
|
||||
* libfm-qt: lupdated translation files
|
||||
* Add Greek (el) translation
|
||||
* added support for mouse back/forward buttons
|
||||
* Update German translation
|
||||
* Add new signal prepareFileMenu() to Fm::SidePane and Fm::DirTree so there's a chance to customize the file menu before its shown.
|
||||
* Port some missing config options from the gtk+ version of pcmanfm.
|
||||
* Also show hidden dirs in the directory tree when the "Show Hidden" option in the menu is turned on.
|
||||
* Fix #190 - Column layout is not always updated.
|
||||
* Create New menu actions, context menu in tree side pane, #163.
|
||||
* Store side pane mode setting, #157.
|
||||
* Fixes an translation regression
|
||||
* Updates translations
|
||||
* Uses LXQt lxqt_translate_ts() to handle translations
|
||||
* Fix lxde/lxqt#447 - missing actions in Places' context menus
|
||||
* Remove trailing whitespaces
|
||||
* polishing German translation
|
||||
* Add menu items and shortcuts for new folder and blank file, fixes #163.
|
||||
* Display folders first when active and sort order descending, fixes #179.
|
||||
* Avoid space wasted by incorrect decoration in detailed view columns, fixes #177.
|
||||
* Avoid flickering column header while resizing manually, fixes #176.
|
||||
* Hungarian translation
|
||||
* Fix #627 - long startup time. (This blocking is caused by checking the availability of "network:///".)
|
||||
* Enable text selection in file properties dialog
|
||||
* Fix some memory leaks reported by valgrind.
|
||||
* Fix warnings reported by cppcheck.
|
||||
* Fix warnings reported by scan-build.
|
||||
* Sort indicators in detailed view, store column and order in settings, fixes #109.
|
||||
* Fix lxde/lxqt#512 - pcmanfm-qt: cannot delete to trash.
|
||||
* Polish translations added
|
||||
* Use 'user-trash' icon for 'Move to Trash'
|
||||
* The "Custom" option in the application chooser combo box inside file properties dialog is broken. Fix by preventing recursive signal handler invocation.
|
||||
* The file property dialog does not show correct default applications. Fix a bug in AppChooserComboBox::setMimeType() that does incorrect app comparison.
|
||||
* When converting an UID or GID to its name, show the number as string when the user or group does not exists.
|
||||
* Add more null checks.
|
||||
* Portuguese update
|
||||
* Add very basic "remaining time" display to the progress dialog. Fix lxde/lxqt#463 - Progress dialog of pcmanfm-qt does not show remaining time.
|
||||
* Fix lxde/pcmanfm-qt#120 - Foucs "Rename" button when file name changed.
|
||||
* Remove unnecessary '\n' charactor from the translated strings.
|
||||
* Fix translations (the newly added string comes from the translation of libfm).
|
||||
* Improve trash can handling: provide an option to delete the files if moving to trashcan fails.
|
||||
* Fix broken filenames of translation files.
|
||||
* More migration to Qt5 new signal/slot syntax for better type safety & speed.
|
||||
* Migrade to new Qt5 signal/slot syntax for better type safety and speed.
|
||||
* Fix the broken sorting option.
|
||||
* Fix lxde/lxqt#448 - PCmanFM-QT renaming place bookmarks does nothing.
|
||||
* Support linguistic sorting of file names. This fixes #105.
|
||||
* Update the folder model & view after files are changed.
|
||||
* Open folders in new tabs by middle clicking on items in the side pane.
|
||||
* Portuguese update
|
||||
* Fix a crash of the context menu of places view caused by change of items.
|
||||
* Save the result of "Edit bookmarks" to gtk+3 bookmarks file instead of the deprecated ~/.gtkbookmarks file. This fixes bug #112 partially.
|
||||
* Add spanish translations
|
||||
* Update Japanese translation
|
||||
* Add German translation
|
||||
* add Japanese translation
|
||||
* Implement "UnTrash" for files in trash:/// and close lxde/lxqt#136.
|
||||
* Add Russian translation
|
||||
* Drop Qt4 support in code
|
||||
* Clean up CMakeLists.txt and drop Qt4 support
|
||||
* New files added from LXDE Pootle server based on templates
|
||||
* Commit from LXDE Pootle server by user Julius22.: 1007 of 1008 strings translated (2 need review).
|
||||
* Commit from LXDE Pootle server by user Julius22.: 709 of 1008 strings translated (2 need review).
|
||||
* Commit from LXDE Pootle server by user mbouzada.: 364 of 364 strings translated (0 need review).
|
||||
* New files added from LXDE Pootle server based on templates
|
||||
* Add cs_CZ translation for libfm-qt.
|
||||
* Commit from LXDE Pootle server by user dforsi.: 364 of 364 strings translated (0 need review).
|
||||
* Commit from LXDE Pootle server by user dforsi.: 358 of 364 strings translated (0 need review).
|
||||
* Bump package version number and library soversion to prepare for 0.2 release.
|
||||
* Fix #85 - Scrolling doesn't work in compact view.
|
||||
* Hide UI elements that are not usable and disable trash can when gvfs is not available. * Add new API Fm::isUriSchemeSupported().
|
||||
* Avoid showing the popup menu when moving desktop items.
|
||||
* Improve handling of file selection and fixing FolderView::selectAll(), which is reported in #45. Delay the handling of selectionChanged() signal to avoid too frequent UI updates.
|
||||
* Little adjustment for the grid of the folder view to decrease unnecessary margins.
|
||||
* Use a new way to optimize the size of filename display based on current view mode and font size. This also fixes lxde/lxde-qt #198 - PCmanFM-qt incorrectly displays 256x256 Thumbnails.
|
||||
* Fully support single click activation and auto-selection with associated options added to the preference dialog.
|
||||
* Add single click and auto-selection on hover support to Fm::FolderView.
|
||||
* New files added from LXDE Pootle server based on templates
|
||||
* New files added from LXDE Pootle server based on templates
|
||||
* Improve update of translations to avoid unnecessary regeneration of ts files.
|
||||
* Improve handling of fallback icons. This closes bug #57.
|
||||
* Translations are lost accidentally in a previous commit. Restore them all.
|
||||
* Fix a crash in Fm::PlacesModel when gvfs is not available. This closes bug #35 - Ctrl+W closes all windows.
|
||||
* Do not detect filename extension and select the whole filename by default while renaming directories. This closes bug #71 - Don't try to detect extensions on directories. * API changed: Fm::renameFile() now accepect FmFileInfo as its first parameter.
|
||||
* Fix bug #80 - make execute in context menu doesn't do change permissions.
|
||||
* Revert "fixed selection issue #45" This patch breaks copying files by DND in icon view mode and moving desktop icons.
|
||||
* Use qss instead of QPalette to set the background color of ColorButton. This fixed bug #192 of lxde-qt.
|
||||
* Rename the library from libfm-qt to libfm-qt5 when built with Qt5.
|
||||
* fixed selection issue #45
|
||||
* Fix middle click position calculation in detailed view mode
|
||||
* Fix crash when context menu is requested but selection is empty
|
||||
* Activate view items only if clicked with left mouse button
|
||||
* Do not emit activated signal when keyboard modifiers are on.
|
||||
* Splits the checks for needed libraries
|
||||
* Removes duplicated include_directories() entry
|
||||
* Make sure clang compiler does not complain
|
||||
* Install pkgconfig file of libfm-qt to correct location in FreeBSD
|
||||
* Fix missing return values in several methods.
|
||||
* Avoid endless popups of error dialogs when there are errors launching files.
|
||||
* Save thumbnails as png files correctly.
|
||||
* Remember custom positions for desktop icons and fix #29.
|
||||
* Add template support to the folder context menus and fix #39.
|
||||
* Show "owner" in the detailed list view mode. * Fix a crash when switching to detailed list mode in qt5.
|
||||
* Use xcb to set EWMH window type hint to the desktop window in Qt5. * Some more cleanup for the CMakeList.txt files
|
||||
* Add initial support for Qt5.
|
||||
* Try to fix #36 again.
|
||||
* Fix a seg fault caused by the widget being deleted during glib signal handling.
|
||||
* Code cleanup, removing unnecessary header inclusion to speed up compilation.
|
||||
* Avoid further handling of MountOperation in the gio finished callback if the object is deleted.
|
||||
* Use modeless dialogs for app chooser and error reporting in Fm::FileLauncher and Fm::FileMenu.
|
||||
* Add an small emblem for symlinks (using icon name "emblem-symbolic-link"). Fix bug #27.
|
||||
* Add missing file to git.
|
||||
* Move internal implementation details to private headers which are not installed to the system.
|
||||
* Implement Fm::AppChooserDialog and Fm::AppMenuView classes. * Add <Open with...>/<Other Applications> menu item to Fm::FileMenu. * Add custom app to Fm::AppChooserComboBox.
|
||||
* Add Fm::AppChooserComboBox and use it in Fm::FilePropsDialog.
|
||||
* Redesign Fm::FileLauncher APIs to make it more usable. * Add Fm::FileMenu::setFileLauncher() and Fm::FolderView::setFileLauncher() APIs. * Move PCManFM::View::onFileClick() and other popup menu handling to Fm::FolderView.
|
||||
* Improve Fm::FileLaucher to make it easy to derive subclasses. * Implement a special dialog for opening executable files (Fix bug #13 - it does not launch executables)
|
||||
* Fix bug #28 - Tash can icon does not refresh when the trash can changes its empty/full status
|
||||
* Load autocompletion list for Fm::PathEdit only when the widget has the keyboard focus to avoid unnecessary I/O.
|
||||
* Add proper popup menu items for selected folders and fix #20 and #19. * Some code refactors, adding openFolders() and openFolderInTerminal() to Application class.
|
||||
* Fix #25 - Amount of items in the folder is not refreshed when the folder content changes. * Update status bar text properly on switching tab pages, selection changes, and folder content changes.
|
||||
* Fix the broken compiler definitions caused by previous commit.
|
||||
* Fix bug #22 - Do not select file extension by default on file rename. * Avoid installing private headers (*_p.h files)
|
||||
* Setup pcmanfm-qt to support optional Custom Actions Menubar detects if libfm was built with vala or not if so a fm-actions.h will exist and support will be compiled in if not, will still compile with no actions menu
|
||||
* Allow installation path configuration with standard CMake X_INSTALL_DIR
|
||||
* Support reordering bookmark items in the places view with DND.
|
||||
* Support adding bookmarks to the places view using drag and drop
|
||||
* Preparing for implementing dnd for places view.
|
||||
* Improve the usability of icon view mode, fixing github bug #24.
|
||||
* Fix crashes caused by invalid pointer access.
|
||||
* Switch current dir of the folder view correctly with dir tree view in the side pane.
|
||||
* Finish chdir operation for Fm::DirTreeView.
|
||||
* Support hiding hidden folders from DirTreeModel.
|
||||
* Move some methods from DirTreeModel to DirTreeModelItem and fix some row updating problems.
|
||||
* Implement dynamic folder loading/unloading when expanding or collapsing dir tree nodes. * Enable horizontal scrollbar of dir tree view.
|
||||
* Move some code from Fm::DirTreeModel to Fm::DirTreeModelItem.
|
||||
* Partially implement Fm::DirTreeView and Fm::DirTreeModel. (not finished yet)
|
||||
* Fix an invalid pointer
|
||||
* Implment different modes for Fm::SidePane, matching libfm-qtk design. * Add basic skeleton for dir tree view/model.
|
||||
* Fix the cosmetic defect introduced by the eject buttons in the places view.
|
||||
* Add eject buttons to mounted volumes in the places side pane.
|
||||
* Add a wrapper class Fm::Path for FmPath C struct.
|
||||
* Initialize icon_ member of PlacesModelItem correctly.
|
||||
* Fix fallback icon when a platform plugin is abscent. * Make Fm::IconTheme::checkUpdate() a static function.
|
||||
* Remove xsettings support. Use a cleaner way to detect config changes by monitor StyleChange events. * Add Fm::IconTheme::changed() signal which is emitted when the icon theme name is changed. * Replace nested Fm::PlacesModel::Item and related classes with their own separate toplevel classes.
|
||||
* Fix the icon for files of unknown mime types, again.
|
||||
* Fix the icon for files of unknown mime types.
|
||||
* Add DES-EMA custom actions to the popup menus.
|
||||
* Make it safe to create multiple Fm::LibFmQt objects and only initialize libfm once.
|
||||
* Fix incorrect export symbols and use GNUInstallDirs for installation destination
|
||||
* Use the latest libfm thumbnailer APIs.
|
||||
* Fix #3614873 - Thumbnails in icon view shown upside down for some jpegs.
|
||||
* Adopt recent changes of libfm. FmIcon is essentially identical to GIcon now.
|
||||
* Add a UI file for application chooser dialog.
|
||||
* Correctly handle display names of folders in path entry auto-completion.
|
||||
* Add a global header and add proper definition for LIBFM_QT_API macro.
|
||||
* Add "Empty trash" and fix a memory leak.
|
||||
* Fix memory leaks for bookmarks. Fix the broken "Network" item in places.
|
||||
* Reduce memory usage: Paint the folder items with our own code instead of using a dirty hacks duplicating pixmaps.
|
||||
* Reduce of size of QPixmapCache in the hope of decreasing memory usage.
|
||||
* Add fallback icons for places item "applications" and "network".
|
||||
* Add class Fm::CachedFolderModel, a convinient way to share Fm::FolderModel objects and reduce memory usage.
|
||||
* Resize the columns of detailed list view when items are inserted or removed.
|
||||
* Optimize column widths in detailed list mode when the view is resized.
|
||||
* Only show thumbnails for the first column in detailed list mode.
|
||||
* Use new "automoc" feature of cmake 2.8.6 and remove cumbersome #include "*.moc" code.
|
||||
* Trivial fix.
|
||||
* Add additional custom filter support to ProxyFolderModel.
|
||||
* Fix some memory leaks.
|
||||
* Fix some compiler errors and update translations.
|
||||
* Support the latest libfm trunk. Remove -fpermissive compiler flag and fix compiler errors/warnings.
|
||||
* Adopt new libfm thumbnail APIs.
|
||||
* Add soname 0.0.0 for libfm-qt, preparing for 0.1 release.
|
||||
* Fix crashes caused by incorrect deletion of dialog objects.
|
||||
* Enable thumbnail related settings.
|
||||
* Update zh_TW translations and translation templates.
|
||||
* Add Portuguese translation (pt).
|
||||
* Add Lithuanian translation (lt_LT).
|
||||
* Adopt the latest thumbnail API in libfm (thumbnail branch) to speed up loading.
|
||||
* Workardound incorrect thumbnail painting caused by bug of QStyledItemDelegate. :-(
|
||||
* Fix a crash caused by accessing data for invalid model index.
|
||||
* Fix a crash caused by accessing data for invalid model index.
|
||||
* Add basic thumbnail support (need the latest thumbnail branch of libfm).
|
||||
* Add archiver integration for file context menus.
|
||||
* Add archiver integration to file context menus.
|
||||
* Add mnemonics for menu items. Make confirm dialog before delete and trash can optional.
|
||||
* Update side pane according to current dir. Little fix.
|
||||
* Implement "Open in Terminal" and "Open as Root".
|
||||
* Implement "Auto Run" for newly inserted removable devices.
|
||||
* Add "Edit Bookmarks" dialog.
|
||||
* Implement "Invert Selection". Little fix of UI, add a Tool menu to main window.
|
||||
* Implement "Create New" menu in the folder popup menu.
|
||||
* Modify make rules for translations. Avoid deleting generated ts files when "make clean". Fix a small error in zh_TW translation.
|
||||
* Add auto-completion to path entry bar.
|
||||
* Rename Fm::Application to Fm::LibFmQt to decrease confusion. Set required Qt version to 4.6.
|
||||
* Load translation files correctly for pcmanfm-qt and libfm-qt.
|
||||
* Add basic skeleton for i18n (using Qt QTranslator & Qt Linguist).
|
||||
* Add separate CMakeLists.txt files for pcmanfm and libfm-qt. Hide more implementation details from libfm-qt headers.
|
||||
* Fix copyright notice in all source files.
|
||||
* Install a pkgconfig file for libfm-qt for use in other projects.
|
||||
* Fix a memory error caused by incorrect array size. Fix incorrect spacing of icons.
|
||||
* Finish chown and chmod supports.
|
||||
* Try to add file opermission settings UI.
|
||||
* Implement very basic drag and drop support.
|
||||
* Supress the incorrect default dnd handling of QListView.
|
||||
* Try to implement Dnd.
|
||||
* Finish desktop preferences.
|
||||
* Improve desktop preferences and apply settings (partially done).
|
||||
* Add desktop preferences dialog.
|
||||
* Apply side pane icon size correctly. Add basic skeleton for archiver integration.
|
||||
* Set shortcuts for frequently used menu options. Implement "rename file" support. Hide tabs when there is only one tab left (optional).
|
||||
* Delete windows properly when they're closed with setAttribute(Qt::WA_DeleteOnClose); Apply settings to windows after clicking OK in the preference dialog.
|
||||
* Improve preferences dialog. Change base class of SidePane to QWidget.
|
||||
* Sync the state of folder popup menu and main menu bar.
|
||||
* Implement sort options for main window.
|
||||
* Fix file sorting options for Fm::FolderMenu.
|
||||
* Correctly implement browse history and fix crashes.
|
||||
* Add very simple browse history (back/forward) handling.
|
||||
* Apply gcc visiblility attributes to export less symbols.
|
||||
* Correctly handle file rename/overwrite during file operations.
|
||||
* Exclude unnecessary files from CPack.
|
||||
* Improve folder popup menu.
|
||||
* Add folder popup menu. Some UI polishing.
|
||||
* Fix a little crash.
|
||||
* Fix crashes when turning off desktop manager.
|
||||
* Show popup menu for blank area of folders.
|
||||
* Do some refactor to make Fm::FolderView cleaner. Add PCManFM::View to do file manager-specific operations.
|
||||
* Move files for libfm-qt and pcmanfm-qt to separate subdirs.
|
@ -1,90 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.0.2)
|
||||
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 13)
|
||||
set(LIBFM_QT_VERSION_PATCH 1)
|
||||
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")
|
||||
|
||||
# We use the libtool versioning scheme for the internal so name, "current:revision:age"
|
||||
# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
|
||||
# https://www.sourceware.org/autobook/autobook/autobook_91.html
|
||||
# http://pusling.com/blog/?p=352
|
||||
# Actually, libtool uses different ways on different operating systems. So there is no
|
||||
# universal way to translate a libtool version-info to a cmake version.
|
||||
# We use "(current-age).age.revision" as the cmake version.
|
||||
# current: 5, revision: 0, age: 0 => version: 5.0.0
|
||||
set(LIBFM_QT_LIB_VERSION "5.0.0")
|
||||
set(LIBFM_QT_LIB_SOVERSION "5")
|
||||
|
||||
set(REQUIRED_QT_VERSION "5.7.1")
|
||||
set(REQUIRED_LIBFM_VERSION "1.2.0")
|
||||
set(REQUIRED_LIBMENUCACHE_VERSION "0.4.0")
|
||||
set(REQUIRED_LXQT_BUILD_TOOLS_VERSION "0.5.0")
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
endif()
|
||||
|
||||
find_package(Qt5Widgets "${REQUIRED_QT_VERSION}" REQUIRED)
|
||||
find_package(Qt5LinguistTools "${REQUIRED_QT_VERSION}" REQUIRED)
|
||||
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}")
|
||||
|
||||
option(UPDATE_TRANSLATIONS "Update source translation translations/*.ts files" OFF)
|
||||
include(GNUInstallDirs)
|
||||
include(GenerateExportHeader)
|
||||
include(CMakePackageConfigHelpers)
|
||||
include(LXQtPreventInSourceBuilds)
|
||||
include(LXQtTranslateTs)
|
||||
include(LXQtTranslateDesktop)
|
||||
include(LXQtCompilerSettings NO_POLICY_SCOPE)
|
||||
|
||||
set(CMAKE_AUTOMOC TRUE)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
write_basic_package_version_file(
|
||||
"${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-config-version.cmake"
|
||||
VERSION ${LIBFM_QT_LIB_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion
|
||||
)
|
||||
|
||||
install(FILES
|
||||
"${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-config-version.cmake"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}"
|
||||
COMPONENT Devel
|
||||
)
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(data)
|
||||
|
||||
# add Doxygen support to generate API docs
|
||||
# References:
|
||||
# https://majewsky.wordpress.com/2010/08/14/tip-of-the-day-cmake-and-doxygen/
|
||||
option(BUILD_DOCUMENTATION "Use Doxygen to create the HTML based API documentation" OFF)
|
||||
if(BUILD_DOCUMENTATION)
|
||||
find_package(Doxygen REQUIRED)
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in" "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile" @ONLY)
|
||||
add_custom_target(doc ALL
|
||||
${DOXYGEN_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
COMMENT "Generating API documentation with Doxygen" VERBATIM
|
||||
)
|
||||
install(DIRECTORY
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/docs"
|
||||
DESTINATION "${CMAKE_INSTALL_DOCDIR}"
|
||||
COMPONENT Devel
|
||||
)
|
||||
endif()
|
File diff suppressed because it is too large
Load Diff
@ -1,458 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
@ -1,45 +0,0 @@
|
||||
# libfm-qt
|
||||
|
||||
## Overview
|
||||
|
||||
libfm-qt is the Qt port of libfm, a library providing components to build
|
||||
desktop file managers which belongs to [LXDE](https://lxde.org).
|
||||
|
||||
libfm-qt is licensed under the terms of the
|
||||
[LGPLv2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html)
|
||||
or any later version. See file LICENSE for its full text.
|
||||
|
||||
## Installation
|
||||
|
||||
### Compiling source code
|
||||
|
||||
Runtime dependencies are Qt X11 Extras and libfm ≥ 1.2
|
||||
(not all features are provided by libfm-qt yet).
|
||||
Additional build dependencies are CMake,
|
||||
[lxqt-build-tools](https://github.com/lxqt/lxqt-build-tools) and optionally Git
|
||||
to pull latest VCS checkouts. The localization files were outsourced to
|
||||
repository [lxqt-l10n](https://github.com/lxqt/lxqt-l10n) so the corresponding
|
||||
dependencies are needed, too. Please refer to this repository's `README.md` for
|
||||
further information.
|
||||
|
||||
Code configuration is handled by CMake. CMake variable `CMAKE_INSTALL_PREFIX`
|
||||
has to be set to `/usr` on most operating systems, depending on the way library
|
||||
paths are dealt with on 64bit systems variables like `CMAKE_INSTALL_LIBDIR` may
|
||||
have to be set as well.
|
||||
|
||||
To build run `make`, to install `make install` which accepts variable `DESTDIR`
|
||||
as usual.
|
||||
|
||||
### Binary packages
|
||||
|
||||
Official binary packages are available in Arch Linux, Debian (as of Debian
|
||||
stretch) and openSUSE (Leap 42.1 and Tumbleweed).
|
||||
The library is still missing in Fedora which is providing version 0.10.0 of
|
||||
PCManFM-Qt only so far. This version was still including the code outsourced
|
||||
into libfm-qt later so libfm-qt will have to be provided by Fedora, too,
|
||||
as soon as the distribution upgrades to PCManFM-Qt ≥ 0.10.1.
|
||||
|
||||
## Development
|
||||
|
||||
Issues should go to the tracker of PCManFM-Qt at
|
||||
https://github.com/lxqt/pcmanfm-qt/issues.
|
@ -1,41 +0,0 @@
|
||||
#=============================================================================
|
||||
# Copyright 2015 Luís Pereira <luis.artur.pereira@gmail.com>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#=============================================================================
|
||||
|
||||
@PACKAGE_INIT@
|
||||
|
||||
if (CMAKE_VERSION VERSION_LESS 3.0.2)
|
||||
message(FATAL_ERROR \"fm-qt requires at least CMake version 3.0.2\")
|
||||
endif()
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
if (NOT TARGET @LIBFM_QT_LIBRARY_NAME@)
|
||||
if (POLICY CMP0024)
|
||||
cmake_policy(SET CMP0024 NEW)
|
||||
endif()
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@LIBFM_QT_LIBRARY_NAME@-targets.cmake")
|
||||
endif()
|
@ -1,10 +0,0 @@
|
||||
install(FILES
|
||||
"archivers.list"
|
||||
"terminals.list"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/libfm-qt"
|
||||
)
|
||||
|
||||
install(FILES
|
||||
"libfm-qt-mimetypes.xml"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/mime/packages"
|
||||
)
|
@ -1,35 +0,0 @@
|
||||
[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
|
@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Additional mime-types provided by libfm, adding some
|
||||
missing but frequently seen globs for some common mime-types.
|
||||
-->
|
||||
<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>
|
||||
|
||||
<mime-type type="text/plain">
|
||||
<glob pattern="*.ini"/>
|
||||
<glob pattern="*.inf"/>
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="application/x-ms-dos-executable">
|
||||
<glob pattern="*.com" />
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="application/x-ms-win-installer">
|
||||
<comment>Windows installer</comment>
|
||||
<comment xml:lang="zh_TW">Windows 安裝程式</comment>
|
||||
<glob pattern="*.msi" />
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="application/x-vbscript">
|
||||
<comment>MS VBScript</comment>
|
||||
<glob pattern="*.vbs" />
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="text/x-csharp">
|
||||
<comment xml:lang="en">C# source</comment>
|
||||
<comment xml:lang="zh_TW">C# 程式碼</comment>
|
||||
<glob pattern="*.cs"/>
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="application/x-desktop">
|
||||
<comment xml:lang="zh_TW">應用程式捷徑</comment>
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="application/x-sharedlib">
|
||||
<!--
|
||||
This pattern matching is not very accurate ,but the probability that
|
||||
a file is named like this and it's not a shared lib, is very low.
|
||||
-->
|
||||
<glob pattern="*.dll"/> <!-- Windows dll are shared libs, too -->
|
||||
<glob pattern="*.so.[0-9]" />
|
||||
<glob pattern="*.so.[0-9].*" />
|
||||
</mime-type>
|
||||
|
||||
<mime-type type="application/x-desktop">
|
||||
<glob pattern="*.directory"/>
|
||||
</mime-type>
|
||||
|
||||
</mime-info>
|
@ -1,80 +0,0 @@
|
||||
[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
|
||||
|
||||
[kitty]
|
||||
desktop_id=kitty.desktop
|
@ -1,269 +0,0 @@
|
||||
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/filetransferjob.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/basicfilelauncher.cpp
|
||||
core/volumemanager.cpp
|
||||
core/userinfocache.cpp
|
||||
core/thumbnailer.cpp
|
||||
core/terminal.cpp
|
||||
core/archiver.cpp
|
||||
core/templates.cpp
|
||||
# custom actions
|
||||
customactions/fileaction.cpp
|
||||
customactions/fileactionprofile.cpp
|
||||
customactions/fileactioncondition.cpp
|
||||
)
|
||||
|
||||
set(libfm_SRCS
|
||||
${libfm_core_SRCS}
|
||||
libfmqt.cpp
|
||||
bookmarkaction.cpp
|
||||
sidepane.cpp
|
||||
filelauncher.cpp
|
||||
foldermodel.cpp
|
||||
foldermodelitem.cpp
|
||||
cachedfoldermodel.cpp
|
||||
proxyfoldermodel.cpp
|
||||
folderview.cpp
|
||||
folderitemdelegate.cpp
|
||||
createnewmenu.cpp
|
||||
filemenu.cpp
|
||||
foldermenu.cpp
|
||||
filepropsdialog.cpp
|
||||
applaunchcontext.cpp
|
||||
placesview.cpp
|
||||
placesmodel.cpp
|
||||
placesmodelitem.cpp
|
||||
dirtreeview.cpp
|
||||
dirtreemodel.cpp
|
||||
dirtreemodelitem.cpp
|
||||
dnddest.cpp
|
||||
mountoperation.cpp
|
||||
mountoperationpassworddialog.cpp
|
||||
mountoperationquestiondialog.cpp
|
||||
fileoperation.cpp
|
||||
fileoperationdialog.cpp
|
||||
renamedialog.cpp
|
||||
pathedit.cpp
|
||||
pathbar.cpp
|
||||
colorbutton.cpp
|
||||
fontbutton.cpp
|
||||
browsehistory.cpp
|
||||
utilities.cpp
|
||||
dndactionmenu.cpp
|
||||
editbookmarksdialog.cpp
|
||||
execfiledialog.cpp
|
||||
appchoosercombobox.cpp
|
||||
appmenuview.cpp
|
||||
appchooserdialog.cpp
|
||||
filesearchdialog.cpp
|
||||
filedialog.cpp
|
||||
fm-search.c # might be moved to libfm later
|
||||
xdndworkaround.cpp
|
||||
)
|
||||
|
||||
set(libfm_UIS
|
||||
file-props.ui
|
||||
file-operation-dialog.ui
|
||||
rename-dialog.ui
|
||||
mount-operation-password.ui
|
||||
edit-bookmarks.ui
|
||||
exec-file.ui
|
||||
app-chooser-dialog.ui
|
||||
filesearch.ui
|
||||
filedialog.ui
|
||||
)
|
||||
|
||||
set(LIBFM_QT_DATA_DIR "${CMAKE_INSTALL_FULL_DATADIR}/libfm-qt")
|
||||
set(LIBFM_QT_INTREE_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/include")
|
||||
|
||||
# add translation for libfm-qt
|
||||
lxqt_translate_ts(QM_FILES
|
||||
UPDATE_TRANSLATIONS ${UPDATE_TRANSLATIONS}
|
||||
SOURCES ${libfm_SRCS} ${libfm_UIS}
|
||||
INSTALL_DIR "${LIBFM_QT_DATA_DIR}/translations"
|
||||
PULL_TRANSLATIONS ${PULL_TRANSLATIONS}
|
||||
CLEAN_TRANSLATIONS ${CLEAN_TRANSLATIONS}
|
||||
TRANSLATIONS_REPO ${TRANSLATIONS_REPO}
|
||||
TRANSLATIONS_REFSPEC ${TRANSLATIONS_REFSPEC}
|
||||
)
|
||||
|
||||
add_library(${LIBFM_QT_LIBRARY_NAME} SHARED
|
||||
${libfm_SRCS}
|
||||
${libfm_UIS}
|
||||
${QM_FILES}
|
||||
)
|
||||
|
||||
install(EXPORT
|
||||
"${LIBFM_QT_LIBRARY_NAME}-targets"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}"
|
||||
COMPONENT Devel
|
||||
)
|
||||
|
||||
target_link_libraries(${LIBFM_QT_LIBRARY_NAME}
|
||||
Qt5::Widgets
|
||||
Qt5::X11Extras
|
||||
${FM_LIBRARIES}
|
||||
${MENUCACHE_LIBRARIES}
|
||||
${XCB_LIBRARIES}
|
||||
${EXIF_LIBRARIES}
|
||||
)
|
||||
|
||||
# set libtool soname
|
||||
set_target_properties(${LIBFM_QT_LIBRARY_NAME} PROPERTIES
|
||||
VERSION ${LIBFM_QT_LIB_VERSION}
|
||||
SOVERSION ${LIBFM_QT_LIB_SOVERSION}
|
||||
)
|
||||
|
||||
target_include_directories(${LIBFM_QT_LIBRARY_NAME}
|
||||
PRIVATE "${Qt5Gui_PRIVATE_INCLUDE_DIRS}"
|
||||
PUBLIC
|
||||
"${FM_INCLUDE_DIRS}"
|
||||
"${FM_INCLUDE_DIR}/libfm" # to workaround incorrect #include in fm-actions.
|
||||
"${MENUCACHE_INCLUDE_DIRS}"
|
||||
"${XCB_INCLUDE_DIRS}"
|
||||
"${EXIF_INCLUDE_DIRS}"
|
||||
INTERFACE
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
|
||||
"$<BUILD_INTERFACE:${LIBFM_QT_INTREE_INCLUDE_DIR}>"
|
||||
)
|
||||
|
||||
target_compile_definitions(${LIBFM_QT_LIBRARY_NAME}
|
||||
PRIVATE "LIBFM_QT_DATA_DIR=\"${LIBFM_QT_DATA_DIR}\""
|
||||
"QT_NO_FOREACH"
|
||||
PUBLIC "QT_NO_KEYWORDS"
|
||||
)
|
||||
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}_export.h"
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libfm-qt"
|
||||
COMPONENT Devel
|
||||
)
|
||||
|
||||
# install include header files (FIXME: can we make this cleaner? should dir name be versioned?)
|
||||
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}
|
||||
EXPORT_MACRO_NAME LIBFM_QT_API
|
||||
)
|
||||
|
||||
# InTree build
|
||||
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"
|
||||
"${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-config.cmake"
|
||||
INSTALL_DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}"
|
||||
)
|
||||
|
||||
install(FILES
|
||||
"${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-config.cmake"
|
||||
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${LIBFM_QT_LIBRARY_NAME}"
|
||||
COMPONENT Devel
|
||||
)
|
||||
|
||||
# FIXME: add libtool version to the lib (soname) later.
|
||||
# FIXME: only export public symbols
|
||||
|
||||
install(TARGETS ${LIBFM_QT_LIBRARY_NAME}
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
EXPORT "${LIBFM_QT_LIBRARY_NAME}-targets"
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
PUBLIC_HEADER
|
||||
COMPONENT Runtime
|
||||
)
|
||||
|
||||
export(TARGETS ${LIBFM_QT_LIBRARY_NAME}
|
||||
FILE "${CMAKE_BINARY_DIR}/${LIBFM_QT_LIBRARY_NAME}-targets.cmake"
|
||||
EXPORT_LINK_INTERFACE_LIBRARIES
|
||||
)
|
||||
|
||||
# install a pkgconfig file for libfm-qt
|
||||
set(REQUIRED_QT "Qt5Widgets >= ${REQUIRED_QT_VERSION} Qt5X11Extras >= ${REQUIRED_QT_VERSION}")
|
||||
configure_file(libfm-qt.pc.in lib${LIBFM_QT_LIBRARY_NAME}.pc @ONLY)
|
||||
# FreeBSD loves to install files to different locations
|
||||
# https://www.freebsd.org/doc/handbook/dirstructure.html
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/lib${LIBFM_QT_LIBRARY_NAME}.pc"
|
||||
DESTINATION libdata/pkgconfig
|
||||
COMPONENT Devel
|
||||
)
|
||||
else()
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/lib${LIBFM_QT_LIBRARY_NAME}.pc"
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
|
||||
COMPONENT Devel
|
||||
)
|
||||
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})
|
||||
|
@ -1,183 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AppChooserDialog</class>
|
||||
<widget class="QDialog" name="AppChooserDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>432</width>
|
||||
<height>387</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Choose an Application</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="fileTypeHeader"/>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Installed Applications</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="Fm::AppMenuView" name="appMenuView"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Custom Command</string>
|
||||
</attribute>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Command line to execute:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLineEdit" name="cmdLine"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Application name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="appName"/>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string><b>These special codes can be used in the command line:</b>
|
||||
<ul>
|
||||
<li><b>%f</b>: Represents a single file name</li>
|
||||
<li><b>%F</b>: Represents multiple file names</li>
|
||||
<li><b>%u</b>: Represents a single URI of the file</li>
|
||||
<li><b>%U</b>: Represents multiple URIs</li>
|
||||
</ul></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="keepTermOpen">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Keep terminal window open after command execution</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="useTerminal">
|
||||
<property name="text">
|
||||
<string>Execute in terminal emulator</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="setDefault">
|
||||
<property name="text">
|
||||
<string>Set selected application as default action of this file type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Fm::AppMenuView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>appmenuview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>AppChooserDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>227</x>
|
||||
<y>359</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>AppChooserDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>295</x>
|
||||
<y>365</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>useTerminal</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>keepTermOpen</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>72</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>79</x>
|
||||
<y>282</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 "appchoosercombobox.h"
|
||||
#include "appchooserdialog.h"
|
||||
#include "utilities.h"
|
||||
#include "core/iconinfo.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
AppChooserComboBox::AppChooserComboBox(QWidget* parent):
|
||||
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: https://forum.qt.io/topic/20998/qt5-new-signals-slots-syntax-does-not-work-solved
|
||||
connect((QComboBox*)this, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &AppChooserComboBox::onCurrentIndexChanged);
|
||||
}
|
||||
|
||||
AppChooserComboBox::~AppChooserComboBox() {
|
||||
}
|
||||
|
||||
void AppChooserComboBox::setMimeType(std::shared_ptr<const Fm::MimeType> 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_);
|
||||
}
|
||||
}
|
||||
|
||||
// returns the currently selected app.
|
||||
Fm::GAppInfoPtr AppChooserComboBox::selectedApp() const {
|
||||
// the elements of appInfos_ and the combo indexes before "Customize"
|
||||
// always have a one-to-one correspondence
|
||||
int idx = currentIndex();
|
||||
return idx >= 0 && !appInfos_.empty() ? appInfos_[idx] : Fm::GAppInfoPtr{};
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// block our handler to prevent recursive calls.
|
||||
blockOnCurrentIndexChanged_ = true;
|
||||
// restore to previously selected item
|
||||
setCurrentIndex(prevIndex_);
|
||||
blockOnCurrentIndexChanged_ = false;
|
||||
}
|
||||
else {
|
||||
prevIndex_ = index;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
/* get a list of custom apps added with app-chooser.
|
||||
* the returned GList is owned by the combo box and shouldn't be freed. */
|
||||
const GList* AppChooserComboBox::customApps() {
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Fm
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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_APPCHOOSERCOMBOBOX_H
|
||||
#define FM_APPCHOOSERCOMBOBOX_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QComboBox>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "core/mimetype.h"
|
||||
#include "core/gioptrs.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API AppChooserComboBox : public QComboBox {
|
||||
Q_OBJECT
|
||||
public:
|
||||
~AppChooserComboBox();
|
||||
explicit AppChooserComboBox(QWidget* parent);
|
||||
|
||||
void setMimeType(std::shared_ptr<const Fm::MimeType> mimeType);
|
||||
|
||||
const std::shared_ptr<const Fm::MimeType>& mimeType() const {
|
||||
return mimeType_;
|
||||
}
|
||||
|
||||
Fm::GAppInfoPtr selectedApp() const;
|
||||
// const GList* customApps();
|
||||
|
||||
bool isChanged() const;
|
||||
|
||||
private Q_SLOTS:
|
||||
void onCurrentIndexChanged(int index);
|
||||
|
||||
private:
|
||||
std::shared_ptr<const Fm::MimeType> mimeType_;
|
||||
std::vector<Fm::GAppInfoPtr> 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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_APPCHOOSERCOMBOBOX_H
|
@ -1,286 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2014 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
* Copyright 2012-2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
|
||||
*
|
||||
* 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 "appchooserdialog.h"
|
||||
#include "ui_app-chooser-dialog.h"
|
||||
#include <QPushButton>
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
AppChooserDialog::AppChooserDialog(std::shared_ptr<const Fm::MimeType> 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);
|
||||
|
||||
if(!ui->appMenuView->isAppSelected()) {
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); // disable OK button
|
||||
}
|
||||
}
|
||||
|
||||
AppChooserDialog::~AppChooserDialog() {
|
||||
delete ui;
|
||||
}
|
||||
|
||||
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 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 = 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, 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 = nullptr;
|
||||
}
|
||||
}
|
||||
if(arg_found) {
|
||||
*arg_found = (p != nullptr);
|
||||
}
|
||||
if(p) {
|
||||
return g_strndup(cmdline, p - cmdline);
|
||||
}
|
||||
else {
|
||||
return g_strdup(cmdline);
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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();
|
||||
|
||||
if(ui->tabWidget->currentIndex() == 0) {
|
||||
selectedApp_ = ui->appMenuView->selectedApp();
|
||||
}
|
||||
else { // custom command line
|
||||
selectedApp_ = customCommandToApp();
|
||||
}
|
||||
|
||||
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_.get(), mimeType_->name(), nullptr);
|
||||
#else
|
||||
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_.get(), mimeType_->name(), nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppChooserDialog::onSelectionChanged() {
|
||||
bool isAppSelected = ui->appMenuView->isAppSelected();
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isAppSelected);
|
||||
}
|
||||
|
||||
void AppChooserDialog::setMimeType(std::shared_ptr<const Fm::MimeType> 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2014 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
* Copyright 2012-2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
|
||||
*
|
||||
* 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_APPCHOOSERDIALOG_H
|
||||
#define FM_APPCHOOSERDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "libfmqtglobals.h"
|
||||
#include <libfm/fm.h>
|
||||
|
||||
#include "core/mimetype.h"
|
||||
#include "core/gioptrs.h"
|
||||
|
||||
namespace Ui {
|
||||
class AppChooserDialog;
|
||||
}
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API AppChooserDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AppChooserDialog(std::shared_ptr<const Fm::MimeType> mimeType, QWidget* parent = nullptr, Qt::WindowFlags f = 0);
|
||||
~AppChooserDialog();
|
||||
|
||||
virtual void accept();
|
||||
|
||||
void setMimeType(std::shared_ptr<const Fm::MimeType> mimeType);
|
||||
|
||||
const std::shared_ptr<const Fm::MimeType>& mimeType() const {
|
||||
return mimeType_;
|
||||
}
|
||||
|
||||
void setCanSetDefault(bool value);
|
||||
|
||||
bool canSetDefault() const {
|
||||
return canSetDefault_;
|
||||
}
|
||||
|
||||
const Fm::GAppInfoPtr& selectedApp() const {
|
||||
return selectedApp_;
|
||||
}
|
||||
|
||||
bool isSetDefault() const;
|
||||
|
||||
private:
|
||||
GAppInfo* customCommandToApp();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onSelectionChanged();
|
||||
void onTabChanged(int index);
|
||||
|
||||
private:
|
||||
Ui::AppChooserDialog* ui;
|
||||
std::shared_ptr<const Fm::MimeType> mimeType_;
|
||||
bool canSetDefault_;
|
||||
Fm::GAppInfoPtr selectedApp_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_APPCHOOSERDIALOG_H
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 "applaunchcontext.h"
|
||||
#include <QX11Info>
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
typedef struct _FmAppLaunchContext {
|
||||
GAppLaunchContext parent;
|
||||
}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*/) {
|
||||
Display* dpy = QX11Info::display();
|
||||
if(dpy) {
|
||||
char* xstr = DisplayString(dpy);
|
||||
return g_strdup(xstr);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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) {
|
||||
GAppLaunchContextClass* app_launch_class = G_APP_LAUNCH_CONTEXT_CLASS(klass);
|
||||
app_launch_class->get_display = fm_app_launch_context_get_display;
|
||||
app_launch_class->get_startup_notify_id = fm_app_launch_context_get_startup_notify_id;
|
||||
}
|
||||
|
||||
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, nullptr);
|
||||
return context;
|
||||
}
|
||||
|
||||
FmAppLaunchContext* fm_app_launch_context_new() {
|
||||
FmAppLaunchContext* context = (FmAppLaunchContext*)g_object_new(FM_TYPE_APP_LAUNCH_CONTEXT, nullptr);
|
||||
return context;
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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_APP_LAUNCHCONTEXT_H
|
||||
#define FM_APP_LAUNCHCONTEXT_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <gio/gio.h>
|
||||
#include <QWidget>
|
||||
|
||||
#define FM_TYPE_APP_LAUNCH_CONTEXT (fm_app_launch_context_get_type())
|
||||
#define FM_APP_LAUNCH_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\
|
||||
FM_TYPE_APP_LAUNCH_CONTEXT, FmAppLaunchContext))
|
||||
#define FM_APP_LAUNCH_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),\
|
||||
FM_TYPE_APP_LAUNCH_CONTEXT, FmAppLaunchContextClass))
|
||||
#define FM_IS_APP_LAUNCH_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),\
|
||||
FM_TYPE_APP_LAUNCH_CONTEXT))
|
||||
#define FM_IS_APP_LAUNCH_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),\
|
||||
FM_TYPE_APP_LAUNCH_CONTEXT))
|
||||
#define FM_APP_LAUNCH_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),\
|
||||
FM_TYPE_APP_LAUNCH_CONTEXT, FmAppLaunchContextClass))
|
||||
|
||||
typedef struct _FmAppLaunchContext FmAppLaunchContext;
|
||||
|
||||
typedef struct _FmAppLaunchContextClass {
|
||||
GAppLaunchContextClass parent;
|
||||
}FmAppLaunchContextClass;
|
||||
|
||||
FmAppLaunchContext* fm_app_launch_context_new();
|
||||
FmAppLaunchContext* fm_app_launch_context_new_for_widget(QWidget* widget);
|
||||
GType fm_app_launch_context_get_type();
|
||||
|
||||
#endif // FM_APPLAUNCHCONTEXT_H
|
@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 "appmenuview.h"
|
||||
#include <QStandardItemModel>
|
||||
#include "appmenuview_p.h"
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
AppMenuView::AppMenuView(QWidget* parent):
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 != 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);
|
||||
}
|
||||
|
||||
void AppMenuView::onMenuCacheReload(MenuCache* mc) {
|
||||
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() const {
|
||||
AppMenuViewItem* item = selectedItem();
|
||||
return (item && item->isApp());
|
||||
}
|
||||
|
||||
AppMenuViewItem* AppMenuView::selectedItem() const {
|
||||
QModelIndexList selected = selectedIndexes();
|
||||
if(!selected.isEmpty()) {
|
||||
AppMenuViewItem* item = static_cast<AppMenuViewItem*>(model_->itemFromIndex(selected.first()
|
||||
));
|
||||
return item;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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() 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() const {
|
||||
AppMenuViewItem* item = selectedItem();
|
||||
if(item && item->isApp()) {
|
||||
return menu_cache_item_get_id(item->item());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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_APPMENUVIEW_H
|
||||
#define FM_APPMENUVIEW_H
|
||||
|
||||
#include <QTreeView>
|
||||
#include "libfmqtglobals.h"
|
||||
#include <libfm/fm.h>
|
||||
#include <menu-cache/menu-cache.h>
|
||||
|
||||
#include "core/gioptrs.h"
|
||||
|
||||
class QStandardItemModel;
|
||||
class QStandardItem;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class AppMenuViewItem;
|
||||
|
||||
class LIBFM_QT_API AppMenuView : public QTreeView {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AppMenuView(QWidget* parent = nullptr);
|
||||
~AppMenuView();
|
||||
|
||||
Fm::GAppInfoPtr selectedApp() const;
|
||||
|
||||
const char* selectedAppDesktopId() const;
|
||||
|
||||
QByteArray selectedAppDesktopFilePath() const;
|
||||
|
||||
FmPath* selectedAppDesktopPath() const;
|
||||
|
||||
bool isAppSelected() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
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<AppMenuView*>(user_data)->onMenuCacheReload(mc);
|
||||
}
|
||||
|
||||
AppMenuViewItem* selectedItem() const;
|
||||
|
||||
private:
|
||||
// gboolean fm_app_menu_view_is_item_app(, GtkTreeIter* it);
|
||||
QStandardItemModel* model_;
|
||||
MenuCache* menu_cache;
|
||||
MenuCacheNotifyId menu_cache_reload_notify;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_APPMENUVIEW_H
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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_APPMENUVIEW_P_H
|
||||
#define FM_APPMENUVIEW_P_H
|
||||
|
||||
#include <QStandardItem>
|
||||
#include <menu-cache/menu-cache.h>
|
||||
#include "core/iconinfo.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class AppMenuViewItem : public QStandardItem {
|
||||
public:
|
||||
explicit AppMenuViewItem(MenuCacheItem* item):
|
||||
item_(menu_cache_item_ref(item)) {
|
||||
std::shared_ptr<const Fm::IconInfo> 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_);
|
||||
}
|
||||
|
||||
MenuCacheItem* item() {
|
||||
return item_;
|
||||
}
|
||||
|
||||
int type() const {
|
||||
return menu_cache_item_get_type(item_);
|
||||
}
|
||||
|
||||
bool isApp() {
|
||||
return type() == MENU_CACHE_TYPE_APP;
|
||||
}
|
||||
|
||||
bool isDir() {
|
||||
return type() == MENU_CACHE_TYPE_DIR;
|
||||
}
|
||||
|
||||
private:
|
||||
MenuCacheItem* item_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_APPMENUVIEW_P_H
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 "bookmarkaction.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
BookmarkAction::BookmarkAction(std::shared_ptr<const Fm::BookmarkItem> item, QObject* parent):
|
||||
QAction(parent),
|
||||
item_(std::move(item)) {
|
||||
|
||||
setText(item_->name());
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 BOOKMARKACTION_H
|
||||
#define BOOKMARKACTION_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QAction>
|
||||
#include "core/bookmarks.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
// action used to create bookmark menu items
|
||||
class LIBFM_QT_API BookmarkAction : public QAction {
|
||||
public:
|
||||
explicit BookmarkAction(std::shared_ptr<const Fm::BookmarkItem> item, QObject* parent = 0);
|
||||
|
||||
const std::shared_ptr<const Fm::BookmarkItem>& bookmark() const {
|
||||
return item_;
|
||||
}
|
||||
|
||||
const Fm::FilePath& path() const {
|
||||
return item_->path();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<const Fm::BookmarkItem> item_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // BOOKMARKACTION_H
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 "browsehistory.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
BrowseHistory::BrowseHistory():
|
||||
currentIndex_(0),
|
||||
maxCount_(10) {
|
||||
}
|
||||
|
||||
BrowseHistory::~BrowseHistory() {
|
||||
}
|
||||
|
||||
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(items_.size() + 1 > static_cast<size_t>(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
|
||||
items_.push_back(BrowseHistoryItem(path, scrollPos));
|
||||
currentIndex_ = items_.size() - 1;
|
||||
}
|
||||
|
||||
void BrowseHistory::setCurrentIndex(int index) {
|
||||
if(index >= 0 && static_cast<size_t>(index) < items_.size()) {
|
||||
currentIndex_ = index;
|
||||
// FIXME: should we emit a signal for the change?
|
||||
}
|
||||
}
|
||||
|
||||
bool BrowseHistory::canBackward() const {
|
||||
return (currentIndex_ > 0);
|
||||
}
|
||||
|
||||
int BrowseHistory::backward() {
|
||||
if(canBackward()) {
|
||||
--currentIndex_;
|
||||
}
|
||||
return currentIndex_;
|
||||
}
|
||||
|
||||
bool BrowseHistory::canForward() const {
|
||||
return (static_cast<size_t>(currentIndex_) + 1 < items_.size());
|
||||
}
|
||||
|
||||
int BrowseHistory::forward() {
|
||||
if(canForward()) {
|
||||
++currentIndex_;
|
||||
}
|
||||
return currentIndex_;
|
||||
}
|
||||
|
||||
void BrowseHistory::setMaxCount(int maxCount) {
|
||||
maxCount_ = maxCount;
|
||||
if(items_.size() > static_cast<size_t>(maxCount)) {
|
||||
// TODO: remove some items
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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_BROWSEHISTORY_H
|
||||
#define FM_BROWSEHISTORY_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <vector>
|
||||
#include <libfm/fm.h>
|
||||
|
||||
#include "core/filepath.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
// class used to story browsing history of folder views
|
||||
// We use this class to replace FmNavHistory provided by libfm since
|
||||
// the original Libfm API is hard to use and confusing.
|
||||
|
||||
class LIBFM_QT_API BrowseHistoryItem {
|
||||
public:
|
||||
|
||||
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:
|
||||
Fm::FilePath path_;
|
||||
int scrollPos_;
|
||||
// TODO: we may need to store current selection as well.
|
||||
};
|
||||
|
||||
class LIBFM_QT_API BrowseHistory {
|
||||
|
||||
public:
|
||||
BrowseHistory();
|
||||
virtual ~BrowseHistory();
|
||||
|
||||
int currentIndex() const {
|
||||
return currentIndex_;
|
||||
}
|
||||
void setCurrentIndex(int index);
|
||||
|
||||
Fm::FilePath currentPath() const {
|
||||
return items_[currentIndex_].path();
|
||||
}
|
||||
|
||||
int currentScrollPos() const {
|
||||
return items_[currentIndex_].scrollPos();
|
||||
}
|
||||
|
||||
BrowseHistoryItem& currentItem() {
|
||||
return items_[currentIndex_];
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return items_.size();
|
||||
}
|
||||
|
||||
BrowseHistoryItem& at(int index) {
|
||||
return items_[index];
|
||||
}
|
||||
|
||||
void add(Fm::FilePath path, int scrollPos = 0);
|
||||
|
||||
bool canForward() const;
|
||||
|
||||
bool canBackward() const;
|
||||
|
||||
int backward();
|
||||
|
||||
int forward();
|
||||
|
||||
int maxCount() const {
|
||||
return maxCount_;
|
||||
}
|
||||
|
||||
void setMaxCount(int maxCount);
|
||||
|
||||
private:
|
||||
std::vector<BrowseHistoryItem> items_;
|
||||
int currentIndex_;
|
||||
int maxCount_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_BROWSEHISTORY_H
|
@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 "cachedfoldermodel.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
CachedFolderModel::CachedFolderModel(const std::shared_ptr<Fm::Folder>& folder):
|
||||
FolderModel(),
|
||||
refCount(1) {
|
||||
FolderModel::setFolder(folder);
|
||||
}
|
||||
|
||||
CachedFolderModel::~CachedFolderModel() {
|
||||
// qDebug("delete CachedFolderModel");
|
||||
}
|
||||
|
||||
CachedFolderModel* CachedFolderModel::modelFromFolder(const std::shared_ptr<Fm::Folder>& folder) {
|
||||
QVariant cache = folder->property(cacheKey);
|
||||
CachedFolderModel* model = cache.value<CachedFolderModel*>();
|
||||
if(model) {
|
||||
model->ref();
|
||||
}
|
||||
else {
|
||||
model = new CachedFolderModel(folder);
|
||||
cache = QVariant::fromValue(model);
|
||||
folder->setProperty(cacheKey, cache);
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
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) {
|
||||
folder()->setProperty(cacheKey, QVariant());
|
||||
delete(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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_CACHEDFOLDERMODEL_H
|
||||
#define FM_CACHEDFOLDERMODEL_H
|
||||
|
||||
#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
|
||||
public:
|
||||
explicit CachedFolderModel(const std::shared_ptr<Fm::Folder>& folder);
|
||||
void ref() {
|
||||
++refCount;
|
||||
}
|
||||
void unref();
|
||||
|
||||
static CachedFolderModel* modelFromFolder(const std::shared_ptr<Fm::Folder>& folder);
|
||||
static CachedFolderModel* modelFromPath(const Fm::FilePath& path);
|
||||
|
||||
private:
|
||||
virtual ~CachedFolderModel();
|
||||
void setFolder(FmFolder* folder);
|
||||
private:
|
||||
int refCount;
|
||||
constexpr static const char* cacheKey = "CachedFolderModel";
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_CACHEDFOLDERMODEL_H
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 "colorbutton.h"
|
||||
#include <QColorDialog>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
ColorButton::ColorButton(QWidget* parent): QPushButton(parent) {
|
||||
connect(this, &QPushButton::clicked, this, &ColorButton::onClicked);
|
||||
}
|
||||
|
||||
ColorButton::~ColorButton() {
|
||||
|
||||
}
|
||||
|
||||
void ColorButton::onClicked() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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_COLORBUTTON_H
|
||||
#define FM_COLORBUTTON_H
|
||||
|
||||
#include "libfmqtglobals.h"
|
||||
#include <QPushButton>
|
||||
#include <QColor>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API ColorButton : public QPushButton {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ColorButton(QWidget* parent = 0);
|
||||
virtual ~ColorButton();
|
||||
|
||||
void setColor(const QColor&);
|
||||
|
||||
QColor color() const {
|
||||
return color_;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void changed();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onClicked();
|
||||
|
||||
private:
|
||||
QColor color_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FM_COLORBUTTON_H
|
@ -1,174 +0,0 @@
|
||||
#include "libfmqtglobals.h"
|
||||
#include "archiver.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
Archiver* Archiver::defaultArchiver_ = nullptr; // static
|
||||
std::vector<std::unique_ptr<Archiver>> Archiver::allArchivers_; // static
|
||||
|
||||
Archiver::Archiver() {
|
||||
}
|
||||
|
||||
bool Archiver::isMimeTypeSupported(const char* type) {
|
||||
char** p;
|
||||
if(G_UNLIKELY(!type)) {
|
||||
return false;
|
||||
}
|
||||
for(p = mimeTypes_.get(); *p; ++p) {
|
||||
if(strcmp(*p, type) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Archiver::launchProgram(GAppLaunchContext* ctx, const char* cmd, const FilePathList& files, const FilePath& dir) {
|
||||
char* _cmd = NULL;
|
||||
const char* dir_place_holder;
|
||||
GKeyFile* dummy;
|
||||
|
||||
if(dir.isValid() && (dir_place_holder = strstr(cmd, "%d"))) {
|
||||
CStrPtr dir_str;
|
||||
int len;
|
||||
if(strstr(cmd, "%U") || strstr(cmd, "%u")) { /* supports URI */
|
||||
dir_str = dir.uri();
|
||||
}
|
||||
else {
|
||||
dir_str = dir.localPath();
|
||||
}
|
||||
|
||||
// FIXME: remove libfm dependency here
|
||||
/* replace all % with %% so encoded URI can be handled correctly when parsing Exec key. */
|
||||
std::string percentEscapedDir;
|
||||
for(auto p = dir_str.get(); *p; ++p) {
|
||||
percentEscapedDir += *p;
|
||||
if(*p == '%') {
|
||||
percentEscapedDir += '%';
|
||||
}
|
||||
}
|
||||
|
||||
/* quote the path or URI */
|
||||
dir_str = CStrPtr{g_shell_quote(percentEscapedDir.c_str())};
|
||||
|
||||
len = strlen(cmd) - 2 + strlen(dir_str.get()) + 1;
|
||||
_cmd = (char*)g_malloc(len);
|
||||
len = (dir_place_holder - cmd);
|
||||
strncpy(_cmd, cmd, len);
|
||||
strcpy(_cmd + len, dir_str.get());
|
||||
strcat(_cmd, dir_place_holder + 2);
|
||||
cmd = _cmd;
|
||||
}
|
||||
|
||||
/* create a fake key file to cheat GDesktopAppInfo */
|
||||
dummy = g_key_file_new();
|
||||
g_key_file_set_string(dummy, G_KEY_FILE_DESKTOP_GROUP, "Type", "Application");
|
||||
g_key_file_set_string(dummy, G_KEY_FILE_DESKTOP_GROUP, "Name", program_.get());
|
||||
|
||||
/* replace all % with %% so encoded URI can be handled correctly when parsing Exec key. */
|
||||
g_key_file_set_string(dummy, G_KEY_FILE_DESKTOP_GROUP, "Exec", cmd);
|
||||
GAppInfoPtr app{reinterpret_cast<GAppInfo*>(g_desktop_app_info_new_from_keyfile(dummy)), false};
|
||||
|
||||
g_key_file_free(dummy);
|
||||
g_debug("cmd = %s", cmd);
|
||||
if(app) {
|
||||
GList* uris = NULL;
|
||||
for(auto& file: files) {
|
||||
uris = g_list_prepend(uris, g_strdup(file.uri().get()));
|
||||
}
|
||||
g_app_info_launch_uris(app.get(), uris, ctx, NULL);
|
||||
g_list_foreach(uris, (GFunc)g_free, NULL);
|
||||
g_list_free(uris);
|
||||
}
|
||||
g_free(_cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Archiver::createArchive(GAppLaunchContext* ctx, const FilePathList& files) {
|
||||
if(createCmd_ && !files.empty()) {
|
||||
launchProgram(ctx, createCmd_.get(), files, FilePath{});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Archiver::extractArchives(GAppLaunchContext* ctx, const FilePathList& files) {
|
||||
if(extractCmd_ && !files.empty()) {
|
||||
launchProgram(ctx, extractCmd_.get(), files, FilePath{});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Archiver::extractArchivesTo(GAppLaunchContext* ctx, const FilePathList& files, const FilePath& dest_dir) {
|
||||
if(extractToCmd_ && !files.empty()) {
|
||||
launchProgram(ctx, extractToCmd_.get(), files, dest_dir);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
Archiver* Archiver::defaultArchiver() {
|
||||
allArchivers(); // to have a preliminary default archiver
|
||||
return defaultArchiver_;
|
||||
}
|
||||
|
||||
void Archiver::setDefaultArchiverByName(const char *name) {
|
||||
if(name) {
|
||||
auto& all = allArchivers();
|
||||
for(auto& archiver: all) {
|
||||
if(archiver->program_ && strcmp(archiver->program_.get(), name) == 0) {
|
||||
defaultArchiver_ = archiver.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void Archiver::setDefaultArchiver(Archiver* archiver) {
|
||||
if(archiver) {
|
||||
defaultArchiver_ = archiver;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
const std::vector<std::unique_ptr<Archiver> >& Archiver::allArchivers() {
|
||||
// load all archivers on demand
|
||||
if(allArchivers_.empty()) {
|
||||
GKeyFile* kf = g_key_file_new();
|
||||
if(g_key_file_load_from_file(kf, LIBFM_QT_DATA_DIR "/archivers.list", G_KEY_FILE_NONE, NULL)) {
|
||||
gsize n_archivers;
|
||||
CStrArrayPtr programs{g_key_file_get_groups(kf, &n_archivers)};
|
||||
if(programs) {
|
||||
gsize i;
|
||||
for(i = 0; i < n_archivers; ++i) {
|
||||
auto program = programs[i];
|
||||
std::unique_ptr<Archiver> archiver{new Archiver{}};
|
||||
archiver->createCmd_ = CStrPtr{g_key_file_get_string(kf, program, "create", NULL)};
|
||||
archiver->extractCmd_ = CStrPtr{g_key_file_get_string(kf, program, "extract", NULL)};
|
||||
archiver->extractToCmd_ = CStrPtr{g_key_file_get_string(kf, program, "extract_to", NULL)};
|
||||
archiver->mimeTypes_ = CStrArrayPtr{g_key_file_get_string_list(kf, program, "mime_types", NULL, NULL)};
|
||||
archiver->program_ = CStrPtr{g_strdup(program)};
|
||||
|
||||
// if default archiver is not set, find the first program existing in the current system.
|
||||
if(!defaultArchiver_) {
|
||||
CStrPtr fullPath{g_find_program_in_path(program)};
|
||||
if(fullPath) {
|
||||
defaultArchiver_ = archiver.get();
|
||||
}
|
||||
}
|
||||
|
||||
allArchivers_.emplace_back(std::move(archiver));
|
||||
}
|
||||
}
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
return allArchivers_;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,69 +0,0 @@
|
||||
#ifndef ARCHIVER_H
|
||||
#define ARCHIVER_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "filepath.h"
|
||||
#include "gioptrs.h"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API Archiver {
|
||||
public:
|
||||
Archiver();
|
||||
|
||||
bool isMimeTypeSupported(const char* type);
|
||||
|
||||
bool canCreateArchive() const {
|
||||
return createCmd_ != nullptr;
|
||||
}
|
||||
|
||||
bool createArchive(GAppLaunchContext* ctx, const FilePathList& files);
|
||||
|
||||
bool canExtractArchives() const {
|
||||
return extractCmd_ != nullptr;
|
||||
}
|
||||
|
||||
bool extractArchives(GAppLaunchContext* ctx, const FilePathList& files);
|
||||
|
||||
bool canExtractArchivesTo() const {
|
||||
return extractToCmd_ != nullptr;
|
||||
}
|
||||
|
||||
bool extractArchivesTo(GAppLaunchContext* ctx, const FilePathList& files, const FilePath& dest_dir);
|
||||
|
||||
/* get default GUI archivers used by libfm */
|
||||
static Archiver* defaultArchiver();
|
||||
|
||||
/* set default GUI archivers used by libfm */
|
||||
static void setDefaultArchiverByName(const char* name);
|
||||
|
||||
/* set default GUI archivers used by libfm */
|
||||
static void setDefaultArchiver(Archiver* archiver);
|
||||
|
||||
/* get a list of FmArchiver* of all GUI archivers known to libfm */
|
||||
static const std::vector<std::unique_ptr<Archiver>>& allArchivers();
|
||||
|
||||
const char* program() const {
|
||||
return program_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
bool launchProgram(GAppLaunchContext* ctx, const char* cmd, const FilePathList& files, const FilePath &dir);
|
||||
|
||||
private:
|
||||
CStrPtr program_;
|
||||
CStrPtr createCmd_;
|
||||
CStrPtr extractCmd_;
|
||||
CStrPtr extractToCmd_;
|
||||
CStrArrayPtr mimeTypes_;
|
||||
|
||||
static Archiver* defaultArchiver_;
|
||||
static std::vector<std::unique_ptr<Archiver>> allArchivers_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // ARCHIVER_H
|
@ -1,345 +0,0 @@
|
||||
#include "basicfilelauncher.h"
|
||||
#include "fileinfojob.h"
|
||||
#include "mountoperation.h"
|
||||
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
#include <QObject>
|
||||
#include <QEventLoop>
|
||||
#include <QDebug>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
BasicFileLauncher::BasicFileLauncher():
|
||||
quickExec_{false} {
|
||||
}
|
||||
|
||||
BasicFileLauncher::~BasicFileLauncher() {
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::launchFiles(const FileInfoList& fileInfos, GAppLaunchContext* ctx) {
|
||||
std::unordered_map<std::string, FileInfoList> mimeTypeToFiles;
|
||||
FileInfoList folderInfos;
|
||||
FilePathList pathsToLaunch;
|
||||
// classify files according to different mimetypes
|
||||
for(auto& fileInfo : fileInfos) {
|
||||
// qDebug("path: %s, target: %s", fileInfo->path().toString().get(), fileInfo->target().c_str());
|
||||
if(fileInfo->isDir()) {
|
||||
folderInfos.emplace_back(fileInfo);
|
||||
}
|
||||
else if(fileInfo->isMountable()) {
|
||||
if(fileInfo->target().empty()) {
|
||||
// the mountable is not yet mounted so we have no target URI.
|
||||
GErrorPtr err{G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED,
|
||||
QObject::tr("The path is not mounted.")};
|
||||
if(!showError(ctx, err, fileInfo->path(), fileInfo)) {
|
||||
// the user fail to handle the error, skip this file.
|
||||
continue;
|
||||
}
|
||||
|
||||
// we do not have the target path in the FileInfo object.
|
||||
// try to launch our path again to query the new file info later so we can get the mounted target URI.
|
||||
pathsToLaunch.emplace_back(fileInfo->path());
|
||||
}
|
||||
else {
|
||||
// we have the target path, launch it later
|
||||
pathsToLaunch.emplace_back(FilePath::fromPathStr(fileInfo->target().c_str()));
|
||||
}
|
||||
}
|
||||
else if(fileInfo->isDesktopEntry()) {
|
||||
// launch the desktop entry
|
||||
launchDesktopEntry(fileInfo, FilePathList{}, ctx);
|
||||
}
|
||||
else if(fileInfo->isExecutableType()) {
|
||||
// directly execute the file
|
||||
launchExecutable(fileInfo, ctx);
|
||||
}
|
||||
else if(fileInfo->isShortcut()) {
|
||||
// for shortcuts, launch their targets instead
|
||||
auto path = handleShortcut(fileInfo, ctx);
|
||||
if(path.isValid()) {
|
||||
pathsToLaunch.emplace_back(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto& mimeType = fileInfo->mimeType();
|
||||
mimeTypeToFiles[mimeType->name()].emplace_back(fileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// open folders
|
||||
if(!folderInfos.empty()) {
|
||||
GErrorPtr err;
|
||||
openFolder(ctx, folderInfos, err);
|
||||
}
|
||||
|
||||
// open files of different mime-types with their default app
|
||||
for(auto& typeFiles : mimeTypeToFiles) {
|
||||
auto& mimeType = typeFiles.first;
|
||||
auto& files = typeFiles.second;
|
||||
GErrorPtr err;
|
||||
GAppInfoPtr app{g_app_info_get_default_for_type(mimeType.c_str(), false), false};
|
||||
if(!app) {
|
||||
app = chooseApp(files, mimeType.c_str(), err);
|
||||
}
|
||||
if(app) {
|
||||
launchWithApp(app.get(), files.paths(), ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if(!pathsToLaunch.empty()) {
|
||||
launchPaths(pathsToLaunch, ctx);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::launchPaths(FilePathList paths, GAppLaunchContext* ctx) {
|
||||
// FIXME: blocking with an event loop is not a good design :-(
|
||||
QEventLoop eventLoop;
|
||||
|
||||
auto job = new FileInfoJob{paths};
|
||||
job->setAutoDelete(false); // do not automatically delete the job since we want its results later.
|
||||
|
||||
GObjectPtr<GAppLaunchContext> ctxPtr{ctx};
|
||||
QObject::connect(job, &FileInfoJob::finished,
|
||||
[&eventLoop]() {
|
||||
// exit the event loop when the job is done
|
||||
eventLoop.exit();
|
||||
});
|
||||
// run the job in another thread to not block the UI
|
||||
job->runAsync();
|
||||
|
||||
// blocking until the job is done with a event loop
|
||||
eventLoop.exec();
|
||||
|
||||
// launch the file info
|
||||
launchFiles(job->files(), ctx);
|
||||
|
||||
delete job;
|
||||
return false;
|
||||
}
|
||||
|
||||
GAppInfoPtr BasicFileLauncher::chooseApp(const FileInfoList& /* fileInfos */, const char* /*mimeType*/, GErrorPtr& /* err */) {
|
||||
return GAppInfoPtr{};
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::openFolder(GAppLaunchContext* ctx, const FileInfoList& folderInfos, GErrorPtr& err) {
|
||||
auto app = chooseApp(folderInfos, "inode/directory", err);
|
||||
if(app) {
|
||||
launchWithApp(app.get(), folderInfos.paths(), ctx);
|
||||
}
|
||||
else {
|
||||
showError(ctx, err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
BasicFileLauncher::ExecAction BasicFileLauncher::askExecFile(const FileInfoPtr & /* file */) {
|
||||
return ExecAction::DIRECT_EXEC;
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::showError(GAppLaunchContext* /* ctx */, GErrorPtr& /* err */, const FilePath& /* path */, const FileInfoPtr& /* info */) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int BasicFileLauncher::ask(const char* /* msg */, char* const* /* btn_labels */, int default_btn) {
|
||||
return default_btn;
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::launchWithApp(GAppInfo* app, const FilePathList& paths, GAppLaunchContext* ctx) {
|
||||
GList* uris = nullptr;
|
||||
for(auto& path : paths) {
|
||||
auto uri = path.uri();
|
||||
uris = g_list_prepend(uris, uri.release());
|
||||
}
|
||||
GErrorPtr err;
|
||||
bool ret = bool(g_app_info_launch_uris(app, uris, ctx, &err));
|
||||
g_list_foreach(uris, reinterpret_cast<GFunc>(g_free), nullptr);
|
||||
g_list_free(uris);
|
||||
if(!ret) {
|
||||
// FIXME: show error for all files
|
||||
showError(ctx, err, paths[0]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool BasicFileLauncher::launchDesktopEntry(const FileInfoPtr &fileInfo, const FilePathList &paths, GAppLaunchContext* ctx) {
|
||||
/* treat desktop entries as executables */
|
||||
auto target = fileInfo->target();
|
||||
CStrPtr filename;
|
||||
const char* desktopEntryName = nullptr;
|
||||
FilePathList shortcutTargetPaths;
|
||||
if(fileInfo->isExecutableType()) {
|
||||
auto act = quickExec_ ? ExecAction::DIRECT_EXEC : askExecFile(fileInfo);
|
||||
switch(act) {
|
||||
case ExecAction::EXEC_IN_TERMINAL:
|
||||
case ExecAction::DIRECT_EXEC: {
|
||||
if(fileInfo->isShortcut()) {
|
||||
auto path = handleShortcut(fileInfo, ctx);
|
||||
if(path.isValid()) {
|
||||
shortcutTargetPaths.emplace_back(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(target.empty()) {
|
||||
filename = fileInfo->path().localPath();
|
||||
}
|
||||
desktopEntryName = !target.empty() ? target.c_str() : filename.get();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExecAction::OPEN_WITH_DEFAULT_APP:
|
||||
return launchWithDefaultApp(fileInfo, ctx);
|
||||
case ExecAction::CANCEL:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/* make exception for desktop entries under menu */
|
||||
else if(fileInfo->isNative() /* an exception */ ||
|
||||
fileInfo->path().hasUriScheme("menu")) {
|
||||
if(target.empty()) {
|
||||
filename = fileInfo->path().localPath();
|
||||
}
|
||||
desktopEntryName = !target.empty() ? target.c_str() : filename.get();
|
||||
}
|
||||
|
||||
if(desktopEntryName) {
|
||||
return launchDesktopEntry(desktopEntryName, paths, ctx);
|
||||
}
|
||||
if(!shortcutTargetPaths.empty()) {
|
||||
launchPaths(shortcutTargetPaths, ctx);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::launchDesktopEntry(const char *desktopEntryName, const FilePathList &paths, GAppLaunchContext *ctx) {
|
||||
bool ret = false;
|
||||
GAppInfo* app;
|
||||
|
||||
/* Let GDesktopAppInfo try first. */
|
||||
if(g_path_is_absolute(desktopEntryName)) {
|
||||
app = G_APP_INFO(g_desktop_app_info_new_from_filename(desktopEntryName));
|
||||
}
|
||||
else {
|
||||
app = G_APP_INFO(g_desktop_app_info_new(desktopEntryName));
|
||||
}
|
||||
/* we handle Type=Link in FmFileInfo so if GIO failed then
|
||||
it cannot be launched in fact */
|
||||
|
||||
if(app) {
|
||||
return launchWithApp(app, paths, ctx);
|
||||
}
|
||||
else {
|
||||
QString msg = QObject::tr("Invalid desktop entry file: '%1'").arg(desktopEntryName);
|
||||
GErrorPtr err{G_IO_ERROR, G_IO_ERROR_FAILED, msg};
|
||||
showError(ctx, err);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
FilePath BasicFileLauncher::handleShortcut(const FileInfoPtr& fileInfo, GAppLaunchContext* ctx) {
|
||||
auto target = fileInfo->target();
|
||||
auto scheme = CStrPtr{g_uri_parse_scheme(target.c_str())};
|
||||
if(scheme) {
|
||||
// collect the uri schemes we support
|
||||
if(strcmp(scheme.get(), "file") == 0
|
||||
|| strcmp(scheme.get(), "trash") == 0
|
||||
|| strcmp(scheme.get(), "network") == 0
|
||||
|| strcmp(scheme.get(), "computer") == 0) {
|
||||
return FilePath::fromUri(fileInfo->target().c_str());
|
||||
}
|
||||
else {
|
||||
// ask gio to launch the default handler for the uri scheme
|
||||
GAppInfoPtr app{g_app_info_get_default_for_uri_scheme(scheme.get()), false};
|
||||
FilePathList uris{FilePath::fromUri(fileInfo->target().c_str())};
|
||||
launchWithApp(app.get(), uris, ctx);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// see it as a local path
|
||||
return FilePath::fromLocalPath(fileInfo->target().c_str());
|
||||
}
|
||||
return FilePath();
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::launchExecutable(const FileInfoPtr &fileInfo, GAppLaunchContext* ctx) {
|
||||
/* if it's an executable file, directly execute it. */
|
||||
auto filename = fileInfo->path().localPath();
|
||||
/* FIXME: we need to use eaccess/euidaccess here. */
|
||||
if(g_file_test(filename.get(), G_FILE_TEST_IS_EXECUTABLE)) {
|
||||
auto act = quickExec_ ? ExecAction::DIRECT_EXEC : askExecFile(fileInfo);
|
||||
int flags = G_APP_INFO_CREATE_NONE;
|
||||
switch(act) {
|
||||
case ExecAction::EXEC_IN_TERMINAL:
|
||||
flags |= G_APP_INFO_CREATE_NEEDS_TERMINAL;
|
||||
/* Falls through. */
|
||||
case ExecAction::DIRECT_EXEC: {
|
||||
/* filename may contain spaces. Fix #3143296 */
|
||||
CStrPtr quoted{g_shell_quote(filename.get())};
|
||||
// FIXME: remove libfm dependency
|
||||
GAppInfoPtr app{fm_app_info_create_from_commandline(quoted.get(), nullptr, GAppInfoCreateFlags(flags), nullptr)};
|
||||
if(app) {
|
||||
CStrPtr run_path{g_path_get_dirname(filename.get())};
|
||||
CStrPtr cwd;
|
||||
/* bug #3589641: scripts are ran from $HOME.
|
||||
since GIO launcher is kinda ugly - it has
|
||||
no means to set running directory so we
|
||||
do workaround - change directory to it */
|
||||
if(run_path && strcmp(run_path.get(), ".")) {
|
||||
cwd = CStrPtr{g_get_current_dir()};
|
||||
if(chdir(run_path.get()) != 0) {
|
||||
cwd.reset();
|
||||
// show errors
|
||||
QString msg = QObject::tr("Cannot set working directory to '%1': %2").arg(run_path.get()).arg(g_strerror(errno));
|
||||
GErrorPtr err{G_IO_ERROR, g_io_error_from_errno(errno), msg};
|
||||
showError(ctx, err);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: remove libfm dependency
|
||||
GErrorPtr err;
|
||||
if(!fm_app_info_launch(app.get(), nullptr, ctx, &err)) {
|
||||
showError(ctx, err);
|
||||
}
|
||||
if(cwd) { /* return back */
|
||||
if(chdir(cwd.get()) != 0) {
|
||||
g_warning("fm_launch_files(): chdir() failed");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExecAction::OPEN_WITH_DEFAULT_APP:
|
||||
return launchWithDefaultApp(fileInfo, ctx);
|
||||
case ExecAction::CANCEL:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BasicFileLauncher::launchWithDefaultApp(const FileInfoPtr &fileInfo, GAppLaunchContext* ctx) {
|
||||
FileInfoList files;
|
||||
files.emplace_back(fileInfo);
|
||||
GErrorPtr err;
|
||||
GAppInfoPtr app{g_app_info_get_default_for_type(fileInfo->mimeType()->name(), false), false};
|
||||
if(app) {
|
||||
return launchWithApp(app.get(), files.paths(), ctx);
|
||||
}
|
||||
else {
|
||||
showError(ctx, err, fileInfo->path());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,72 +0,0 @@
|
||||
#ifndef BASICFILELAUNCHER_H
|
||||
#define BASICFILELAUNCHER_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
|
||||
#include "fileinfo.h"
|
||||
#include "filepath.h"
|
||||
#include "mimetype.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API BasicFileLauncher {
|
||||
public:
|
||||
|
||||
enum class ExecAction {
|
||||
NONE,
|
||||
DIRECT_EXEC,
|
||||
EXEC_IN_TERMINAL,
|
||||
OPEN_WITH_DEFAULT_APP,
|
||||
CANCEL
|
||||
};
|
||||
|
||||
explicit BasicFileLauncher();
|
||||
virtual ~BasicFileLauncher();
|
||||
|
||||
bool launchFiles(const FileInfoList &fileInfos, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool launchPaths(FilePathList paths, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool launchDesktopEntry(const FileInfoPtr &fileInfo, const FilePathList& paths = FilePathList{}, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool launchDesktopEntry(const char* desktopEntryName, const FilePathList& paths = FilePathList{}, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool launchWithDefaultApp(const FileInfoPtr& fileInfo, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool launchWithApp(GAppInfo* app, const FilePathList& paths, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool launchExecutable(const FileInfoPtr &fileInfo, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
bool quickExec() const {
|
||||
return quickExec_;
|
||||
}
|
||||
|
||||
void setQuickExec(bool value) {
|
||||
quickExec_ = value;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
virtual GAppInfoPtr chooseApp(const FileInfoList& fileInfos, const char* mimeType, GErrorPtr& err);
|
||||
|
||||
virtual bool openFolder(GAppLaunchContext* ctx, const FileInfoList& folderInfos, GErrorPtr& err);
|
||||
|
||||
virtual bool showError(GAppLaunchContext* ctx, GErrorPtr& err, const FilePath& path = FilePath{}, const FileInfoPtr& info = FileInfoPtr{});
|
||||
|
||||
virtual ExecAction askExecFile(const FileInfoPtr& file);
|
||||
|
||||
virtual int ask(const char* msg, char* const* btn_labels, int default_btn);
|
||||
|
||||
private:
|
||||
|
||||
FilePath handleShortcut(const FileInfoPtr &fileInfo, GAppLaunchContext* ctx = nullptr);
|
||||
|
||||
private:
|
||||
bool quickExec_; // Don't ask options on launch executable file
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // BASICFILELAUNCHER_H
|
@ -1,217 +0,0 @@
|
||||
#include "bookmarks.h"
|
||||
#include "cstrptr.h"
|
||||
#include <algorithm>
|
||||
#include <QTimer>
|
||||
#include <QStandardPaths>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::weak_ptr<Bookmarks> 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)};
|
||||
}
|
||||
|
||||
BookmarkItem::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();
|
||||
}
|
||||
// We cannot rely on FileInfos to set bookmark icons because there is no guarantee
|
||||
// that FileInfos already exist, while their creation is costly. Therefore, we have
|
||||
// to get folder icons directly, as is done at `FileInfo::setFromGFileInfo` and more.
|
||||
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) {
|
||||
icon_ = IconInfo::fromName(icon_name.get());
|
||||
}
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
if(!icon_ || !icon_->isValid()) {
|
||||
// first check some standard folders that are shared by Qt and GLib
|
||||
if(path_ == FilePath::homeDir()) {
|
||||
icon_ = IconInfo::fromName("user-home");
|
||||
}
|
||||
else if (path_.parent() == FilePath::homeDir()) {
|
||||
QString folderPath = QString::fromUtf8(path_.toString().get());
|
||||
if(folderPath == QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)) {
|
||||
icon_ = IconInfo::fromName("user-desktop");
|
||||
}
|
||||
else if(folderPath == QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)) {
|
||||
icon_ = IconInfo::fromName("folder-documents");
|
||||
}
|
||||
else if(folderPath == QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)) {
|
||||
icon_ = IconInfo::fromName("folder-download");
|
||||
}
|
||||
else if(folderPath == QStandardPaths::writableLocation(QStandardPaths::MusicLocation)) {
|
||||
icon_ = IconInfo::fromName("folder-music");
|
||||
}
|
||||
else if(folderPath == QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)) {
|
||||
icon_ = IconInfo::fromName("folder-pictures");
|
||||
}
|
||||
else if(folderPath == QStandardPaths::writableLocation(QStandardPaths::MoviesLocation)) {
|
||||
icon_ = IconInfo::fromName("folder-videos");
|
||||
}
|
||||
}
|
||||
// fall back to the default folder icon
|
||||
if(!icon_ || !icon_->isValid()) {
|
||||
icon_ = IconInfo::fromName("folder");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<GFileMonitor>{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<const BookmarkItem>& Bookmarks::insert(const FilePath& path, const QString& name, int pos) {
|
||||
const auto insert_pos = (pos < 0 || static_cast<size_t>(pos) > items_.size()) ? items_.cend() : items_.cbegin() + pos;
|
||||
auto it = items_.insert(insert_pos, std::make_shared<const BookmarkItem>(path, name));
|
||||
queueSave();
|
||||
return *it;
|
||||
}
|
||||
|
||||
void Bookmarks::remove(const std::shared_ptr<const BookmarkItem>& item) {
|
||||
items_.erase(std::remove(items_.begin(), items_.end(), item), items_.end());
|
||||
queueSave();
|
||||
}
|
||||
|
||||
void Bookmarks::reorder(const std::shared_ptr<const BookmarkItem>& item, int pos) {
|
||||
auto old_it = std::find(items_.cbegin(), items_.cend(), item);
|
||||
if(old_it == items_.cend())
|
||||
return;
|
||||
std::shared_ptr<const BookmarkItem> 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<const BookmarkItem>& item, QString new_name) {
|
||||
auto it = std::find_if(items_.cbegin(), items_.cend(), [item](const std::shared_ptr<const BookmarkItem>& 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<const BookmarkItem>(item->path(), new_name));
|
||||
items_.erase(it + 1); // remove the old item
|
||||
queueSave();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Bookmarks> Bookmarks::globalInstance() {
|
||||
auto bookmarks = globalInstance_.lock();
|
||||
if(!bookmarks) {
|
||||
bookmarks = std::make_shared<Bookmarks>();
|
||||
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:
|
||||
// <URI> <name>\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<BookmarkItem>(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
|
@ -1,87 +0,0 @@
|
||||
#ifndef FM2_BOOKMARKS_H
|
||||
#define FM2_BOOKMARKS_H
|
||||
|
||||
#include <QObject>
|
||||
#include "gobjectptr.h"
|
||||
#include "filepath.h"
|
||||
#include "iconinfo.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API BookmarkItem {
|
||||
public:
|
||||
friend class Bookmarks;
|
||||
|
||||
explicit BookmarkItem(const FilePath& path, const QString name);
|
||||
|
||||
const QString& name() const {
|
||||
return name_;
|
||||
}
|
||||
|
||||
const FilePath& path() const {
|
||||
return path_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const IconInfo>& icon() const {
|
||||
return icon_;
|
||||
}
|
||||
|
||||
private:
|
||||
void setName(const QString& name) {
|
||||
name_ = name;
|
||||
}
|
||||
|
||||
private:
|
||||
FilePath path_;
|
||||
QString name_;
|
||||
std::shared_ptr<const IconInfo> icon_;
|
||||
};
|
||||
|
||||
|
||||
class LIBFM_QT_API Bookmarks : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Bookmarks(QObject* parent = 0);
|
||||
|
||||
~Bookmarks();
|
||||
|
||||
const std::shared_ptr<const BookmarkItem> &insert(const FilePath& path, const QString& name, int pos);
|
||||
|
||||
void remove(const std::shared_ptr<const BookmarkItem>& item);
|
||||
|
||||
void reorder(const std::shared_ptr<const BookmarkItem> &item, int pos);
|
||||
|
||||
void rename(const std::shared_ptr<const BookmarkItem>& item, QString new_name);
|
||||
|
||||
const std::vector<std::shared_ptr<const BookmarkItem>>& items() const {
|
||||
return items_;
|
||||
}
|
||||
|
||||
static std::shared_ptr<Bookmarks> 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<GFileMonitor> mon;
|
||||
std::vector<std::shared_ptr<const BookmarkItem>> items_;
|
||||
static std::weak_ptr<Bookmarks> globalInstance_;
|
||||
bool idle_handler;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_BOOKMARKS_H
|
@ -1,42 +0,0 @@
|
||||
#ifndef FM2_CSTRPTR_H
|
||||
#define FM2_CSTRPTR_H
|
||||
|
||||
#include <memory>
|
||||
#include <glib.h>
|
||||
|
||||
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<char[], CStrDeleter> 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<char*[], CStrVDeleter> CStrArrayPtr;
|
||||
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_CSTRPTR_H
|
@ -1,158 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: get parent dir of the current path.
|
||||
// if there is a Fm::Folder object created for it, block the update for the folder temporarily.
|
||||
|
||||
/* 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 isTrashRoot = false;
|
||||
// special handling for trash:///
|
||||
if(!path.isNative() && g_strcmp0(path.uriScheme().get(), "trash") == 0) {
|
||||
// little trick: basename of trash root is /
|
||||
auto basename = path.baseName();
|
||||
if(basename && basename[0] == G_DIR_SEPARATOR) {
|
||||
isTrashRoot = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasError = false;
|
||||
while(!isCancelled()) {
|
||||
GErrorPtr err;
|
||||
// try to delete the path directly (but don't delete if it's trash:///)
|
||||
if(isTrashRoot || 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) {
|
||||
GErrorPtr err;
|
||||
GFileEnumeratorPtr enu {
|
||||
g_file_enumerate_children(path.gfile().get(), defaultGFileInfoQueryAttribs,
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
DeleteJob::DeleteJob(const FilePathList &paths): paths_{paths} {
|
||||
setCalcProgressUsingSize(false);
|
||||
}
|
||||
|
||||
DeleteJob::DeleteJob(FilePathList &&paths): paths_{paths} {
|
||||
setCalcProgressUsingSize(false);
|
||||
}
|
||||
|
||||
DeleteJob::~DeleteJob() {
|
||||
}
|
||||
|
||||
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
|
@ -1,33 +0,0 @@
|
||||
#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);
|
||||
|
||||
explicit DeleteJob(FilePathList&& 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
|
@ -1,178 +0,0 @@
|
||||
#include "dirlistjob.h"
|
||||
#include <gio/gio.h>
|
||||
#include "fileinfo_p.h"
|
||||
#include "gioptrs.h"
|
||||
#include <QDebug>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
DirListJob::DirListJob(const FilePath& path, Flags _flags, const std::shared_ptr<const HashSet>& 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(), defaultGFileInfoQueryAttribs,
|
||||
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<std::mutex> lock{mutex_};
|
||||
dir_fi = std::make_shared<FileInfo>(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(), defaultGFileInfoQueryAttribs,
|
||||
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<FileInfo>(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<std::mutex> 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
|
@ -1,65 +0,0 @@
|
||||
#ifndef FM2_DIRLISTJOB_H
|
||||
#define FM2_DIRLISTJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <mutex>
|
||||
#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<const HashSet>& cutFilesHashSet = nullptr);
|
||||
|
||||
FileInfoList& files() {
|
||||
return files_;
|
||||
}
|
||||
|
||||
void setIncremental(bool set);
|
||||
|
||||
bool incremental() const {
|
||||
return emit_files_found;
|
||||
}
|
||||
|
||||
FilePath dirPath() const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
return dir_path;
|
||||
}
|
||||
|
||||
std::shared_ptr<const FileInfo> dirInfo() const {
|
||||
std::lock_guard<std::mutex> 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<const FileInfo> dir_fi;
|
||||
FileInfoList files_;
|
||||
const std::shared_ptr<const HashSet> cutFilesHashSet_;
|
||||
bool emit_files_found;
|
||||
// guint delay_add_files_handler;
|
||||
// GSList* files_to_add;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_DIRLISTJOB_H
|
@ -1,324 +0,0 @@
|
||||
#include "filechangeattrjob.h"
|
||||
#include "totalsizejob.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
static const char query[] = G_FILE_ATTRIBUTE_STANDARD_TYPE","
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME","
|
||||
G_FILE_ATTRIBUTE_UNIX_GID","
|
||||
G_FILE_ATTRIBUTE_UNIX_UID","
|
||||
G_FILE_ATTRIBUTE_UNIX_MODE","
|
||||
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME;
|
||||
|
||||
FileChangeAttrJob::FileChangeAttrJob(FilePathList paths):
|
||||
paths_{std::move(paths)},
|
||||
recursive_{false},
|
||||
// chmod
|
||||
fileModeEnabled_{false},
|
||||
newMode_{0},
|
||||
newModeMask_{0},
|
||||
// chown
|
||||
ownerEnabled_{false},
|
||||
uid_{0},
|
||||
groupEnabled_{false},
|
||||
gid_{0},
|
||||
// Display name
|
||||
displayNameEnabled_{false},
|
||||
// icon
|
||||
iconEnabled_{false},
|
||||
// hidden
|
||||
hiddenEnabled_{false},
|
||||
hidden_{false},
|
||||
// target uri
|
||||
targetUriEnabled_{false} {
|
||||
|
||||
// the progress of chmod/chown is not related to file size
|
||||
setCalcProgressUsingSize(false);
|
||||
}
|
||||
|
||||
void FileChangeAttrJob::exec() {
|
||||
// count total amount of the work
|
||||
if(recursive_) {
|
||||
TotalSizeJob totalSizeJob{paths_};
|
||||
connect(&totalSizeJob, &TotalSizeJob::error, this, &FileChangeAttrJob::error);
|
||||
connect(this, &FileChangeAttrJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel);
|
||||
totalSizeJob.run();
|
||||
std::uint64_t totalSize, totalCount;
|
||||
totalSizeJob.totalAmount(totalSize, totalCount);
|
||||
setTotalAmount(totalSize, totalCount);
|
||||
}
|
||||
else {
|
||||
setTotalAmount(paths_.size(), paths_.size());
|
||||
}
|
||||
|
||||
// ready to start
|
||||
Q_EMIT preparedToRun();
|
||||
|
||||
// do the actual change attrs job
|
||||
for(auto& path : paths_) {
|
||||
if(isCancelled()) {
|
||||
break;
|
||||
}
|
||||
GErrorPtr err;
|
||||
GFileInfoPtr info{
|
||||
g_file_query_info(path.gfile().get(), query,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(info) {
|
||||
processFile(path, info);
|
||||
}
|
||||
else {
|
||||
handleError(err, path, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::processFile(const FilePath& path, const GFileInfoPtr& info) {
|
||||
setCurrentFile(path);
|
||||
bool ret = true;
|
||||
|
||||
if(ownerEnabled_) {
|
||||
changeFileOwner(path, info, uid_);
|
||||
}
|
||||
if(groupEnabled_) {
|
||||
changeFileGroup(path, info, gid_);
|
||||
}
|
||||
if(fileModeEnabled_) {
|
||||
changeFileMode(path, info, newMode_, newModeMask_);
|
||||
}
|
||||
/* change display name, icon, hidden, target */
|
||||
if(displayNameEnabled_ && !displayName().empty()) {
|
||||
changeFileDisplayName(path, info, displayName_.c_str());
|
||||
}
|
||||
if(iconEnabled_ && icon_) {
|
||||
changeFileIcon(path, info, icon_);
|
||||
}
|
||||
if(hiddenEnabled_) {
|
||||
changeFileHidden(path, info, hidden_);
|
||||
}
|
||||
if(targetUriEnabled_ && !targetUri_.empty()) {
|
||||
changeFileTargetUri(path, info, targetUri_.c_str());
|
||||
}
|
||||
|
||||
// FIXME: do not use size 1 here.
|
||||
addFinishedAmount(1, 1);
|
||||
|
||||
// recursively apply to subfolders
|
||||
auto type = g_file_info_get_file_type(info.get());
|
||||
if(!isCancelled() && recursive_ && type == G_FILE_TYPE_DIRECTORY) {
|
||||
bool retry;
|
||||
do {
|
||||
retry = false;
|
||||
GErrorPtr err;
|
||||
GFileEnumeratorPtr enu{
|
||||
g_file_enumerate_children(path.gfile().get(), query,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(enu) {
|
||||
while(!isCancelled()) {
|
||||
err.reset();
|
||||
GFileInfoPtr childInfo{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
|
||||
if(childInfo) {
|
||||
auto childPath = path.child(g_file_info_get_name(childInfo.get()));
|
||||
ret = processFile(childPath, childInfo);
|
||||
if(!ret) { /* _fm_file_ops_job_change_attr_file() failed */
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(err) {
|
||||
handleError(err, path, info, ErrorSeverity::MILD);
|
||||
retry = false;
|
||||
/* FM_JOB_RETRY is not supported here */
|
||||
}
|
||||
else { /* EOF */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_file_enumerator_close(enu.get(), cancellable().get(), nullptr);
|
||||
}
|
||||
else {
|
||||
retry = handleError(err, path, info);
|
||||
}
|
||||
} while(!isCancelled() && retry);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::handleError(GErrorPtr &err, const FilePath &path, const GFileInfoPtr &info, ErrorSeverity severity) {
|
||||
auto act = emitError(err, severity);
|
||||
if (act == ErrorAction::RETRY) {
|
||||
err.reset();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileOwner(const FilePath& path, const GFileInfoPtr& info, uid_t uid) {
|
||||
/* change owner */
|
||||
bool ret = false;
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
if(!g_file_set_attribute_uint32(path.gfile().get(), G_FILE_ATTRIBUTE_UNIX_UID,
|
||||
uid, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileGroup(const FilePath& path, const GFileInfoPtr& info, gid_t gid) {
|
||||
/* change group */
|
||||
bool ret = false;
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
if(!g_file_set_attribute_uint32(path.gfile().get(), G_FILE_ATTRIBUTE_UNIX_GID,
|
||||
gid, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileMode(const FilePath& path, const GFileInfoPtr& info, mode_t newMode, mode_t newModeMask) {
|
||||
bool ret = false;
|
||||
/* change mode */
|
||||
if(newModeMask) {
|
||||
guint32 mode = g_file_info_get_attribute_uint32(info.get(), G_FILE_ATTRIBUTE_UNIX_MODE);
|
||||
mode &= ~newModeMask;
|
||||
mode |= (newMode & newModeMask);
|
||||
|
||||
auto type = g_file_info_get_file_type(info.get());
|
||||
/* FIXME: this behavior should be optional. */
|
||||
/* treat dirs with 'r' as 'rx' */
|
||||
if(type == G_FILE_TYPE_DIRECTORY) {
|
||||
if((newModeMask & S_IRUSR) && (mode & S_IRUSR)) {
|
||||
mode |= S_IXUSR;
|
||||
}
|
||||
if((newModeMask & S_IRGRP) && (mode & S_IRGRP)) {
|
||||
mode |= S_IXGRP;
|
||||
}
|
||||
if((newModeMask & S_IROTH) && (mode & S_IROTH)) {
|
||||
mode |= S_IXOTH;
|
||||
}
|
||||
}
|
||||
|
||||
/* new mode */
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
if(!g_file_set_attribute_uint32(path.gfile().get(), G_FILE_ATTRIBUTE_UNIX_MODE,
|
||||
mode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileDisplayName(const FilePath& path, const GFileInfoPtr& info, const char* displayName) {
|
||||
bool ret = false;
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
if(!g_file_set_display_name(path.gfile().get(), displayName, cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileIcon(const FilePath& path, const GFileInfoPtr& info, GIconPtr& icon) {
|
||||
bool ret = false;
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
if(!g_file_set_attribute(path.gfile().get(), G_FILE_ATTRIBUTE_STANDARD_ICON,
|
||||
G_FILE_ATTRIBUTE_TYPE_OBJECT, icon.get(),
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileHidden(const FilePath& path, const GFileInfoPtr& info, bool hidden) {
|
||||
bool ret = false;
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
gboolean g_hidden = hidden;
|
||||
if(!g_file_set_attribute(path.gfile().get(), G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
|
||||
G_FILE_ATTRIBUTE_TYPE_BOOLEAN, &g_hidden,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileChangeAttrJob::changeFileTargetUri(const FilePath& path, const GFileInfoPtr& info, const char* targetUri) {
|
||||
bool ret = false;
|
||||
bool retry;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
if(!g_file_set_attribute_string(path.gfile().get(), G_FILE_ATTRIBUTE_STANDARD_TARGET_URI,
|
||||
targetUri, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err)) {
|
||||
retry = handleError(err, path, info, ErrorSeverity::MILD);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
retry = false;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,145 +0,0 @@
|
||||
#ifndef FM2_FILECHANGEATTRJOB_H
|
||||
#define FM2_FILECHANGEATTRJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "fileoperationjob.h"
|
||||
#include "gioptrs.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FileChangeAttrJob : public Fm::FileOperationJob {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FileChangeAttrJob(FilePathList paths);
|
||||
|
||||
void setFileModeEnabled(bool enabled) {
|
||||
fileModeEnabled_ = enabled;
|
||||
}
|
||||
void setFileMode(mode_t newMode, mode_t newModeMask) {
|
||||
newMode_ = newMode;
|
||||
newModeMask_ = newModeMask;
|
||||
}
|
||||
|
||||
bool ownerEnabled() const {
|
||||
return ownerEnabled_;
|
||||
}
|
||||
void setOwnerEnabled(bool enabled) {
|
||||
ownerEnabled_ = enabled;
|
||||
}
|
||||
void setOwner(uid_t uid) {
|
||||
uid_ = uid;
|
||||
}
|
||||
|
||||
bool groupEnabled() const {
|
||||
return groupEnabled_;
|
||||
}
|
||||
void setGroupEnabled(bool groupEnabled) {
|
||||
groupEnabled_ = groupEnabled;
|
||||
}
|
||||
void setGroup(gid_t gid) {
|
||||
gid_ = gid;
|
||||
}
|
||||
|
||||
// This only work for change attr jobs.
|
||||
void setRecursive(bool recursive) {
|
||||
recursive_ = recursive;
|
||||
}
|
||||
|
||||
void setHiddenEnabled(bool enabled) {
|
||||
hiddenEnabled_ = enabled;
|
||||
}
|
||||
void setHidden(bool hidden) {
|
||||
hidden_ = hidden;
|
||||
}
|
||||
|
||||
bool iconEnabled() const {
|
||||
return iconEnabled_;
|
||||
}
|
||||
void setIconEnabled(bool iconEnabled) {
|
||||
iconEnabled_ = iconEnabled;
|
||||
}
|
||||
|
||||
bool displayNameEnabled() const {
|
||||
return displayNameEnabled_;
|
||||
}
|
||||
void setDisplayNameEnabled(bool displayNameEnabled) {
|
||||
displayNameEnabled_ = displayNameEnabled;
|
||||
}
|
||||
|
||||
const std::string& displayName() const {
|
||||
return displayName_;
|
||||
}
|
||||
void setDisplayName(const std::string& displayName) {
|
||||
displayName_ = displayName;
|
||||
}
|
||||
|
||||
bool targetUriEnabled() const {
|
||||
return targetUriEnabled_;
|
||||
}
|
||||
void setTargetUriEnabled(bool targetUriEnabled) {
|
||||
targetUriEnabled_ = targetUriEnabled;
|
||||
}
|
||||
|
||||
const std::string& targetUri() const {
|
||||
return targetUri_;
|
||||
}
|
||||
void setTargetUri(const std::string& value) {
|
||||
targetUri_ = value;
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
bool processFile(const FilePath& path, const GFileInfoPtr& info);
|
||||
bool handleError(GErrorPtr& err, const FilePath& path, const GFileInfoPtr& info, ErrorSeverity severity = ErrorSeverity::MODERATE);
|
||||
|
||||
bool changeFileOwner(const FilePath& path, const GFileInfoPtr& info, uid_t uid);
|
||||
bool changeFileGroup(const FilePath& path, const GFileInfoPtr& info, gid_t gid);
|
||||
bool changeFileMode(const FilePath& path, const GFileInfoPtr& info, mode_t newMode, mode_t newModeMask);
|
||||
bool changeFileDisplayName(const FilePath& path, const GFileInfoPtr& info, const char* displayName);
|
||||
bool changeFileIcon(const FilePath& path, const GFileInfoPtr& info, GIconPtr& icon);
|
||||
bool changeFileHidden(const FilePath& path, const GFileInfoPtr& info, bool hidden);
|
||||
bool changeFileTargetUri(const FilePath& path, const GFileInfoPtr& info, const char* targetUri_);
|
||||
|
||||
private:
|
||||
FilePathList paths_;
|
||||
bool recursive_;
|
||||
|
||||
// chmod
|
||||
bool fileModeEnabled_;
|
||||
mode_t newMode_;
|
||||
mode_t newModeMask_;
|
||||
|
||||
// chown
|
||||
bool ownerEnabled_;
|
||||
uid_t uid_;
|
||||
|
||||
bool groupEnabled_;
|
||||
gid_t gid_;
|
||||
|
||||
// Display name
|
||||
bool displayNameEnabled_;
|
||||
std::string displayName_;
|
||||
|
||||
// icon
|
||||
bool iconEnabled_;
|
||||
GIconPtr icon_;
|
||||
|
||||
// hidden
|
||||
bool hiddenEnabled_;
|
||||
bool hidden_;
|
||||
|
||||
// target uri
|
||||
bool targetUriEnabled_;
|
||||
std::string targetUri_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_FILECHANGEATTRJOB_H
|
@ -1,409 +0,0 @@
|
||||
#include "fileinfo.h"
|
||||
#include "fileinfo_p.h"
|
||||
#include <gio/gio.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
const char defaultGFileInfoQueryAttribs[] = "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<GFileInfo>& inf, const FilePath& parentDirPath) {
|
||||
dirPath_ = parentDirPath;
|
||||
const char* tmp, *uri;
|
||||
GIcon* gicon;
|
||||
GFileType type;
|
||||
|
||||
if (const char * name = g_file_info_get_name(inf.get()))
|
||||
name_ = name;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
isShortcut_ = false;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
case G_FILE_TYPE_SHORTCUT:
|
||||
isShortcut_ = true;
|
||||
/* Falls through. */
|
||||
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());
|
||||
}
|
||||
}
|
||||
/* Falls through. */
|
||||
/* 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());
|
||||
// g_file_info_get_is_backup() does not cover ".bak" and ".old".
|
||||
// NOTE: Here, dispName_ is not modified for desktop entries yet.
|
||||
isBackup_ = g_file_info_get_is_backup(inf.get())
|
||||
|| dispName_.endsWith(QLatin1String(".bak"))
|
||||
|| dispName_.endsWith(QLatin1String(".old"));
|
||||
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<const HashSet>& 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(isDesktopEntry()) {
|
||||
/* treat desktop entries as executables if
|
||||
they are native and have read permission */
|
||||
if(isNative() && (mode_ & (S_IRUSR|S_IRGRP|S_IROTH))) {
|
||||
if(isShortcut() && !target_.empty()) {
|
||||
/* handle shortcuts from desktop to menu entries:
|
||||
first check for entries in /usr/share/applications and such
|
||||
which may be considered as a safe desktop entry path
|
||||
then check if that is a shortcut to a native file
|
||||
otherwise it is a link to a file under menu:// */
|
||||
if (!g_str_has_prefix(target_.c_str(), "/usr/share/")) {
|
||||
auto target = FilePath::fromPathStr(target_.c_str());
|
||||
bool is_native = target.isNative();
|
||||
if (is_native) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else 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
|
@ -1,283 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 <libfm/fm.h>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include "../libfmqtglobals.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <string>
|
||||
#include <forward_list>
|
||||
|
||||
#include "gioptrs.h"
|
||||
#include "filepath.h"
|
||||
#include "iconinfo.h"
|
||||
#include "mimetype.h"
|
||||
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class FileInfoList;
|
||||
typedef std::set<unsigned int> 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<const IconInfo>& icon() const {
|
||||
return icon_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const MimeType>& mimeType() const {
|
||||
return mimeType_;
|
||||
}
|
||||
|
||||
quint64 ctime() const {
|
||||
return ctime_;
|
||||
}
|
||||
|
||||
|
||||
quint64 atime() const {
|
||||
return atime_;
|
||||
}
|
||||
|
||||
quint64 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 S_ISDIR(mode_) || 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_;
|
||||
}
|
||||
|
||||
QString description() const {
|
||||
return QString::fromUtf8(mimeType_ ? mimeType_->desc() : "");
|
||||
}
|
||||
|
||||
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<const HashSet>& cutFilesHashSet);
|
||||
|
||||
const std::forward_list<std::shared_ptr<const IconInfo>>& 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_;
|
||||
quint64 mtime_;
|
||||
quint64 atime_;
|
||||
quint64 ctime_;
|
||||
|
||||
uint64_t blksize_;
|
||||
uint64_t blocks_;
|
||||
|
||||
std::shared_ptr<const MimeType> mimeType_;
|
||||
std::shared_ptr<const IconInfo> icon_;
|
||||
std::forward_list<std::shared_ptr<const IconInfo>> 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<const HashSet> cutFilesHashSet_;
|
||||
// std::vector<std::tuple<int, void*, void(void*)>> extraData_;
|
||||
};
|
||||
|
||||
|
||||
class LIBFM_QT_API FileInfoList: public std::vector<std::shared_ptr<const FileInfo>> {
|
||||
public:
|
||||
|
||||
bool isSameType() const;
|
||||
|
||||
bool isSameFilesystem() const;
|
||||
|
||||
FilePathList paths() const {
|
||||
FilePathList ret;
|
||||
for(auto& file: *this) {
|
||||
ret.push_back(file->path());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
// smart pointer to FileInfo objects (once created, FileInfo objects should stay immutable so const is needed here)
|
||||
typedef std::shared_ptr<const FileInfo> FileInfoPtr;
|
||||
|
||||
typedef std::pair<FileInfoPtr, FileInfoPtr> FileInfoPair;
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<const Fm::FileInfo>)
|
||||
|
||||
|
||||
#endif // __LIBFM_QT_FM2_FILE_INFO_H__
|
@ -1,10 +0,0 @@
|
||||
#ifndef FILEINFO_P_H
|
||||
#define FILEINFO_P_H
|
||||
|
||||
namespace Fm {
|
||||
|
||||
extern const char defaultGFileInfoQueryAttribs[];
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FILEINFO_P_H
|
@ -1,44 +0,0 @@
|
||||
#include "fileinfojob.h"
|
||||
#include "fileinfo_p.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileInfoJob::FileInfoJob(FilePathList paths, FilePathList deletionPaths, FilePath commonDirPath, const std::shared_ptr<const HashSet>& cutFilesHashSet):
|
||||
Job(),
|
||||
paths_{std::move(paths)},
|
||||
deletionPaths_{std::move(deletionPaths)},
|
||||
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(), defaultGFileInfoQueryAttribs,
|
||||
G_FILE_QUERY_INFO_NONE, cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(!inf) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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<const FileInfo>(fileInfo);
|
||||
|
||||
results_.push_back(fileInfoPtr);
|
||||
Q_EMIT gotInfo(path, fileInfoPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,46 +0,0 @@
|
||||
#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, FilePathList deletionPaths = FilePathList(), FilePath commonDirPath = FilePath(), const std::shared_ptr<const HashSet>& cutFilesHashSet = nullptr);
|
||||
|
||||
const FilePathList& paths() const {
|
||||
return paths_;
|
||||
}
|
||||
|
||||
const FilePathList& deletionPaths() const {
|
||||
return deletionPaths_;
|
||||
}
|
||||
|
||||
const FileInfoList& files() const {
|
||||
return results_;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void gotInfo(const FilePath& path, std::shared_ptr<const FileInfo>& info);
|
||||
|
||||
protected:
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
FilePathList paths_;
|
||||
FilePathList deletionPaths_;
|
||||
FileInfoList results_;
|
||||
FilePath commonDirPath_;
|
||||
const std::shared_ptr<const HashSet> cutFilesHashSet_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_FILEINFOJOB_H
|
@ -1,9 +0,0 @@
|
||||
#include "filelinkjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileLinkJob::FileLinkJob() {
|
||||
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,16 +0,0 @@
|
||||
#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
|
@ -1,9 +0,0 @@
|
||||
#include "filemonitor.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileMonitor::FileMonitor() {
|
||||
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,26 +0,0 @@
|
||||
#ifndef FM2_FILEMONITOR_H
|
||||
#define FM2_FILEMONITOR_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <QObject>
|
||||
#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
|
@ -1,102 +0,0 @@
|
||||
#include "fileoperationjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileOperationJob::FileOperationJob():
|
||||
hasTotalAmount_{false},
|
||||
calcProgressUsingSize_{true},
|
||||
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<std::mutex> 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<std::mutex> lock{mutex_};
|
||||
if(currentFile_.isValid()) {
|
||||
path = currentFile_;
|
||||
totalSize = currentFileSize_;
|
||||
finishedSize = currentFileFinished_;
|
||||
}
|
||||
return currentFile_.isValid();
|
||||
}
|
||||
|
||||
double FileOperationJob::progress() const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
double finishedRatio;
|
||||
if(calcProgressUsingSize_) {
|
||||
finishedRatio = totalSize_ > 0 ? double(finishedSize_ + currentFileFinished_) / totalSize_ : 0.0;
|
||||
}
|
||||
else {
|
||||
finishedRatio = totalCount_ > 0 ? double(finishedCount_) / totalCount_ : 0.0;
|
||||
}
|
||||
|
||||
if(finishedRatio > 1.0) {
|
||||
finishedRatio = 1.0;
|
||||
}
|
||||
return finishedRatio;
|
||||
}
|
||||
|
||||
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<std::mutex> lock{mutex_};
|
||||
if(hasTotalAmount_) {
|
||||
finishedSize = finishedSize_;
|
||||
finishedCount = finishedCount_;
|
||||
}
|
||||
return hasTotalAmount_;
|
||||
}
|
||||
|
||||
void FileOperationJob::setTotalAmount(uint64_t fileSize, uint64_t fileCount) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
hasTotalAmount_ = true;
|
||||
totalSize_ = fileSize;
|
||||
totalCount_ = fileCount;
|
||||
}
|
||||
|
||||
void FileOperationJob::setFinishedAmount(uint64_t finishedSize, uint64_t finishedCount) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
finishedSize_ = finishedSize;
|
||||
finishedCount_ = finishedCount;
|
||||
}
|
||||
|
||||
void FileOperationJob::addFinishedAmount(uint64_t finishedSize, uint64_t finishedCount) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
finishedSize_ += finishedSize;
|
||||
finishedCount_ += finishedCount;
|
||||
}
|
||||
|
||||
FilePath FileOperationJob::currentFile() const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
auto ret = currentFile_;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void FileOperationJob::setCurrentFile(const FilePath& path) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
currentFile_ = path;
|
||||
}
|
||||
|
||||
void FileOperationJob::setCurrentFileProgress(uint64_t totalSize, uint64_t finishedSize) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
currentFileSize_ = totalSize;
|
||||
currentFileFinished_ = finishedSize;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,96 +0,0 @@
|
||||
#ifndef FM2_FILEOPERATIONJOB_H
|
||||
#define FM2_FILEOPERATIONJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "job.h"
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <cstdint>
|
||||
#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();
|
||||
|
||||
// get total amount of work to do
|
||||
bool totalAmount(std::uint64_t& fileSize, std::uint64_t& fileCount) const;
|
||||
|
||||
// get currently finished job amount
|
||||
bool finishedAmount(std::uint64_t& finishedSize, std::uint64_t& finishedCount) const;
|
||||
|
||||
// get the current file
|
||||
FilePath currentFile() const;
|
||||
|
||||
// get progress of the current file
|
||||
bool currentFileProgress(FilePath& path, std::uint64_t& totalSize, std::uint64_t& finishedSize) const;
|
||||
|
||||
// is the job calculate progress based on file size or file counts
|
||||
bool calcProgressUsingSize() const {
|
||||
return calcProgressUsingSize_;
|
||||
}
|
||||
|
||||
// get currently finished amount (0.0 to 1.0)
|
||||
virtual double progress() 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);
|
||||
|
||||
void setCalcProgressUsingSize(bool value) {
|
||||
calcProgressUsingSize_ = value;
|
||||
}
|
||||
|
||||
std::mutex& mutex() {
|
||||
return mutex_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool hasTotalAmount_;
|
||||
bool calcProgressUsingSize_;
|
||||
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
|
@ -1,21 +0,0 @@
|
||||
#include "filepath.h"
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
#include <glib.h>
|
||||
|
||||
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
|
@ -1,177 +0,0 @@
|
||||
#ifndef FM2_FILEPATH_H
|
||||
#define FM2_FILEPATH_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "gobjectptr.h"
|
||||
#include "cstrptr.h"
|
||||
#include <gio/gio.h>
|
||||
#include <vector>
|
||||
#include <QMetaType>
|
||||
|
||||
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>& 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> gfile_;
|
||||
static FilePath homeDir_;
|
||||
};
|
||||
|
||||
struct FilePathHash {
|
||||
std::size_t operator() (const FilePath& path) const {
|
||||
return path.hash();
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::vector<FilePath> FilePathList;
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
Q_DECLARE_METATYPE(Fm::FilePath)
|
||||
|
||||
#endif // FM2_FILEPATH_H
|
@ -1,24 +0,0 @@
|
||||
#include "filesysteminfojob.h"
|
||||
#include "gobjectptr.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
void FileSystemInfoJob::exec() {
|
||||
GObjectPtr<GFileInfo> inf = GObjectPtr<GFileInfo>{
|
||||
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
|
@ -1,45 +0,0 @@
|
||||
#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
|
@ -1,641 +0,0 @@
|
||||
#include "filetransferjob.h"
|
||||
#include "totalsizejob.h"
|
||||
#include "fileinfo_p.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
FileTransferJob::FileTransferJob(FilePathList srcPaths, Mode mode):
|
||||
FileOperationJob{},
|
||||
srcPaths_{std::move(srcPaths)},
|
||||
mode_{mode} {
|
||||
}
|
||||
|
||||
FileTransferJob::FileTransferJob(FilePathList srcPaths, FilePathList destPaths, Mode mode):
|
||||
FileTransferJob{std::move(srcPaths), mode} {
|
||||
destPaths_ = std::move(destPaths);
|
||||
}
|
||||
|
||||
FileTransferJob::FileTransferJob(FilePathList srcPaths, const FilePath& destDirPath, Mode mode):
|
||||
FileTransferJob{std::move(srcPaths), mode} {
|
||||
setDestDirPath(destDirPath);
|
||||
}
|
||||
|
||||
void FileTransferJob::setSrcPaths(FilePathList srcPaths) {
|
||||
srcPaths_ = std::move(srcPaths);
|
||||
}
|
||||
|
||||
void FileTransferJob::setDestPaths(FilePathList destPaths) {
|
||||
destPaths_ = std::move(destPaths);
|
||||
}
|
||||
|
||||
void FileTransferJob::setDestDirPath(const FilePath& destDirPath) {
|
||||
destPaths_.clear();
|
||||
destPaths_.reserve(srcPaths_.size());
|
||||
for(const auto& srcPath: srcPaths_) {
|
||||
FilePath destPath;
|
||||
if(mode_ == Mode::LINK && !srcPath.isNative()) {
|
||||
// special handling for URLs
|
||||
auto fullBasename = srcPath.baseName();
|
||||
char* basename = fullBasename.get();
|
||||
char* dname = nullptr;
|
||||
// if we drop URI query onto native filesystem, omit query part
|
||||
if(!srcPath.isNative()) {
|
||||
dname = strchr(basename, '?');
|
||||
}
|
||||
// if basename consist only from query then use first part of it
|
||||
if(dname == basename) {
|
||||
basename++;
|
||||
dname = strchr(basename, '&');
|
||||
}
|
||||
|
||||
CStrPtr _basename;
|
||||
if(dname) {
|
||||
_basename = CStrPtr{g_strndup(basename, dname - basename)};
|
||||
dname = strrchr(_basename.get(), G_DIR_SEPARATOR);
|
||||
g_debug("cutting '%s' to '%s'", basename, dname ? &dname[1] : _basename.get());
|
||||
if(dname) {
|
||||
basename = &dname[1];
|
||||
}
|
||||
else {
|
||||
basename = _basename.get();
|
||||
}
|
||||
}
|
||||
destPath = destDirPath.child(basename);
|
||||
}
|
||||
else {
|
||||
destPath = destDirPath.child(srcPath.baseName().get());
|
||||
}
|
||||
destPaths_.emplace_back(std::move(destPath));
|
||||
}
|
||||
}
|
||||
|
||||
void FileTransferJob::gfileCopyProgressCallback(goffset current_num_bytes, goffset total_num_bytes, FileTransferJob* _this) {
|
||||
_this->setCurrentFileProgress(total_num_bytes, current_num_bytes);
|
||||
}
|
||||
|
||||
bool FileTransferJob::moveFileSameFs(const FilePath& srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath) {
|
||||
int flags = G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS;
|
||||
GErrorPtr err;
|
||||
bool retry;
|
||||
do {
|
||||
retry = false;
|
||||
err.reset();
|
||||
// do the file operation
|
||||
if(!g_file_move(srcPath.gfile().get(), destPath.gfile().get(), GFileCopyFlags(flags), cancellable().get(),
|
||||
nullptr, this, &err)) {
|
||||
retry = handleError(err, srcPath, srcInfo, destPath, flags);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileTransferJob::copyRegularFile(const FilePath& srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath) {
|
||||
int flags = G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS;
|
||||
GErrorPtr err;
|
||||
bool retry;
|
||||
do {
|
||||
retry = false;
|
||||
err.reset();
|
||||
|
||||
// reset progress of the current file (only for copy)
|
||||
auto size = g_file_info_get_size(srcInfo.get());
|
||||
setCurrentFileProgress(size, 0);
|
||||
|
||||
// do the file operation
|
||||
if(!g_file_copy(srcPath.gfile().get(), destPath.gfile().get(), GFileCopyFlags(flags), cancellable().get(),
|
||||
(GFileProgressCallback)&gfileCopyProgressCallback, this, &err)) {
|
||||
retry = handleError(err, srcPath, srcInfo, destPath, flags);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
} while(retry && !isCancelled());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FileTransferJob::copySpecialFile(const FilePath& srcPath, const GFileInfoPtr& srcInfo, FilePath &destPath) {
|
||||
bool ret = false;
|
||||
// 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) {
|
||||
GErrorPtr err;
|
||||
g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
("Cannot copy file '%s': not supported"),
|
||||
g_file_info_get_display_name(srcInfo.get()));
|
||||
emitError(err, ErrorSeverity::MODERATE);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileTransferJob::copyDirContent(const FilePath& srcPath, GFileInfoPtr srcInfo, FilePath& destPath, bool skip) {
|
||||
bool ret = false;
|
||||
// copy dir content
|
||||
GErrorPtr err;
|
||||
auto enu = GFileEnumeratorPtr{
|
||||
g_file_enumerate_children(srcPath.gfile().get(),
|
||||
defaultGFileInfoQueryAttribs,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false};
|
||||
if(enu) {
|
||||
int n_children = 0;
|
||||
int n_copied = 0;
|
||||
ret = true;
|
||||
while(!isCancelled()) {
|
||||
err.reset();
|
||||
GFileInfoPtr inf{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false};
|
||||
if(inf) {
|
||||
++n_children;
|
||||
const char* name = g_file_info_get_name(inf.get());
|
||||
FilePath childPath = srcPath.child(name);
|
||||
bool child_ret = copyFile(childPath, inf, destPath, name, skip);
|
||||
if(child_ret) {
|
||||
++n_copied;
|
||||
}
|
||||
else {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(err) {
|
||||
// fail to read directory content
|
||||
// NOTE: since we cannot read the source dir, we cannot calculate the progress correctly, either.
|
||||
emitError(err, ErrorSeverity::MODERATE);
|
||||
err.reset();
|
||||
/* 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) {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
/* else job->skip_dir_content is true */
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
g_file_enumerator_close(enu.get(), nullptr, &err);
|
||||
}
|
||||
else {
|
||||
if(err) {
|
||||
emitError(err, ErrorSeverity::MODERATE);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileTransferJob::makeDir(const FilePath& srcPath, GFileInfoPtr srcInfo, FilePath& destPath) {
|
||||
if(isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mkdir_done = false;
|
||||
do {
|
||||
GErrorPtr err;
|
||||
mkdir_done = g_file_make_directory_with_parents(destPath.gfile().get(), cancellable().get(), &err);
|
||||
if(!mkdir_done) {
|
||||
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 destInfo = GFileInfoPtr {
|
||||
g_file_query_info(destPath.gfile().get(),
|
||||
defaultGFileInfoQueryAttribs,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), nullptr),
|
||||
false
|
||||
};
|
||||
if(!destInfo) {
|
||||
// FIXME: error handling
|
||||
break;
|
||||
}
|
||||
|
||||
FilePath newDestPath;
|
||||
FileExistsAction opt = askRename(FileInfo{srcInfo, srcPath.parent()}, FileInfo{destInfo, destPath.parent()}, newDestPath);
|
||||
switch(opt) {
|
||||
case FileOperationJob::RENAME:
|
||||
destPath = std::move(newDestPath);
|
||||
break;
|
||||
case FileOperationJob::SKIP:
|
||||
/* when a dir is skipped, we need to know its total size to calculate correct progress */
|
||||
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();
|
||||
return false;
|
||||
case FileOperationJob::SKIP_ERROR: ; /* FIXME */
|
||||
}
|
||||
}
|
||||
else {
|
||||
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
|
||||
if(act != ErrorAction::RETRY) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(!mkdir_done && !isCancelled());
|
||||
|
||||
bool chmod_done = false;
|
||||
if(mkdir_done && !isCancelled()) {
|
||||
mode_t mode = g_file_info_get_attribute_uint32(srcInfo.get(), G_FILE_ATTRIBUTE_UNIX_MODE);
|
||||
if(mode) {
|
||||
mode |= (S_IRUSR | S_IWUSR); /* ensure we have rw permission to this file. */
|
||||
do {
|
||||
GErrorPtr err;
|
||||
// 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);
|
||||
if(act != ErrorAction::RETRY) {
|
||||
break;
|
||||
}
|
||||
/* FIXME: some filesystems may not support this. */
|
||||
}
|
||||
} while(!chmod_done && !isCancelled());
|
||||
}
|
||||
}
|
||||
return mkdir_done && chmod_done;
|
||||
}
|
||||
|
||||
bool FileTransferJob::handleError(GErrorPtr &err, const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath, int& flags) {
|
||||
bool retry = false;
|
||||
/* 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)) {
|
||||
flags &= ~G_FILE_COPY_OVERWRITE;
|
||||
|
||||
// get info of the existing file
|
||||
GFileInfoPtr destInfo = GFileInfoPtr {
|
||||
g_file_query_info(destPath.gfile().get(),
|
||||
defaultGFileInfoQueryAttribs,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), nullptr),
|
||||
false
|
||||
};
|
||||
|
||||
// ask the user to rename or overwrite the existing file
|
||||
if(!isCancelled() && destInfo) {
|
||||
FilePath newDestPath;
|
||||
FileExistsAction opt = askRename(FileInfo{srcInfo, srcPath.parent()},
|
||||
FileInfo{destInfo, destPath.parent()},
|
||||
newDestPath);
|
||||
switch(opt) {
|
||||
case FileOperationJob::RENAME:
|
||||
// try a new file name
|
||||
if(newDestPath.isValid()) {
|
||||
destPath = std::move(newDestPath);
|
||||
// FIXME: handle the error when newDestPath is invalid.
|
||||
}
|
||||
retry = true;
|
||||
break;
|
||||
case FileOperationJob::OVERWRITE:
|
||||
// overwrite existing file
|
||||
flags |= G_FILE_COPY_OVERWRITE;
|
||||
retry = true;
|
||||
err.reset();
|
||||
break;
|
||||
case FileOperationJob::CANCEL:
|
||||
// cancel the whole job.
|
||||
cancel();
|
||||
break;
|
||||
case FileOperationJob::SKIP:
|
||||
// skip current file and don't copy it
|
||||
case FileOperationJob::SKIP_ERROR: ; /* FIXME */
|
||||
retry = false;
|
||||
break;
|
||||
}
|
||||
err.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// show error message
|
||||
if(!isCancelled() && err) {
|
||||
ErrorAction act = emitError(err, ErrorSeverity::MODERATE);
|
||||
err.reset();
|
||||
if(act == ErrorAction::RETRY) {
|
||||
// the user wants retry the operation again
|
||||
retry = true;
|
||||
}
|
||||
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) {
|
||||
// run out of disk space. delete the partial content we copied.
|
||||
g_file_delete(destPath.gfile().get(), cancellable().get(), nullptr);
|
||||
}
|
||||
}
|
||||
return retry;
|
||||
}
|
||||
|
||||
bool FileTransferJob::processPath(const FilePath& srcPath, const FilePath& destDirPath, const char* destFileName) {
|
||||
GErrorPtr err;
|
||||
GFileInfoPtr srcInfo = GFileInfoPtr {
|
||||
g_file_query_info(srcPath.gfile().get(),
|
||||
defaultGFileInfoQueryAttribs,
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(), &err),
|
||||
false
|
||||
};
|
||||
if(!srcInfo || isCancelled()) {
|
||||
// FIXME: report error
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret;
|
||||
switch(mode_) {
|
||||
case Mode::MOVE:
|
||||
ret = moveFile(srcPath, srcInfo, destDirPath, destFileName);
|
||||
break;
|
||||
case Mode::COPY: {
|
||||
bool deleteSrc = false;
|
||||
ret = copyFile(srcPath, srcInfo, destDirPath, destFileName, deleteSrc);
|
||||
break;
|
||||
}
|
||||
case Mode::LINK:
|
||||
ret = linkFile(srcPath, srcInfo, destDirPath, destFileName);
|
||||
break;
|
||||
default:
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileTransferJob::moveFile(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()) {
|
||||
// FIXME: report errors
|
||||
return false;
|
||||
}
|
||||
|
||||
// If src and dest are on the same filesystem, do move.
|
||||
// Exception: if src FS is trash:///, we always do move
|
||||
// Otherwise, do copy & delete src files.
|
||||
auto src_fs = g_file_info_get_attribute_string(srcInfo.get(), "id::filesystem");
|
||||
auto dest_fs = g_file_info_get_attribute_string(destDirInfo.get(), "id::filesystem");
|
||||
bool ret;
|
||||
if(src_fs && dest_fs && (strcmp(src_fs, dest_fs) == 0 || g_str_has_prefix(src_fs, "trash"))) {
|
||||
// src and dest are on the same filesystem
|
||||
auto destPath = destDirPath.child(destFileName);
|
||||
ret = moveFileSameFs(srcPath, srcInfo, destPath);
|
||||
|
||||
// increase current progress
|
||||
// FIXME: it's not appropriate to calculate the progress of move operations using file size
|
||||
// since the time required to move a file is not related to it's file size.
|
||||
auto size = g_file_info_get_size(srcInfo.get());
|
||||
addFinishedAmount(size, 1);
|
||||
}
|
||||
else {
|
||||
// cross device/filesystem move: copy & delete
|
||||
ret = copyFile(srcPath, srcInfo, destDirPath, destFileName);
|
||||
// NOTE: do not need to increase progress here since it's done by copyPath().
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileTransferJob::copyFile(const FilePath& srcPath, const GFileInfoPtr& srcInfo, const FilePath& destDirPath, const char* destFileName, bool skip) {
|
||||
setCurrentFile(srcPath);
|
||||
|
||||
auto size = g_file_info_get_size(srcInfo.get());
|
||||
bool success = false;
|
||||
setCurrentFileProgress(size, 0);
|
||||
|
||||
auto destPath = destDirPath.child(destFileName);
|
||||
auto file_type = g_file_info_get_file_type(srcInfo.get());
|
||||
if(!skip) {
|
||||
switch(file_type) {
|
||||
case G_FILE_TYPE_DIRECTORY:
|
||||
success = makeDir(srcPath, srcInfo, destPath);
|
||||
break;
|
||||
case G_FILE_TYPE_SPECIAL:
|
||||
success = copySpecialFile(srcPath, srcInfo, destPath);
|
||||
break;
|
||||
default:
|
||||
success = copyRegularFile(srcPath, srcInfo, destPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else { // skip the file
|
||||
success = true;
|
||||
}
|
||||
|
||||
if(success) {
|
||||
// finish copying the file
|
||||
addFinishedAmount(size, 1);
|
||||
setCurrentFileProgress(0, 0);
|
||||
|
||||
// recursively copy dir content
|
||||
if(file_type == G_FILE_TYPE_DIRECTORY) {
|
||||
success = copyDirContent(srcPath, srcInfo, destPath, skip);
|
||||
}
|
||||
|
||||
if(!skip && success && mode_ == Mode::MOVE) {
|
||||
// delete the source file for cross-filesystem move
|
||||
GErrorPtr err;
|
||||
if(g_file_delete(srcPath.gfile().get(), cancellable().get(), &err)) {
|
||||
// FIXME: add some file size to represent the amount of work need to delete a file
|
||||
addFinishedAmount(1, 1);
|
||||
}
|
||||
else {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool FileTransferJob::linkFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName) {
|
||||
setCurrentFile(srcPath);
|
||||
|
||||
bool ret = false;
|
||||
// cannot create links on non-native filesystems
|
||||
if(!destDirPath.isNative()) {
|
||||
auto msg = tr("Cannot create a link on non-native filesystem");
|
||||
GErrorPtr err{g_error_new_literal(G_IO_ERROR, G_IO_ERROR_FAILED, msg.toUtf8().constData())};
|
||||
emitError(err, ErrorSeverity::CRITICAL);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(srcPath.isNative()) {
|
||||
// create symlinks for native files
|
||||
auto destPath = destDirPath.child(destFileName);
|
||||
ret = createSymlink(srcPath, srcInfo, destPath);
|
||||
}
|
||||
else {
|
||||
// ensure that the dest file has *.desktop filename extension.
|
||||
CStrPtr desktopEntryFileName{g_strconcat(destFileName, ".desktop", nullptr)};
|
||||
auto destPath = destDirPath.child(desktopEntryFileName.get());
|
||||
ret = createShortcut(srcPath, srcInfo, destPath);
|
||||
}
|
||||
|
||||
// update progress
|
||||
// FIXME: increase the progress by 1 byte is not appropriate
|
||||
addFinishedAmount(1, 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileTransferJob::createSymlink(const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath) {
|
||||
bool ret = false;
|
||||
auto src = srcPath.localPath();
|
||||
int flags = 0;
|
||||
GErrorPtr err;
|
||||
bool retry;
|
||||
do {
|
||||
retry = false;
|
||||
if(flags & G_FILE_COPY_OVERWRITE) { // overwrite existing file
|
||||
// creating symlink cannot overwrite existing files directly, so we delete the existing file first.
|
||||
g_file_delete(destPath.gfile().get(), cancellable().get(), nullptr);
|
||||
}
|
||||
if(!g_file_make_symbolic_link(destPath.gfile().get(), src.get(), cancellable().get(), &err)) {
|
||||
retry = handleError(err, srcPath, srcInfo, destPath, flags);
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
} while(!isCancelled() && retry);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool FileTransferJob::createShortcut(const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath) {
|
||||
bool ret = false;
|
||||
const char* iconName = nullptr;
|
||||
GIcon* icon = g_file_info_get_icon(srcInfo.get());
|
||||
if(icon && G_IS_THEMED_ICON(icon)) {
|
||||
auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(icon));
|
||||
if(iconNames && iconNames[0]) {
|
||||
iconName = iconNames[0];
|
||||
}
|
||||
}
|
||||
|
||||
CStrPtr srcPathUri;
|
||||
auto uri = g_file_info_get_attribute_string(srcInfo.get(), G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
|
||||
if(!uri) {
|
||||
srcPathUri = srcPath.uri();
|
||||
uri = srcPathUri.get();
|
||||
}
|
||||
|
||||
CStrPtr srcPathDispName;
|
||||
auto name = g_file_info_get_display_name(srcInfo.get());
|
||||
if(!name) {
|
||||
srcPathDispName = srcPath.displayName();
|
||||
name = srcPathDispName.get();
|
||||
}
|
||||
|
||||
GKeyFile* kf = g_key_file_new();
|
||||
if(kf) {
|
||||
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TYPE, "Link");
|
||||
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, name);
|
||||
if(iconName) {
|
||||
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, iconName);
|
||||
}
|
||||
if(uri) {
|
||||
g_key_file_set_string(kf, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_URL, uri);
|
||||
}
|
||||
gsize contentLen;
|
||||
CStrPtr content{g_key_file_to_data(kf, &contentLen, nullptr)};
|
||||
g_key_file_free(kf);
|
||||
|
||||
int flags = 0;
|
||||
if(content) {
|
||||
bool retry;
|
||||
GErrorPtr err;
|
||||
do {
|
||||
retry = false;
|
||||
if(flags & G_FILE_COPY_OVERWRITE) { // overwrite existing file
|
||||
g_file_delete(destPath.gfile().get(), cancellable().get(), nullptr);
|
||||
}
|
||||
|
||||
if(!g_file_replace_contents(destPath.gfile().get(), content.get(), contentLen, nullptr, false, G_FILE_CREATE_NONE, nullptr, cancellable().get(), &err)) {
|
||||
retry = handleError(err, srcPath, srcInfo, destPath, flags);
|
||||
err.reset();
|
||||
}
|
||||
else {
|
||||
ret = true;
|
||||
}
|
||||
} while(!isCancelled() && retry);
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void FileTransferJob::exec() {
|
||||
// calculate the total size of files to copy
|
||||
auto totalSizeFlags = (mode_ == Mode::COPY ? TotalSizeJob::DEFAULT : TotalSizeJob::PREPARE_MOVE);
|
||||
TotalSizeJob totalSizeJob{srcPaths_, totalSizeFlags};
|
||||
connect(&totalSizeJob, &TotalSizeJob::error, this, &FileTransferJob::error);
|
||||
connect(this, &FileTransferJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel);
|
||||
totalSizeJob.run();
|
||||
if(isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ready to start
|
||||
setTotalAmount(totalSizeJob.totalSize(), totalSizeJob.fileCount());
|
||||
Q_EMIT preparedToRun();
|
||||
|
||||
if(srcPaths_.size() != destPaths_.size()) {
|
||||
qWarning("error: srcPaths.size() != destPaths.size() when copying files");
|
||||
return;
|
||||
}
|
||||
|
||||
// copy the files
|
||||
for(size_t i = 0; i < srcPaths_.size(); ++i) {
|
||||
if(isCancelled()) {
|
||||
break;
|
||||
}
|
||||
const auto& srcPath = srcPaths_[i];
|
||||
const auto& destPath = destPaths_[i];
|
||||
auto destDirPath = destPath.parent();
|
||||
processPath(srcPath, destDirPath, destPath.baseName().get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
@ -1,58 +0,0 @@
|
||||
#ifndef FM2_COPYJOB_H
|
||||
#define FM2_COPYJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "fileoperationjob.h"
|
||||
#include "gioptrs.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API FileTransferJob : public Fm::FileOperationJob {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
enum class Mode {
|
||||
COPY,
|
||||
MOVE,
|
||||
LINK
|
||||
};
|
||||
|
||||
explicit FileTransferJob(FilePathList srcPaths, Mode mode = Mode::COPY);
|
||||
explicit FileTransferJob(FilePathList srcPaths, FilePathList destPaths, Mode mode = Mode::COPY);
|
||||
explicit FileTransferJob(FilePathList srcPaths, const FilePath &destDirPath, Mode mode = Mode::COPY);
|
||||
|
||||
void setSrcPaths(FilePathList srcPaths);
|
||||
void setDestPaths(FilePathList destPaths);
|
||||
void setDestDirPath(const FilePath &destDirPath);
|
||||
|
||||
protected:
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
bool processPath(const FilePath& srcPath, const FilePath& destPath, const char *destFileName);
|
||||
bool moveFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName);
|
||||
bool copyFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName, bool skip = false);
|
||||
bool linkFile(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName);
|
||||
|
||||
bool moveFileSameFs(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath &destPath);
|
||||
bool copyRegularFile(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath &destPath);
|
||||
bool copySpecialFile(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath);
|
||||
bool copyDirContent(const FilePath &srcPath, GFileInfoPtr srcInfo, FilePath &destPath, bool skip = false);
|
||||
bool makeDir(const FilePath &srcPath, GFileInfoPtr srcInfo, FilePath &destPath);
|
||||
bool createSymlink(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath);
|
||||
bool createShortcut(const FilePath &srcPath, const GFileInfoPtr& srcInfo, FilePath& destPath);
|
||||
|
||||
bool handleError(GErrorPtr& err, const FilePath &srcPath, const GFileInfoPtr &srcInfo, FilePath &destPath, int& flags);
|
||||
|
||||
static void gfileCopyProgressCallback(goffset current_num_bytes, goffset total_num_bytes, FileTransferJob* _this);
|
||||
|
||||
private:
|
||||
FilePathList srcPaths_;
|
||||
FilePathList destPaths_;
|
||||
Mode mode_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_COPYJOB_H
|
@ -1,914 +0,0 @@
|
||||
/*
|
||||
* fm-folder.c
|
||||
*
|
||||
* Copyright 2009 - 2012 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
* Copyright 2012-2016 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
|
||||
*
|
||||
* 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 <string.h>
|
||||
#include <cassert>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
|
||||
#include "dirlistjob.h"
|
||||
#include "filesysteminfojob.h"
|
||||
#include "fileinfojob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::unordered_map<FilePath, std::weak_ptr<Folder>, FilePathHash> Folder::cache_;
|
||||
QString Folder::cutFilesDirPath_;
|
||||
QString Folder::lastCutFilesDirPath_;
|
||||
std::shared_ptr<const HashSet> 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<std::mutex> lock{mutex_};
|
||||
auto it = cache_.find(dirPath_);
|
||||
if(it != cache_.end()) {
|
||||
cache_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<Folder> Folder::fromPath(const FilePath& path) {
|
||||
std::lock_guard<std::mutex> 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<Folder>(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<const FileInfo> 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<const FileInfo>& 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<FileInfoJob*>(sender());
|
||||
fileinfoJobs_.erase(std::find(fileinfoJobs_.cbegin(), fileinfoJobs_.cend(), job));
|
||||
|
||||
if(job->isCancelled())
|
||||
return;
|
||||
|
||||
FileInfoList files_to_add;
|
||||
FileInfoList files_to_delete;
|
||||
std::vector<FileInfoPair> files_to_update;
|
||||
|
||||
const auto& paths = job->paths();
|
||||
const auto& deletionPaths = job->deletionPaths();
|
||||
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;
|
||||
}
|
||||
// add/update the file only if it isn't going to be deleted
|
||||
else if(std::find(deletionPaths.cbegin(), deletionPaths.cend(), path) == deletionPaths.cend()) {
|
||||
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);
|
||||
}
|
||||
// deletion should be done now, after the info job is processed
|
||||
for(const auto &path: deletionPaths) {
|
||||
auto name = path.baseName();
|
||||
auto it = files_.find(name.get());
|
||||
if(it != files_.end()) {
|
||||
files_to_delete.push_back(it->second);
|
||||
files_.erase(it);
|
||||
}
|
||||
}
|
||||
if(!files_to_delete.empty()) {
|
||||
Q_EMIT filesRemoved(files_to_delete);
|
||||
}
|
||||
Q_EMIT contentChanged();
|
||||
}
|
||||
|
||||
void Folder::processPendingChanges() {
|
||||
has_idle_update_handler = false;
|
||||
// FmFileInfoJob* job = nullptr;
|
||||
std::lock_guard<std::mutex> 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() || !paths_to_del.empty()) {
|
||||
FilePathList paths, deletionPaths;
|
||||
paths.insert(paths.end(), paths_to_add.cbegin(), paths_to_add.cend());
|
||||
paths.insert(paths.end(), paths_to_update.cbegin(), paths_to_update.cend());
|
||||
deletionPaths.insert(deletionPaths.end(), paths_to_del.cbegin(), paths_to_del.cend());
|
||||
info_job = new FileInfoJob{paths, deletionPaths, dirPath_,
|
||||
hasCutFiles() ? cutFilesHashSet_ : nullptr};
|
||||
paths_to_update.clear();
|
||||
paths_to_add.clear();
|
||||
paths_to_del.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(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, addition or deletion */
|
||||
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()
|
||||
&& std::find(paths_to_del.cbegin(), paths_to_del.cend(), path) == paths_to_del.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);
|
||||
/* Queue the file for deletion only if it is not in the addition queue but
|
||||
if it is, remove it from that queue instead of queueing it for deletion.
|
||||
Moreover, as was the case with eventFileChanged(), here too queueing
|
||||
should be done regardless of what "files_" may contain. */
|
||||
if(std::find(paths_to_add.cbegin(), paths_to_add.cend(), path) == paths_to_add.cend()) {
|
||||
if(std::find(paths_to_del.cbegin(), paths_to_del.cend(), path) == paths_to_del.cend()) {
|
||||
paths_to_del.push_back(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
paths_to_add.erase(std::remove(paths_to_add.begin(), paths_to_add.end(), path), paths_to_add.cend());
|
||||
}
|
||||
/* the update queue should be canceled for a file that is going to be deleted */
|
||||
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<std::mutex> 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<std::mutex> 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:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checks whether there were cut files here
|
||||
// and if there were, invalidates this last cut path
|
||||
bool Folder::hadCutFilesUnset() {
|
||||
if(lastCutFilesDirPath_ == dirPath_.toString().get()) {
|
||||
lastCutFilesDirPath_ = QString();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Folder::hasCutFiles() {
|
||||
return cutFilesHashSet_
|
||||
&& !cutFilesHashSet_->empty()
|
||||
&& cutFilesDirPath_ == dirPath_.toString().get();
|
||||
}
|
||||
|
||||
void Folder::setCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet) {
|
||||
if(cutFilesHashSet_ && !cutFilesHashSet_->empty()) {
|
||||
lastCutFilesDirPath_ = cutFilesDirPath_;
|
||||
}
|
||||
cutFilesDirPath_ = dirPath_.toString().get();
|
||||
cutFilesHashSet_ = cutFilesHashSet;
|
||||
}
|
||||
|
||||
void Folder::onDirListFinished() {
|
||||
DirListJob* job = static_cast<DirListJob*>(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<FileInfoPair> 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<FileSystemInfoJob*>(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
|
@ -1,196 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 <gio/gio.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#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<Folder> 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<const FileInfo> fileByName(const char* name) const;
|
||||
|
||||
bool isEmpty() const;
|
||||
|
||||
FileInfoList files() const;
|
||||
|
||||
const FilePath& path() const;
|
||||
|
||||
const std::shared_ptr<const FileInfo> &info() const;
|
||||
|
||||
bool hadCutFilesUnset();
|
||||
|
||||
bool hasCutFiles();
|
||||
|
||||
void setCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet);
|
||||
|
||||
void forEachFile(std::function<void (const std::shared_ptr<const FileInfo>&)> func) const {
|
||||
std::lock_guard<std::mutex> 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<FileInfoPair>& 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<const FileInfo> dirInfo_;
|
||||
DirListJob* dirlist_job;
|
||||
std::vector<FileInfoJob*> fileinfoJobs_;
|
||||
FileSystemInfoJob* fsInfoJob_;
|
||||
|
||||
std::shared_ptr<VolumeManager> volumeManager_;
|
||||
|
||||
/* for file monitor */
|
||||
bool has_idle_reload_handler;
|
||||
bool has_idle_update_handler;
|
||||
std::vector<FilePath> paths_to_add;
|
||||
std::vector<FilePath> paths_to_update;
|
||||
std::vector<FilePath> 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<const std::string, std::shared_ptr<const FileInfo>, std::hash<std::string>> 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<FilePath, std::weak_ptr<Folder>, FilePathHash> cache_;
|
||||
static QString cutFilesDirPath_;
|
||||
static QString lastCutFilesDirPath_;
|
||||
static std::shared_ptr<const HashSet> cutFilesHashSet_;
|
||||
static std::mutex mutex_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // __LIBFM_QT_FM2_FOLDER_H__
|
@ -1,137 +0,0 @@
|
||||
#ifndef GIOPTRS_H
|
||||
#define GIOPTRS_H
|
||||
// define smart pointers for GIO data types
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
#include "gobjectptr.h"
|
||||
#include "cstrptr.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
typedef GObjectPtr<GFile> GFilePtr;
|
||||
typedef GObjectPtr<GFileInfo> GFileInfoPtr;
|
||||
typedef GObjectPtr<GFileMonitor> GFileMonitorPtr;
|
||||
typedef GObjectPtr<GCancellable> GCancellablePtr;
|
||||
typedef GObjectPtr<GFileEnumerator> GFileEnumeratorPtr;
|
||||
|
||||
typedef GObjectPtr<GInputStream> GInputStreamPtr;
|
||||
typedef GObjectPtr<GFileInputStream> GFileInputStreamPtr;
|
||||
typedef GObjectPtr<GOutputStream> GOutputStreamPtr;
|
||||
typedef GObjectPtr<GFileOutputStream> GFileOutputStreamPtr;
|
||||
|
||||
typedef GObjectPtr<GIcon> GIconPtr;
|
||||
|
||||
typedef GObjectPtr<GVolumeMonitor> GVolumeMonitorPtr;
|
||||
typedef GObjectPtr<GVolume> GVolumePtr;
|
||||
typedef GObjectPtr<GMount> GMountPtr;
|
||||
|
||||
typedef GObjectPtr<GAppInfo> 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
|
@ -1,104 +0,0 @@
|
||||
#ifndef FM2_GOBJECTPTR_H
|
||||
#define FM2_GOBJECTPTR_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <cstddef>
|
||||
#include <QDebug>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
template <typename T>
|
||||
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<T*>(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<T*>(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<T*>(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
|
@ -1,147 +0,0 @@
|
||||
#include "iconinfo.h"
|
||||
#include "iconinfo_p.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::unordered_map<GIcon*, std::shared_ptr<IconInfo>, IconInfo::GIconHash, IconInfo::GIconEqual> IconInfo::cache_;
|
||||
std::mutex IconInfo::mutex_;
|
||||
QList<QIcon> IconInfo::fallbackQicons_;
|
||||
|
||||
static const char* fallbackIconNames[] = {
|
||||
"unknown",
|
||||
"application-octet-stream",
|
||||
"application-x-generic",
|
||||
"text-x-generic",
|
||||
nullptr
|
||||
};
|
||||
|
||||
static QIcon getFirst(const QList<QIcon> & icons)
|
||||
{
|
||||
for (const auto & icon : icons) {
|
||||
if (!icon.isNull())
|
||||
return icon;
|
||||
}
|
||||
return QIcon{};
|
||||
}
|
||||
|
||||
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<const IconInfo> IconInfo::fromName(const char* name) {
|
||||
GObjectPtr<GIcon> gicon{g_themed_icon_new(name), false};
|
||||
return fromGIcon(gicon);
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<const IconInfo> IconInfo::fromGIcon(GIconPtr gicon) {
|
||||
if(Q_LIKELY(gicon)) {
|
||||
std::lock_guard<std::mutex> 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<IconInfo>(std::move(gicon));
|
||||
cache_.insert(std::make_pair(icon->gicon_.get(), icon));
|
||||
return icon;
|
||||
}
|
||||
return std::shared_ptr<const IconInfo>{};
|
||||
}
|
||||
|
||||
void IconInfo::updateQIcons() {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
for(auto& elem: cache_) {
|
||||
auto& info = elem.second;
|
||||
info->internalQicons_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
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_ = getFirst(internalQicons_);
|
||||
}
|
||||
}
|
||||
}
|
||||
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_ = getFirst(internalQicons_);
|
||||
}
|
||||
}
|
||||
}
|
||||
return !transparent ? qicon_ : qiconTransparent_;
|
||||
}
|
||||
|
||||
QList<QIcon> IconInfo::qiconsFromNames(const char* const* names) {
|
||||
QList<QIcon> icons;
|
||||
// qDebug("names: %p", names);
|
||||
for(const gchar* const* name = names; *name; ++name) {
|
||||
// qDebug("icon name=%s", *name);
|
||||
icons.push_back(QIcon::fromTheme(*name));
|
||||
}
|
||||
return icons;
|
||||
}
|
||||
|
||||
std::forward_list<std::shared_ptr<const IconInfo>> IconInfo::emblems() const {
|
||||
std::forward_list<std::shared_ptr<const IconInfo>> 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 {
|
||||
QIcon ret_icon;
|
||||
if(Q_UNLIKELY(internalQicons_.isEmpty())) {
|
||||
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));
|
||||
internalQicons_ = qiconsFromNames(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)};
|
||||
internalQicons_.push_back(QIcon(fpath.get()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ret_icon = getFirst(internalQicons_);
|
||||
|
||||
// fallback to default icon
|
||||
if(Q_UNLIKELY(ret_icon.isNull())) {
|
||||
if(Q_UNLIKELY(fallbackQicons_.isEmpty())) {
|
||||
fallbackQicons_ = qiconsFromNames(fallbackIconNames);
|
||||
}
|
||||
ret_icon = getFirst(fallbackQicons_);
|
||||
}
|
||||
return ret_icon;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,112 +0,0 @@
|
||||
/*
|
||||
* fm-icon.h
|
||||
*
|
||||
* Copyright 2009 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
* Copyright 2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
|
||||
*
|
||||
* 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 <gio/gio.h>
|
||||
#include "gioptrs.h"
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <forward_list>
|
||||
#include <QIcon>
|
||||
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API IconInfo: public std::enable_shared_from_this<IconInfo> {
|
||||
public:
|
||||
friend class IconEngine;
|
||||
|
||||
explicit IconInfo() {}
|
||||
|
||||
explicit IconInfo(const char* name);
|
||||
|
||||
explicit IconInfo(const GIconPtr gicon);
|
||||
|
||||
~IconInfo();
|
||||
|
||||
static std::shared_ptr<const IconInfo> fromName(const char* name);
|
||||
|
||||
static std::shared_ptr<const IconInfo> fromGIcon(GIconPtr gicon);
|
||||
|
||||
static std::shared_ptr<const IconInfo> 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<std::shared_ptr<const IconInfo>> emblems() const;
|
||||
|
||||
bool isValid() const {
|
||||
return gicon_ != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static QList<QIcon> qiconsFromNames(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 QList<QIcon> internalQicons_;
|
||||
|
||||
static std::unordered_map<GIcon*, std::shared_ptr<IconInfo>, GIconHash, GIconEqual> cache_;
|
||||
static std::mutex mutex_;
|
||||
static QList<QIcon> fallbackQicons_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<const Fm::IconInfo>)
|
||||
|
||||
#endif /* __FM2_ICON_INFO_H__ */
|
@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 <QIconEngine>
|
||||
#include <QPainter>
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "iconinfo.h"
|
||||
#include <gio/gio.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class IconEngine: public QIconEngine {
|
||||
public:
|
||||
|
||||
IconEngine(std::shared_ptr<const Fm::IconInfo> 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<const Fm::IconInfo> info_;
|
||||
bool transparent_;
|
||||
};
|
||||
|
||||
IconEngine::IconEngine(std::shared_ptr<const IconInfo> 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<QIconEngine::AvailableSizesArgument*>(data);
|
||||
args->sizes = info ? info->internalQicon().availableSizes(args->mode, args->state) : QList<QSize>{};
|
||||
break;
|
||||
}
|
||||
case QIconEngine::IconNameHook: {
|
||||
QString* result = reinterpret_cast<QString*>(data);
|
||||
*result = info ? info->internalQicon().name() : QString{};
|
||||
break;
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
|
||||
case QIconEngine::IsNullHook: {
|
||||
bool* result = reinterpret_cast<bool*>(data);
|
||||
*result = info ? info->internalQicon().isNull() : true;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM_ICONENGINE_H
|
@ -1,57 +0,0 @@
|
||||
#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
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 <libfm/fm.h>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include <QThread>
|
||||
#include <QRunnable>
|
||||
#include <memory>
|
||||
#include <gio/gio.h>
|
||||
#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__
|
@ -1,26 +0,0 @@
|
||||
#ifndef JOB_P_H
|
||||
#define JOB_P_H
|
||||
|
||||
#include <QThread>
|
||||
#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
|
@ -1,64 +0,0 @@
|
||||
#include "mimetype.h"
|
||||
#include <cstring>
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::unordered_map<const char*, std::shared_ptr<const MimeType>, CStrHash, CStrEqual> MimeType::cache_;
|
||||
std::mutex MimeType::mutex_;
|
||||
|
||||
std::shared_ptr<const MimeType> MimeType::inodeDirectory_; // inode/directory
|
||||
std::shared_ptr<const MimeType> MimeType::inodeShortcut_; // inode/x-shortcut
|
||||
std::shared_ptr<const MimeType> MimeType::inodeMountPoint_; // inode/mount-point
|
||||
std::shared_ptr<const MimeType> MimeType::desktopEntry_; // application/x-desktop
|
||||
|
||||
|
||||
MimeType::MimeType(const char* typeName):
|
||||
name_{g_strdup(typeName)},
|
||||
desc_{nullptr} {
|
||||
|
||||
GObjectPtr<GIcon> 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<const MimeType> MimeType::fromName(const char* typeName) {
|
||||
std::shared_ptr<const MimeType> ret;
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto it = cache_.find(typeName);
|
||||
if(it == cache_.end()) {
|
||||
ret = std::make_shared<MimeType>(typeName);
|
||||
cache_.insert(std::make_pair(ret->name_.get(), ret));
|
||||
}
|
||||
else {
|
||||
ret = it->second;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// static
|
||||
std::shared_ptr<const MimeType> 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
|
@ -1,172 +0,0 @@
|
||||
/*
|
||||
* fm-mime-type.h
|
||||
*
|
||||
* Copyright 2009 - 2012 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
#include <forward_list>
|
||||
#include <functional>
|
||||
|
||||
#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<const Thumbnailer> firstThumbnailer() const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
return thumbnailers_.empty() ? nullptr : thumbnailers_.front();
|
||||
}
|
||||
|
||||
void forEachThumbnailer(std::function<bool(const std::shared_ptr<const Thumbnailer>&)> func) const {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
for(auto& thumbnailer: thumbnailers_) {
|
||||
if(func(thumbnailer)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::shared_ptr<const IconInfo>& 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<const MimeType> fromName(const char* typeName);
|
||||
|
||||
static std::shared_ptr<const MimeType> 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<const MimeType> inodeDirectory() { // inode/directory
|
||||
if(!inodeDirectory_)
|
||||
inodeDirectory_ = fromName("inode/directory");
|
||||
return inodeDirectory_;
|
||||
}
|
||||
|
||||
static std::shared_ptr<const MimeType> inodeShortcut() { // inode/x-shortcut
|
||||
if(!inodeShortcut_)
|
||||
inodeShortcut_ = fromName("inode/x-shortcut");
|
||||
return inodeShortcut_;
|
||||
}
|
||||
|
||||
static std::shared_ptr<const MimeType> inodeMountPoint() { // inode/mount-point
|
||||
if(!inodeMountPoint_)
|
||||
inodeMountPoint_ = fromName("inode/mount-point");
|
||||
return inodeMountPoint_;
|
||||
}
|
||||
|
||||
static std::shared_ptr<const MimeType> desktopEntry() { // application/x-desktop
|
||||
if(!desktopEntry_)
|
||||
desktopEntry_ = fromName("application/x-desktop");
|
||||
return desktopEntry_;
|
||||
}
|
||||
|
||||
private:
|
||||
void removeThumbnailer(std::shared_ptr<const Thumbnailer>& thumbnailer) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
thumbnailers_.remove(thumbnailer);
|
||||
}
|
||||
|
||||
void addThumbnailer(std::shared_ptr<const Thumbnailer> thumbnailer) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
thumbnailers_.push_front(std::move(thumbnailer));
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<const IconInfo> icon_;
|
||||
CStrPtr name_;
|
||||
mutable CStrPtr desc_;
|
||||
std::forward_list<std::shared_ptr<const Thumbnailer>> thumbnailers_;
|
||||
static std::unordered_map<const char*, std::shared_ptr<const MimeType>, CStrHash, CStrEqual> cache_;
|
||||
static std::mutex mutex_;
|
||||
|
||||
static std::shared_ptr<const MimeType> inodeDirectory_; // inode/directory
|
||||
static std::shared_ptr<const MimeType> inodeShortcut_; // inode/x-shortcut
|
||||
static std::shared_ptr<const MimeType> inodeMountPoint_; // inode/mount-point
|
||||
static std::shared_ptr<const MimeType> desktopEntry_; // application/x-desktop
|
||||
};
|
||||
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif
|
@ -1,130 +0,0 @@
|
||||
#include "templates.h"
|
||||
#include "gioptrs.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <QDebug>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::weak_ptr<Templates> Templates::globalInstance_;
|
||||
|
||||
TemplateItem::TemplateItem(std::shared_ptr<const FileInfo> file): fileInfo_{file} {
|
||||
}
|
||||
|
||||
FilePath TemplateItem::filePath() const {
|
||||
auto& target = fileInfo_->target();
|
||||
if(fileInfo_->isDesktopEntry() && !target.empty()) {
|
||||
if(target[0] == '/') { // target is an absolute path
|
||||
return FilePath::fromLocalPath(target.c_str());
|
||||
}
|
||||
else { // resolve relative path
|
||||
return fileInfo_->dirPath().relativePath(target.c_str());
|
||||
}
|
||||
}
|
||||
return fileInfo_->path();
|
||||
}
|
||||
|
||||
Templates::Templates() : QObject() {
|
||||
auto* data_dirs = g_get_system_data_dirs();
|
||||
// system-wide template dirs
|
||||
for(auto data_dir = data_dirs; *data_dir; ++data_dir) {
|
||||
CStrPtr dir_name{g_build_filename(*data_dir, "templates", nullptr)};
|
||||
addTemplateDir(dir_name.get());
|
||||
}
|
||||
|
||||
// user-specific template dir
|
||||
CStrPtr dir_name{g_build_filename(g_get_user_data_dir(), "templates", nullptr)};
|
||||
addTemplateDir(dir_name.get());
|
||||
|
||||
// $XDG_TEMPLATES_DIR (FIXME: this might change at runtime)
|
||||
const gchar *special_dir = g_get_user_special_dir(G_USER_DIRECTORY_TEMPLATES);
|
||||
if (special_dir) {
|
||||
addTemplateDir(special_dir);
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Templates> Templates::globalInstance() {
|
||||
auto templates = globalInstance_.lock();
|
||||
if(!templates) {
|
||||
templates = make_shared<Templates>();
|
||||
globalInstance_ = templates;
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
void Templates::addTemplateDir(const char* dirPathName) {
|
||||
auto dir_path = FilePath::fromLocalPath(dirPathName);
|
||||
if(dir_path.isValid()) {
|
||||
auto folder = Folder::fromPath(dir_path);
|
||||
connect(folder.get(), &Folder::filesAdded, this, &Templates::onFilesAdded);
|
||||
connect(folder.get(), &Folder::filesChanged, this, &Templates::onFilesChanged);
|
||||
connect(folder.get(), &Folder::filesRemoved, this, &Templates::onFilesRemoved);
|
||||
connect(folder.get(), &Folder::removed, this, &Templates::onTemplateDirRemoved);
|
||||
templateFolders_.emplace_back(std::move(folder));
|
||||
}
|
||||
}
|
||||
|
||||
void Templates::onFilesAdded(FileInfoList& addedFiles) {
|
||||
for(auto& file : addedFiles) {
|
||||
// FIXME: we do not support subdirs right now (only XFCE supports this)
|
||||
if(file->isHidden() || file->isDir()) {
|
||||
continue;
|
||||
}
|
||||
items_.emplace_back(std::make_shared<TemplateItem>(file));
|
||||
// emit a signal for the addition
|
||||
Q_EMIT itemAdded(items_.back());
|
||||
}
|
||||
}
|
||||
|
||||
void Templates::onFilesChanged(std::vector<FileInfoPair>& changePairs) {
|
||||
for(auto& change: changePairs) {
|
||||
auto& old_file = change.first;
|
||||
auto& new_file = change.second;
|
||||
auto it = std::find_if(items_.begin(), items_.end(), [&](const std::shared_ptr<TemplateItem>& item) {
|
||||
return item->fileInfo() == old_file;
|
||||
});
|
||||
if(it != items_.end()) {
|
||||
// emit a signal for the change
|
||||
auto old = *it;
|
||||
*it = std::make_shared<TemplateItem>(new_file);
|
||||
Q_EMIT itemChanged(old, *it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Templates::onFilesRemoved(FileInfoList& removedFiles) {
|
||||
for(auto& file : removedFiles) {
|
||||
auto filePath = file->path();
|
||||
auto it = std::remove_if(items_.begin(), items_.end(), [&](const std::shared_ptr<TemplateItem>& item) {
|
||||
return item->fileInfo() == file;
|
||||
});
|
||||
for(auto removed_it = it; it != items_.end(); ++it) {
|
||||
// emit a signal for the removal
|
||||
Q_EMIT itemRemoved(*removed_it);
|
||||
}
|
||||
items_.erase(it, items_.end());
|
||||
}
|
||||
}
|
||||
|
||||
void Templates::onTemplateDirRemoved() {
|
||||
// the whole template dir is removed
|
||||
auto folder = static_cast<Folder*>(sender());
|
||||
if(!folder) {
|
||||
return;
|
||||
}
|
||||
auto dirPath = folder->path();
|
||||
|
||||
// remote all files under this dir
|
||||
auto it = std::remove_if(items_.begin(), items_.end(), [&](const std::shared_ptr<TemplateItem>& item) {
|
||||
return dirPath.isPrefixOf(item->filePath());
|
||||
});
|
||||
for(auto removed_it = it; it != items_.end(); ++it) {
|
||||
// emit a signal for the removal
|
||||
Q_EMIT itemRemoved(*removed_it);
|
||||
}
|
||||
items_.erase(it, items_.end());
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,100 +0,0 @@
|
||||
#ifndef TEMPLATES_H
|
||||
#define TEMPLATES_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "folder.h"
|
||||
#include "fileinfo.h"
|
||||
#include "mimetype.h"
|
||||
#include "iconinfo.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API TemplateItem {
|
||||
public:
|
||||
explicit TemplateItem(std::shared_ptr<const FileInfo> fileInfo);
|
||||
|
||||
QString displayName() const {
|
||||
return fileInfo_->displayName();
|
||||
}
|
||||
|
||||
const std::string& name() const {
|
||||
return fileInfo_->name();
|
||||
}
|
||||
|
||||
std::shared_ptr<const IconInfo> icon() const {
|
||||
return fileInfo_->icon();
|
||||
}
|
||||
|
||||
std::shared_ptr<const FileInfo> fileInfo() const {
|
||||
return fileInfo_;
|
||||
}
|
||||
|
||||
std::shared_ptr<const MimeType> mimeType() const {
|
||||
return fileInfo_->mimeType();
|
||||
}
|
||||
|
||||
FilePath filePath() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<const FileInfo> fileInfo_;
|
||||
};
|
||||
|
||||
|
||||
class LIBFM_QT_API Templates : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Templates();
|
||||
|
||||
// FIXME: the first call to this method will get no templates since dir loading is in progress.
|
||||
static std::shared_ptr<Templates> globalInstance();
|
||||
|
||||
void forEachItem(std::function<void (const std::shared_ptr<const TemplateItem>&)> func) const {
|
||||
for(const auto& item : items_) {
|
||||
func(item);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<const TemplateItem>> items() const {
|
||||
std::vector<std::shared_ptr<const TemplateItem>> tmp_items;
|
||||
for(auto& item: items_) {
|
||||
tmp_items.emplace_back(item);
|
||||
}
|
||||
return tmp_items;
|
||||
}
|
||||
|
||||
bool hasTemplates() const {
|
||||
return !items_.empty();
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void itemAdded(const std::shared_ptr<const TemplateItem>& item);
|
||||
|
||||
void itemChanged(const std::shared_ptr<const TemplateItem>& oldItem, const std::shared_ptr<const TemplateItem>& newItem);
|
||||
|
||||
void itemRemoved(const std::shared_ptr<const TemplateItem>& item);
|
||||
|
||||
private:
|
||||
void addTemplateDir(const char* dirPathName);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onFilesAdded(FileInfoList& addedFiles);
|
||||
|
||||
void onFilesChanged(std::vector<FileInfoPair>& changePairs);
|
||||
|
||||
void onFilesRemoved(FileInfoList& removedFiles);
|
||||
|
||||
void onTemplateDirRemoved();
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<TemplateItem>> items_;
|
||||
std::vector<std::shared_ptr<Folder>> templateFolders_;
|
||||
static std::weak_ptr<Templates> globalInstance_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // TEMPLATES_H
|
@ -1,127 +0,0 @@
|
||||
#include "terminal.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gdesktopappinfo.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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<CStrPtr> allKnownTerminals() {
|
||||
std::vector<CStrPtr> 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
|
@ -1,17 +0,0 @@
|
||||
#ifndef TERMINAL_H
|
||||
#define TERMINAL_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "gioptrs.h"
|
||||
#include "filepath.h"
|
||||
#include <vector>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
LIBFM_QT_API bool launchTerminal(const char* programName, const FilePath& workingDir, GErrorPtr& error);
|
||||
|
||||
LIBFM_QT_API std::vector<CStrPtr> allKnownTerminals();
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // TERMINAL_H
|
@ -1,141 +0,0 @@
|
||||
#include "thumbnailer.h"
|
||||
#include "mimetype.h"
|
||||
#include <string>
|
||||
#include <QDebug>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::mutex Thumbnailer::mutex_;
|
||||
std::vector<std::shared_ptr<Thumbnailer>> 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:
|
||||
* https://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<std::string, const char*>& 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<std::string, const char*> 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<std::mutex> 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<Thumbnailer>(base_name.c_str(), kf);
|
||||
if(thumbnailer->exec_) {
|
||||
char** mime_types = g_key_file_get_string_list(kf, "Thumbnailer Entry", "MimeType", nullptr, nullptr);
|
||||
if(mime_types) {
|
||||
for(char** name = mime_types; *name; ++name) {
|
||||
auto mime_type = MimeType::fromName(*name);
|
||||
if(mime_type) {
|
||||
std::const_pointer_cast<MimeType>(mime_type)->addThumbnailer(thumbnailer);
|
||||
}
|
||||
}
|
||||
g_strfreev(mime_types);
|
||||
}
|
||||
}
|
||||
allThumbnailers_.push_back(std::move(thumbnailer));
|
||||
}
|
||||
}
|
||||
g_key_file_free(kf);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,37 +0,0 @@
|
||||
#ifndef FM2_THUMBNAILER_H
|
||||
#define FM2_THUMBNAILER_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "cstrptr.h"
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
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<std::shared_ptr<const MimeType>> mimeTypes_;
|
||||
|
||||
static std::mutex mutex_;
|
||||
static std::vector<std::shared_ptr<Thumbnailer>> allThumbnailers_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_THUMBNAILER_H
|
@ -1,266 +0,0 @@
|
||||
#include "thumbnailjob.h"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <libexif/exif-loader.h>
|
||||
#include <QImageReader>
|
||||
#include <QDir>
|
||||
#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<unsigned char[]> 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<const FileInfo> &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<const unsigned char*>(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<const MimeType>& 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<const FileInfo>& file, const QImage &thumbnail) const {
|
||||
QString thumb_mtime = thumbnail.text("Thumb::MTime");
|
||||
return (thumb_mtime.isEmpty() || thumb_mtime.toULongLong() != 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:
|
||||
* https://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<const FileInfo>& 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<const Thumbnailer>& 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
|
@ -1,141 +0,0 @@
|
||||
#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);
|
||||
if(fs_id && dest_fs_id && (strcmp(fs_id, dest_fs_id) == 0 || g_str_has_prefix(fs_id, "trash"))) {
|
||||
// same filesystem or move from trash:///
|
||||
descend = false;
|
||||
}
|
||||
else {
|
||||
/* files on different device requires an additional 'delete' for the source file. */
|
||||
++totalSize_; /* this is for the additional delete */
|
||||
++totalOndiskSize_;
|
||||
++fileCount_;
|
||||
descend = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(type == G_FILE_TYPE_DIRECTORY) {
|
||||
/* check if we need to decends into the dir. */
|
||||
/* trash:/// doesn't support deleting files recursively (but we want to descend into trash root "trash:///" */
|
||||
if(flags_ & PREPARE_DELETE && path.hasUriScheme("trash") && path.baseName()[0] != '/') {
|
||||
descend = false;
|
||||
}
|
||||
else {
|
||||
/* only descends into files on the same filesystem */
|
||||
if(flags_ & SAME_FS) {
|
||||
fs_id = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_ID_FILESYSTEM);
|
||||
descend = (g_strcmp0(fs_id, dest_fs_id) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
@ -1,56 +0,0 @@
|
||||
#ifndef FM2_TOTALSIZEJOB_H
|
||||
#define FM2_TOTALSIZEJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "fileoperationjob.h"
|
||||
#include "filepath.h"
|
||||
#include <cstdint>
|
||||
#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
|
@ -1,71 +0,0 @@
|
||||
#include "trashjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
TrashJob::TrashJob(FilePathList paths): paths_{std::move(paths)} {
|
||||
// calculate progress using finished file counts rather than their sizes
|
||||
setCalcProgressUsingSize(false);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// TODO: get parent dir of the current path.
|
||||
// if there is a Fm::Folder object created for it, block the update for the folder temporarily.
|
||||
|
||||
for(;;) { // retry the i/o operation on errors
|
||||
auto gf = path.gfile();
|
||||
bool ret = false;
|
||||
// FIXME: do not depend on fm_config
|
||||
if(fm_config->no_usb_trash) {
|
||||
GMountPtr mnt{g_file_find_enclosing_mount(gf.get(), nullptr, nullptr), false};
|
||||
if(mnt) {
|
||||
ret = g_mount_can_unmount(mnt.get()); /* TRUE if it's removable media */
|
||||
if(ret) {
|
||||
unsupportedFiles_.push_back(path);
|
||||
break; // don't trash the file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// move the file to trash
|
||||
GErrorPtr err;
|
||||
ret = g_file_trash(gf.get(), cancellable().get(), &err);
|
||||
if(ret) { // trash operation succeeded
|
||||
break;
|
||||
}
|
||||
else { // failed
|
||||
// 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
|
@ -1,30 +0,0 @@
|
||||
#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(FilePathList paths);
|
||||
|
||||
FilePathList unsupportedFiles() const {
|
||||
return unsupportedFiles_;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
FilePathList paths_;
|
||||
FilePathList unsupportedFiles_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_TRASHJOB_H
|
@ -1,76 +0,0 @@
|
||||
#include "untrashjob.h"
|
||||
#include "filetransferjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
UntrashJob::UntrashJob(FilePathList srcPaths):
|
||||
srcPaths_{std::move(srcPaths)} {
|
||||
}
|
||||
|
||||
void UntrashJob::exec() {
|
||||
// preparing for the job
|
||||
FilePathList validSrcPaths;
|
||||
FilePathList origPaths;
|
||||
for(auto& srcPath: srcPaths_) {
|
||||
if(isCancelled()) {
|
||||
break;
|
||||
}
|
||||
GErrorPtr err;
|
||||
GFileInfoPtr srcInfo{
|
||||
g_file_query_info(srcPath.gfile().get(),
|
||||
"trash::orig-path",
|
||||
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
|
||||
cancellable().get(),
|
||||
&err),
|
||||
false
|
||||
};
|
||||
if(srcInfo) {
|
||||
const char* orig_path_str = g_file_info_get_attribute_byte_string(srcInfo.get(), "trash::orig-path");
|
||||
if(orig_path_str) {
|
||||
validSrcPaths.emplace_back(srcPath);
|
||||
origPaths.emplace_back(FilePath::fromPathStr(orig_path_str));
|
||||
}
|
||||
else {
|
||||
ErrorAction act;
|
||||
g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
tr("Cannot untrash file '%s': original path not known").toUtf8().constData(),
|
||||
g_file_info_get_display_name(srcInfo.get()));
|
||||
// FIXME: do we need to retry here?
|
||||
emitError(err, ErrorSeverity::MODERATE);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// FIXME: do we need to retry here?
|
||||
emitError(err);
|
||||
}
|
||||
}
|
||||
|
||||
// collected original paths of the trashed files
|
||||
// use the file transfer job to handle the actual file move
|
||||
FileTransferJob fileTransferJob{std::move(validSrcPaths), std::move(origPaths), FileTransferJob::Mode::MOVE};
|
||||
// FIXME:
|
||||
// I'm not sure why specifying Qt::DirectConnection is needed here since the caller & receiver are in the same thread. :-(
|
||||
// However without this, the signals/slots here will cause deadlocks.
|
||||
connect(&fileTransferJob, &FileTransferJob::preparedToRun, this, &UntrashJob::preparedToRun, Qt::DirectConnection);
|
||||
connect(&fileTransferJob, &FileTransferJob::error, this, &UntrashJob::error, Qt::DirectConnection);
|
||||
connect(&fileTransferJob, &FileTransferJob::fileExists, this, &UntrashJob::fileExists, Qt::DirectConnection);
|
||||
|
||||
// cancel the file transfer subjob if the parent job is cancelled
|
||||
connect(this, &UntrashJob::cancelled, &fileTransferJob,
|
||||
[&fileTransferJob]() {
|
||||
if(!fileTransferJob.isCancelled()) {
|
||||
fileTransferJob.cancel();
|
||||
}
|
||||
}, Qt::DirectConnection);
|
||||
|
||||
// cancel the parent job if the file transfer subjob is cancelled
|
||||
connect(&fileTransferJob, &FileTransferJob::cancelled, this,
|
||||
[this]() {
|
||||
if(!isCancelled()) {
|
||||
cancel();
|
||||
}
|
||||
}, Qt::DirectConnection);
|
||||
fileTransferJob.run();
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,22 +0,0 @@
|
||||
#ifndef FM2_UNTRASHJOB_H
|
||||
#define FM2_UNTRASHJOB_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include "fileoperationjob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class LIBFM_QT_API UntrashJob : public FileOperationJob {
|
||||
public:
|
||||
explicit UntrashJob(FilePathList srcPaths);
|
||||
|
||||
protected:
|
||||
void exec() override;
|
||||
|
||||
private:
|
||||
FilePathList srcPaths_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_UNTRASHJOB_H
|
@ -1,47 +0,0 @@
|
||||
#include "userinfocache.h"
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
UserInfoCache* UserInfoCache::globalInstance_ = nullptr;
|
||||
std::mutex UserInfoCache::mutex_;
|
||||
|
||||
UserInfoCache::UserInfoCache() : QObject() {
|
||||
}
|
||||
|
||||
const std::shared_ptr<const UserInfo>& UserInfoCache::userFromId(uid_t uid) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
auto it = users_.find(uid);
|
||||
if(it != users_.end())
|
||||
return it->second;
|
||||
std::shared_ptr<const UserInfo> user;
|
||||
auto pw = getpwuid(uid);
|
||||
if(pw) {
|
||||
user = std::make_shared<UserInfo>(uid, pw->pw_name, pw->pw_gecos);
|
||||
}
|
||||
return (users_[uid] = user);
|
||||
}
|
||||
|
||||
const std::shared_ptr<const GroupInfo>& UserInfoCache::groupFromId(gid_t gid) {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
auto it = groups_.find(gid);
|
||||
if(it != groups_.end())
|
||||
return it->second;
|
||||
std::shared_ptr<const GroupInfo> group;
|
||||
auto gr = getgrgid(gid);
|
||||
if(gr) {
|
||||
group = std::make_shared<GroupInfo>(gid, gr->gr_name);
|
||||
}
|
||||
return (groups_[gid] = group);
|
||||
}
|
||||
|
||||
// static
|
||||
UserInfoCache* UserInfoCache::globalInstance() {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
if(!globalInstance_)
|
||||
globalInstance_ = new UserInfoCache();
|
||||
return globalInstance_;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,82 +0,0 @@
|
||||
#ifndef FM2_USERINFOCACHE_H
|
||||
#define FM2_USERINFOCACHE_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <QObject>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <sys/types.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
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<const UserInfo>& userFromId(uid_t uid);
|
||||
|
||||
const std::shared_ptr<const GroupInfo>& groupFromId(gid_t gid);
|
||||
|
||||
static UserInfoCache* globalInstance();
|
||||
|
||||
Q_SIGNALS:
|
||||
void changed();
|
||||
|
||||
private:
|
||||
std::unordered_map<uid_t, std::shared_ptr<const UserInfo>> users_;
|
||||
std::unordered_map<gid_t, std::shared_ptr<const GroupInfo>> groups_;
|
||||
static UserInfoCache* globalInstance_;
|
||||
static std::mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_USERINFOCACHE_H
|
@ -1,111 +0,0 @@
|
||||
#include "volumemanager.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
std::mutex VolumeManager::mutex_;
|
||||
std::weak_ptr<VolumeManager> 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> VolumeManager::globalInstance() {
|
||||
std::lock_guard<std::mutex> lock{mutex_};
|
||||
auto mon = globalInstance_.lock();
|
||||
if(mon == nullptr) {
|
||||
mon = std::make_shared<VolumeManager>();
|
||||
globalInstance_ = mon;
|
||||
}
|
||||
return mon;
|
||||
}
|
||||
|
||||
void VolumeManager::onGetGVolumeMonitorFinished() {
|
||||
auto job = static_cast<GetGVolumeMonitorJob*>(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
|
@ -1,237 +0,0 @@
|
||||
#ifndef FM2_VOLUMEMANAGER_H
|
||||
#define FM2_VOLUMEMANAGER_H
|
||||
|
||||
#include "../libfmqtglobals.h"
|
||||
#include <QObject>
|
||||
#include <gio/gio.h>
|
||||
#include "gioptrs.h"
|
||||
#include "filepath.h"
|
||||
#include "iconinfo.h"
|
||||
#include "job.h"
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
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<const IconInfo> 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<const IconInfo> 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<Volume>& volumes() const {
|
||||
return volumes_;
|
||||
}
|
||||
|
||||
const std::vector<Mount>& mounts() const {
|
||||
return mounts_;
|
||||
}
|
||||
|
||||
static std::shared_ptr<VolumeManager> 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<Volume> volumes_;
|
||||
std::vector<Mount> mounts_;
|
||||
|
||||
static std::mutex mutex_;
|
||||
static std::weak_ptr<VolumeManager> globalInstance_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FM2_VOLUMEMANAGER_H
|
@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 "createnewmenu.h"
|
||||
#include "folderview.h"
|
||||
#include "utilities.h"
|
||||
#include "core/iconinfo.h"
|
||||
#include "core/templates.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Fm {
|
||||
|
||||
|
||||
class TemplateAction: public QAction {
|
||||
public:
|
||||
TemplateAction(std::shared_ptr<const TemplateItem> item, QObject* parent):
|
||||
QAction(parent) {
|
||||
setTemplateItem(std::move(item));
|
||||
}
|
||||
|
||||
const std::shared_ptr<const TemplateItem> templateItem() const {
|
||||
return templateItem_;
|
||||
}
|
||||
|
||||
void setTemplateItem(std::shared_ptr<const TemplateItem> item) {
|
||||
templateItem_ = std::move(item);
|
||||
auto mimeType = templateItem_->mimeType();
|
||||
setText(QString("%1 (%2)").arg(templateItem_->displayName()).arg(mimeType->desc()));
|
||||
setIcon(templateItem_->icon()->qicon());
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<const TemplateItem> templateItem_;
|
||||
};
|
||||
|
||||
|
||||
CreateNewMenu::CreateNewMenu(QWidget* dialogParent, Fm::FilePath dirPath, QWidget* parent):
|
||||
QMenu(parent),
|
||||
dialogParent_(dialogParent),
|
||||
dirPath_(std::move(dirPath)),
|
||||
templateSeparator_{nullptr},
|
||||
templates_{Templates::globalInstance()} {
|
||||
|
||||
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);
|
||||
|
||||
// add more items to "Create New" menu from templates
|
||||
connect(templates_.get(), &Templates::itemAdded, this, &CreateNewMenu::addTemplateItem);
|
||||
connect(templates_.get(), &Templates::itemChanged, this, &CreateNewMenu::updateTemplateItem);
|
||||
connect(templates_.get(), &Templates::itemRemoved, this, &CreateNewMenu::removeTemplateItem);
|
||||
templates_->forEachItem([this](const std::shared_ptr<const TemplateItem>& item) {
|
||||
addTemplateItem(item);
|
||||
});
|
||||
}
|
||||
|
||||
CreateNewMenu::~CreateNewMenu() {
|
||||
}
|
||||
|
||||
void CreateNewMenu::onCreateNewFile() {
|
||||
if(dirPath_) {
|
||||
createFileOrFolder(CreateNewTextFile, dirPath_);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateNewMenu::onCreateNewFolder() {
|
||||
if(dirPath_) {
|
||||
createFileOrFolder(CreateNewFolder, dirPath_);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateNewMenu::onCreateNew() {
|
||||
TemplateAction* action = static_cast<TemplateAction*>(sender());
|
||||
if(dirPath_) {
|
||||
createFileOrFolder(CreateWithTemplate, dirPath_, action->templateItem().get(), dialogParent_);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateNewMenu::addTemplateItem(const std::shared_ptr<const TemplateItem> &item) {
|
||||
if(!templateSeparator_) {
|
||||
templateSeparator_= addSeparator();
|
||||
}
|
||||
auto mimeType = item->mimeType();
|
||||
/* we support directories differently */
|
||||
if(mimeType->isDir()) {
|
||||
return;
|
||||
}
|
||||
QAction* action = new TemplateAction{item, this};
|
||||
connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNew);
|
||||
addAction(action);
|
||||
}
|
||||
|
||||
void CreateNewMenu::updateTemplateItem(const std::shared_ptr<const TemplateItem> &oldItem, const std::shared_ptr<const TemplateItem> &newItem) {
|
||||
auto allActions = actions();
|
||||
auto separatorPos = allActions.indexOf(templateSeparator_);
|
||||
// all items after the separator are templates
|
||||
for(auto i = separatorPos + 1; i < allActions.size(); ++i) {
|
||||
auto action = static_cast<TemplateAction*>(allActions[i]);
|
||||
if(action->templateItem() == oldItem) {
|
||||
// update the menu item
|
||||
action->setTemplateItem(newItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateNewMenu::removeTemplateItem(const std::shared_ptr<const TemplateItem> &item) {
|
||||
if(!templateSeparator_) {
|
||||
return;
|
||||
}
|
||||
auto allActions = actions();
|
||||
auto separatorPos = allActions.indexOf(templateSeparator_);
|
||||
// all items after the separator are templates
|
||||
for(auto i = separatorPos + 1; i < allActions.size(); ++i) {
|
||||
auto action = static_cast<TemplateAction*>(allActions[i]);
|
||||
if(action->templateItem() == item) {
|
||||
// delete the action from the menu
|
||||
removeAction(action);
|
||||
allActions.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// no more template items. remove the separator
|
||||
if(separatorPos == allActions.size() - 1) {
|
||||
removeAction(templateSeparator_);
|
||||
templateSeparator_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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_CUSTOMACTION_P_H
|
||||
#define FM_CUSTOMACTION_P_H
|
||||
|
||||
#include <QAction>
|
||||
#include "customactions/fileaction.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
class CustomAction : public QAction {
|
||||
public:
|
||||
explicit CustomAction(std::shared_ptr<const FileActionItem> 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<const FileActionItem>& item() const {
|
||||
return item_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<const FileActionItem> item_;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif
|
@ -1,615 +0,0 @@
|
||||
#include "fileaction.h"
|
||||
#include <unordered_map>
|
||||
#include <QDebug>
|
||||
|
||||
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<const char*, shared_ptr<FileActionObject>, 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<FileActionCondition> {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<const FileInfo> 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<FileActionProfile>(kf, *profile_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<FileActionProfile> 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> FileActionItem::fromActionObject(std::shared_ptr<FileActionObject> action_obj, const FileInfoList& files) {
|
||||
std::shared_ptr<FileActionItem> item;
|
||||
if(action_obj->type == FileActionType::MENU) {
|
||||
auto menu = static_pointer_cast<FileActionMenu>(action_obj);
|
||||
if(menu->match(files)) {
|
||||
item = make_shared<FileActionItem>(menu, files);
|
||||
// eliminate empty menus
|
||||
if(item->children.empty()) {
|
||||
item = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// handle profiles here
|
||||
auto action = static_pointer_cast<FileAction>(action_obj);
|
||||
auto profile = action->match(files);
|
||||
if(profile) {
|
||||
item = make_shared<FileActionItem>(action, profile, files);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
FileActionItem::FileActionItem(std::shared_ptr<FileAction> _action, std::shared_ptr<FileActionProfile> _profile, const FileInfoList& files):
|
||||
FileActionItem{static_pointer_cast<FileActionObject>(_action), files} {
|
||||
profile = _profile;
|
||||
}
|
||||
|
||||
FileActionItem::FileActionItem(std::shared_ptr<FileActionMenu> menu, const FileInfoList& files):
|
||||
FileActionItem{static_pointer_cast<FileActionObject>(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<FileActionObject> _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<FileActionObject> action;
|
||||
if(strcmp(type.get(), "Action") == 0) {
|
||||
action = static_pointer_cast<FileActionObject>(make_shared<FileAction>(kf));
|
||||
// stdout.printf("load action: %s\n", id);
|
||||
}
|
||||
else if(strcmp(type.get(), "Menu") == 0) {
|
||||
action = static_pointer_cast<FileActionObject>(make_shared<FileActionMenu>(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<const FileActionItem> a, std::shared_ptr<const FileActionItem> 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<FileActionMenu>(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<FileActionMenu>(action_obj);
|
||||
menu->cached_children.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(items.begin(), items.end(), compare_items);
|
||||
return items;
|
||||
}
|
||||
|
||||
} // namespace Fm
|
@ -1,156 +0,0 @@
|
||||
#ifndef FILEACTION_H
|
||||
#define FILEACTION_H
|
||||
|
||||
#include <glib.h>
|
||||
#include <string>
|
||||
|
||||
#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<const FileInfo> 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<FileActionCondition> condition;
|
||||
|
||||
// values cached during menu generation
|
||||
bool has_parent;
|
||||
};
|
||||
|
||||
|
||||
class FileAction: public FileActionObject {
|
||||
public:
|
||||
|
||||
FileAction(GKeyFile* kf);
|
||||
|
||||
std::shared_ptr<FileActionProfile> match(const FileInfoList& files) const;
|
||||
|
||||
int target; // bitwise or of FileActionTarget
|
||||
CStrPtr toolbar_label;
|
||||
|
||||
// FIXME: currently we don't support dynamic profiles
|
||||
std::vector<std::shared_ptr<FileActionProfile>> 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<std::shared_ptr<FileActionObject>> cached_children;
|
||||
};
|
||||
|
||||
|
||||
class FileActionItem {
|
||||
public:
|
||||
|
||||
static std::shared_ptr<FileActionItem> fromActionObject(std::shared_ptr<FileActionObject> action_obj, const FileInfoList &files);
|
||||
|
||||
FileActionItem(std::shared_ptr<FileAction> _action, std::shared_ptr<FileActionProfile> _profile, const FileInfoList& files);
|
||||
|
||||
FileActionItem(std::shared_ptr<FileActionMenu> menu, const FileInfoList& files);
|
||||
|
||||
FileActionItem(std::shared_ptr<FileActionObject> _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<FileAction*>(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<std::shared_ptr<const FileActionItem>>& get_sub_items() const {
|
||||
return children;
|
||||
}
|
||||
|
||||
static bool compare_items(std::shared_ptr<const FileActionItem> a, std::shared_ptr<const FileActionItem> b);
|
||||
static std::vector<std::shared_ptr<const FileActionItem>> get_actions_for_files(const FileInfoList& files);
|
||||
|
||||
std::string name;
|
||||
std::string desc;
|
||||
std::string icon;
|
||||
std::shared_ptr<FileActionObject> action;
|
||||
std::shared_ptr<FileActionProfile> profile; // only used by action item
|
||||
std::vector<std::shared_ptr<const FileActionItem>> children; // only used by menu
|
||||
};
|
||||
|
||||
typedef std::vector<std::shared_ptr<const FileActionItem>> FileActionItemList;
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
|
||||
#endif // FILEACTION_H
|
@ -1,508 +0,0 @@
|
||||
#include "fileactioncondition.h"
|
||||
#include "fileaction.h"
|
||||
#include <string>
|
||||
|
||||
|
||||
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 = g_str_has_suffix(folder, "/") ? string(folder) + "*" // be tolerant
|
||||
: string(folder) + "/*";
|
||||
pattern = g_pattern_spec_new(pat_str.c_str());
|
||||
}
|
||||
for(auto& fi: files) {
|
||||
auto dirname = fi->isDir() ? fi->path().toString() // also match "folder" itself
|
||||
: fi->dirPath().toString();
|
||||
// Since the pattern ends with "/*", if the directory path is equal to "folder",
|
||||
// it should end with "/" to be found as a match. Adding "/" is always harmless.
|
||||
auto path_str = string(dirname.get()) + "/";
|
||||
if(g_pattern_match_string(pattern, path_str.c_str())) { // 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
|
@ -1,123 +0,0 @@
|
||||
#ifndef FILEACTIONCONDITION_H
|
||||
#define FILEACTIONCONDITION_H
|
||||
|
||||
#include <glib.h>
|
||||
#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
|
@ -1,124 +0,0 @@
|
||||
#include "fileactionprofile.h"
|
||||
#include "fileaction.h"
|
||||
#include <QDebug>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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<FileActionCondition>(kf, group_name.c_str());
|
||||
}
|
||||
|
||||
|
||||
bool FileActionProfile::launch_once(GAppLaunchContext* /*ctx*/, std::shared_ptr<const FileInfo> 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);
|
||||
}
|
||||
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
#ifndef FILEACTIONPROFILE_H
|
||||
#define FILEACTIONPROFILE_H
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#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<const FileInfo> 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<FileActionCondition> condition;
|
||||
};
|
||||
|
||||
} // namespace Fm
|
||||
|
||||
#endif // FILEACTIONPROFILE_H
|
@ -1,233 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
||||
*
|
||||
* 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 "dirtreemodel.h"
|
||||
#include "dirtreemodelitem.h"
|
||||
#include <QDebug>
|
||||
#include "core/fileinfojob.h"
|
||||
|
||||
namespace Fm {
|
||||
|
||||
DirTreeModel::DirTreeModel(QObject* parent):
|
||||
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<Fm::FileInfoJob*>(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);
|
||||
}
|
||||
|
||||
QVariant DirTreeModel::data(const QModelIndex& index, int role) const {
|
||||
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();
|
||||
}
|
||||
|
||||
int DirTreeModel::columnCount(const QModelIndex& /*parent*/) const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DirTreeModel::rowCount(const QModelIndex& parent) const {
|
||||
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 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();
|
||||
}
|
||||
|
||||
QModelIndex DirTreeModel::index(int row, int column, const QModelIndex& parent) const {
|
||||
if(row >= 0 && column >= 0 && column == 0) {
|
||||
if(!parent.isValid()) { // root items
|
||||
if(static_cast<size_t>(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<size_t>(row) < parentItem->children_.size()) {
|
||||
const DirTreeModelItem* item = parentItem->children_.at(row);
|
||||
return createIndex(row, column, (void*)item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return QModelIndex(); // invalid index
|
||||
}
|
||||
|
||||
bool DirTreeModel::hasChildren(const QModelIndex& parent) const {
|
||||
DirTreeModelItem* item = itemFromIndex(parent);
|
||||
return item ? !item->isPlaceHolder() : true;
|
||||
}
|
||||
|
||||
QModelIndex DirTreeModel::indexFromItem(DirTreeModelItem* item) const {
|
||||
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(std::shared_ptr<const Fm::FileInfo> 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<DirTreeModelItem*>(index.internalPointer());
|
||||
}
|
||||
|
||||
QModelIndex DirTreeModel::indexFromPath(const Fm::FilePath &path) const {
|
||||
DirTreeModelItem* item = itemFromPath(path);
|
||||
return item ? item->index() : QModelIndex();
|
||||
}
|
||||
|
||||
DirTreeModelItem* DirTreeModel::itemFromPath(const Fm::FilePath &path) const {
|
||||
for(DirTreeModelItem* const item : qAsConst(rootItems_)) {
|
||||
if(item->fileInfo_ && path == item->fileInfo_->path()) {
|
||||
return item;
|
||||
}
|
||||
else {
|
||||
DirTreeModelItem* child = item->childFromPath(path, true);
|
||||
if(child) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
void DirTreeModel::loadRow(const QModelIndex& index) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
bool DirTreeModel::isLoaded(const QModelIndex& index) {
|
||||
DirTreeModelItem* item = itemFromIndex(index);
|
||||
return item ? item->loaded_ : false;
|
||||
}
|
||||
|
||||
QIcon DirTreeModel::icon(const QModelIndex& index) {
|
||||
DirTreeModelItem* item = itemFromIndex(index);
|
||||
return item ? item->icon_ : QIcon();
|
||||
}
|
||||
|
||||
std::shared_ptr<const Fm::FileInfo> DirTreeModel::fileInfo(const QModelIndex& index) {
|
||||
DirTreeModelItem* item = itemFromIndex(index);
|
||||
return item ? item->fileInfo_ : nullptr;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void DirTreeModel::setShowHidden(bool show_hidden) {
|
||||
showHidden_ = show_hidden;
|
||||
for(DirTreeModelItem* const item : qAsConst(rootItems_)) {
|
||||
item->setShowHidden(show_hidden);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Fm
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue