Added new versions, mangled some symbols
This commit is contained in:
		
							parent
							
								
									f95b471116
								
							
						
					
					
						commit
						0829311d25
					
				
							
								
								
									
										4
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								AUTHORS
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| Upstream Authors: | ||||
|     LXQt team: http://lxqt.org | ||||
|     LXQt team: https://lxqt.org | ||||
|     Hong Jen Yee (PCMan) <pcman.tw@gmail.com> | ||||
| 
 | ||||
| Copyright: | ||||
|     Copyright (c) 2013-2017 LXQt team | ||||
|     Copyright (c) 2013-2018 LXQt team | ||||
|  | ||||
							
								
								
									
										81
									
								
								CHANGELOG
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								CHANGELOG
									
									
									
									
									
								
							| @ -1,7 +1,86 @@ | ||||
| 
 | ||||
| libfm-qt-0.12.0 / 2017-10-21 | ||||
| libfm-qt-0.13.0 / 2018-05-21 | ||||
| ============================ | ||||
| 
 | ||||
|   * 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 | ||||
|  | ||||
| @ -4,27 +4,27 @@ 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 12) | ||||
| set(LIBFM_QT_VERSION_MINOR 13) | ||||
| set(LIBFM_QT_VERSION_PATCH 0) | ||||
| set(LIBFM_QT_VERSION ${LIBFM_QT_VERSION_MAJOR}.${LIBFM_QT_VERSION_MINOR}.${LIBFM_QT_VERSION_PATCH}) | ||||
| 
 | ||||
| list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") | ||||
| 
 | ||||
| # We use the libtool versioning scheme for the internal so name, "current:revision:age" | ||||
| # http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info | ||||
| # 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: 4, revision: 0, age: 1 => version: 3.1.0 | ||||
| set(LIBFM_QT_LIB_VERSION "3.1.0") | ||||
| set(LIBFM_QT_LIB_SOVERSION "3") | ||||
| # 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.2") | ||||
| 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.4.0") | ||||
| set(REQUIRED_LXQT_BUILD_TOOLS_VERSION "0.5.0") | ||||
| 
 | ||||
| if (NOT CMAKE_BUILD_TYPE) | ||||
|     set(CMAKE_BUILD_TYPE Release) | ||||
| @ -46,11 +46,13 @@ option(UPDATE_TRANSLATIONS "Update source translation translations/*.ts files" O | ||||
| 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( | ||||
| @ -70,8 +72,7 @@ add_subdirectory(data) | ||||
| 
 | ||||
| # add Doxygen support to generate API docs | ||||
| # References: | ||||
| # http://majewsky.wordpress.com/2010/08/14/tip-of-the-day-cmake-and-doxygen/ | ||||
| # http://www.bluequartz.net/projects/EIM_Segmentation/SoftwareDocumentation/html/usewithcmakeproject.html | ||||
| # 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) | ||||
|  | ||||
							
								
								
									
										12
									
								
								Doxyfile.in
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								Doxyfile.in
									
									
									
									
									
								
							| @ -20,7 +20,7 @@ | ||||
| # that follow. The default is UTF-8 which is also the encoding used for all | ||||
| # text before the first occurrence of this tag. Doxygen uses libiconv (or the | ||||
| # iconv built into libc) for the transcoding. See | ||||
| # http://www.gnu.org/software/libiconv for the list of possible encodings. | ||||
| # https://www.gnu.org/software/libiconv for the list of possible encodings. | ||||
| 
 | ||||
| DOXYFILE_ENCODING = UTF-8 | ||||
| 
 | ||||
| @ -247,7 +247,7 @@ EXTENSION_MAPPING = | ||||
| 
 | ||||
| # If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all | ||||
| # comments according to the Markdown format, which allows for more readable | ||||
| # documentation. See http://daringfireball.net/projects/markdown/ for details. | ||||
| # documentation. See https://daringfireball.net/projects/markdown/ for details. | ||||
| # The output of markdown processing is further processed by doxygen, so you | ||||
| # can mix doxygen, HTML, and XML commands with Markdown formatting. | ||||
| # Disable only in case of backward compatibilities issues. | ||||
| @ -587,7 +587,7 @@ LAYOUT_FILE = | ||||
| # containing the references data. This must be a list of .bib files. The | ||||
| # .bib extension is automatically appended if omitted. Using this command | ||||
| # requires the bibtex tool to be installed. See also | ||||
| # http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style | ||||
| # https://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style | ||||
| # of the bibliography can be controlled using LATEX_BIB_STYLE. To use this | ||||
| # feature you need bibtex and perl available in the search path. Do not use | ||||
| # file names with spaces, bibtex cannot handle them. | ||||
| @ -659,7 +659,7 @@ INPUT = "@PROJECT_SOURCE_DIR@/src" | ||||
| # This tag can be used to specify the character encoding of the source files | ||||
| # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is | ||||
| # also the default input encoding. Doxygen uses libiconv (or the iconv built | ||||
| # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for | ||||
| # into libc) for the transcoding. See https://www.gnu.org/software/libiconv for | ||||
| # the list of possible encodings. | ||||
| 
 | ||||
| INPUT_ENCODING = UTF-8 | ||||
| @ -931,7 +931,7 @@ HTML_EXTRA_FILES = | ||||
| # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. | ||||
| # Doxygen will adjust the colors in the style sheet and background images | ||||
| # according to this color. Hue is specified as an angle on a colorwheel, | ||||
| # see http://en.wikipedia.org/wiki/Hue for more information. | ||||
| # see https://en.wikipedia.org/wiki/Hue for more information. | ||||
| # For instance the value 0 represents red, 60 is yellow, 120 is green, | ||||
| # 180 is cyan, 240 is blue, 300 purple, and 360 is red again. | ||||
| # The allowed range is 0 to 359. | ||||
| @ -984,7 +984,7 @@ HTML_INDEX_NUM_ENTRIES = 100 | ||||
| # directory and running "make install" will install the docset in | ||||
| # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find | ||||
| # it at startup. | ||||
| # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html | ||||
| # See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html | ||||
| # for more information. | ||||
| 
 | ||||
| GENERATE_DOCSET = NO | ||||
|  | ||||
							
								
								
									
										36
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								README.md
									
									
									
									
									
								
							| @ -2,26 +2,44 @@ | ||||
| 
 | ||||
| ## Overview | ||||
| 
 | ||||
| libfm-qt is the Qt port of libfm, a library providing components to build desktop file managers which belongs to [LXDE](http://lxde.org). | ||||
| 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.    | ||||
| 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/lxde/lxqt-build-tools) and optionally Git to pull latest VCS checkouts. The localization files were outsourced to repository [lxqt-l10n](https://github.com/lxde/lxqt-l10n) so the corresponding dependencies are needed, too. Please refer to this repository's `README.md` for further information.    | ||||
| 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.    | ||||
| 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.    | ||||
| 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.    | ||||
| 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/lxde/pcmanfm-qt/issues. | ||||
| Issues should go to the tracker of PCManFM-Qt at | ||||
| https://github.com/lxqt/pcmanfm-qt/issues. | ||||
|  | ||||
| @ -75,3 +75,6 @@ desktop_id=terminology.desktop | ||||
| open_arg=-e | ||||
| noclose_arg=--hold -e | ||||
| desktop_id=termite.desktop | ||||
| 
 | ||||
| [kitty] | ||||
| desktop_id=kitty.desktop | ||||
|  | ||||
							
								
								
									
										11
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								debian/changelog
									
									
									
									
										vendored
									
									
								
							| @ -1,8 +1,15 @@ | ||||
| libfm-qt (0.13.0-1) experimental; urgency=medium | ||||
| 
 | ||||
|   * | ||||
|   * Cherry-picking upstream release 0.13.0. | ||||
|   * Removed build dependency libglib2.0-dev, thrown in via lxqt-build-tools | ||||
|   * Bumped build dependency lxqt-build-tools to >= 0.5.0~ | ||||
|   * Renamed libfm-qt3 -> libfm-qt5. soname bumped | ||||
|   * Added Breaks and replaces for libfm-qt3 | ||||
|   * Bumped years in copyright | ||||
|   * Added symbols and removed two not used old (internal) ones, no soname bump | ||||
|     needed. | ||||
| 
 | ||||
|  -- Alf Gaida <agaida@siduction.org>  Mon, 21 May 2018 16:44:53 +0200 | ||||
|  -- Alf Gaida <agaida@siduction.org>  Wed, 23 May 2018 20:58:31 +0200 | ||||
| 
 | ||||
| libfm-qt (0.12.0-17) unstable; urgency=medium | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										402
									
								
								debian/libfm-qt3.symbols → debian/libfm-qt5.symbols
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										402
									
								
								debian/libfm-qt3.symbols → debian/libfm-qt5.symbols
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | ||||
| libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
| libfm-qt.so.5 libfm-qt5 #MINVER# | ||||
|  (c++)"Fm::AppChooserComboBox::AppChooserComboBox(QWidget*)@Base" 0.10.0 | ||||
|  (c++)"Fm::AppChooserComboBox::isChanged() const@Base" 0.12.0 | ||||
|  (c++)"Fm::AppChooserComboBox::metaObject() const@Base" 0.10.0 | ||||
| @ -37,7 +37,35 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::AppMenuView::selectionChanged(QItemSelection const&, QItemSelection const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::AppMenuView::staticMetaObject@Base" 0.10.0 | ||||
|  (c++)"Fm::AppMenuView::~AppMenuView()@Base" 0.10.0 | ||||
|  (c++)"Fm::Archiver::Archiver()@Base" 0.13.0~ | ||||
|  (c++)"Fm::Archiver::allArchivers()@Base" 0.13.0~ | ||||
|  (c++)"Fm::Archiver::allArchivers_@Base" 0.13.0~ | ||||
|  (c++)"Fm::Archiver::createArchive(_GAppLaunchContext*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Archiver::defaultArchiver()@Base" 0.13.0~ | ||||
|  (c++)"Fm::Archiver::defaultArchiver_@Base" 0.13.0~ | ||||
|  (c++)"Fm::Archiver::extractArchives(_GAppLaunchContext*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Archiver::extractArchivesTo(_GAppLaunchContext*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&, Fm::FilePath const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Archiver::isMimeTypeSupported(char const*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Archiver::launchProgram(_GAppLaunchContext*, char const*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&, Fm::FilePath const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Archiver::setDefaultArchiver(Fm::Archiver*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Archiver::setDefaultArchiverByName(char const*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::BasicFileLauncher()@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::ask(char const*, char* const*, int)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::askExecFile(std::shared_ptr<Fm::FileInfo const> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::chooseApp(Fm::FileInfoList const&, char const*, Fm::GErrorPtr&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::handleShortcut(std::shared_ptr<Fm::FileInfo const> const&, _GAppLaunchContext*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::launchDesktopEntry(char const*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&, _GAppLaunchContext*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::launchDesktopEntry(std::shared_ptr<Fm::FileInfo const> const&, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&, _GAppLaunchContext*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::launchExecutable(std::shared_ptr<Fm::FileInfo const> const&, _GAppLaunchContext*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::launchFiles(Fm::FileInfoList const&, _GAppLaunchContext*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::launchPaths(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, _GAppLaunchContext*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::launchWithApp(_GAppInfo*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&, _GAppLaunchContext*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::launchWithDefaultApp(std::shared_ptr<Fm::FileInfo const> const&, _GAppLaunchContext*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::openFolder(_GAppLaunchContext*, Fm::FileInfoList const&, Fm::GErrorPtr&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::showError(_GAppLaunchContext*, Fm::GErrorPtr&, Fm::FilePath const&, std::shared_ptr<Fm::FileInfo const> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::BasicFileLauncher::~BasicFileLauncher()@Base" 0.13.0~ | ||||
|  (c++)"Fm::BookmarkAction::BookmarkAction(std::shared_ptr<Fm::BookmarkItem const>, QObject*)@Base" 0.12.0 | ||||
|  (c++)"Fm::BookmarkItem::BookmarkItem(Fm::FilePath const&, QString)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Bookmarks::Bookmarks(QObject*)@Base" 0.12.0 | ||||
|  (c++)"Fm::Bookmarks::changed()@Base" 0.12.0 | ||||
|  (c++)"Fm::Bookmarks::globalInstance()@Base" 0.12.0 | ||||
| @ -82,30 +110,20 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::ColorButton::setColor(QColor const&)@Base" 0.10.0 | ||||
|  (c++)"Fm::ColorButton::staticMetaObject@Base" 0.10.0 | ||||
|  (c++)"Fm::ColorButton::~ColorButton()@Base" 0.10.0 | ||||
|  (c++)"Fm::CopyJob::CopyJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&&, Fm::FilePath const&&, Fm::CopyJob::Mode)@Base" 0.12.0 | ||||
|  (c++)"Fm::CopyJob::CopyJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&, Fm::FilePath const&, Fm::CopyJob::Mode)@Base" 0.12.0 | ||||
|  (c++)"Fm::CopyJob::copyDir(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo>, Fm::FilePath const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::CopyJob::copyPath(Fm::FilePath const&, Fm::FilePath const&, char const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::CopyJob::copyPath(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, Fm::FilePath const&, char const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::CopyJob::copyRegularFile(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo>, Fm::FilePath const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::CopyJob::copySpecialFile(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo>, Fm::FilePath const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::CopyJob::exec()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"Fm::CopyJob::gfileProgressCallback(long, long, Fm::CopyJob*)@Base" 0.12.0 | ||||
|  (c++|arch= !amd64 !arm64 !mips64el !ppc64el !s390x !alpha !ia64 !kfreebsd-amd64 !ppc64 !riscv64 !sparc64 )"Fm::CopyJob::gfileProgressCallback(long long, long long, Fm::CopyJob*)@Base" 0.12.0 | ||||
|  (c++)"Fm::CopyJob::makeDir(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo>, Fm::FilePath const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::CopyJob::metaObject() const@Base" 0.12.0 | ||||
|  (c++)"Fm::CopyJob::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.12.0 | ||||
|  (c++)"Fm::CopyJob::qt_metacast(char const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::CopyJob::staticMetaObject@Base" 0.12.0 | ||||
|  (c++)"Fm::CreateNewMenu::CreateNewMenu(QWidget*, Fm::FilePath, QWidget*)@Base" 0.12.0 | ||||
|  (c++)"Fm::CreateNewMenu::addTemplateItem(std::shared_ptr<Fm::TemplateItem const> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::CreateNewMenu::metaObject() const@Base" 0.10.0 | ||||
|  (c++)"Fm::CreateNewMenu::onCreateNew()@Base" 0.10.0 | ||||
|  (c++)"Fm::CreateNewMenu::onCreateNewFile()@Base" 0.10.0 | ||||
|  (c++)"Fm::CreateNewMenu::onCreateNewFolder()@Base" 0.10.0 | ||||
|  (c++)"Fm::CreateNewMenu::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.10.0 | ||||
|  (c++)"Fm::CreateNewMenu::qt_metacast(char const*)@Base" 0.10.0 | ||||
|  (c++)"Fm::CreateNewMenu::removeTemplateItem(std::shared_ptr<Fm::TemplateItem const> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::CreateNewMenu::staticMetaObject@Base" 0.10.0 | ||||
|  (c++)"Fm::CreateNewMenu::updateTemplateItem(std::shared_ptr<Fm::TemplateItem const> const&, std::shared_ptr<Fm::TemplateItem const> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::CreateNewMenu::~CreateNewMenu()@Base" 0.10.0 | ||||
|  (c++)"Fm::DeleteJob::DeleteJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::DeleteJob::DeleteJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >&&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::DeleteJob::deleteDirContent(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo>)@Base" 0.12.0 | ||||
|  (c++)"Fm::DeleteJob::deleteFile(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo>)@Base" 0.12.0 | ||||
|  (c++)"Fm::DeleteJob::exec()@Base" 0.12.0 | ||||
| @ -113,6 +131,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::DeleteJob::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.12.0 | ||||
|  (c++)"Fm::DeleteJob::qt_metacast(char const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::DeleteJob::staticMetaObject@Base" 0.12.0 | ||||
|  (c++)"Fm::DeleteJob::~DeleteJob()@Base" 0.13.0~ | ||||
|  (c++)"Fm::DirListJob::DirListJob(Fm::FilePath const&, Fm::DirListJob::Flags, std::shared_ptr<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > const> const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::DirListJob::exec()@Base" 0.12.0 | ||||
|  (c++)"Fm::DirListJob::filesFound(Fm::FileInfoList&)@Base" 0.12.0 | ||||
| @ -206,8 +225,18 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::EditBookmarksDialog::qt_metacast(char const*)@Base" 0.10.0 | ||||
|  (c++)"Fm::EditBookmarksDialog::staticMetaObject@Base" 0.10.0 | ||||
|  (c++)"Fm::EditBookmarksDialog::~EditBookmarksDialog()@Base" 0.10.0 | ||||
|  (c++)"Fm::FileChangeAttrJob::FileChangeAttrJob()@Base" 0.12.0 | ||||
|  (c++)"Fm::FileChangeAttrJob::FileChangeAttrJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileChangeAttrJob::changeFileDisplayName(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, char const*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileChangeAttrJob::changeFileGroup(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, unsigned int)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileChangeAttrJob::changeFileHidden(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, bool)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileChangeAttrJob::changeFileIcon(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, Fm::GObjectPtr<_GIcon>&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileChangeAttrJob::changeFileMode(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, unsigned int, unsigned int)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileChangeAttrJob::changeFileOwner(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, unsigned int)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileChangeAttrJob::changeFileTargetUri(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, char const*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileChangeAttrJob::exec()@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileChangeAttrJob::handleError(Fm::GErrorPtr&, Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, Fm::Job::ErrorSeverity)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileChangeAttrJob::metaObject() const@Base" 0.12.0 | ||||
|  (c++)"Fm::FileChangeAttrJob::processFile(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileChangeAttrJob::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileChangeAttrJob::qt_metacast(char const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileChangeAttrJob::staticMetaObject@Base" 0.12.0 | ||||
| @ -270,7 +299,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::FileInfo::isExecutableType() const@Base" 0.12.0 | ||||
|  (c++)"Fm::FileInfo::setFromGFileInfo(Fm::GObjectPtr<_GFileInfo> const&, Fm::FilePath const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileInfo::~FileInfo()@Base" 0.12.0 | ||||
|  (c++)"Fm::FileInfoJob::FileInfoJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, Fm::FilePath, std::shared_ptr<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > const> const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileInfoJob::FileInfoJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, Fm::FilePath, std::shared_ptr<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > const> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileInfoJob::exec()@Base" 0.12.0 | ||||
|  (c++)"Fm::FileInfoJob::gotInfo(Fm::FilePath const&, std::shared_ptr<Fm::FileInfo const>&)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileInfoJob::metaObject() const@Base" 0.12.0 | ||||
| @ -281,15 +310,12 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::FileInfoList::isSameType() const@Base" 0.12.0 | ||||
|  (c++)"Fm::FileLauncher::FileLauncher()@Base" 0.10.0 | ||||
|  (c++)"Fm::FileLauncher::ask(char const*, char* const*, int)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileLauncher::error(_GAppLaunchContext*, _GError*, _FmPath*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileLauncher::execFile(_FmFileInfo*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileLauncher::funcs@Base" 0.10.0 | ||||
|  (c++)"Fm::FileLauncher::getApp(_GList*, _FmMimeType*, _GError**)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileLauncher::launchFiles(QWidget*, Fm::FileInfoList)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileLauncher::launchFiles(QWidget*, _GList*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileLauncher::launchPaths(QWidget*, _GList*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileLauncher::launchPaths(QWidget*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileLauncher::openFolder(_GAppLaunchContext*, _GList*, _GError**)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileLauncher::askExecFile(std::shared_ptr<Fm::FileInfo const> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileLauncher::chooseApp(Fm::FileInfoList const&, char const*, Fm::GErrorPtr&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileLauncher::launchFiles(QWidget*, Fm::FileInfoList const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileLauncher::launchPaths(QWidget*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileLauncher::openFolder(_GAppLaunchContext*, Fm::FileInfoList const&, Fm::GErrorPtr&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileLauncher::showError(_GAppLaunchContext*, Fm::GErrorPtr&, Fm::FilePath const&, std::shared_ptr<Fm::FileInfo const> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileLauncher::~FileLauncher()@Base" 0.12.0 | ||||
|  (c++)"Fm::FileLinkJob::FileLinkJob()@Base" 0.12.0 | ||||
|  (c++)"Fm::FileMenu::FileMenu(Fm::FileInfoList, std::shared_ptr<Fm::FileInfo const>, Fm::FilePath, bool, QString const&, QWidget*)@Base" 0.12.0 | ||||
| @ -321,37 +347,41 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::FileMonitor::qt_metacast(char const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileMonitor::staticMetaObject@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperation::FileOperation(Fm::FileOperation::Type, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, QObject*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperation::cancel()@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::changeAttrFiles(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, QWidget*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperation::copyFiles(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, Fm::FilePath, QWidget*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperation::copyFiles(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, QWidget*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::deleteFiles(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, bool, QWidget*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperation::disconnectJob()@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::finished()@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::handleFinish()@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::metaObject() const@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::moveFiles(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, Fm::FilePath, QWidget*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperation::onFileOpsJobAsk(_FmFileOpsJob*, char const*, char* const*, Fm::FileOperation*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::onFileOpsJobAskRename(_FmFileOpsJob*, _FmFileInfo*, _FmFileInfo*, char**, Fm::FileOperation*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::onFileOpsJobCancelled(_FmFileOpsJob*, Fm::FileOperation*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::onFileOpsJobCurFile(_FmFileOpsJob*, char const*, Fm::FileOperation*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::onFileOpsJobError(_FmFileOpsJob*, _GError*, FmJobErrorSeverity, Fm::FileOperation*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::onFileOpsJobFinished(_FmFileOpsJob*, Fm::FileOperation*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::onFileOpsJobPercent(_FmFileOpsJob*, unsigned int, Fm::FileOperation*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::onFileOpsJobPrepared(_FmFileOpsJob*, Fm::FileOperation*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::moveFiles(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, QWidget*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::onJobCancalled()@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::onJobError(Fm::GErrorPtr const&, Fm::Job::ErrorSeverity, Fm::Job::ErrorAction&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::onJobFileExists(Fm::FileInfo const&, Fm::FileInfo const&, Fm::FileOperationJob::FileExistsAction&, Fm::FilePath&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::onJobFinish()@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::onJobPrepared()@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::onUiTimeout()@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::qt_metacast(char const*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::run()@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::setChmod(unsigned int, unsigned int)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::setChown(unsigned int, unsigned int)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::setDestFiles(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::setDestination(Fm::FilePath)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperation::setRecursiveChattr(bool)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::showDialog()@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::staticMetaObject@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperation::symlinkFiles(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, Fm::FilePath, QWidget*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperation::symlinkFiles(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, QWidget*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperation::trashFiles(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, bool, QWidget*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperation::unTrashFiles(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, QWidget*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperation::~FileOperation()@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperationDialog::FileOperationDialog(Fm::FileOperation*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperationDialog::ask(QString, char* const*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperationDialog::askRename(_FmFileInfo*, _FmFileInfo*, QString&)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperationDialog::error(_GError*, FmJobErrorSeverity)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperationDialog::askRename(Fm::FileInfo const&, Fm::FileInfo const&, Fm::FilePath&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperationDialog::error(_GError*, Fm::Job::ErrorSeverity)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperationDialog::metaObject() const@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperationDialog::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperationDialog::qt_metacast(char const*)@Base" 0.10.0 | ||||
| @ -360,6 +390,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"Fm::FileOperationDialog::setDataTransferred(unsigned long, unsigned long)@Base" 0.12.0 | ||||
|  (c++|arch= !amd64 !arm64 !mips64el !ppc64el !s390x !alpha !ia64 !kfreebsd-amd64 !ppc64 !ppc64 !riscv64 !sparc64 )"Fm::FileOperationDialog::setDataTransferred(unsigned long long, unsigned long long)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperationDialog::setDestPath(Fm::FilePath const&)@Base" 0.12.0 | ||||
|  (optional|c++|arch= !i386 )"Fm::FileOperationDialog::setFilesProcessed(unsigned long, unsigned long)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperationDialog::setPercent(unsigned int)@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperationDialog::setPrepared()@Base" 0.10.0 | ||||
|  (c++)"Fm::FileOperationDialog::setRemainingTime(unsigned int)@Base" 0.10.0 | ||||
| @ -370,6 +401,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"Fm::FileOperationJob::addFinishedAmount(unsigned long, unsigned long)@Base" 0.12.0 | ||||
|  (c++|arch= !amd64 !arm64 !mips64el !ppc64el !s390x !alpha !ia64 !kfreebsd-amd64 !ppc64 !riscv64 !sparc64 )"Fm::FileOperationJob::addFinishedAmount(unsigned long long, unsigned long long)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperationJob::askRename(Fm::FileInfo const&, Fm::FileInfo const&, Fm::FilePath&)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperationJob::currentFile() const@Base" 0.13.0~ | ||||
|  (c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"Fm::FileOperationJob::currentFileProgress(Fm::FilePath&, unsigned long&, unsigned long&) const@Base" 0.12.0 | ||||
|  (c++|arch= !amd64 !arm64 !mips64el !ppc64el !s390x !alpha !ia64 !kfreebsd-amd64 !ppc64 !riscv64 !sparc64 )"Fm::FileOperationJob::currentFileProgress(Fm::FilePath&, unsigned long long&, unsigned long long&) const@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperationJob::fileExists(Fm::FileInfo const&, Fm::FileInfo const&, Fm::FileOperationJob::FileExistsAction&, Fm::FilePath&)@Base" 0.12.0 | ||||
| @ -377,6 +409,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++|arch= !amd64 !arm64 !mips64el !ppc64el !s390x !alpha !ia64 !kfreebsd-amd64 !ppc64 !riscv64 !sparc64 )"Fm::FileOperationJob::finishedAmount(unsigned long long&, unsigned long long&) const@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperationJob::metaObject() const@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperationJob::preparedToRun()@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperationJob::progress() const@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileOperationJob::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperationJob::qt_metacast(char const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileOperationJob::setCurrentFile(Fm::FilePath const&)@Base" 0.12.0 | ||||
| @ -427,6 +460,30 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::FileSystemInfoJob::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileSystemInfoJob::qt_metacast(char const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FileSystemInfoJob::staticMetaObject@Base" 0.12.0 | ||||
|  (c++)"Fm::FileTransferJob::FileTransferJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, Fm::FilePath const&, Fm::FileTransferJob::Mode)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::FileTransferJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, Fm::FileTransferJob::Mode)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::FileTransferJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >, Fm::FileTransferJob::Mode)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::copyDirContent(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo>, Fm::FilePath&, bool)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::copyFile(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, Fm::FilePath const&, char const*, bool)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::copyRegularFile(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, Fm::FilePath&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::copySpecialFile(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, Fm::FilePath&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::createShortcut(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, Fm::FilePath&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::createSymlink(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, Fm::FilePath&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::exec()@Base" 0.13.0~ | ||||
|  (optional|c++|arch= !i386 )"Fm::FileTransferJob::gfileCopyProgressCallback(long, long, Fm::FileTransferJob*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::handleError(Fm::GErrorPtr&, Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, Fm::FilePath&, int&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::linkFile(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, Fm::FilePath const&, char const*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::makeDir(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo>, Fm::FilePath&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::metaObject() const@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::moveFile(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, Fm::FilePath const&, char const*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::moveFileSameFs(Fm::FilePath const&, Fm::GObjectPtr<_GFileInfo> const&, Fm::FilePath&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::processPath(Fm::FilePath const&, Fm::FilePath const&, char const*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::qt_metacast(char const*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::setDestDirPath(Fm::FilePath const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::setDestPaths(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::setSrcPaths(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FileTransferJob::staticMetaObject@Base" 0.13.0~ | ||||
|  (c++)"Fm::Folder::Folder()@Base" 0.12.0 | ||||
|  (c++)"Fm::Folder::Folder(Fm::FilePath const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::Folder::cache_@Base" 0.12.0 | ||||
| @ -522,6 +579,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::FolderModel::dropMimeData(QMimeData const*, Qt::DropAction, int, int, QModelIndex const&)@Base" 0.10.0 | ||||
|  (c++)"Fm::FolderModel::fileInfoFromIndex(QModelIndex const&) const@Base" 0.10.0 | ||||
|  (c++)"Fm::FolderModel::fileSizeChanged(QModelIndex const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::FolderModel::filesAdded(Fm::FileInfoList)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FolderModel::findItemByFileInfo(Fm::FileInfo const*, int*)@Base" 0.12.0 | ||||
|  (c++)"Fm::FolderModel::findItemByName(char const*, int*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FolderModel::findItemByPath(Fm::FilePath const&, int*)@Base" 0.12.0 | ||||
| @ -597,8 +655,10 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::FolderView::prepareFolderMenu(Fm::FolderMenu*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FolderView::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.10.0 | ||||
|  (c++)"Fm::FolderView::qt_metacast(char const*)@Base" 0.10.0 | ||||
|  (c++)"Fm::FolderView::scrollSmoothly()@Base" 0.13.0~ | ||||
|  (c++)"Fm::FolderView::selChanged()@Base" 0.12.0 | ||||
|  (c++)"Fm::FolderView::selectAll()@Base" 0.10.0 | ||||
|  (c++)"Fm::FolderView::selectFiles(Fm::FileInfoList const&, bool)@Base" 0.13.0~ | ||||
|  (c++)"Fm::FolderView::selectedFilePaths() const@Base" 0.10.0 | ||||
|  (c++)"Fm::FolderView::selectedFiles() const@Base" 0.10.0 | ||||
|  (c++)"Fm::FolderView::selectedIndexes() const@Base" 0.10.0 | ||||
| @ -627,25 +687,15 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::IconInfo::IconInfo(char const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::IconInfo::cache_@Base" 0.12.0 | ||||
|  (c++)"Fm::IconInfo::emblems() const@Base" 0.12.0 | ||||
|  (c++)"Fm::IconInfo::fallbackQicon_@Base" 0.12.0 | ||||
|  (c++)"Fm::IconInfo::fallbackQicons_@Base" 0.13.0~ | ||||
|  (c++)"Fm::IconInfo::fromGIcon(Fm::GObjectPtr<_GIcon>)@Base" 0.12.0 | ||||
|  (c++)"Fm::IconInfo::fromName(char const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::IconInfo::internalQicon() const@Base" 0.12.0 | ||||
|  (c++)"Fm::IconInfo::mutex_@Base" 0.12.0 | ||||
|  (c++)"Fm::IconInfo::qicon(bool const&) const@Base" 0.12.0 | ||||
|  (c++)"Fm::IconInfo::qiconFromNames(char const* const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::IconInfo::qiconsFromNames(char const* const*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::IconInfo::updateQIcons()@Base" 0.12.0 | ||||
|  (c++)"Fm::IconInfo::~IconInfo()@Base" 0.12.0 | ||||
|  (c++)"Fm::IconTheme::IconTheme()@Base" 0.10.0 | ||||
|  (c++)"Fm::IconTheme::changed()@Base" 0.10.0 | ||||
|  (c++)"Fm::IconTheme::checkChanged()@Base" 0.10.0 | ||||
|  (c++)"Fm::IconTheme::eventFilter(QObject*, QEvent*)@Base" 0.10.0 | ||||
|  (c++)"Fm::IconTheme::instance()@Base" 0.10.0 | ||||
|  (c++)"Fm::IconTheme::metaObject() const@Base" 0.10.0 | ||||
|  (c++)"Fm::IconTheme::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.10.0 | ||||
|  (c++)"Fm::IconTheme::qt_metacast(char const*)@Base" 0.10.0 | ||||
|  (c++)"Fm::IconTheme::staticMetaObject@Base" 0.10.0 | ||||
|  (c++)"Fm::IconTheme::~IconTheme()@Base" 0.12.0 | ||||
|  (c++)"Fm::Job::Job()@Base" 0.12.0 | ||||
|  (c++)"Fm::Job::cancel()@Base" 0.12.0 | ||||
|  (c++)"Fm::Job::cancelled()@Base" 0.12.0 | ||||
| @ -676,12 +726,15 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::MountOperation::finished(_GError*)@Base" 0.10.0 | ||||
|  (c++)"Fm::MountOperation::handleFinish(_GError*)@Base" 0.10.0 | ||||
|  (c++)"Fm::MountOperation::metaObject() const@Base" 0.10.0 | ||||
|  (c++)"Fm::MountOperation::mountEnclosingVolume(Fm::FilePath const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::MountOperation::mountMountable(Fm::FilePath const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::MountOperation::onAbort(_GMountOperation*, Fm::MountOperation*)@Base" 0.10.0 | ||||
|  (c++)"Fm::MountOperation::onAskPassword(_GMountOperation*, char*, char*, char*, GAskPasswordFlags, Fm::MountOperation*)@Base" 0.10.0 | ||||
|  (c++)"Fm::MountOperation::onAskQuestion(_GMountOperation*, char*, char**, Fm::MountOperation*)@Base" 0.10.0 | ||||
|  (c++)"Fm::MountOperation::onEjectMountFinished(_GMount*, _GAsyncResult*, QPointer<Fm::MountOperation>*)@Base" 0.10.0 | ||||
|  (c++)"Fm::MountOperation::onEjectVolumeFinished(_GVolume*, _GAsyncResult*, QPointer<Fm::MountOperation>*)@Base" 0.10.0 | ||||
|  (c++)"Fm::MountOperation::onMountFileFinished(_GFile*, _GAsyncResult*, QPointer<Fm::MountOperation>*)@Base" 0.10.0 | ||||
|  (c++)"Fm::MountOperation::onMountMountableFinished(_GFile*, _GAsyncResult*, QPointer<Fm::MountOperation>*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::MountOperation::onMountVolumeFinished(_GVolume*, _GAsyncResult*, QPointer<Fm::MountOperation>*)@Base" 0.10.0 | ||||
|  (c++)"Fm::MountOperation::onShowProcesses(_GMountOperation*, char*, _GArray*, char**, Fm::MountOperation*)@Base" 0.10.0 | ||||
|  (c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"Fm::MountOperation::onShowUnmountProgress(_GMountOperation*, char*, long, long, Fm::MountOperation*)@Base" 0.12.0 | ||||
| @ -780,6 +833,16 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::PlacesModelVolumeItem::PlacesModelVolumeItem(_GVolume*)@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesModelVolumeItem::isMounted()@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesModelVolumeItem::update()@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesProxyModel::PlacesProxyModel(QObject*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesProxyModel::filterAcceptsRow(int, QModelIndex const&) const@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesProxyModel::metaObject() const@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesProxyModel::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesProxyModel::qt_metacast(char const*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesProxyModel::restoreHiddenItems(QSet<QString> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesProxyModel::setHidden(QString const&, bool)@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesProxyModel::showAll(bool)@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesProxyModel::staticMetaObject@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesProxyModel::~PlacesProxyModel()@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesView::PlacesView(QWidget*)@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::activateRow(int, QModelIndex const&)@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::chdirRequested(int, Fm::FilePath const&)@Base" 0.12.0 | ||||
| @ -787,6 +850,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::PlacesView::contextMenuEvent(QContextMenuEvent*)@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::dragMoveEvent(QDragMoveEvent*)@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::dropEvent(QDropEvent*)@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::hiddenItemSet(QString const&, bool)@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesView::metaObject() const@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::onClicked(QModelIndex const&)@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::onDeleteBookmark()@Base" 0.10.0 | ||||
| @ -803,9 +867,12 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::PlacesView::onRenameBookmark()@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::onUnmountMount()@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::onUnmountVolume()@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::proxyModel_@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesView::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::qt_metacast(char const*)@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::restoreHiddenItems(QSet<QString> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesView::setCurrentPath(Fm::FilePath)@Base" 0.12.0 | ||||
|  (c++)"Fm::PlacesView::showAll(bool)@Base" 0.13.0~ | ||||
|  (c++)"Fm::PlacesView::staticMetaObject@Base" 0.10.0 | ||||
|  (c++)"Fm::PlacesView::~PlacesView()@Base" 0.10.0 | ||||
|  (c++)"Fm::ProxyFolderModel::ProxyFolderModel(QObject*)@Base" 0.10.0 | ||||
| @ -821,6 +888,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::ProxyFolderModel::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.10.0 | ||||
|  (c++)"Fm::ProxyFolderModel::qt_metacast(char const*)@Base" 0.10.0 | ||||
|  (c++)"Fm::ProxyFolderModel::removeFilter(Fm::ProxyFolderModelFilter*)@Base" 0.10.0 | ||||
|  (c++)"Fm::ProxyFolderModel::setBackupAsHidden(bool)@Base" 0.13.0~ | ||||
|  (c++)"Fm::ProxyFolderModel::setCutFiles(QItemSelection const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::ProxyFolderModel::setFolderFirst(bool)@Base" 0.10.0 | ||||
|  (c++)"Fm::ProxyFolderModel::setShowHidden(bool)@Base" 0.10.0 | ||||
| @ -833,7 +901,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::ProxyFolderModel::staticMetaObject@Base" 0.10.0 | ||||
|  (c++)"Fm::ProxyFolderModel::updateFilters()@Base" 0.10.0 | ||||
|  (c++)"Fm::ProxyFolderModel::~ProxyFolderModel()@Base" 0.10.0 | ||||
|  (c++)"Fm::RenameDialog::RenameDialog(_FmFileInfo*, _FmFileInfo*, QWidget*, QFlags<Qt::WindowType>)@Base" 0.10.0 | ||||
|  (c++)"Fm::RenameDialog::RenameDialog(Fm::FileInfo const&, Fm::FileInfo const&, QWidget*, QFlags<Qt::WindowType>)@Base" 0.13.0~ | ||||
|  (c++)"Fm::RenameDialog::accept()@Base" 0.10.0 | ||||
|  (c++)"Fm::RenameDialog::metaObject() const@Base" 0.10.0 | ||||
|  (c++)"Fm::RenameDialog::onFileNameChanged(QString)@Base" 0.10.0 | ||||
| @ -847,6 +915,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::SidePane::SidePane(QWidget*)@Base" 0.10.0 | ||||
|  (c++)"Fm::SidePane::chdirRequested(int, Fm::FilePath const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::SidePane::createNewFolderRequested(Fm::FilePath const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::SidePane::hiddenPlaceSet(QString const&, bool)@Base" 0.13.0~ | ||||
|  (c++)"Fm::SidePane::initDirTree()@Base" 0.10.0 | ||||
|  (c++)"Fm::SidePane::metaObject() const@Base" 0.10.0 | ||||
|  (c++)"Fm::SidePane::modeByName(char const*)@Base" 0.10.0 | ||||
| @ -859,6 +928,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::SidePane::prepareFileMenu(Fm::FileMenu*)@Base" 0.10.0 | ||||
|  (c++)"Fm::SidePane::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.10.0 | ||||
|  (c++)"Fm::SidePane::qt_metacast(char const*)@Base" 0.10.0 | ||||
|  (c++)"Fm::SidePane::restoreHiddenPlaces(QSet<QString> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::SidePane::setCurrentPath(Fm::FilePath)@Base" 0.12.0 | ||||
|  (c++)"Fm::SidePane::setHomeDir(char const*)@Base" 0.10.0 | ||||
|  (c++)"Fm::SidePane::setIconSize(QSize)@Base" 0.10.0 | ||||
| @ -866,6 +936,23 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::SidePane::setShowHidden(bool)@Base" 0.10.0 | ||||
|  (c++)"Fm::SidePane::staticMetaObject@Base" 0.10.0 | ||||
|  (c++)"Fm::SidePane::~SidePane()@Base" 0.10.0 | ||||
|  (c++)"Fm::TemplateItem::TemplateItem(std::shared_ptr<Fm::FileInfo const>)@Base" 0.13.0~ | ||||
|  (c++)"Fm::TemplateItem::filePath() const@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::Templates()@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::addTemplateDir(char const*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::globalInstance()@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::globalInstance_@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::itemAdded(std::shared_ptr<Fm::TemplateItem const> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::itemChanged(std::shared_ptr<Fm::TemplateItem const> const&, std::shared_ptr<Fm::TemplateItem const> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::itemRemoved(std::shared_ptr<Fm::TemplateItem const> const&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::metaObject() const@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::onFilesAdded(Fm::FileInfoList&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::onFilesChanged(std::vector<std::pair<std::shared_ptr<Fm::FileInfo const>, std::shared_ptr<Fm::FileInfo const> >, std::allocator<std::pair<std::shared_ptr<Fm::FileInfo const>, std::shared_ptr<Fm::FileInfo const> > > >&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::onFilesRemoved(Fm::FileInfoList&)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::onTemplateDirRemoved()@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::qt_metacast(char const*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::Templates::staticMetaObject@Base" 0.13.0~ | ||||
|  (c++)"Fm::ThumbnailJob::ThumbnailJob(Fm::FileInfoList, int)@Base" 0.12.0 | ||||
|  (c++)"Fm::ThumbnailJob::exec()@Base" 0.12.0 | ||||
|  (c++)"Fm::ThumbnailJob::generateThumbnail(std::shared_ptr<Fm::FileInfo const> const&, Fm::FilePath const&, char const*, QString const&)@Base" 0.12.0 | ||||
| @ -898,15 +985,13 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::TotalSizeJob::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.12.0 | ||||
|  (c++)"Fm::TotalSizeJob::qt_metacast(char const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::TotalSizeJob::staticMetaObject@Base" 0.12.0 | ||||
|  (c++)"Fm::TrashJob::TrashJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&&)@Base" 0.12.0 | ||||
|  (c++)"Fm::TrashJob::TrashJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::TrashJob::TrashJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >)@Base" 0.13.0~ | ||||
|  (c++)"Fm::TrashJob::exec()@Base" 0.12.0 | ||||
|  (c++)"Fm::TrashJob::metaObject() const@Base" 0.12.0 | ||||
|  (c++)"Fm::TrashJob::qt_metacall(QMetaObject::Call, int, void**)@Base" 0.12.0 | ||||
|  (c++)"Fm::TrashJob::qt_metacast(char const*)@Base" 0.12.0 | ||||
|  (c++)"Fm::TrashJob::staticMetaObject@Base" 0.12.0 | ||||
|  (c++)"Fm::UntrashJob::UntrashJob()@Base" 0.12.0 | ||||
|  (c++)"Fm::UntrashJob::ensure_parent_dir(_GFile*)@Base" 0.12.0 | ||||
|  (c++)"Fm::UntrashJob::UntrashJob(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >)@Base" 0.13.0~ | ||||
|  (c++)"Fm::UntrashJob::exec()@Base" 0.12.0 | ||||
|  (c++)"Fm::UserInfoCache::UserInfoCache()@Base" 0.12.0 | ||||
|  (c++)"Fm::UserInfoCache::changed()@Base" 0.12.0 | ||||
| @ -943,9 +1028,9 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"Fm::VolumeManager::volumeRemoved(Fm::Volume const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::VolumeManager::~VolumeManager()@Base" 0.12.0 | ||||
|  (c++)"Fm::allKnownTerminals()@Base" 0.12.0 | ||||
|  (c++)"Fm::changeFileName(Fm::FilePath const&, QString const&, QWidget*)@Base" 0.12.0 | ||||
|  (c++)"Fm::changeFileName(Fm::FilePath const&, QString const&, QWidget*, bool)@Base" 0.13.0~ | ||||
|  (c++)"Fm::copyFilesToClipboard(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::createFileOrFolder(Fm::CreateFileType, Fm::FilePath, _FmTemplate*, QWidget*)@Base" 0.12.0 | ||||
|  (c++)"Fm::createFileOrFolder(Fm::CreateFileType, Fm::FilePath, Fm::TemplateItem const*, QWidget*)@Base" 0.13.0~ | ||||
|  (c++)"Fm::cutFilesToClipboard(std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > const&)@Base" 0.12.0 | ||||
|  (c++)"Fm::execModelessDialog(QDialog*)@Base" 0.10.0 | ||||
|  (c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"Fm::formatFileSize(unsigned long, bool)@Base" 0.12.0 | ||||
| @ -969,6 +1054,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"non-virtual thunk to Fm::AppMenuView::~AppMenuView()@Base" 0.10.0 | ||||
|  (c++)"non-virtual thunk to Fm::ColorButton::~ColorButton()@Base" 0.10.0 | ||||
|  (c++)"non-virtual thunk to Fm::CreateNewMenu::~CreateNewMenu()@Base" 0.10.0 | ||||
|  (c++)"non-virtual thunk to Fm::DeleteJob::~DeleteJob()@Base" 0.13.0~ | ||||
|  (c++)"non-virtual thunk to Fm::DirTreeView::~DirTreeView()@Base" 0.10.0 | ||||
|  (c++)"non-virtual thunk to Fm::EditBookmarksDialog::~EditBookmarksDialog()@Base" 0.10.0 | ||||
|  (c++)"non-virtual thunk to Fm::FileDialog::~FileDialog()@Base" 0.12.0 | ||||
| @ -988,14 +1074,15 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"non-virtual thunk to Fm::ThumbnailJob::~ThumbnailJob()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> > >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const>, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_find_before_node(unsigned long, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned long) const@Base" 0.12.0 | ||||
|  (c++|arch= !amd64 !arm64 !mips64el !ppc64el !s390x !alpha !ia64 !kfreebsd-amd64 !ppc64 !riscv64 !sparc64 )"std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> > >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const>, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_find_before_node(unsigned int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned int) const@Base" 0.12.0 | ||||
|  (c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> > >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const>, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> >, true>*)@Base" 0.12.0 | ||||
|  (c++|arch= !amd64 !arm64 !mips64el !ppc64el !s390x !alpha !ia64 !kfreebsd-amd64 !ppc64 !riscv64 !sparc64 )"std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> > >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const>, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_insert_unique_node(unsigned int, unsigned int, std::__detail::_Hash_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> >, true>*)@Base" 0.12.0 | ||||
|  (c++)"std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> > >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const>, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::clear()@Base" 0.12.0 | ||||
|  (optional=gcc8|c++|arch= !i386 )"std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> > >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const>, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::erase(std::__detail::_Node_const_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> >, false, true>)@Base" 0.13.0~ | ||||
|  (c++)"std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> > >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const>, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::find(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@Base" 0.12.0 | ||||
|  (c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"std::_Hashtable<unsigned int, std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> >, std::allocator<std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> > >, std::__detail::_Select1st, std::equal_to<unsigned int>, std::hash<unsigned int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> >, false>*)@Base" 0.12.0 | ||||
|  (c++|arch= !amd64 !arm64 !mips64el !ppc64el !s390x !alpha !ia64 !kfreebsd-amd64 !ppc64 !riscv64 !sparc64 )"std::_Hashtable<unsigned int, std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> >, std::allocator<std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> > >, std::__detail::_Select1st, std::equal_to<unsigned int>, std::hash<unsigned int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned int, unsigned int, std::__detail::_Hash_node<std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> >, false>*)@Base" 0.12.0 | ||||
|  (c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"std::_Hashtable<unsigned int, std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> >, std::allocator<std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> > >, std::__detail::_Select1st, std::equal_to<unsigned int>, std::hash<unsigned int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> >, false>*)@Base" 0.12.0 | ||||
|  (c++|arch= !amd64 !arm64 !mips64el !ppc64el !s390x !alpha !ia64 !kfreebsd-amd64 !ppc64 !riscv64 !sparc64 )"std::_Hashtable<unsigned int, std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> >, std::allocator<std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> > >, std::__detail::_Select1st, std::equal_to<unsigned int>, std::hash<unsigned int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned int, unsigned int, std::__detail::_Hash_node<std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> >, false>*)@Base" 0.12.0 | ||||
|  (optional=gcc8|c++|arch= !i386 )"std::_Hashtable<unsigned int, std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> >, std::allocator<std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> > >, std::__detail::_Select1st, std::equal_to<unsigned int>, std::hash<unsigned int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> >, false>*, unsigned long)@Base" 0.13.0~ | ||||
|  (optional=gcc8|c++)"std::_Hashtable<unsigned int, std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> >, std::allocator<std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> > >, std::__detail::_Select1st, std::equal_to<unsigned int>, std::hash<unsigned int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> >, false>*, unsigned long)@Base" 0.13.0~ | ||||
|  (optional=gcc7|c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"std::_Hashtable<unsigned int, std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> >, std::allocator<std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> > >, std::__detail::_Select1st, std::equal_to<unsigned int>, std::hash<unsigned int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> >, false>*)@Base" 0.12.0 | ||||
|  (optional=gcc7|c++|arch= !amd64 !arm64 !mips64el !ppc64el !s390x !alpha !ia64 !kfreebsd-amd64 !ppc64 !riscv64 !sparc64 )"std::_Hashtable<unsigned int, std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> >, std::allocator<std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> > >, std::__detail::_Select1st, std::equal_to<unsigned int>, std::hash<unsigned int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned int, unsigned int, std::__detail::_Hash_node<std::pair<unsigned int const, std::shared_ptr<Fm::GroupInfo const> >, false>*)@Base" 0.12.0 | ||||
|  (optional=gcc7|c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"std::_Hashtable<unsigned int, std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> >, std::allocator<std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> > >, std::__detail::_Select1st, std::equal_to<unsigned int>, std::hash<unsigned int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned long, unsigned long, std::__detail::_Hash_node<std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> >, false>*)@Base" 0.12.0 | ||||
|  (optional=gcc7|c++|arch= !amd64 !arm64 !mips64el !ppc64el !s390x !alpha !ia64 !kfreebsd-amd64 !ppc64 !riscv64 !sparc64 )"std::_Hashtable<unsigned int, std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> >, std::allocator<std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> > >, std::__detail::_Select1st, std::equal_to<unsigned int>, std::hash<unsigned int>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true> >::_M_insert_unique_node(unsigned int, unsigned int, std::__detail::_Hash_node<std::pair<unsigned int const, std::shared_ptr<Fm::UserInfo const> >, false>*)@Base" 0.12.0 | ||||
|  (c++)"std::_Rb_tree<unsigned int, unsigned int, std::_Identity<unsigned int>, std::less<unsigned int>, std::allocator<unsigned int> >::_M_erase(std::_Rb_tree_node<unsigned int>*)@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)1>::_M_destroy()@Base" 0.12.0 | ||||
| @ -1074,78 +1161,62 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::MimeType, std::allocator<Fm::MimeType>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::MimeType, std::allocator<Fm::MimeType>, (__gnu_cxx::_Lock_policy)1>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::MimeType, std::allocator<Fm::MimeType>, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::MimeType, std::allocator<Fm::MimeType>, (__gnu_cxx::_Lock_policy)1>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)1>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)1>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)1>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)1>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)1>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)1>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)1>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)1>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)1>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)1>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)1>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)1>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)1>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)1>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)1>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)1>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)1>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)1>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)1>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)1>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++|arch= !arm64 !armel !armhf !i386 !mips !mipsel !mips64el !ppc64el !alpha !hppa !hurd-i386 !ia64 !kfreebsd-i386 !m68k !powerpc !ppc64 !riscv64 !sh4 !sparc64 !x32 )"std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned long, char const*, __va_list_tag*), unsigned long, char const*, ...)@Base" 0.12.0 | ||||
|  (optional|c++|arch= !amd64 !arm64 !armel !armhf !i386 !mips !mips64el !mipsel !ppc64el !s390x !hppa !ia64 !kfreebsd-amd64 !kfreebsd-i386 !m68k !powerpc !ppc64 !riscv64 !sh4 !x32 )"std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned long, char const*, __va_list_tag), unsigned long, char const*, ...)@Base" 0.12.0 | ||||
|  (optional|c++|arch= !amd64 !arm64 !i386 !mips !mips64el !mipsel !ppc64el !s390x !alpha !hppa !hurd-i386 !ia64 !kfreebsd-amd64 !kfreebsd-i386 !m68k !powerpc !ppc64 !riscv64 !sh4 !x32 )"std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned int, char const*, std::__va_list), unsigned int, char const*, ...)@Base" 0.12.0 | ||||
|  (optional|c++|arch= !amd64 !arm64 !armel !armhf !i386 !mips64el !ppc64el !s390x !alpha !hurd-i386 !ia64 !kfreebsd-amd64 !kfreebsd-i386 !powerpc !ppc64 !riscv64 !sh4 !x32 )"std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned int, char const*, void*), unsigned int, char const*, ...)@Base" 0.12.0 | ||||
|  (optional|c++|arch= !amd64 !arm64 !armel !armhf !i386 !mips !mips64el !mipsel !s390x !alpha !hppa !ia64 !kfreebsd-amd64 !kfreebsd-i386 !m68k !powerpc !riscv64 !sh4 !x32 )"std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned long, char const*, char*), unsigned long, char const*, ...)@Base" 0.12.0 | ||||
|  (optional|c++|arch= !amd64 !arm64 !armel !armhf !mips !mipsel !mips64el !ppc64el !s390x !alpha !ia64 !kfreebsd-amd64 !hppa !m68k !powerpc !ppc64 !riscv64 !sh4 !sparc64 !x32 )"std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned int, char const*, char*), unsigned int, char const*, ...)@Base" 0.12.0 | ||||
|  (optional|c++|arch= !amd64 !arm64 !armel !armhf !i386 !mips !mips64el !mipsel !ppc64el !s390x !alpha !hppa !ia64 !kfreebsd-amd64 !kfreebsd-i386 !m68k !ppc64 !riscv64 !sh4 )"std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned int, char const*, __va_list_tag*), unsigned int, char const*, ...)@Base" 0.12.0 | ||||
|  (optional|c++|arch= !amd64 !arm64 !armel !armhf !i386 !mips !mips64el !mipsel !ppc64el !s390x !alpha !hppa !ia64 !kfreebsd-amd64 !kfreebsd-i386 !m68k !powerpc !ppc64 !riscv64 !x32)"std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned int, char const*, __va_list_tag), unsigned int, char const*, ...)@Base" 0.12.0 | ||||
|  (optional|c++|arch= !amd64 !arm64 !armel !armhf !i386 !mips !mipsel !ppc64el !s390x !alpha !hppa !kfreebsd-amd64 !kfreebsd-i386 !m68k !powerpc !ppc64 !sh4 !x32 )"std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned long, char const*, void*), unsigned long, char const*, ...)@Base" 0.12.0 | ||||
|  (optional|c++|arch= !amd64 !armel !armhf !i386 !mips !mipsel !mips64el !ppc64el !s390x !alpha !hppa !hurd-i386 !ia64 !kfreebsd-amd64 !kfreebsd-i386 !m68k !powerpc !ppc64 !riscv64 !sh4 !sparc64 !x32 )"std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned long, char const*, std::__va_list), unsigned long, char const*, ...)@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.13.0~ | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.13.0~ | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.13.0~ | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.13.0~ | ||||
|  (optional|c++)"std::_Sp_counted_ptr_inplace<Fm::PlacesProxyModel, std::allocator<Fm::PlacesProxyModel>, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.13.0~ | ||||
|  (optional|c++)"std::_Sp_counted_ptr_inplace<Fm::PlacesProxyModel, std::allocator<Fm::PlacesProxyModel>, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.13.0~ | ||||
|  (optional|c++)"std::_Sp_counted_ptr_inplace<Fm::PlacesProxyModel, std::allocator<Fm::PlacesProxyModel>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.13.0~ | ||||
|  (optional|c++)"std::_Sp_counted_ptr_inplace<Fm::PlacesProxyModel, std::allocator<Fm::PlacesProxyModel>, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.13.0~ | ||||
|  (optional|c++)"std::_Sp_counted_ptr_inplace<Fm::TemplateItem, std::allocator<Fm::TemplateItem>, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.13.0~ | ||||
|  (optional|c++)"std::_Sp_counted_ptr_inplace<Fm::TemplateItem, std::allocator<Fm::TemplateItem>, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.13.0~ | ||||
|  (optional|c++)"std::_Sp_counted_ptr_inplace<Fm::TemplateItem, std::allocator<Fm::TemplateItem>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.13.0~ | ||||
|  (optional|c++)"std::_Sp_counted_ptr_inplace<Fm::TemplateItem, std::allocator<Fm::TemplateItem>, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.13.0~ | ||||
|  (optional|c++)"std::_Sp_counted_ptr_inplace<Fm::Templates, std::allocator<Fm::Templates>, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.13.0~ | ||||
|  (optional|c++)"std::_Sp_counted_ptr_inplace<Fm::Templates, std::allocator<Fm::Templates>, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.13.0~ | ||||
|  (optional|c++)"std::_Sp_counted_ptr_inplace<Fm::Templates, std::allocator<Fm::Templates>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.13.0~ | ||||
|  (optional|c++)"std::_Sp_counted_ptr_inplace<Fm::Templates, std::allocator<Fm::Templates>, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.13.0~ | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)2>::_M_destroy()@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)2>::_M_dispose()@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter(std::type_info const&)@Base" 0.12.0 | ||||
|  (c++)"std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)2>::~_Sp_counted_ptr_inplace()@Base" 0.12.0 | ||||
|  (c++)"std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned long, char const*, __va_list_tag*), unsigned long, char const*, ...)@Base" 0.12.0 | ||||
|  (c++)"std::__detail::_Map_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::shared_ptr<Fm::FileInfo const> > >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const>, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true>, true>::operator[](std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@Base" 0.12.0 | ||||
|  (c++)"std::__detail::_Map_base<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, Fm::FileInfoList>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, Fm::FileInfoList> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true>, true>::operator[](std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&)@Base" 0.13.0~ | ||||
|  (c++)"std::pair<std::_Rb_tree_iterator<unsigned int>, bool> std::_Rb_tree<unsigned int, unsigned int, std::_Identity<unsigned int>, std::less<unsigned int>, std::allocator<unsigned int> >::_M_insert_unique<unsigned int>(unsigned int&&)@Base" 0.12.0 | ||||
|  (optional=gcc8|c++)"std::pair<std::__detail::_Node_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, char const*>, false, true>, bool> std::_Hashtable<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, char const*>, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, char const*> >, std::__detail::_Select1st, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::_M_emplace<std::pair<char const*, char const*> >(std::integral_constant<bool, true>, std::pair<char const*, char const*>&&)@Base" 0.13.0~ | ||||
|  (c++)"std::vector<Fm::BrowseHistoryItem, std::allocator<Fm::BrowseHistoryItem> >::_M_erase(__gnu_cxx::__normal_iterator<Fm::BrowseHistoryItem*, std::vector<Fm::BrowseHistoryItem, std::allocator<Fm::BrowseHistoryItem> > >)@Base" 0.12.0 | ||||
|  (c++)"std::vector<Fm::DirTreeModelItem*, std::allocator<Fm::DirTreeModelItem*> >::_M_erase(__gnu_cxx::__normal_iterator<Fm::DirTreeModelItem**, std::vector<Fm::DirTreeModelItem*, std::allocator<Fm::DirTreeModelItem*> > >)@Base" 0.12.0 | ||||
|  (c++)"std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >::_M_erase(__gnu_cxx::__normal_iterator<Fm::FilePath*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > >, __gnu_cxx::__normal_iterator<Fm::FilePath*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > >)@Base" 0.12.0 | ||||
|  (c++|arch= !i386 !mips !mipsel !s390x !alpha !hurd-i386 !kfreebsd-i386 !powerpc !ppc64 )"std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >::insert(__gnu_cxx::__normal_iterator<Fm::FilePath const*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > >, Fm::FilePath const&)@Base" 0.12.0 | ||||
|  (optional=gcc8|c++)"std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >::reserve(unsigned long)@Base" 0.13.0~ | ||||
|  (c++)"std::vector<std::shared_ptr<Fm::BookmarkItem const>, std::allocator<std::shared_ptr<Fm::BookmarkItem const> > >::_M_erase(__gnu_cxx::__normal_iterator<std::shared_ptr<Fm::BookmarkItem const>*, std::vector<std::shared_ptr<Fm::BookmarkItem const>, std::allocator<std::shared_ptr<Fm::BookmarkItem const> > > >)@Base" 0.12.0 | ||||
|  (c++)"std::vector<std::shared_ptr<Fm::BookmarkItem const>, std::allocator<std::shared_ptr<Fm::BookmarkItem const> > >::_M_insert_rval(__gnu_cxx::__normal_iterator<std::shared_ptr<Fm::BookmarkItem const> const*, std::vector<std::shared_ptr<Fm::BookmarkItem const>, std::allocator<std::shared_ptr<Fm::BookmarkItem const> > > >, std::shared_ptr<Fm::BookmarkItem const>&&)@Base" 0.12.0 | ||||
|  (c++)"std::vector<std::shared_ptr<Fm::FileInfo const>, std::allocator<std::shared_ptr<Fm::FileInfo const> > >::operator=(std::vector<std::shared_ptr<Fm::FileInfo const>, std::allocator<std::shared_ptr<Fm::FileInfo const> > > const&)@Base" 0.12.0 | ||||
|  (c++|arch= !armel !armhf !i386 !mips !mipsel !hppa !hurd-i386 !kfreebsd-i386 !m68k !powerpc !sh4 !x32 )"std::vector<std::shared_ptr<Fm::FileInfo const>, std::allocator<std::shared_ptr<Fm::FileInfo const> > >::reserve(unsigned long)@Base" 0.12.0 | ||||
|  (optional|c++|arch= !amd64 !arm64 !mips64el !ppc64el !s390x !ia64 !kfreebsd-amd64 !alpha !ppc64 !riscv64 !sparc64 )"std::vector<std::shared_ptr<Fm::FileInfo const>, std::allocator<std::shared_ptr<Fm::FileInfo const> > >::reserve(unsigned int)@Base" 0.12.0 | ||||
|  (c++)"std::vector<std::shared_ptr<Fm::TemplateItem>, std::allocator<std::shared_ptr<Fm::TemplateItem> > >::_M_erase(__gnu_cxx::__normal_iterator<std::shared_ptr<Fm::TemplateItem>*, std::vector<std::shared_ptr<Fm::TemplateItem>, std::allocator<std::shared_ptr<Fm::TemplateItem> > > >, __gnu_cxx::__normal_iterator<std::shared_ptr<Fm::TemplateItem>*, std::vector<std::shared_ptr<Fm::TemplateItem>, std::allocator<std::shared_ptr<Fm::TemplateItem> > > >)@Base" 0.13.0~ | ||||
|  (c++)"std::vector<std::shared_ptr<Fm::Thumbnailer>, std::allocator<std::shared_ptr<Fm::Thumbnailer> > >::~vector()@Base" 0.12.0 | ||||
|  (c++)"std::vector<std::unique_ptr<Fm::Archiver, std::default_delete<Fm::Archiver> >, std::allocator<std::unique_ptr<Fm::Archiver, std::default_delete<Fm::Archiver> > > >::~vector()@Base" 0.13.0~ | ||||
|  (c++)"typeinfo for Fm::AppChooserComboBox@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::AppChooserDialog@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::AppMenuView@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::BasicFileLauncher@Base" 0.13.0~ | ||||
|  (c++)"typeinfo for Fm::BookmarkAction@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::Bookmarks@Base" 0.12.0 | ||||
|  (c++)"typeinfo for Fm::BrowseHistory@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::CachedFolderModel@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::ColorButton@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::CopyJob@Base" 0.12.0 | ||||
|  (c++)"typeinfo for Fm::CreateNewMenu@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::DeleteJob@Base" 0.12.0 | ||||
|  (c++)"typeinfo for Fm::DirListJob@Base" 0.12.0 | ||||
| @ -1167,6 +1238,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"typeinfo for Fm::FilePropsDialog@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::FileSearchDialog@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::FileSystemInfoJob@Base" 0.12.0 | ||||
|  (c++)"typeinfo for Fm::FileTransferJob@Base" 0.13.0~ | ||||
|  (c++)"typeinfo for Fm::Folder@Base" 0.12.0 | ||||
|  (c++)"typeinfo for Fm::FolderItemDelegate@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::FolderMenu@Base" 0.10.0 | ||||
| @ -1174,7 +1246,6 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"typeinfo for Fm::FolderModelItem@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::FolderView@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::FontButton@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::IconTheme@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::Job@Base" 0.12.0 | ||||
|  (c++)"typeinfo for Fm::MountOperation@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::PathBar@Base" 0.11.2 | ||||
| @ -1185,11 +1256,13 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"typeinfo for Fm::PlacesModelItem@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::PlacesModelMountItem@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::PlacesModelVolumeItem@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::PlacesProxyModel@Base" 0.13.0~ | ||||
|  (c++)"typeinfo for Fm::PlacesView@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::ProxyFolderModel@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::ProxyFolderModelFilter@Base" 0.12.0 | ||||
|  (c++)"typeinfo for Fm::RenameDialog@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::SidePane@Base" 0.10.0 | ||||
|  (c++)"typeinfo for Fm::Templates@Base" 0.13.0~ | ||||
|  (c++)"typeinfo for Fm::ThumbnailJob@Base" 0.12.0 | ||||
|  (c++)"typeinfo for Fm::TotalSizeJob@Base" 0.12.0 | ||||
|  (c++)"typeinfo for Fm::TrashJob@Base" 0.12.0 | ||||
| @ -1219,28 +1292,24 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++|arch=  armel riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<Fm::IconInfo, std::allocator<Fm::IconInfo>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<Fm::MimeType, std::allocator<Fm::MimeType>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<Fm::MimeType, std::allocator<Fm::MimeType>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"typeinfo for std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (optional|c++|arch=armel riscv64 )"typeinfo for __gnu_cxx::__mutex@Base" 0.12.0 | ||||
|  (optional|c++|arch=armel riscv64 )"typeinfo name for __gnu_cxx::__mutex@Base" 0.12.0 | ||||
|  (c++)"typeinfo for std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++)"typeinfo for std::_Sp_counted_ptr_inplace<Fm::PlacesProxyModel, std::allocator<Fm::PlacesProxyModel>, (__gnu_cxx::_Lock_policy)2>@Base" 0.13.0~ | ||||
|  (c++)"typeinfo for std::_Sp_counted_ptr_inplace<Fm::TemplateItem, std::allocator<Fm::TemplateItem>, (__gnu_cxx::_Lock_policy)2>@Base" 0.13.0~ | ||||
|  (c++)"typeinfo for std::_Sp_counted_ptr_inplace<Fm::Templates, std::allocator<Fm::Templates>, (__gnu_cxx::_Lock_policy)2>@Base" 0.13.0~ | ||||
|  (c++)"typeinfo for std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++)"typeinfo for std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++)"typeinfo for std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++)"typeinfo for std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++)"typeinfo for std::_Sp_make_shared_tag@Base" 0.12.0 | ||||
|  (c++)"typeinfo name for Fm::AppChooserComboBox@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::AppChooserDialog@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::AppMenuView@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::BasicFileLauncher@Base" 0.13.0~ | ||||
|  (c++)"typeinfo name for Fm::BookmarkAction@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::Bookmarks@Base" 0.12.0 | ||||
|  (c++)"typeinfo name for Fm::BrowseHistory@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::CachedFolderModel@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::ColorButton@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::CopyJob@Base" 0.12.0 | ||||
|  (c++)"typeinfo name for Fm::CreateNewMenu@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::DeleteJob@Base" 0.12.0 | ||||
|  (c++)"typeinfo name for Fm::DirListJob@Base" 0.12.0 | ||||
| @ -1262,6 +1331,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"typeinfo name for Fm::FilePropsDialog@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::FileSearchDialog@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::FileSystemInfoJob@Base" 0.12.0 | ||||
|  (c++)"typeinfo name for Fm::FileTransferJob@Base" 0.13.0~ | ||||
|  (c++)"typeinfo name for Fm::Folder@Base" 0.12.0 | ||||
|  (c++)"typeinfo name for Fm::FolderItemDelegate@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::FolderMenu@Base" 0.10.0 | ||||
| @ -1269,7 +1339,6 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"typeinfo name for Fm::FolderModelItem@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::FolderView@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::FontButton@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::IconTheme@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::Job@Base" 0.12.0 | ||||
|  (c++)"typeinfo name for Fm::MountOperation@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::PathBar@Base" 0.11.2 | ||||
| @ -1280,11 +1349,13 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"typeinfo name for Fm::PlacesModelItem@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::PlacesModelMountItem@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::PlacesModelVolumeItem@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::PlacesProxyModel@Base" 0.13.0~ | ||||
|  (c++)"typeinfo name for Fm::PlacesView@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::ProxyFolderModel@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::ProxyFolderModelFilter@Base" 0.12.0 | ||||
|  (c++)"typeinfo name for Fm::RenameDialog@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::SidePane@Base" 0.10.0 | ||||
|  (c++)"typeinfo name for Fm::Templates@Base" 0.13.0~ | ||||
|  (c++)"typeinfo name for Fm::ThumbnailJob@Base" 0.12.0 | ||||
|  (c++)"typeinfo name for Fm::TotalSizeJob@Base" 0.12.0 | ||||
|  (c++)"typeinfo name for Fm::TrashJob@Base" 0.12.0 | ||||
| @ -1315,9 +1386,10 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++|arch= !armel !riscv64 )"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::MimeType, std::allocator<Fm::MimeType>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::MimeType, std::allocator<Fm::MimeType>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++)"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::PlacesProxyModel, std::allocator<Fm::PlacesProxyModel>, (__gnu_cxx::_Lock_policy)2>@Base" 0.13.0~ | ||||
|  (c++)"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::TemplateItem, std::allocator<Fm::TemplateItem>, (__gnu_cxx::_Lock_policy)2>@Base" 0.13.0~ | ||||
|  (c++)"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::Templates, std::allocator<Fm::Templates>, (__gnu_cxx::_Lock_policy)2>@Base" 0.13.0~ | ||||
|  (c++)"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>@Base" 0.13.0~ | ||||
|  (c++|arch= !armel !riscv64 )"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"typeinfo name for std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
| @ -1325,11 +1397,15 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++|arch= !armel !riscv64 )"typeinfo name for std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"typeinfo name for std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++)"typeinfo name for std::_Sp_make_shared_tag@Base" 0.12.0 | ||||
|  (optional=gcc8|c++)"void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char const*>(char const*, char const*, std::forward_iterator_tag)@Base" 0.13.0~ | ||||
|  (optional=gcc8|c++)"void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char*>(char*, char*, std::forward_iterator_tag)@Base" 0.13.0~ | ||||
|  (c++)"void std::vector<Fm::BrowseHistoryItem, std::allocator<Fm::BrowseHistoryItem> >::_M_realloc_insert<Fm::BrowseHistoryItem>(__gnu_cxx::__normal_iterator<Fm::BrowseHistoryItem*, std::vector<Fm::BrowseHistoryItem, std::allocator<Fm::BrowseHistoryItem> > >, Fm::BrowseHistoryItem&&)@Base" 0.12.0 | ||||
|  (optional|c++)"void std::vector<Fm::DirTreeModelItem*, std::allocator<Fm::DirTreeModelItem*> >::_M_realloc_insert<Fm::DirTreeModelItem*>(__gnu_cxx::__normal_iterator<Fm::DirTreeModelItem**, std::vector<Fm::DirTreeModelItem*, std::allocator<Fm::DirTreeModelItem*> > >, Fm::DirTreeModelItem*&&)@Base" 0.12.0 | ||||
|  (optional=gcc8|c++)"void std::vector<Fm::DirTreeModelItem*, std::allocator<Fm::DirTreeModelItem*> >::emplace_back<Fm::DirTreeModelItem*>(Fm::DirTreeModelItem*&&)@Base" 0.13.0~ | ||||
|  (c++)"void std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >::_M_range_insert<__gnu_cxx::__normal_iterator<Fm::FilePath const*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > > >(__gnu_cxx::__normal_iterator<Fm::FilePath*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > >, __gnu_cxx::__normal_iterator<Fm::FilePath const*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > >, __gnu_cxx::__normal_iterator<Fm::FilePath const*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > >, std::forward_iterator_tag)@Base" 0.12.0 | ||||
|  (c++)"void std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >::_M_realloc_insert<Fm::FilePath const&>(__gnu_cxx::__normal_iterator<Fm::FilePath*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > >, Fm::FilePath const&)@Base" 0.12.0 | ||||
|  (c++)"void std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >::_M_realloc_insert<Fm::FilePath&>(__gnu_cxx::__normal_iterator<Fm::FilePath*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > >, Fm::FilePath&)@Base" 0.13.0~ | ||||
|  (c++)"void std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >::_M_realloc_insert<Fm::FilePath>(__gnu_cxx::__normal_iterator<Fm::FilePath*, std::vector<Fm::FilePath, std::allocator<Fm::FilePath> > >, Fm::FilePath&&)@Base" 0.12.0 | ||||
|  (c++)"void std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >::emplace_back<Fm::FilePath&>(Fm::FilePath&)@Base" 0.13.0~ | ||||
|  (c++)"void std::vector<Fm::FilePath, std::allocator<Fm::FilePath> >::emplace_back<Fm::FilePath>(Fm::FilePath&&)@Base" 0.12.0 | ||||
|  (c++)"void std::vector<Fm::Mount, std::allocator<Fm::Mount> >::_M_realloc_insert<Fm::Mount>(__gnu_cxx::__normal_iterator<Fm::Mount*, std::vector<Fm::Mount, std::allocator<Fm::Mount> > >, Fm::Mount&&)@Base" 0.12.0 | ||||
|  (c++)"void std::vector<Fm::Mount, std::allocator<Fm::Mount> >::emplace_back<Fm::Mount>(Fm::Mount&&)@Base" 0.12.0 | ||||
| @ -1343,16 +1419,20 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"void std::vector<std::shared_ptr<Fm::BookmarkItem const>, std::allocator<std::shared_ptr<Fm::BookmarkItem const> > >::_M_realloc_insert<std::shared_ptr<Fm::BookmarkItem const> >(__gnu_cxx::__normal_iterator<std::shared_ptr<Fm::BookmarkItem const>*, std::vector<std::shared_ptr<Fm::BookmarkItem const>, std::allocator<std::shared_ptr<Fm::BookmarkItem const> > > >, std::shared_ptr<Fm::BookmarkItem const>&&)@Base" 0.12.0 | ||||
|  (c++)"void std::vector<std::shared_ptr<Fm::FileInfo const>, std::allocator<std::shared_ptr<Fm::FileInfo const> > >::_M_realloc_insert<std::shared_ptr<Fm::FileInfo const> >(__gnu_cxx::__normal_iterator<std::shared_ptr<Fm::FileInfo const>*, std::vector<std::shared_ptr<Fm::FileInfo const>, std::allocator<std::shared_ptr<Fm::FileInfo const> > > >, std::shared_ptr<Fm::FileInfo const>&&)@Base" 0.12.0 | ||||
|  (c++)"void std::vector<std::shared_ptr<Fm::FileInfo const>, std::allocator<std::shared_ptr<Fm::FileInfo const> > >::_M_realloc_insert<std::shared_ptr<Fm::FileInfo const> const&>(__gnu_cxx::__normal_iterator<std::shared_ptr<Fm::FileInfo const>*, std::vector<std::shared_ptr<Fm::FileInfo const>, std::allocator<std::shared_ptr<Fm::FileInfo const> > > >, std::shared_ptr<Fm::FileInfo const> const&)@Base" 0.12.0 | ||||
|  (c++)"void std::vector<std::shared_ptr<Fm::FileInfo const>, std::allocator<std::shared_ptr<Fm::FileInfo const> > >::emplace_back<std::shared_ptr<Fm::FileInfo const> const&>(std::shared_ptr<Fm::FileInfo const> const&)@Base" 0.13.0~ | ||||
|  (c++)"void std::vector<std::shared_ptr<Fm::Folder>, std::allocator<std::shared_ptr<Fm::Folder> > >::_M_realloc_insert<std::shared_ptr<Fm::Folder> >(__gnu_cxx::__normal_iterator<std::shared_ptr<Fm::Folder>*, std::vector<std::shared_ptr<Fm::Folder>, std::allocator<std::shared_ptr<Fm::Folder> > > >, std::shared_ptr<Fm::Folder>&&)@Base" 0.13.0~ | ||||
|  (c++)"void std::vector<std::shared_ptr<Fm::TemplateItem>, std::allocator<std::shared_ptr<Fm::TemplateItem> > >::_M_realloc_insert<std::shared_ptr<Fm::TemplateItem> >(__gnu_cxx::__normal_iterator<std::shared_ptr<Fm::TemplateItem>*, std::vector<std::shared_ptr<Fm::TemplateItem>, std::allocator<std::shared_ptr<Fm::TemplateItem> > > >, std::shared_ptr<Fm::TemplateItem>&&)@Base" 0.13.0~ | ||||
|  (c++)"void std::vector<std::shared_ptr<Fm::Thumbnailer>, std::allocator<std::shared_ptr<Fm::Thumbnailer> > >::_M_realloc_insert<std::shared_ptr<Fm::Thumbnailer> >(__gnu_cxx::__normal_iterator<std::shared_ptr<Fm::Thumbnailer>*, std::vector<std::shared_ptr<Fm::Thumbnailer>, std::allocator<std::shared_ptr<Fm::Thumbnailer> > > >, std::shared_ptr<Fm::Thumbnailer>&&)@Base" 0.12.0 | ||||
|  (c++)"void std::vector<std::unique_ptr<Fm::Archiver, std::default_delete<Fm::Archiver> >, std::allocator<std::unique_ptr<Fm::Archiver, std::default_delete<Fm::Archiver> > > >::_M_realloc_insert<std::unique_ptr<Fm::Archiver, std::default_delete<Fm::Archiver> > >(__gnu_cxx::__normal_iterator<std::unique_ptr<Fm::Archiver, std::default_delete<Fm::Archiver> >*, std::vector<std::unique_ptr<Fm::Archiver, std::default_delete<Fm::Archiver> >, std::allocator<std::unique_ptr<Fm::Archiver, std::default_delete<Fm::Archiver> > > > >, std::unique_ptr<Fm::Archiver, std::default_delete<Fm::Archiver> >&&)@Base" 0.13.0~ | ||||
|  (c++)"vtable for Fm::AppChooserComboBox@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::AppChooserDialog@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::AppMenuView@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::BasicFileLauncher@Base" 0.13.0~ | ||||
|  (c++)"vtable for Fm::BookmarkAction@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::Bookmarks@Base" 0.12.0 | ||||
|  (c++)"vtable for Fm::BrowseHistory@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::CachedFolderModel@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::ColorButton@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::CopyJob@Base" 0.12.0 | ||||
|  (c++)"vtable for Fm::CreateNewMenu@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::DeleteJob@Base" 0.12.0 | ||||
|  (c++)"vtable for Fm::DirListJob@Base" 0.12.0 | ||||
| @ -1374,6 +1454,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"vtable for Fm::FilePropsDialog@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::FileSearchDialog@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::FileSystemInfoJob@Base" 0.12.0 | ||||
|  (c++)"vtable for Fm::FileTransferJob@Base" 0.13.0~ | ||||
|  (c++)"vtable for Fm::Folder@Base" 0.12.0 | ||||
|  (c++)"vtable for Fm::FolderItemDelegate@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::FolderMenu@Base" 0.10.0 | ||||
| @ -1381,7 +1462,6 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"vtable for Fm::FolderModelItem@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::FolderView@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::FontButton@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::IconTheme@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::Job@Base" 0.12.0 | ||||
|  (c++)"vtable for Fm::MountOperation@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::PathBar@Base" 0.11.2 | ||||
| @ -1392,10 +1472,12 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"vtable for Fm::PlacesModelItem@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::PlacesModelMountItem@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::PlacesModelVolumeItem@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::PlacesProxyModel@Base" 0.13.0~ | ||||
|  (c++)"vtable for Fm::PlacesView@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::ProxyFolderModel@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::RenameDialog@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::SidePane@Base" 0.10.0 | ||||
|  (c++)"vtable for Fm::Templates@Base" 0.13.0~ | ||||
|  (c++)"vtable for Fm::ThumbnailJob@Base" 0.12.0 | ||||
|  (c++)"vtable for Fm::TotalSizeJob@Base" 0.12.0 | ||||
|  (c++)"vtable for Fm::TrashJob@Base" 0.12.0 | ||||
| @ -1403,7 +1485,7 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++)"vtable for Fm::UserInfoCache@Base" 0.12.0 | ||||
|  (c++)"vtable for Fm::VolumeManager::GetGVolumeMonitorJob@Base" 0.12.0 | ||||
|  (c++)"vtable for Fm::VolumeManager@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::BookmarkItem const, std::allocator<Fm::BookmarkItem>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (optional|c++|arch= !armel !riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::BookmarkItem const, std::allocator<Fm::BookmarkItem>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::BookmarkItem const, std::allocator<Fm::BookmarkItem>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::BookmarkItem, std::allocator<Fm::BookmarkItem>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::BookmarkItem, std::allocator<Fm::BookmarkItem>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
| @ -1422,45 +1504,13 @@ libfm-qt.so.3 libfm-qt3 #MINVER# | ||||
|  (c++|arch= !armel !riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::MimeType, std::allocator<Fm::MimeType>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::MimeType, std::allocator<Fm::MimeType>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::PlacesModel, std::allocator<Fm::PlacesModel>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++)"vtable for std::_Sp_counted_ptr_inplace<Fm::PlacesProxyModel, std::allocator<Fm::PlacesProxyModel>, (__gnu_cxx::_Lock_policy)2>@Base" 0.13.0~ | ||||
|  (c++)"vtable for std::_Sp_counted_ptr_inplace<Fm::TemplateItem, std::allocator<Fm::TemplateItem>, (__gnu_cxx::_Lock_policy)2>@Base" 0.13.0~ | ||||
|  (c++)"vtable for std::_Sp_counted_ptr_inplace<Fm::Templates, std::allocator<Fm::Templates>, (__gnu_cxx::_Lock_policy)2>@Base" 0.13.0~ | ||||
|  (c++)"vtable for std::_Sp_counted_ptr_inplace<Fm::Thumbnailer, std::allocator<Fm::Thumbnailer>, (__gnu_cxx::_Lock_policy)2>@Base" 0.13.0~ | ||||
|  (c++|arch= !armel !riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::UserInfo, std::allocator<Fm::UserInfo>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"vtable for std::_Sp_counted_ptr_inplace<Fm::VolumeManager, std::allocator<Fm::VolumeManager>, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  (c++|arch= !armel !riscv64 )"vtable for std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)2>@Base" 0.12.0 | ||||
|  (c++|arch=  armel riscv64 )"vtable for std::_Sp_counted_ptr_inplace<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >, std::allocator<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >, (__gnu_cxx::_Lock_policy)1>@Base" 0.12.0 | ||||
|  fm_search_add_dir@Base 0.10.0 | ||||
|  fm_search_add_mime_type@Base 0.10.0 | ||||
|  fm_search_dup_path@Base 0.10.0 | ||||
|  fm_search_free@Base 0.10.0 | ||||
|  fm_search_get_content_ci@Base 0.10.0 | ||||
|  fm_search_get_content_pattern@Base 0.10.0 | ||||
|  fm_search_get_content_regex@Base 0.10.0 | ||||
|  fm_search_get_dirs@Base 0.10.0 | ||||
|  fm_search_get_max_mtime@Base 0.10.0 | ||||
|  fm_search_get_max_size@Base 0.10.0 | ||||
|  fm_search_get_mime_types@Base 0.10.0 | ||||
|  fm_search_get_min_mtime@Base 0.10.0 | ||||
|  fm_search_get_min_size@Base 0.10.0 | ||||
|  fm_search_get_name_ci@Base 0.10.0 | ||||
|  fm_search_get_name_patterns@Base 0.10.0 | ||||
|  fm_search_get_name_regex@Base 0.10.0 | ||||
|  fm_search_get_recursive@Base 0.10.0 | ||||
|  fm_search_get_show_hidden@Base 0.10.0 | ||||
|  fm_search_new@Base 0.10.0 | ||||
|  fm_search_remove_dir@Base 0.10.0 | ||||
|  fm_search_remove_mime_type@Base 0.10.0 | ||||
|  fm_search_set_content_ci@Base 0.10.0 | ||||
|  fm_search_set_content_pattern@Base 0.10.0 | ||||
|  fm_search_set_content_regex@Base 0.10.0 | ||||
|  fm_search_set_max_mtime@Base 0.10.0 | ||||
|  fm_search_set_max_size@Base 0.10.0 | ||||
|  fm_search_set_min_mtime@Base 0.10.0 | ||||
|  fm_search_set_min_size@Base 0.10.0 | ||||
|  fm_search_set_name_ci@Base 0.10.0 | ||||
|  fm_search_set_name_patterns@Base 0.10.0 | ||||
|  fm_search_set_name_regex@Base 0.10.0 | ||||
|  fm_search_set_recursive@Base 0.10.0 | ||||
|  fm_search_set_show_hidden@Base 0.10.0 | ||||
| @ -9,7 +9,7 @@ set(libfm_core_SRCS | ||||
|     core/filemonitor.cpp | ||||
|     # i/o jobs | ||||
|     core/job.cpp | ||||
|     core/copyjob.cpp | ||||
|     core/filetransferjob.cpp | ||||
|     core/deletejob.cpp | ||||
|     core/dirlistjob.cpp | ||||
|     core/filechangeattrjob.cpp | ||||
| @ -24,10 +24,13 @@ set(libfm_core_SRCS | ||||
|     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 | ||||
| @ -39,7 +42,6 @@ set(libfm_SRCS | ||||
|     libfmqt.cpp | ||||
|     bookmarkaction.cpp | ||||
|     sidepane.cpp | ||||
|     icontheme.cpp | ||||
|     filelauncher.cpp | ||||
|     foldermodel.cpp | ||||
|     foldermodelitem.cpp | ||||
| @ -95,9 +97,6 @@ set(libfm_UIS | ||||
|     filedialog.ui | ||||
| ) | ||||
| 
 | ||||
| qt5_wrap_ui(libfm_UIS_H ${libfm_UIS}) | ||||
| 
 | ||||
| 
 | ||||
| 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") | ||||
| 
 | ||||
| @ -114,7 +113,7 @@ lxqt_translate_ts(QM_FILES | ||||
| 
 | ||||
| add_library(${LIBFM_QT_LIBRARY_NAME} SHARED | ||||
|     ${libfm_SRCS} | ||||
|     ${libfm_UIS_H} | ||||
|     ${libfm_UIS} | ||||
|     ${QM_FILES} | ||||
| ) | ||||
| 
 | ||||
| @ -154,6 +153,7 @@ target_include_directories(${LIBFM_QT_LIBRARY_NAME} | ||||
| 
 | ||||
| target_compile_definitions(${LIBFM_QT_LIBRARY_NAME} | ||||
|     PRIVATE "LIBFM_QT_DATA_DIR=\"${LIBFM_QT_DATA_DIR}\"" | ||||
|             "QT_NO_FOREACH" | ||||
|     PUBLIC "QT_NO_KEYWORDS" | ||||
| ) | ||||
| 
 | ||||
| @ -216,7 +216,7 @@ export(TARGETS ${LIBFM_QT_LIBRARY_NAME} | ||||
| 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 | ||||
| # http://www.freebsd.org/doc/handbook/dirstructure.html | ||||
| # 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" | ||||
|  | ||||
| @ -18,7 +18,6 @@ | ||||
|  */ | ||||
| 
 | ||||
| #include "appchoosercombobox.h" | ||||
| #include "icontheme.h" | ||||
| #include "appchooserdialog.h" | ||||
| #include "utilities.h" | ||||
| #include "core/iconinfo.h" | ||||
| @ -33,7 +32,7 @@ AppChooserComboBox::AppChooserComboBox(QWidget* parent): | ||||
| 
 | ||||
|     // the new Qt5 signal/slot syntax cannot handle overloaded methods by default
 | ||||
|     // hence a type-casting is needed here. really ugly!
 | ||||
|     // reference: http://qt-project.org/forums/viewthread/21513
 | ||||
|     // 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); | ||||
| } | ||||
| 
 | ||||
| @ -72,8 +71,10 @@ void AppChooserComboBox::setMimeType(std::shared_ptr<const Fm::MimeType> mimeTyp | ||||
| 
 | ||||
| // 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_[idx] : Fm::GAppInfoPtr{}; | ||||
|     return idx >= 0 && !appInfos_.empty() ? appInfos_[idx] : Fm::GAppInfoPtr{}; | ||||
| } | ||||
| 
 | ||||
| bool AppChooserComboBox::isChanged() const { | ||||
|  | ||||
| @ -19,7 +19,6 @@ | ||||
| 
 | ||||
| #include "appmenuview.h" | ||||
| #include <QStandardItemModel> | ||||
| #include "icontheme.h" | ||||
| #include "appmenuview_p.h" | ||||
| #include <gio/gdesktopappinfo.h> | ||||
| 
 | ||||
|  | ||||
| @ -22,7 +22,6 @@ | ||||
| 
 | ||||
| #include <QStandardItem> | ||||
| #include <menu-cache/menu-cache.h> | ||||
| #include "icontheme.h" | ||||
| #include "core/iconinfo.h" | ||||
| 
 | ||||
| namespace Fm { | ||||
|  | ||||
							
								
								
									
										143
									
								
								src/archiver.h
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								src/archiver.h
									
									
									
									
									
								
							| @ -1,143 +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_ARCHIVER_H__ | ||||
| #define __LIBFM_QT_FM_ARCHIVER_H__ | ||||
| 
 | ||||
| #include <libfm/fm.h> | ||||
| #include <QObject> | ||||
| #include <QtGlobal> | ||||
| #include "libfmqtglobals.h" | ||||
| 
 | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| 
 | ||||
| class LIBFM_QT_API Archiver { | ||||
| public: | ||||
| 
 | ||||
| 
 | ||||
|   // default constructor
 | ||||
|   Archiver() { | ||||
|     dataPtr_ = nullptr; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // move constructor
 | ||||
|   Archiver(Archiver&& other) noexcept { | ||||
|     dataPtr_ = reinterpret_cast<FmArchiver*>(other.takeDataPtr()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // destructor
 | ||||
|   ~Archiver() { | ||||
|     if(dataPtr_ != nullptr) { | ||||
|       (dataPtr_); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // create a wrapper for the data pointer without increasing the reference count
 | ||||
|   static Archiver wrapPtr(FmArchiver* dataPtr) { | ||||
|     Archiver obj; | ||||
|     obj.dataPtr_ = reinterpret_cast<FmArchiver*>(dataPtr); | ||||
|     return obj; | ||||
|   } | ||||
| 
 | ||||
|   // disown the managed data pointer
 | ||||
|   FmArchiver* takeDataPtr() { | ||||
|     FmArchiver* data = reinterpret_cast<FmArchiver*>(dataPtr_); | ||||
|     dataPtr_ = nullptr; | ||||
|     return data; | ||||
|   } | ||||
| 
 | ||||
|   // get the raw pointer wrapped
 | ||||
|   FmArchiver* dataPtr() { | ||||
|     return reinterpret_cast<FmArchiver*>(dataPtr_); | ||||
|   } | ||||
| 
 | ||||
|   // automatic type casting
 | ||||
|   operator FmArchiver*() { | ||||
|     return dataPtr(); | ||||
|   } | ||||
| 
 | ||||
|   // automatic type casting
 | ||||
|   operator void*() { | ||||
|     return dataPtr(); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   // move assignment
 | ||||
|   Archiver& operator=(Archiver&& other) noexcept { | ||||
|     dataPtr_ = reinterpret_cast<FmArchiver*>(other.takeDataPtr()); | ||||
|     return *this; | ||||
|   } | ||||
| 
 | ||||
|   bool isNull() { | ||||
|     return (dataPtr_ == nullptr); | ||||
|   } | ||||
| 
 | ||||
|   // methods
 | ||||
| 
 | ||||
|   void setDefault(void) { | ||||
|     fm_archiver_set_default(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static Archiver getDefault( ) { | ||||
|     return wrapPtr(fm_archiver_get_default()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   bool extractArchivesTo(GAppLaunchContext* ctx, FmPathList* files, FmPath* dest_dir) { | ||||
|     return fm_archiver_extract_archives_to(dataPtr(), ctx, files, dest_dir); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   bool extractArchives(GAppLaunchContext* ctx, FmPathList* files) { | ||||
|     return fm_archiver_extract_archives(dataPtr(), ctx, files); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   bool createArchive(GAppLaunchContext* ctx, FmPathList* files) { | ||||
|     return fm_archiver_create_archive(dataPtr(), ctx, files); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   bool isMimeTypeSupported(const char* type) { | ||||
|     return fm_archiver_is_mime_type_supported(dataPtr(), type); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| // the wrapped object cannot be copied.
 | ||||
| private: | ||||
|   Archiver(const Archiver& other) = delete; | ||||
|   Archiver& operator=(const Archiver& other) = delete; | ||||
| 
 | ||||
| 
 | ||||
| private: | ||||
|   FmArchiver* dataPtr_; // data pointer for the underlying C struct
 | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // __LIBFM_QT_FM_ARCHIVER_H__
 | ||||
| @ -59,7 +59,7 @@ void CachedFolderModel::unref() { | ||||
|     --refCount; | ||||
|     if(refCount <= 0) { | ||||
|         folder()->setProperty(cacheKey, QVariant()); | ||||
|         deleteLater(); | ||||
|         delete(this); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										174
									
								
								src/core/archiver.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								src/core/archiver.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,174 @@ | ||||
| #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
 | ||||
							
								
								
									
										69
									
								
								src/core/archiver.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/core/archiver.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | ||||
| #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
 | ||||
							
								
								
									
										345
									
								
								src/core/basicfilelauncher.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								src/core/basicfilelauncher.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,345 @@ | ||||
| #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
 | ||||
							
								
								
									
										72
									
								
								src/core/basicfilelauncher.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/core/basicfilelauncher.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| #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
 | ||||
| @ -2,6 +2,7 @@ | ||||
| #include "cstrptr.h" | ||||
| #include <algorithm> | ||||
| #include <QTimer> | ||||
| #include <QStandardPaths> | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| @ -15,6 +16,60 @@ 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} { | ||||
|  | ||||
| @ -1,11 +1,10 @@ | ||||
| #ifndef FM2_BOOKMARKS_H | ||||
| #define FM2_BOOKMARKS_H | ||||
| 
 | ||||
| #include "../libfmqtglobals.h" | ||||
| #include <QObject> | ||||
| #include "gobjectptr.h" | ||||
| #include "fileinfo.h" | ||||
| 
 | ||||
| #include "filepath.h" | ||||
| #include "iconinfo.h" | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| @ -13,11 +12,7 @@ class LIBFM_QT_API BookmarkItem { | ||||
| public: | ||||
|     friend class Bookmarks; | ||||
| 
 | ||||
|     explicit BookmarkItem(const FilePath& path, const QString name): path_{path}, name_{name} { | ||||
|         if(name_.isEmpty()) { // if the name is not specified, use basename of the path
 | ||||
|             name_ = path_.baseName().get(); | ||||
|         } | ||||
|     } | ||||
|     explicit BookmarkItem(const FilePath& path, const QString name); | ||||
| 
 | ||||
|     const QString& name() const { | ||||
|         return name_; | ||||
| @ -27,15 +22,11 @@ public: | ||||
|         return path_; | ||||
|     } | ||||
| 
 | ||||
|     const std::shared_ptr<const FmFileInfo>& info() const { | ||||
|         return info_; | ||||
|     const std::shared_ptr<const IconInfo>& icon() const { | ||||
|         return icon_; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     void setInfo(const std::shared_ptr<const FmFileInfo>& info) { | ||||
|         info_ = info; | ||||
|     } | ||||
| 
 | ||||
|     void setName(const QString& name) { | ||||
|         name_ = name; | ||||
|     } | ||||
| @ -43,7 +34,7 @@ private: | ||||
| private: | ||||
|     FilePath path_; | ||||
|     QString name_; | ||||
|     std::shared_ptr<const FmFileInfo> info_; | ||||
|     std::shared_ptr<const IconInfo> icon_; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -1,53 +0,0 @@ | ||||
| #ifndef LIBFM_QT_COMPAT_P_H | ||||
| #define LIBFM_QT_COMPAT_P_H | ||||
| 
 | ||||
| #include "../libfmqtglobals.h" | ||||
| #include "core/filepath.h" | ||||
| #include "core/fileinfo.h" | ||||
| #include "core/gioptrs.h" | ||||
| 
 | ||||
| // deprecated
 | ||||
| #include <libfm/fm.h> | ||||
| #include "path.h" | ||||
| 
 | ||||
| // compatibility functions bridging the old libfm C APIs and new C++ APIs.
 | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| inline FM_QT_DEPRECATED Fm::Path _convertPath(const Fm::FilePath& path) { | ||||
|     return Fm::Path::newForGfile(path.gfile().get()); | ||||
| } | ||||
| 
 | ||||
| inline FM_QT_DEPRECATED Fm::PathList _convertPathList(const Fm::FilePathList& srcFiles) { | ||||
|     Fm::PathList ret; | ||||
|     for(auto& file: srcFiles) { | ||||
|         ret.pushTail(_convertPath(file)); | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| inline FM_QT_DEPRECATED FmFileInfo* _convertFileInfo(const std::shared_ptr<const Fm::FileInfo>& info) { | ||||
|     // conver to GFileInfo first
 | ||||
|     GFileInfoPtr ginfo{g_file_info_new(), false}; | ||||
|     g_file_info_set_name(ginfo.get(), info->name().c_str()); | ||||
|     g_file_info_set_display_name(ginfo.get(), info->displayName().toUtf8().constData()); | ||||
|     g_file_info_set_content_type(ginfo.get(), info->mimeType()->name()); | ||||
| 
 | ||||
|     auto mode = info->mode(); | ||||
|     g_file_info_set_attribute_uint32(ginfo.get(), G_FILE_ATTRIBUTE_UNIX_MODE, mode); | ||||
|     GFileType ftype = info->isDir() ? G_FILE_TYPE_DIRECTORY : G_FILE_TYPE_REGULAR;  // FIXME: generate more accurate type
 | ||||
|     g_file_info_set_file_type(ginfo.get(), ftype); | ||||
|     g_file_info_set_size(ginfo.get(), info->size()); | ||||
|     g_file_info_set_icon(ginfo.get(), info->icon()->gicon().get()); | ||||
| 
 | ||||
|     g_file_info_set_attribute_uint64(ginfo.get(), G_FILE_ATTRIBUTE_TIME_MODIFIED, info->mtime()); | ||||
|     g_file_info_set_attribute_uint64(ginfo.get(), G_FILE_ATTRIBUTE_TIME_ACCESS, info->atime()); | ||||
|     g_file_info_set_attribute_uint64(ginfo.get(), G_FILE_ATTRIBUTE_TIME_CHANGED, info->ctime()); | ||||
| 
 | ||||
|     auto gf = info->path().gfile(); | ||||
|     return fm_file_info_new_from_g_file_data(gf.get(), ginfo.get(), nullptr); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // LIBFM_QT_COMPAT_P_H
 | ||||
| @ -1,453 +0,0 @@ | ||||
| #include "copyjob.h" | ||||
| #include "totalsizejob.h" | ||||
| #include "fileinfo_p.h" | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| CopyJob::CopyJob(const FilePathList& paths, const FilePath& destDirPath, Mode mode): | ||||
|     FileOperationJob{}, | ||||
|     srcPaths_{paths}, | ||||
|     destDirPath_{destDirPath}, | ||||
|     mode_{mode}, | ||||
|     skip_dir_content{false} { | ||||
| } | ||||
| 
 | ||||
| CopyJob::CopyJob(const FilePathList &&paths, const FilePath &&destDirPath, Mode mode): | ||||
|     FileOperationJob{}, | ||||
|     srcPaths_{paths}, | ||||
|     destDirPath_{destDirPath}, | ||||
|     mode_{mode}, | ||||
|     skip_dir_content{false} { | ||||
| } | ||||
| 
 | ||||
| void CopyJob::gfileProgressCallback(goffset current_num_bytes, goffset total_num_bytes, CopyJob* _this) { | ||||
|     _this->setCurrentFileProgress(total_num_bytes, current_num_bytes); | ||||
| } | ||||
| 
 | ||||
| bool CopyJob::copyRegularFile(const FilePath& srcPath, GFileInfoPtr /*srcFile*/, const FilePath& destPath) { | ||||
|     int flags = G_FILE_COPY_ALL_METADATA | G_FILE_COPY_NOFOLLOW_SYMLINKS; | ||||
|     GErrorPtr err; | ||||
| _retry_copy: | ||||
|     if(!g_file_copy(srcPath.gfile().get(), destPath.gfile().get(), GFileCopyFlags(flags), cancellable().get(), | ||||
|                     GFileProgressCallback(gfileProgressCallback), this, &err)) { | ||||
|         flags &= ~G_FILE_COPY_OVERWRITE; | ||||
|         /* handle existing files or file name conflict */ | ||||
|         if(err.domain() == G_IO_ERROR && (err.code() == G_IO_ERROR_EXISTS || | ||||
|                                          err.code() == G_IO_ERROR_INVALID_FILENAME || | ||||
|                                          err.code() == G_IO_ERROR_FILENAME_TOO_LONG)) { | ||||
| #if 0 | ||||
|             GFile* dest_cp = new_dest; | ||||
|             bool dest_exists = (err->code == G_IO_ERROR_EXISTS); | ||||
|             FmFileOpOption opt = 0; | ||||
|             g_error_free(err); | ||||
|             err = nullptr; | ||||
| 
 | ||||
|             new_dest = nullptr; | ||||
|             opt = _fm_file_ops_job_ask_new_name(job, src, dest, &new_dest, dest_exists); | ||||
|             if(!new_dest) { /* restoring status quo */ | ||||
|                 new_dest = dest_cp; | ||||
|             } | ||||
|             else if(dest_cp) { /* we got new new_dest, forget old one */ | ||||
|                 g_object_unref(dest_cp); | ||||
|             } | ||||
|             switch(opt) { | ||||
|             case FM_FILE_OP_RENAME: | ||||
|                 dest = new_dest; | ||||
|                 goto _retry_copy; | ||||
|                 break; | ||||
|             case FM_FILE_OP_OVERWRITE: | ||||
|                 flags |= G_FILE_COPY_OVERWRITE; | ||||
|                 goto _retry_copy; | ||||
|                 break; | ||||
|             case FM_FILE_OP_CANCEL: | ||||
|                 fm_job_cancel(fmjob); | ||||
|                 break; | ||||
|             case FM_FILE_OP_SKIP: | ||||
|                 ret = true; | ||||
|                 delete_src = false; /* don't delete source file. */ | ||||
|                 break; | ||||
|             case FM_FILE_OP_SKIP_ERROR: ; /* FIXME */ | ||||
|             } | ||||
| #endif | ||||
|         } | ||||
|         else { | ||||
|             ErrorAction act = emitError( err, ErrorSeverity::MODERATE); | ||||
|             err.reset(); | ||||
|             if(act == ErrorAction::RETRY) { | ||||
|                 // FIXME: job->current_file_finished = 0;
 | ||||
|                 goto _retry_copy; | ||||
|             } | ||||
| # if 0 | ||||
|             const bool is_no_space = (err.domain() == G_IO_ERROR && | ||||
|                                 err.code() == G_IO_ERROR_NO_SPACE); | ||||
|             /* FIXME: ask to leave partial content? */ | ||||
|             if(is_no_space) { | ||||
|                 g_file_delete(dest, fm_job_get_cancellable(fmjob), nullptr); | ||||
|             } | ||||
|             ret = false; | ||||
|             delete_src = false; | ||||
| #endif | ||||
|         } | ||||
|         err.reset(); | ||||
|     } | ||||
|     else { | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool CopyJob::copySpecialFile(const FilePath& srcPath, GFileInfoPtr srcFile, const FilePath& destPath) { | ||||
|     bool ret = false; | ||||
|     GError* err = nullptr; | ||||
|     /* only handle FIFO for local files */ | ||||
|     if(srcPath.isNative() && destPath.isNative()) { | ||||
|         auto src_path = srcPath.localPath(); | ||||
|         struct stat src_st; | ||||
|         int r; | ||||
|         r = lstat(src_path.get(), &src_st); | ||||
|         if(r == 0) { | ||||
|             /* Handle FIFO on native file systems. */ | ||||
|             if(S_ISFIFO(src_st.st_mode)) { | ||||
|                 auto dest_path = destPath.localPath(); | ||||
|                 if(mkfifo(dest_path.get(), src_st.st_mode) == 0) { | ||||
|                     ret = true; | ||||
|                 } | ||||
|             } | ||||
|             /* FIXME: how about block device, char device, and socket? */ | ||||
|         } | ||||
|     } | ||||
|     if(!ret) { | ||||
|         g_set_error(&err, G_IO_ERROR, G_IO_ERROR_FAILED, | ||||
|                     ("Cannot copy file '%s': not supported"), | ||||
|                     g_file_info_get_display_name(srcFile.get())); | ||||
|         // emitError( err, ErrorSeverity::MODERATE);
 | ||||
|         g_clear_error(&err); | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool CopyJob::copyDir(const FilePath& srcPath, GFileInfoPtr srcFile, const FilePath& destPath) { | ||||
|     bool ret = false; | ||||
|     if(makeDir(srcPath, srcFile, destPath)) { | ||||
|         GError* err = nullptr; | ||||
|         auto enu = GFileEnumeratorPtr{ | ||||
|                 g_file_enumerate_children(srcPath.gfile().get(), | ||||
|                                           gfile_info_query_attribs, | ||||
|                                           G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, | ||||
|                                           cancellable().get(), &err), | ||||
|                 false}; | ||||
|         if(enu) { | ||||
|             int n_children = 0; | ||||
|             int n_copied = 0; | ||||
|             ret = true; | ||||
|             while(!isCancelled()) { | ||||
|                 auto inf = GFileInfoPtr{g_file_enumerator_next_file(enu.get(), cancellable().get(), &err), false}; | ||||
|                 if(inf) { | ||||
|                     ++n_children; | ||||
|                     /* don't overwrite dir content, only calculate progress. */ | ||||
|                     if(Q_UNLIKELY(skip_dir_content)) { | ||||
|                         /* FIXME: this is incorrect as we don't do the calculation recursively. */ | ||||
|                         addFinishedAmount(g_file_info_get_size(inf.get()), 1); | ||||
|                     } | ||||
|                     else { | ||||
|                         const char* name = g_file_info_get_name(inf.get()); | ||||
|                         FilePath childPath = srcPath.child(name); | ||||
|                         bool child_ret = copyPath(childPath, inf, destPath, name); | ||||
|                         if(child_ret) { | ||||
|                             ++n_copied; | ||||
|                         } | ||||
|                         else { | ||||
|                             ret = false; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 else { | ||||
|                     if(err) { | ||||
|                         // FIXME: emitError( err, ErrorSeverity::MODERATE);
 | ||||
|                         g_error_free(err); | ||||
|                         err = nullptr; | ||||
|                         /* ErrorAction::RETRY is not supported here */ | ||||
|                         ret = false; | ||||
|                     } | ||||
|                     else { /* EOF is reached */ | ||||
|                         /* all files are successfully copied. */ | ||||
|                         if(isCancelled()) { | ||||
|                             ret = false; | ||||
|                         } | ||||
|                         else { | ||||
|                             /* some files are not copied */ | ||||
|                             if(n_children != n_copied) { | ||||
|                                 /* if the copy actions are skipped deliberately, it's ok */ | ||||
|                                 if(!skip_dir_content) { | ||||
|                                     ret = false; | ||||
|                                 } | ||||
|                             } | ||||
|                             /* else job->skip_dir_content is true */ | ||||
|                         } | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             g_file_enumerator_close(enu.get(), nullptr, &err); | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool CopyJob::makeDir(const FilePath& srcPath, GFileInfoPtr srcFile, const FilePath& dirPath) { | ||||
|     GError* err = nullptr; | ||||
|     if(isCancelled()) | ||||
|         return false; | ||||
| 
 | ||||
|     FilePath destPath = dirPath; | ||||
|     bool mkdir_done = false; | ||||
|     do { | ||||
|         mkdir_done = g_file_make_directory(destPath.gfile().get(), cancellable().get(), &err); | ||||
|         if(err->domain == G_IO_ERROR && (err->code == G_IO_ERROR_EXISTS || | ||||
|                                          err->code == G_IO_ERROR_INVALID_FILENAME || | ||||
|                                          err->code == G_IO_ERROR_FILENAME_TOO_LONG)) { | ||||
|             GFileInfoPtr destFile; | ||||
|             // FIXME: query its info
 | ||||
|             FilePath newDestPath; | ||||
|             FileExistsAction opt = askRename(FileInfo{srcFile, srcPath.parent()}, FileInfo{destFile, dirPath.parent()}, newDestPath); | ||||
|             g_error_free(err); | ||||
|             err = nullptr; | ||||
| 
 | ||||
|             switch(opt) { | ||||
|             case FileOperationJob::RENAME: | ||||
|                 destPath = newDestPath; | ||||
|                 break; | ||||
|             case FileOperationJob::SKIP: | ||||
|                 /* when a dir is skipped, we need to know its total size to calculate correct progress */ | ||||
|                 // job->finished += size;
 | ||||
|                 // fm_file_ops_job_emit_percent(job);
 | ||||
|                 // job->skip_dir_content = skip_dir_content = true;
 | ||||
|                 mkdir_done = true; /* pretend that dir creation succeeded */ | ||||
|                 break; | ||||
|             case FileOperationJob::OVERWRITE: | ||||
|                 mkdir_done = true; /* pretend that dir creation succeeded */ | ||||
|                 break; | ||||
|             case FileOperationJob::CANCEL: | ||||
|                 cancel(); | ||||
|                 break; | ||||
|             case FileOperationJob::SKIP_ERROR: ; /* FIXME */ | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
| #if 0 | ||||
|             ErrorAction act = emitError( err, ErrorSeverity::MODERATE); | ||||
|             g_error_free(err); | ||||
|             err = nullptr; | ||||
|             if(act == ErrorAction::RETRY) { | ||||
|                 goto _retry_mkdir; | ||||
|             } | ||||
| #endif | ||||
|             break; | ||||
|         } | ||||
|         // job->finished += size;
 | ||||
|     } while(!mkdir_done && !isCancelled()); | ||||
| 
 | ||||
|     if(mkdir_done && !isCancelled()) { | ||||
|         bool chmod_done = false; | ||||
|         mode_t mode = g_file_info_get_attribute_uint32(srcFile.get(), G_FILE_ATTRIBUTE_UNIX_MODE); | ||||
|         if(mode) { | ||||
|             mode |= (S_IRUSR | S_IWUSR); /* ensure we have rw permission to this file. */ | ||||
|             do { | ||||
|                 /* chmod the newly created dir properly */ | ||||
|                 // if(!fm_job_is_cancelled(fmjob) && !job->skip_dir_content)
 | ||||
|                 chmod_done = g_file_set_attribute_uint32(destPath.gfile().get(), | ||||
|                                                          G_FILE_ATTRIBUTE_UNIX_MODE, | ||||
|                                                          mode, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, | ||||
|                                                          cancellable().get(), &err); | ||||
|                 if(!chmod_done) { | ||||
| /*
 | ||||
|                     ErrorAction act = emitError( err, ErrorSeverity::MODERATE); | ||||
|                     g_error_free(err); | ||||
|                     err = nullptr; | ||||
|                     if(act == ErrorAction::RETRY) { | ||||
|                         goto _retry_chmod_for_dir; | ||||
|                     } | ||||
| */ | ||||
|                     /* FIXME: some filesystems may not support this. */ | ||||
|                 } | ||||
|             } while(!chmod_done && !isCancelled()); | ||||
|             // finished += size;
 | ||||
|             // fm_file_ops_job_emit_percent(job);
 | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool CopyJob::copyPath(const FilePath& srcPath, const FilePath& destDirPath, const char* destFileName) { | ||||
|     GErrorPtr err; | ||||
|     GFileInfoPtr srcInfo = GFileInfoPtr { | ||||
|         g_file_query_info(srcPath.gfile().get(), | ||||
|         gfile_info_query_attribs, | ||||
|         G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, | ||||
|         cancellable().get(), &err), | ||||
|         false | ||||
|     }; | ||||
|     if(!srcInfo || isCancelled()) { | ||||
|         return false; | ||||
|     } | ||||
|     return copyPath(srcPath, srcInfo, destDirPath, destFileName); | ||||
| } | ||||
| 
 | ||||
| bool CopyJob::copyPath(const FilePath& srcPath, const GFileInfoPtr& srcInfo, const FilePath& destDirPath, const char* destFileName) { | ||||
|     setCurrentFile(srcPath); | ||||
|     GErrorPtr err; | ||||
|     GFileInfoPtr destDirInfo = GFileInfoPtr { | ||||
|         g_file_query_info(destDirPath.gfile().get(), | ||||
|         "id::filesystem", | ||||
|         G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, | ||||
|         cancellable().get(), &err), | ||||
|         false | ||||
|     }; | ||||
| 
 | ||||
|     if(!destDirInfo || isCancelled()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     auto size = g_file_info_get_size(srcInfo.get()); | ||||
|     setCurrentFileProgress(size, 0); | ||||
| 
 | ||||
|     auto destPath = destDirPath.child(destFileName); | ||||
|     bool success = false; | ||||
|     switch(g_file_info_get_file_type(srcInfo.get())) { | ||||
|     case G_FILE_TYPE_DIRECTORY: | ||||
|         success = copyDir(srcPath, srcInfo, destPath); | ||||
|         break; | ||||
|     case G_FILE_TYPE_SPECIAL: | ||||
|         success = copySpecialFile(srcPath, srcInfo, destPath); | ||||
|         break; | ||||
|     default: | ||||
|         success = copyRegularFile(srcPath, srcInfo, destPath); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     if(success) { | ||||
|         addFinishedAmount(size, 1); | ||||
| #if 0 | ||||
| 
 | ||||
|         if(ret && dest_folder) { | ||||
|             fm_dest = fm_path_new_for_gfile(dest); | ||||
|             if(!_fm_folder_event_file_added(dest_folder, fm_dest)) { | ||||
|                 fm_path_unref(fm_dest); | ||||
|             } | ||||
|         } | ||||
| #endif | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| #if 0 | ||||
| 
 | ||||
| bool _fm_file_ops_job_copy_run(FmFileOpsJob* job) { | ||||
|     bool ret = true; | ||||
|     GFile* dest_dir; | ||||
|     GList* l; | ||||
|     FmJob* fmjob = FM_JOB(job); | ||||
|     /* prepare the job, count total work needed with FmDeepCountJob */ | ||||
|     FmDeepCountJob* dc = fm_deep_count_job_new(job->srcs, FM_DC_JOB_DEFAULT); | ||||
|     FmFolder* df; | ||||
| 
 | ||||
|     /* let the deep count job share the same cancellable object. */ | ||||
|     fm_job_set_cancellable(FM_JOB(dc), fm_job_get_cancellable(fmjob)); | ||||
|     fm_job_run_sync(FM_JOB(dc)); | ||||
|     job->total = dc->total_size; | ||||
|     if(fm_job_is_cancelled(fmjob)) { | ||||
|         g_object_unref(dc); | ||||
|         return false; | ||||
|     } | ||||
|     g_object_unref(dc); | ||||
|     g_debug("total size to copy: %llu", (long long unsigned int)job->total); | ||||
| 
 | ||||
|     dest_dir = fm_path_to_gfile(job->dest); | ||||
|     /* suspend updates for destination */ | ||||
|     df = fm_folder_find_by_path(job->dest); | ||||
|     if(df) { | ||||
|         fm_folder_block_updates(df); | ||||
|     } | ||||
| 
 | ||||
|     fm_file_ops_job_emit_prepared(job); | ||||
| 
 | ||||
|     for(l = fm_path_list_peek_head_link(job->srcs); !fm_job_is_cancelled(fmjob) && l; l = l->next) { | ||||
|         FmPath* path = FM_PATH(l->data); | ||||
|         GFile* src = fm_path_to_gfile(path); | ||||
|         GFile* dest; | ||||
|         char* tmp_basename; | ||||
| 
 | ||||
|         if(g_file_is_native(src) && g_file_is_native(dest_dir)) | ||||
|             /* both are native */ | ||||
|         { | ||||
|             tmp_basename = nullptr; | ||||
|         } | ||||
|         else if(g_file_is_native(src)) /* copy from native to virtual */ | ||||
|             tmp_basename = g_filename_to_utf8(fm_path_get_basename(path), | ||||
|                                               -1, nullptr, nullptr, nullptr); | ||||
|         /* gvfs escapes it itself */ | ||||
|         else { /* copy from virtual to native/virtual */ | ||||
|             /* if we drop URI query onto native filesystem, omit query part */ | ||||
|             const char* basename = fm_path_get_basename(path); | ||||
|             char* sub_name; | ||||
| 
 | ||||
|             sub_name = strchr(basename, '?'); | ||||
|             if(sub_name) { | ||||
|                 sub_name = g_strndup(basename, sub_name - basename); | ||||
|                 basename = strrchr(sub_name, G_DIR_SEPARATOR); | ||||
|                 if(basename) { | ||||
|                     basename++; | ||||
|                 } | ||||
|                 else { | ||||
|                     basename = sub_name; | ||||
|                 } | ||||
|             } | ||||
|             tmp_basename = fm_uri_subpath_to_native_subpath(basename, nullptr); | ||||
|             g_free(sub_name); | ||||
|         } | ||||
|         dest = g_file_get_child(dest_dir, | ||||
|                                 tmp_basename ? tmp_basename : fm_path_get_basename(path)); | ||||
|         g_free(tmp_basename); | ||||
|         if(!_fm_file_ops_job_copy_file(job, src, nullptr, dest, nullptr, df)) { | ||||
|             ret = false; | ||||
|         } | ||||
|         g_object_unref(src); | ||||
|         g_object_unref(dest); | ||||
|     } | ||||
| 
 | ||||
|     /* g_debug("finished: %llu, total: %llu", job->finished, job->total); */ | ||||
|     fm_file_ops_job_emit_percent(job); | ||||
| 
 | ||||
|     /* restore updates for destination */ | ||||
|     if(df) { | ||||
|         fm_folder_unblock_updates(df); | ||||
|         g_object_unref(df); | ||||
|     } | ||||
|     g_object_unref(dest_dir); | ||||
|     return ret; | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| void CopyJob::exec() { | ||||
|     TotalSizeJob totalSizeJob{srcPaths_}; | ||||
|     connect(&totalSizeJob, &TotalSizeJob::error, this, &CopyJob::error); | ||||
|     connect(this, &CopyJob::cancelled, &totalSizeJob, &TotalSizeJob::cancel); | ||||
|     totalSizeJob.run(); | ||||
|     if(isCancelled()) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     setTotalAmount(totalSizeJob.totalSize(), totalSizeJob.fileCount()); | ||||
|     Q_EMIT preparedToRun(); | ||||
| 
 | ||||
|     for(auto& srcPath : srcPaths_) { | ||||
|         if(isCancelled()) { | ||||
|             break; | ||||
|         } | ||||
|         copyPath(srcPath, destDirPath_, srcPath.baseName().get()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| } // namespace Fm
 | ||||
| @ -1,46 +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 CopyJob : public Fm::FileOperationJob { | ||||
|     Q_OBJECT | ||||
| public: | ||||
| 
 | ||||
|     enum class Mode { | ||||
|         COPY, | ||||
|         MOVE | ||||
|     }; | ||||
| 
 | ||||
|     explicit CopyJob(const FilePathList& paths, const FilePath& destDirPath, Mode mode = Mode::COPY); | ||||
| 
 | ||||
|     explicit CopyJob(const FilePathList&& paths, const FilePath&& destDirPath, Mode mode = Mode::COPY); | ||||
| 
 | ||||
| protected: | ||||
|     void exec() override; | ||||
| 
 | ||||
| private: | ||||
|     bool copyPath(const FilePath& srcPath, const FilePath& destPath, const char *destFileName); | ||||
|     bool copyPath(const FilePath &srcPath, const GFileInfoPtr &srcInfo, const FilePath &destDirPath, const char *destFileName); | ||||
|     bool copyRegularFile(const FilePath &srcPath, GFileInfoPtr srcFile, const FilePath& destPath); | ||||
|     bool copySpecialFile(const FilePath &srcPath, GFileInfoPtr srcFile, const FilePath& destPath); | ||||
|     bool copyDir(const FilePath &srcPath, GFileInfoPtr srcFile, const FilePath& destPath); | ||||
|     bool makeDir(const FilePath &srcPath, GFileInfoPtr srcFile, const FilePath& dirPath); | ||||
| 
 | ||||
|     static void gfileProgressCallback(goffset current_num_bytes, goffset total_num_bytes, CopyJob* _this); | ||||
| 
 | ||||
| private: | ||||
|     FilePathList srcPaths_; | ||||
|     FilePath destDirPath_; | ||||
|     Mode mode_; | ||||
|     bool skip_dir_content; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| } // namespace Fm
 | ||||
| 
 | ||||
| #endif // FM2_COPYJOB_H
 | ||||
| @ -25,6 +25,9 @@ bool DeleteJob::deleteFile(const FilePath& path, GFileInfoPtr inf) { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // 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); | ||||
| 
 | ||||
| @ -33,11 +36,21 @@ bool DeleteJob::deleteFile(const FilePath& path, GFileInfoPtr inf) { | ||||
|         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
 | ||||
|         if(g_file_delete(path.gfile().get(), cancellable().get(), &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) { | ||||
| @ -69,26 +82,9 @@ bool DeleteJob::deleteFile(const FilePath& path, GFileInfoPtr inf) { | ||||
| } | ||||
| 
 | ||||
| bool DeleteJob::deleteDirContent(const FilePath& path, GFileInfoPtr inf) { | ||||
| #if 0 | ||||
|     FmFolder* sub_folder; | ||||
|     /* special handling for trash:/// */ | ||||
|     if(!g_file_is_native(gf)) { | ||||
|         char* scheme = g_file_get_uri_scheme(gf); | ||||
|         if(g_strcmp0(scheme, "trash") == 0) { | ||||
|             /* little trick: basename of trash root is /. */ | ||||
|             char* basename = g_file_get_basename(gf); | ||||
|             if(basename && basename[0] == G_DIR_SEPARATOR) { | ||||
|                 is_trash_root = true; | ||||
|             } | ||||
|             g_free(basename); | ||||
|         } | ||||
|         g_free(scheme); | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     GErrorPtr err; | ||||
|     GFileEnumeratorPtr enu { | ||||
|         g_file_enumerate_children(path.gfile().get(), gfile_info_query_attribs, | ||||
|         g_file_enumerate_children(path.gfile().get(), defaultGFileInfoQueryAttribs, | ||||
|         G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, | ||||
|         cancellable().get(), &err), | ||||
|         false | ||||
| @ -126,6 +122,17 @@ bool DeleteJob::deleteDirContent(const FilePath& path, GFileInfoPtr inf) { | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 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}; | ||||
|  | ||||
| @ -11,14 +11,11 @@ namespace Fm { | ||||
| class LIBFM_QT_API DeleteJob : public Fm::FileOperationJob { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     explicit DeleteJob(const FilePathList& paths): paths_{paths} { | ||||
|     } | ||||
|     explicit DeleteJob(const FilePathList& paths); | ||||
| 
 | ||||
|     explicit DeleteJob(FilePathList&& paths): paths_{paths} { | ||||
|     } | ||||
|     explicit DeleteJob(FilePathList&& paths); | ||||
| 
 | ||||
|     ~DeleteJob() { | ||||
|     } | ||||
|     ~DeleteJob(); | ||||
| 
 | ||||
| protected: | ||||
|     void exec() override; | ||||
|  | ||||
| @ -25,7 +25,7 @@ void DirListJob::exec() { | ||||
| _retry: | ||||
|     err.reset(); | ||||
|     dir_inf = GFileInfoPtr{ | ||||
|         g_file_query_info(dir_gfile.get(), gfile_info_query_attribs, | ||||
|         g_file_query_info(dir_gfile.get(), defaultGFileInfoQueryAttribs, | ||||
|                           G_FILE_QUERY_INFO_NONE, cancellable().get(), &err), | ||||
|         false | ||||
|     }; | ||||
| @ -58,7 +58,7 @@ _retry: | ||||
|     // FIXME:  _fm_file_info_job_update_fs_readonly(gf, inf, nullptr, nullptr);
 | ||||
|     err.reset(); | ||||
|     GFileEnumeratorPtr enu = GFileEnumeratorPtr{ | ||||
|             g_file_enumerate_children(dir_gfile.get(), gfile_info_query_attribs, | ||||
|             g_file_enumerate_children(dir_gfile.get(), defaultGFileInfoQueryAttribs, | ||||
|                                       G_FILE_QUERY_INFO_NONE, cancellable().get(), &err), | ||||
|             false | ||||
|     }; | ||||
|  | ||||
| @ -1,9 +1,324 @@ | ||||
| #include "filechangeattrjob.h" | ||||
| #include "totalsizejob.h" | ||||
| 
 | ||||
| #include <sys/stat.h> | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| FileChangeAttrJob::FileChangeAttrJob() { | ||||
| 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
 | ||||
|  | ||||
| @ -3,13 +3,141 @@ | ||||
| 
 | ||||
| #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(); | ||||
|     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
 | ||||
|  | ||||
| @ -4,12 +4,12 @@ | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| const char gfile_info_query_attribs[] = "standard::*," | ||||
|                                         "unix::*," | ||||
|                                         "time::*," | ||||
|                                         "access::*," | ||||
|                                         "id::filesystem," | ||||
|                                         "metadata::emblems"; | ||||
| const char defaultGFileInfoQueryAttribs[] = "standard::*," | ||||
|                                             "unix::*," | ||||
|                                             "time::*," | ||||
|                                             "access::*," | ||||
|                                             "id::filesystem," | ||||
|                                             "metadata::emblems"; | ||||
| 
 | ||||
| FileInfo::FileInfo() { | ||||
|     // FIXME: initialize numeric data members
 | ||||
| @ -28,7 +28,8 @@ void FileInfo::setFromGFileInfo(const GObjectPtr<GFileInfo>& inf, const FilePath | ||||
|     GIcon* gicon; | ||||
|     GFileType type; | ||||
| 
 | ||||
|     name_ = g_file_info_get_name(inf.get()); | ||||
|     if (const char * name = g_file_info_get_name(inf.get())) | ||||
|         name_ = name; | ||||
| 
 | ||||
|     dispName_ = g_file_info_get_display_name(inf.get()); | ||||
| 
 | ||||
| @ -118,6 +119,8 @@ void FileInfo::setFromGFileInfo(const GObjectPtr<GFileInfo>& inf, const FilePath | ||||
|         isDeletable_ = true; | ||||
|     } | ||||
| 
 | ||||
|     isShortcut_ = false; | ||||
| 
 | ||||
|     /* special handling for symlinks */ | ||||
|     if(g_file_info_get_is_symlink(inf.get())) { | ||||
|         mode_ &= ~S_IFMT; /* reset type */ | ||||
| @ -125,11 +128,10 @@ void FileInfo::setFromGFileInfo(const GObjectPtr<GFileInfo>& inf, const FilePath | ||||
|         goto _file_is_symlink; | ||||
|     } | ||||
| 
 | ||||
|     isShortcut_ = false; | ||||
| 
 | ||||
|     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) { | ||||
| @ -184,6 +186,7 @@ _file_is_symlink: | ||||
|                 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_)) { | ||||
| @ -246,7 +249,11 @@ _file_is_symlink: | ||||
|     atime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_ACCESS); | ||||
|     ctime_ = g_file_info_get_attribute_uint64(inf.get(), G_FILE_ATTRIBUTE_TIME_CHANGED); | ||||
|     isHidden_ = g_file_info_get_is_hidden(inf.get()); | ||||
|     isBackup_ = g_file_info_get_is_backup(inf.get()); | ||||
|     // 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)) { | ||||
| @ -326,7 +333,31 @@ bool FileInfo::canThumbnail() const { | ||||
| 
 | ||||
| /* full path of the file is required by this function */ | ||||
| bool FileInfo::isExecutableType() const { | ||||
|     if(isText()) { /* g_content_type_can_be_executable reports text files as executables too */ | ||||
|     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 */ | ||||
|  | ||||
| @ -90,16 +90,16 @@ public: | ||||
|         return mimeType_; | ||||
|     } | ||||
| 
 | ||||
|     time_t ctime() const { | ||||
|     quint64 ctime() const { | ||||
|         return ctime_; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     time_t atime() const { | ||||
|     quint64 atime() const { | ||||
|         return atime_; | ||||
|     } | ||||
| 
 | ||||
|     time_t mtime() const { | ||||
|     quint64 mtime() const { | ||||
|         return mtime_; | ||||
|     } | ||||
| 
 | ||||
| @ -163,7 +163,7 @@ public: | ||||
|     } | ||||
| 
 | ||||
|     bool isDir() const { | ||||
|         return mimeType_->isDir(); | ||||
|         return S_ISDIR(mode_) || mimeType_->isDir(); | ||||
|     } | ||||
| 
 | ||||
|     bool isNative() const { | ||||
| @ -194,6 +194,10 @@ public: | ||||
|         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()); | ||||
|     } | ||||
| @ -221,9 +225,9 @@ private: | ||||
|     uid_t uid_; | ||||
|     gid_t gid_; | ||||
|     uint64_t size_; | ||||
|     time_t mtime_; | ||||
|     time_t atime_; | ||||
|     time_t ctime_; | ||||
|     quint64 mtime_; | ||||
|     quint64 atime_; | ||||
|     quint64 ctime_; | ||||
| 
 | ||||
|     uint64_t blksize_; | ||||
|     uint64_t blocks_; | ||||
| @ -266,9 +270,10 @@ public: | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // 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<std::shared_ptr<const FileInfo>, std::shared_ptr<const FileInfo>> FileInfoPair; | ||||
| 
 | ||||
| typedef std::pair<FileInfoPtr, FileInfoPtr> FileInfoPair; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
|     extern const char gfile_info_query_attribs[]; | ||||
|     extern const char defaultGFileInfoQueryAttribs[]; | ||||
| 
 | ||||
| } // namespace Fm
 | ||||
| 
 | ||||
|  | ||||
| @ -3,9 +3,10 @@ | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| FileInfoJob::FileInfoJob(FilePathList paths, FilePath commonDirPath, const std::shared_ptr<const HashSet>& cutFilesHashSet): | ||||
| 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} { | ||||
| } | ||||
| @ -15,12 +16,13 @@ void FileInfoJob::exec() { | ||||
|         if(!isCancelled()) { | ||||
|             GErrorPtr err; | ||||
|             GFileInfoPtr inf{ | ||||
|                 g_file_query_info(path.gfile().get(), gfile_info_query_attribs, | ||||
|                 g_file_query_info(path.gfile().get(), defaultGFileInfoQueryAttribs, | ||||
|                                   G_FILE_QUERY_INFO_NONE, cancellable().get(), &err), | ||||
|                 false | ||||
|             }; | ||||
|             if(!inf) | ||||
|                 return; | ||||
|             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(); | ||||
|  | ||||
| @ -13,12 +13,16 @@ class LIBFM_QT_API FileInfoJob : public Job { | ||||
|     Q_OBJECT | ||||
| public: | ||||
| 
 | ||||
|     explicit FileInfoJob(FilePathList paths, FilePath commonDirPath = FilePath(), const std::shared_ptr<const HashSet>& cutFilesHashSet = nullptr); | ||||
|     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_; | ||||
|     } | ||||
| @ -31,6 +35,7 @@ protected: | ||||
| 
 | ||||
| private: | ||||
|     FilePathList paths_; | ||||
|     FilePathList deletionPaths_; | ||||
|     FileInfoList results_; | ||||
|     FilePath commonDirPath_; | ||||
|     const std::shared_ptr<const HashSet> cutFilesHashSet_; | ||||
|  | ||||
| @ -4,6 +4,7 @@ namespace Fm { | ||||
| 
 | ||||
| FileOperationJob::FileOperationJob(): | ||||
|     hasTotalAmount_{false}, | ||||
|     calcProgressUsingSize_{true}, | ||||
|     totalSize_{0}, | ||||
|     totalCount_{0}, | ||||
|     finishedSize_{0}, | ||||
| @ -31,6 +32,22 @@ bool FileOperationJob::currentFileProgress(FilePath& path, uint64_t& totalSize, | ||||
|     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); | ||||
| @ -47,31 +64,37 @@ bool FileOperationJob::finishedAmount(uint64_t& finishedSize, uint64_t& finished | ||||
| } | ||||
| 
 | ||||
| void FileOperationJob::setTotalAmount(uint64_t fileSize, uint64_t fileCount) { | ||||
|     std::lock_guard<std::mutex> locl{mutex_}; | ||||
|     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> locl{mutex_}; | ||||
|     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> locl{mutex_}; | ||||
|     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> locl{mutex_}; | ||||
|     std::lock_guard<std::mutex> lock{mutex_}; | ||||
|     currentFile_ = path; | ||||
| } | ||||
| 
 | ||||
| void FileOperationJob::setCurrentFileProgress(uint64_t totalSize, uint64_t finishedSize) { | ||||
|     std::lock_guard<std::mutex> locl{mutex_}; | ||||
|     std::lock_guard<std::mutex> lock{mutex_}; | ||||
|     currentFileSize_ = totalSize; | ||||
|     currentFileFinished_ = finishedSize; | ||||
| } | ||||
|  | ||||
| @ -24,12 +24,26 @@ public: | ||||
| 
 | ||||
|     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(); | ||||
| @ -55,8 +69,17 @@ protected: | ||||
| 
 | ||||
|     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_; | ||||
|  | ||||
							
								
								
									
										641
									
								
								src/core/filetransferjob.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										641
									
								
								src/core/filetransferjob.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,641 @@ | ||||
| #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
 | ||||
							
								
								
									
										58
									
								
								src/core/filetransferjob.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/core/filetransferjob.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | ||||
| #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
 | ||||
| @ -34,8 +34,8 @@ | ||||
| namespace Fm { | ||||
| 
 | ||||
| std::unordered_map<FilePath, std::weak_ptr<Folder>, FilePathHash> Folder::cache_; | ||||
| FilePath Folder::cutFilesDirPath_; | ||||
| FilePath Folder::lastCutFilesDirPath_; | ||||
| QString Folder::cutFilesDirPath_; | ||||
| QString Folder::lastCutFilesDirPath_; | ||||
| std::shared_ptr<const HashSet> Folder::cutFilesHashSet_; | ||||
| std::mutex Folder::mutex_; | ||||
| 
 | ||||
| @ -205,9 +205,11 @@ void Folder::onFileInfoFinished() { | ||||
|         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(); | ||||
| @ -218,7 +220,8 @@ void Folder::onFileInfoFinished() { | ||||
|         if(path == dirPath_) { // got the info for the folder itself.
 | ||||
|             dirInfo_ = info; | ||||
|         } | ||||
|         else { | ||||
|         // 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)); | ||||
| @ -235,6 +238,18 @@ void Folder::onFileInfoFinished() { | ||||
|     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(); | ||||
| } | ||||
| 
 | ||||
| @ -250,14 +265,16 @@ void Folder::processPendingChanges() { | ||||
|     } | ||||
| 
 | ||||
|     FileInfoJob* info_job = nullptr; | ||||
|     if(!paths_to_update.empty() || !paths_to_add.empty()) { | ||||
|         FilePathList paths; | ||||
|     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()); | ||||
|         info_job = new FileInfoJob{paths, dirPath_, | ||||
|                 hasCutFiles() ? cutFilesHashSet_ : nullptr}; | ||||
|         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) { | ||||
| @ -275,21 +292,6 @@ void Folder::processPendingChanges() { | ||||
| #endif | ||||
|     } | ||||
| 
 | ||||
|     if(!paths_to_del.empty()) { | ||||
|         FileInfoList deleted_files; | ||||
|         for(const auto &path: paths_to_del) { | ||||
|             auto name = path.baseName(); | ||||
|             auto it = files_.find(name.get()); | ||||
|             if(it != files_.end()) { | ||||
|                 deleted_files.push_back(it->second); | ||||
|                 files_.erase(it); | ||||
|             } | ||||
|         } | ||||
|         Q_EMIT filesRemoved(deleted_files); | ||||
|         Q_EMIT contentChanged(); | ||||
|         paths_to_del.clear(); | ||||
|     } | ||||
| 
 | ||||
|     if(pending_change_notify) { | ||||
|         Q_EMIT changed(); | ||||
|         /* update volume info */ | ||||
| @ -346,10 +348,10 @@ bool Folder::eventFileAdded(const FilePath &path) { | ||||
| bool Folder::eventFileChanged(const FilePath &path) { | ||||
|     bool added; | ||||
|     // G_LOCK(lists);
 | ||||
|     /* make sure that the file is not already queued for changes or
 | ||||
|      * it's already queued for addition. */ | ||||
|     /* 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_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. */ | ||||
| @ -367,14 +369,19 @@ bool Folder::eventFileChanged(const FilePath &path) { | ||||
| void Folder::eventFileDeleted(const FilePath& path) { | ||||
|     // qDebug() << "delete " << path.baseName().get();
 | ||||
|     // G_LOCK(lists);
 | ||||
|     if(files_.find(path.baseName().get()) != files_.cend()) { | ||||
|     /* 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); | ||||
|         } | ||||
|     } | ||||
|     /* if the file is already queued for addition or update, that operation
 | ||||
|        will be just a waste, therefore cancel it right now */ | ||||
|     paths_to_add.erase(std::remove(paths_to_add.begin(), paths_to_add.end(), path), paths_to_add.cend()); | ||||
|     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);
 | ||||
| @ -451,17 +458,16 @@ void Folder::onFileChangeEvents(GFileMonitor* /*monitor*/, GFile* gf, GFile* /*o | ||||
|             eventFileDeleted(path); | ||||
|             break; | ||||
|         default: | ||||
|             return; | ||||
|             break; | ||||
|         } | ||||
|         queueUpdate(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // checks whether there were cut files here
 | ||||
| // and if there were, invalidates this last cut path
 | ||||
| bool Folder::hadCutFilesUnset() { | ||||
|     if(lastCutFilesDirPath_ == dirPath_) { | ||||
|         lastCutFilesDirPath_ = FilePath(); | ||||
|     if(lastCutFilesDirPath_ == dirPath_.toString().get()) { | ||||
|         lastCutFilesDirPath_ = QString(); | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| @ -470,14 +476,14 @@ bool Folder::hadCutFilesUnset() { | ||||
| bool Folder::hasCutFiles() { | ||||
|     return cutFilesHashSet_ | ||||
|             && !cutFilesHashSet_->empty() | ||||
|             && cutFilesDirPath_ == dirPath_; | ||||
|             && cutFilesDirPath_ == dirPath_.toString().get(); | ||||
| } | ||||
| 
 | ||||
| void Folder::setCutFiles(const std::shared_ptr<const HashSet>& cutFilesHashSet) { | ||||
|     if(cutFilesHashSet_ && !cutFilesHashSet_->empty()) { | ||||
|         lastCutFilesDirPath_ = cutFilesDirPath_; | ||||
|     } | ||||
|     cutFilesDirPath_ = dirPath_; | ||||
|     cutFilesDirPath_ = dirPath_.toString().get(); | ||||
|     cutFilesHashSet_ = cutFilesHashSet; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -185,8 +185,8 @@ private: | ||||
|     bool defer_content_test : 1; | ||||
| 
 | ||||
|     static std::unordered_map<FilePath, std::weak_ptr<Folder>, FilePathHash> cache_; | ||||
|     static FilePath cutFilesDirPath_; | ||||
|     static FilePath lastCutFilesDirPath_; | ||||
|     static QString cutFilesDirPath_; | ||||
|     static QString lastCutFilesDirPath_; | ||||
|     static std::shared_ptr<const HashSet> cutFilesHashSet_; | ||||
|     static std::mutex mutex_; | ||||
| }; | ||||
|  | ||||
| @ -5,7 +5,7 @@ namespace Fm { | ||||
| 
 | ||||
| std::unordered_map<GIcon*, std::shared_ptr<IconInfo>, IconInfo::GIconHash, IconInfo::GIconEqual> IconInfo::cache_; | ||||
| std::mutex IconInfo::mutex_; | ||||
| QIcon IconInfo::fallbackQicon_; | ||||
| QList<QIcon> IconInfo::fallbackQicons_; | ||||
| 
 | ||||
| static const char* fallbackIconNames[] = { | ||||
|     "unknown", | ||||
| @ -15,6 +15,15 @@ static const char* fallbackIconNames[] = { | ||||
|     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} { | ||||
| } | ||||
| @ -50,10 +59,9 @@ std::shared_ptr<const IconInfo> IconInfo::fromGIcon(GIconPtr gicon) { | ||||
| 
 | ||||
| void IconInfo::updateQIcons() { | ||||
|     std::lock_guard<std::mutex> lock{mutex_}; | ||||
|     fallbackQicon_ = QIcon(); | ||||
|     for(auto& elem: cache_) { | ||||
|         auto& info = elem.second; | ||||
|         info->internalQicon_ = QIcon(); | ||||
|         info->internalQicons_.clear(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -64,7 +72,7 @@ QIcon IconInfo::qicon(const bool& transparent) const { | ||||
|                 qicon_ = QIcon(new IconEngine{shared_from_this()}); | ||||
|             } | ||||
|             else { | ||||
|                 qicon_ = internalQicon_; | ||||
|                 qicon_ = getFirst(internalQicons_); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @ -74,24 +82,21 @@ QIcon IconInfo::qicon(const bool& transparent) const { | ||||
|                 qiconTransparent_ = QIcon(new IconEngine{shared_from_this(), transparent}); | ||||
|             } | ||||
|             else { | ||||
|                 qiconTransparent_ = internalQicon_; | ||||
|                 qiconTransparent_ = getFirst(internalQicons_); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return !transparent ? qicon_ : qiconTransparent_; | ||||
| } | ||||
| 
 | ||||
| QIcon IconInfo::qiconFromNames(const char* const* names) { | ||||
|     const gchar* const* name; | ||||
| QList<QIcon> IconInfo::qiconsFromNames(const char* const* names) { | ||||
|     QList<QIcon> icons; | ||||
|     // qDebug("names: %p", names);
 | ||||
|     for(name = names; *name; ++name) { | ||||
|     for(const gchar* const* name = names; *name; ++name) { | ||||
|         // qDebug("icon name=%s", *name);
 | ||||
|         QIcon qicon = QIcon::fromTheme(*name); | ||||
|         if(!qicon.isNull()) { | ||||
|             return qicon; | ||||
|         } | ||||
|         icons.push_back(QIcon::fromTheme(*name)); | ||||
|     } | ||||
|     return QIcon(); | ||||
|     return icons; | ||||
| } | ||||
| 
 | ||||
| std::forward_list<std::shared_ptr<const IconInfo>> IconInfo::emblems() const { | ||||
| @ -109,30 +114,34 @@ std::forward_list<std::shared_ptr<const IconInfo>> IconInfo::emblems() const { | ||||
| } | ||||
| 
 | ||||
| QIcon IconInfo::internalQicon() const { | ||||
|     if(Q_UNLIKELY(internalQicon_.isNull())) { | ||||
|     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)); | ||||
|             internalQicon_ = qiconFromNames(names); | ||||
|             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)}; | ||||
|             internalQicon_ = QIcon(fpath.get()); | ||||
|             internalQicons_.push_back(QIcon(fpath.get())); | ||||
|         } | ||||
| 
 | ||||
|         // fallback to default icon
 | ||||
|         if(Q_UNLIKELY(internalQicon_.isNull())) { | ||||
|             if(Q_UNLIKELY(fallbackQicon_.isNull())) { | ||||
|                 fallbackQicon_ = qiconFromNames(fallbackIconNames); | ||||
|             } | ||||
|             internalQicon_ = fallbackQicon_; | ||||
|         } | ||||
|     } | ||||
|     return internalQicon_; | ||||
| 
 | ||||
|     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
 | ||||
|  | ||||
| @ -77,7 +77,7 @@ public: | ||||
| 
 | ||||
| private: | ||||
| 
 | ||||
|     static QIcon qiconFromNames(const char* const* names); | ||||
|     static QList<QIcon> qiconsFromNames(const char* const* names); | ||||
| 
 | ||||
|     // actual QIcon loaded by QIcon::fromTheme
 | ||||
|     QIcon internalQicon() const; | ||||
| @ -98,11 +98,11 @@ private: | ||||
|     GIconPtr gicon_; | ||||
|     mutable QIcon qicon_; | ||||
|     mutable QIcon qiconTransparent_; | ||||
|     mutable QIcon internalQicon_; | ||||
|     mutable QList<QIcon> internalQicons_; | ||||
| 
 | ||||
|     static std::unordered_map<GIcon*, std::shared_ptr<IconInfo>, GIconHash, GIconEqual> cache_; | ||||
|     static std::mutex mutex_; | ||||
|     static QIcon fallbackQicon_; | ||||
|     static QList<QIcon> fallbackQicons_; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Fm
 | ||||
|  | ||||
							
								
								
									
										130
									
								
								src/core/templates.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/core/templates.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,130 @@ | ||||
| #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
 | ||||
							
								
								
									
										100
									
								
								src/core/templates.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/core/templates.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,100 @@ | ||||
| #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
 | ||||
| @ -21,7 +21,7 @@ CStrPtr Thumbnailer::commandForUri(const char* uri, const char* output_file, gui | ||||
|         /* FIXME: how to handle TryExec? */ | ||||
| 
 | ||||
|         /* parse the command line and do required substitutions according to:
 | ||||
|          * http://developer.gnome.org/integration-guide/stable/thumbnailer.html.en
 | ||||
|          * https://developer.gnome.org/integration-guide/stable/thumbnailer.html.en
 | ||||
|          */ | ||||
|         GString* cmd_line = g_string_sized_new(1024); | ||||
|         const char* p; | ||||
| @ -119,16 +119,17 @@ void Thumbnailer::loadAll() { | ||||
|             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); | ||||
|                 char** mime_types = g_key_file_get_string_list(kf, "Thumbnailer Entry", "MimeType", nullptr, nullptr); | ||||
|                 if(mime_types && thumbnailer->exec_) { | ||||
|                     for(char** name = mime_types; *name; ++name) { | ||||
|                         auto mime_type = MimeType::fromName(*name); | ||||
|                         if(mime_type) { | ||||
|                             thumbnailer->mimeTypes_.push_back(mime_type); | ||||
|                             std::const_pointer_cast<MimeType>(mime_type)->addThumbnailer(thumbnailer); | ||||
|                 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); | ||||
|                     } | ||||
|                     g_strfreev(mime_types); | ||||
|                 } | ||||
|                 allThumbnailers_.push_back(std::move(thumbnailer)); | ||||
|             } | ||||
|  | ||||
| @ -26,7 +26,7 @@ private: | ||||
|     CStrPtr id_; | ||||
|     CStrPtr try_exec_; /* FIXME: is this useful? */ | ||||
|     CStrPtr exec_; | ||||
|     std::vector<std::shared_ptr<const MimeType>> mimeTypes_; | ||||
|     //std::vector<std::shared_ptr<const MimeType>> mimeTypes_;
 | ||||
| 
 | ||||
|     static std::mutex mutex_; | ||||
|     static std::vector<std::shared_ptr<Thumbnailer>> allThumbnailers_; | ||||
|  | ||||
| @ -120,7 +120,7 @@ bool ThumbnailJob::isSupportedImageType(const std::shared_ptr<const MimeType>& m | ||||
| 
 | ||||
| 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.toInt() != file->mtime()); | ||||
|     return (thumb_mtime.isEmpty() || thumb_mtime.toULongLong() != file->mtime()); | ||||
| } | ||||
| 
 | ||||
| bool ThumbnailJob::readJpegExif(GInputStream *stream, QImage& thumbnail, int& rotate_degrees) { | ||||
| @ -140,7 +140,7 @@ bool ThumbnailJob::readJpegExif(GInputStream *stream, QImage& thumbnail, int& ro | ||||
|     exif_loader_unref(exif_loader); | ||||
|     if(exif_data) { | ||||
|         /* reference for EXIF orientation tag:
 | ||||
|          * http://www.impulseadventure.com/photo/exif-orientation.html */
 | ||||
|          * 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; | ||||
|  | ||||
| @ -61,37 +61,34 @@ _retry_query_info: | ||||
|     /* prepare for moving across different devices */ | ||||
|     if(flags_ & PREPARE_MOVE) { | ||||
|         fs_id = g_file_info_get_attribute_string(inf.get(), G_FILE_ATTRIBUTE_ID_FILESYSTEM); | ||||
|         fs_id = g_intern_string(fs_id); | ||||
|         if(g_strcmp0(fs_id, dest_fs_id) != 0) { | ||||
|         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_; | ||||
|         } | ||||
|         else { | ||||
|             descend = false; | ||||
|             descend = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(type == G_FILE_TYPE_DIRECTORY) { | ||||
| #if 0 | ||||
|         FmPath* fm_path = fm_path_new_for_gfile(gf); | ||||
|         /* check if we need to decends into the dir. */ | ||||
|         /* trash:/// doesn't support deleting files recursively */ | ||||
|         if(flags & PREPARE_DELETE && fm_path_is_trash(fm_path) && ! fm_path_is_trash_root(fm_path)) { | ||||
|         /* 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 & FM_DC_JOB_SAME_FS) { | ||||
|                 fs_id = g_file_info_get_attribute_string(inf, G_FILE_ATTRIBUTE_ID_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); | ||||
|             } | ||||
|         } | ||||
|         fm_path_unref(fm_path); | ||||
| #endif | ||||
|         inf = nullptr; | ||||
| 
 | ||||
|         inf = nullptr; | ||||
|         if(descend) { | ||||
| _retry_enum_children: | ||||
|             GErrorPtr err; | ||||
|  | ||||
| @ -2,10 +2,9 @@ | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| TrashJob::TrashJob(const FilePathList& paths): paths_{paths} { | ||||
| } | ||||
| 
 | ||||
| TrashJob::TrashJob(const FilePathList&& paths): paths_{paths} { | ||||
| TrashJob::TrashJob(FilePathList paths): paths_{std::move(paths)} { | ||||
|     // calculate progress using finished file counts rather than their sizes
 | ||||
|     setCalcProgressUsingSize(false); | ||||
| } | ||||
| 
 | ||||
| void TrashJob::exec() { | ||||
| @ -20,33 +19,32 @@ void TrashJob::exec() { | ||||
| 
 | ||||
|         setCurrentFile(path); | ||||
| 
 | ||||
|         for(;;) { | ||||
|             GErrorPtr err; | ||||
|             GFile* gf = path.gfile().get(); | ||||
|             GFileInfoPtr inf{ | ||||
|                 g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, G_FILE_QUERY_INFO_NONE, | ||||
|                 cancellable().get(), &err), | ||||
|                 false | ||||
|             }; | ||||
|         // 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.
 | ||||
| 
 | ||||
|             bool ret = FALSE; | ||||
|         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) { | ||||
|                 err.reset(); | ||||
|                 GMountPtr mnt{g_file_find_enclosing_mount(gf, nullptr, &err), false}; | ||||
|                 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
 | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(!ret) { | ||||
|                 err.reset(); | ||||
|                 ret = g_file_trash(gf, cancellable().get(), &err); | ||||
|             // move the file to trash
 | ||||
|             GErrorPtr err; | ||||
|             ret = g_file_trash(gf.get(), cancellable().get(), &err); | ||||
|             if(ret) {  // trash operation succeeded
 | ||||
|                 break; | ||||
|             } | ||||
|             if(!ret) { | ||||
|                 /* if trashing is not supported by the file system */ | ||||
|             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); | ||||
|                 } | ||||
|  | ||||
| @ -10,8 +10,7 @@ namespace Fm { | ||||
| class LIBFM_QT_API TrashJob : public Fm::FileOperationJob { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     explicit TrashJob(const FilePathList& paths); | ||||
|     explicit TrashJob(const FilePathList&& paths); | ||||
|     explicit TrashJob(FilePathList paths); | ||||
| 
 | ||||
|     FilePathList unsupportedFiles() const { | ||||
|         return unsupportedFiles_; | ||||
|  | ||||
| @ -1,132 +1,76 @@ | ||||
| #include "untrashjob.h" | ||||
| #include "filetransferjob.h" | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| UntrashJob::UntrashJob() { | ||||
| 
 | ||||
| UntrashJob::UntrashJob(FilePathList srcPaths): | ||||
|     srcPaths_{std::move(srcPaths)} { | ||||
| } | ||||
| 
 | ||||
| static const char trash_query[] = | ||||
|     G_FILE_ATTRIBUTE_STANDARD_TYPE"," | ||||
|     G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME"," | ||||
|     G_FILE_ATTRIBUTE_STANDARD_NAME"," | ||||
|     G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL"," | ||||
|     G_FILE_ATTRIBUTE_STANDARD_SIZE"," | ||||
|     G_FILE_ATTRIBUTE_UNIX_BLOCKS"," | ||||
|     G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE"," | ||||
|     G_FILE_ATTRIBUTE_ID_FILESYSTEM"," | ||||
|     "trash::orig-path"; | ||||
| 
 | ||||
| bool UntrashJob::ensure_parent_dir(GFile* orig_path) { | ||||
|     GFile* parent = g_file_get_parent(orig_path); | ||||
|     gboolean ret = g_file_query_exists(parent, cancellable().get()); | ||||
|     if(!ret) { | ||||
|         GErrorPtr err; | ||||
| _retry_mkdir: | ||||
|         if(!g_file_make_directory_with_parents(parent, cancellable().get(), &err)) { | ||||
|             if(!isCancelled()) { | ||||
|                 ErrorAction act = emitError(err, ErrorSeverity::MODERATE); | ||||
|                 err = nullptr; | ||||
|                 if(act == ErrorAction::RETRY) { | ||||
|                     goto _retry_mkdir; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             ret = TRUE; | ||||
|         } | ||||
|     } | ||||
|     g_object_unref(parent); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void UntrashJob::exec() { | ||||
| #if 0 | ||||
|     gboolean ret = TRUE; | ||||
|     GList* l; | ||||
|     GError* err = nullptr; | ||||
|     FmJob* fmjob = FM_JOB(job); | ||||
|     job->total = fm_path_list_get_length(job->srcs); | ||||
|     fm_file_ops_job_emit_prepared(job); | ||||
| 
 | ||||
|     l = fm_path_list_peek_head_link(job->srcs); | ||||
|     for(; !fm_job_is_cancelled(fmjob) && l; l = l->next) { | ||||
|         GFile* gf; | ||||
|         GFileInfo* inf; | ||||
|         FmPath* path = FM_PATH(l->data); | ||||
|         if(!fm_path_is_trash(path)) { | ||||
|             continue; | ||||
|     // preparing for the job
 | ||||
|     FilePathList validSrcPaths; | ||||
|     FilePathList origPaths; | ||||
|     for(auto& srcPath: srcPaths_) { | ||||
|         if(isCancelled()) { | ||||
|             break; | ||||
|         } | ||||
|         gf = fm_path_to_gfile(path); | ||||
| _retry_get_orig_path: | ||||
|         inf = g_file_query_info(gf, trash_query, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, fm_job_get_cancellable(fmjob), &err); | ||||
|         if(inf) { | ||||
|             const char* orig_path_str = g_file_info_get_attribute_byte_string(inf, "trash::orig-path"); | ||||
|             fm_file_ops_job_emit_cur_file(job, g_file_info_get_display_name(inf)); | ||||
| 
 | ||||
|         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) { | ||||
|                 /* FIXME: what if orig_path_str is a relative path?
 | ||||
|                  * This is actually allowed by the horrible trash spec. */ | ||||
|                 GFile* orig_path = fm_file_new_for_commandline_arg(orig_path_str); | ||||
|                 FmFolder* src_folder = fm_folder_find_by_path(fm_path_get_parent(path)); | ||||
|                 FmPath* orig_fm_path = fm_path_new_for_gfile(orig_path); | ||||
|                 FmFolder* dst_folder = fm_folder_find_by_path(fm_path_get_parent(orig_fm_path)); | ||||
|                 fm_path_unref(orig_fm_path); | ||||
|                 /* ensure the existence of parent folder. */ | ||||
|                 if(ensure_parent_dir(fmjob, orig_path)) { | ||||
|                     ret = _fm_file_ops_job_move_file(job, gf, inf, orig_path, path, src_folder, dst_folder); | ||||
|                 } | ||||
|                 if(src_folder) { | ||||
|                     g_object_unref(src_folder); | ||||
|                 } | ||||
|                 if(dst_folder) { | ||||
|                     g_object_unref(dst_folder); | ||||
|                 } | ||||
|                 g_object_unref(orig_path); | ||||
|                 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, | ||||
|                             _("Cannot untrash file '%s': original path not known"), | ||||
|                             g_file_info_get_display_name(inf)); | ||||
|                 act = emitError( err, ErrorSeverity::MODERATE); | ||||
|                 g_clear_error(&err); | ||||
|                 if(act == ErrorAction::ABORT) { | ||||
|                     g_object_unref(inf); | ||||
|                     g_object_unref(gf); | ||||
|                     return FALSE; | ||||
|                 } | ||||
|                             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); | ||||
|             } | ||||
|             g_object_unref(inf); | ||||
|         } | ||||
|         else { | ||||
|             char* basename = g_file_get_basename(gf); | ||||
|             char* disp = basename ? g_filename_display_name(basename) : nullptr; | ||||
|             g_free(basename); | ||||
|             /* FIXME: translate it */ | ||||
|             fm_file_ops_job_emit_cur_file(job, disp ? disp : "(invalid file)"); | ||||
|             g_free(disp); | ||||
| 
 | ||||
|             if(err) { | ||||
|                 ErrorAction act = emitError( err, ErrorSeverity::MODERATE); | ||||
|                 g_error_free(err); | ||||
|                 err = nullptr; | ||||
|                 if(act == ErrorAction::RETRY) { | ||||
|                     goto _retry_get_orig_path; | ||||
|                 } | ||||
|                 else if(act == ErrorAction::ABORT) { | ||||
|                     g_object_unref(gf); | ||||
|                     return FALSE; | ||||
|                 } | ||||
|             } | ||||
|             // FIXME: do we need to retry here?
 | ||||
|             emitError(err); | ||||
|         } | ||||
|         g_object_unref(gf); | ||||
|         ++job->finished; | ||||
|         fm_file_ops_job_emit_percent(job); | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     // 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
 | ||||
|  | ||||
| @ -6,15 +6,15 @@ | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| class LIBFM_QT_API UntrashJob : public Fm::FileOperationJob { | ||||
| class LIBFM_QT_API UntrashJob : public FileOperationJob { | ||||
| public: | ||||
|     explicit UntrashJob(); | ||||
|     explicit UntrashJob(FilePathList srcPaths); | ||||
| 
 | ||||
| protected: | ||||
|     void exec() override; | ||||
| 
 | ||||
| private: | ||||
|     bool ensure_parent_dir(GFile *orig_path); | ||||
|     FilePathList srcPaths_; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Fm
 | ||||
|  | ||||
| @ -19,14 +19,45 @@ | ||||
| 
 | ||||
| #include "createnewmenu.h" | ||||
| #include "folderview.h" | ||||
| #include "icontheme.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)) { | ||||
|     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); | ||||
| @ -36,27 +67,12 @@ CreateNewMenu::CreateNewMenu(QWidget* dialogParent, Fm::FilePath dirPath, QWidge | ||||
|     addAction(action); | ||||
| 
 | ||||
|     // add more items to "Create New" menu from templates
 | ||||
|     GList* templates = fm_template_list_all(fm_config->only_user_templates); | ||||
|     if(templates) { | ||||
|         addSeparator(); | ||||
|         for(GList* l = templates; l; l = l->next) { | ||||
|             FmTemplate* templ = (FmTemplate*)l->data; | ||||
|             /* we support directories differently */ | ||||
|             if(fm_template_is_directory(templ)) { | ||||
|                 continue; | ||||
|             } | ||||
|             FmMimeType* mime_type = fm_template_get_mime_type(templ); | ||||
|             const char* label = fm_template_get_label(templ); | ||||
|             QString text = QString("%1 (%2)").arg(QString::fromUtf8(label)).arg(QString::fromUtf8(fm_mime_type_get_desc(mime_type))); | ||||
|             FmIcon* icon = fm_template_get_icon(templ); | ||||
|             if(!icon) { | ||||
|                 icon = fm_mime_type_get_icon(mime_type); | ||||
|             } | ||||
|             QAction* action = addAction(Fm::IconInfo::fromGIcon(G_ICON(icon))->qicon(), text); | ||||
|             action->setObjectName(QString::fromUtf8(fm_template_get_name(templ, nullptr))); | ||||
|             connect(action, &QAction::triggered, this, &CreateNewMenu::onCreateNew); | ||||
|         } | ||||
|     } | ||||
|     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() { | ||||
| @ -75,22 +91,62 @@ void CreateNewMenu::onCreateNewFolder() { | ||||
| } | ||||
| 
 | ||||
| void CreateNewMenu::onCreateNew() { | ||||
|     QAction* action = static_cast<QAction*>(sender()); | ||||
|     QByteArray name = action->objectName().toUtf8(); | ||||
|     GList* templates = fm_template_list_all(fm_config->only_user_templates); | ||||
|     FmTemplate* templ = nullptr; | ||||
|     for(GList* l = templates; l; l = l->next) { | ||||
|         FmTemplate* t = (FmTemplate*)l->data; | ||||
|         if(name == fm_template_get_name(t, nullptr)) { | ||||
|             templ = t; | ||||
|             break; | ||||
|         } | ||||
|     TemplateAction* action = static_cast<TemplateAction*>(sender()); | ||||
|     if(dirPath_) { | ||||
|         createFileOrFolder(CreateWithTemplate, dirPath_, action->templateItem().get(), dialogParent_); | ||||
|     } | ||||
|     if(templ) { // template found
 | ||||
|         if(dirPath_) { | ||||
|             createFileOrFolder(CreateWithTemplate, dirPath_, templ, 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,4 +1,4 @@ | ||||
| /*
 | ||||
| /*
 | ||||
|  * Copyright (C) 2012 - 2015  Hong Jen Yee (PCMan) <pcman.tw@gmail.com> | ||||
|  * | ||||
|  * This library is free software; you can redistribute it and/or | ||||
| @ -29,6 +29,8 @@ | ||||
| namespace Fm { | ||||
| 
 | ||||
| class FolderView; | ||||
| class Templates; | ||||
| class TemplateItem; | ||||
| 
 | ||||
| class LIBFM_QT_API CreateNewMenu : public QMenu { | ||||
|     Q_OBJECT | ||||
| @ -39,12 +41,23 @@ public: | ||||
| 
 | ||||
| protected Q_SLOTS: | ||||
|     void onCreateNewFolder(); | ||||
| 
 | ||||
|     void onCreateNewFile(); | ||||
| 
 | ||||
|     void onCreateNew(); | ||||
| 
 | ||||
| private Q_SLOTS: | ||||
|     void addTemplateItem(const std::shared_ptr<const TemplateItem>& item); | ||||
| 
 | ||||
|     void updateTemplateItem(const std::shared_ptr<const TemplateItem>& oldItem, const std::shared_ptr<const TemplateItem>& newItem); | ||||
| 
 | ||||
|     void removeTemplateItem(const std::shared_ptr<const TemplateItem>& item); | ||||
| 
 | ||||
| private: | ||||
|     QWidget* dialogParent_; | ||||
|     Fm::FilePath dirPath_; | ||||
|     QAction* templateSeparator_; | ||||
|     std::shared_ptr<Templates> templates_; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -373,12 +373,17 @@ bool FileActionCondition::match_folder(const FileInfoList& files, const char* fo | ||||
|         pattern = g_pattern_spec_new(folder); | ||||
|     } | ||||
|     else { | ||||
|         auto pat_str = string(folder) + "/*"; | ||||
|         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->dirPath().toString(); | ||||
|         if(g_pattern_match_string(pattern, dirname.get())) { // at least 1 file is in the folder
 | ||||
|         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; | ||||
|             } | ||||
|  | ||||
| @ -32,6 +32,9 @@ FileActionProfile::FileActionProfile(GKeyFile *kf, const char* profile_name) { | ||||
|             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)}; | ||||
|  | ||||
| @ -167,7 +167,7 @@ QModelIndex DirTreeModel::indexFromPath(const Fm::FilePath &path) const { | ||||
| } | ||||
| 
 | ||||
| DirTreeModelItem* DirTreeModel::itemFromPath(const Fm::FilePath &path) const { | ||||
|     Q_FOREACH(DirTreeModelItem* item, rootItems_) { | ||||
|     for(DirTreeModelItem* const item : qAsConst(rootItems_)) { | ||||
|         if(item->fileInfo_ && path == item->fileInfo_->path()) { | ||||
|             return item; | ||||
|         } | ||||
| @ -224,7 +224,7 @@ QString DirTreeModel::dispName(const QModelIndex& index) { | ||||
| 
 | ||||
| void DirTreeModel::setShowHidden(bool show_hidden) { | ||||
|     showHidden_ = show_hidden; | ||||
|     Q_FOREACH(DirTreeModelItem* item, rootItems_) { | ||||
|     for(DirTreeModelItem* const item : qAsConst(rootItems_)) { | ||||
|         item->setShowHidden(show_hidden); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -19,7 +19,6 @@ | ||||
| 
 | ||||
| #include "dirtreemodelitem.h" | ||||
| #include "dirtreemodel.h" | ||||
| #include "icontheme.h" | ||||
| #include <QDebug> | ||||
| 
 | ||||
| namespace Fm { | ||||
| @ -55,12 +54,12 @@ DirTreeModelItem::~DirTreeModelItem() { | ||||
|     freeFolder(); | ||||
|     // delete child items if needed
 | ||||
|     if(!children_.empty()) { | ||||
|         Q_FOREACH(DirTreeModelItem* item, children_) { | ||||
|         for(DirTreeModelItem* const item : qAsConst(children_)) { | ||||
|             delete item; | ||||
|         } | ||||
|     } | ||||
|     if(!hiddenChildren_.empty()) { | ||||
|         Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) { | ||||
|         for(DirTreeModelItem* const item : qAsConst(hiddenChildren_)) { | ||||
|             delete item; | ||||
|         } | ||||
|     } | ||||
| @ -124,7 +123,7 @@ void DirTreeModelItem::unloadFolder() { | ||||
|         // delete all visible child items
 | ||||
|         model_->beginRemoveRows(index(), 0, children_.size() - 1); | ||||
|         if(!children_.empty()) { | ||||
|             Q_FOREACH(DirTreeModelItem* item, children_) { | ||||
|             for(DirTreeModelItem* const item : qAsConst(children_)) { | ||||
|                 delete item; | ||||
|             } | ||||
|             children_.clear(); | ||||
| @ -133,7 +132,7 @@ void DirTreeModelItem::unloadFolder() { | ||||
| 
 | ||||
|         // remove hidden children
 | ||||
|         if(!hiddenChildren_.empty()) { | ||||
|             Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) { | ||||
|             for(DirTreeModelItem* const item : qAsConst(hiddenChildren_)) { | ||||
|                 delete item; | ||||
|             } | ||||
|             hiddenChildren_.clear(); | ||||
| @ -343,7 +342,7 @@ DirTreeModelItem* DirTreeModelItem::childFromName(const char* utf8_name, int* po | ||||
| DirTreeModelItem* DirTreeModelItem::childFromPath(Fm::FilePath path, bool recursive) const { | ||||
|     Q_ASSERT(path != nullptr); | ||||
| 
 | ||||
|     Q_FOREACH(DirTreeModelItem* item, children_) { | ||||
|     for(DirTreeModelItem* const item : qAsConst(children_)) { | ||||
|         // if(item->fileInfo_)
 | ||||
|         //  qDebug() << "child: " << QString::fromUtf8(fm_file_info_get_disp_name(item->fileInfo_));
 | ||||
|         if(item->fileInfo_ && item->fileInfo_->path() == path) { | ||||
|  | ||||
| @ -306,7 +306,7 @@ void DirTreeView::rowsRemoved(const QModelIndex& parent, int start, int end) { | ||||
| 
 | ||||
| void DirTreeView::doQueuedDeletions() { | ||||
|     if(!queuedForDeletion_.empty()) { | ||||
|         Q_FOREACH(DirTreeModelItem* item, queuedForDeletion_) { | ||||
|         for(DirTreeModelItem* const item : qAsConst(queuedForDeletion_)) { | ||||
|             delete item; | ||||
|         } | ||||
|         queuedForDeletion_.clear(); | ||||
|  | ||||
| @ -51,6 +51,7 @@ bool DndDest::dropMimeData(const QMimeData* data, Qt::DropAction action) { | ||||
|         break; | ||||
|       case Qt::LinkAction: | ||||
|         FileOperation::symlinkFiles(srcPaths, destPath_); | ||||
|       /* Falls through. */ | ||||
|       default: | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| @ -102,8 +102,8 @@ void EditBookmarksDialog::onAddItem() { | ||||
| } | ||||
| 
 | ||||
| void EditBookmarksDialog::onRemoveItem() { | ||||
|     QList<QTreeWidgetItem*> sels = ui->treeWidget->selectedItems(); | ||||
|     Q_FOREACH(QTreeWidgetItem* item, sels) { | ||||
|     const QList<QTreeWidgetItem*> sels = ui->treeWidget->selectedItems(); | ||||
|     for(QTreeWidgetItem* const item : sels) { | ||||
|         delete item; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -19,31 +19,37 @@ | ||||
| 
 | ||||
| #include "execfiledialog_p.h" | ||||
| #include "ui_exec-file.h" | ||||
| #include "icontheme.h" | ||||
| #include "core/iconinfo.h" | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| ExecFileDialog::ExecFileDialog(FmFileInfo* file, QWidget* parent, Qt::WindowFlags f): | ||||
| ExecFileDialog::ExecFileDialog(const FileInfo &fileInfo, QWidget* parent, Qt::WindowFlags f): | ||||
|     QDialog(parent, f), | ||||
|     ui(new Ui::ExecFileDialog()), | ||||
|     fileInfo_(fm_file_info_ref(file)), | ||||
|     result_(FM_FILE_LAUNCHER_EXEC_CANCEL) { | ||||
|     result_(BasicFileLauncher::ExecAction::DIRECT_EXEC) { | ||||
| 
 | ||||
|     ui->setupUi(this); | ||||
|     // show file icon
 | ||||
|     GIcon* gicon = G_ICON(fm_file_info_get_icon(fileInfo_)); | ||||
|     ui->icon->setPixmap(Fm::IconInfo::fromGIcon(gicon)->qicon().pixmap(QSize(48, 48))); | ||||
|     auto gicon = fileInfo.icon(); | ||||
|     if(gicon) { | ||||
|         ui->icon->setPixmap(gicon->qicon().pixmap(QSize(48, 48))); | ||||
|     } | ||||
| 
 | ||||
|     QString msg; | ||||
|     if(fm_file_info_is_text(file)) { | ||||
|     if(fileInfo.isDesktopEntry()) { | ||||
|         msg = tr("This file '%1' seems to be a desktop entry.\nWhat do you want to do with it?") | ||||
|               .arg(fileInfo.displayName()); | ||||
|         ui->exec->setDefault(true); | ||||
|         ui->execTerm->hide(); | ||||
|     } | ||||
|     else if(fileInfo.isText()) { | ||||
|         msg = tr("This text file '%1' seems to be an executable script.\nWhat do you want to do with it?") | ||||
|               .arg(QString::fromUtf8(fm_file_info_get_disp_name(file))); | ||||
|               .arg(fileInfo.displayName()); | ||||
|         ui->execTerm->setDefault(true); | ||||
|     } | ||||
|     else { | ||||
|         msg = tr("This file '%1' is executable. Do you want to execute it?") | ||||
|               .arg(QString::fromUtf8(fm_file_info_get_disp_name(file))); | ||||
|               .arg(fileInfo.displayName()); | ||||
|         ui->exec->setDefault(true); | ||||
|         ui->open->hide(); | ||||
|     } | ||||
| @ -52,23 +58,28 @@ ExecFileDialog::ExecFileDialog(FmFileInfo* file, QWidget* parent, Qt::WindowFlag | ||||
| 
 | ||||
| ExecFileDialog::~ExecFileDialog() { | ||||
|     delete ui; | ||||
|     if(fileInfo_) { | ||||
|         fm_file_info_unref(fileInfo_); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ExecFileDialog::accept() { | ||||
|     QObject* _sender = sender(); | ||||
|     if(_sender == ui->exec) { | ||||
|         result_ = FM_FILE_LAUNCHER_EXEC; | ||||
|         result_ = BasicFileLauncher::ExecAction::DIRECT_EXEC; | ||||
|     } | ||||
|     else if(_sender == ui->execTerm) { | ||||
|         result_ = FM_FILE_LAUNCHER_EXEC_IN_TERMINAL; | ||||
|         result_ = BasicFileLauncher::ExecAction::EXEC_IN_TERMINAL; | ||||
|     } | ||||
|     else if(_sender == ui->open) { | ||||
|         result_ = FM_FILE_LAUNCHER_EXEC_OPEN; | ||||
|         result_ = BasicFileLauncher::ExecAction::OPEN_WITH_DEFAULT_APP; | ||||
|     } | ||||
|     else { | ||||
|         result_ = BasicFileLauncher::ExecAction::CANCEL; | ||||
|     } | ||||
|     QDialog::accept(); | ||||
| } | ||||
| 
 | ||||
| void ExecFileDialog::reject() { | ||||
|     result_ = BasicFileLauncher::ExecAction::CANCEL; | ||||
|     QDialog::reject(); | ||||
| } | ||||
| 
 | ||||
| } // namespace Fm
 | ||||
|  | ||||
| @ -20,8 +20,12 @@ | ||||
| #ifndef FM_EXECFILEDIALOG_H | ||||
| #define FM_EXECFILEDIALOG_H | ||||
| 
 | ||||
| #include "core/basicfilelauncher.h" | ||||
| #include "core/fileinfo.h" | ||||
| 
 | ||||
| #include <QDialog> | ||||
| #include <libfm/fm.h> | ||||
| 
 | ||||
| #include <memory> | ||||
| 
 | ||||
| namespace Ui { | ||||
|   class ExecFileDialog; | ||||
| @ -33,19 +37,19 @@ class ExecFileDialog : public QDialog { | ||||
|   Q_OBJECT | ||||
| public: | ||||
|   ~ExecFileDialog(); | ||||
|   ExecFileDialog(FmFileInfo* fileInfo, QWidget* parent = 0, Qt::WindowFlags f = 0); | ||||
|   ExecFileDialog(const FileInfo& fileInfo, QWidget* parent = 0, Qt::WindowFlags f = 0); | ||||
| 
 | ||||
|   FmFileLauncherExecAction result() { | ||||
|   BasicFileLauncher::ExecAction result() { | ||||
|     return result_; | ||||
|   } | ||||
| 
 | ||||
| protected: | ||||
|   virtual void accept(); | ||||
|   virtual void accept() override; | ||||
|   virtual void reject() override; | ||||
| 
 | ||||
| private: | ||||
|   Ui::ExecFileDialog* ui; | ||||
|   FmFileInfo* fileInfo_; | ||||
|   FmFileLauncherExecAction result_; | ||||
|   BasicFileLauncher::ExecAction result_; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>450</width> | ||||
|     <height>246</height> | ||||
|     <height>297</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
| @ -34,7 +34,7 @@ | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item row="1" column="1"> | ||||
|       <widget class="QLabel" name="dest"> | ||||
|       <widget class="Fm::ElidedLabel" name="dest"> | ||||
|        <property name="sizePolicy"> | ||||
|         <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> | ||||
|          <horstretch>0</horstretch> | ||||
| @ -57,7 +57,7 @@ | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item row="2" column="1"> | ||||
|       <widget class="QLabel" name="curFile"> | ||||
|       <widget class="Fm::ElidedLabel" name="curFile"> | ||||
|        <property name="sizePolicy"> | ||||
|         <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> | ||||
|          <horstretch>0</horstretch> | ||||
| @ -122,12 +122,12 @@ | ||||
|      <item row="4" column="0"> | ||||
|       <widget class="QLabel" name="label_6"> | ||||
|        <property name="text"> | ||||
|         <string>Data transferred:</string> | ||||
|         <string>Files processed:</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item row="4" column="1"> | ||||
|       <widget class="QLabel" name="dataTransferred"> | ||||
|       <widget class="QLabel" name="filesProcessed"> | ||||
|        <property name="text"> | ||||
|         <string/> | ||||
|        </property> | ||||
| @ -147,6 +147,13 @@ | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
|  <customwidgets> | ||||
|   <customwidget> | ||||
|    <class>Fm::ElidedLabel</class> | ||||
|    <extends>QLabel</extends> | ||||
|    <header>fileoperationdialog_p.h</header> | ||||
|   </customwidget> | ||||
|  </customwidgets> | ||||
|  <resources/> | ||||
|  <connections> | ||||
|   <connection> | ||||
|  | ||||
| @ -96,7 +96,7 @@ | ||||
|           <bool>true</bool> | ||||
|          </property> | ||||
|          <property name="textInteractionFlags"> | ||||
|           <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> | ||||
|           <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
| @ -119,7 +119,7 @@ | ||||
|           <string/> | ||||
|          </property> | ||||
|          <property name="textInteractionFlags"> | ||||
|           <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> | ||||
|           <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
| @ -142,7 +142,7 @@ | ||||
|           <string/> | ||||
|          </property> | ||||
|          <property name="textInteractionFlags"> | ||||
|           <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> | ||||
|           <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
| @ -165,7 +165,7 @@ | ||||
|           <string/> | ||||
|          </property> | ||||
|          <property name="textInteractionFlags"> | ||||
|           <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> | ||||
|           <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
| @ -188,7 +188,7 @@ | ||||
|           <string/> | ||||
|          </property> | ||||
|          <property name="textInteractionFlags"> | ||||
|           <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> | ||||
|           <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
| @ -211,7 +211,7 @@ | ||||
|           <string/> | ||||
|          </property> | ||||
|          <property name="textInteractionFlags"> | ||||
|           <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> | ||||
|           <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
| @ -237,7 +237,7 @@ | ||||
|           <bool>true</bool> | ||||
|          </property> | ||||
|          <property name="textInteractionFlags"> | ||||
|           <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> | ||||
|           <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
| @ -246,9 +246,6 @@ | ||||
|          <property name="text"> | ||||
|           <string>Open With:</string> | ||||
|          </property> | ||||
|          <property name="textInteractionFlags"> | ||||
|           <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|        <item row="5" column="1"> | ||||
| @ -274,7 +271,7 @@ | ||||
|           <string/> | ||||
|          </property> | ||||
|          <property name="textInteractionFlags"> | ||||
|           <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> | ||||
|           <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> | ||||
|          </property> | ||||
|         </widget> | ||||
|        </item> | ||||
|  | ||||
| @ -346,41 +346,43 @@ void FileDialog::goHome() { | ||||
| } | ||||
| 
 | ||||
| void FileDialog::setDirectoryPath(FilePath directory, FilePath selectedPath, bool addHistory) { | ||||
|     if(!directory.isValid() || directoryPath_ == directory) { | ||||
|     if(!directory.isValid()) { | ||||
|         updateAcceptButtonState(); // FIXME: is this needed?
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|    if(folder_) { | ||||
|         if(folderModel_) { | ||||
|             proxyModel_->setSourceModel(nullptr); | ||||
|             folderModel_->unref(); // unref the cached model
 | ||||
|             folderModel_ = nullptr; | ||||
|    if(directoryPath_ != directory) { | ||||
|        if(folder_) { | ||||
|             if(folderModel_) { | ||||
|                 proxyModel_->setSourceModel(nullptr); | ||||
|                 folderModel_->unref(); // unref the cached model
 | ||||
|                 folderModel_ = nullptr; | ||||
|             } | ||||
|             freeFolder(); | ||||
|        } | ||||
|      | ||||
|         directoryPath_ = std::move(directory); | ||||
|      | ||||
|         ui->location->setPath(directoryPath_); | ||||
|         ui->sidePane->chdir(directoryPath_); | ||||
|         if(addHistory) { | ||||
|             history_.add(directoryPath_); | ||||
|         } | ||||
|         freeFolder(); | ||||
|         backAction_->setEnabled(history_.canBackward()); | ||||
|         forwardAction_->setEnabled(history_.canForward()); | ||||
|      | ||||
|         folder_ = Fm::Folder::fromPath(directoryPath_); | ||||
|         folderModel_ = CachedFolderModel::modelFromFolder(folder_); | ||||
|         proxyModel_->setSourceModel(folderModel_); | ||||
|      | ||||
|         // no lambda in these connections for easy disconnection
 | ||||
|         connect(folder_.get(), &Fm::Folder::removed, this, &FileDialog::goHome); | ||||
|         connect(folder_.get(), &Fm::Folder::unmount, this, &FileDialog::goHome); | ||||
|      | ||||
|         QUrl uri = QUrl::fromEncoded(directory.uri().get()); | ||||
|         Q_EMIT directoryEntered(uri); | ||||
|    } | ||||
| 
 | ||||
|     directoryPath_ = std::move(directory); | ||||
| 
 | ||||
|     ui->location->setPath(directoryPath_); | ||||
|     ui->sidePane->chdir(directoryPath_); | ||||
|     if(addHistory) { | ||||
|         history_.add(directoryPath_); | ||||
|     } | ||||
|     backAction_->setEnabled(history_.canBackward()); | ||||
|     forwardAction_->setEnabled(history_.canForward()); | ||||
| 
 | ||||
|     folder_ = Fm::Folder::fromPath(directoryPath_); | ||||
|     folderModel_ = CachedFolderModel::modelFromFolder(folder_); | ||||
|     proxyModel_->setSourceModel(folderModel_); | ||||
| 
 | ||||
|     // no lambda in these connections for easy disconnection
 | ||||
|     connect(folder_.get(), &Fm::Folder::removed, this, &FileDialog::goHome); | ||||
|     connect(folder_.get(), &Fm::Folder::unmount, this, &FileDialog::goHome); | ||||
| 
 | ||||
|     QUrl uri = QUrl::fromEncoded(directory.uri().get()); | ||||
|     Q_EMIT directoryEntered(uri); | ||||
| 
 | ||||
|     // select the path if valid
 | ||||
|     if(selectedPath.isValid()) { | ||||
|         if(folder_->isLoaded()) { | ||||
| @ -415,13 +417,13 @@ void FileDialog::selectFilePath(const FilePath &path) { | ||||
|     QItemSelectionModel* selModel = ui->folderView->selectionModel(); | ||||
|     selModel->select(idx, flags); | ||||
|     selModel->setCurrentIndex(idx, QItemSelectionModel::Current); | ||||
|     QTimer::singleShot(0, [this, idx]() { | ||||
|     QTimer::singleShot(0, this, [this, idx]() { | ||||
|         ui->folderView->childView()->scrollTo(idx, QAbstractItemView::PositionAtCenter); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void FileDialog::selectFilePathWithDelay(const FilePath &path) { | ||||
|     QTimer::singleShot(0, [this, path]() { | ||||
|     QTimer::singleShot(0, this, [this, path]() { | ||||
|         if(acceptMode_ == QFileDialog::AcceptSave) { | ||||
|             // with a save dialog, always put the base name in line-edit, regardless of selection
 | ||||
|             ui->fileName->setText(path.baseName().get()); | ||||
| @ -436,7 +438,7 @@ void FileDialog::selectFilePathWithDelay(const FilePath &path) { | ||||
| 
 | ||||
| void FileDialog::selectFilesOnReload(const Fm::FileInfoList& infos) { | ||||
|     QObject::disconnect(lambdaConnection_); | ||||
|     QTimer::singleShot(0, [this, infos]() { | ||||
|     QTimer::singleShot(0, this, [this, infos]() { | ||||
|         for(auto& fileInfo: infos) { | ||||
|             selectFilePath(fileInfo->path()); | ||||
|         } | ||||
| @ -635,13 +637,8 @@ void FileDialog::selectFile(const QUrl& filename) { | ||||
|     auto urlStr = filename.toEncoded(); | ||||
|     auto path = FilePath::fromUri(urlStr.constData()); | ||||
|     auto parent = path.parent(); | ||||
|     if(parent.isValid() && parent != directoryPath_) { | ||||
|         // chdir into file's parent if it isn't the current directory
 | ||||
|         setDirectoryPath(parent, path); | ||||
|     } | ||||
|     else { | ||||
|         selectFilePathWithDelay(path); | ||||
|     } | ||||
|     // chdir into file's parent if needed and select the file
 | ||||
|     setDirectoryPath(parent, path); | ||||
| } | ||||
| 
 | ||||
| QList<QUrl> FileDialog::selectedFiles() { | ||||
| @ -770,7 +767,8 @@ void FileDialog::setMimeTypeFilters(const QStringList& filters) { | ||||
|         auto nameFilter = mimeType.comment(); | ||||
|         if(!mimeType.suffixes().empty()) { | ||||
|             nameFilter + " ("; | ||||
|             for(const auto& suffix: mimeType.suffixes()) { | ||||
|             const auto suffixes = mimeType.suffixes(); | ||||
|             for(const auto& suffix: suffixes) { | ||||
|                 nameFilter += "*."; | ||||
|                 nameFilter += suffix; | ||||
|                 nameFilter += ' '; | ||||
|  | ||||
| @ -26,111 +26,73 @@ | ||||
| #include "appchooserdialog.h" | ||||
| #include "utilities.h" | ||||
| 
 | ||||
| #include "core/fileinfojob.h" | ||||
| #include "mountoperation.h" | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| FmFileLauncher FileLauncher::funcs = { | ||||
|     FileLauncher::_getApp, | ||||
|     /* gboolean (*before_open)(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data); */ | ||||
|     (FmLaunchFolderFunc)FileLauncher::_openFolder, | ||||
|     FileLauncher::_execFile, | ||||
|     FileLauncher::_error, | ||||
|     FileLauncher::_ask, | ||||
|     nullptr | ||||
| }; | ||||
| 
 | ||||
| FileLauncher::FileLauncher(): | ||||
|     quickExec_(false) { | ||||
| FileLauncher::FileLauncher() { | ||||
| } | ||||
| 
 | ||||
| FileLauncher::~FileLauncher() { | ||||
| } | ||||
| 
 | ||||
| bool FileLauncher::launchFiles(QWidget *parent, Fm::FileInfoList file_infos) { | ||||
|     // FIXME: rewrite
 | ||||
|     return launchPaths(parent, file_infos.paths()); | ||||
| } | ||||
| 
 | ||||
| bool FileLauncher::launchPaths(QWidget *parent, Fm::FilePathList paths) { | ||||
|     // FIXME: rewrite, port to new api
 | ||||
|     GList* tmp = nullptr; | ||||
|     for(auto& path: paths) { | ||||
|         auto fmpath = fm_path_new_for_gfile(path.gfile().get()); | ||||
|         tmp = g_list_prepend(tmp, fmpath); | ||||
|     } | ||||
|     tmp = g_list_reverse(tmp); | ||||
|     bool ret = launchPaths(parent, tmp); | ||||
|     g_list_free(tmp); | ||||
| bool FileLauncher::launchFiles(QWidget* parent, const FileInfoList &file_infos) { | ||||
|     GObjectPtr<FmAppLaunchContext> context{fm_app_launch_context_new_for_widget(parent), false}; | ||||
|     bool ret = BasicFileLauncher::launchFiles(file_infos, G_APP_LAUNCH_CONTEXT(context.get())); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool FileLauncher::launchFiles(QWidget* parent, GList* file_infos) { | ||||
|     FmAppLaunchContext* context = fm_app_launch_context_new_for_widget(parent); | ||||
|     bool ret = fm_launch_files(G_APP_LAUNCH_CONTEXT(context), file_infos, &funcs, this); | ||||
|     g_object_unref(context); | ||||
| bool FileLauncher::launchPaths(QWidget* parent, const FilePathList& paths) { | ||||
|     GObjectPtr<FmAppLaunchContext> context{fm_app_launch_context_new_for_widget(parent), false}; | ||||
|     bool ret = BasicFileLauncher::launchPaths(paths, G_APP_LAUNCH_CONTEXT(context.get())); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool FileLauncher::launchPaths(QWidget* parent, GList* paths) { | ||||
|     FmAppLaunchContext* context = fm_app_launch_context_new_for_widget(parent); | ||||
|     bool ret = fm_launch_paths(G_APP_LAUNCH_CONTEXT(context), paths, &funcs, this); | ||||
|     g_object_unref(context); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| GAppInfo* FileLauncher::getApp(GList* /*file_infos*/, FmMimeType* mime_type, GError** /*err*/) { | ||||
|     AppChooserDialog dlg(nullptr); | ||||
|     if(mime_type) { | ||||
|         dlg.setMimeType(Fm::MimeType::fromName(fm_mime_type_get_type(mime_type))); | ||||
|     } | ||||
|     else { | ||||
|         dlg.setCanSetDefault(false); | ||||
|     } | ||||
|     // FIXME: show error properly?
 | ||||
|     if(execModelessDialog(&dlg) == QDialog::Accepted) { | ||||
|         auto app = dlg.selectedApp(); | ||||
|         return app.release(); | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| bool FileLauncher::openFolder(GAppLaunchContext* /*ctx*/, GList* folder_infos, GError** /*err*/) { | ||||
|     for(GList* l = folder_infos; l; l = l->next) { | ||||
|         FmFileInfo* fi = FM_FILE_INFO(l->data); | ||||
|         qDebug() << "  folder:" << QString::fromUtf8(fm_file_info_get_disp_name(fi)); | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| FmFileLauncherExecAction FileLauncher::execFile(FmFileInfo* file) { | ||||
|     if(quickExec_) { | ||||
|         /* SF bug#838: open terminal for each script may be just a waste.
 | ||||
|            User should open a terminal and start the script there | ||||
|            in case if user wants to see the script output anyway. | ||||
|         if (fm_file_info_is_text(file)) | ||||
|             return FM_FILE_LAUNCHER_EXEC_IN_TERMINAL; */ | ||||
|         return FM_FILE_LAUNCHER_EXEC; | ||||
|     } | ||||
| 
 | ||||
|     FmFileLauncherExecAction res = FM_FILE_LAUNCHER_EXEC_CANCEL; | ||||
|     ExecFileDialog dlg(file); | ||||
|     if(execModelessDialog(&dlg) == QDialog::Accepted) { | ||||
|         res = dlg.result(); | ||||
|     } | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| int FileLauncher::ask(const char* /*msg*/, char* const* /*btn_labels*/, int /*default_btn*/) { | ||||
|     /* FIXME: set default button properly */ | ||||
|     // return fm_askv(data->parent, nullptr, msg, btn_labels);
 | ||||
|     return -1; | ||||
| } | ||||
| 
 | ||||
| bool FileLauncher::error(GAppLaunchContext* /*ctx*/, GError* err, FmPath* path) { | ||||
| GAppInfoPtr FileLauncher::chooseApp(const FileInfoList& /*fileInfos*/, const char *mimeType, GErrorPtr& /*err*/) { | ||||
|     AppChooserDialog dlg(nullptr); | ||||
|     GAppInfoPtr app; | ||||
|     if(mimeType) { | ||||
|         dlg.setMimeType(Fm::MimeType::fromName(mimeType)); | ||||
|     } | ||||
|     else { | ||||
|         dlg.setCanSetDefault(false); | ||||
|     } | ||||
|     // FIXME: show error properly?
 | ||||
|     if(execModelessDialog(&dlg) == QDialog::Accepted) { | ||||
|         app = dlg.selectedApp(); | ||||
|     } | ||||
|     return app; | ||||
| } | ||||
| 
 | ||||
| bool FileLauncher::openFolder(GAppLaunchContext *ctx, const FileInfoList &folderInfos, GErrorPtr &err) { | ||||
|     return BasicFileLauncher::openFolder(ctx, folderInfos, err); | ||||
| } | ||||
| 
 | ||||
| bool FileLauncher::showError(GAppLaunchContext* /*ctx*/, GErrorPtr &err, const FilePath &path, const FileInfoPtr &info) { | ||||
|     /* ask for mount if trying to launch unmounted path */ | ||||
|     if(err->domain == G_IO_ERROR) { | ||||
|         if(path && err->code == G_IO_ERROR_NOT_MOUNTED) { | ||||
|             //if(fm_mount_path(data->parent, path, TRUE))
 | ||||
|             //  return FALSE; /* ask to retry */
 | ||||
|             MountOperation* op = new MountOperation(true); | ||||
|             op->setAutoDestroy(true); | ||||
|             if(info && info->isMountable()) { | ||||
|                 // this is a mountable shortcut (such as computer:///xxxx.drive)
 | ||||
|                 op->mountMountable(path); | ||||
|             } | ||||
|             else { | ||||
|                 op->mountEnclosingVolume(path); | ||||
|             } | ||||
|             if(op->wait()) { | ||||
|                 // if the mount operation succeeds, we can ignore the error and continue
 | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         else if(err->code == G_IO_ERROR_FAILED_HANDLED) { | ||||
|             return true;    /* don't show error message */ | ||||
| @ -138,7 +100,16 @@ bool FileLauncher::error(GAppLaunchContext* /*ctx*/, GError* err, FmPath* path) | ||||
|     } | ||||
|     QMessageBox dlg(QMessageBox::Critical, QObject::tr("Error"), QString::fromUtf8(err->message), QMessageBox::Ok); | ||||
|     execModelessDialog(&dlg); | ||||
|     return true; | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| BasicFileLauncher::ExecAction FileLauncher::askExecFile(const FileInfoPtr &file) { | ||||
|     auto res = BasicFileLauncher::ExecAction::CANCEL; | ||||
|     ExecFileDialog dlg(*file); | ||||
|     if(execModelessDialog(&dlg) == QDialog::Accepted) { | ||||
|         res = dlg.result(); | ||||
|     } | ||||
|     return res; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -23,60 +23,31 @@ | ||||
| 
 | ||||
| #include "libfmqtglobals.h" | ||||
| #include <QWidget> | ||||
| #include <libfm/fm.h> | ||||
| #include "core/fileinfo.h" | ||||
| #include "core/basicfilelauncher.h" | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| class LIBFM_QT_API FileLauncher { | ||||
| class LIBFM_QT_API FileLauncher: public BasicFileLauncher { | ||||
| public: | ||||
|     explicit FileLauncher(); | ||||
|     virtual ~FileLauncher(); | ||||
| 
 | ||||
|     bool launchFiles(QWidget* parent, Fm::FileInfoList file_infos); | ||||
|     bool launchFiles(QWidget* parent, const FileInfoList& file_infos); | ||||
| 
 | ||||
|     bool launchPaths(QWidget* parent, Fm::FilePathList paths); | ||||
| 
 | ||||
|     bool quickExec() const { | ||||
|         return quickExec_; | ||||
|     } | ||||
| 
 | ||||
|     void setQuickExec(bool value) { | ||||
|         quickExec_ = value; | ||||
|     } | ||||
|     bool launchPaths(QWidget* parent, const FilePathList &paths); | ||||
| 
 | ||||
| protected: | ||||
| 
 | ||||
|     virtual GAppInfo* getApp(GList* file_infos, FmMimeType* mime_type, GError** err); | ||||
|     virtual bool openFolder(GAppLaunchContext* ctx, GList* folder_infos, GError** err); | ||||
|     virtual FmFileLauncherExecAction execFile(FmFileInfo* file); | ||||
|     virtual bool error(GAppLaunchContext* ctx, GError* err, FmPath* path); | ||||
|     virtual int ask(const char* msg, char* const* btn_labels, int default_btn); | ||||
|     GAppInfoPtr chooseApp(const FileInfoList& fileInfos, const char* mimeType, GErrorPtr& err) override; | ||||
| 
 | ||||
| private: | ||||
|     bool launchFiles(QWidget* parent, GList* file_infos); | ||||
|     bool openFolder(GAppLaunchContext* ctx, const FileInfoList& folderInfos, GErrorPtr& err) override; | ||||
| 
 | ||||
|     bool launchPaths(QWidget* parent, GList* paths); | ||||
|     bool showError(GAppLaunchContext* ctx, GErrorPtr& err, const FilePath& path = FilePath{}, const FileInfoPtr& info = FileInfoPtr{}) override; | ||||
| 
 | ||||
|     static GAppInfo* _getApp(GList* file_infos, FmMimeType* mime_type, gpointer user_data, GError** err) { | ||||
|         return reinterpret_cast<FileLauncher*>(user_data)->getApp(file_infos, mime_type, err); | ||||
|     } | ||||
|     static gboolean _openFolder(GAppLaunchContext* ctx, GList* folder_infos, gpointer user_data, GError** err) { | ||||
|         return reinterpret_cast<FileLauncher*>(user_data)->openFolder(ctx, folder_infos, err); | ||||
|     } | ||||
|     static FmFileLauncherExecAction _execFile(FmFileInfo* file, gpointer user_data) { | ||||
|         return reinterpret_cast<FileLauncher*>(user_data)->execFile(file); | ||||
|     } | ||||
|     static gboolean _error(GAppLaunchContext* ctx, GError* err, FmPath* file, gpointer user_data) { | ||||
|         return reinterpret_cast<FileLauncher*>(user_data)->error(ctx, err, file); | ||||
|     } | ||||
|     static int _ask(const char* msg, char* const* btn_labels, int default_btn, gpointer user_data) { | ||||
|         return reinterpret_cast<FileLauncher*>(user_data)->ask(msg, btn_labels, default_btn); | ||||
|     } | ||||
|     ExecAction askExecFile(const FileInfoPtr& file) override; | ||||
| 
 | ||||
| private: | ||||
|     static FmFileLauncher funcs; | ||||
|     bool quickExec_; // Don't ask options on launch executable file
 | ||||
|     int ask(const char* msg, char* const* btn_labels, int default_btn) override; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -20,7 +20,6 @@ | ||||
| 
 | ||||
| #include "filemenu.h" | ||||
| #include "createnewmenu.h" | ||||
| #include "icontheme.h" | ||||
| #include "filepropsdialog.h" | ||||
| #include "utilities.h" | ||||
| #include "fileoperation.h" | ||||
| @ -35,7 +34,7 @@ | ||||
| #include <QDebug> | ||||
| #include "filemenu_p.h" | ||||
| 
 | ||||
| #include "core/compat_p.h" | ||||
| #include "core/archiver.h" | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| @ -207,18 +206,17 @@ FileMenu::FileMenu(Fm::FileInfoList files, std::shared_ptr<const Fm::FileInfo> i | ||||
|     // FIXME: we need to modify upstream libfm to include some Qt-based archiver programs.
 | ||||
|     if(!allVirtual_) { | ||||
|         if(sameType_) { | ||||
|             // FIXME: port these parts to Fm API
 | ||||
|             FmArchiver* archiver = fm_archiver_get_default(); | ||||
|             auto archiver = Archiver::defaultArchiver(); | ||||
|             if(archiver) { | ||||
|                 if(fm_archiver_is_mime_type_supported(archiver, mime_type->name())) { | ||||
|                 if(archiver->isMimeTypeSupported(mime_type->name())) { | ||||
|                     QAction* archiverSeparator = nullptr; | ||||
|                     if(cwd_ && archiver->extract_to_cmd) { | ||||
|                     if(cwd_ && archiver->canExtractArchivesTo()) { | ||||
|                         archiverSeparator = addSeparator(); | ||||
|                         QAction* action = new QAction(tr("Extract to..."), this); | ||||
|                         connect(action, &QAction::triggered, this, &FileMenu::onExtract); | ||||
|                         addAction(action); | ||||
|                     } | ||||
|                     if(archiver->extract_cmd) { | ||||
|                     if(archiver->canExtractArchives()) { | ||||
|                         if(!archiverSeparator) { | ||||
|                             addSeparator(); | ||||
|                         } | ||||
| @ -227,7 +225,7 @@ FileMenu::FileMenu(Fm::FileInfoList files, std::shared_ptr<const Fm::FileInfo> i | ||||
|                         addAction(action); | ||||
|                     } | ||||
|                 } | ||||
|                 else { | ||||
|                 else if(archiver->canCreateArchive()){ | ||||
|                     addSeparator(); | ||||
|                     QAction* action = new QAction(tr("Compress"), this); | ||||
|                     connect(action, &QAction::triggered, this, &FileMenu::onCompress); | ||||
| @ -376,7 +374,9 @@ void FileMenu::onRenameTriggered() { | ||||
|         } | ||||
|     } | ||||
|     for(auto& info: files_) { | ||||
|         Fm::renameFile(info, nullptr); | ||||
|         if(!Fm::renameFile(info, nullptr)) { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -391,27 +391,23 @@ void FileMenu::setUseTrash(bool trash) { | ||||
| } | ||||
| 
 | ||||
| void FileMenu::onCompress() { | ||||
|     FmArchiver* archiver = fm_archiver_get_default(); | ||||
|     auto archiver = Archiver::defaultArchiver(); | ||||
|     if(archiver) { | ||||
|         auto paths = Fm::_convertPathList(files_.paths()); | ||||
|         fm_archiver_create_archive(archiver, nullptr, paths.dataPtr()); | ||||
|         archiver->createArchive(nullptr, files_.paths()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FileMenu::onExtract() { | ||||
|     FmArchiver* archiver = fm_archiver_get_default(); | ||||
|     auto archiver = Archiver::defaultArchiver(); | ||||
|     if(archiver) { | ||||
|         auto paths = Fm::_convertPathList(files_.paths()); | ||||
|         fm_archiver_extract_archives(archiver, nullptr, paths.dataPtr()); | ||||
|         archiver->extractArchives(nullptr, files_.paths()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FileMenu::onExtractHere() { | ||||
|     FmArchiver* archiver = fm_archiver_get_default(); | ||||
|     auto archiver = Archiver::defaultArchiver(); | ||||
|     if(archiver) { | ||||
|         auto paths = Fm::_convertPathList(files_.paths()); | ||||
|         auto cwd = Fm::_convertPath(cwd_); | ||||
|         fm_archiver_extract_archives_to(archiver, nullptr, paths.dataPtr(), cwd); | ||||
|         archiver->extractArchivesTo(nullptr, files_.paths(), cwd_); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -20,7 +20,6 @@ | ||||
| #ifndef FM_FILEMENU_P_H | ||||
| #define FM_FILEMENU_P_H | ||||
| 
 | ||||
| #include "icontheme.h" | ||||
| #include <QDebug> | ||||
| #include "core/gioptrs.h" | ||||
| #include "core/iconinfo.h" | ||||
|  | ||||
| @ -24,87 +24,207 @@ | ||||
| #include <QElapsedTimer> | ||||
| #include <QMessageBox> | ||||
| #include <QDebug> | ||||
| #include "path.h" | ||||
| 
 | ||||
| #include "core/compat_p.h" | ||||
| #include "core/deletejob.h" | ||||
| #include "core/trashjob.h" | ||||
| #include "core/untrashjob.h" | ||||
| #include "core/filetransferjob.h" | ||||
| #include "core/filechangeattrjob.h" | ||||
| #include "utilities.h" | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| #define SHOW_DLG_DELAY  1000 | ||||
| 
 | ||||
| FileOperation::FileOperation(Type type, Fm::FilePathList srcFiles, QObject* parent): | ||||
| FileOperation::FileOperation(Type type, Fm::FilePathList srcPaths, QObject* parent): | ||||
|     QObject(parent), | ||||
|     job_{fm_file_ops_job_new((FmFileOpType)type, Fm::_convertPathList(srcFiles))}, | ||||
|     dlg{nullptr}, | ||||
|     srcPaths{std::move(srcFiles)}, | ||||
|     uiTimer(nullptr), | ||||
|     type_{type}, | ||||
|     job_{nullptr}, | ||||
|     dlg_{nullptr}, | ||||
|     srcPaths_{std::move(srcPaths)}, | ||||
|     uiTimer_(nullptr), | ||||
|     elapsedTimer_(nullptr), | ||||
|     lastElapsed_(0), | ||||
|     updateRemainingTime_(true), | ||||
|     autoDestroy_(true) { | ||||
| 
 | ||||
|     g_signal_connect(job_, "ask", G_CALLBACK(onFileOpsJobAsk), this); | ||||
|     g_signal_connect(job_, "ask-rename", G_CALLBACK(onFileOpsJobAskRename), this); | ||||
|     g_signal_connect(job_, "error", G_CALLBACK(onFileOpsJobError), this); | ||||
|     g_signal_connect(job_, "prepared", G_CALLBACK(onFileOpsJobPrepared), this); | ||||
|     g_signal_connect(job_, "cur-file", G_CALLBACK(onFileOpsJobCurFile), this); | ||||
|     g_signal_connect(job_, "percent", G_CALLBACK(onFileOpsJobPercent), this); | ||||
|     g_signal_connect(job_, "finished", G_CALLBACK(onFileOpsJobFinished), this); | ||||
|     g_signal_connect(job_, "cancelled", G_CALLBACK(onFileOpsJobCancelled), this); | ||||
|     switch(type_) { | ||||
|     case Copy: | ||||
|         job_ = new FileTransferJob(srcPaths_, FileTransferJob::Mode::COPY); | ||||
|         break; | ||||
|     case Move: | ||||
|         job_ = new FileTransferJob(srcPaths_, FileTransferJob::Mode::MOVE); | ||||
|         break; | ||||
|     case Link: | ||||
|         job_ = new FileTransferJob(srcPaths_, FileTransferJob::Mode::LINK); | ||||
|         break; | ||||
|     case Delete: | ||||
|         job_ = new Fm::DeleteJob(srcPaths_); | ||||
|         break; | ||||
|     case Trash: | ||||
|         job_ = new Fm::TrashJob(srcPaths_); | ||||
|         break; | ||||
|     case UnTrash: | ||||
|         job_ = new Fm::UntrashJob(srcPaths_); | ||||
|         break; | ||||
|     case ChangeAttr: | ||||
|         job_ = new Fm::FileChangeAttrJob(srcPaths_); | ||||
|         break; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     if(job_) { | ||||
|         // automatically delete the job object when it's finished.
 | ||||
|         job_->setAutoDelete(true); | ||||
| 
 | ||||
|         // new C++ jobs
 | ||||
|         connect(job_, &Fm::Job::finished, this, &Fm::FileOperation::onJobFinish); | ||||
|         connect(job_, &Fm::Job::cancelled, this, &Fm::FileOperation::onJobCancalled); | ||||
|         connect(job_, &Fm::Job::error, this, &Fm::FileOperation::onJobError, Qt::BlockingQueuedConnection); | ||||
|         connect(job_, &Fm::FileOperationJob::fileExists, this, &Fm::FileOperation::onJobFileExists, Qt::BlockingQueuedConnection); | ||||
| 
 | ||||
|         // we block the job deliberately until we prepare to start (initiailize the timer) so we can calculate elapsed time correctly.
 | ||||
|         connect(job_, &Fm::FileOperationJob::preparedToRun, this, &Fm::FileOperation::onJobPrepared, Qt::BlockingQueuedConnection); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FileOperation::disconnectJob() { | ||||
|     g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobAsk), this); | ||||
|     g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobAskRename), this); | ||||
|     g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobError), this); | ||||
|     g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobPrepared), this); | ||||
|     g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobCurFile), this); | ||||
|     g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobPercent), this); | ||||
|     g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobFinished), this); | ||||
|     g_signal_handlers_disconnect_by_func(job_, (gpointer)G_CALLBACK(onFileOpsJobCancelled), this); | ||||
|     if(job_) { | ||||
|         disconnect(job_, &Fm::Job::finished, this, &Fm::FileOperation::onJobFinish); | ||||
|         disconnect(job_, &Fm::Job::cancelled, this, &Fm::FileOperation::onJobCancalled); | ||||
|         disconnect(job_, &Fm::Job::error, this, &Fm::FileOperation::onJobError); | ||||
|         disconnect(job_, &Fm::FileOperationJob::fileExists, this, &Fm::FileOperation::onJobFileExists); | ||||
|         disconnect(job_, &Fm::FileOperationJob::preparedToRun, this, &Fm::FileOperation::onJobPrepared); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| FileOperation::~FileOperation() { | ||||
|     if(uiTimer) { | ||||
|         uiTimer->stop(); | ||||
|         delete uiTimer; | ||||
|         uiTimer = nullptr; | ||||
|     if(uiTimer_) { | ||||
|         uiTimer_->stop(); | ||||
|         delete uiTimer_; | ||||
|         uiTimer_ = nullptr; | ||||
|     } | ||||
|     if(elapsedTimer_) { | ||||
|         delete elapsedTimer_; | ||||
|         elapsedTimer_ = nullptr; | ||||
|     } | ||||
| 
 | ||||
|     if(job_) { | ||||
|         disconnectJob(); | ||||
|         g_object_unref(job_); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FileOperation::setDestination(Fm::FilePath dest) { | ||||
|     destPath = std::move(dest); | ||||
|     auto tmp = Fm::Path::newForGfile(dest.gfile().get()); | ||||
|     fm_file_ops_job_set_dest(job_, tmp.dataPtr()); | ||||
|     destPath_ = std::move(dest); | ||||
|     switch(type_) { | ||||
|     case Copy: | ||||
|     case Move: | ||||
|     case Link: | ||||
|         if(job_) { | ||||
|             static_cast<FileTransferJob*>(job_)->setDestDirPath(destPath_); | ||||
|         } | ||||
|         break; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FileOperation::setDestFiles(FilePathList destFiles) { | ||||
|     switch(type_) { | ||||
|     case Copy: | ||||
|     case Move: | ||||
|     case Link: | ||||
|         if(job_) { | ||||
|             static_cast<FileTransferJob*>(job_)->setDestPaths(std::move(destFiles)); | ||||
|         } | ||||
|         break; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FileOperation::setChmod(mode_t newMode, mode_t newModeMask) { | ||||
|     if(job_) { | ||||
|         auto job = static_cast<FileChangeAttrJob*>(job_); | ||||
|         job->setFileModeEnabled(true); | ||||
|         job->setFileMode(newMode, newModeMask); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FileOperation::setChown(uid_t uid, gid_t gid) { | ||||
|     if(job_) { | ||||
|         auto job = static_cast<FileChangeAttrJob*>(job_); | ||||
|         if(uid != INVALID_UID) { | ||||
|             job->setOwnerEnabled(true); | ||||
|             job->setOwner(uid); | ||||
|         } | ||||
|         if(gid != INVALID_GID) { | ||||
|             job->setGroupEnabled(true); | ||||
|             job->setGroup(gid); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FileOperation::setRecursiveChattr(bool recursive) { | ||||
|     if(job_) { | ||||
|         auto job = static_cast<FileChangeAttrJob*>(job_); | ||||
|         job->setRecursive(recursive); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool FileOperation::run() { | ||||
|     delete uiTimer; | ||||
|     delete uiTimer_; | ||||
|     // run the job
 | ||||
|     uiTimer = new QTimer(); | ||||
|     uiTimer->start(SHOW_DLG_DELAY); | ||||
|     connect(uiTimer, &QTimer::timeout, this, &FileOperation::onUiTimeout); | ||||
|     uiTimer_ = new QTimer(); | ||||
|     uiTimer_->start(SHOW_DLG_DELAY); | ||||
|     connect(uiTimer_, &QTimer::timeout, this, &FileOperation::onUiTimeout); | ||||
| 
 | ||||
|     return fm_job_run_async(FM_JOB(job_)); | ||||
|     if(job_) { | ||||
|         job_->runAsync(); | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void FileOperation::cancel() { | ||||
|     if(job_) { | ||||
|         job_->cancel(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FileOperation::onUiTimeout() { | ||||
|     if(dlg) { | ||||
|         dlg->setCurFile(curFile); | ||||
|     if(dlg_) { | ||||
|         // estimate remaining time based on past history
 | ||||
|         // FIXME: avoid directly access data member of FmFileOpsJob
 | ||||
|         if(Q_LIKELY(job_->percent > 0 && updateRemainingTime_)) { | ||||
|             gint64 remaining = elapsedTime() * ((double(100 - job_->percent) / job_->percent) / 1000); | ||||
|             dlg->setRemainingTime(remaining); | ||||
|         if(job_) { | ||||
|             Fm::FilePath curFilePath = job_->currentFile(); | ||||
|             // update progress bar
 | ||||
|             double finishedRatio = job_->progress(); | ||||
|             if(finishedRatio > 0.0 && updateRemainingTime_) { | ||||
|                 dlg_->setPercent(int(finishedRatio * 100)); | ||||
| 
 | ||||
|                 std::uint64_t totalSize, totalCount, finishedSize, finishedCount; | ||||
|                 job_->totalAmount(totalSize, totalCount); | ||||
|                 job_->finishedAmount(finishedSize, finishedCount); | ||||
| 
 | ||||
|                 // only show data transferred if the job progress can be calculated by file size.
 | ||||
|                 // for jobs not related to data transfer (for example: change attr, delete,...), hide the UI
 | ||||
|                 if(job_->calcProgressUsingSize()) { | ||||
|                     dlg_->setDataTransferred(finishedSize, totalSize); | ||||
|                 } | ||||
|                 else { | ||||
|                     dlg_->setFilesProcessed(finishedCount, totalCount); | ||||
|                 } | ||||
| 
 | ||||
|                 double remainRatio = 1.0 - finishedRatio; | ||||
|                 gint64 remaining = elapsedTime() * (remainRatio / finishedRatio) / 1000; | ||||
|                 // qDebug("etime: %llu, finished: %lf, remain:%lf, remaining secs: %llu",
 | ||||
|                 //        elapsedTime(), finishedRatio, remainRatio, remaining);
 | ||||
|                 dlg_->setRemainingTime(remaining); | ||||
|             } | ||||
|             // update currently processed file
 | ||||
|             if(curFilePath_ != curFilePath) { | ||||
|                 curFilePath_ = std::move(curFilePath); | ||||
|                 // FIXME: make this cleaner
 | ||||
|                 curFile = QString::fromUtf8(curFilePath_.toString().get()); | ||||
|                 dlg_->setCurFile(curFile); | ||||
|             } | ||||
|         } | ||||
|         // this timeout slot is called every 0.5 second.
 | ||||
|         // by adding this flag, we can update remaining time every 1 second.
 | ||||
| @ -116,125 +236,84 @@ void FileOperation::onUiTimeout() { | ||||
| } | ||||
| 
 | ||||
| void FileOperation::showDialog() { | ||||
|     if(!dlg) { | ||||
|         dlg = new FileOperationDialog(this); | ||||
|         dlg->setSourceFiles(srcPaths); | ||||
|     if(!dlg_) { | ||||
|         dlg_ = new FileOperationDialog(this); | ||||
|         dlg_->setSourceFiles(srcPaths_); | ||||
| 
 | ||||
|         if(destPath) { | ||||
|             dlg->setDestPath(destPath); | ||||
|         if(destPath_) { | ||||
|             dlg_->setDestPath(destPath_); | ||||
|         } | ||||
| 
 | ||||
|         if(curFile.isEmpty()) { | ||||
|             dlg->setPrepared(); | ||||
|             dlg->setCurFile(curFile); | ||||
|             dlg_->setPrepared(); | ||||
|             dlg_->setCurFile(curFile); | ||||
|         } | ||||
|         uiTimer->setInterval(500); // change the interval of the timer
 | ||||
|         uiTimer_->setInterval(500); // change the interval of the timer
 | ||||
|         // now the timer is used to update current file display
 | ||||
|         dlg->show(); | ||||
|         dlg_->show(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| gint FileOperation::onFileOpsJobAsk(FmFileOpsJob* /*job*/, const char* question, char* const* options, FileOperation* pThis) { | ||||
|     pThis->pauseElapsedTimer(); | ||||
|     pThis->showDialog(); | ||||
|     int ret = pThis->dlg->ask(QString::fromUtf8(question), options); | ||||
|     pThis->resumeElapsedTimer(); | ||||
|     return ret; | ||||
| void FileOperation::onJobFileExists(const FileInfo& src, const FileInfo& dest, Fm::FileOperationJob::FileExistsAction& response, FilePath& newDest) { | ||||
|     pauseElapsedTimer(); | ||||
|     showDialog(); | ||||
|     response = dlg_->askRename(src, dest, newDest); | ||||
|     resumeElapsedTimer(); | ||||
| } | ||||
| 
 | ||||
| gint FileOperation::onFileOpsJobAskRename(FmFileOpsJob* /*job*/, FmFileInfo* src, FmFileInfo* dest, char** new_name, FileOperation* pThis) { | ||||
|     pThis->pauseElapsedTimer(); | ||||
|     pThis->showDialog(); | ||||
|     QString newName; | ||||
|     int ret = pThis->dlg->askRename(src, dest, newName); | ||||
|     if(!newName.isEmpty()) { | ||||
|         *new_name = g_strdup(newName.toUtf8().constData()); | ||||
|     } | ||||
|     pThis->resumeElapsedTimer(); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| void FileOperation::onFileOpsJobCancelled(FmFileOpsJob* /*job*/, FileOperation* /*pThis*/) { | ||||
| void FileOperation::onJobCancalled() { | ||||
|     qDebug("file operation is cancelled!"); | ||||
| } | ||||
| 
 | ||||
| void FileOperation::onFileOpsJobCurFile(FmFileOpsJob* /*job*/, const char* cur_file, FileOperation* pThis) { | ||||
|     pThis->curFile = QString::fromUtf8(cur_file); | ||||
| 
 | ||||
|     // We update the current file name in a timeout slot because drawing a string
 | ||||
|     // in the UI is expansive. Updating the label text too often cause
 | ||||
|     // significant impact on performance.
 | ||||
|     // if(pThis->dlg)
 | ||||
|     //  pThis->dlg->setCurFile(pThis->curFile);
 | ||||
| void FileOperation::onJobError(const GErrorPtr& err, Fm::Job::ErrorSeverity severity, Fm::Job::ErrorAction& response) { | ||||
|     pauseElapsedTimer(); | ||||
|     showDialog(); | ||||
|     response = Fm::Job::ErrorAction(dlg_->error(err.get(), severity)); | ||||
|     resumeElapsedTimer(); | ||||
| } | ||||
| 
 | ||||
| FmJobErrorAction FileOperation::onFileOpsJobError(FmFileOpsJob* /*job*/, GError* err, FmJobErrorSeverity severity, FileOperation* pThis) { | ||||
|     pThis->pauseElapsedTimer(); | ||||
|     pThis->showDialog(); | ||||
|     FmJobErrorAction act = pThis->dlg->error(err, severity); | ||||
|     pThis->resumeElapsedTimer(); | ||||
|     return act; | ||||
| } | ||||
| 
 | ||||
| void FileOperation::onFileOpsJobFinished(FmFileOpsJob* /*job*/, FileOperation* pThis) { | ||||
|     pThis->handleFinish(); | ||||
| } | ||||
| 
 | ||||
| void FileOperation::onFileOpsJobPercent(FmFileOpsJob* job, guint percent, FileOperation* pThis) { | ||||
|     if(pThis->dlg) { | ||||
|         pThis->dlg->setPercent(percent); | ||||
|         pThis->dlg->setDataTransferred(job->finished, job->total); | ||||
| void FileOperation::onJobPrepared() { | ||||
|     if(!elapsedTimer_) { | ||||
|         elapsedTimer_ = new QElapsedTimer(); | ||||
|         elapsedTimer_->start(); | ||||
|     } | ||||
|     if(dlg_) { | ||||
|         dlg_->setPrepared(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FileOperation::onFileOpsJobPrepared(FmFileOpsJob* /*job*/, FileOperation* pThis) { | ||||
|     if(!pThis->elapsedTimer_) { | ||||
|         pThis->elapsedTimer_ = new QElapsedTimer(); | ||||
|         pThis->elapsedTimer_->start(); | ||||
|     } | ||||
|     if(pThis->dlg) { | ||||
|         pThis->dlg->setPrepared(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FileOperation::handleFinish() { | ||||
| void FileOperation::onJobFinish() { | ||||
|     disconnectJob(); | ||||
| 
 | ||||
|     if(uiTimer) { | ||||
|         uiTimer->stop(); | ||||
|         delete uiTimer; | ||||
|         uiTimer = nullptr; | ||||
|     if(uiTimer_) { | ||||
|         uiTimer_->stop(); | ||||
|         delete uiTimer_; | ||||
|         uiTimer_ = nullptr; | ||||
|     } | ||||
| 
 | ||||
|     if(dlg) { | ||||
|         dlg->done(QDialog::Accepted); | ||||
|         delete dlg; | ||||
|         dlg = nullptr; | ||||
|     if(dlg_) { | ||||
|         dlg_->done(QDialog::Accepted); | ||||
|         delete dlg_; | ||||
|         dlg_ = nullptr; | ||||
|     } | ||||
|     Q_EMIT finished(); | ||||
| 
 | ||||
|     /* sepcial handling for trash
 | ||||
|      * FIXME: need to refactor this to use a more elegant way later. */ | ||||
|     if(job_->type == FM_FILE_OP_TRASH) { /* FIXME: direct access to job struct! */ | ||||
|         auto unable_to_trash = static_cast<FmPathList*>(g_object_get_data(G_OBJECT(job_), "trash-unsupported")); | ||||
|     // special handling for trash job
 | ||||
|     if(type_ == Trash && !job_->isCancelled()) { | ||||
|         auto trashJob = static_cast<Fm::TrashJob*>(job_); | ||||
|         /* some files cannot be trashed because underlying filesystems don't support it. */ | ||||
|         if(unable_to_trash) { /* delete them instead */ | ||||
|             Fm::FilePathList filesToDel; | ||||
|             for(GList* l = fm_path_list_peek_head_link(unable_to_trash); l; l = l->next) { | ||||
|                 filesToDel.push_back(Fm::FilePath{fm_path_to_gfile(FM_PATH(l->data)), false}); | ||||
|             } | ||||
|         auto unsupportedFiles = trashJob->unsupportedFiles(); | ||||
|         if(!unsupportedFiles.empty()) { /* delete them instead */ | ||||
|             /* FIXME: parent window might be already destroyed! */ | ||||
|             QWidget* parent = nullptr; // FIXME: currently, parent window is not set
 | ||||
|             if(QMessageBox::question(parent, tr("Error"), | ||||
|                                      tr("Some files cannot be moved to trash can because " | ||||
|                                         "the underlying file systems don't support this operation.\n" | ||||
|                                         "Do you want to delete them instead?")) == QMessageBox::Yes) { | ||||
|                 deleteFiles(std::move(filesToDel), false); | ||||
|                 deleteFiles(std::move(unsupportedFiles), false); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     g_object_unref(job_); | ||||
|     job_ = nullptr; | ||||
| 
 | ||||
|     if(autoDestroy_) { | ||||
|         delete this; | ||||
| @ -249,6 +328,15 @@ FileOperation* FileOperation::copyFiles(Fm::FilePathList srcFiles, Fm::FilePath | ||||
|     return op; | ||||
| } | ||||
| 
 | ||||
| //static
 | ||||
| FileOperation *FileOperation::copyFiles(FilePathList srcFiles, FilePathList destFiles, QWidget *parent) { | ||||
|     qDebug("copy: %s -> %s", srcFiles[0].toString().get(), destFiles[0].toString().get()); | ||||
|     FileOperation* op = new FileOperation(FileOperation::Copy, std::move(srcFiles), parent); | ||||
|     op->setDestFiles(std::move(destFiles)); | ||||
|     op->run(); | ||||
|     return op; | ||||
| } | ||||
| 
 | ||||
| // static
 | ||||
| FileOperation* FileOperation::moveFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent) { | ||||
|     FileOperation* op = new FileOperation(FileOperation::Move, std::move(srcFiles), parent); | ||||
| @ -257,6 +345,14 @@ FileOperation* FileOperation::moveFiles(Fm::FilePathList srcFiles, Fm::FilePath | ||||
|     return op; | ||||
| } | ||||
| 
 | ||||
| //static
 | ||||
| FileOperation *FileOperation::moveFiles(FilePathList srcFiles, FilePathList destFiles, QWidget *parent) { | ||||
|     FileOperation* op = new FileOperation(FileOperation::Move, std::move(srcFiles), parent); | ||||
|     op->setDestFiles(std::move(destFiles)); | ||||
|     op->run(); | ||||
|     return op; | ||||
| } | ||||
| 
 | ||||
| //static
 | ||||
| FileOperation* FileOperation::symlinkFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent) { | ||||
|     FileOperation* op = new FileOperation(FileOperation::Link, std::move(srcFiles), parent); | ||||
| @ -265,6 +361,14 @@ FileOperation* FileOperation::symlinkFiles(Fm::FilePathList srcFiles, Fm::FilePa | ||||
|     return op; | ||||
| } | ||||
| 
 | ||||
| //static
 | ||||
| FileOperation *FileOperation::symlinkFiles(FilePathList srcFiles, FilePathList destFiles, QWidget *parent) { | ||||
|     FileOperation* op = new FileOperation(FileOperation::Link, std::move(srcFiles), parent); | ||||
|     op->setDestFiles(std::move(destFiles)); | ||||
|     op->run(); | ||||
|     return op; | ||||
| } | ||||
| 
 | ||||
| //static
 | ||||
| FileOperation* FileOperation::deleteFiles(Fm::FilePathList srcFiles, bool prompt, QWidget* parent) { | ||||
|     if(prompt) { | ||||
|  | ||||
| @ -24,8 +24,8 @@ | ||||
| #include "libfmqtglobals.h" | ||||
| #include <QObject> | ||||
| #include <QElapsedTimer> | ||||
| #include <libfm/fm.h> | ||||
| #include "core/filepath.h" | ||||
| #include "core/fileoperationjob.h" | ||||
| 
 | ||||
| class QTimer; | ||||
| 
 | ||||
| @ -37,51 +37,47 @@ class LIBFM_QT_API FileOperation : public QObject { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     enum Type { | ||||
|         Copy = FM_FILE_OP_COPY, | ||||
|         Move = FM_FILE_OP_MOVE, | ||||
|         Link = FM_FILE_OP_LINK, | ||||
|         Delete = FM_FILE_OP_DELETE, | ||||
|         Trash = FM_FILE_OP_TRASH, | ||||
|         UnTrash = FM_FILE_OP_UNTRASH, | ||||
|         ChangeAttr = FM_FILE_OP_CHANGE_ATTR | ||||
|         Copy, | ||||
|         Move, | ||||
|         Link, | ||||
|         Delete, | ||||
|         Trash, | ||||
|         UnTrash, | ||||
|         ChangeAttr | ||||
|     }; | ||||
| 
 | ||||
| public: | ||||
|     explicit FileOperation(Type type, Fm::FilePathList srcFiles, QObject* parent = 0); | ||||
| 
 | ||||
|     virtual ~FileOperation(); | ||||
| 
 | ||||
|     void setDestination(Fm::FilePath dest); | ||||
| 
 | ||||
|     void setChmod(mode_t newMode, mode_t newModeMask) { | ||||
|         fm_file_ops_job_set_chmod(job_, newMode, newModeMask); | ||||
|     } | ||||
|     void setDestFiles(FilePathList destFiles); | ||||
| 
 | ||||
|     void setChown(gint uid, gint gid) { | ||||
|         fm_file_ops_job_set_chown(job_, uid, gid); | ||||
|     } | ||||
|     void setChmod(mode_t newMode, mode_t newModeMask); | ||||
| 
 | ||||
|     void setChown(uid_t uid, gid_t gid); | ||||
| 
 | ||||
|     // This only work for change attr jobs.
 | ||||
|     void setRecursiveChattr(bool recursive) { | ||||
|         fm_file_ops_job_set_recursive(job_, (gboolean)recursive); | ||||
|     } | ||||
|     void setRecursiveChattr(bool recursive); | ||||
| 
 | ||||
|     bool run(); | ||||
| 
 | ||||
|     void cancel() { | ||||
|         if(job_) { | ||||
|             fm_job_cancel(FM_JOB(job_)); | ||||
|         } | ||||
|     } | ||||
|     void cancel(); | ||||
| 
 | ||||
|     bool isRunning() const { | ||||
|         return job_ ? fm_job_is_running(FM_JOB(job_)) : false; | ||||
|         return job_ && !isCancelled(); | ||||
|     } | ||||
| 
 | ||||
|     bool isCancelled() const { | ||||
|         return job_ ? fm_job_is_cancelled(FM_JOB(job_)) : false; | ||||
|         if(job_) { | ||||
|             return job_->isCancelled(); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     FmFileOpsJob* job() { | ||||
|     Fm::FileOperationJob* job() { | ||||
|         return job_; | ||||
|     } | ||||
| 
 | ||||
| @ -93,32 +89,54 @@ public: | ||||
|     } | ||||
| 
 | ||||
|     Type type() { | ||||
|         return (Type)job_->type; | ||||
|         return type_; | ||||
|     } | ||||
| 
 | ||||
|     // convinient static functions
 | ||||
|     static FileOperation* copyFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent = 0); | ||||
| 
 | ||||
|     static FileOperation* copyFiles(Fm::FilePathList srcFiles, Fm::FilePathList destFiles, QWidget* parent = 0); | ||||
| 
 | ||||
|     static FileOperation* copyFile(Fm::FilePath srcFile, Fm::FilePath destFile, QWidget* parent = 0) { | ||||
|         return copyFiles(FilePathList{std::move(srcFile)}, FilePathList{std::move(destFile)}, parent); | ||||
|     } | ||||
| 
 | ||||
|     static FileOperation* moveFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent = 0); | ||||
| 
 | ||||
|     static FileOperation* moveFiles(Fm::FilePathList srcFiles, Fm::FilePathList destFiles, QWidget* parent = 0); | ||||
| 
 | ||||
|     static FileOperation* moveFile(Fm::FilePath srcFile, Fm::FilePath destFile, QWidget* parent = 0) { | ||||
|         return moveFiles(FilePathList{std::move(srcFile)}, FilePathList{std::move(destFile)}, parent); | ||||
|     } | ||||
| 
 | ||||
|     static FileOperation* symlinkFiles(Fm::FilePathList srcFiles, Fm::FilePath dest, QWidget* parent = 0); | ||||
| 
 | ||||
|     static FileOperation* symlinkFiles(Fm::FilePathList srcFiles, Fm::FilePathList destFiles, QWidget* parent = 0); | ||||
| 
 | ||||
|     static FileOperation* symlinkFile(Fm::FilePath srcFile, Fm::FilePath destFile, QWidget* parent = 0) { | ||||
|         return symlinkFiles(FilePathList{std::move(srcFile)}, FilePathList{std::move(destFile)}, parent); | ||||
|     } | ||||
| 
 | ||||
|     static FileOperation* deleteFiles(Fm::FilePathList srcFiles, bool promp = true, QWidget* parent = 0); | ||||
| 
 | ||||
|     static FileOperation* trashFiles(Fm::FilePathList srcFiles, bool promp = true, QWidget* parent = 0); | ||||
| 
 | ||||
|     static FileOperation* unTrashFiles(Fm::FilePathList srcFiles, QWidget* parent = 0); | ||||
| 
 | ||||
|     static FileOperation* changeAttrFiles(Fm::FilePathList srcFiles, QWidget* parent = 0); | ||||
| 
 | ||||
| Q_SIGNALS: | ||||
|     void finished(); | ||||
| 
 | ||||
| private: | ||||
|     static gint onFileOpsJobAsk(FmFileOpsJob* job, const char* question, char* const* options, FileOperation* pThis); | ||||
|     static gint onFileOpsJobAskRename(FmFileOpsJob* job, FmFileInfo* src, FmFileInfo* dest, char** new_name, FileOperation* pThis); | ||||
|     static FmJobErrorAction onFileOpsJobError(FmFileOpsJob* job, GError* err, FmJobErrorSeverity severity, FileOperation* pThis); | ||||
|     static void onFileOpsJobPrepared(FmFileOpsJob* job, FileOperation* pThis); | ||||
|     static void onFileOpsJobCurFile(FmFileOpsJob* job, const char* cur_file, FileOperation* pThis); | ||||
|     static void onFileOpsJobPercent(FmFileOpsJob* job, guint percent, FileOperation* pThis); | ||||
|     static void onFileOpsJobFinished(FmFileOpsJob* job, FileOperation* pThis); | ||||
|     static void onFileOpsJobCancelled(FmFileOpsJob* job, FileOperation* pThis); | ||||
| private Q_SLOTS: | ||||
|     void onJobPrepared(); | ||||
|     void onJobFinish(); | ||||
|     void onJobCancalled(); | ||||
|     void onJobError(const GErrorPtr& err, Fm::Job::ErrorSeverity severity, Fm::Job::ErrorAction& response); | ||||
|     void onJobFileExists(const FileInfo& src, const FileInfo& dest, Fm::FileOperationJob::FileExistsAction& response, FilePath& newDest); | ||||
| 
 | ||||
| private: | ||||
| 
 | ||||
|     void handleFinish(); | ||||
|     void disconnectJob(); | ||||
|     void showDialog(); | ||||
| 
 | ||||
| @ -146,11 +164,13 @@ private Q_SLOTS: | ||||
|     void onUiTimeout(); | ||||
| 
 | ||||
| private: | ||||
|     FmFileOpsJob* job_; | ||||
|     FileOperationDialog* dlg; | ||||
|     Fm::FilePath destPath; | ||||
|     Fm::FilePathList srcPaths; | ||||
|     QTimer* uiTimer; | ||||
|     Type type_; | ||||
|     FileOperationJob* job_; | ||||
|     FileOperationDialog* dlg_; | ||||
|     FilePath destPath_; | ||||
|     FilePath curFilePath_; | ||||
|     FilePathList srcPaths_; | ||||
|     QTimer* uiTimer_; | ||||
|     QElapsedTimer* elapsedTimer_; | ||||
|     qint64 lastElapsed_; | ||||
|     bool updateRemainingTime_; | ||||
|  | ||||
| @ -27,6 +27,7 @@ | ||||
| #include "utilities.h" | ||||
| #include "ui_file-operation-dialog.h" | ||||
| 
 | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| FileOperationDialog::FileOperationDialog(FileOperation* _operation): | ||||
| @ -41,35 +42,35 @@ FileOperationDialog::FileOperationDialog(FileOperation* _operation): | ||||
|     QString title; | ||||
|     QString message; | ||||
|     switch(_operation->type()) { | ||||
|     case FM_FILE_OP_MOVE: | ||||
|     case FileOperation::Move: | ||||
|         title = tr("Move files"); | ||||
|         message = tr("Moving the following files to destination folder:"); | ||||
|         break; | ||||
|     case FM_FILE_OP_COPY: | ||||
|     case FileOperation::Copy: | ||||
|         title = tr("Copy Files"); | ||||
|         message = tr("Copying the following files to destination folder:"); | ||||
|         break; | ||||
|     case FM_FILE_OP_TRASH: | ||||
|     case FileOperation::Trash: | ||||
|         title = tr("Trash Files"); | ||||
|         message = tr("Moving the following files to trash can:"); | ||||
|         break; | ||||
|     case FM_FILE_OP_DELETE: | ||||
|     case FileOperation::Delete: | ||||
|         title = tr("Delete Files"); | ||||
|         message = tr("Deleting the following files:"); | ||||
|         ui->dest->hide(); | ||||
|         ui->destLabel->hide(); | ||||
|         break; | ||||
|     case FM_FILE_OP_LINK: | ||||
|     case FileOperation::Link: | ||||
|         title = tr("Create Symlinks"); | ||||
|         message = tr("Creating symlinks for the following files:"); | ||||
|         break; | ||||
|     case FM_FILE_OP_CHANGE_ATTR: | ||||
|     case FileOperation::ChangeAttr: | ||||
|         title = tr("Change Attributes"); | ||||
|         message = tr("Changing attributes of the following files:"); | ||||
|         ui->dest->hide(); | ||||
|         ui->destLabel->hide(); | ||||
|         break; | ||||
|     case FM_FILE_OP_UNTRASH: | ||||
|     case FileOperation::UnTrash: | ||||
|         title = tr("Restore Trashed Files"); | ||||
|         message = tr("Restoring the following files from trash can:"); | ||||
|         ui->dest->hide(); | ||||
| @ -100,47 +101,53 @@ int FileOperationDialog::ask(QString /*question*/, char* const* /*options*/) { | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| int FileOperationDialog::askRename(FmFileInfo* src, FmFileInfo* dest, QString& new_name) { | ||||
|     int ret; | ||||
| 
 | ||||
| FileOperationJob::FileExistsAction FileOperationDialog::askRename(const FileInfo &src, const FileInfo &dest, FilePath &newDest) { | ||||
|     FileOperationJob::FileExistsAction ret; | ||||
|     if(defaultOption == -1) { // default action is not set, ask the user
 | ||||
|         RenameDialog dlg(src, dest, this); | ||||
|         dlg.exec(); | ||||
|         switch(dlg.action()) { | ||||
|         case RenameDialog::ActionOverwrite: | ||||
|             ret = FM_FILE_OP_OVERWRITE; | ||||
|             ret = FileOperationJob::OVERWRITE; | ||||
|             if(dlg.applyToAll()) { | ||||
|                 defaultOption = ret; | ||||
|             } | ||||
|             break; | ||||
|         case RenameDialog::ActionRename: | ||||
|             ret = FM_FILE_OP_RENAME; | ||||
|             new_name = dlg.newName(); | ||||
|         case RenameDialog::ActionRename: { | ||||
|             ret = FileOperationJob::RENAME; | ||||
|             auto newName = dlg.newName(); | ||||
|             if(!newName.isEmpty()) { | ||||
|                 auto destDirPath = dest.path().parent(); | ||||
|                 newDest = destDirPath.child(newName.toUtf8().constData()); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|         case RenameDialog::ActionIgnore: | ||||
|             ret = FM_FILE_OP_SKIP; | ||||
|             ret = FileOperationJob::SKIP; | ||||
|             if(dlg.applyToAll()) { | ||||
|                 defaultOption = ret; | ||||
|             } | ||||
|             break; | ||||
|         default: | ||||
|             ret = FM_FILE_OP_CANCEL; | ||||
|             ret = FileOperationJob::CANCEL; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         ret = defaultOption; | ||||
|         ret = (FileOperationJob::FileExistsAction)defaultOption; | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| FmJobErrorAction FileOperationDialog::error(GError* err, FmJobErrorSeverity severity) { | ||||
|     if(severity >= FM_JOB_ERROR_MODERATE) { | ||||
|         if(severity == FM_JOB_ERROR_CRITICAL) { | ||||
| Job::ErrorAction FileOperationDialog::error(GError* err, Job::ErrorSeverity severity) { | ||||
|     if(severity >= Job::ErrorSeverity::MODERATE) { | ||||
|         if(severity == Job::ErrorSeverity::CRITICAL) { | ||||
|             QMessageBox::critical(this, tr("Error"), QString::fromUtf8(err->message)); | ||||
|             return FM_JOB_ABORT; | ||||
|             return Job::ErrorAction::ABORT; | ||||
|         } | ||||
|         if (ignoreNonCriticalErrors_) { | ||||
|             return FM_JOB_CONTINUE; | ||||
|             return Job::ErrorAction::CONTINUE; | ||||
|         } | ||||
|         QMessageBox::StandardButton stb = QMessageBox::critical(this, tr("Error"), QString::fromUtf8(err->message), | ||||
|                                                                 QMessageBox::Ok | QMessageBox::Ignore); | ||||
| @ -148,7 +155,7 @@ FmJobErrorAction FileOperationDialog::error(GError* err, FmJobErrorSeverity seve | ||||
|             ignoreNonCriticalErrors_ = true; | ||||
|         } | ||||
|     } | ||||
|     return FM_JOB_CONTINUE; | ||||
|     return Job::ErrorAction::CONTINUE; | ||||
| } | ||||
| 
 | ||||
| void FileOperationDialog::setCurFile(QString cur_file) { | ||||
| @ -156,15 +163,22 @@ void FileOperationDialog::setCurFile(QString cur_file) { | ||||
| } | ||||
| 
 | ||||
| void FileOperationDialog::setDataTransferred(uint64_t finishedSize, std::uint64_t totalSize) { | ||||
|     ui->dataTransferred->setText(QString("%1 / %2") | ||||
|     ui->filesProcessed->setText(QString("%1 / %2") | ||||
|                                  .arg(formatFileSize(finishedSize, fm_config->si_unit)) | ||||
|                                  .arg(formatFileSize(totalSize, fm_config->si_unit))); | ||||
| } | ||||
| 
 | ||||
| void FileOperationDialog::setFilesProcessed(uint64_t finishedCount, uint64_t totalCount) { | ||||
|     ui->filesProcessed->setText(QString("%1 / %2") | ||||
|                                  .arg(finishedCount) | ||||
|                                  .arg(totalCount)); | ||||
| } | ||||
| 
 | ||||
| void FileOperationDialog::setPercent(unsigned int percent) { | ||||
|     ui->progressBar->setValue(percent); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void FileOperationDialog::setRemainingTime(unsigned int sec) { | ||||
|     unsigned int min = 0; | ||||
|     unsigned int hr = 0; | ||||
|  | ||||
| @ -27,6 +27,7 @@ | ||||
| #include <libfm/fm.h> | ||||
| #include "core/filepath.h" | ||||
| #include "core/fileinfo.h" | ||||
| #include "core/fileoperationjob.h" | ||||
| 
 | ||||
| namespace Ui { | ||||
| class FileOperationDialog; | ||||
| @ -46,12 +47,15 @@ public: | ||||
|     void setDestPath(const Fm::FilePath& dest); | ||||
| 
 | ||||
|     int ask(QString question, char* const* options); | ||||
|     int askRename(FmFileInfo* src, FmFileInfo* dest, QString& new_name); | ||||
|     FmJobErrorAction error(GError* err, FmJobErrorSeverity severity); | ||||
| 
 | ||||
|     FileOperationJob::FileExistsAction askRename(const FileInfo& src, const FileInfo& dest, FilePath& newDest); | ||||
| 
 | ||||
|     Job::ErrorAction error(GError* err, Job::ErrorSeverity severity); | ||||
|     void setPrepared(); | ||||
|     void setCurFile(QString cur_file); | ||||
|     void setPercent(unsigned int percent); | ||||
|     void setDataTransferred(std::uint64_t finishedSize, std::uint64_t totalSize); | ||||
|     void setFilesProcessed(std::uint64_t finishedCount, std::uint64_t totalCount); | ||||
|     void setRemainingTime(unsigned int sec); | ||||
| 
 | ||||
|     virtual void reject(); | ||||
|  | ||||
							
								
								
									
										69
									
								
								src/fileoperationdialog_p.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/fileoperationdialog_p.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | ||||
| /*
 | ||||
|  * 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_FILEOPERATIONDIALOG_P_H | ||||
| #define FM_FILEOPERATIONDIALOG_P_H | ||||
| 
 | ||||
| #include <QPainter> | ||||
| #include <QStyleOption> | ||||
| #include <QLabel> | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| class ElidedLabel : public QLabel { | ||||
| Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit ElidedLabel(QWidget *parent = 0, Qt::WindowFlags f = Qt::WindowFlags()): | ||||
|         QLabel(parent, f), | ||||
|         lastWidth_(0) { | ||||
|             setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); | ||||
|             // set a min width to prevent the window from widening with long texts
 | ||||
|             setMinimumWidth(fontMetrics().averageCharWidth() * 10); | ||||
|         } | ||||
| 
 | ||||
| protected: | ||||
|     // A simplified version of QLabel::paintEvent() without pixmap or shortcut but with eliding.
 | ||||
|     void paintEvent(QPaintEvent* /*event*/) override { | ||||
|         QRect cr = contentsRect().adjusted(margin(), margin(), -margin(), -margin()); | ||||
|         QString txt = text(); | ||||
|         // if the text is changed or its rect is resized (due to window resizing),
 | ||||
|         // find whether it needs to be elided...
 | ||||
|         if (txt != lastText_ || cr.width() != lastWidth_) { | ||||
|             lastText_ = txt; | ||||
|             lastWidth_ = cr.width(); | ||||
|             elidedText_ = fontMetrics().elidedText(txt, Qt::ElideMiddle, cr.width()); | ||||
|         } | ||||
|         // ... then, draw the (elided) text
 | ||||
|         if(!elidedText_.isEmpty()) { | ||||
|             QPainter painter(this); | ||||
|             QStyleOption opt; | ||||
|             opt.initFrom(this); | ||||
|             style()->drawItemText(&painter, cr, alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     QString elidedText_; | ||||
|     QString lastText_; | ||||
|     int lastWidth_; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // FM_FILEOPERATIONDIALOG_P_H
 | ||||
| @ -20,7 +20,6 @@ | ||||
| 
 | ||||
| #include "filepropsdialog.h" | ||||
| #include "ui_file-props.h" | ||||
| #include "icontheme.h" | ||||
| #include "utilities.h" | ||||
| #include "fileoperation.h" | ||||
| #include <QStringBuilder> | ||||
| @ -403,9 +402,9 @@ void FilePropsDialog::accept() { | ||||
|     } | ||||
| 
 | ||||
|     // check if chown or chmod is needed
 | ||||
|     gint32 newUid = uidFromName(ui->owner->text()); | ||||
|     gint32 newGid = gidFromName(ui->ownerGroup->text()); | ||||
|     bool needChown = (newUid != -1 && newUid != uid) || (newGid != -1 && newGid != gid); | ||||
|     uid_t newUid = uidFromName(ui->owner->text()); | ||||
|     gid_t newGid = gidFromName(ui->ownerGroup->text()); | ||||
|     bool needChown = (newUid != INVALID_UID && newUid != uid) || (newGid != INVALID_GID && newGid != gid); | ||||
| 
 | ||||
|     int newOwnerPermSel = ui->ownerPerm->currentIndex(); | ||||
|     int newGroupPermSel = ui->groupPerm->currentIndex(); | ||||
| @ -421,10 +420,10 @@ void FilePropsDialog::accept() { | ||||
|         if(needChown) { | ||||
|             // don't do chown if new uid/gid and the original ones are actually the same.
 | ||||
|             if(newUid == uid) { | ||||
|                 newUid = -1; | ||||
|                 newUid = INVALID_UID; | ||||
|             } | ||||
|             if(newGid == gid) { | ||||
|                 newGid = -1; | ||||
|                 newGid = INVALID_GID; | ||||
|             } | ||||
|             op->setChown(newUid, newGid); | ||||
|         } | ||||
|  | ||||
| @ -80,8 +80,8 @@ private: | ||||
| 
 | ||||
|     std::shared_ptr<const Fm::MimeType> mimeType; // mime type of the files
 | ||||
| 
 | ||||
|     gint32 uid; // owner uid of the files, -1 means all files do not have the same uid
 | ||||
|     gint32 gid; // owner gid of the files, -1 means all files do not have the same uid
 | ||||
|     uid_t uid; // owner uid of the files, INVALID_UID means all files do not have the same uid
 | ||||
|     gid_t gid; // owner gid of the files, INVALID_GID means all files do not have the same uid
 | ||||
|     mode_t ownerPerm; // read permission of the files, -1 means not all files have the same value
 | ||||
|     int ownerPermSel; | ||||
|     mode_t groupPerm; // read permission of the files, -1 means not all files have the same value
 | ||||
|  | ||||
| @ -33,7 +33,7 @@ FileSearchDialog::FileSearchDialog(QStringList paths, QWidget* parent, Qt::Windo | ||||
|     ui->setupUi(this); | ||||
|     ui->minSize->setMaximum(std::numeric_limits<int>().max()); | ||||
|     ui->maxSize->setMaximum(std::numeric_limits<int>().max()); | ||||
|     Q_FOREACH(const QString& path, paths) { | ||||
|     for(const QString& path : qAsConst(paths)) { | ||||
|         ui->listView->addItem(path); | ||||
|     } | ||||
| 
 | ||||
| @ -120,7 +120,7 @@ void FileSearchDialog::accept() { | ||||
|             fm_search_set_min_mtime(search, ui->minTime->date().toString(QStringLiteral("yyyy-MM-dd")).toUtf8().constData()); | ||||
|         } | ||||
| 
 | ||||
|         searchUri_ = Path::wrapPtr(fm_search_dup_path(search)); | ||||
|         searchUri_ = FilePath{fm_search_to_gfile(search), false}; | ||||
| 
 | ||||
|         fm_search_free(search); | ||||
|     } | ||||
| @ -144,7 +144,8 @@ void FileSearchDialog::onAddPath() { | ||||
| 
 | ||||
| void FileSearchDialog::onRemovePath() { | ||||
|     // remove selected items
 | ||||
|     Q_FOREACH(QListWidgetItem* item, ui->listView->selectedItems()) { | ||||
|     const auto itemList = ui->listView->selectedItems(); | ||||
|     for(QListWidgetItem* const item : itemList) { | ||||
|         delete item; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -22,7 +22,7 @@ | ||||
| 
 | ||||
| #include "libfmqtglobals.h" | ||||
| #include <QDialog> | ||||
| #include "path.h" | ||||
| #include "core/filepath.h" | ||||
| 
 | ||||
| namespace Ui { | ||||
| class SearchDialog; | ||||
| @ -35,7 +35,7 @@ public: | ||||
|     explicit FileSearchDialog(QStringList paths = QStringList(), QWidget* parent = 0, Qt::WindowFlags f = 0); | ||||
|     ~FileSearchDialog(); | ||||
| 
 | ||||
|     Path searchUri() const { | ||||
|     const FilePath& searchUri() const { | ||||
|         return searchUri_; | ||||
|     } | ||||
| 
 | ||||
| @ -65,7 +65,7 @@ private Q_SLOTS: | ||||
| 
 | ||||
| private: | ||||
|     Ui::SearchDialog* ui; | ||||
|     Path searchUri_; | ||||
|     FilePath searchUri_; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -229,9 +229,9 @@ void fm_search_set_min_mtime(FmSearch* search, const char* mtime) | ||||
| } | ||||
| 
 | ||||
| /* really build the path */ | ||||
| FmPath* fm_search_dup_path(FmSearch* search) | ||||
| GFile* fm_search_to_gfile(FmSearch* search) | ||||
| { | ||||
|     FmPath* search_path = NULL; | ||||
|     GFile* search_path = NULL; | ||||
|     GString* search_str = g_string_sized_new(1024); | ||||
|     /* build the search:// URI to perform the search */ | ||||
|     g_string_append(search_str, "search://"); | ||||
| @ -310,7 +310,7 @@ FmPath* fm_search_dup_path(FmSearch* search) | ||||
|         if(search->max_mtime) | ||||
|             g_string_append_printf(search_str, "&max_mtime=%s", search->max_mtime); | ||||
| 
 | ||||
|         search_path = fm_path_new_for_uri(search_str->str); | ||||
|         search_path = g_file_new_for_uri(search_str->str); | ||||
|         g_string_free(search_str, TRUE); | ||||
|     } | ||||
|     return search_path; | ||||
|  | ||||
| @ -27,7 +27,7 @@ | ||||
| #ifndef _FM_SEARCH_H_ | ||||
| #define _FM_SEARCH_H_ | ||||
| 
 | ||||
| #include <libfm/fm.h> | ||||
| #include <gio/gio.h> | ||||
| 
 | ||||
| G_BEGIN_DECLS | ||||
| 
 | ||||
| @ -36,7 +36,7 @@ typedef struct _FmSearch         FmSearch; | ||||
| FmSearch* fm_search_new(void); | ||||
| void fm_search_free(FmSearch* search); | ||||
| 
 | ||||
| FmPath* fm_search_dup_path(FmSearch* search); | ||||
| GFile* fm_search_to_gfile(FmSearch* search); | ||||
| 
 | ||||
| gboolean fm_search_get_recursive(FmSearch* search); | ||||
| void fm_search_set_recursive(FmSearch* search, gboolean recursive); | ||||
|  | ||||
| @ -111,6 +111,16 @@ void FolderItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& op | ||||
|     auto file = index.data(fileInfoRole_).value<std::shared_ptr<const Fm::FileInfo>>(); | ||||
|     const auto& emblems = file ? file->emblems() : icon_emblems; | ||||
| 
 | ||||
|     QStyleOptionViewItem opt = option; | ||||
|     initStyleOption(&opt, index); | ||||
| 
 | ||||
|     // distinguish the hidden items visually by making their texts italic
 | ||||
|     if(file && file->isHidden()) { | ||||
|         QFont f(opt.font); | ||||
|         f.setItalic(true); | ||||
|         opt.font = f; | ||||
|     } | ||||
| 
 | ||||
|     bool isSymlink = file && file->isSymlink(); | ||||
|     // vertical layout (icon mode, thumbnail mode)
 | ||||
|     if(option.decorationPosition == QStyleOptionViewItem::Top || | ||||
| @ -118,8 +128,6 @@ void FolderItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& op | ||||
|         painter->save(); | ||||
|         painter->setClipRect(option.rect); | ||||
| 
 | ||||
|         QStyleOptionViewItem opt = option; | ||||
|         initStyleOption(&opt, index); | ||||
|         opt.decorationAlignment = Qt::AlignHCenter | Qt::AlignTop; | ||||
|         opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; | ||||
| 
 | ||||
| @ -168,12 +176,10 @@ void FolderItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& op | ||||
|         // let QStyledItemDelegate does its default painting
 | ||||
|         // FIXME: For better text alignment, here we should increase
 | ||||
|         // the icon width if it's smaller that the requested size
 | ||||
|         QStyledItemDelegate::paint(painter, option, index); | ||||
|         QStyledItemDelegate::paint(painter, opt, index); | ||||
| 
 | ||||
|         // draw emblems if needed
 | ||||
|         if(isSymlink || !emblems.empty()) { | ||||
|             QStyleOptionViewItem opt = option; | ||||
|             initStyleOption(&opt, index); | ||||
|             QIcon::Mode iconMode = iconModeFromState(opt.state); | ||||
|             // draw some emblems for the item if needed
 | ||||
|             if(isSymlink) { | ||||
| @ -249,7 +255,8 @@ void FolderItemDelegate::drawText(QPainter* painter, QStyleOptionViewItem& opt, | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // ?????
 | ||||
|     // Respect the active and inactive palettes (some styles can use different colors for them).
 | ||||
|     // Also, take into account a probable disabled palette.
 | ||||
|     QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled) | ||||
|                                   ? (opt.state & QStyle::State_Active) | ||||
|                                       ? QPalette::Active | ||||
| @ -350,8 +357,13 @@ QWidget* FolderItemDelegate::createEditor(QWidget* parent, const QStyleOptionVie | ||||
|         return textEdit; | ||||
|     } | ||||
|     else { | ||||
|         // return the default line-edit in compact view
 | ||||
|         return QStyledItemDelegate::createEditor(parent, option, index); | ||||
|         // return the default line-edit in other views and
 | ||||
|         // ensure that its background isn't transparent (on the side-pane)
 | ||||
|         QWidget* editor = QStyledItemDelegate::createEditor(parent, option, index); | ||||
|         QPalette p = editor->palette(); | ||||
|         p.setColor(QPalette::Base, QApplication::palette().color(QPalette::Base)); | ||||
|         editor->setPalette(p); | ||||
|         return editor; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -19,7 +19,6 @@ | ||||
| 
 | ||||
| 
 | ||||
| #include "foldermodel.h" | ||||
| #include "icontheme.h" | ||||
| #include <iostream> | ||||
| #include <algorithm> | ||||
| #include <QtAlgorithms> | ||||
| @ -40,7 +39,6 @@ FolderModel::FolderModel(): | ||||
| } | ||||
| 
 | ||||
| FolderModel::~FolderModel() { | ||||
|     qDebug("delete FolderModel"); | ||||
|     // if the thumbnail requests list is not empty, cancel them
 | ||||
|     for(auto job: pendingThumbnailJobs_) { | ||||
|         job->cancel(); | ||||
| @ -87,6 +85,7 @@ void FolderModel::onFilesAdded(const Fm::FileInfoList& files) { | ||||
|         items.append(item); | ||||
|     } | ||||
|     endInsertRows(); | ||||
|     Q_EMIT filesAdded(files); | ||||
| } | ||||
| 
 | ||||
| void FolderModel::onFilesChanged(std::vector<Fm::FileInfoPair>& files) { | ||||
| @ -161,7 +160,8 @@ void FolderModel::setCutFiles(const QItemSelection& selection) { | ||||
|         if(!selection.isEmpty()) { | ||||
|             auto cutFilesHashSet = std::make_shared<HashSet>(); | ||||
|             folder_->setCutFiles(cutFilesHashSet); | ||||
|             for(const auto& index : selection.indexes()) { | ||||
|             const auto indexes = selection.indexes(); | ||||
|             for(const auto& index : indexes) { | ||||
|                 auto item = itemFromIndex(index); | ||||
|                 item->bindCutFiles(cutFilesHashSet); | ||||
|                 cutFilesHashSet->insert(item->info->path().hash()); | ||||
| @ -230,6 +230,8 @@ QVariant FolderModel::data(const QModelIndex& index, int role/* = Qt::DisplayRol | ||||
|             return item->displaySize(); | ||||
|         case ColumnFileOwner: | ||||
|             return item->ownerName(); | ||||
|         case ColumnFileGroup: | ||||
|             return item->ownerGroup(); | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
| @ -275,6 +277,9 @@ QVariant FolderModel::headerData(int section, Qt::Orientation orientation, int r | ||||
|             case ColumnFileOwner: | ||||
|                 title = tr("Owner"); | ||||
|                 break; | ||||
|             case ColumnFileGroup: | ||||
|                 title = tr("Group"); | ||||
|                 break; | ||||
|             } | ||||
|             return QVariant(title); | ||||
|         } | ||||
| @ -361,12 +366,12 @@ QList< FolderModelItem >::iterator FolderModel::findItemByFileInfo(const Fm::Fil | ||||
| } | ||||
| 
 | ||||
| QStringList FolderModel::mimeTypes() const { | ||||
|     qDebug("FolderModel::mimeTypes"); | ||||
|     //qDebug("FolderModel::mimeTypes");
 | ||||
|     QStringList types = QAbstractItemModel::mimeTypes(); | ||||
|     // now types contains "application/x-qabstractitemmodeldatalist"
 | ||||
| 
 | ||||
|     // add support for freedesktop Xdnd direct save (XDS) protocol.
 | ||||
|     // http://www.freedesktop.org/wiki/Specifications/XDS/#index4h2
 | ||||
|     // https://www.freedesktop.org/wiki/Specifications/XDS/#index4h2
 | ||||
|     // the real implementation is in FolderView::childDropEvent().
 | ||||
|     types << "XdndDirectSave0"; | ||||
|     types << "text/uri-list"; | ||||
| @ -376,7 +381,7 @@ QStringList FolderModel::mimeTypes() const { | ||||
| 
 | ||||
| QMimeData* FolderModel::mimeData(const QModelIndexList& indexes) const { | ||||
|     QMimeData* data = QAbstractItemModel::mimeData(indexes); | ||||
|     qDebug("FolderModel::mimeData"); | ||||
|     //qDebug("FolderModel::mimeData");
 | ||||
|     // build a uri list
 | ||||
|     QByteArray urilist; | ||||
|     urilist.reserve(4096); | ||||
| @ -398,7 +403,7 @@ QMimeData* FolderModel::mimeData(const QModelIndexList& indexes) const { | ||||
| } | ||||
| 
 | ||||
| bool FolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { | ||||
|     qDebug("FolderModel::dropMimeData"); | ||||
|     //qDebug("FolderModel::dropMimeData");
 | ||||
|     if(!folder_ || !data) { | ||||
|         return false; | ||||
|     } | ||||
| @ -413,7 +418,12 @@ bool FolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int | ||||
|             info = fileInfoFromIndex(itemIndex); | ||||
|         } | ||||
|         if(info) { | ||||
|             destPath = info->path(); | ||||
|             if (info->isDir()) { | ||||
|                 destPath = info->path(); | ||||
|             } | ||||
|             else { | ||||
|                 destPath = path(); // don't drop on file
 | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             return false; | ||||
| @ -425,7 +435,7 @@ bool FolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int | ||||
| 
 | ||||
|     // FIXME: should we put this in dropEvent handler of FolderView instead?
 | ||||
|     if(data->hasUrls()) { | ||||
|         qDebug("drop action: %d", action); | ||||
|         //qDebug("drop action: %d", action);
 | ||||
|         auto srcPaths = pathListFromQUrls(data->urls()); | ||||
|         switch(action) { | ||||
|         case Qt::CopyAction: | ||||
| @ -436,6 +446,7 @@ bool FolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int | ||||
|             break; | ||||
|         case Qt::LinkAction: | ||||
|             FileOperation::symlinkFiles(srcPaths, destPath); | ||||
|         /* Falls through. */ | ||||
|         default: | ||||
|             return false; | ||||
|         } | ||||
| @ -448,7 +459,7 @@ bool FolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int | ||||
| } | ||||
| 
 | ||||
| Qt::DropActions FolderModel::supportedDropActions() const { | ||||
|     qDebug("FolderModel::supportedDropActions"); | ||||
|     //qDebug("FolderModel::supportedDropActions");
 | ||||
|     return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -54,6 +54,7 @@ public: | ||||
|         ColumnFileSize, | ||||
|         ColumnFileMTime, | ||||
|         ColumnFileOwner, | ||||
|         ColumnFileGroup, | ||||
|         NumOfColumns | ||||
|     }; | ||||
| 
 | ||||
| @ -98,6 +99,7 @@ public: | ||||
| Q_SIGNALS: | ||||
|     void thumbnailLoaded(const QModelIndex& index, int size); | ||||
|     void fileSizeChanged(const QModelIndex& index); | ||||
|     void filesAdded(FileInfoList infoList); | ||||
| 
 | ||||
| protected Q_SLOTS: | ||||
| 
 | ||||
|  | ||||
| @ -43,10 +43,7 @@ QString FolderModelItem::ownerName() const { | ||||
|     QString name; | ||||
|     auto user = Fm::UserInfoCache::globalInstance()->userFromId(info->uid()); | ||||
|     if(user) { | ||||
|         name = user->realName(); | ||||
|         if(name.isEmpty()) { | ||||
|             name = user->name(); | ||||
|         } | ||||
|         name = user->name(); | ||||
|     } | ||||
|     return name; | ||||
| } | ||||
|  | ||||
| @ -27,7 +27,6 @@ | ||||
| #include <QString> | ||||
| #include <QIcon> | ||||
| #include <QVector> | ||||
| #include "icontheme.h" | ||||
| 
 | ||||
| #include "core/folder.h" | ||||
| 
 | ||||
|  | ||||
| @ -45,12 +45,16 @@ | ||||
| #include <QX11Info> // for XDS support
 | ||||
| #include <xcb/xcb.h> // for XDS support
 | ||||
| #include "xdndworkaround.h" // for XDS support
 | ||||
| #include "path.h" | ||||
| #include "folderview_p.h" | ||||
| #include "utilities.h" | ||||
| 
 | ||||
| Q_DECLARE_OPAQUE_POINTER(FmFileInfo*) | ||||
| 
 | ||||
| #define SCROLL_FRAMES_PER_SEC 50 | ||||
| #define SCROLL_DURATION 300 // in ms
 | ||||
| 
 | ||||
| static const int scrollAnimFrames = SCROLL_FRAMES_PER_SEC * SCROLL_DURATION / 1000; | ||||
| 
 | ||||
| using namespace Fm; | ||||
| 
 | ||||
| FolderViewListView::FolderViewListView(QWidget* parent): | ||||
| @ -144,7 +148,7 @@ void FolderViewListView::dragEnterEvent(QDragEnterEvent* event) { | ||||
|     else { | ||||
|         QAbstractItemView::dragEnterEvent(event); | ||||
|     } | ||||
|     qDebug("dragEnterEvent"); | ||||
|     //qDebug("dragEnterEvent");
 | ||||
|     //static_cast<FolderView*>(parent())->childDragEnterEvent(event);
 | ||||
| } | ||||
| 
 | ||||
| @ -410,7 +414,7 @@ void FolderViewTreeView::reset() { | ||||
|     // This is for performance reason so in this case rowsInserted() and rowsAboutToBeRemoved()
 | ||||
|     // might not be called. Hence we also have to re-layout the columns when the model is reset.
 | ||||
|     // This fixes bug #190
 | ||||
|     // https://github.com/lxde/pcmanfm-qt/issues/190
 | ||||
|     // https://github.com/lxqt/pcmanfm-qt/issues/190
 | ||||
|     QTreeView::reset(); | ||||
|     queueLayoutColumns(); | ||||
| } | ||||
| @ -476,7 +480,9 @@ FolderView::FolderView(FolderView::ViewMode _mode, QWidget *parent): | ||||
|     autoSelectionDelay_(600), | ||||
|     autoSelectionTimer_(nullptr), | ||||
|     selChangedTimer_(nullptr), | ||||
|     itemDelegateMargins_(QSize(3, 3)) { | ||||
|     itemDelegateMargins_(QSize(3, 3)), | ||||
|     smoothScrollTimer_(nullptr), | ||||
|     wheelEvent_(nullptr) { | ||||
| 
 | ||||
|     iconSize_[IconMode - FirstViewMode] = QSize(48, 48); | ||||
|     iconSize_[CompactMode - FirstViewMode] = QSize(24, 24); | ||||
| @ -495,6 +501,11 @@ FolderView::FolderView(FolderView::ViewMode _mode, QWidget *parent): | ||||
| } | ||||
| 
 | ||||
| FolderView::~FolderView() { | ||||
|     if(smoothScrollTimer_) { | ||||
|         disconnect(smoothScrollTimer_, &QTimer::timeout, this, &FolderView::scrollSmoothly); | ||||
|         smoothScrollTimer_->stop(); | ||||
|         delete smoothScrollTimer_; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void FolderView::onItemActivated(QModelIndex index) { | ||||
| @ -567,6 +578,13 @@ void FolderView::setViewMode(ViewMode _mode) { | ||||
|     if(_mode == mode) { // if it's the same more, ignore
 | ||||
|         return; | ||||
|     } | ||||
|     // smooth scrolling is only for icon and thumbnail modes
 | ||||
|     if(smoothScrollTimer_ && (_mode == DetailedListMode || _mode == CompactMode)) { | ||||
|         disconnect(smoothScrollTimer_, &QTimer::timeout, this, &FolderView::scrollSmoothly); | ||||
|         smoothScrollTimer_->stop(); | ||||
|         delete smoothScrollTimer_; | ||||
|         smoothScrollTimer_ = nullptr; | ||||
|     } | ||||
|     // FIXME: retain old selection
 | ||||
| 
 | ||||
|     // since only detailed list mode uses QTreeView, and others
 | ||||
| @ -656,11 +674,10 @@ void FolderView::setViewMode(ViewMode _mode) { | ||||
|         view->setSelectionMode(QAbstractItemView::ExtendedSelection); | ||||
|         layout()->addWidget(view); | ||||
| 
 | ||||
|         // enable dnd
 | ||||
|         // enable dnd (the drop indicator is set at "FolderView::childDragMoveEvent()")
 | ||||
|         view->setDragEnabled(true); | ||||
|         view->setAcceptDrops(true); | ||||
|         view->setDragDropMode(QAbstractItemView::DragDrop); | ||||
|         view->setDropIndicatorShown(true); | ||||
| 
 | ||||
|         // inline renaming
 | ||||
|         if(delegate) { | ||||
| @ -891,6 +908,43 @@ QModelIndex FolderView::indexFromFolderPath(const Fm::FilePath& folderPath) cons | ||||
|     return QModelIndex(); | ||||
| } | ||||
| 
 | ||||
| void FolderView::selectFiles(const Fm::FileInfoList& files, bool add) { | ||||
|   if(!model_ || files.empty()) { | ||||
|       return; | ||||
|   } | ||||
|   if(!add) { | ||||
|       selectionModel()->clear(); | ||||
|   } | ||||
|   QModelIndex index, firstIndex; | ||||
|   int count = model_->rowCount(); | ||||
|   Fm::FileInfoList list = files; | ||||
|   bool singleFile(files.size() == 1); | ||||
|   for(int row = 0; row < count; ++row) { | ||||
|       if (list.empty()) { | ||||
|           break; | ||||
|       } | ||||
|       index = model_->index(row, 0); | ||||
|       auto info = model_->fileInfoFromIndex(index); | ||||
|       for(auto it = list.cbegin(); it != list.cend(); ++it) { | ||||
|           auto& item = *it; | ||||
|           if(item == info) { | ||||
|               selectionModel()->select(index, QItemSelectionModel::Select); | ||||
|               if (!firstIndex.isValid()) { | ||||
|                   firstIndex = index; | ||||
|               } | ||||
|               list.erase(it); | ||||
|               break; | ||||
|           } | ||||
|       } | ||||
|   } | ||||
|   if (firstIndex.isValid()) { | ||||
|       view->scrollTo(firstIndex, QAbstractItemView::EnsureVisible); | ||||
|       if (singleFile) { // give focus to the single file
 | ||||
|           selectionModel()->setCurrentIndex(firstIndex, QItemSelectionModel::Current); | ||||
|       } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| Fm::FileInfoList FolderView::selectedFiles() const { | ||||
|     if(model_) { | ||||
|         QModelIndexList selIndexes = mode == DetailedListMode ? selectedRows() : selectedIndexes(); | ||||
| @ -941,7 +995,7 @@ void FolderView::invertSelection() { | ||||
| } | ||||
| 
 | ||||
| void FolderView::childDragEnterEvent(QDragEnterEvent* event) { | ||||
|     qDebug("drag enter"); | ||||
|     //qDebug("drag enter");
 | ||||
|     if(event->mimeData()->hasFormat("text/uri-list")) { | ||||
|         event->accept(); | ||||
|     } | ||||
| @ -951,12 +1005,23 @@ void FolderView::childDragEnterEvent(QDragEnterEvent* event) { | ||||
| } | ||||
| 
 | ||||
| void FolderView::childDragLeaveEvent(QDragLeaveEvent* e) { | ||||
|     qDebug("drag leave"); | ||||
|     //qDebug("drag leave");
 | ||||
|     e->accept(); | ||||
| } | ||||
| 
 | ||||
| void FolderView::childDragMoveEvent(QDragMoveEvent* /*e*/) { | ||||
|     qDebug("drag move"); | ||||
| void FolderView::childDragMoveEvent(QDragMoveEvent* e) { | ||||
|     // Since it isn't possible to drop on a file (see "FolderModel::dropMimeData()"),
 | ||||
|     // we enable the drop indicator only when the cursor is on a folder.
 | ||||
|     QModelIndex index = view->indexAt(e->pos()); | ||||
|     if(index.isValid() && index.model()) { | ||||
|         QVariant data = index.model()->data(index, FolderModel::FileInfoRole); | ||||
|         auto info = data.value<std::shared_ptr<const Fm::FileInfo>>(); | ||||
|         if(info && !info->isDir()) { | ||||
|             view->setDropIndicatorShown(false); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     view->setDropIndicatorShown(true); | ||||
| } | ||||
| 
 | ||||
| void FolderView::childDropEvent(QDropEvent* e) { | ||||
| @ -1049,8 +1114,8 @@ bool FolderView::eventFilter(QObject* watched, QEvent* event) { | ||||
|                     } | ||||
|                     autoSelectionTimer_->start(autoSelectionDelay_); | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|             break; | ||||
|         case QEvent::HoverLeave: | ||||
|             if(style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) { | ||||
|                 setCursor(Qt::ArrowCursor); | ||||
| @ -1086,6 +1151,42 @@ bool FolderView::eventFilter(QObject* watched, QEvent* event) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             // Smooth Scrolling
 | ||||
|             // Some tricks are adapted from <https://github.com/zhou13/qsmoothscrollarea>.
 | ||||
|             else if(mode != DetailedListMode | ||||
|                     && event->spontaneous() | ||||
|                     && !(QApplication::keyboardModifiers() & (Qt::ShiftModifier | Qt::AltModifier))) { | ||||
|                 if(QScrollBar* vbar = view->verticalScrollBar()) { | ||||
|                     // keep track of the wheel event for smooth scrolling
 | ||||
|                     wheelEvent_ = static_cast<QWheelEvent*>(event); | ||||
|                     int delta = wheelEvent_->angleDelta().y(); | ||||
|                     if((delta > 0 && vbar->value() == vbar->minimum()) || (delta < 0 && vbar->value() == vbar->maximum())) { | ||||
|                         break; // the scrollbar can't move
 | ||||
|                     } | ||||
|                     // get a rough estimation of the wheel speed and disable animation if it's too high
 | ||||
|                     static QList<qint64> wheelEvents; | ||||
|                     wheelEvents << QDateTime::currentMSecsSinceEpoch(); | ||||
|                     while(wheelEvents.last() - wheelEvents.first() > 500) { | ||||
|                         wheelEvents.removeFirst(); | ||||
|                     } | ||||
|                     if(wheelEvents.size() > 10) { | ||||
|                         break; | ||||
|                     } | ||||
| 
 | ||||
|                     if(!smoothScrollTimer_) { | ||||
|                         smoothScrollTimer_ = new QTimer(); | ||||
|                         connect(smoothScrollTimer_, &QTimer::timeout, this, &FolderView::scrollSmoothly); | ||||
|                     } | ||||
| 
 | ||||
|                     // set the data for smooth scrolling
 | ||||
|                     scollData data; | ||||
|                     data.delta = delta; | ||||
|                     data.leftFrames = scrollAnimFrames; | ||||
|                     queuedScrollSteps_.append(data); | ||||
|                     smoothScrollTimer_->start(1000 / SCROLL_FRAMES_PER_SEC); | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
| @ -1094,6 +1195,36 @@ bool FolderView::eventFilter(QObject* watched, QEvent* event) { | ||||
|     return QObject::eventFilter(watched, event); | ||||
| } | ||||
| 
 | ||||
| void FolderView::scrollSmoothly() { | ||||
|     if(!wheelEvent_ || !view->verticalScrollBar()) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     int totalDelta = 0; | ||||
|     QList<scollData>::iterator it = queuedScrollSteps_.begin(); | ||||
|     while(it != queuedScrollSteps_.end()) { | ||||
|         if(it->leftFrames == 1) { // find the exact delta for the last frame
 | ||||
|             totalDelta += it->delta - (scrollAnimFrames - 1) * qRound((qreal)it->delta / (qreal)scrollAnimFrames); | ||||
|             it = queuedScrollSteps_.erase(it); | ||||
|         } | ||||
|         else { | ||||
|             totalDelta += qRound((qreal)it->delta / (qreal)scrollAnimFrames); | ||||
|             -- it->leftFrames; | ||||
|             ++it; | ||||
|         } | ||||
|     } | ||||
|     if(totalDelta != 0) { | ||||
|         // as in qevent.cpp -> QWheelEvent::QWheelEvent()
 | ||||
|         QWheelEvent e(wheelEvent_->pos(), wheelEvent_->globalPos(), | ||||
|                       totalDelta, | ||||
|                       wheelEvent_->buttons(), Qt::NoModifier, Qt::Vertical); | ||||
|         QApplication::sendEvent(view->verticalScrollBar(), &e); | ||||
|     } | ||||
|     if(queuedScrollSteps_.empty()) { | ||||
|         smoothScrollTimer_->stop(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // this slot handles auto-selection of items.
 | ||||
| void FolderView::onAutoSelectionTimeout() { | ||||
|     if(QApplication::mouseButtons() != Qt::NoButton) { | ||||
| @ -1218,8 +1349,10 @@ void FolderView::onClipboardDataChange() { | ||||
|         if(!folder()->path().hasUriScheme("search") // skip for search results
 | ||||
|            && isCutSelection | ||||
|            && Fm::isCurrentPidClipboardData(*data)) { // set cut files only with this app
 | ||||
|             auto cutDirPath = paths.size() > 0 ? paths[0].parent(): FilePath(); | ||||
|             if(folder()->path() == cutDirPath) { | ||||
|             auto cutDirPath = paths.size() > 0 ? paths[0].parent() : FilePath(); | ||||
|             // set the cut file(s) only if the cutting is done here
 | ||||
|             if(folder()->path() == cutDirPath | ||||
|                && selectedFilePaths() == paths) { | ||||
|                 model_->setCutFiles(selectionModel()->selection()); | ||||
|             } | ||||
|             else if(folder()->hadCutFilesUnset() || folder()->hasCutFiles()) { | ||||
|  | ||||
| @ -29,7 +29,6 @@ | ||||
| #include <libfm/fm.h> | ||||
| #include "foldermodel.h" | ||||
| #include "proxyfoldermodel.h" | ||||
| #include "path.h" | ||||
| 
 | ||||
| #include "core/folder.h" | ||||
| 
 | ||||
| @ -102,6 +101,7 @@ public: | ||||
|     Fm::FilePathList selectedFilePaths() const; | ||||
|     bool hasSelection() const; | ||||
|     QModelIndex indexFromFolderPath(const Fm::FilePath& folderPath) const; | ||||
|     void selectFiles(const Fm::FileInfoList& files, bool add = false); | ||||
| 
 | ||||
|     void selectAll(); | ||||
| 
 | ||||
| @ -160,6 +160,7 @@ private Q_SLOTS: | ||||
|     void onAutoSelectionTimeout(); | ||||
|     void onSelChangedTimeout(); | ||||
|     void onClosingEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint); | ||||
|     void scrollSmoothly(); | ||||
| 
 | ||||
| Q_SIGNALS: | ||||
|     void clicked(int type, const std::shared_ptr<const Fm::FileInfo>& file); | ||||
| @ -181,6 +182,14 @@ private: | ||||
|     QTimer* selChangedTimer_; | ||||
|     // the cell margins in the icon and thumbnail modes
 | ||||
|     QSize itemDelegateMargins_; | ||||
|     // smooth scrolling:
 | ||||
|     struct scollData { | ||||
|         int delta; | ||||
|         int leftFrames; | ||||
|     }; | ||||
|     QTimer *smoothScrollTimer_; | ||||
|     QWheelEvent *wheelEvent_; | ||||
|     QList<scollData> queuedScrollSteps_; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -1,80 +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 "icontheme.h" | ||||
| #include <libfm/fm.h> | ||||
| #include <QList> | ||||
| #include <QIcon> | ||||
| #include <QtGlobal> | ||||
| #include <QApplication> | ||||
| #include <QDesktopWidget> | ||||
| 
 | ||||
| #include "core/iconinfo.h" | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| static IconTheme* theIconTheme = nullptr; // the global single instance of IconTheme.
 | ||||
| 
 | ||||
| IconTheme::IconTheme(): | ||||
|     currentThemeName_(QIcon::themeName()) { | ||||
|     // NOTE: only one instance is allowed
 | ||||
|     Q_ASSERT(theIconTheme == nullptr); | ||||
|     Q_ASSERT(qApp != nullptr); // QApplication should exists before contructing IconTheme.
 | ||||
| 
 | ||||
|     theIconTheme = this; | ||||
| 
 | ||||
|     // We need to get notified when there is a QEvent::StyleChange event so
 | ||||
|     // we can check if the current icon theme name is changed.
 | ||||
|     // To do this, we can filter QApplication object itself to intercept
 | ||||
|     // signals of all widgets, but this may be too inefficient.
 | ||||
|     // So, we only filter the events on QDesktopWidget instead.
 | ||||
|     qApp->desktop()->installEventFilter(this); | ||||
| } | ||||
| 
 | ||||
| IconTheme::~IconTheme() { | ||||
| } | ||||
| 
 | ||||
| IconTheme* IconTheme::instance() { | ||||
|     return theIconTheme; | ||||
| } | ||||
| 
 | ||||
| // check if the icon theme name is changed and emit "changed()" signal if any change is detected.
 | ||||
| void IconTheme::checkChanged() { | ||||
|     if(QIcon::themeName() != theIconTheme->currentThemeName_) { | ||||
|         // if the icon theme is changed
 | ||||
|         theIconTheme->currentThemeName_ = QIcon::themeName(); | ||||
|         // invalidate the cached data
 | ||||
|         Fm::IconInfo::updateQIcons(); | ||||
|         Q_EMIT theIconTheme->changed(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // this method is called whenever there is an event on the QDesktopWidget object.
 | ||||
| bool IconTheme::eventFilter(QObject* obj, QEvent* event) { | ||||
|     // we're only interested in the StyleChange event.
 | ||||
|     // FIXME: QEvent::ThemeChange seems to be interal to Qt 5 and is not documented
 | ||||
|     if(event->type() == QEvent::StyleChange || event->type() == QEvent::ThemeChange) { | ||||
|         checkChanged(); // check if the icon theme is changed
 | ||||
|     } | ||||
|     return QObject::eventFilter(obj, event); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| } // namespace Fm
 | ||||
| @ -1,53 +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_ICONTHEME_H | ||||
| #define FM_ICONTHEME_H | ||||
| 
 | ||||
| #include "libfmqtglobals.h" | ||||
| #include <QIcon> | ||||
| #include <QString> | ||||
| #include "libfm/fm.h" | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| class LIBFM_QT_API IconTheme: public QObject { | ||||
|     Q_OBJECT | ||||
| public: | ||||
|     IconTheme(); | ||||
|     ~IconTheme(); | ||||
| 
 | ||||
|     static IconTheme* instance(); | ||||
| 
 | ||||
|     static void checkChanged(); // check if current icon theme name is changed
 | ||||
| 
 | ||||
| Q_SIGNALS: | ||||
|     void changed(); // emitted when the name of current icon theme is changed
 | ||||
| 
 | ||||
| protected: | ||||
|     bool eventFilter(QObject* obj, QEvent* event); | ||||
| 
 | ||||
| private: | ||||
|     QString currentThemeName_; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif // FM_ICONTHEME_H
 | ||||
| @ -5,7 +5,7 @@ includedir=${prefix}/include | ||||
| 
 | ||||
| Name: libfm-qt | ||||
| Description: A Qt/glib/gio-based lib used to develop file managers providing some file management utilities. (This is a Qt port of the original libfm library) | ||||
| URL: http://pcmanfm.sourceforge.net/ | ||||
| URL: https://github.com/lxqt/libfm-qt | ||||
| Requires: @REQUIRED_QT@ libfm >= 1.2.0 | ||||
| Version: @LIBFM_QT_VERSION@ | ||||
| Libs: -L${libdir} -lfm -l@LIBFM_QT_LIBRARY_NAME@ | ||||
|  | ||||
| @ -21,7 +21,6 @@ | ||||
| #include "libfmqt.h" | ||||
| #include <QLocale> | ||||
| #include <QPixmapCache> | ||||
| #include "icontheme.h" | ||||
| #include "core/thumbnailer.h" | ||||
| #include "xdndworkaround.h" | ||||
| 
 | ||||
| @ -31,7 +30,6 @@ struct LibFmQtData { | ||||
|     LibFmQtData(); | ||||
|     ~LibFmQtData(); | ||||
| 
 | ||||
|     IconTheme* iconTheme; | ||||
|     QTranslator translator; | ||||
|     XdndWorkaround workaround; | ||||
|     int refCount; | ||||
| @ -52,7 +50,6 @@ LibFmQtData::LibFmQtData(): refCount(1) { | ||||
|     fm_init(nullptr); | ||||
|     // turn on glib debug message
 | ||||
|     // g_setenv("G_MESSAGES_DEBUG", "all", true);
 | ||||
|     iconTheme = new IconTheme(); | ||||
|     Fm::Thumbnailer::loadAll(); | ||||
|     translator.load("libfm-qt_" + QLocale::system().name(), LIBFM_QT_DATA_DIR "/translations"); | ||||
| 
 | ||||
| @ -67,7 +64,6 @@ LibFmQtData::~LibFmQtData() { | ||||
|     GVfs* vfs = g_vfs_get_default(); | ||||
|     g_vfs_unregister_uri_scheme(vfs, "menu"); | ||||
|     g_vfs_unregister_uri_scheme(vfs, "search"); | ||||
|     delete iconTheme; | ||||
|     fm_finalize(); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -26,6 +26,7 @@ | ||||
| #include "mountoperationpassworddialog_p.h" | ||||
| #include "mountoperationquestiondialog_p.h" | ||||
| #include "ui_mount-operation-password.h" | ||||
| #include "core/gioptrs.h" | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| @ -83,6 +84,16 @@ MountOperation::~MountOperation() { | ||||
|     // qDebug("MountOperation deleted");
 | ||||
| } | ||||
| 
 | ||||
| void MountOperation::mountEnclosingVolume(const FilePath &path) { | ||||
|     g_file_mount_enclosing_volume(path.gfile().get(), G_MOUNT_MOUNT_NONE, op, cancellable_, | ||||
|                                   (GAsyncReadyCallback)onMountFileFinished, new QPointer<MountOperation>(this)); | ||||
| } | ||||
| 
 | ||||
| void MountOperation::mountMountable(const FilePath &mountable) { | ||||
|     g_file_mount_mountable(mountable.gfile().get(), G_MOUNT_MOUNT_NONE, op, cancellable_, | ||||
|                            (GAsyncReadyCallback)onMountMountableFinished, new QPointer<MountOperation>(this)); | ||||
| } | ||||
| 
 | ||||
| void MountOperation::onAbort(GMountOperation* /*_op*/, MountOperation* /*pThis*/) { | ||||
| 
 | ||||
| } | ||||
| @ -143,6 +154,15 @@ void MountOperation::onMountFileFinished(GFile* file, GAsyncResult* res, QPointe | ||||
|     delete pThis; | ||||
| } | ||||
| 
 | ||||
| void MountOperation::onMountMountableFinished(GFile* file, GAsyncResult* res, QPointer<MountOperation>* pThis) { | ||||
|     if(*pThis) { | ||||
|         GError* error = nullptr; | ||||
|         g_file_mount_mountable_finish(file, res, &error); | ||||
|         (*pThis)->handleFinish(error); | ||||
|     } | ||||
|     delete pThis; | ||||
| } | ||||
| 
 | ||||
| void MountOperation::onMountVolumeFinished(GVolume* volume, GAsyncResult* res, QPointer< MountOperation >* pThis) { | ||||
|     if(*pThis) { | ||||
|         GError* error = nullptr; | ||||
|  | ||||
| @ -48,11 +48,15 @@ public: | ||||
|     explicit MountOperation(bool interactive = true, QWidget* parent = 0); | ||||
|     ~MountOperation(); | ||||
| 
 | ||||
|     FM_QT_DEPRECATED | ||||
|     void mount(const Fm::FilePath& path) { | ||||
|         g_file_mount_enclosing_volume(path.gfile().get(), G_MOUNT_MOUNT_NONE, op, cancellable_, | ||||
|                                       (GAsyncReadyCallback)onMountFileFinished, new QPointer<MountOperation>(this)); | ||||
|         mountEnclosingVolume(path); | ||||
|     } | ||||
| 
 | ||||
|     void mountEnclosingVolume(const Fm::FilePath& path); | ||||
| 
 | ||||
|     void mountMountable(const Fm::FilePath& mountable); | ||||
| 
 | ||||
|     void mount(GVolume* volume) { | ||||
|         g_volume_mount(volume, G_MOUNT_MOUNT_NONE, op, cancellable_, (GAsyncReadyCallback)onMountVolumeFinished, new QPointer<MountOperation>(this)); | ||||
|     } | ||||
| @ -135,6 +139,7 @@ private: | ||||
| 
 | ||||
|     // it's possible that this object is freed when the callback is called by gio, so guarding with QPointer is needed here.
 | ||||
|     static void onMountFileFinished(GFile* file, GAsyncResult* res, QPointer<MountOperation>* pThis); | ||||
|     static void onMountMountableFinished(GFile* file, GAsyncResult* res, QPointer<MountOperation>* pThis); | ||||
|     static void onMountVolumeFinished(GVolume* volume, GAsyncResult* res, QPointer<MountOperation>* pThis); | ||||
|     static void onUnmountMountFinished(GMount* mount, GAsyncResult* res, QPointer<MountOperation>* pThis); | ||||
|     static void onEjectMountFinished(GMount* mount, GAsyncResult* res, QPointer<MountOperation>* pThis); | ||||
|  | ||||
							
								
								
									
										441
									
								
								src/path.h
									
									
									
									
									
								
							
							
						
						
									
										441
									
								
								src/path.h
									
									
									
									
									
								
							| @ -1,441 +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_PATH_H__ | ||||
| #define __LIBFM_QT_FM_PATH_H__ | ||||
| 
 | ||||
| #include <libfm/fm.h> | ||||
| #include <QObject> | ||||
| #include <QtGlobal> | ||||
| #include <QMetaType> | ||||
| #include "libfmqtglobals.h" | ||||
| 
 | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| 
 | ||||
| class LIBFM_QT_API PathList { | ||||
| public: | ||||
| 
 | ||||
| 
 | ||||
|   PathList(void ) { | ||||
|     dataPtr_ = reinterpret_cast<FmPathList*>(fm_path_list_new()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   PathList(FmPathList* dataPtr){ | ||||
|     dataPtr_ = dataPtr != nullptr ? reinterpret_cast<FmPathList*>(fm_list_ref(FM_LIST(dataPtr))) : nullptr; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // copy constructor
 | ||||
|   PathList(const PathList& other) { | ||||
|     dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<FmPathList*>(fm_list_ref(FM_LIST(other.dataPtr_))) : nullptr; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // move constructor
 | ||||
|   PathList(PathList&& other) { | ||||
|     dataPtr_ = reinterpret_cast<FmPathList*>(other.takeDataPtr()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // destructor
 | ||||
|   ~PathList() { | ||||
|     if(dataPtr_ != nullptr) { | ||||
|       fm_list_unref(FM_LIST(dataPtr_)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // create a wrapper for the data pointer without increasing the reference count
 | ||||
|   static PathList wrapPtr(FmPathList* dataPtr) { | ||||
|     PathList obj; | ||||
|     obj.dataPtr_ = reinterpret_cast<FmPathList*>(dataPtr); | ||||
|     return obj; | ||||
|   } | ||||
| 
 | ||||
|   // disown the managed data pointer
 | ||||
|   FmPathList* takeDataPtr() { | ||||
|     FmPathList* data = reinterpret_cast<FmPathList*>(dataPtr_); | ||||
|     dataPtr_ = nullptr; | ||||
|     return data; | ||||
|   } | ||||
| 
 | ||||
|   // get the raw pointer wrapped
 | ||||
|   FmPathList* dataPtr() { | ||||
|     return reinterpret_cast<FmPathList*>(dataPtr_); | ||||
|   } | ||||
| 
 | ||||
|   // automatic type casting
 | ||||
|   operator FmPathList*() { | ||||
|     return dataPtr(); | ||||
|   } | ||||
| 
 | ||||
|   // copy assignment
 | ||||
|   PathList& operator=(const PathList& other) { | ||||
|     if(dataPtr_ != nullptr) { | ||||
|       fm_list_unref(FM_LIST(dataPtr_)); | ||||
|     } | ||||
|     dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<FmPathList*>(fm_list_ref(FM_LIST(other.dataPtr_))) : nullptr; | ||||
|     return *this; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // move assignment
 | ||||
|   PathList& operator=(PathList&& other) { | ||||
|     dataPtr_ = reinterpret_cast<FmPathList*>(other.takeDataPtr()); | ||||
|     return *this; | ||||
|   } | ||||
| 
 | ||||
|   bool isNull() { | ||||
|     return (dataPtr_ == nullptr); | ||||
|   } | ||||
| 
 | ||||
|   // methods
 | ||||
| 
 | ||||
|   void writeUriList(GString* buf) { | ||||
|     fm_path_list_write_uri_list(dataPtr(), buf); | ||||
|   } | ||||
| 
 | ||||
|   char* toUriList(void) { | ||||
|     return fm_path_list_to_uri_list(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   unsigned int getLength() { | ||||
|     return fm_path_list_get_length(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   bool isEmpty() { | ||||
|     return fm_path_list_is_empty(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   FmPath* peekHead() { | ||||
|     return fm_path_list_peek_head(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   GList* peekHeadLink() { | ||||
|     return fm_path_list_peek_head_link(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   void pushTail(FmPath* path) { | ||||
|     fm_path_list_push_tail(dataPtr(), path); | ||||
|   } | ||||
| 
 | ||||
|   static PathList newFromFileInfoGslist(GSList* fis) { | ||||
|     return PathList::wrapPtr(fm_path_list_new_from_file_info_gslist(fis)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static PathList newFromFileInfoGlist(GList* fis) { | ||||
|     return PathList::wrapPtr(fm_path_list_new_from_file_info_glist(fis)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static PathList newFromFileInfoList(FmFileInfoList* fis) { | ||||
|     return PathList::wrapPtr(fm_path_list_new_from_file_info_list(fis)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static PathList newFromUris(char* const* uris) { | ||||
|     return PathList::wrapPtr(fm_path_list_new_from_uris(uris)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static PathList newFromUriList(const char* uri_list) { | ||||
|     return PathList::wrapPtr(fm_path_list_new_from_uri_list(uri_list)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| private: | ||||
|   FmPathList* dataPtr_; // data pointer for the underlying C struct
 | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class LIBFM_QT_API Path { | ||||
| public: | ||||
| 
 | ||||
| 
 | ||||
|   // default constructor
 | ||||
|   Path() { | ||||
|     dataPtr_ = nullptr; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   Path(FmPath* dataPtr){ | ||||
|     dataPtr_ = dataPtr != nullptr ? reinterpret_cast<FmPath*>(fm_path_ref(dataPtr)) : nullptr; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // copy constructor
 | ||||
|   Path(const Path& other) { | ||||
|     dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<FmPath*>(fm_path_ref(other.dataPtr_)) : nullptr; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // move constructor
 | ||||
|   Path(Path&& other) { | ||||
|     dataPtr_ = reinterpret_cast<FmPath*>(other.takeDataPtr()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // destructor
 | ||||
|   ~Path() { | ||||
|     if(dataPtr_ != nullptr) { | ||||
|       fm_path_unref(dataPtr_); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // create a wrapper for the data pointer without increasing the reference count
 | ||||
|   static Path wrapPtr(FmPath* dataPtr) { | ||||
|     Path obj; | ||||
|     obj.dataPtr_ = reinterpret_cast<FmPath*>(dataPtr); | ||||
|     return obj; | ||||
|   } | ||||
| 
 | ||||
|   // disown the managed data pointer
 | ||||
|   FmPath* takeDataPtr() { | ||||
|     FmPath* data = reinterpret_cast<FmPath*>(dataPtr_); | ||||
|     dataPtr_ = nullptr; | ||||
|     return data; | ||||
|   } | ||||
| 
 | ||||
|   // get the raw pointer wrapped
 | ||||
|   FmPath* dataPtr() { | ||||
|     return reinterpret_cast<FmPath*>(dataPtr_); | ||||
|   } | ||||
| 
 | ||||
|   // automatic type casting
 | ||||
|   operator FmPath*() { | ||||
|     return dataPtr(); | ||||
|   } | ||||
| 
 | ||||
|   // copy assignment
 | ||||
|   Path& operator=(const Path& other) { | ||||
|     if(dataPtr_ != nullptr) { | ||||
|       fm_path_unref(dataPtr_); | ||||
|     } | ||||
|     dataPtr_ = other.dataPtr_ != nullptr ? reinterpret_cast<FmPath*>(fm_path_ref(other.dataPtr_)) : nullptr; | ||||
|     return *this; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // move assignment
 | ||||
|   Path& operator=(Path&& other) { | ||||
|     dataPtr_ = reinterpret_cast<FmPath*>(other.takeDataPtr()); | ||||
|     return *this; | ||||
|   } | ||||
| 
 | ||||
|   bool isNull() { | ||||
|     return (dataPtr_ == nullptr); | ||||
|   } | ||||
| 
 | ||||
|   // methods
 | ||||
|   bool isNative() { | ||||
|     return fm_path_is_native(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   bool isTrash() { | ||||
|     return fm_path_is_trash(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   bool isTrashRoot() { | ||||
|     return fm_path_is_trash_root(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   bool isNativeOrTrash() { | ||||
|     return fm_path_is_native_or_trash(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   int depth(void) { | ||||
|     return fm_path_depth(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   bool equalStr(const gchar* str, int n) { | ||||
|     return fm_path_equal_str(dataPtr(), str, n); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   int compare(FmPath* p2) { | ||||
|     return fm_path_compare(dataPtr(), p2); | ||||
|   } | ||||
| 
 | ||||
|   int compare(Path& p2) { | ||||
|     return fm_path_compare(dataPtr(), p2.dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   bool equal(FmPath* p2) { | ||||
|     return fm_path_equal(dataPtr(), p2); | ||||
|   } | ||||
| 
 | ||||
|   bool equal(Path& p2) { | ||||
|     return fm_path_equal(dataPtr(), p2.dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   bool operator == (Path& other) { | ||||
|     return fm_path_equal(dataPtr(), other.dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   bool operator != (Path& other) { | ||||
|     return !fm_path_equal(dataPtr(), other.dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   bool operator < (Path& other) { | ||||
|     return compare(other); | ||||
|   } | ||||
| 
 | ||||
|   bool operator > (Path& other) { | ||||
|     return (other < *this); | ||||
|   } | ||||
| 
 | ||||
|   unsigned int hash(void) { | ||||
|     return fm_path_hash(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   char* displayBasename(void) { | ||||
|     return fm_path_display_basename(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
|   char* displayName(gboolean human_readable) { | ||||
|     return fm_path_display_name(dataPtr(), human_readable); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   GFile* toGfile(void) { | ||||
|     return fm_path_to_gfile(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   char* toUri(void) { | ||||
|     return fm_path_to_uri(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   char* toStr(void) { | ||||
|     return fm_path_to_str(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   Path getSchemePath(void) { | ||||
|     return Path(fm_path_get_scheme_path(dataPtr())); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   bool hasPrefix(FmPath* prefix) { | ||||
|     return fm_path_has_prefix(dataPtr(), prefix); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   FmPathFlags getFlags(void) { | ||||
|     return fm_path_get_flags(dataPtr()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   Path getParent(void) { | ||||
|     return Path(fm_path_get_parent(dataPtr())); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static Path getAppsMenu(void ) { | ||||
|     return Path(fm_path_get_apps_menu()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static Path getTrash(void ) { | ||||
|     return Path(fm_path_get_trash()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static Path getDesktop(void ) { | ||||
|     return Path(fm_path_get_desktop()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static Path getHome(void ) { | ||||
|     return Path(fm_path_get_home()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static Path getRoot(void ) { | ||||
|     return Path(fm_path_get_root()); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static Path newForGfile(GFile* gf) { | ||||
|     return Path::wrapPtr(fm_path_new_for_gfile(gf)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   Path newRelative(const char* rel) { | ||||
|     return Path::wrapPtr(fm_path_new_relative(dataPtr(), rel)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   Path newChildLen(const char* basename, int name_len) { | ||||
|     return Path::wrapPtr(fm_path_new_child_len(dataPtr(), basename, name_len)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   Path newChild(const char* basename) { | ||||
|     return Path::wrapPtr(fm_path_new_child(dataPtr(), basename)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static Path newForCommandlineArg(const char* arg) { | ||||
|     return Path::wrapPtr(fm_path_new_for_commandline_arg(arg)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static Path newForStr(const char* path_str) { | ||||
|     return Path::wrapPtr(fm_path_new_for_str(path_str)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static Path newForDisplayName(const char* path_name) { | ||||
|     return Path::wrapPtr(fm_path_new_for_display_name(path_name)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static Path newForUri(const char* uri) { | ||||
|     return Path::wrapPtr(fm_path_new_for_uri(uri)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   static Path newForPath(const char* path_name) { | ||||
|     return Path::wrapPtr(fm_path_new_for_path(path_name)); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| private: | ||||
|   FmPath* dataPtr_; // data pointer for the underlying C struct
 | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| Q_DECLARE_OPAQUE_POINTER(FmPath*) | ||||
| 
 | ||||
| #endif // __LIBFM_QT_FM_PATH_H__
 | ||||
| @ -305,8 +305,9 @@ void PathBar::openEditor() { | ||||
|         connect(tempPathEdit_, &PathEdit::returnPressed, this, &PathBar::onReturnPressed); | ||||
|         connect(tempPathEdit_, &PathEdit::editingFinished, this, &PathBar::closeEditor); | ||||
|     } | ||||
|     tempPathEdit_->setFocus(); | ||||
|     tempPathEdit_->selectAll(); | ||||
|     QApplication::clipboard()->setText(tempPathEdit_->text(), QClipboard::Selection); | ||||
|     QTimer::singleShot(0, tempPathEdit_, SLOT(setFocus())); | ||||
| } | ||||
| 
 | ||||
| void PathBar::closeEditor() { | ||||
|  | ||||
| @ -112,7 +112,7 @@ bool PathEdit::event(QEvent* e) { | ||||
|         if(keyEvent->key() == Qt::Key_Tab && keyEvent->modifiers() == Qt::NoModifier) { // Tab key is pressed
 | ||||
|             e->accept(); | ||||
|             // do auto-completion when the user press the Tab key.
 | ||||
|             // This fixes #201: https://github.com/lxde/pcmanfm-qt/issues/201
 | ||||
|             // This fixes #201: https://github.com/lxqt/pcmanfm-qt/issues/201
 | ||||
|             autoComplete(); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
| @ -19,7 +19,6 @@ | ||||
| 
 | ||||
| 
 | ||||
| #include "placesmodel.h" | ||||
| #include "icontheme.h" | ||||
| #include <gio/gio.h> | ||||
| #include <QDebug> | ||||
| #include <QMimeData> | ||||
| @ -55,25 +54,19 @@ PlacesModel::PlacesModel(QObject* parent): | ||||
| 
 | ||||
|     createTrashItem(); | ||||
| 
 | ||||
|     // FIXME: add an option to hide network:///
 | ||||
|     if(true) { | ||||
|         computerItem = new PlacesModelItem("computer", tr("Computer"), Fm::FilePath::fromUri("computer:///")); | ||||
|         placesRoot->appendRow(computerItem); | ||||
|     } | ||||
|     else { | ||||
|         computerItem = nullptr; | ||||
|     computerItem = new PlacesModelItem("computer", tr("Computer"), Fm::FilePath::fromUri("computer:///")); | ||||
|     placesRoot->appendRow(computerItem); | ||||
| 
 | ||||
|     { // Applications
 | ||||
|         const char* applicaion_icon_names[] = {"system-software-install", "applications-accessories", "application-x-executable"}; | ||||
|         // NOTE: g_themed_icon_new_from_names() accepts char**, but actually const char** is OK.
 | ||||
|         Fm::GIconPtr gicon{g_themed_icon_new_from_names((char**)applicaion_icon_names, G_N_ELEMENTS(applicaion_icon_names)), false}; | ||||
|         auto fmicon = Fm::IconInfo::fromGIcon(std::move(gicon)); | ||||
|         applicationsItem = new PlacesModelItem(fmicon, tr("Applications"), Fm::FilePath::fromUri("menu:///applications/")); | ||||
|         placesRoot->appendRow(applicationsItem); | ||||
|     } | ||||
| 
 | ||||
|     // FIXME: add an option to hide applications:///
 | ||||
|     const char* applicaion_icon_names[] = {"system-software-install", "applications-accessories", "application-x-executable"}; | ||||
|     // NOTE: g_themed_icon_new_from_names() accepts char**, but actually const char** is OK.
 | ||||
|     Fm::GIconPtr gicon{g_themed_icon_new_from_names((char**)applicaion_icon_names, G_N_ELEMENTS(applicaion_icon_names)), false}; | ||||
|     auto fmicon = Fm::IconInfo::fromGIcon(std::move(gicon)); | ||||
|     applicationsItem = new PlacesModelItem(fmicon, tr("Applications"), Fm::FilePath::fromUri("menu:///applications/")); | ||||
|     placesRoot->appendRow(applicationsItem); | ||||
| 
 | ||||
|     // FIXME: add an option to hide network:///
 | ||||
|     if(true) { | ||||
|     { // Network
 | ||||
|         const char* network_icon_names[] = {"network", "folder-network", "folder"}; | ||||
|         // NOTE: g_themed_icon_new_from_names() accepts char**, but actually const char** is OK.
 | ||||
|         Fm::GIconPtr gicon{g_themed_icon_new_from_names((char**)network_icon_names, G_N_ELEMENTS(network_icon_names)), false}; | ||||
| @ -81,9 +74,6 @@ PlacesModel::PlacesModel(QObject* parent): | ||||
|         networkItem = new PlacesModelItem(fmicon, tr("Network"), Fm::FilePath::fromUri("network:///")); | ||||
|         placesRoot->appendRow(networkItem); | ||||
|     } | ||||
|     else { | ||||
|         networkItem = nullptr; | ||||
|     } | ||||
| 
 | ||||
|     devicesRoot = new QStandardItem(tr("Devices")); | ||||
|     devicesRoot->setSelectable(false); | ||||
| @ -171,7 +161,7 @@ PlacesModel::~PlacesModel() { | ||||
|         g_object_unref(trashMonitor_); | ||||
|     } | ||||
| 
 | ||||
|     Q_FOREACH(GMount* mount, shadowedMounts_) { | ||||
|     for(GMount* const mount : qAsConst(shadowedMounts_)) { | ||||
|         g_object_unref(mount); | ||||
|     } | ||||
| } | ||||
| @ -429,6 +419,18 @@ void PlacesModel::onMountRemoved(GVolumeMonitor* monitor, GMount* mount, PlacesM | ||||
| } | ||||
| 
 | ||||
| void PlacesModel::onVolumeAdded(GVolumeMonitor* /*monitor*/, GVolume* volume, PlacesModel* pThis) { | ||||
|     // the item may have been added with "mount-added" (as in loopback mounting)
 | ||||
|     bool itemExists = false; | ||||
|     GMount* mount = g_volume_get_mount(volume); | ||||
|     if(mount) { | ||||
|         if(pThis->itemFromMount(mount)) { | ||||
|             itemExists = true; | ||||
|         } | ||||
|         g_object_unref(mount); | ||||
|     } | ||||
|     if(itemExists) { | ||||
|         return; | ||||
|     } | ||||
|     // for some unknown reasons, sometimes we get repeated volume-added
 | ||||
|     // signals and added a device more than one. So, make a sanity check here.
 | ||||
|     PlacesModelVolumeItem* volumeItem = pThis->itemFromVolume(volume); | ||||
|  | ||||
| @ -19,7 +19,6 @@ | ||||
| 
 | ||||
| 
 | ||||
| #include "placesmodelitem.h" | ||||
| #include "icontheme.h" | ||||
| #include <gio/gio.h> | ||||
| #include <QPainter> | ||||
| 
 | ||||
| @ -87,7 +86,7 @@ QVariant PlacesModelItem::data(int role) const { | ||||
| } | ||||
| 
 | ||||
| PlacesModelBookmarkItem::PlacesModelBookmarkItem(std::shared_ptr<const Fm::BookmarkItem> bm_item): | ||||
|     PlacesModelItem{Fm::IconInfo::fromName("folder"), bm_item->name(), bm_item->path()}, | ||||
|     PlacesModelItem{bm_item->icon(), bm_item->name(), bm_item->path()}, | ||||
|     bookmarkItem_{std::move(bm_item)} { | ||||
|     setEditable(true); | ||||
| } | ||||
|  | ||||
| @ -32,12 +32,112 @@ | ||||
| 
 | ||||
| namespace Fm { | ||||
| 
 | ||||
| std::shared_ptr<PlacesProxyModel> PlacesView::proxyModel_; | ||||
| 
 | ||||
| PlacesProxyModel::PlacesProxyModel(QObject* parent) : | ||||
|     QSortFilterProxyModel(parent), | ||||
|     showAll_(false), | ||||
|     hiddenItemsRestored_(false) { | ||||
| } | ||||
| 
 | ||||
| PlacesProxyModel::~PlacesProxyModel() { | ||||
| } | ||||
| 
 | ||||
| void PlacesProxyModel::restoreHiddenItems(const QSet<QString>& items) { | ||||
|     // hidden items should be restored only once
 | ||||
|     if(!hiddenItemsRestored_ && !items.isEmpty()) { | ||||
|         hidden_.clear(); | ||||
|         QSet<QString>::const_iterator i = items.constBegin(); | ||||
|         while (i != items.constEnd()) { | ||||
|             if(!(*i).isEmpty()) { | ||||
|                 hidden_ << *i; | ||||
|             } | ||||
|             ++i; | ||||
|         } | ||||
|         hiddenItemsRestored_ = true; | ||||
|         invalidateFilter(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PlacesProxyModel::setHidden(const QString& str, bool hide) { | ||||
|     if(hide) { | ||||
|         if(!str.isEmpty()) { | ||||
|             hidden_ << str; | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         hidden_.remove(str); | ||||
|     } | ||||
|     invalidateFilter(); | ||||
| } | ||||
| 
 | ||||
| void PlacesProxyModel::showAll(bool show) { | ||||
|     showAll_ = show; | ||||
|     invalidateFilter(); | ||||
| } | ||||
| 
 | ||||
| bool PlacesProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { | ||||
|     if(showAll_ || hidden_.isEmpty()) { | ||||
|         return true; | ||||
|     } | ||||
|     if(PlacesModel* srcModel = static_cast<PlacesModel*>(sourceModel())) { | ||||
|         QModelIndex index = srcModel->index(source_row, 0, source_parent); | ||||
|         if(PlacesModelItem* item = static_cast<PlacesModelItem*>(srcModel->itemFromIndex(index))) { | ||||
|             if(item->type() == PlacesModelItem::Places) { | ||||
|                 if(auto path = item->path()) { | ||||
|                     if(hidden_.contains(path.toString().get())) { | ||||
|                         return false; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else if(item->type() == PlacesModelItem::Volume) { | ||||
|                 CStrPtr uuid{g_volume_get_uuid(static_cast<PlacesModelVolumeItem*>(item)->volume())}; | ||||
|                 if(uuid && hidden_.contains(uuid.get())) { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|             // show a root items only if, at least, one of its children is shown
 | ||||
|             else if((source_row == 0 || source_row == 1) && !source_parent.isValid()) { | ||||
|                 QModelIndex indx = index.child(0, 0); | ||||
|                 while(PlacesModelItem* childItem = static_cast<PlacesModelItem*>(srcModel->itemFromIndex(indx))) { | ||||
|                     if(childItem->type() == PlacesModelItem::Places) { | ||||
|                         if(auto path = childItem->path()) { | ||||
|                             if(!hidden_.contains(path.toString().get())) { | ||||
|                                 return true; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     else if(childItem->type() == PlacesModelItem::Volume) { | ||||
|                         CStrPtr uuid{g_volume_get_uuid(static_cast<PlacesModelVolumeItem*>(childItem)->volume())}; | ||||
|                         if(uuid == nullptr || !hidden_.contains(uuid.get())) { | ||||
|                             return true; | ||||
|                         } | ||||
|                     } | ||||
|                     else { | ||||
|                         return true; | ||||
|                     } | ||||
|                     indx = indx.sibling(indx.row() + 1, 0); | ||||
|                 } | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| PlacesView::PlacesView(QWidget* parent): | ||||
|     QTreeView(parent) { | ||||
|     setRootIsDecorated(false); | ||||
|     setHeaderHidden(true); | ||||
|     setIndentation(12); | ||||
| 
 | ||||
|     /* merge with the surroundings */ | ||||
|     setFrameShape(QFrame::NoFrame); | ||||
|     QPalette p = palette(); | ||||
|     p.setColor(QPalette::Base, QColor(Qt::transparent)); | ||||
|     setPalette(p); | ||||
|     viewport()->setAutoFillBackground(false); | ||||
| 
 | ||||
|     connect(this, &QTreeView::clicked, this, &PlacesView::onClicked); | ||||
|     connect(this, &QTreeView::pressed, this, &PlacesView::onPressed); | ||||
| 
 | ||||
| @ -49,7 +149,27 @@ PlacesView::PlacesView(QWidget* parent): | ||||
|     setItemDelegateForColumn(0, delegate); | ||||
| 
 | ||||
|     model_ = PlacesModel::globalInstance(); | ||||
|     setModel(model_.get()); | ||||
|     if(!proxyModel_) { | ||||
|         proxyModel_ = std::make_shared<PlacesProxyModel>(); | ||||
|     } | ||||
|     if(!proxyModel_->sourceModel()) { // all places-views may have been closed
 | ||||
|         proxyModel_->setSourceModel(model_.get()); | ||||
|     } | ||||
|     setModel(proxyModel_.get()); | ||||
| 
 | ||||
|     // these 2 connections are needed to update filtering
 | ||||
|     connect(model_.get(), &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex&, int, int) { | ||||
|         proxyModel_->setHidden(QString()); // just invalidates filter
 | ||||
|         expandAll(); | ||||
|         // for some reason (a Qt bug?), spanning is reset
 | ||||
|         setFirstColumnSpanned(0, QModelIndex(), true); | ||||
|         setFirstColumnSpanned(1, QModelIndex(), true); | ||||
|         setFirstColumnSpanned(2, QModelIndex(), true); | ||||
| 
 | ||||
|     }); | ||||
|     connect(model_.get(), &QAbstractItemModel::rowsRemoved, this, [this](const QModelIndex&, int, int) { | ||||
|         proxyModel_->setHidden(QString()); | ||||
|     }); | ||||
| 
 | ||||
|     QHeaderView* headerView = header(); | ||||
|     headerView->setSectionResizeMode(0, QHeaderView::Stretch); | ||||
| @ -84,7 +204,7 @@ void PlacesView::activateRow(int type, const QModelIndex& index) { | ||||
|     if(!index.parent().isValid()) { // ignore root items
 | ||||
|         return; | ||||
|     } | ||||
|     PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(index)); | ||||
|     PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(proxyModel_->mapToSource(index))); | ||||
|     if(item) { | ||||
|         auto path = item->path(); | ||||
|         if(!path) { | ||||
| @ -155,10 +275,10 @@ void PlacesView::onClicked(const QModelIndex& index) { | ||||
|         activateRow(0, index); | ||||
|     } | ||||
|     else if(index.column() == 1) { // column 1 contains eject buttons of the mounted devices
 | ||||
|         if(index.parent() == model_->devicesRoot->index()) { // this is a mounted device
 | ||||
|         if(index.parent() == proxyModel_->mapFromSource(model_->devicesRoot->index())) { // this is a mounted device
 | ||||
|             // the eject button is clicked
 | ||||
|             QModelIndex itemIndex = index.sibling(index.row(), 0); // the real item is at column 0
 | ||||
|             PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(itemIndex)); | ||||
|             PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(proxyModel_->mapToSource(itemIndex))); | ||||
|             if(item) { | ||||
|                 // eject the volume or the mount
 | ||||
|                 onEjectButtonClicked(item); | ||||
| @ -176,7 +296,7 @@ void PlacesView::setCurrentPath(Fm::FilePath path) { | ||||
|         // TODO: search for item with the path in model_ and select it.
 | ||||
|         PlacesModelItem* item = model_->itemFromPath(currentPath_); | ||||
|         if(item) { | ||||
|             selectionModel()->select(item->index(), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); | ||||
|             selectionModel()->select(proxyModel_->mapFromSource(item->index()), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); | ||||
|         } | ||||
|         else { | ||||
|             clearSelection(); | ||||
| @ -252,7 +372,7 @@ void PlacesView::onDeleteBookmark() { | ||||
| // virtual
 | ||||
| void PlacesView::commitData(QWidget* editor) { | ||||
|     QTreeView::commitData(editor); | ||||
|     PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(currentIndex())); | ||||
|     PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(proxyModel_->mapToSource(currentIndex()))); | ||||
|     auto bookmarkItem = item->bookmark(); | ||||
|     // rename bookmark
 | ||||
|     Fm::Bookmarks::globalInstance()->rename(bookmarkItem, item->text()); | ||||
| @ -287,8 +407,8 @@ void PlacesView::onRenameBookmark() { | ||||
|     } | ||||
|     PlacesModelBookmarkItem* item = static_cast<PlacesModelBookmarkItem*>(model_->itemFromIndex(action->index())); | ||||
|     setFocus(); | ||||
|     setCurrentIndex(item->index()); | ||||
|     edit(item->index()); | ||||
|     setCurrentIndex(proxyModel_->mapFromSource(item->index())); | ||||
|     edit(proxyModel_->mapFromSource(item->index())); | ||||
| } | ||||
| 
 | ||||
| void PlacesView::onMountVolume() { | ||||
| @ -338,21 +458,22 @@ void PlacesView::onEjectVolume() { | ||||
| 
 | ||||
| void PlacesView::contextMenuEvent(QContextMenuEvent* event) { | ||||
|     QModelIndex index = indexAt(event->pos()); | ||||
|     if(index.isValid() && index.parent().isValid()) { | ||||
|     if(index.isValid()) { | ||||
|         if(index.column() != 0) { // the real item is at column 0
 | ||||
|             index = index.sibling(index.row(), 0); | ||||
|         } | ||||
| 
 | ||||
|         // Do not take the ownership of the menu since
 | ||||
|         // it will be deleted with deleteLater() upon hidden.
 | ||||
|         // This is possibly related to #145 - https://github.com/lxde/pcmanfm-qt/issues/145
 | ||||
|         // This is possibly related to #145 - https://github.com/lxqt/pcmanfm-qt/issues/145
 | ||||
|         QMenu* menu = new QMenu(); | ||||
|         QAction* action; | ||||
|         PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(index)); | ||||
|         QAction* action = nullptr; | ||||
|         PlacesModelItem* item = static_cast<PlacesModelItem*>(model_->itemFromIndex(proxyModel_->mapToSource(index))); | ||||
| 
 | ||||
|         if(item->type() != PlacesModelItem::Mount | ||||
|                 && (item->type() != PlacesModelItem::Volume | ||||
|                     || static_cast<PlacesModelVolumeItem*>(item)->isMounted())) { | ||||
|         if(index.parent().isValid() | ||||
|            && item->type() != PlacesModelItem::Mount | ||||
|            && (item->type() != PlacesModelItem::Volume | ||||
|                || static_cast<PlacesModelVolumeItem*>(item)->isMounted())) { | ||||
|             action = new PlacesModel::ItemAction(item->index(), tr("Open in New Tab"), menu); | ||||
|             connect(action, &QAction::triggered, this, &PlacesView::onOpenNewTab); | ||||
|             menu->addAction(action); | ||||
| @ -364,11 +485,40 @@ void PlacesView::contextMenuEvent(QContextMenuEvent* event) { | ||||
|         switch(item->type()) { | ||||
|         case PlacesModelItem::Places: { | ||||
|             auto path = item->path(); | ||||
|             auto path_str = path.toString(); | ||||
|             // FIXME: inefficient
 | ||||
|             if(path && strcmp(path_str.get(), "trash:///") == 0) { | ||||
|                 action = new PlacesModel::ItemAction(item->index(), tr("Empty Trash"), menu); | ||||
|                 connect(action, &QAction::triggered, this, &PlacesView::onEmptyTrash); | ||||
|             if(path) { | ||||
|                 auto path_str = path.toString(); | ||||
|                 if(strcmp(path_str.get(), "trash:///") == 0) { | ||||
|                     action = new PlacesModel::ItemAction(item->index(), tr("Empty Trash"), menu); | ||||
|                     auto icn = item->icon(); | ||||
|                     if(icn && icn->qicon().name() == QLatin1String("user-trash")) { // surely an empty trash
 | ||||
|                         action->setEnabled(false); | ||||
|                     } | ||||
|                     else { | ||||
|                         connect(action, &QAction::triggered, this, &PlacesView::onEmptyTrash); | ||||
|                     } | ||||
|                     // add the "Empty Trash" item on the top
 | ||||
|                     QList<QAction*> actions = menu->actions(); | ||||
|                     if(!actions.isEmpty()) { | ||||
|                         menu->insertAction(actions.at(0), action); | ||||
|                         menu->insertSeparator(actions.at(0)); | ||||
|                     } | ||||
|                     else { // impossible
 | ||||
|                         menu->addAction(action); | ||||
|                     } | ||||
|                 } | ||||
|                 // add a "Hide" action to the end
 | ||||
|                 menu->addSeparator(); | ||||
|                 action = new PlacesModel::ItemAction(item->index(), tr("Hide"), menu); | ||||
|                 QString pathStr(path_str.get()); | ||||
|                 action->setCheckable(true); | ||||
|                 if(proxyModel_->isShowingAll()) { | ||||
|                     action->setChecked(proxyModel_->isHidden(pathStr)); | ||||
|                 } | ||||
|                 connect(action, &QAction::triggered, [this, pathStr](bool checked) { | ||||
|                     proxyModel_->setHidden(pathStr, checked); | ||||
|                     Q_EMIT hiddenItemSet(pathStr, checked); | ||||
|                 }); | ||||
|                 menu->addAction(action); | ||||
|             } | ||||
|             break; | ||||
| @ -411,6 +561,22 @@ void PlacesView::contextMenuEvent(QContextMenuEvent* event) { | ||||
|                 connect(action, &QAction::triggered, this, &PlacesView::onEjectVolume); | ||||
|                 menu->addAction(action); | ||||
|             } | ||||
|             // add a "Hide" action to the end
 | ||||
|             CStrPtr uuid{g_volume_get_uuid(static_cast<PlacesModelVolumeItem*>(item)->volume())}; | ||||
|             if(uuid) { | ||||
|                 QString str = uuid.get(); | ||||
|                 menu->addSeparator(); | ||||
|                 action = new PlacesModel::ItemAction(item->index(), tr("Hide"), menu); | ||||
|                 action->setCheckable(true); | ||||
|                 if(proxyModel_->isShowingAll()) { | ||||
|                     action->setChecked(proxyModel_->isHidden(str)); | ||||
|                 } | ||||
|                 connect(action, &QAction::triggered, [this, str](bool checked) { | ||||
|                     proxyModel_->setHidden(str, checked); | ||||
|                     Q_EMIT hiddenItemSet(str, checked); | ||||
|                 }); | ||||
|                 menu->addAction(action); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|         case PlacesModelItem::Mount: { | ||||
| @ -420,6 +586,21 @@ void PlacesView::contextMenuEvent(QContextMenuEvent* event) { | ||||
|             break; | ||||
|         } | ||||
|         } | ||||
| 
 | ||||
|         // also add an acton for showing all hidden items
 | ||||
|         if(proxyModel_->hasHidden()) { | ||||
|             if(item->type() == PlacesModelItem::Bookmark) { | ||||
|                 menu->addSeparator(); | ||||
|             } | ||||
|             action = new PlacesModel::ItemAction(item->index(), tr("Show All Entries"), menu); | ||||
|             action->setCheckable(true); | ||||
|             action->setChecked(proxyModel_->isShowingAll()); | ||||
|             connect(action, &QAction::triggered, [this](bool checked) { | ||||
|                 showAll(checked); | ||||
|             }); | ||||
|             menu->addAction(action); | ||||
|         } | ||||
| 
 | ||||
|         if(menu->actions().size()) { | ||||
|             menu->popup(mapToGlobal(event->pos())); | ||||
|             connect(menu, &QMenu::aboutToHide, menu, &QMenu::deleteLater); | ||||
| @ -430,5 +611,19 @@ void PlacesView::contextMenuEvent(QContextMenuEvent* event) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PlacesView::restoreHiddenItems(const QSet<QString>& items) { | ||||
|     proxyModel_->restoreHiddenItems(items); | ||||
| } | ||||
| 
 | ||||
| void PlacesView::showAll(bool show) { | ||||
|     proxyModel_->showAll(show); | ||||
|     if(show) { | ||||
|         expandAll(); | ||||
|         // for some reason (a Qt bug?), spanning is reset
 | ||||
|         setFirstColumnSpanned(0, QModelIndex(), true); | ||||
|         setFirstColumnSpanned(1, QModelIndex(), true); | ||||
|         setFirstColumnSpanned(2, QModelIndex(), true); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| } // namespace Fm
 | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user