Cherry-picking upstream release 0.14.0.

* Bumped Standards to 4.3.0, no changes needed
* Dropped d/compat, use debhelper-compat = 12, no changes needed
* Fixed years in d/copyright
* Bumped minimum version libfm-qt-dev (>= 0.14.0~)
* Bumped minimum version lxqt-build-tools (>= 0.6.0~)
* Depend now on libfm-qt6 (>= 0.14.0~)
* Removed obsolete PULL_TRANSLATIONS= OFF from dh_auto_configure
* Added Build-Depends-Package field to symbols
* Added l10n-package, moved from lxqt-l10n
* Added d/upstream/metadata
ubuntu/disco debian/0.14.0-1
Alf Gaida 6 years ago
parent 1e04a14cf3
commit 2e4ab0a73b

@ -1,3 +1,26 @@
pcmanfm-qt-0.14.0 / 2019-01-25
==============================
* Removed the use of libfm C APIs.
* An option for showing full names (instead of display names).
* Prefer theme icons for view actions.
* Shadow hidden icons optionally.
* Fixed DND and drop indicator on desktop.
* Fixed closing tab on ejecting/unmounting.
* Fixed a rare crash on unmounting.
* Fixed filtering for detailed list mode
* Fixed status-bar message when some selected files were filtered out.
* Restart warning bar for Preferences.
* A (warning) bar for the root instance.
* Transient filter-bar by default.
* Added split view.
* Delete trashed files with Delete key.
* Select the first row of Preferences by default.
* Optional Desktop shortcuts (Trash, Home, Computer, and Network). The Trash shortcut is interactive.
* A Tool menu item (and shortcut) to copy the full path of the selected file.
* Really use the terminal emulator chosen by user when launching desktop files.
* Preserve relative positions of Desktop items with DND.
* Dropped QDesktopWidget, and used QScreen instead.
pcmanfm-qt-0.13.0 / 2018-05-21 pcmanfm-qt-0.13.0 / 2018-05-21
============================== ==============================
@ -50,8 +73,8 @@ pcmanfm-qt-0.13.0 / 2018-05-21
* Tab DND * Tab DND
* View tool-buttons * View tool-buttons
0.12.0 / 2017-10-21 pcmanfm-qt-0.12.0 / 2017-10-21
=================== ==============================
* Release 0.12.0: Update changelog * Release 0.12.0: Update changelog
* Set Version * Set Version
@ -136,8 +159,8 @@ pcmanfm-qt-0.13.0 / 2018-05-21
* Use const iterators * Use const iterators
* Checks bookmarks iterators validity (#444) * Checks bookmarks iterators validity (#444)
0.11.3 / 2017-01-14 pcmanfm-qt-0.11.3 / 2017-01-14
=================== ==============================
* Release 0.11.3: Update changelog * Release 0.11.3: Update changelog
* remove 0.11.3 changelog entries * remove 0.11.3 changelog entries
@ -146,8 +169,8 @@ pcmanfm-qt-0.13.0 / 2018-05-21
* Add a workaround for the Qt5 bug which causes broken wallpaper background. * Add a workaround for the Qt5 bug which causes broken wallpaper background.
* Update AUTHORS * Update AUTHORS
0.11.2 / 2016-12-21 pcmanfm-qt-0.11.2 / 2016-12-21
=================== ==============================
* Release 0.11.2: Update changelog * Release 0.11.2: Update changelog
* Use static_cast instead of the C style cast * Use static_cast instead of the C style cast
@ -179,8 +202,8 @@ pcmanfm-qt-0.13.0 / 2018-05-21
* Add Catalan translations * Add Catalan translations
* Added Brazilian Portuguese Translation (pt_BR) * Added Brazilian Portuguese Translation (pt_BR)
0.11.1 / 2016-09-24 pcmanfm-qt-0.11.1 / 2016-09-24
=================== ==============================
* Release 0.11.1: Add changelog * Release 0.11.1: Add changelog
* Bump version to 0.11.1 (#399) * Bump version to 0.11.1 (#399)
@ -208,8 +231,8 @@ pcmanfm-qt-0.13.0 / 2018-05-21
* Add setting for Desktop con size * Add setting for Desktop con size
* Fix a few compiler warnings * Fix a few compiler warnings
0.11.0 / 2016-03-13 pcmanfm-qt-0.11.0 / 2016-03-13
=================== ==============================
* Switch automatically to newly opened tabs * Switch automatically to newly opened tabs
* Fixes libfm-qt dependency contradiction on README.md * Fixes libfm-qt dependency contradiction on README.md
@ -247,8 +270,8 @@ pcmanfm-qt-0.13.0 / 2018-05-21
* Add config values for customizing "places" (not implemented yet). * Add config values for customizing "places" (not implemented yet).
* Updated Russian translation Removed ru_RU files * Updated Russian translation Removed ru_RU files
0.10.1 / 2015-12-05 pcmanfm-qt-0.10.1 / 2015-12-05
=================== ==============================
* hide 'Create New...' menu for files * hide 'Create New...' menu for files
* Russian translation update * Russian translation update

@ -1,22 +1,28 @@
cmake_minimum_required(VERSION 3.0.2) cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
# CMP0000: Call the cmake_minimum_required() command at the beginning of the top-level
# CMakeLists.txt file even before calling the project() command.
# The cmake_minimum_required(VERSION) command implicitly invokes the cmake_policy(VERSION)
# command to specify that the current project code is written for the given range of CMake
# versions.
project(pcmanfm-qt) project(pcmanfm-qt)
# PcmanFm-Qt Version # PcmanFm-Qt Version
set(PCMANFM_QT_VERSION_MAJOR 0) set(PCMANFM_QT_VERSION_MAJOR 0)
set(PCMANFM_QT_VERSION_MINOR 13) set(PCMANFM_QT_VERSION_MINOR 14)
set(PCMANFM_QT_VERSION_PATCH 0) set(PCMANFM_QT_VERSION_PATCH 0)
set(PCMANFM_QT_VERSION ${PCMANFM_QT_VERSION_MAJOR}.${PCMANFM_QT_VERSION_MINOR}.${PCMANFM_QT_VERSION_PATCH}) set(PCMANFM_QT_VERSION ${PCMANFM_QT_VERSION_MAJOR}.${PCMANFM_QT_VERSION_MINOR}.${PCMANFM_QT_VERSION_PATCH})
# Minimum versions
set(LIBFMQT_MINIMUM_VERSION "0.14.0")
set(LXQTBT_MINIMUM_VERSION "0.6.0")
set(QT_MINIMUM_VERSION "5.7.1") set(QT_MINIMUM_VERSION "5.7.1")
set(LXQTBT_MINIMUM_VERSION "0.5.0")
set(LIBFMQT_MINIMUM_VERSION "5.0.0")
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
find_package(Qt5Widgets ${QT_MINIMUM_VERSION} REQUIRED)
find_package(Qt5DBus ${QT_MINIMUM_VERSION} REQUIRED) find_package(Qt5DBus ${QT_MINIMUM_VERSION} REQUIRED)
find_package(Qt5LinguistTools ${QT_MINIMUM_VERSION} REQUIRED) find_package(Qt5LinguistTools ${QT_MINIMUM_VERSION} REQUIRED)
find_package(Qt5Widgets ${QT_MINIMUM_VERSION} REQUIRED)
find_package(Qt5X11Extras ${QT_MINIMUM_VERSION} REQUIRED) find_package(Qt5X11Extras ${QT_MINIMUM_VERSION} REQUIRED)
find_package(fm-qt ${LIBFMQT_MINIMUM_VERSION} REQUIRED) find_package(fm-qt ${LIBFMQT_MINIMUM_VERSION} REQUIRED)
find_package(lxqt-build-tools ${LXQTBT_MINIMUM_VERSION} REQUIRED) find_package(lxqt-build-tools ${LXQTBT_MINIMUM_VERSION} REQUIRED)
@ -64,6 +70,5 @@ if(BUILD_DOCUMENTATION)
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/docs" DESTINATION "${CMAKE_INSTALL_DOCDIR}") install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/docs" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
endif() endif()
# merged from lxqt-common
add_subdirectory(autostart) add_subdirectory(autostart)
add_subdirectory(config) add_subdirectory(config)

@ -60,3 +60,10 @@ All switches (command line options) mentioned above are explained in detail in
## Development ## Development
Issues should go to the tracker of PCManFM-Qt at https://github.com/lxqt/pcmanfm-qt/issues. Issues should go to the tracker of PCManFM-Qt at https://github.com/lxqt/pcmanfm-qt/issues.
### Translation (Weblate)
<a href="https://weblate.lxqt.org/projects/lxqt/pcmanfm-qt/">
<img src="https://weblate.lxqt.org/widgets/lxqt/-/pcmanfm-qt/multi-auto.svg" alt="Translation status" />
</a>

@ -1,5 +1,3 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
file(GLOB DESKTOP_FILES_IN *.desktop.in) file(GLOB DESKTOP_FILES_IN *.desktop.in)
# Translations ********************************** # Translations **********************************

@ -1,2 +0,0 @@
# Translations
Name[cs_CZ]=Plocha

16
debian/changelog vendored

@ -1,3 +1,19 @@
pcmanfm-qt (0.14.0-1) unstable; urgency=medium
* Cherry-picking upstream release 0.14.0.
* Bumped Standards to 4.3.0, no changes needed
* Dropped d/compat, use debhelper-compat = 12, no changes needed
* Fixed years in d/copyright
* Bumped minimum version libfm-qt-dev (>= 0.14.0~)
* Bumped minimum version lxqt-build-tools (>= 0.6.0~)
* Depend now on libfm-qt6 (>= 0.14.0~)
* Removed obsolete PULL_TRANSLATIONS= OFF from dh_auto_configure
* Added Build-Depends-Package field to symbols
* Added l10n-package, moved from lxqt-l10n
* Added d/upstream/metadata
-- Alf Gaida <agaida@siduction.org> Sun, 27 Jan 2019 19:40:51 +0100
pcmanfm-qt (0.13.0-2) unstable; urgency=medium pcmanfm-qt (0.13.0-2) unstable; urgency=medium
* Switch to unstable * Switch to unstable

1
debian/compat vendored

@ -1 +0,0 @@
11

22
debian/control vendored

@ -7,16 +7,16 @@ Uploaders: Alf Gaida <agaida@siduction.org>,
Yuan CHAO <yuanchao@gmail.com> Yuan CHAO <yuanchao@gmail.com>
Section: x11 Section: x11
Priority: optional Priority: optional
Build-Depends: debhelper (>= 11~), Build-Depends: debhelper-compat (= 12),
libexif-dev, libexif-dev,
libfm-qt-dev (>= 0.13.1~), libfm-qt-dev (>= 0.14.0~),
libkf5windowsystem-dev, libkf5windowsystem-dev,
libmenu-cache-dev, libmenu-cache-dev,
libqt5svg5-dev, libqt5svg5-dev,
libqt5x11extras5-dev, libqt5x11extras5-dev,
libx11-dev, libx11-dev,
lxqt-build-tools (>= 0.5.0~), lxqt-build-tools (>= 0.6.0~),
Standards-Version: 4.1.5 Standards-Version: 4.3.0
Vcs-Browser: https://salsa.debian.org/lxqt-team/pcmanfm-qt Vcs-Browser: https://salsa.debian.org/lxqt-team/pcmanfm-qt
Vcs-Git: https://salsa.debian.org/lxqt-team/pcmanfm-qt.git Vcs-Git: https://salsa.debian.org/lxqt-team/pcmanfm-qt.git
Homepage: https://github.com/lxqt/pcmanfm-qt Homepage: https://github.com/lxqt/pcmanfm-qt
@ -27,8 +27,7 @@ Depends: ${misc:Depends},
${shlibs:Depends}, ${shlibs:Depends},
default-dbus-session-bus | dbus-session-bus | dbus-x11, default-dbus-session-bus | dbus-session-bus | dbus-x11,
desktop-file-utils, desktop-file-utils,
libfm-modules, libfm-qt6 (>= 0.14.0~),
libfm-qt5 (>= 0.13.1~),
lxqt-sudo lxqt-sudo
Recommends: eject, Recommends: eject,
ffmpegthumbnailer, ffmpegthumbnailer,
@ -47,3 +46,14 @@ Description: extremely fast and lightweight file and desktop icon manager
. .
Libfm-Qt is a companion library providing components to build desktop file Libfm-Qt is a companion library providing components to build desktop file
managers. managers.
Package: pcmanfm-qt-l10n
Architecture: all
Multi-Arch: foreign
Section: localization
Depends: ${misc:Depends},
qttranslations5-l10n
Breaks: pcmanfm-qt (<< 0.11.1)
Replaces: pcmanfm-qt (<< 0.11.1)
Description: Language package for pcmanfm-qt
This package contains the l10n files needed by the pcmanfm-qt.

4
debian/copyright vendored

@ -3,7 +3,7 @@ Upstream-Name: pcmanfm-qt
Source: https://github.com/lxqt/pcmanfm-qt Source: https://github.com/lxqt/pcmanfm-qt
Files: * Files: *
Copyright: 2013-2018 LXQt team Copyright: 2013-2019 LXQt team
2013-2018 Hong Jen Yee (PCMan) <pcman.tw@gmail.com> 2013-2018 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
2012-2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua> 2012-2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
2014 Kuzma Shapran <kuzma.shapran@gmail.com> 2014 Kuzma Shapran <kuzma.shapran@gmail.com>
@ -12,7 +12,7 @@ License: GPL-2.0+
Files: debian/* Files: debian/*
Copyright: 2014-2015 Wen Liao <wen.cf83@gmail.com> Copyright: 2014-2015 Wen Liao <wen.cf83@gmail.com>
2014-2016 ChangZhuo Chen (陳昌倬) <czchen@debian.org> 2014-2016 ChangZhuo Chen (陳昌倬) <czchen@debian.org>
2013-2018 Alf Gaida <agaida@siduction.org> 2013-2019 Alf Gaida <agaida@siduction.org>
2015 Andrew Lee (李健秋) <ajqlee@debian.org> 2015 Andrew Lee (李健秋) <ajqlee@debian.org>
License: GPL-2.0+ License: GPL-2.0+

@ -0,0 +1 @@
usr/share/pcmanfm-qt/translations/

@ -0,0 +1,7 @@
etc/xdg/autostart/lxqt-desktop.desktop
usr/share/applications/pcmanfm-qt.desktop
usr/share/applications/pcmanfm-qt-desktop-pref.desktop
usr/share/man/man1/pcmanfm-qt.1
usr/share/pcmanfm-qt/lxqt/settings.conf
usr/bin/pcmanfm-qt

4
debian/rules vendored

@ -8,8 +8,10 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all
%: %:
dh ${@} --buildsystem cmake dh ${@} --buildsystem cmake
override_dh_missing:
dh_missing --fail-missing
override_dh_auto_configure: override_dh_auto_configure:
dh_auto_configure -- \ dh_auto_configure -- \
-DPULL_TRANSLATIONS=OFF\
-DUPDATE_TRANSLATIONS=OFF \ -DUPDATE_TRANSLATIONS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_BUILD_TYPE=RelWithDebInfo

@ -0,0 +1,7 @@
Name: pcmanfm-qt
Bug-Database: https://github.com/lxqt/pcmanfm-qt/issues
Bug-Submit: https://github.com/lxqt/pcmanfm-qt/issues/new
Changelog: https://github.com/lxqt/pcmanfm-qt/blob/master/CHANGELOG
Repository: https://github.com/lxqt/pcmanfm-qt
Repository-Browser: https://github.com/lxqt/pcmanfm-qt

@ -46,10 +46,6 @@ lxqt_translate_ts(QM_FILES
UPDATE_TRANSLATIONS ${UPDATE_TRANSLATIONS} UPDATE_TRANSLATIONS ${UPDATE_TRANSLATIONS}
SOURCES ${pcmanfm_SRCS} ${pcmanfm_UIS} SOURCES ${pcmanfm_SRCS} ${pcmanfm_UIS}
INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/translations" INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/translations"
PULL_TRANSLATIONS ${PULL_TRANSLATIONS}
CLEAN_TRANSLATIONS ${CLEAN_TRANSLATIONS}
TRANSLATIONS_REPO ${TRANSLATIONS_REPO}
TRANSLATIONS_REFSPEC ${TRANSLATIONS_REFSPEC}
) )
# translate desktop entry files for pcmanfm-qt and desktop preferences # translate desktop entry files for pcmanfm-qt and desktop preferences
@ -72,7 +68,6 @@ target_compile_definitions(pcmanfm-qt
PCMANFM_DATA_DIR="${CMAKE_INSTALL_PREFIX}/share/pcmanfm-qt" PCMANFM_DATA_DIR="${CMAKE_INSTALL_PREFIX}/share/pcmanfm-qt"
PCMANFM_QT_VERSION="${PCMANFM_QT_VERSION}" PCMANFM_QT_VERSION="${PCMANFM_QT_VERSION}"
LIBFM_DATA_DIR="${PKG_FM_PREFIX}/share/libfm" LIBFM_DATA_DIR="${PKG_FM_PREFIX}/share/libfm"
QT_NO_FOREACH
) )
target_include_directories(pcmanfm-qt target_include_directories(pcmanfm-qt

@ -24,7 +24,6 @@
#include <QDBusConnection> #include <QDBusConnection>
#include <QDBusInterface> #include <QDBusInterface>
#include <QDir> #include <QDir>
#include <QDesktopWidget>
#include <QVector> #include <QVector>
#include <QLocale> #include <QLocale>
#include <QLibraryInfo> #include <QLibraryInfo>
@ -43,6 +42,8 @@
#include <libfm-qt/mountoperation.h> #include <libfm-qt/mountoperation.h>
#include <libfm-qt/filesearchdialog.h> #include <libfm-qt/filesearchdialog.h>
#include <libfm-qt/core/terminal.h> #include <libfm-qt/core/terminal.h>
#include <libfm-qt/core/bookmarks.h>
#include <libfm-qt/core/folderconfig.h>
#include "applicationadaptor.h" #include "applicationadaptor.h"
#include "preferencesdialog.h" #include "preferencesdialog.h"
@ -93,7 +94,7 @@ Application::Application(int& argc, char** argv):
// we successfully registered the service // we successfully registered the service
isPrimaryInstance = true; isPrimaryInstance = true;
setStyle(new ProxyStyle()); setStyle(new ProxyStyle());
desktop()->installEventFilter(this); //desktop()->installEventFilter(this);
new ApplicationAdaptor(this); new ApplicationAdaptor(this);
dbus.registerObject("/Application", this); dbus.registerObject("/Application", this);
@ -101,14 +102,6 @@ Application::Application(int& argc, char** argv):
connect(this, &Application::aboutToQuit, this, &Application::onAboutToQuit); connect(this, &Application::aboutToQuit, this, &Application::onAboutToQuit);
// aboutToQuit() is not signalled on SIGTERM, install signal handler // aboutToQuit() is not signalled on SIGTERM, install signal handler
installSigtermHandler(); installSigtermHandler();
settings_.load(profileName_);
// decrease the cache size to reduce memory usage
QPixmapCache::setCacheLimit(2048);
if(settings_.useFallbackIconTheme()) {
QIcon::setThemeName(settings_.fallbackIconThemeName());
}
// Check if LXQt Session is running. LXQt has it's own Desktop Folder // Check if LXQt Session is running. LXQt has it's own Desktop Folder
// editor. We just hide our editor when LXQt is running. // editor. We just hide our editor when LXQt is running.
@ -133,7 +126,7 @@ Application::Application(int& argc, char** argv):
} }
Application::~Application() { Application::~Application() {
desktop()->removeEventFilter(this); //desktop()->removeEventFilter(this);
if(volumeMonitor_) { if(volumeMonitor_) {
g_signal_handlers_disconnect_by_func(volumeMonitor_, gpointer(onVolumeAdded), this); g_signal_handlers_disconnect_by_func(volumeMonitor_, gpointer(onVolumeAdded), this);
@ -212,9 +205,20 @@ bool Application::parseCommandLineArgs() {
profileName_ = parser.value(profileOption); profileName_ = parser.value(profileOption);
} }
// load settings // load app config
settings_.load(profileName_); settings_.load(profileName_);
// init per-folder config
QString perFolderConfigFile = settings_.profileDir(profileName_) + "/dir-settings.conf";
Fm::FolderConfig::init(perFolderConfigFile.toLocal8Bit().constData());
// decrease the cache size to reduce memory usage
QPixmapCache::setCacheLimit(2048);
if(settings_.useFallbackIconTheme()) {
QIcon::setThemeName(settings_.fallbackIconThemeName());
}
// desktop icon management // desktop icon management
if(parser.isSet(desktopOption)) { if(parser.isSet(desktopOption)) {
desktopManager(true); desktopManager(true);
@ -363,7 +367,7 @@ void Application::onAboutToQuit() {
settings_.save(); settings_.save();
} }
bool Application::eventFilter(QObject* watched, QEvent* event) { /*bool Application::eventFilter(QObject* watched, QEvent* event) {
if(watched == desktop()) { if(watched == desktop()) {
if(event->type() == QEvent::StyleChange || if(event->type() == QEvent::StyleChange ||
event->type() == QEvent::ThemeChange) { event->type() == QEvent::ThemeChange) {
@ -371,7 +375,7 @@ bool Application::eventFilter(QObject* watched, QEvent* event) {
} }
} }
return QObject::eventFilter(watched, event); return QObject::eventFilter(watched, event);
} }*/
void Application::onLastWindowClosed() { void Application::onLastWindowClosed() {
@ -383,29 +387,28 @@ void Application::onSaveStateRequest(QSessionManager& /*manager*/) {
void Application::desktopManager(bool enabled) { void Application::desktopManager(bool enabled) {
// TODO: turn on or turn off desktpo management (desktop icons & wallpaper) // TODO: turn on or turn off desktpo management (desktop icons & wallpaper)
qDebug("desktopManager: %d", enabled); //qDebug("desktopManager: %d", enabled);
QDesktopWidget* desktopWidget = desktop();
if(enabled) { if(enabled) {
if(!enableDesktopManager_) { if(!enableDesktopManager_) {
// installNativeEventFilter(this); // installNativeEventFilter(this);
const auto allScreens = screens(); const auto allScreens = screens();
for(QScreen* screen : allScreens) { for(QScreen* screen : allScreens) {
connect(screen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged); connect(screen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
connect(screen, &QScreen::availableGeometryChanged, this, &Application::onAvailableGeometryChanged);
connect(screen, &QObject::destroyed, this, &Application::onScreenDestroyed); connect(screen, &QObject::destroyed, this, &Application::onScreenDestroyed);
} }
connect(this, &QApplication::screenAdded, this, &Application::onScreenAdded); connect(this, &QApplication::screenAdded, this, &Application::onScreenAdded);
connect(desktopWidget, &QDesktopWidget::resized, this, &Application::onScreenResized); connect(this, &QApplication::screenRemoved, this, &Application::onScreenRemoved);
connect(desktopWidget, &QDesktopWidget::screenCountChanged, this, &Application::onScreenCountChanged);
// NOTE: there are two modes // NOTE: there are two modes
// When virtual desktop is used (all screens are combined to form a large virtual desktop), // When virtual desktop is used (all screens are combined to form a large virtual desktop),
// we only create one DesktopWindow. Otherwise, we create one for each screen. // we only create one DesktopWindow. Otherwise, we create one for each screen.
if(desktopWidget->isVirtualDesktop()) { if(primaryScreen() && primaryScreen()->virtualSiblings().size() > 1) {
DesktopWindow* window = createDesktopWindow(-1); DesktopWindow* window = createDesktopWindow(-1);
desktopWindows_.push_back(window); desktopWindows_.push_back(window);
} }
else { else {
int n = desktopWidget->numScreens(); int n = qMax(allScreens.size(), 1);
desktopWindows_.reserve(n); desktopWindows_.reserve(n);
for(int i = 0; i < n; ++i) { for(int i = 0; i < n; ++i) {
DesktopWindow* window = createDesktopWindow(i); DesktopWindow* window = createDesktopWindow(i);
@ -416,8 +419,6 @@ void Application::desktopManager(bool enabled) {
} }
else { else {
if(enableDesktopManager_) { if(enableDesktopManager_) {
disconnect(desktopWidget, &QDesktopWidget::resized, this, &Application::onScreenResized);
disconnect(desktopWidget, &QDesktopWidget::screenCountChanged, this, &Application::onScreenCountChanged);
int n = desktopWindows_.size(); int n = desktopWindows_.size();
for(int i = 0; i < n; ++i) { for(int i = 0; i < n; ++i) {
DesktopWindow* window = desktopWindows_.at(i); DesktopWindow* window = desktopWindows_.at(i);
@ -427,9 +428,11 @@ void Application::desktopManager(bool enabled) {
const auto allScreens = screens(); const auto allScreens = screens();
for(QScreen* screen : allScreens) { for(QScreen* screen : allScreens) {
disconnect(screen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged); disconnect(screen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
disconnect(screen, &QScreen::availableGeometryChanged, this, &Application::onAvailableGeometryChanged);
disconnect(screen, &QObject::destroyed, this, &Application::onScreenDestroyed); disconnect(screen, &QObject::destroyed, this, &Application::onScreenDestroyed);
} }
disconnect(this, &QApplication::screenAdded, this, &Application::onScreenAdded); disconnect(this, &QApplication::screenAdded, this, &Application::onScreenAdded);
disconnect(this, &QApplication::screenRemoved, this, &Application::onScreenRemoved);
// removeNativeEventFilter(this); // removeNativeEventFilter(this);
} }
} }
@ -470,7 +473,7 @@ void Application::onConnectToServerAccepted() {
ConnectServerDialog* dlg = static_cast<ConnectServerDialog*>(sender()); ConnectServerDialog* dlg = static_cast<ConnectServerDialog*>(sender());
QString uri = dlg->uriText(); QString uri = dlg->uriText();
Fm::FilePathList paths; Fm::FilePathList paths;
paths.push_back(Fm::FilePath::fromDisplayName(uri.toUtf8().constData())); paths.push_back(Fm::FilePath::fromUri(uri.toUtf8().constData()));
MainWindow* window = MainWindow::lastActive(); MainWindow* window = MainWindow::lastActive();
Launcher(window).launchPaths(nullptr, paths); Launcher(window).launchPaths(nullptr, paths);
} }
@ -586,93 +589,41 @@ void Application::setWallpaper(QString path, QString modeString) {
// update wallpaper // update wallpaper
if(changed) { if(changed) {
if(enableDesktopManager_) { if(enableDesktopManager_) {
for(DesktopWindow* desktopWindow : qAsConst(desktopWindows_)) { for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
if(!path.isEmpty()) { if(!path.isEmpty()) {
desktopWindow->setWallpaperFile(path); desktopWin->setWallpaperFile(path);
} }
if(mode != settings_.wallpaperMode()) { if(mode != settings_.wallpaperMode()) {
desktopWindow->setWallpaperMode(mode); desktopWin->setWallpaperMode(mode);
} }
desktopWindow->updateWallpaper(); desktopWin->updateWallpaper();
} }
settings_.save(); // save the settings to the config file settings_.save(); // save the settings to the config file
} }
} }
} }
void Application::onScreenResized(int num) {
if(desktop()->isVirtualDesktop()) {
// in virtual desktop mode, we only have one desktop window. that is the first one.
DesktopWindow* window = desktopWindows_.at(0);
window->setGeometry(desktop()->geometry());
}
else {
DesktopWindow* window = desktopWindows_.at(num);
QRect rect = desktop()->screenGeometry(num);
window->setGeometry(rect);
}
}
DesktopWindow* Application::createDesktopWindow(int screenNum) { DesktopWindow* Application::createDesktopWindow(int screenNum) {
DesktopWindow* window = new DesktopWindow(screenNum); DesktopWindow* window = new DesktopWindow(screenNum);
if(screenNum == -1) { // one large virtual desktop only if(screenNum == -1) { // one large virtual desktop only
QRect rect = desktop()->geometry(); QRect rect = primaryScreen()->virtualGeometry();
window->setGeometry(rect); window->setGeometry(rect);
} }
else { else {
QRect rect = desktop()->screenGeometry(screenNum); QRect rect;
const auto allScreens = screens();
if(auto screen = window->getDesktopScreen()) {
rect = screen->geometry();
}
window->setGeometry(rect); window->setGeometry(rect);
} }
window->updateFromSettings(settings_); window->updateFromSettings(settings_);
window->show(); window->show();
return window; return window;
} }
void Application::onScreenCountChanged(int newCount) {
QDesktopWidget* desktopWidget = desktop();
bool oldVirtual = (desktopWindows_.size() == 1 && desktopWindows_.at(0)->screenNum() == -1);
bool isVirtual = desktopWidget->isVirtualDesktop();
if(oldVirtual && isVirtual) {
// if we are using virtual desktop mode previously, and the new mode is sitll virtual
// no further change is needed, only do relayout.
desktopWindows_.at(0)->queueRelayout();
return;
}
// we used non-virtual mode originally, but now we're switched to virtual mode
if(isVirtual) {
newCount = 1; // we only want one desktop window for all screens in virtual mode
}
if(newCount > desktopWindows_.size()) {
// add more desktop windows
for(int i = desktopWindows_.size(); i < newCount; ++i) {
DesktopWindow* desktop = createDesktopWindow(i);
desktopWindows_.push_back(desktop);
}
}
else if(newCount < desktopWindows_.size()) {
// delete excessive desktop windows
for(int i = newCount; i < desktopWindows_.size(); ++i) {
DesktopWindow* desktop = desktopWindows_.at(i);
delete desktop;
}
desktopWindows_.resize(newCount);
}
if(newCount == 1) { // now only 1 screen is in use
DesktopWindow* desktop = desktopWindows_.at(0);
if(isVirtual) {
desktop->setScreenNum(-1);
}
else { // non-virtual mode, and we only have 1 screen
desktop->setScreenNum(0);
}
desktop->updateWallpaper();
}
}
// called when Settings is changed to update UI // called when Settings is changed to update UI
void Application::updateFromSettings() { void Application::updateFromSettings() {
// if(iconTheme.isEmpty()) // if(iconTheme.isEmpty())
@ -696,16 +647,14 @@ void Application::updateFromSettings() {
void Application::updateDesktopsFromSettings(bool changeSlide) { void Application::updateDesktopsFromSettings(bool changeSlide) {
QVector<DesktopWindow*>::iterator it; QVector<DesktopWindow*>::iterator it;
for(it = desktopWindows_.begin(); it != desktopWindows_.end(); ++it) { for(it = desktopWindows_.begin(); it != desktopWindows_.end(); ++it) {
DesktopWindow* desktopWindow = static_cast<DesktopWindow*>(*it); DesktopWindow* desktopWin = static_cast<DesktopWindow*>(*it);
desktopWindow->updateFromSettings(settings_, changeSlide); desktopWin->updateFromSettings(settings_, changeSlide);
} }
} }
void Application::editBookmarks() { void Application::editBookmarks() {
if(!editBookmarksialog_) { if(!editBookmarksialog_) {
FmBookmarks* bookmarks = fm_bookmarks_dup(); editBookmarksialog_ = new Fm::EditBookmarksDialog(Fm::Bookmarks::globalInstance());
editBookmarksialog_ = new Fm::EditBookmarksDialog(bookmarks);
g_object_unref(bookmarks);
} }
editBookmarksialog_.data()->show(); editBookmarksialog_.data()->show();
} }
@ -780,7 +729,58 @@ bool Application::nativeEventFilter(const QByteArray& eventType, void* message,
void Application::onScreenAdded(QScreen* newScreen) { void Application::onScreenAdded(QScreen* newScreen) {
if(enableDesktopManager_) { if(enableDesktopManager_) {
connect(newScreen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged); connect(newScreen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
connect(newScreen, &QScreen::availableGeometryChanged, this, &Application::onAvailableGeometryChanged);
connect(newScreen, &QObject::destroyed, this, &Application::onScreenDestroyed); connect(newScreen, &QObject::destroyed, this, &Application::onScreenDestroyed);
const auto siblings = primaryScreen()->virtualSiblings();
if(siblings.contains(newScreen)) { // the primary screen is changed
if(desktopWindows_.size() == 1) {
desktopWindows_.at(0)->setGeometry(newScreen->virtualGeometry());
if(siblings.size() > 1) { // a virtual desktop is created
desktopWindows_.at(0)->setScreenNum(-1);
}
}
else if(desktopWindows_.isEmpty()) { // for the sake of certainty
DesktopWindow* window = createDesktopWindow(desktopWindows_.size());
desktopWindows_.push_back(window);
}
}
else { // a separate screen is added
DesktopWindow* window = createDesktopWindow(desktopWindows_.size());
desktopWindows_.push_back(window);
}
}
}
void Application::onScreenRemoved(QScreen* oldScreen) {
if(enableDesktopManager_){
disconnect(oldScreen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
disconnect(oldScreen, &QScreen::availableGeometryChanged, this, &Application::onAvailableGeometryChanged);
disconnect(oldScreen, &QObject::destroyed, this, &Application::onScreenDestroyed);
if(desktopWindows_.isEmpty()) {
return;
}
if(desktopWindows_.size() == 1) { // a single desktop is changed
if(primaryScreen() != nullptr) {
desktopWindows_.at(0)->setGeometry(primaryScreen()->virtualGeometry());
if(primaryScreen()->virtualSiblings().size() == 1) {
desktopWindows_.at(0)->setScreenNum(0); // there is no virtual desktop anymore
}
}
else if (screens().isEmpty()) { // for the sake of certainty
desktopWindows_.at(0)->setScreenNum(0);
}
}
else { // a separate desktop is removed
int n = desktopWindows_.size();
for(int i = 0; i < n; ++i) {
DesktopWindow* window = desktopWindows_.at(i);
if(window->getDesktopScreen() == oldScreen) {
desktopWindows_.remove(i);
delete window;
break;
}
}
}
} }
} }
@ -812,9 +812,9 @@ void Application::onScreenDestroyed(QObject* screenObj) {
if(enableDesktopManager_) { if(enableDesktopManager_) {
bool reloadNeeded = false; bool reloadNeeded = false;
// FIXME: add workarounds for Qt5 bug #40681 and #40791 here. // FIXME: add workarounds for Qt5 bug #40681 and #40791 here.
for(DesktopWindow* desktop : qAsConst(desktopWindows_)) { for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
if(desktop->windowHandle()->screen() == screenObj) { if(desktopWin->windowHandle()->screen() == screenObj) {
desktop->destroy(); // destroy the underlying native window desktopWin->destroy(); // destroy the underlying native window
reloadNeeded = true; reloadNeeded = true;
} }
} }
@ -827,31 +827,33 @@ void Application::onScreenDestroyed(QObject* screenObj) {
void Application::reloadDesktopsAsNeeded() { void Application::reloadDesktopsAsNeeded() {
if(enableDesktopManager_) { if(enableDesktopManager_) {
// workarounds for Qt5 bug #40681 and #40791 here. // workarounds for Qt5 bug #40681 and #40791 here.
for(DesktopWindow* desktop : qAsConst(desktopWindows_)) { for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
if(!desktop->windowHandle()) { if(!desktopWin->windowHandle()) {
desktop->create(); // re-create the underlying native window desktopWin->create(); // re-create the underlying native window
desktop->queueRelayout(); desktopWin->queueRelayout();
desktop->show(); desktopWin->show();
} }
} }
} }
} }
// This slot is for Qt 5 onlt, but the stupid Qt moc cannot do conditional compilation
// so we have to define it for Qt 4 as well.
void Application::onVirtualGeometryChanged(const QRect& /*rect*/) { void Application::onVirtualGeometryChanged(const QRect& /*rect*/) {
// NOTE: the following is a workaround for Qt bug 32567. // update desktop geometries
// https://bugreports.qt-project.org/browse/QTBUG-32567 if(enableDesktopManager_) {
// Though the status of the bug report is closed, it's not yet fixed for X11. for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
// In theory, QDesktopWidget should emit "workAreaResized()" signal when the work area auto desktopScreen = desktopWin->getDesktopScreen();
// of any screen is changed, but in fact it does not do it. if(desktopScreen) {
// However, QScreen provided since Qt5 does not have the bug and desktopWin->setGeometry(desktopScreen->virtualGeometry());
// virtualGeometryChanged() is emitted correctly when the workAreas changed. }
// So we use it in Qt5. }
}
}
void Application::onAvailableGeometryChanged(const QRect& /*rect*/) {
// update desktop layouts
if(enableDesktopManager_) { if(enableDesktopManager_) {
// qDebug() << "onVirtualGeometryChanged"; for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
for(DesktopWindow* desktop : qAsConst(desktopWindows_)) { desktopWin->queueRelayout();
desktop->queueRelayout();
} }
} }
} }

@ -102,20 +102,20 @@ protected Q_SLOTS:
void onLastWindowClosed(); void onLastWindowClosed();
void onSaveStateRequest(QSessionManager& manager); void onSaveStateRequest(QSessionManager& manager);
void onScreenResized(int num);
void onScreenCountChanged(int newCount);
void initVolumeManager(); void initVolumeManager();
void onVirtualGeometryChanged(const QRect& rect); void onVirtualGeometryChanged(const QRect& rect);
void onAvailableGeometryChanged(const QRect& rect);
void onScreenDestroyed(QObject* screenObj); void onScreenDestroyed(QObject* screenObj);
void onScreenAdded(QScreen* newScreen); void onScreenAdded(QScreen* newScreen);
void onScreenRemoved(QScreen* oldScreen);
void reloadDesktopsAsNeeded(); void reloadDesktopsAsNeeded();
void onFindFileAccepted(); void onFindFileAccepted();
void onConnectToServerAccepted(); void onConnectToServerAccepted();
protected: protected:
virtual bool eventFilter(QObject* watched, QEvent* event); //virtual bool eventFilter(QObject* watched, QEvent* event);
bool parseCommandLineArgs(); bool parseCommandLineArgs();
DesktopWindow* createDesktopWindow(int screenNum); DesktopWindow* createDesktopWindow(int screenNum);
bool autoMountVolume(GVolume* volume, bool interactive = true); bool autoMountVolume(GVolume* volume, bool interactive = true);

@ -453,6 +453,59 @@ A space is also reserved for 3 lines of text.</string>
<string>Advanced</string> <string>Advanced</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="advancedPageLayout"> <layout class="QVBoxLayout" name="advancedPageLayout">
<item>
<widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>Visible Shortcuts</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QCheckBox" name="homeBox">
<property name="text">
<string>Home</string>
</property>
<property name="icon">
<iconset theme="user-home">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="trashBox">
<property name="text">
<string>Trash</string>
</property>
<property name="icon">
<iconset theme="user-trash">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="computerBox">
<property name="text">
<string>Computer</string>
</property>
<property name="icon">
<iconset theme="computer">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="networkBox">
<property name="text">
<string>Network</string>
</property>
<property name="icon">
<iconset theme="folder-network">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBox_4"> <widget class="QGroupBox" name="groupBox_4">
<property name="title"> <property name="title">

@ -104,6 +104,13 @@ DesktopPreferencesDialog::DesktopPreferencesDialog(QWidget* parent, Qt::WindowFl
ui.backgroundColor->setColor(settings.desktopBgColor()); ui.backgroundColor->setColor(settings.desktopBgColor());
ui.textColor->setColor(settings.desktopFgColor()); ui.textColor->setColor(settings.desktopFgColor());
ui.shadowColor->setColor(settings.desktopShadowColor()); ui.shadowColor->setColor(settings.desktopShadowColor());
const QStringList ds = settings.desktopShortcuts();
ui.homeBox->setChecked(ds.contains(QLatin1String("Home")));
ui.trashBox->setChecked(ds.contains(QLatin1String("Trash")));
ui.computerBox->setChecked(ds.contains(QLatin1String("Computer")));
ui.networkBox->setChecked(ds.contains(QLatin1String("Network")));
ui.showWmMenu->setChecked(settings.showWmMenu()); ui.showWmMenu->setChecked(settings.showWmMenu());
connect(ui.buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, connect(ui.buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked,
@ -167,6 +174,22 @@ void DesktopPreferencesDialog::applySettings()
settings.setDesktopBgColor(ui.backgroundColor->color()); settings.setDesktopBgColor(ui.backgroundColor->color());
settings.setDesktopFgColor(ui.textColor->color()); settings.setDesktopFgColor(ui.textColor->color());
settings.setDesktopShadowColor(ui.shadowColor->color()); settings.setDesktopShadowColor(ui.shadowColor->color());
QStringList ds;
if(ui.homeBox->isChecked()) {
ds << QLatin1String("Home");
}
if(ui.trashBox->isChecked()) {
ds << QLatin1String("Trash");
}
if(ui.computerBox->isChecked()) {
ds << QLatin1String("Computer");
}
if(ui.networkBox->isChecked()) {
ds << QLatin1String("Network");
}
settings.setDesktopShortcuts(ds);
settings.setShowWmMenu(ui.showWmMenu->isChecked()); settings.setShowWmMenu(ui.showWmMenu->isChecked());
settings.setDesktopCellMargins(QSize(ui.hMargin->value(), ui.vMargin->value())); settings.setDesktopCellMargins(QSize(ui.hMargin->value(), ui.vMargin->value()));

@ -19,7 +19,6 @@
#include "desktopwindow.h" #include "desktopwindow.h"
#include <QWidget> #include <QWidget>
#include <QDesktopWidget>
#include <QPainter> #include <QPainter>
#include <QImage> #include <QImage>
#include <QImageReader> #include <QImageReader>
@ -39,6 +38,8 @@
#include <QMimeData> #include <QMimeData>
#include <QPaintEvent> #include <QPaintEvent>
#include <QStandardPaths> #include <QStandardPaths>
#include <QClipboard>
#include <QWindow>
#include "./application.h" #include "./application.h"
#include "mainwindow.h" #include "mainwindow.h"
@ -59,6 +60,7 @@
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#define WORK_AREA_MARGIN 12 // margin of the work area
#define MIN_SLIDE_INTERVAL 5*60000 // 5 min #define MIN_SLIDE_INTERVAL 5*60000 // 5 min
#define MAX_SLIDE_INTERVAL (24*60+55)*60000 // 24 h and 55 min #define MAX_SLIDE_INTERVAL (24*60+55)*60000 // 24 h and 55 min
@ -77,9 +79,10 @@ DesktopWindow::DesktopWindow(int screenNum):
desktopHideItems_(false), desktopHideItems_(false),
screenNum_(screenNum), screenNum_(screenNum),
relayoutTimer_(nullptr), relayoutTimer_(nullptr),
selectionTimer_(nullptr) { selectionTimer_(nullptr),
trashUpdateTimer_(nullptr),
trashMonitor_(nullptr) {
QDesktopWidget* desktopWidget = QApplication::desktop();
setWindowFlags(Qt::Window | Qt::FramelessWindowHint); setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
setAttribute(Qt::WA_X11NetWmWindowTypeDesktop); setAttribute(Qt::WA_X11NetWmWindowTypeDesktop);
setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose);
@ -91,6 +94,7 @@ DesktopWindow::DesktopWindow(int screenNum):
listView_->setMovement(QListView::Snap); listView_->setMovement(QListView::Snap);
listView_->setResizeMode(QListView::Adjust); listView_->setResizeMode(QListView::Adjust);
listView_->setFlow(QListView::TopToBottom); listView_->setFlow(QListView::TopToBottom);
listView_->setDropIndicatorShown(false); // we draw the drop indicator ourself
// This is to workaround Qt bug 54384 which affects Qt >= 5.6 // This is to workaround Qt bug 54384 which affects Qt >= 5.6
// https://bugreports.qt.io/browse/QTBUG-54384 // https://bugreports.qt.io/browse/QTBUG-54384
@ -99,14 +103,17 @@ DesktopWindow::DesktopWindow(int screenNum):
// Then we paint desktop's background ourselves by using its paint event handling method. // Then we paint desktop's background ourselves by using its paint event handling method.
listView_->viewport()->setAutoFillBackground(false); listView_->viewport()->setAutoFillBackground(false);
// NOTE: When XRnadR is in use, the all screens are actually combined to form a Settings& settings = static_cast<Application* >(qApp)->settings();
// NOTE: When XRandR is in use, the all screens are actually combined to form a
// large virtual desktop and only one DesktopWindow needs to be created and screenNum is -1. // large virtual desktop and only one DesktopWindow needs to be created and screenNum is -1.
// In some older multihead setups, such as xinerama, every physical screen // In some older multihead setups, such as xinerama, every physical screen
// is treated as a separate desktop so many instances of DesktopWindow may be created. // is treated as a separate desktop so many instances of DesktopWindow may be created.
// In this case we only want to show desktop icons on the primary screen. // In this case we only want to show desktop icons on the primary screen.
if(desktopWidget->isVirtualDesktop() || screenNum_ == desktopWidget->primaryScreen()) { if((screenNum_ == 0 || qApp->primaryScreen()->virtualSiblings().size() > 1)) {
loadItemPositions(); loadItemPositions();
Settings& settings = static_cast<Application* >(qApp)->settings();
setShadowHidden(settings.shadowHidden());
auto desktopPath = Fm::FilePath::fromLocalPath(XdgDir::readDesktopDir().toStdString().c_str()); auto desktopPath = Fm::FilePath::fromLocalPath(XdgDir::readDesktopDir().toStdString().c_str());
model_ = Fm::CachedFolderModel::modelFromPath(desktopPath); model_ = Fm::CachedFolderModel::modelFromPath(desktopPath);
@ -126,7 +133,6 @@ DesktopWindow::DesktopWindow(int screenNum):
connect(proxyModel_, &Fm::ProxyFolderModel::layoutChanged, this, &DesktopWindow::onLayoutChanged); connect(proxyModel_, &Fm::ProxyFolderModel::layoutChanged, this, &DesktopWindow::onLayoutChanged);
connect(proxyModel_, &Fm::ProxyFolderModel::sortFilterChanged, this, &DesktopWindow::onModelSortFilterChanged); connect(proxyModel_, &Fm::ProxyFolderModel::sortFilterChanged, this, &DesktopWindow::onModelSortFilterChanged);
connect(proxyModel_, &Fm::ProxyFolderModel::dataChanged, this, &DesktopWindow::onDataChanged); connect(proxyModel_, &Fm::ProxyFolderModel::dataChanged, this, &DesktopWindow::onDataChanged);
connect(listView_, &QListView::indexesMoved, this, &DesktopWindow::onIndexesMoved);
} }
// remove frame // remove frame
@ -148,6 +154,9 @@ DesktopWindow::DesktopWindow(int screenNum):
shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_C), this); // copy shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_C), this); // copy
connect(shortcut, &QShortcut::activated, this, &DesktopWindow::onCopyActivated); connect(shortcut, &QShortcut::activated, this, &DesktopWindow::onCopyActivated);
shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C), this); // copy full path
connect(shortcut, &QShortcut::activated, this, &DesktopWindow::onCopyFullPathActivated);
shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_V), this); // paste shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_V), this); // paste
connect(shortcut, &QShortcut::activated, this, &DesktopWindow::onPasteActivated); connect(shortcut, &QShortcut::activated, this, &DesktopWindow::onPasteActivated);
@ -171,6 +180,12 @@ DesktopWindow::DesktopWindow(int screenNum):
} }
DesktopWindow::~DesktopWindow() { DesktopWindow::~DesktopWindow() {
if(trashMonitor_) {
g_signal_handlers_disconnect_by_func(trashMonitor_, (gpointer)G_CALLBACK(onTrashChanged), this);
g_object_unref(trashMonitor_);
trashMonitor_ = nullptr;
}
listView_->viewport()->removeEventFilter(this); listView_->viewport()->removeEventFilter(this);
listView_->removeEventFilter(this); listView_->removeEventFilter(this);
@ -196,6 +211,221 @@ DesktopWindow::~DesktopWindow() {
} }
} }
void DesktopWindow::updateShortcutsFromSettings(Settings& settings) {
// Shortcuts should be deleted only when the user removes them
// in the Preferences dialog, not when the desktop is created.
static bool firstCall = true;
const QStringList ds = settings.desktopShortcuts();
Fm::FilePathList paths;
// Trash
if(ds.contains(QLatin1String("Trash")) && settings.useTrash()) {
createTrash();
}
else {
if(trashUpdateTimer_) {
trashUpdateTimer_->stop();
delete trashUpdateTimer_;
trashUpdateTimer_ = nullptr;
}
if(trashMonitor_) {
g_signal_handlers_disconnect_by_func(trashMonitor_, (gpointer)G_CALLBACK(onTrashChanged), this);
g_object_unref(trashMonitor_);
trashMonitor_ = nullptr;
}
if(!firstCall) {
QString trash = XdgDir::readDesktopDir() + QLatin1String("/trash-can.desktop");
if(QFile::exists(trash)) {
paths.push_back(Fm::FilePath::fromLocalPath(trash.toStdString().c_str()));
}
}
}
// Home
if(ds.contains(QLatin1String("Home"))) {
createHomeShortcut();
}
else if(!firstCall) {
QString home = XdgDir::readDesktopDir() + QLatin1String("/user-home.desktop");
if(QFile::exists(home)) {
paths.push_back(Fm::FilePath::fromLocalPath(home.toStdString().c_str()));
}
}
// Computer
if(ds.contains(QLatin1String("Computer"))) {
createComputerShortcut();
}
else if(!firstCall) {
QString computer = XdgDir::readDesktopDir() + QLatin1String("/computer.desktop");
if(QFile::exists(computer)) {
paths.push_back(Fm::FilePath::fromLocalPath(computer.toStdString().c_str()));
}
}
// Network
if(ds.contains(QLatin1String("Network"))) {
createNetworkShortcut();
}
else if(!firstCall) {
QString network = XdgDir::readDesktopDir() + QLatin1String("/network.desktop");
if(QFile::exists(network)) {
paths.push_back(Fm::FilePath::fromLocalPath(network.toStdString().c_str()));
}
}
// WARNING: QFile::remove() is not compatible with libfm-qt and should not be used.
if(!paths.empty()) {
Fm::FileOperation::deleteFiles(paths, false);
}
firstCall = false; // desktop is created
}
void DesktopWindow::createTrashShortcut(int items) {
GKeyFile* kf = g_key_file_new();
g_key_file_set_string(kf, "Desktop Entry", "Type", "Application");
g_key_file_set_string(kf, "Desktop Entry", "Exec", "pcmanfm-qt trash:///");
// icon
const char* icon_name = items > 0 ? "user-trash-full" : "user-trash";
g_key_file_set_string(kf, "Desktop Entry", "Icon", icon_name);
// name
QString name;
if(items > 0) {
if (items == 1) {
name = tr("Trash (One item)");
}
else {
name = tr("Trash (%Ln items)", "", items);
}
}
else {
name = tr("Trash (Empty)");
}
g_key_file_set_string(kf, "Desktop Entry", "Name", name.toStdString().c_str());
auto path = Fm::FilePath::fromLocalPath(XdgDir::readDesktopDir().toStdString().c_str()).localPath();
auto trash_can = Fm::CStrPtr{g_build_filename(path.get(), "trash-can.desktop", nullptr)};
g_key_file_save_to_file(kf, trash_can.get(), nullptr);
g_key_file_free(kf);
}
void DesktopWindow::createHomeShortcut() {
GKeyFile* kf = g_key_file_new();
g_key_file_set_string(kf, "Desktop Entry", "Type", "Application");
g_key_file_set_string(kf, "Desktop Entry", "Exec", "pcmanfm-qt");
g_key_file_set_string(kf, "Desktop Entry", "Icon", "user-home");
const QString name = tr("Home");
g_key_file_set_string(kf, "Desktop Entry", "Name", name.toStdString().c_str());
auto path = Fm::FilePath::fromLocalPath(XdgDir::readDesktopDir().toStdString().c_str()).localPath();
auto trash_can = Fm::CStrPtr{g_build_filename(path.get(), "user-home.desktop", nullptr)};
g_key_file_save_to_file(kf, trash_can.get(), nullptr);
g_key_file_free(kf);
}
void DesktopWindow::createComputerShortcut() {
GKeyFile* kf = g_key_file_new();
g_key_file_set_string(kf, "Desktop Entry", "Type", "Application");
g_key_file_set_string(kf, "Desktop Entry", "Exec", "pcmanfm-qt computer:///");
g_key_file_set_string(kf, "Desktop Entry", "Icon", "computer");
const QString name = tr("Computer");
g_key_file_set_string(kf, "Desktop Entry", "Name", name.toStdString().c_str());
auto path = Fm::FilePath::fromLocalPath(XdgDir::readDesktopDir().toStdString().c_str()).localPath();
auto trash_can = Fm::CStrPtr{g_build_filename(path.get(), "computer.desktop", nullptr)};
g_key_file_save_to_file(kf, trash_can.get(), nullptr);
g_key_file_free(kf);
}
void DesktopWindow::createNetworkShortcut() {
GKeyFile* kf = g_key_file_new();
g_key_file_set_string(kf, "Desktop Entry", "Type", "Application");
g_key_file_set_string(kf, "Desktop Entry", "Exec", "pcmanfm-qt network:///");
g_key_file_set_string(kf, "Desktop Entry", "Icon", "folder-network");
const QString name = tr("Network");
g_key_file_set_string(kf, "Desktop Entry", "Name", name.toStdString().c_str());
auto path = Fm::FilePath::fromLocalPath(XdgDir::readDesktopDir().toStdString().c_str()).localPath();
auto trash_can = Fm::CStrPtr{g_build_filename(path.get(), "network.desktop", nullptr)};
g_key_file_save_to_file(kf, trash_can.get(), nullptr);
g_key_file_free(kf);
}
void DesktopWindow::createTrash() {
if(trashMonitor_) {
return;
}
Fm::FilePath trashPath = Fm::FilePath::fromUri("trash:///");
// check if trash is supported by the current vfs
// if gvfs is not installed, this can be unavailable.
if(!g_file_query_exists(trashPath.gfile().get(), nullptr)) {
trashMonitor_ = nullptr;
return;
}
trashMonitor_ = g_file_monitor_directory(trashPath.gfile().get(), G_FILE_MONITOR_NONE, nullptr, nullptr);
if(trashMonitor_) {
if(trashUpdateTimer_ == nullptr) {
trashUpdateTimer_ = new QTimer(this);
trashUpdateTimer_->setSingleShot(true);
connect(trashUpdateTimer_, &QTimer::timeout, this, &DesktopWindow::updateTrashIcon);
}
updateTrashIcon();
g_signal_connect(trashMonitor_, "changed", G_CALLBACK(onTrashChanged), this);
}
}
// static
void DesktopWindow::onTrashChanged(GFileMonitor* /*monitor*/, GFile* /*gf*/, GFile* /*other*/, GFileMonitorEvent /*evt*/, DesktopWindow* pThis) {
if(pThis->trashUpdateTimer_ != nullptr && !pThis->trashUpdateTimer_->isActive()) {
pThis->trashUpdateTimer_->start(250); // don't update trash very fast
}
}
void DesktopWindow::updateTrashIcon() {
struct UpdateTrashData {
QPointer<DesktopWindow> desktop;
Fm::FilePath trashPath;
UpdateTrashData(DesktopWindow* _desktop) : desktop(_desktop) {
trashPath = Fm::FilePath::fromUri("trash:///");
}
};
UpdateTrashData* data = new UpdateTrashData(this);
g_file_query_info_async(data->trashPath.gfile().get(), G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT, G_FILE_QUERY_INFO_NONE, G_PRIORITY_LOW, nullptr,
[](GObject * /*source_object*/, GAsyncResult * res, gpointer user_data) {
// the callback lambda function is called when the asyn query operation is finished
UpdateTrashData* data = reinterpret_cast<UpdateTrashData*>(user_data);
DesktopWindow* _this = data->desktop.data();
if(_this != nullptr) {
Fm::GFileInfoPtr inf{g_file_query_info_finish(data->trashPath.gfile().get(), res, nullptr), false};
if(inf) {
guint32 n = g_file_info_get_attribute_uint32(inf.get(), G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT);
_this->createTrashShortcut(static_cast<int>(n));
}
}
delete data; // free the data used for this async operation.
}, data);
}
bool DesktopWindow::isTrashCan(std::shared_ptr<const Fm::FileInfo> file) {
bool ret(false);
if(file && (file->isDesktopEntry() || file->isShortcut()) && trashMonitor_) {
const QString fileName = QString::fromStdString(file->name());
const char* execStr = fileName == QLatin1String("trash-can.desktop")
? "pcmanfm-qt trash:///" : nullptr;
if(execStr) {
GKeyFile* kf = g_key_file_new();
if(g_key_file_load_from_file(kf, file->path().toString().get(), G_KEY_FILE_NONE, nullptr)) {
Fm::CStrPtr str{g_key_file_get_string(kf, "Desktop Entry", "Exec", nullptr)};
if(str && strcmp(str.get(), execStr) == 0) {
ret = true;
}
}
g_key_file_free(kf);
}
}
return ret;
}
void DesktopWindow::setBackground(const QColor& color) { void DesktopWindow::setBackground(const QColor& color) {
bgColor_ = color; bgColor_ = color;
} }
@ -505,6 +735,7 @@ void DesktopWindow::updateFromSettings(Settings& settings, bool changeSlide) {
setFont(settings.desktopFont()); setFont(settings.desktopFont());
setIconSize(Fm::FolderView::IconMode, QSize(settings.desktopIconSize(), settings.desktopIconSize())); setIconSize(Fm::FolderView::IconMode, QSize(settings.desktopIconSize(), settings.desktopIconSize()));
setMargins(settings.desktopCellMargins()); setMargins(settings.desktopCellMargins());
updateShortcutsFromSettings(settings);
// setIconSize and setMargins may trigger relayout of items by QListView, so we need to do the layout again. // setIconSize and setMargins may trigger relayout of items by QListView, so we need to do the layout again.
queueRelayout(); queueRelayout();
setForeground(settings.desktopFgColor()); setForeground(settings.desktopFgColor());
@ -566,6 +797,54 @@ void DesktopWindow::onFileClicked(int type, const std::shared_ptr<const Fm::File
delete menu; delete menu;
} }
else { else {
// special right-click menus for our desktop shortcuts
if(fileInfo && (fileInfo->isDesktopEntry() || fileInfo->isShortcut())
&& type == Fm::FolderView::ContextMenuClick) {
Settings& settings = static_cast<Application* >(qApp)->settings();
const QStringList ds = settings.desktopShortcuts();
if(!ds.isEmpty()) {
const QString fileName = QString::fromStdString(fileInfo->name());
if((fileName == QLatin1String("trash-can.desktop") && ds.contains(QLatin1String("Trash")))
|| (fileName == QLatin1String("user-home.desktop") && ds.contains(QLatin1String("Home")))
|| (fileName == QLatin1String("computer.desktop") && ds.contains(QLatin1String("Computer")))
|| (fileName == QLatin1String("network.desktop") && ds.contains(QLatin1String("Network")))) {
QMenu* menu = new QMenu(this);
// "Open" action for all
QAction* action = menu->addAction(tr("Open"));
connect(action, &QAction::triggered, this, [this, fileInfo] {
onFileClicked(Fm::FolderView::ActivatedClick, fileInfo);
});
// "Stick" action for all
action = menu->addAction(tr("Stic&k to Current Position"));
action->setCheckable(true);
action->setChecked(customItemPos_.find(fileInfo->name()) != customItemPos_.cend());
connect(action, &QAction::toggled, this, &DesktopWindow::onStickToCurrentPos);
// "Empty Trash" action for Trash shortcut
if(fileName == QLatin1String("trash-can.desktop")) {
menu->addSeparator();
action = menu->addAction(tr("Empty Trash"));
// disable the item is Trash is empty
GKeyFile* kf = g_key_file_new();
if(g_key_file_load_from_file(kf, fileInfo->path().toString().get(), G_KEY_FILE_NONE, nullptr)) {
Fm::CStrPtr str{g_key_file_get_string(kf, "Desktop Entry", "Icon", nullptr)};
if(str && strcmp(str.get(), "user-trash") == 0) {
action->setEnabled(false);
}
}
g_key_file_free(kf);
// empty Trash on clicking the item
connect(action, &QAction::triggered, this, [] {
Fm::FilePathList files;
files.push_back(Fm::FilePath::fromUri("trash:///"));
Fm::FileOperation::deleteFiles(std::move(files));
});
}
menu->exec(QCursor::pos());
delete menu;
return;
}
}
}
View::onFileClicked(type, fileInfo); View::onFileClicked(type, fileInfo);
} }
} }
@ -710,41 +989,6 @@ void DesktopWindow::onDataChanged(const QModelIndex& topLeft, const QModelIndex&
} }
} }
void DesktopWindow::onIndexesMoved(const QModelIndexList& indexes) {
auto delegate = static_cast<Fm::FolderItemDelegate*>(listView_->itemDelegateForColumn(0));
auto itemSize = delegate->itemSize();
// remember the custom position for the items
for(const QModelIndex& index : indexes) {
// Under some circumstances, Qt might emit indexMoved for
// every single cells in the same row. (when QAbstractItemView::SelectItems is set)
// So indexes list may contain several indixes for the same row.
// Since we only care about rows, not individual cells,
// let's handle column 0 of every row here.
if(index.column() == 0) {
auto file = proxyModel_->fileInfoFromIndex(index);
QRect itemRect = listView_->rectForIndex(index);
QPoint tl = itemRect.topLeft();
QRect workArea = qApp->desktop()->availableGeometry(screenNum_);
workArea.adjust(12, 12, -12, -12);
// check if the position is occupied by another item
auto existingItem = std::find_if(customItemPos_.cbegin(), customItemPos_.cend(), [tl](const std::pair<std::string, QPoint>& elem){
return elem.second == tl;
});
if(existingItem == customItemPos_.cend() // don't put items on each other
&& tl.x() >= workArea.x() && tl.y() >= workArea.y()
&& tl.x() + itemSize.width() <= workArea.right() + 1 // for historical reasons (-> Qt doc)
&& tl.y() + itemSize.height() <= workArea.bottom() + 1) { // as above
customItemPos_[file->name()] = tl;
// qDebug() << "indexMoved:" << name << index << itemRect;
}
}
}
saveItemPositions();
queueRelayout();
}
void DesktopWindow::onFolderStartLoading() { // desktop may be reloaded void DesktopWindow::onFolderStartLoading() { // desktop may be reloaded
if(model_) { if(model_) {
disconnect(model_, &Fm::FolderModel::filesAdded, this, &DesktopWindow::onFilesAdded); disconnect(model_, &Fm::FolderModel::filesAdded, this, &DesktopWindow::onFilesAdded);
@ -775,6 +1019,10 @@ void DesktopWindow::onFilesAdded(const Fm::FileInfoList files) {
} }
void DesktopWindow::removeBottomGap() { void DesktopWindow::removeBottomGap() {
auto screen = getDesktopScreen();
if(screen == nullptr) {
return;
}
/************************************************************ /************************************************************
NOTE: Desktop is an area bounded from below while icons snap NOTE: Desktop is an area bounded from below while icons snap
to its grid srarting from above. Therefore, we try to adjust to its grid srarting from above. Therefore, we try to adjust
@ -785,8 +1033,8 @@ void DesktopWindow::removeBottomGap() {
auto itemSize = delegate->itemSize(); auto itemSize = delegate->itemSize();
//qDebug() << "delegate:" << delegate->itemSize(); //qDebug() << "delegate:" << delegate->itemSize();
QSize cellMargins = getMargins(); QSize cellMargins = getMargins();
int workAreaHeight = qApp->desktop()->availableGeometry(screenNum_).height() int workAreaHeight = screen->availableVirtualGeometry().height()
- 24; // a 12-pix margin will be considered everywhere - 2 * WORK_AREA_MARGIN;
int cellHeight = itemSize.height() + listView_->spacing(); int cellHeight = itemSize.height() + listView_->spacing();
int iconNumber = workAreaHeight / cellHeight; int iconNumber = workAreaHeight / cellHeight;
int bottomGap = workAreaHeight % cellHeight; int bottomGap = workAreaHeight % cellHeight;
@ -832,9 +1080,39 @@ void DesktopWindow::paintBackground(QPaintEvent* event) {
} }
} }
void DesktopWindow::trustOurDesktopShortcut(std::shared_ptr<const Fm::FileInfo> file) {
if(file->isTrustable()) {
return;
}
Settings& settings = static_cast<Application*>(qApp)->settings();
const QStringList ds = settings.desktopShortcuts();
if(ds.isEmpty()) {
return;
}
const QString fileName = QString::fromStdString(file->name());
const char* execStr = fileName == QLatin1String("trash-can.desktop") && ds.contains(QLatin1String("Trash")) ? "pcmanfm-qt trash:///" :
fileName == QLatin1String("user-home.desktop") && ds.contains(QLatin1String("Home")) ? "pcmanfm-qt" :
fileName == QLatin1String("computer.desktop") && ds.contains(QLatin1String("Computer")) ? "pcmanfm-qt computer:///" :
fileName == QLatin1String("network.desktop") && ds.contains(QLatin1String("Network")) ? "pcmanfm-qt network:///" : nullptr;
if(execStr) {
GKeyFile* kf = g_key_file_new();
if(g_key_file_load_from_file(kf, file->path().toString().get(), G_KEY_FILE_NONE, nullptr)) {
Fm::CStrPtr str{g_key_file_get_string(kf, "Desktop Entry", "Exec", nullptr)};
if(str && strcmp(str.get(), execStr) == 0) {
file->setTrustable(true);
}
}
g_key_file_free(kf);
}
}
// QListView does item layout in a very inflexible way, so let's do our custom layout again. // QListView does item layout in a very inflexible way, so let's do our custom layout again.
// FIXME: this is very inefficient, but due to the design flaw of QListView, this is currently the only workaround. // FIXME: this is very inefficient, but due to the design flaw of QListView, this is currently the only workaround.
void DesktopWindow::relayoutItems() { void DesktopWindow::relayoutItems() {
auto screen = getDesktopScreen();
if(screen == nullptr) {
return;
}
displayNames_.clear(); displayNames_.clear();
loadItemPositions(); // something may have changed loadItemPositions(); // something may have changed
// qDebug("relayoutItems()"); // qDebug("relayoutItems()");
@ -844,26 +1122,15 @@ void DesktopWindow::relayoutItems() {
relayoutTimer_ = nullptr; relayoutTimer_ = nullptr;
} }
QDesktopWidget* desktop = qApp->desktop();
int screen = 0;
int row = 0; int row = 0;
int rowCount = proxyModel_->rowCount(); int rowCount = proxyModel_->rowCount();
auto delegate = static_cast<Fm::FolderItemDelegate*>(listView_->itemDelegateForColumn(0)); auto delegate = static_cast<Fm::FolderItemDelegate*>(listView_->itemDelegateForColumn(0));
auto itemSize = delegate->itemSize(); auto itemSize = delegate->itemSize();
for(;;) { QRect workArea = screen->availableVirtualGeometry();
if(desktop->isVirtualDesktop()) { workArea.adjust(WORK_AREA_MARGIN, WORK_AREA_MARGIN, -WORK_AREA_MARGIN, -WORK_AREA_MARGIN);
if(screen >= desktop->numScreens()) { // qDebug() << "workArea" << screenNum_ << workArea;
break;
}
}
else {
screen = screenNum_;
}
QRect workArea = desktop->availableGeometry(screen);
workArea.adjust(12, 12, -12, -12); // add a 12 pixel margin to the work area
// qDebug() << "workArea" << screen << workArea;
// FIXME: we use an internal class declared in a private header here, which is pretty bad. // FIXME: we use an internal class declared in a private header here, which is pretty bad.
QPoint pos = workArea.topLeft(); QPoint pos = workArea.topLeft();
for(; row < rowCount; ++row) { for(; row < rowCount; ++row) {
@ -873,6 +1140,7 @@ void DesktopWindow::relayoutItems() {
// remember display names of desktop entries and shortcuts // remember display names of desktop entries and shortcuts
if(file->isDesktopEntry() || file->isShortcut()) { if(file->isDesktopEntry() || file->isShortcut()) {
displayNames_[index] = file->displayName(); displayNames_[index] = file->displayName();
trustOurDesktopShortcut(file);
} }
auto name = file->name(); auto name = file->name();
auto find_it = customItemPos_.find(name); auto find_it = customItemPos_.find(name);
@ -883,7 +1151,7 @@ void DesktopWindow::relayoutItems() {
// qDebug() << "set custom pos:" << name << row << index << customPos; // qDebug() << "set custom pos:" << name << row << index << customPos;
continue; continue;
} }
// check if the current pos is alredy occupied by a custom item // check if the current pos is already occupied by a custom item
bool used = false; bool used = false;
for(auto it = customItemPos_.cbegin(); it != customItemPos_.cend(); ++it) { for(auto it = customItemPos_.cbegin(); it != customItemPos_.cend(); ++it) {
QPoint customPos = it->second; QPoint customPos = it->second;
@ -906,19 +1174,6 @@ void DesktopWindow::relayoutItems() {
// if the next position may exceed the bottom of work area, go to the top of next column // if the next position may exceed the bottom of work area, go to the top of next column
pos.setX(pos.x() + itemSize.width() + listView_->spacing()); pos.setX(pos.x() + itemSize.width() + listView_->spacing());
pos.setY(workArea.top()); pos.setY(workArea.top());
// check if the new column exceeds the right margin of work area
if(pos.x() + itemSize.width() > workArea.right() + 1) {
if(desktop->isVirtualDesktop()) {
// in virtual desktop mode, go to next screen
++screen;
break;
}
}
}
}
if(row >= rowCount) {
break;
} }
} }
@ -928,6 +1183,10 @@ void DesktopWindow::relayoutItems() {
} }
void DesktopWindow::loadItemPositions() { void DesktopWindow::loadItemPositions() {
auto screen = getDesktopScreen();
if(screen == nullptr) {
return;
}
// load custom item positions // load custom item positions
customItemPos_.clear(); customItemPos_.clear();
Settings& settings = static_cast<Application*>(qApp)->settings(); Settings& settings = static_cast<Application*>(qApp)->settings();
@ -936,8 +1195,8 @@ void DesktopWindow::loadItemPositions() {
auto delegate = static_cast<Fm::FolderItemDelegate*>(listView_->itemDelegateForColumn(0)); auto delegate = static_cast<Fm::FolderItemDelegate*>(listView_->itemDelegateForColumn(0));
auto grid = delegate->itemSize(); auto grid = delegate->itemSize();
QRect workArea = qApp->desktop()->availableGeometry(screenNum_); QRect workArea = screen->availableVirtualGeometry();
workArea.adjust(12, 12, -12, -12); workArea.adjust(WORK_AREA_MARGIN, WORK_AREA_MARGIN, -WORK_AREA_MARGIN, -WORK_AREA_MARGIN);
QString desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); QString desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
desktopDir += '/'; desktopDir += '/';
std::vector<QPoint> usedPos; std::vector<QPoint> usedPos;
@ -1055,6 +1314,16 @@ void DesktopWindow::onCopyActivated() {
} }
} }
void DesktopWindow::onCopyFullPathActivated() {
if(desktopHideItems_) {
return;
}
auto paths = selectedFilePaths();
if(paths.size() == 1) {
QApplication::clipboard()->setText(QString(paths.front().toString().get()), QClipboard::Clipboard);
}
}
void DesktopWindow::onPasteActivated() { void DesktopWindow::onPasteActivated() {
if(desktopHideItems_) { if(desktopHideItems_) {
return; return;
@ -1247,6 +1516,14 @@ bool DesktopWindow::eventFilter(QObject* watched, QEvent* event) {
} }
} }
break; break;
case QEvent::Paint:
// NOTE: The drop indicator isn't drawn/updated automatically, perhaps,
// because we paint desktop ourself. So, we draw it here.
paintDropIndicator();
break;
case QEvent::Wheel:
// removal of scrollbars is not enough to prevent scrolling
return true;
default: default:
break; break;
} }
@ -1254,69 +1531,267 @@ bool DesktopWindow::eventFilter(QObject* watched, QEvent* event) {
return Fm::FolderView::eventFilter(watched, event); return Fm::FolderView::eventFilter(watched, event);
} }
void DesktopWindow::childDragMoveEvent(QDragMoveEvent* e) {
// see DesktopWindow::eventFilter for an explanation
QRect oldDropRect = dropRect_;
dropRect_ = QRect();
QModelIndex dropIndex = listView_->indexAt(e->pos());
if(dropIndex.isValid()) {
bool dragOnSelf = false;
if(e->source() == listView_ && e->keyboardModifiers() == Qt::NoModifier) { // drag source is desktop
QModelIndex curIndx = listView_->currentIndex();
if(curIndx.isValid() && curIndx == dropIndex) {
dragOnSelf = true;
}
}
if(!dragOnSelf && dropIndex.model()) {
QVariant data = dropIndex.model()->data(dropIndex, Fm::FolderModel::Role::FileInfoRole);
auto info = data.value<std::shared_ptr<const Fm::FileInfo>>();
if(info && (info->isDir() || isTrashCan(info))) {
dropRect_ = listView_->rectForIndex(dropIndex);
}
}
}
if(oldDropRect != dropRect_) {
listView_->viewport()->update();
}
}
void DesktopWindow::paintDropIndicator()
{
if(!dropRect_.isNull()) {
QPainter painter(listView_->viewport());
QStyleOption opt;
opt.init(listView_->viewport());
opt.rect = dropRect_;
style()->drawPrimitive(QStyle::PE_IndicatorItemViewItemDrop, &opt, &painter, listView_);
}
}
void DesktopWindow::childDropEvent(QDropEvent* e) { void DesktopWindow::childDropEvent(QDropEvent* e) {
const QMimeData* mimeData = e->mimeData(); const QMimeData* mimeData = e->mimeData();
bool moveItem = false; bool moveItem = false;
QModelIndex curIndx = listView_->currentIndex();
if(e->source() == listView_ && e->keyboardModifiers() == Qt::NoModifier) { if(e->source() == listView_ && e->keyboardModifiers() == Qt::NoModifier) {
// drag source is our list view, and no other modifier keys are pressed // drag source is our list view, and no other modifier keys are pressed
// => we're dragging desktop items // => we're dragging desktop items
if(mimeData->hasFormat("application/x-qabstractitemmodeldatalist")) { if(mimeData->hasFormat("application/x-qabstractitemmodeldatalist")) {
QModelIndex dropIndex = listView_->indexAt(e->pos()); QModelIndex dropIndex = listView_->indexAt(e->pos());
if(dropIndex.isValid()) { // drop on an item if(dropIndex.isValid() // drop on an item
QModelIndexList selected = selectedIndexes(); // the dragged items && curIndx.isValid() && curIndx != dropIndex) { // not a drop on self
if(selected.contains(dropIndex)) { // drop on self, ignore if(auto file = proxyModel_->fileInfoFromIndex(dropIndex)) {
if(!file->isDir()) { // drop on a non-directory file
// if the files are dropped on our Trash shortcut item,
// move them to Trash instead of moving them on desktop
if(isTrashCan(file)) {
auto paths = selectedFilePaths();
if(!paths.empty()) {
e->accept();
Settings& settings = static_cast<Application*>(qApp)->settings();
Fm::FileOperation::trashFiles(paths, settings.confirmTrash());
// remove the drop indicator
dropRect_ = QRect();
listView_->viewport()->update();
return;
}
}
moveItem = true; moveItem = true;
} }
} }
else { // drop on a blank area }
else { // drop on a blank area (maybe, between other items)
moveItem = true; moveItem = true;
} }
} }
} }
if(moveItem) { if(moveItem) {
e->accept(); auto screen = getDesktopScreen();
if(screen == nullptr) {
return;
} }
else { e->accept();
// move selected items to the drop position, preserving their relative positions
const QPoint dropPos = e->pos();
if(curIndx.isValid()) {
auto delegate = static_cast<Fm::FolderItemDelegate*>(listView_->itemDelegateForColumn(0)); auto delegate = static_cast<Fm::FolderItemDelegate*>(listView_->itemDelegateForColumn(0));
auto grid = delegate->itemSize(); auto grid = delegate->itemSize();
QRect workArea = screen->availableVirtualGeometry();
workArea.adjust(WORK_AREA_MARGIN, WORK_AREA_MARGIN, -WORK_AREA_MARGIN, -WORK_AREA_MARGIN);
QPoint curPoint = listView_->visualRect(curIndx).topLeft();
// first move the current item to the drop position
auto file = proxyModel_->fileInfoFromIndex(curIndx);
if(file) {
QPoint pos = dropPos;
stickToPosition(file->name(), pos, workArea, grid);
}
// then move the other items so that their relative postions are preserved
const QModelIndexList selected = selectedIndexes();
for(const QModelIndex& indx : selected) {
if(indx == curIndx) {
continue;
}
file = proxyModel_->fileInfoFromIndex(indx);
if(file) {
QPoint nxtDropPos = dropPos + listView_->visualRect(indx).topLeft() - curPoint;
nxtDropPos.setX(qBound(workArea.left(), nxtDropPos.x(), workArea.right() + 1));
nxtDropPos.setY(qBound(workArea.top(), nxtDropPos.y(), workArea.bottom() + 1));
stickToPosition(file->name(), nxtDropPos, workArea, grid);
}
}
}
saveItemPositions();
queueRelayout();
}
else {
// remove the drop indicator
dropRect_ = QRect();
listView_->viewport()->update();
// move items to Trash if they are dropped on Trash shortcut
QModelIndex dropIndex = listView_->indexAt(e->pos());
if(dropIndex.isValid()) {
if(auto file = proxyModel_->fileInfoFromIndex(dropIndex)) {
if(isTrashCan(file)) {
if(mimeData->hasUrls()) {
Fm::FilePathList paths;
const QList<QUrl> urlList = mimeData->urls();
for(const QUrl& url : urlList) {
QString uri = url.toDisplayString();
if(!uri.isEmpty()) {
paths.push_back(Fm::FilePath::fromUri(uri.toStdString().c_str()));
}
}
if(!paths.empty()) {
e->accept();
Settings& settings = static_cast<Application*>(qApp)->settings();
Fm::FileOperation::trashFiles(paths, settings.confirmTrash());
return;
}
}
}
}
}
Fm::FolderView::childDropEvent(e); Fm::FolderView::childDropEvent(e);
// position dropped items successively, starting with the drop rectangle // position dropped items successively, starting with the drop rectangle
if(mimeData->hasUrls() if(mimeData->hasUrls()
&& (e->dropAction() == Qt::CopyAction && (e->dropAction() == Qt::CopyAction
|| e->dropAction() == Qt::MoveAction || e->dropAction() == Qt::MoveAction
|| e->dropAction() == Qt::LinkAction)) { || e->dropAction() == Qt::LinkAction)) {
QList<QUrl> urlList = mimeData->urls(); auto screen = getDesktopScreen();
for(int i = 0; i < urlList.count(); ++i) { if(screen == nullptr) {
std::string name = urlList.at(i).fileName().toUtf8().constData(); return;
if(!name.empty()) { // respect the positions of existing files }
QString desktopDir = XdgDir::readDesktopDir() + QString(QLatin1String("/")); auto delegate = static_cast<Fm::FolderItemDelegate*>(listView_->itemDelegateForColumn(0));
if(!QFile::exists(desktopDir + QString::fromStdString(name))) { auto grid = delegate->itemSize();
QRect workArea = qApp->desktop()->availableGeometry(screenNum_); QRect workArea = screen->availableVirtualGeometry();
workArea.adjust(12, 12, -12, -12); workArea.adjust(WORK_AREA_MARGIN, WORK_AREA_MARGIN, -WORK_AREA_MARGIN, -WORK_AREA_MARGIN);
QPoint pos = mapFromGlobal(e->pos()); const QString desktopDir = XdgDir::readDesktopDir() + QString(QLatin1String("/"));
alignToGrid(pos, workArea.topLeft(), grid, listView_->spacing()); QPoint dropPos = e->pos();
if(i > 0) const QList<QUrl> urlList = mimeData->urls();
pos.setY(pos.y() + grid.height() + listView_->spacing()); bool reachedLastCell = false;
for(const QUrl& url : urlList) {
QString name = url.fileName();
if(!name.isEmpty()
// don't stick to the position if there is an overwrite prompt
&& !QFile::exists(desktopDir + name)) {
reachedLastCell = stickToPosition(name.toStdString(), dropPos, workArea, grid, reachedLastCell);
}
}
saveItemPositions();
}
}
}
// NOTE: This function positions items from top to bottom and left to right,
// starting from the drop point, and carries the existing sticky items with them,
// until it reaches the last cell and then puts the remaining items in the opposite
// direction. In this way, it creates a natural DND, especially with multiple files.
bool DesktopWindow::stickToPosition(const std::string& file, QPoint& pos, const QRect& workArea, const QSize& grid, bool reachedLastCell) {
// normalize the position, depending on the positioning direction
if(!reachedLastCell) { // default direction: top -> bottom, left -> right
// put the drop point inside the work area to prevent unnatural jumps
if(pos.y() + grid.height() > workArea.bottom() + 1) { if(pos.y() + grid.height() > workArea.bottom() + 1) {
pos.setX(pos.x() + grid.width() + listView_->spacing()); pos.setY(workArea.bottom() + 1 - grid.height());
pos.setY(workArea.top());
} }
customItemPos_[name] = pos; if(pos.x() + grid.width() > workArea.right() + 1) {
pos.setX(workArea.right() + 1 - grid.width());
} }
pos.setX(qMax(workArea.left(), pos.x()));
pos.setY(qMax(workArea.top(), pos.y()));
alignToGrid(pos, workArea.topLeft(), grid, listView_->spacing());
} }
else { // backward direction: bottom -> top, right -> left
if(pos.y() < workArea.top()) {
// reached the top; go to the left bottom
pos.setY(workArea.bottom() + 1 - grid.height());
pos.setX(pos.x() - grid.width() - listView_->spacing());
}
alignToGrid(pos, workArea.topLeft(), grid, listView_->spacing());
if (pos.x() < workArea.left()) {
// there's no space to the left, which means that
// the work area is exhausted, so ignore stickiness
return reachedLastCell;
} }
saveItemPositions();
} }
// find if there is a sticky item at this position
std::string otherFile;
auto oldItem = std::find_if(customItemPos_.cbegin(),
customItemPos_.cend(),
[pos](const std::pair<std::string, QPoint>& elem) {
return elem.second == pos;
});
if(oldItem != customItemPos_.cend()) {
otherFile = oldItem->first;
} }
// stick to the position
customItemPos_[file] = pos;
// check whether we are in the last visible cell if it isn't reached already
if(!reachedLastCell
&& pos.y() + 2 * grid.height() + listView_->spacing() > workArea.bottom() + 1
&& pos.x() + 2 * grid.width() + listView_->spacing() > workArea.right() + 1) {
reachedLastCell = true;
}
// find the next position
if(reachedLastCell) {
// when this is the last visible cell, reverse the positioning direction
// to avoid off-screen items later
pos.setY(pos.y() - grid.height() - listView_->spacing());
}
else {
// the last visible cell is not reached yet; go forward
if(pos.y() + 2 * grid.height() + listView_->spacing() > workArea.bottom() + 1) {
pos.setY(workArea.top());
pos.setX(pos.x() + grid.width() + listView_->spacing());
}
else {
pos.setY(pos.y() + grid.height() + listView_->spacing());
}
}
// if there was another sticky item at the same position, move it to the next position
if(!otherFile.empty() && otherFile != file) {
reachedLastCell = stickToPosition(otherFile, pos, workArea, grid, reachedLastCell);
}
return reachedLastCell;
} }
void DesktopWindow::alignToGrid(QPoint& pos, const QPoint& topLeft, const QSize& grid, const int spacing) { void DesktopWindow::alignToGrid(QPoint& pos, const QPoint& topLeft, const QSize& grid, const int spacing) {
qreal w = qAbs((qreal)pos.x() - (qreal)topLeft.x()) int w = (pos.x() - topLeft.x()) / (grid.width() + spacing); // can be negative with DND
/ (qreal)(grid.width() + spacing); int h = (pos.y() - topLeft.y()) / (grid.height() + spacing); // can be negative with DND
qreal h = qAbs(pos.y() - (qreal)topLeft.y()) pos.setX(topLeft.x() + w * (grid.width() + spacing));
/ (qreal)(grid.height() + spacing); pos.setY(topLeft.y() + h * (grid.height() + spacing));
pos.setX(topLeft.x() + qRound(w) * (grid.width() + spacing));
pos.setY(topLeft.y() + qRound(h) * (grid.height() + spacing));
} }
void DesktopWindow::closeEvent(QCloseEvent* event) { void DesktopWindow::closeEvent(QCloseEvent* event) {
@ -1336,4 +1811,21 @@ void DesktopWindow::setScreenNum(int num) {
} }
} }
QScreen* DesktopWindow::getDesktopScreen() const {
QScreen* desktopScreen = nullptr;
if(screenNum_ == -1) {
desktopScreen = qApp->primaryScreen();
}
else {
const auto allScreens = qApp->screens();
if(allScreens.size() > screenNum_) {
desktopScreen = allScreens.at(screenNum_);
}
if(desktopScreen == nullptr && windowHandle()) {
desktopScreen = windowHandle()->screen();
}
}
return desktopScreen;
}
} // namespace PCManFM } // namespace PCManFM

@ -29,6 +29,7 @@
#include <QHash> #include <QHash>
#include <QPoint> #include <QPoint>
#include <QByteArray> #include <QByteArray>
#include <QScreen>
#include <xcb/xcb.h> #include <xcb/xcb.h>
#include <libfm-qt/core/folder.h> #include <libfm-qt/core/folder.h>
@ -84,6 +85,8 @@ public:
void setScreenNum(int num); void setScreenNum(int num);
QScreen* getDesktopScreen() const;
protected: protected:
virtual void prepareFolderMenu(Fm::FolderMenu* menu) override; virtual void prepareFolderMenu(Fm::FolderMenu* menu) override;
virtual void prepareFileMenu(Fm::FileMenu* menu) override; virtual void prepareFileMenu(Fm::FileMenu* menu) override;
@ -98,6 +101,7 @@ protected:
virtual bool event(QEvent* event) override; virtual bool event(QEvent* event) override;
virtual bool eventFilter(QObject* watched, QEvent* event) override; virtual bool eventFilter(QObject* watched, QEvent* event) override;
virtual void childDragMoveEvent(QDragMoveEvent* e) override;
virtual void childDropEvent(QDropEvent* e) override; virtual void childDropEvent(QDropEvent* e) override;
virtual void closeEvent(QCloseEvent* event) override; virtual void closeEvent(QCloseEvent* event) override;
virtual void paintEvent(QPaintEvent* event) override; virtual void paintEvent(QPaintEvent* event) override;
@ -112,7 +116,6 @@ protected Q_SLOTS:
void onRowsInserted(const QModelIndex& parent, int start, int end); void onRowsInserted(const QModelIndex& parent, int start, int end);
void onLayoutChanged(); void onLayoutChanged();
void onModelSortFilterChanged(); void onModelSortFilterChanged();
void onIndexesMoved(const QModelIndexList& indexes);
void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
void onFolderStartLoading(); void onFolderStartLoading();
void onFolderFinishLoading(); void onFolderFinishLoading();
@ -126,18 +129,34 @@ protected Q_SLOTS:
// file operations // file operations
void onCutActivated(); void onCutActivated();
void onCopyActivated(); void onCopyActivated();
void onCopyFullPathActivated();
void onPasteActivated(); void onPasteActivated();
void onRenameActivated(); void onRenameActivated();
void onBulkRenameActivated(); void onBulkRenameActivated();
void onDeleteActivated(); void onDeleteActivated();
void onFilePropertiesActivated(); void onFilePropertiesActivated();
void updateTrashIcon();
private: private:
void removeBottomGap(); void removeBottomGap();
void addDesktopActions(QMenu* menu); void addDesktopActions(QMenu* menu);
void paintBackground(QPaintEvent* event); void paintBackground(QPaintEvent* event);
void paintDropIndicator();
bool stickToPosition(const std::string& file, QPoint& pos, const QRect& workArea, const QSize& grid, bool reachedLastCell = false);
static void alignToGrid(QPoint& pos, const QPoint& topLeft, const QSize& grid, const int spacing); static void alignToGrid(QPoint& pos, const QPoint& topLeft, const QSize& grid, const int spacing);
void updateShortcutsFromSettings(Settings& settings);
void createTrashShortcut(int items);
void createHomeShortcut();
void createComputerShortcut();
void createNetworkShortcut();
void createTrash();
static void onTrashChanged(GFileMonitor* monitor, GFile* gf, GFile* other, GFileMonitorEvent evt, DesktopWindow* pThis);
void trustOurDesktopShortcut(std::shared_ptr<const Fm::FileInfo> file);
bool isTrashCan(std::shared_ptr<const Fm::FileInfo> file);
private: private:
Fm::ProxyFolderModel* proxyModel_; Fm::ProxyFolderModel* proxyModel_;
Fm::CachedFolderModel* model_; Fm::CachedFolderModel* model_;
@ -164,6 +183,11 @@ private:
QHash<QModelIndex, QString> displayNames_; // only for desktop entries and shortcuts QHash<QModelIndex, QString> displayNames_; // only for desktop entries and shortcuts
QTimer* relayoutTimer_; QTimer* relayoutTimer_;
QTimer* selectionTimer_; QTimer* selectionTimer_;
QRect dropRect_;
QTimer* trashUpdateTimer_;
GFileMonitor* trashMonitor_;
}; };
} }

@ -31,19 +31,6 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<widget class="PCManFM::TabBar" name="tabBar" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
</widget>
</item>
<item> <item>
<widget class="QSplitter" name="splitter"> <widget class="QSplitter" name="splitter">
<property name="orientation"> <property name="orientation">
@ -57,48 +44,11 @@
</sizepolicy> </sizepolicy>
</property> </property>
</widget> </widget>
<widget class="QFrame" name="frame"> <widget class="QSplitter" name="viewSplitter">
<layout class="QVBoxLayout" name="verticalLayout_2"> <property name="orientation">
<property name="spacing"> <enum>Qt::Horizontal</enum>
<number>2</number>
</property>
<property name="leftMargin">
<number>1</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
</widget> </widget>
</item>
<item>
<widget class="QLineEdit" name="filterBar">
<property name="toolTip">
<string>Focus with Ctrl+I</string>
</property>
<property name="placeholderText">
<string>Filter by string...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -187,15 +137,25 @@
<addaction name="actionLocationBar"/> <addaction name="actionLocationBar"/>
<addaction name="actionPathButtons"/> <addaction name="actionPathButtons"/>
</widget> </widget>
<widget class="QMenu" name="menuFiltering">
<property name="title">
<string>&amp;Filtering</string>
</property>
<addaction name="actionShowFilter"/>
<addaction name="actionUnfilter"/>
</widget>
<addaction name="actionReload"/> <addaction name="actionReload"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionShowHidden"/> <addaction name="actionShowHidden"/>
<addaction name="actionSplitView"/>
<addaction name="menuSorting"/> <addaction name="menuSorting"/>
<addaction name="menu_View_2"/> <addaction name="menu_View_2"/>
<addaction name="actionPreserveView"/> <addaction name="actionPreserveView"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="menuToolbars"/> <addaction name="menuToolbars"/>
<addaction name="menuPathBarStyle"/> <addaction name="menuPathBarStyle"/>
<addaction name="separator"/>
<addaction name="menuFiltering"/>
</widget> </widget>
<widget class="QMenu" name="menu_Edit"> <widget class="QMenu" name="menu_Edit">
<property name="title"> <property name="title">
@ -245,6 +205,7 @@
</property> </property>
<addaction name="actionOpenTerminal"/> <addaction name="actionOpenTerminal"/>
<addaction name="actionOpenAsRoot"/> <addaction name="actionOpenAsRoot"/>
<addaction name="actionCopyFullPath"/>
<addaction name="actionFindFiles"/> <addaction name="actionFindFiles"/>
</widget> </widget>
<addaction name="menu_File"/> <addaction name="menu_File"/>
@ -752,12 +713,20 @@
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Filter</string> <string>Permanent &amp;filter bar</string>
</property> </property>
<property name="shortcut"> <property name="shortcut">
<string>Ctrl+B</string> <string>Ctrl+B</string>
</property> </property>
</action> </action>
<action name="actionUnfilter">
<property name="text">
<string>&amp;Clear All Filters</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+K</string>
</property>
</action>
<action name="actionCloseLeft"> <action name="actionCloseLeft">
<property name="icon"> <property name="icon">
<iconset theme="go-previous"> <iconset theme="go-previous">
@ -847,14 +816,41 @@
<string>Ctrl+F2</string> <string>Ctrl+F2</string>
</property> </property>
</action> </action>
<action name="actionShowFilter">
<property name="text">
<string>&amp;Show/Focus Filter Bar</string>
</property>
<property name="toolTip">
<string>Show Filter Bar</string>
</property>
<property name="shortcut">
<string>Ctrl+I</string>
</property>
</action>
<action name="actionSplitView">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>S&amp;plit View</string>
</property>
<property name="toolTip">
<string>Split View</string>
</property>
<property name="shortcut">
<string>F6</string>
</property>
</action>
<action name="actionCopyFullPath">
<property name="text">
<string>&amp;Copy Full Path</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+C</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget>
<class>PCManFM::TabBar</class>
<extends>QWidget</extends>
<header>tabbar.h</header>
<container>1</container>
</customwidget>
<customwidget> <customwidget>
<class>PCManFM::StatusBar</class> <class>PCManFM::StatusBar</class>
<extends>QStatusBar</extends> <extends>QStatusBar</extends>

File diff suppressed because it is too large Load Diff

@ -26,12 +26,12 @@
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QLineEdit> #include <QLineEdit>
#include <QTabWidget> #include <QTabWidget>
#include <libfm/fm.h>
#include <QMessageBox> #include <QMessageBox>
#include <QTabBar> #include <QTabBar>
#include <QStackedWidget> #include <QStackedWidget>
#include <QSplitter> #include <QSplitter>
#include "launcher.h" #include "launcher.h"
#include "tabbar.h"
#include <libfm-qt/core/filepath.h> #include <libfm-qt/core/filepath.h>
#include <libfm-qt/core/bookmarks.h> #include <libfm-qt/core/bookmarks.h>
@ -42,6 +42,33 @@ class PathBar;
namespace PCManFM { namespace PCManFM {
class ViewFrame : public QFrame {
Q_OBJECT
public:
ViewFrame(QWidget* parent = nullptr);
~ViewFrame() {};
void createTopBar(bool usePathButtons);
void removeTopBar();
QWidget* getTopBar() const {
return topBar_;
}
TabBar* getTabBar() const {
return tabBar_;
}
QStackedWidget* getStackedWidget() const {
return stackedWidget_;
}
private:
QWidget* topBar_;
TabBar* tabBar_;
QStackedWidget* stackedWidget_;
};
//======================================================================
class TabPage; class TabPage;
class Settings; class Settings;
@ -51,11 +78,21 @@ public:
MainWindow(Fm::FilePath path = Fm::FilePath()); MainWindow(Fm::FilePath path = Fm::FilePath());
virtual ~MainWindow(); virtual ~MainWindow();
void chdir(Fm::FilePath path); void chdir(Fm::FilePath path, ViewFrame* viewFrame);
int addTab(Fm::FilePath path); void chdir(Fm::FilePath path) {
chdir(path, activeViewFrame_);
}
int addTab(Fm::FilePath path, ViewFrame* viewFrame);
int addTab(Fm::FilePath path) {
return addTab(path, activeViewFrame_);
}
TabPage* currentPage(ViewFrame* viewFrame) {
return reinterpret_cast<TabPage*>(viewFrame->getStackedWidget()->currentWidget());
}
TabPage* currentPage() { TabPage* currentPage() {
return reinterpret_cast<TabPage*>(ui.stackedWidget->currentWidget()); return currentPage(activeViewFrame_);
} }
void updateFromSettings(Settings& settings); void updateFromSettings(Settings& settings);
@ -103,6 +140,7 @@ protected Q_SLOTS:
void on_actionGo_triggered(); void on_actionGo_triggered();
void on_actionShowHidden_triggered(bool check); void on_actionShowHidden_triggered(bool check);
void on_actionSplitView_triggered(bool check);
void on_actionPreserveView_triggered(bool checked); void on_actionPreserveView_triggered(bool checked);
void on_actionByFileName_triggered(bool checked); void on_actionByFileName_triggered(bool checked);
@ -115,6 +153,8 @@ protected Q_SLOTS:
void on_actionFolderFirst_triggered(bool checked); void on_actionFolderFirst_triggered(bool checked);
void on_actionCaseSensitive_triggered(bool checked); void on_actionCaseSensitive_triggered(bool checked);
void on_actionFilter_triggered(bool checked); void on_actionFilter_triggered(bool checked);
void on_actionUnfilter_triggered();
void on_actionShowFilter_triggered();
void on_actionLocationBar_triggered(bool checked); void on_actionLocationBar_triggered(bool checked);
void on_actionPathButtons_triggered(bool checked); void on_actionPathButtons_triggered(bool checked);
@ -129,6 +169,7 @@ protected Q_SLOTS:
void on_actionOpenTerminal_triggered(); void on_actionOpenTerminal_triggered();
void on_actionOpenAsRoot_triggered(); void on_actionOpenAsRoot_triggered();
void on_actionCopyFullPath_triggered();
void on_actionFindFiles_triggered(); void on_actionFindFiles_triggered();
void on_actionAbout_triggered(); void on_actionAbout_triggered();
@ -139,9 +180,6 @@ protected Q_SLOTS:
void onTabBarCurrentChanged(int index); void onTabBarCurrentChanged(int index);
void onTabBarTabMoved(int from, int to); void onTabBarTabMoved(int from, int to);
void focusFilterBar();
void onFilterStringChanged(QString str);
void onShortcutPrevTab(); void onShortcutPrevTab();
void onShortcutNextTab(); void onShortcutNextTab();
void onShortcutJumpToTab(); void onShortcutJumpToTab();
@ -164,7 +202,10 @@ protected Q_SLOTS:
void onBackForwardContextMenu(QPoint pos); void onBackForwardContextMenu(QPoint pos);
void onFolderUnmounted();
void tabContextMenu(const QPoint& pos); void tabContextMenu(const QPoint& pos);
void onTabBarClicked(int index);
void closeLeftTabs(); void closeLeftTabs();
void closeRightTabs(); void closeRightTabs();
void closeOtherTabs() { void closeOtherTabs() {
@ -182,21 +223,27 @@ protected Q_SLOTS:
protected: protected:
bool event(QEvent* event) override; bool event(QEvent* event) override;
void changeEvent(QEvent* event) override; void changeEvent(QEvent* event) override;
void closeTab(int index); void closeTab(int index, ViewFrame* viewFrame);
void closeTab(int index) {
closeTab(index, activeViewFrame_);
}
virtual void resizeEvent(QResizeEvent* event) override; virtual void resizeEvent(QResizeEvent* event) override;
virtual void closeEvent(QCloseEvent* event) override; virtual void closeEvent(QCloseEvent* event) override;
virtual void dragEnterEvent(QDragEnterEvent* event) override; virtual void dragEnterEvent(QDragEnterEvent* event) override;
virtual void dropEvent(QDropEvent* event) override; virtual void dropEvent(QDropEvent* event) override;
virtual bool eventFilter(QObject* watched, QEvent* event);
private: private:
void loadBookmarksMenu(); void loadBookmarksMenu();
void updateUIForCurrentPage(); void updateUIForCurrentPage(bool setFocus = true);
void updateViewMenuForCurrentPage(); void updateViewMenuForCurrentPage();
void updateEditSelectedActions(); void updateEditSelectedActions();
void updateStatusBarForCurrentPage(); void updateStatusBarForCurrentPage();
void setRTLIcons(bool isRTL); void setRTLIcons(bool isRTL);
void createPathBar(bool usePathButtons); void createPathBar(bool usePathButtons);
int addTabWithPage(TabPage* page, Fm::FilePath path = Fm::FilePath()); void addViewFrame(const Fm::FilePath& path);
ViewFrame* viewFrameForTabPage(TabPage* page);
int addTabWithPage(TabPage* page, ViewFrame* viewFrame, Fm::FilePath path = Fm::FilePath());
void dropTab(); void dropTab();
private: private:
@ -209,6 +256,12 @@ private:
int rightClickIndex_; int rightClickIndex_;
bool updatingViewMenu_; bool updatingViewMenu_;
QAction* menuSep_; QAction* menuSep_;
QAction* menuSpacer_;
ViewFrame* activeViewFrame_;
// The split mode of this window is changed only from its settings,
// not from another window. So, we get the mode at the start and keep it.
bool splitView_;
static MainWindow* lastActive_; static MainWindow* lastActive_;
}; };

@ -1,4 +1,3 @@
#include <libfm/fm.h>
#include "application.h" #include "application.h"
#include <libfm-qt/libfmqt.h> #include <libfm-qt/libfmqt.h>

@ -28,7 +28,7 @@
<enum>Qt::ScrollBarAlwaysOff</enum> <enum>Qt::ScrollBarAlwaysOff</enum>
</property> </property>
<property name="currentRow"> <property name="currentRow">
<number>-1</number> <number>0</number>
</property> </property>
<item> <item>
<property name="text"> <property name="text">
@ -189,9 +189,11 @@
</item> </item>
<item> <item>
<widget class="QCheckBox" name="quickExec"> <widget class="QCheckBox" name="quickExec">
<property name="toolTip">
<string>Requires application restart to take effect completely</string>
</property>
<property name="text"> <property name="text">
<string>Launch executable files without prompt <string>Launch executable files without prompt</string>
(Requires application restart to take effect)</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -337,8 +339,8 @@
</item> </item>
<item row="2" column="0" colspan="6"> <item row="2" column="0" colspan="6">
<widget class="QCheckBox" name="showFullNames"> <widget class="QCheckBox" name="showFullNames">
<property name="enabled"> <property name="toolTip">
<bool>false</bool> <string>Requires application restart to take effect completely</string>
</property> </property>
<property name="text"> <property name="text">
<string>Always show full file names</string> <string>Always show full file names</string>
@ -347,8 +349,8 @@
</item> </item>
<item row="3" column="0" colspan="6"> <item row="3" column="0" colspan="6">
<widget class="QCheckBox" name="shadowHidden"> <widget class="QCheckBox" name="shadowHidden">
<property name="enabled"> <property name="toolTip">
<bool>false</bool> <string>Requires application restart to take effect completely</string>
</property> </property>
<property name="text"> <property name="text">
<string>Show icons of hidden files shadowed</string> <string>Show icons of hidden files shadowed</string>
@ -479,52 +481,41 @@ only if there are more than one tab.</string>
</widget> </widget>
</item> </item>
<item row="1" column="0" colspan="2"> <item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="fullWidthTabbar">
<property name="toolTip">
<string>If unchecked, the tab bar will be positioned only
above the folder-view and not above the left pane.</string>
</property>
<property name="text">
<string>Fullwidth tab bar</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="showTabClose"> <widget class="QCheckBox" name="showTabClose">
<property name="text"> <property name="text">
<string>Show 'Close' buttons on tabs </string> <string>Show 'Close' buttons on tabs </string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2"> <item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="rememberWindowSize"> <widget class="QCheckBox" name="rememberWindowSize">
<property name="text"> <property name="text">
<string>Remember the size of the last closed window</string> <string>Remember the size of the last closed window</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="3" column="0">
<widget class="QLabel" name="label_12"> <widget class="QLabel" name="label_12">
<property name="text"> <property name="text">
<string>Default width of new windows:</string> <string>Default width of new windows:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="3" column="1">
<widget class="QSpinBox" name="fixedWindowWidth"> <widget class="QSpinBox" name="fixedWindowWidth">
<property name="maximum"> <property name="maximum">
<number>32768</number> <number>32768</number>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="4" column="0">
<widget class="QLabel" name="label_13"> <widget class="QLabel" name="label_13">
<property name="text"> <property name="text">
<string>Default height of new windows:</string> <string>Default height of new windows:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="4" column="1">
<widget class="QSpinBox" name="fixedWindowHeight"> <widget class="QSpinBox" name="fixedWindowHeight">
<property name="maximum"> <property name="maximum">
<number>32768</number> <number>32768</number>
@ -534,100 +525,6 @@ above the folder-view and not above the left pane.</string>
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox_10">
<property name="title">
<string>Show in places</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
<widget class="QListWidget" name="showInPlaces">
<item>
<property name="text">
<string>Home</string>
</property>
<property name="checkState">
<enum>Unchecked</enum>
</property>
<property name="icon">
<iconset theme="user-home">
<normaloff/>
</iconset>
</property>
</item>
<item>
<property name="text">
<string>Desktop</string>
</property>
<property name="checkState">
<enum>Unchecked</enum>
</property>
<property name="icon">
<iconset theme="user-desktop">
<normaloff/>
</iconset>
</property>
</item>
<item>
<property name="text">
<string>Trash can</string>
</property>
<property name="checkState">
<enum>Unchecked</enum>
</property>
<property name="icon">
<iconset theme="user-trash">
<normaloff/>
</iconset>
</property>
</item>
<item>
<property name="text">
<string>Computer</string>
</property>
<property name="checkState">
<enum>Unchecked</enum>
</property>
<property name="icon">
<iconset theme="computer">
<normaloff/>
</iconset>
</property>
</item>
<item>
<property name="text">
<string>Applications</string>
</property>
<property name="checkState">
<enum>Unchecked</enum>
</property>
</item>
<item>
<property name="text">
<string>Devices</string>
</property>
<property name="checkState">
<enum>Unchecked</enum>
</property>
</item>
<item>
<property name="text">
<string>Network</string>
</property>
<property name="checkState">
<enum>Unchecked</enum>
</property>
<property name="icon">
<iconset theme="folder-network">
<normaloff/>
</iconset>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer_4"> <spacer name="verticalSpacer_4">
<property name="orientation"> <property name="orientation">
@ -887,6 +784,22 @@ above the folder-view and not above the left pane.</string>
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QLabel" name="warningLabel">
<property name="styleSheet">
<string notr="true">background-color:#7d0000; color:white; font-weight:bold; border-radius:3px; margin:2px; padding:5px;</string>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="text">
<string>Application restart is needed for changes to take effect.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation"> <property name="orientation">

@ -36,6 +36,8 @@ PreferencesDialog::PreferencesDialog(QString activePage, QWidget* parent):
QDialog(parent) { QDialog(parent) {
ui.setupUi(this); ui.setupUi(this);
setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose);
warningCounter_ = 0;
ui.warningLabel->hide();
// resize the list widget according to the width of its content. // resize the list widget according to the width of its content.
ui.listWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); ui.listWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
@ -168,21 +170,21 @@ void PreferencesDialog::initDisplayPage(Settings& settings) {
ui.showFullNames->setChecked(settings.showFullNames()); ui.showFullNames->setChecked(settings.showFullNames());
ui.shadowHidden->setChecked(settings.shadowHidden()); ui.shadowHidden->setChecked(settings.shadowHidden());
// FIXME: Hide options that we don't support yet. // app restart warning
ui.showFullNames->hide(); connect(ui.showFullNames, &QAbstractButton::toggled, [this, &settings] (bool checked) {
ui.shadowHidden->hide(); restartWarning(settings.showFullNames() != checked);
});
connect(ui.shadowHidden, &QAbstractButton::toggled, [this, &settings] (bool checked) {
restartWarning(settings.shadowHidden() != checked);
});
} }
void PreferencesDialog::initUiPage(Settings& settings) { void PreferencesDialog::initUiPage(Settings& settings) {
ui.alwaysShowTabs->setChecked(settings.alwaysShowTabs()); ui.alwaysShowTabs->setChecked(settings.alwaysShowTabs());
ui.fullWidthTabbar->setChecked(settings.fullWidthTabBar());
ui.showTabClose->setChecked(settings.showTabClose()); ui.showTabClose->setChecked(settings.showTabClose());
ui.rememberWindowSize->setChecked(settings.rememberWindowSize()); ui.rememberWindowSize->setChecked(settings.rememberWindowSize());
ui.fixedWindowWidth->setValue(settings.fixedWindowWidth()); ui.fixedWindowWidth->setValue(settings.fixedWindowWidth());
ui.fixedWindowHeight->setValue(settings.fixedWindowHeight()); ui.fixedWindowHeight->setValue(settings.fixedWindowHeight());
// FIXME: Hide options that we don't support yet.
ui.showInPlaces->parentWidget()->hide();
} }
void PreferencesDialog::initBehaviorPage(Settings& settings) { void PreferencesDialog::initBehaviorPage(Settings& settings) {
@ -221,6 +223,11 @@ void PreferencesDialog::initBehaviorPage(Settings& settings) {
ui.confirmTrash->setChecked(settings.confirmTrash()); ui.confirmTrash->setChecked(settings.confirmTrash());
ui.quickExec->setChecked(settings.quickExec()); ui.quickExec->setChecked(settings.quickExec());
ui.selectNewFiles->setChecked(settings.selectNewFiles()); ui.selectNewFiles->setChecked(settings.selectNewFiles());
// app restart warning
connect(ui.quickExec, &QAbstractButton::toggled, [this, &settings] (bool checked) {
restartWarning(settings.quickExec() != checked);
});
} }
void PreferencesDialog::initThumbnailPage(Settings& settings) { void PreferencesDialog::initThumbnailPage(Settings& settings) {
@ -304,7 +311,6 @@ void PreferencesDialog::applyDisplayPage(Settings& settings) {
void PreferencesDialog::applyUiPage(Settings& settings) { void PreferencesDialog::applyUiPage(Settings& settings) {
settings.setAlwaysShowTabs(ui.alwaysShowTabs->isChecked()); settings.setAlwaysShowTabs(ui.alwaysShowTabs->isChecked());
settings.setFullWidthTabBar(ui.fullWidthTabbar->isChecked());
settings.setShowTabClose(ui.showTabClose->isChecked()); settings.setShowTabClose(ui.showTabClose->isChecked());
settings.setRememberWindowSize(ui.rememberWindowSize->isChecked()); settings.setRememberWindowSize(ui.rememberWindowSize->isChecked());
settings.setFixedWindowWidth(ui.fixedWindowWidth->value()); settings.setFixedWindowWidth(ui.fixedWindowWidth->value());
@ -397,4 +403,14 @@ void PreferencesDialog::lockMargins(bool lock) {
} }
} }
void PreferencesDialog::restartWarning(bool warn) {
if(warn) {
++warningCounter_;
}
else {
--warningCounter_;
}
ui.warningLabel->setVisible(warningCounter_ > 0);
}
} // namespace PCManFM } // namespace PCManFM

@ -65,8 +65,11 @@ private:
void initFromSettings(); void initFromSettings();
void applySettings(); void applySettings();
void restartWarning(bool warn);
private: private:
Ui::PreferencesDialog ui; Ui::PreferencesDialog ui;
int warningCounter_;
}; };
} }

@ -26,7 +26,8 @@
#include <QApplication> #include <QApplication>
#include "desktopwindow.h" #include "desktopwindow.h"
#include <libfm-qt/utilities.h> #include <libfm-qt/utilities.h>
#include <libfm-qt/folderconfig.h> #include <libfm-qt/core/folderconfig.h>
#include <libfm-qt/core/terminal.h>
#include <QStandardPaths> #include <QStandardPaths>
namespace PCManFM { namespace PCManFM {
@ -88,7 +89,7 @@ Settings::Settings():
splitterPos_(120), splitterPos_(120),
sidePaneMode_(Fm::SidePane::ModePlaces), sidePaneMode_(Fm::SidePane::ModePlaces),
showMenuBar_(true), showMenuBar_(true),
fullWidthTabBar_(true), splitView_(false),
viewMode_(Fm::FolderView::IconMode), viewMode_(Fm::FolderView::IconMode),
showHidden_(false), showHidden_(false),
sortOrder_(Qt::AscendingOrder), sortOrder_(Qt::AscendingOrder),
@ -231,6 +232,7 @@ bool Settings::loadFile(QString filePath) {
desktopFont_ = QApplication::font(); desktopFont_ = QApplication::font();
} }
desktopIconSize_ = settings.value("DesktopIconSize", 48).toInt(); desktopIconSize_ = settings.value("DesktopIconSize", 48).toInt();
desktopShortcuts_ = settings.value("DesktopShortcuts").toStringList();
showWmMenu_ = settings.value("ShowWmMenu", false).toBool(); showWmMenu_ = settings.value("ShowWmMenu", false).toBool();
desktopShowHidden_ = settings.value("ShowHidden", false).toBool(); desktopShowHidden_ = settings.value("ShowHidden", false).toBool();
desktopHideItems_ = settings.value("HideItems", false).toBool(); desktopHideItems_ = settings.value("HideItems", false).toBool();
@ -302,7 +304,7 @@ bool Settings::loadFile(QString filePath) {
splitterPos_ = settings.value("SplitterPos", 150).toInt(); splitterPos_ = settings.value("SplitterPos", 150).toInt();
sidePaneMode_ = sidePaneModeFromString(settings.value("SidePaneMode").toString()); sidePaneMode_ = sidePaneModeFromString(settings.value("SidePaneMode").toString());
showMenuBar_ = settings.value("ShowMenuBar", true).toBool(); showMenuBar_ = settings.value("ShowMenuBar", true).toBool();
fullWidthTabBar_ = settings.value("FullWidthTabBar", true).toBool(); splitView_ = settings.value("SplitView", false).toBool();
pathBarButtons_ = settings.value("PathBarButtons", true).toBool(); pathBarButtons_ = settings.value("PathBarButtons", true).toBool();
settings.endGroup(); settings.endGroup();
@ -361,6 +363,7 @@ bool Settings::saveFile(QString filePath) {
settings.setValue("ShadowColor", desktopShadowColor_.name()); settings.setValue("ShadowColor", desktopShadowColor_.name());
settings.setValue("Font", desktopFont_.toString()); settings.setValue("Font", desktopFont_.toString());
settings.setValue("DesktopIconSize", desktopIconSize_); settings.setValue("DesktopIconSize", desktopIconSize_);
settings.setValue("DesktopShortcuts", desktopShortcuts_);
settings.setValue("ShowWmMenu", showWmMenu_); settings.setValue("ShowWmMenu", showWmMenu_);
settings.setValue("ShowHidden", desktopShowHidden_); settings.setValue("ShowHidden", desktopShowHidden_);
settings.setValue("HideItems", desktopHideItems_); settings.setValue("HideItems", desktopHideItems_);
@ -434,7 +437,7 @@ bool Settings::saveFile(QString filePath) {
settings.setValue("SplitterPos", splitterPos_); settings.setValue("SplitterPos", splitterPos_);
settings.setValue("SidePaneMode", sidePaneModeToString(sidePaneMode_)); settings.setValue("SidePaneMode", sidePaneModeToString(sidePaneMode_));
settings.setValue("ShowMenuBar", showMenuBar_); settings.setValue("ShowMenuBar", showMenuBar_);
settings.setValue("FullWidthTabBar", fullWidthTabBar_); settings.setValue("SplitView", splitView_);
settings.setValue("PathBarButtons", pathBarButtons_); settings.setValue("PathBarButtons", pathBarButtons_);
settings.endGroup(); settings.endGroup();
@ -686,10 +689,7 @@ static Fm::SidePane::Mode sidePaneModeFromString(const QString& str) {
void Settings::setTerminal(QString terminalCommand) { void Settings::setTerminal(QString terminalCommand) {
terminal_ = terminalCommand; terminal_ = terminalCommand;
// override the settings in libfm FmConfig. Fm::setDefaultTerminal(terminal_.toStdString());
g_free(fm_config->terminal);
fm_config->terminal = g_strdup(terminal_.toLocal8Bit().constData());
g_signal_emit_by_name(fm_config, "changed::terminal");
} }
@ -730,17 +730,17 @@ FolderSettings Settings::loadFolderSettings(const Fm::FilePath& path) const {
g_free(str); g_free(str);
} }
gboolean show_hidden; bool show_hidden;
if(cfg.getBoolean("ShowHidden", &show_hidden)) { if(cfg.getBoolean("ShowHidden", &show_hidden)) {
settings.setShowHidden(show_hidden); settings.setShowHidden(show_hidden);
} }
gboolean folder_first; bool folder_first;
if(cfg.getBoolean("SortFolderFirst", &folder_first)) { if(cfg.getBoolean("SortFolderFirst", &folder_first)) {
settings.setSortFolderFirst(folder_first); settings.setSortFolderFirst(folder_first);
} }
gboolean case_sensitive; bool case_sensitive;
if(cfg.getBoolean("SortCaseSensitive", &case_sensitive)) { if(cfg.getBoolean("SortCaseSensitive", &case_sensitive)) {
settings.setSortCaseSensitive(case_sensitive); settings.setSortCaseSensitive(case_sensitive);
} }

@ -22,13 +22,13 @@
#define PCMANFM_SETTINGS_H #define PCMANFM_SETTINGS_H
#include <QObject> #include <QObject>
#include <libfm/fm.h>
#include <libfm-qt/folderview.h> #include <libfm-qt/folderview.h>
#include <libfm-qt/foldermodel.h> #include <libfm-qt/foldermodel.h>
#include "desktopwindow.h" #include "desktopwindow.h"
#include <libfm-qt/sidepane.h> #include <libfm-qt/sidepane.h>
#include <libfm-qt/core/thumbnailjob.h> #include <libfm-qt/core/thumbnailjob.h>
#include <libfm-qt/core/archiver.h> #include <libfm-qt/core/archiver.h>
#include <libfm-qt/core/legacy/fm-config.h>
namespace PCManFM { namespace PCManFM {
@ -313,6 +313,14 @@ public:
desktopIconSize_ = desktopIconSize; desktopIconSize_ = desktopIconSize;
} }
QStringList desktopShortcuts() const {
return desktopShortcuts_;
}
void setDesktopShortcuts(const QStringList& list) {
desktopShortcuts_ = list;
}
bool showWmMenu() const { bool showWmMenu() const {
return showWmMenu_; return showWmMenu_;
} }
@ -464,12 +472,12 @@ public:
showMenuBar_ = showMenuBar; showMenuBar_ = showMenuBar;
} }
bool fullWidthTabBar() const { bool splitView() const {
return fullWidthTabBar_; return splitView_;
} }
void setFullWidthTabBar(bool fullWith) { void setSplitView(bool split) {
fullWidthTabBar_ = fullWith; splitView_ = split;
} }
Fm::FolderView::ViewMode viewMode() const { Fm::FolderView::ViewMode viewMode() const {
@ -899,6 +907,7 @@ private:
QColor desktopShadowColor_; QColor desktopShadowColor_;
QFont desktopFont_; QFont desktopFont_;
int desktopIconSize_; int desktopIconSize_;
QStringList desktopShortcuts_;
bool showWmMenu_; bool showWmMenu_;
bool desktopShowHidden_; bool desktopShowHidden_;
@ -918,7 +927,7 @@ private:
int splitterPos_; int splitterPos_;
Fm::SidePane::Mode sidePaneMode_; Fm::SidePane::Mode sidePaneMode_;
bool showMenuBar_; bool showMenuBar_;
bool fullWidthTabBar_; bool splitView_;
Fm::FolderView::ViewMode viewMode_; Fm::FolderView::ViewMode viewMode_;
bool showHidden_; bool showHidden_;

@ -29,23 +29,31 @@ namespace PCManFM {
TabBar::TabBar(QWidget *parent): TabBar::TabBar(QWidget *parent):
QTabBar(parent), QTabBar(parent),
dragStarted_(false) dragStarted_(false),
detachable_(true)
{ {
} }
void TabBar::mousePressEvent(QMouseEvent *event) { void TabBar::mousePressEvent(QMouseEvent *event) {
QTabBar::mousePressEvent (event); QTabBar::mousePressEvent (event);
if(detachable_){
if(event->button() == Qt::LeftButton if(event->button() == Qt::LeftButton
&& tabAt(event->pos()) > -1) { && tabAt(event->pos()) > -1) {
dragStartPosition_ = event->pos(); dragStartPosition_ = event->pos();
} }
dragStarted_ = false; dragStarted_ = false;
} }
}
void TabBar::mouseMoveEvent(QMouseEvent *event) void TabBar::mouseMoveEvent(QMouseEvent *event)
{ {
if(!detachable_) {
QTabBar::mouseMoveEvent(event);
return;
}
if(!dragStartPosition_.isNull() if(!dragStartPosition_.isNull()
&& (event->pos() - dragStartPosition_).manhattanLength() < QApplication::startDragDistance()) { && (event->pos() - dragStartPosition_).manhattanLength() >= QApplication::startDragDistance()) {
dragStarted_ = true; dragStarted_ = true;
} }
@ -102,7 +110,7 @@ void TabBar::mouseReleaseEvent(QMouseEvent *event) {
// Let the main window receive dragged tabs! // Let the main window receive dragged tabs!
void TabBar::dragEnterEvent(QDragEnterEvent *event) { void TabBar::dragEnterEvent(QDragEnterEvent *event) {
if(event->mimeData()->hasFormat("application/pcmanfm-qt-tab")) { if(detachable_ && event->mimeData()->hasFormat("application/pcmanfm-qt-tab")) {
event->ignore(); event->ignore();
} }
} }

@ -35,6 +35,10 @@ public:
void finishMouseMoveEvent(); void finishMouseMoveEvent();
void releaseMouse(); void releaseMouse();
void setDetachable(bool detachable) {
detachable_ = detachable;
}
Q_SIGNALS: Q_SIGNALS:
void tabDetached(); void tabDetached();
@ -48,6 +52,7 @@ protected:
private: private:
QPoint dragStartPosition_; QPoint dragStartPosition_;
bool dragStarted_; bool dragStarted_;
bool detachable_;
}; };
} }

@ -30,6 +30,8 @@
#include <QCursor> #include <QCursor>
#include <QMessageBox> #include <QMessageBox>
#include <QScrollBar> #include <QScrollBar>
#include <QToolButton>
#include <QLabel>
#include <QDir> #include <QDir>
#include "settings.h" #include "settings.h"
#include "application.h" #include "application.h"
@ -52,6 +54,50 @@ bool ProxyFilter::filterAcceptsRow(const Fm::ProxyFolderModel* model, const std:
return true; return true;
} }
//==================================================
FilterEdit::FilterEdit(QWidget* parent) : QLineEdit(parent) {
setClearButtonEnabled(true);
if(QToolButton *clearButton = findChild<QToolButton*>()) {
clearButton->setToolTip(tr("Clear text (Ctrl+K)"));
}
}
void FilterEdit::keyPressEvent(QKeyEvent* event) {
// since two views can be shown in the split mode, Ctrl+K can't be
// used as a QShortcut but can come here for clearing the text
if(event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_K) {
clear();
}
QLineEdit::keyPressEvent(event);
}
void FilterEdit::keyPressed(QKeyEvent* event) {
// NOTE: Movement and delete keys should be left to the view.
// Copy/paste shortcuts are taken by the view but they aren't needed here
// (Shift+Insert works for pasting but, since most users may not be familiar
// with it, an action is added to the main window for focusing an empty bar).
if(!hasFocus()
&& event->key() != Qt::Key_Left && event->key() != Qt::Key_Right
&& event->key() != Qt::Key_Home && event->key() != Qt::Key_End
&& event->key() != Qt::Key_Delete) {
keyPressEvent(event);
}
}
FilterBar::FilterBar(QWidget* parent) : QWidget(parent) {
QHBoxLayout* HLayout = new QHBoxLayout(this);
HLayout->setSpacing(5);
filterEdit_ = new FilterEdit();
QLabel *label = new QLabel(tr("Filter:"));
HLayout->addWidget(label);
HLayout->addWidget(filterEdit_);
connect(filterEdit_, &QLineEdit::textChanged, this, &FilterBar::textChanged);
connect(filterEdit_, &FilterEdit::lostFocus, this, &FilterBar::lostFocus);
}
//==================================================
TabPage::TabPage(QWidget* parent): TabPage::TabPage(QWidget* parent):
QWidget(parent), QWidget(parent),
folderView_{nullptr}, folderView_{nullptr},
@ -60,7 +106,8 @@ TabPage::TabPage(QWidget* parent):
proxyFilter_{nullptr}, proxyFilter_{nullptr},
verticalLayout{nullptr}, verticalLayout{nullptr},
overrideCursor_(false), overrideCursor_(false),
selectionTimer_(nullptr) { selectionTimer_(nullptr),
filterBar_(nullptr) {
Settings& settings = static_cast<Application*>(qApp)->settings(); Settings& settings = static_cast<Application*>(qApp)->settings();
@ -76,6 +123,7 @@ TabPage::TabPage(QWidget* parent):
folderView_ = new View(settings.viewMode(), this); folderView_ = new View(settings.viewMode(), this);
folderView_->setMargins(settings.folderViewCellMargins()); folderView_->setMargins(settings.folderViewCellMargins());
folderView_->setShadowHidden(settings.shadowHidden());
// newView->setColumnWidth(Fm::FolderModel::ColumnName, 200); // newView->setColumnWidth(Fm::FolderModel::ColumnName, 200);
connect(folderView_, &View::openDirRequested, this, &TabPage::openDirRequested); connect(folderView_, &View::openDirRequested, this, &TabPage::openDirRequested);
connect(folderView_, &View::selChanged, this, &TabPage::onSelChanged); connect(folderView_, &View::selChanged, this, &TabPage::onSelChanged);
@ -88,6 +136,14 @@ TabPage::TabPage(QWidget* parent):
// FIXME: this is very dirty // FIXME: this is very dirty
folderView_->setModel(proxyModel_); folderView_->setModel(proxyModel_);
verticalLayout->addWidget(folderView_); verticalLayout->addWidget(folderView_);
// filter-bar and its settings
filterBar_ = new FilterBar();
verticalLayout->addWidget(filterBar_);
if(!settings.showFilter()){
transientFilterBar(true);
}
connect(filterBar_, &FilterBar::textChanged, this, &TabPage::onFilterStringChanged);
} }
TabPage::~TabPage() { TabPage::~TabPage() {
@ -109,6 +165,78 @@ TabPage::~TabPage() {
} }
} }
void TabPage::transientFilterBar(bool transient) {
if(filterBar_) {
filterBar_->clear();
if(transient) {
filterBar_->hide();
folderView_->childView()->removeEventFilter(this);
folderView_->childView()->installEventFilter(this);
connect(filterBar_, &FilterBar::lostFocus, this, &TabPage::onLosingFilterBarFocus);
}
else {
filterBar_->show();
folderView_->childView()->removeEventFilter(this);
disconnect(filterBar_, &FilterBar::lostFocus, this, &TabPage::onLosingFilterBarFocus);
}
}
}
void TabPage::onLosingFilterBarFocus() {
// hide the empty transient filter-bar when it loses focus
if(getFilterStr().isEmpty()) {
filterBar_->hide();
}
}
void TabPage::showFilterBar() {
if(filterBar_) {
filterBar_->show();
if(isVisibleTo(this)) { // the page itself may be in an inactive tab
filterBar_->focusBar();
}
}
}
bool TabPage::eventFilter(QObject* watched, QEvent* event) {
// when a text is typed inside the view, type it inside the filter-bar
if(filterBar_ && watched == folderView_->childView() && event->type() == QEvent::KeyPress) {
if(QKeyEvent* ke = static_cast<QKeyEvent*>(event)) {
filterBar_->keyPressed(ke);
}
}
return QWidget::eventFilter(watched, event);
}
void TabPage::backspacePressed() {
if(filterBar_ && filterBar_->isVisible()) {
QKeyEvent bs = QKeyEvent(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier);
filterBar_->keyPressed(&bs);
}
}
void TabPage::onFilterStringChanged(QString str) {
if(filterBar_ && str != getFilterStr()) {
setFilterStr(str);
applyFilter();
// show/hide the transient filter-bar appropriately
if(!static_cast<Application*>(qApp)->settings().showFilter()) {
if(filterBar_->isVisibleTo(this)) { // the page itself may be in an inactive tab
if(str.isEmpty()) {
// focus the view BEFORE hiding the filter-bar to avoid redundant "FocusIn" events;
// otherwise, another widget inside the main window might gain focus immediately
// after the filter-bar is hidden and only after that, the view will be focused.
folderView()->childView()->setFocus();
filterBar_->hide();
}
}
else if(!str.isEmpty()) {
filterBar_->show();
}
}
}
}
void TabPage::freeFolder() { void TabPage::freeFolder() {
if(folder_) { if(folder_) {
if(folderSettings_.isCustomized()) { if(folderSettings_.isCustomized()) {
@ -285,13 +413,9 @@ void TabPage::onFolderFsInfo() {
guint64 free, total; guint64 free, total;
QString& msg = statusText_[StatusTextFSInfo]; QString& msg = statusText_[StatusTextFSInfo];
if(folder_->getFilesystemInfo(&total, &free)) { if(folder_->getFilesystemInfo(&total, &free)) {
char total_str[64];
char free_str[64];
fm_file_size_to_str(free_str, sizeof(free_str), free, fm_config->si_unit);
fm_file_size_to_str(total_str, sizeof(total_str), total, fm_config->si_unit);
msg = tr("Free space: %1 (Total: %2)") msg = tr("Free space: %1 (Total: %2)")
.arg(QString::fromUtf8(free_str), .arg(formatFileSize(free, fm_config->si_unit))
QString::fromUtf8(total_str)); .arg(formatFileSize(total, fm_config->si_unit));
} }
else { else {
msg.clear(); msg.clear();
@ -340,21 +464,12 @@ void TabPage::onFolderRemoved() {
void TabPage::onFolderUnmount() { void TabPage::onFolderUnmount() {
// the folder we're showing is unmounted, destroy the widget // the folder we're showing is unmounted, destroy the widget
qDebug("folder unmount"); qDebug("folder unmount");
// NOTE: call deleteLater() directly from this GObject signal handler // NOTE: We cannot delete the page or change its directory here
// does not work but I don't know why. // because unmounting might be done from places view, in which case,
// Maybe it's the problem of glib mainloop integration? // the mount operation is a child of the places view and should be
// Call it when idle works, though. // finished before doing anything else.
Settings& settings = static_cast<Application*>(qApp)->settings(); freeFolder();
// NOTE: call deleteLater() directly from this GObject signal handler Q_EMIT folderUnmounted();
// does not work but I don't know why.
// Maybe it's the problem of glib mainloop integration?
// Call it when idle works, though.
if(settings.closeOnUnmount()) {
QTimer::singleShot(0, this, SLOT(deleteLater()));
}
else {
chdir(Fm::FilePath::homeDir());
}
} }
void TabPage::onFolderContentChanged() { void TabPage::onFolderContentChanged() {
@ -372,6 +487,9 @@ QString TabPage::pathName() {
void TabPage::chdir(Fm::FilePath newPath, bool addHistory) { void TabPage::chdir(Fm::FilePath newPath, bool addHistory) {
// qDebug() << "TABPAGE CHDIR:" << newPath.toString().get(); // qDebug() << "TABPAGE CHDIR:" << newPath.toString().get();
if(filterBar_){
filterBar_->clear();
}
if(folder_) { if(folder_) {
// we're already in the specified dir // we're already in the specified dir
if(newPath == folder_->path()) { if(newPath == folder_->path()) {
@ -421,10 +539,11 @@ void TabPage::chdir(Fm::FilePath newPath, bool addHistory) {
connect(folder_.get(), &Fm::Folder::unmount, this, &TabPage::onFolderUnmount); connect(folder_.get(), &Fm::Folder::unmount, this, &TabPage::onFolderUnmount);
connect(folder_.get(), &Fm::Folder::contentChanged, this, &TabPage::onFolderContentChanged); connect(folder_.get(), &Fm::Folder::contentChanged, this, &TabPage::onFolderContentChanged);
Settings& settings = static_cast<Application*>(qApp)->settings();
folderModel_ = CachedFolderModel::modelFromFolder(folder_); folderModel_ = CachedFolderModel::modelFromFolder(folder_);
folderModel_->setShowFullName(settings.showFullNames());
// set sorting, considering customized folders // set sorting, considering customized folders
Settings& settings = static_cast<Application*>(qApp)->settings();
folderSettings_ = settings.loadFolderSettings(path()); folderSettings_ = settings.loadFolderSettings(path());
proxyModel_->sort(folderSettings_.sortColumn(), folderSettings_.sortOrder()); proxyModel_->sort(folderSettings_.sortColumn(), folderSettings_.sortOrder());
proxyModel_->setFolderFirst(folderSettings_.sortFolderFirst()); proxyModel_->setFolderFirst(folderSettings_.sortFolderFirst());
@ -432,7 +551,7 @@ void TabPage::chdir(Fm::FilePath newPath, bool addHistory) {
proxyModel_->setSortCaseSensitivity(folderSettings_.sortCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive); proxyModel_->setSortCaseSensitivity(folderSettings_.sortCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive);
proxyModel_->setSourceModel(folderModel_); proxyModel_->setSourceModel(folderModel_);
folderView_->setViewMode(folderSettings_.viewMode()); setViewMode(folderSettings_.viewMode());
if(folder_->isLoaded()) { if(folder_->isLoaded()) {
onFolderStartLoading(); onFolderStartLoading();
@ -600,13 +719,22 @@ void TabPage::updateFromSettings(Settings& settings) {
} }
void TabPage::setViewMode(Fm::FolderView::ViewMode mode) { void TabPage::setViewMode(Fm::FolderView::ViewMode mode) {
Settings& settings = static_cast<Application*>(qApp)->settings();
if(folderSettings_.viewMode() != mode) { if(folderSettings_.viewMode() != mode) {
folderSettings_.setViewMode(mode); folderSettings_.setViewMode(mode);
if(folderSettings_.isCustomized()) { if(folderSettings_.isCustomized()) {
static_cast<Application*>(qApp)->settings().saveFolderSettings(path(), folderSettings_); settings.saveFolderSettings(path(), folderSettings_);
} }
} }
Fm::FolderView::ViewMode prevMode = folderView_->viewMode();
folderView_->setViewMode(mode); folderView_->setViewMode(mode);
folderView_->childView()->setFocus();
if(!settings.showFilter() && prevMode != folderView_->viewMode()) {
// FolderView::setViewMode() may delete the view to switch between list and tree.
// So, the event filter should be re-installed.
folderView_->childView()->removeEventFilter(this);
folderView_->childView()->installEventFilter(this);
}
} }
void TabPage::sort(int col, Qt::SortOrder order) { void TabPage::sort(int col, Qt::SortOrder order) {
@ -665,7 +793,12 @@ void TabPage::applyFilter() {
if(!proxyModel_) { if(!proxyModel_) {
return; return;
} }
int prevSelSize = folderView_->selectionModel()->selectedIndexes().size();
proxyModel_->updateFilters(); proxyModel_->updateFilters();
// if some selected files are filtered out, "View::selChanged()" won't be emitted
if(prevSelSize > folderView_->selectionModel()->selectedIndexes().size()) {
onSelChanged();
}
statusText_[StatusTextNormal] = formatStatusText(); statusText_[StatusTextNormal] = formatStatusText();
Q_EMIT statusChanged(StatusTextNormal, statusText_[StatusTextNormal]); Q_EMIT statusChanged(StatusTextNormal, statusText_[StatusTextNormal]);
} }

@ -23,7 +23,7 @@
#include <QWidget> #include <QWidget>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <libfm/fm.h> #include <QLineEdit>
#include <libfm-qt/browsehistory.h> #include <libfm-qt/browsehistory.h>
#include "view.h" #include "view.h"
#include "settings.h" #include "settings.h"
@ -58,6 +58,52 @@ private:
QString filterStr_; QString filterStr_;
}; };
//==================================================
class FilterEdit : public QLineEdit {
Q_OBJECT
public:
FilterEdit(QWidget *parent = nullptr);
~FilterEdit() {};
void keyPressed(QKeyEvent* event);
protected:
virtual void focusOutEvent(QFocusEvent* event) override {
Q_EMIT lostFocus();
QLineEdit::focusOutEvent(event);
}
virtual void keyPressEvent(QKeyEvent* event) override;
Q_SIGNALS:
void lostFocus();
};
class FilterBar : public QWidget {
Q_OBJECT
public:
FilterBar(QWidget *parent = nullptr);
~FilterBar() {};
void focusBar() {
filterEdit_->setFocus();
}
void clear() {
filterEdit_->clear();
}
void keyPressed(QKeyEvent* event) {
filterEdit_->keyPressed(event);
}
Q_SIGNALS:
void textChanged(const QString &text);
void lostFocus();
private:
FilterEdit* filterEdit_;
};
//==================================================
class TabPage : public QWidget { class TabPage : public QWidget {
Q_OBJECT Q_OBJECT
@ -197,6 +243,20 @@ public:
void setCustomizedView(bool value); void setCustomizedView(bool value);
void transientFilterBar(bool transient);
void showFilterBar();
bool isFilterBarVisible() const {
return (filterBar_ && filterBar_->isVisible());
}
void clearFilter() {
if(filterBar_) {
filterBar_->clear();
}
}
void backspacePressed();
Q_SIGNALS: Q_SIGNALS:
void statusChanged(int type, QString statusText); void statusChanged(int type, QString statusText);
void titleChanged(QString title); void titleChanged(QString title);
@ -204,12 +264,18 @@ Q_SIGNALS:
void sortFilterChanged(); void sortFilterChanged();
void forwardRequested(); void forwardRequested();
void backwardRequested(); void backwardRequested();
void folderUnmounted();
protected:
virtual bool eventFilter(QObject* watched, QEvent* event);
protected Q_SLOTS: protected Q_SLOTS:
void onSelChanged(); void onSelChanged();
void onUiUpdated(); void onUiUpdated();
void onFileSizeChanged(const QModelIndex& index); void onFileSizeChanged(const QModelIndex& index);
void onFilesAdded(const Fm::FileInfoList files); void onFilesAdded(const Fm::FileInfoList files);
void onFilterStringChanged(QString str);
void onLosingFilterBarFocus();
private: private:
void freeFolder(); void freeFolder();
@ -242,6 +308,7 @@ private:
bool overrideCursor_; bool overrideCursor_;
FolderSettings folderSettings_; FolderSettings folderSettings_;
QTimer* selectionTimer_; QTimer* selectionTimer_;
FilterBar* filterBar_;
}; };
} }

@ -0,0 +1,3 @@
project(pcmanfm-qt)
build_component("." "${CMAKE_INSTALL_FULL_DATADIR}/pcmanfm-qt/translations")

@ -0,0 +1,4 @@
#Translations
Name[cs]=Pracovní plocha
GenericName[cs]=Nastavení pracovní plochy
Comment[cs]=Změna pozadí plochy a chování jejího správce

@ -0,0 +1,4 @@
#Translations
Name[ja]=デスクトップ
GenericName[ja]=デスクトップ設定
Comment[ja]=壁紙やその他のデスクトップ設定を変更します

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,4 @@
#Translations
Name[cs]=PCManFM-Qt
GenericName[cs]=Správce souborů
Comment[cs]=Procházejte souborový systém a spravujte soubory

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,4 @@
#Translations
Name[ja]=PCManFM-Qt ファイルマネージャ
GenericName[ja]=ファイルマネージャ
Comment[ja]=LXQt環境のファイル管理をします

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save