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 5 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
==============================
@ -50,8 +73,8 @@ pcmanfm-qt-0.13.0 / 2018-05-21
* Tab DND
* View tool-buttons
0.12.0 / 2017-10-21
===================
pcmanfm-qt-0.12.0 / 2017-10-21
==============================
* Release 0.12.0: Update changelog
* Set Version
@ -136,8 +159,8 @@ pcmanfm-qt-0.13.0 / 2018-05-21
* Use const iterators
* 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
* 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.
* Update AUTHORS
0.11.2 / 2016-12-21
===================
pcmanfm-qt-0.11.2 / 2016-12-21
==============================
* Release 0.11.2: Update changelog
* Use static_cast instead of the C style cast
@ -179,8 +202,8 @@ pcmanfm-qt-0.13.0 / 2018-05-21
* Add Catalan translations
* 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
* 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
* 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
* 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).
* 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
* 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)
# PcmanFm-Qt Version
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 ${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(LXQTBT_MINIMUM_VERSION "0.5.0")
set(LIBFMQT_MINIMUM_VERSION "5.0.0")
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(Qt5LinguistTools ${QT_MINIMUM_VERSION} REQUIRED)
find_package(Qt5Widgets ${QT_MINIMUM_VERSION} REQUIRED)
find_package(Qt5X11Extras ${QT_MINIMUM_VERSION} REQUIRED)
find_package(fm-qt ${LIBFMQT_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}")
endif()
# merged from lxqt-common
add_subdirectory(autostart)
add_subdirectory(config)

@ -60,3 +60,10 @@ All switches (command line options) mentioned above are explained in detail in
## Development
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)
# 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
* 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>
Section: x11
Priority: optional
Build-Depends: debhelper (>= 11~),
Build-Depends: debhelper-compat (= 12),
libexif-dev,
libfm-qt-dev (>= 0.13.1~),
libfm-qt-dev (>= 0.14.0~),
libkf5windowsystem-dev,
libmenu-cache-dev,
libqt5svg5-dev,
libqt5x11extras5-dev,
libx11-dev,
lxqt-build-tools (>= 0.5.0~),
Standards-Version: 4.1.5
lxqt-build-tools (>= 0.6.0~),
Standards-Version: 4.3.0
Vcs-Browser: https://salsa.debian.org/lxqt-team/pcmanfm-qt
Vcs-Git: https://salsa.debian.org/lxqt-team/pcmanfm-qt.git
Homepage: https://github.com/lxqt/pcmanfm-qt
@ -27,8 +27,7 @@ Depends: ${misc:Depends},
${shlibs:Depends},
default-dbus-session-bus | dbus-session-bus | dbus-x11,
desktop-file-utils,
libfm-modules,
libfm-qt5 (>= 0.13.1~),
libfm-qt6 (>= 0.14.0~),
lxqt-sudo
Recommends: eject,
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
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
Files: *
Copyright: 2013-2018 LXQt team
Copyright: 2013-2019 LXQt team
2013-2018 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
2012-2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
2014 Kuzma Shapran <kuzma.shapran@gmail.com>
@ -12,7 +12,7 @@ License: GPL-2.0+
Files: debian/*
Copyright: 2014-2015 Wen Liao <wen.cf83@gmail.com>
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>
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

8
debian/rules vendored

@ -8,8 +8,10 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all
%:
dh ${@} --buildsystem cmake
override_dh_missing:
dh_missing --fail-missing
override_dh_auto_configure:
dh_auto_configure --\
-DPULL_TRANSLATIONS=OFF\
-DUPDATE_TRANSLATIONS=OFF\
dh_auto_configure -- \
-DUPDATE_TRANSLATIONS=OFF \
-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}
SOURCES ${pcmanfm_SRCS} ${pcmanfm_UIS}
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
@ -72,7 +68,6 @@ target_compile_definitions(pcmanfm-qt
PCMANFM_DATA_DIR="${CMAKE_INSTALL_PREFIX}/share/pcmanfm-qt"
PCMANFM_QT_VERSION="${PCMANFM_QT_VERSION}"
LIBFM_DATA_DIR="${PKG_FM_PREFIX}/share/libfm"
QT_NO_FOREACH
)
target_include_directories(pcmanfm-qt

@ -24,7 +24,6 @@
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDir>
#include <QDesktopWidget>
#include <QVector>
#include <QLocale>
#include <QLibraryInfo>
@ -43,6 +42,8 @@
#include <libfm-qt/mountoperation.h>
#include <libfm-qt/filesearchdialog.h>
#include <libfm-qt/core/terminal.h>
#include <libfm-qt/core/bookmarks.h>
#include <libfm-qt/core/folderconfig.h>
#include "applicationadaptor.h"
#include "preferencesdialog.h"
@ -93,7 +94,7 @@ Application::Application(int& argc, char** argv):
// we successfully registered the service
isPrimaryInstance = true;
setStyle(new ProxyStyle());
desktop()->installEventFilter(this);
//desktop()->installEventFilter(this);
new ApplicationAdaptor(this);
dbus.registerObject("/Application", this);
@ -101,14 +102,6 @@ Application::Application(int& argc, char** argv):
connect(this, &Application::aboutToQuit, this, &Application::onAboutToQuit);
// aboutToQuit() is not signalled on SIGTERM, install signal handler
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
// editor. We just hide our editor when LXQt is running.
@ -133,7 +126,7 @@ Application::Application(int& argc, char** argv):
}
Application::~Application() {
desktop()->removeEventFilter(this);
//desktop()->removeEventFilter(this);
if(volumeMonitor_) {
g_signal_handlers_disconnect_by_func(volumeMonitor_, gpointer(onVolumeAdded), this);
@ -212,9 +205,20 @@ bool Application::parseCommandLineArgs() {
profileName_ = parser.value(profileOption);
}
// load settings
// load app config
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
if(parser.isSet(desktopOption)) {
desktopManager(true);
@ -363,7 +367,7 @@ void Application::onAboutToQuit() {
settings_.save();
}
bool Application::eventFilter(QObject* watched, QEvent* event) {
/*bool Application::eventFilter(QObject* watched, QEvent* event) {
if(watched == desktop()) {
if(event->type() == QEvent::StyleChange ||
event->type() == QEvent::ThemeChange) {
@ -371,7 +375,7 @@ bool Application::eventFilter(QObject* watched, QEvent* event) {
}
}
return QObject::eventFilter(watched, event);
}
}*/
void Application::onLastWindowClosed() {
@ -383,29 +387,28 @@ void Application::onSaveStateRequest(QSessionManager& /*manager*/) {
void Application::desktopManager(bool enabled) {
// TODO: turn on or turn off desktpo management (desktop icons & wallpaper)
qDebug("desktopManager: %d", enabled);
QDesktopWidget* desktopWidget = desktop();
//qDebug("desktopManager: %d", enabled);
if(enabled) {
if(!enableDesktopManager_) {
// installNativeEventFilter(this);
const auto allScreens = screens();
for(QScreen* screen : allScreens) {
connect(screen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
connect(screen, &QScreen::availableGeometryChanged, this, &Application::onAvailableGeometryChanged);
connect(screen, &QObject::destroyed, this, &Application::onScreenDestroyed);
}
connect(this, &QApplication::screenAdded, this, &Application::onScreenAdded);
connect(desktopWidget, &QDesktopWidget::resized, this, &Application::onScreenResized);
connect(desktopWidget, &QDesktopWidget::screenCountChanged, this, &Application::onScreenCountChanged);
connect(this, &QApplication::screenRemoved, this, &Application::onScreenRemoved);
// NOTE: there are two modes
// 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.
if(desktopWidget->isVirtualDesktop()) {
if(primaryScreen() && primaryScreen()->virtualSiblings().size() > 1) {
DesktopWindow* window = createDesktopWindow(-1);
desktopWindows_.push_back(window);
}
else {
int n = desktopWidget->numScreens();
int n = qMax(allScreens.size(), 1);
desktopWindows_.reserve(n);
for(int i = 0; i < n; ++i) {
DesktopWindow* window = createDesktopWindow(i);
@ -416,8 +419,6 @@ void Application::desktopManager(bool enabled) {
}
else {
if(enableDesktopManager_) {
disconnect(desktopWidget, &QDesktopWidget::resized, this, &Application::onScreenResized);
disconnect(desktopWidget, &QDesktopWidget::screenCountChanged, this, &Application::onScreenCountChanged);
int n = desktopWindows_.size();
for(int i = 0; i < n; ++i) {
DesktopWindow* window = desktopWindows_.at(i);
@ -427,9 +428,11 @@ void Application::desktopManager(bool enabled) {
const auto allScreens = screens();
for(QScreen* screen : allScreens) {
disconnect(screen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
disconnect(screen, &QScreen::availableGeometryChanged, this, &Application::onAvailableGeometryChanged);
disconnect(screen, &QObject::destroyed, this, &Application::onScreenDestroyed);
}
disconnect(this, &QApplication::screenAdded, this, &Application::onScreenAdded);
disconnect(this, &QApplication::screenRemoved, this, &Application::onScreenRemoved);
// removeNativeEventFilter(this);
}
}
@ -470,7 +473,7 @@ void Application::onConnectToServerAccepted() {
ConnectServerDialog* dlg = static_cast<ConnectServerDialog*>(sender());
QString uri = dlg->uriText();
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();
Launcher(window).launchPaths(nullptr, paths);
}
@ -586,93 +589,41 @@ void Application::setWallpaper(QString path, QString modeString) {
// update wallpaper
if(changed) {
if(enableDesktopManager_) {
for(DesktopWindow* desktopWindow : qAsConst(desktopWindows_)) {
for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
if(!path.isEmpty()) {
desktopWindow->setWallpaperFile(path);
desktopWin->setWallpaperFile(path);
}
if(mode != settings_.wallpaperMode()) {
desktopWindow->setWallpaperMode(mode);
desktopWin->setWallpaperMode(mode);
}
desktopWindow->updateWallpaper();
desktopWin->updateWallpaper();
}
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* window = new DesktopWindow(screenNum);
if(screenNum == -1) { // one large virtual desktop only
QRect rect = desktop()->geometry();
QRect rect = primaryScreen()->virtualGeometry();
window->setGeometry(rect);
}
else {
QRect rect = desktop()->screenGeometry(screenNum);
QRect rect;
const auto allScreens = screens();
if(auto screen = window->getDesktopScreen()) {
rect = screen->geometry();
}
window->setGeometry(rect);
}
window->updateFromSettings(settings_);
window->show();
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
void Application::updateFromSettings() {
// if(iconTheme.isEmpty())
@ -696,16 +647,14 @@ void Application::updateFromSettings() {
void Application::updateDesktopsFromSettings(bool changeSlide) {
QVector<DesktopWindow*>::iterator it;
for(it = desktopWindows_.begin(); it != desktopWindows_.end(); ++it) {
DesktopWindow* desktopWindow = static_cast<DesktopWindow*>(*it);
desktopWindow->updateFromSettings(settings_, changeSlide);
DesktopWindow* desktopWin = static_cast<DesktopWindow*>(*it);
desktopWin->updateFromSettings(settings_, changeSlide);
}
}
void Application::editBookmarks() {
if(!editBookmarksialog_) {
FmBookmarks* bookmarks = fm_bookmarks_dup();
editBookmarksialog_ = new Fm::EditBookmarksDialog(bookmarks);
g_object_unref(bookmarks);
editBookmarksialog_ = new Fm::EditBookmarksDialog(Fm::Bookmarks::globalInstance());
}
editBookmarksialog_.data()->show();
}
@ -780,7 +729,58 @@ bool Application::nativeEventFilter(const QByteArray& eventType, void* message,
void Application::onScreenAdded(QScreen* newScreen) {
if(enableDesktopManager_) {
connect(newScreen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
connect(newScreen, &QScreen::availableGeometryChanged, this, &Application::onAvailableGeometryChanged);
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_) {
bool reloadNeeded = false;
// FIXME: add workarounds for Qt5 bug #40681 and #40791 here.
for(DesktopWindow* desktop : qAsConst(desktopWindows_)) {
if(desktop->windowHandle()->screen() == screenObj) {
desktop->destroy(); // destroy the underlying native window
for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
if(desktopWin->windowHandle()->screen() == screenObj) {
desktopWin->destroy(); // destroy the underlying native window
reloadNeeded = true;
}
}
@ -827,31 +827,33 @@ void Application::onScreenDestroyed(QObject* screenObj) {
void Application::reloadDesktopsAsNeeded() {
if(enableDesktopManager_) {
// workarounds for Qt5 bug #40681 and #40791 here.
for(DesktopWindow* desktop : qAsConst(desktopWindows_)) {
if(!desktop->windowHandle()) {
desktop->create(); // re-create the underlying native window
desktop->queueRelayout();
desktop->show();
for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
if(!desktopWin->windowHandle()) {
desktopWin->create(); // re-create the underlying native window
desktopWin->queueRelayout();
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*/) {
// NOTE: the following is a workaround for Qt bug 32567.
// https://bugreports.qt-project.org/browse/QTBUG-32567
// Though the status of the bug report is closed, it's not yet fixed for X11.
// In theory, QDesktopWidget should emit "workAreaResized()" signal when the work area
// of any screen is changed, but in fact it does not do it.
// However, QScreen provided since Qt5 does not have the bug and
// virtualGeometryChanged() is emitted correctly when the workAreas changed.
// So we use it in Qt5.
// update desktop geometries
if(enableDesktopManager_) {
for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
auto desktopScreen = desktopWin->getDesktopScreen();
if(desktopScreen) {
desktopWin->setGeometry(desktopScreen->virtualGeometry());
}
}
}
}
void Application::onAvailableGeometryChanged(const QRect& /*rect*/) {
// update desktop layouts
if(enableDesktopManager_) {
// qDebug() << "onVirtualGeometryChanged";
for(DesktopWindow* desktop : qAsConst(desktopWindows_)) {
desktop->queueRelayout();
for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
desktopWin->queueRelayout();
}
}
}

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

@ -173,7 +173,7 @@
</sizepolicy>
</property>
<property name="text">
<string>Select text color:</string>
<string>Select text color:</string>
</property>
</widget>
</item>
@ -453,6 +453,59 @@ A space is also reserved for 3 lines of text.</string>
<string>Advanced</string>
</attribute>
<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>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">

@ -104,6 +104,13 @@ DesktopPreferencesDialog::DesktopPreferencesDialog(QWidget* parent, Qt::WindowFl
ui.backgroundColor->setColor(settings.desktopBgColor());
ui.textColor->setColor(settings.desktopFgColor());
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());
connect(ui.buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked,
@ -167,6 +174,22 @@ void DesktopPreferencesDialog::applySettings()
settings.setDesktopBgColor(ui.backgroundColor->color());
settings.setDesktopFgColor(ui.textColor->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.setDesktopCellMargins(QSize(ui.hMargin->value(), ui.vMargin->value()));

@ -19,7 +19,6 @@
#include "desktopwindow.h"
#include <QWidget>
#include <QDesktopWidget>
#include <QPainter>
#include <QImage>
#include <QImageReader>
@ -39,6 +38,8 @@
#include <QMimeData>
#include <QPaintEvent>
#include <QStandardPaths>
#include <QClipboard>
#include <QWindow>
#include "./application.h"
#include "mainwindow.h"
@ -59,6 +60,7 @@
#include <xcb/xcb.h>
#include <X11/Xlib.h>
#define WORK_AREA_MARGIN 12 // margin of the work area
#define MIN_SLIDE_INTERVAL 5*60000 // 5 min
#define MAX_SLIDE_INTERVAL (24*60+55)*60000 // 24 h and 55 min
@ -77,9 +79,10 @@ DesktopWindow::DesktopWindow(int screenNum):
desktopHideItems_(false),
screenNum_(screenNum),
relayoutTimer_(nullptr),
selectionTimer_(nullptr) {
selectionTimer_(nullptr),
trashUpdateTimer_(nullptr),
trashMonitor_(nullptr) {
QDesktopWidget* desktopWidget = QApplication::desktop();
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
setAttribute(Qt::WA_X11NetWmWindowTypeDesktop);
setAttribute(Qt::WA_DeleteOnClose);
@ -91,6 +94,7 @@ DesktopWindow::DesktopWindow(int screenNum):
listView_->setMovement(QListView::Snap);
listView_->setResizeMode(QListView::Adjust);
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
// 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.
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.
// 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.
// 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();
Settings& settings = static_cast<Application* >(qApp)->settings();
setShadowHidden(settings.shadowHidden());
auto desktopPath = Fm::FilePath::fromLocalPath(XdgDir::readDesktopDir().toStdString().c_str());
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::sortFilterChanged, this, &DesktopWindow::onModelSortFilterChanged);
connect(proxyModel_, &Fm::ProxyFolderModel::dataChanged, this, &DesktopWindow::onDataChanged);
connect(listView_, &QListView::indexesMoved, this, &DesktopWindow::onIndexesMoved);
}
// remove frame
@ -148,6 +154,9 @@ DesktopWindow::DesktopWindow(int screenNum):
shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_C), this); // copy
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
connect(shortcut, &QShortcut::activated, this, &DesktopWindow::onPasteActivated);
@ -171,6 +180,12 @@ DesktopWindow::DesktopWindow(int screenNum):
}
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_->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) {
bgColor_ = color;
}
@ -505,6 +735,7 @@ void DesktopWindow::updateFromSettings(Settings& settings, bool changeSlide) {
setFont(settings.desktopFont());
setIconSize(Fm::FolderView::IconMode, QSize(settings.desktopIconSize(), settings.desktopIconSize()));
setMargins(settings.desktopCellMargins());
updateShortcutsFromSettings(settings);
// setIconSize and setMargins may trigger relayout of items by QListView, so we need to do the layout again.
queueRelayout();
setForeground(settings.desktopFgColor());
@ -566,6 +797,54 @@ void DesktopWindow::onFileClicked(int type, const std::shared_ptr<const Fm::File
delete menu;
}
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);
}
}
@ -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
if(model_) {
disconnect(model_, &Fm::FolderModel::filesAdded, this, &DesktopWindow::onFilesAdded);
@ -775,6 +1019,10 @@ void DesktopWindow::onFilesAdded(const Fm::FileInfoList files) {
}
void DesktopWindow::removeBottomGap() {
auto screen = getDesktopScreen();
if(screen == nullptr) {
return;
}
/************************************************************
NOTE: Desktop is an area bounded from below while icons snap
to its grid srarting from above. Therefore, we try to adjust
@ -785,8 +1033,8 @@ void DesktopWindow::removeBottomGap() {
auto itemSize = delegate->itemSize();
//qDebug() << "delegate:" << delegate->itemSize();
QSize cellMargins = getMargins();
int workAreaHeight = qApp->desktop()->availableGeometry(screenNum_).height()
- 24; // a 12-pix margin will be considered everywhere
int workAreaHeight = screen->availableVirtualGeometry().height()
- 2 * WORK_AREA_MARGIN;
int cellHeight = itemSize.height() + listView_->spacing();
int iconNumber = 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.
// FIXME: this is very inefficient, but due to the design flaw of QListView, this is currently the only workaround.
void DesktopWindow::relayoutItems() {
auto screen = getDesktopScreen();
if(screen == nullptr) {
return;
}
displayNames_.clear();
loadItemPositions(); // something may have changed
// qDebug("relayoutItems()");
@ -844,81 +1122,58 @@ void DesktopWindow::relayoutItems() {
relayoutTimer_ = nullptr;
}
QDesktopWidget* desktop = qApp->desktop();
int screen = 0;
int row = 0;
int rowCount = proxyModel_->rowCount();
auto delegate = static_cast<Fm::FolderItemDelegate*>(listView_->itemDelegateForColumn(0));
auto itemSize = delegate->itemSize();
for(;;) {
if(desktop->isVirtualDesktop()) {
if(screen >= desktop->numScreens()) {
QRect workArea = screen->availableVirtualGeometry();
workArea.adjust(WORK_AREA_MARGIN, WORK_AREA_MARGIN, -WORK_AREA_MARGIN, -WORK_AREA_MARGIN);
// qDebug() << "workArea" << screenNum_ << workArea;
// FIXME: we use an internal class declared in a private header here, which is pretty bad.
QPoint pos = workArea.topLeft();
for(; row < rowCount; ++row) {
QModelIndex index = proxyModel_->index(row, 0);
int itemWidth = delegate->sizeHint(listView_->getViewOptions(), index).width();
auto file = proxyModel_->fileInfoFromIndex(index);
// remember display names of desktop entries and shortcuts
if(file->isDesktopEntry() || file->isShortcut()) {
displayNames_[index] = file->displayName();
trustOurDesktopShortcut(file);
}
auto name = file->name();
auto find_it = customItemPos_.find(name);
if(find_it != customItemPos_.cend()) { // the item has a custom position
QPoint customPos = find_it->second;
// center the contents vertically
listView_->setPositionForIndex(customPos + QPoint((itemSize.width() - itemWidth) / 2, 0), index);
// qDebug() << "set custom pos:" << name << row << index << customPos;
continue;
}
// check if the current pos is already occupied by a custom item
bool used = false;
for(auto it = customItemPos_.cbegin(); it != customItemPos_.cend(); ++it) {
QPoint customPos = it->second;
if(QRect(customPos, itemSize).contains(pos)) {
used = true;
break;
}
}
if(used) { // go to next pos
--row;
}
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.
QPoint pos = workArea.topLeft();
for(; row < rowCount; ++row) {
QModelIndex index = proxyModel_->index(row, 0);
int itemWidth = delegate->sizeHint(listView_->getViewOptions(), index).width();
auto file = proxyModel_->fileInfoFromIndex(index);
// remember display names of desktop entries and shortcuts
if(file->isDesktopEntry() || file->isShortcut()) {
displayNames_[index] = file->displayName();
}
auto name = file->name();
auto find_it = customItemPos_.find(name);
if(find_it != customItemPos_.cend()) { // the item has a custom position
QPoint customPos = find_it->second;
// center the contents vertically
listView_->setPositionForIndex(customPos + QPoint((itemSize.width() - itemWidth) / 2, 0), index);
// qDebug() << "set custom pos:" << name << row << index << customPos;
continue;
}
// check if the current pos is alredy occupied by a custom item
bool used = false;
for(auto it = customItemPos_.cbegin(); it != customItemPos_.cend(); ++it) {
QPoint customPos = it->second;
if(QRect(customPos, itemSize).contains(pos)) {
used = true;
break;
}
}
if(used) { // go to next pos
--row;
}
else {
// center the contents vertically
listView_->setPositionForIndex(pos + QPoint((itemSize.width() - itemWidth) / 2, 0), index);
// qDebug() << "set pos" << name << row << index << pos;
}
// move to next cell in the column
pos.setY(pos.y() + itemSize.height() + listView_->spacing());
if(pos.y() + itemSize.height() > workArea.bottom() + 1) {
// 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.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;
}
}
}
// center the contents vertically
listView_->setPositionForIndex(pos + QPoint((itemSize.width() - itemWidth) / 2, 0), index);
// qDebug() << "set pos" << name << row << index << pos;
}
if(row >= rowCount) {
break;
// move to next cell in the column
pos.setY(pos.y() + itemSize.height() + listView_->spacing());
if(pos.y() + itemSize.height() > workArea.bottom() + 1) {
// 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.setY(workArea.top());
}
}
@ -928,6 +1183,10 @@ void DesktopWindow::relayoutItems() {
}
void DesktopWindow::loadItemPositions() {
auto screen = getDesktopScreen();
if(screen == nullptr) {
return;
}
// load custom item positions
customItemPos_.clear();
Settings& settings = static_cast<Application*>(qApp)->settings();
@ -936,8 +1195,8 @@ void DesktopWindow::loadItemPositions() {
auto delegate = static_cast<Fm::FolderItemDelegate*>(listView_->itemDelegateForColumn(0));
auto grid = delegate->itemSize();
QRect workArea = qApp->desktop()->availableGeometry(screenNum_);
workArea.adjust(12, 12, -12, -12);
QRect workArea = screen->availableVirtualGeometry();
workArea.adjust(WORK_AREA_MARGIN, WORK_AREA_MARGIN, -WORK_AREA_MARGIN, -WORK_AREA_MARGIN);
QString desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
desktopDir += '/';
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() {
if(desktopHideItems_) {
return;
@ -1247,6 +1516,14 @@ bool DesktopWindow::eventFilter(QObject* watched, QEvent* event) {
}
}
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:
break;
}
@ -1254,55 +1531,173 @@ bool DesktopWindow::eventFilter(QObject* watched, QEvent* 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) {
const QMimeData* mimeData = e->mimeData();
bool moveItem = false;
QModelIndex curIndx = listView_->currentIndex();
if(e->source() == listView_ && e->keyboardModifiers() == Qt::NoModifier) {
// drag source is our list view, and no other modifier keys are pressed
// => we're dragging desktop items
if(mimeData->hasFormat("application/x-qabstractitemmodeldatalist")) {
QModelIndex dropIndex = listView_->indexAt(e->pos());
if(dropIndex.isValid()) { // drop on an item
QModelIndexList selected = selectedIndexes(); // the dragged items
if(selected.contains(dropIndex)) { // drop on self, ignore
moveItem = true;
if(dropIndex.isValid() // drop on an item
&& curIndx.isValid() && curIndx != dropIndex) { // not a drop on self
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;
}
}
}
else { // drop on a blank area
else { // drop on a blank area (maybe, between other items)
moveItem = true;
}
}
}
if(moveItem) {
auto screen = getDesktopScreen();
if(screen == nullptr) {
return;
}
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 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 {
auto delegate = static_cast<Fm::FolderItemDelegate*>(listView_->itemDelegateForColumn(0));
auto grid = delegate->itemSize();
// 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);
// position dropped items successively, starting with the drop rectangle
if(mimeData->hasUrls()
&& (e->dropAction() == Qt::CopyAction
|| e->dropAction() == Qt::MoveAction
|| e->dropAction() == Qt::LinkAction)) {
QList<QUrl> urlList = mimeData->urls();
for(int i = 0; i < urlList.count(); ++i) {
std::string name = urlList.at(i).fileName().toUtf8().constData();
if(!name.empty()) { // respect the positions of existing files
QString desktopDir = XdgDir::readDesktopDir() + QString(QLatin1String("/"));
if(!QFile::exists(desktopDir + QString::fromStdString(name))) {
QRect workArea = qApp->desktop()->availableGeometry(screenNum_);
workArea.adjust(12, 12, -12, -12);
QPoint pos = mapFromGlobal(e->pos());
alignToGrid(pos, workArea.topLeft(), grid, listView_->spacing());
if(i > 0)
pos.setY(pos.y() + grid.height() + listView_->spacing());
if(pos.y() + grid.height() > workArea.bottom() + 1) {
pos.setX(pos.x() + grid.width() + listView_->spacing());
pos.setY(workArea.top());
}
customItemPos_[name] = pos;
}
auto screen = getDesktopScreen();
if(screen == nullptr) {
return;
}
auto delegate = static_cast<Fm::FolderItemDelegate*>(listView_->itemDelegateForColumn(0));
auto grid = delegate->itemSize();
QRect workArea = screen->availableVirtualGeometry();
workArea.adjust(WORK_AREA_MARGIN, WORK_AREA_MARGIN, -WORK_AREA_MARGIN, -WORK_AREA_MARGIN);
const QString desktopDir = XdgDir::readDesktopDir() + QString(QLatin1String("/"));
QPoint dropPos = e->pos();
const QList<QUrl> urlList = mimeData->urls();
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();
@ -1310,13 +1705,93 @@ void DesktopWindow::childDropEvent(QDropEvent* e) {
}
}
// 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) {
pos.setY(workArea.bottom() + 1 - grid.height());
}
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;
}
}
// 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) {
qreal w = qAbs((qreal)pos.x() - (qreal)topLeft.x())
/ (qreal)(grid.width() + spacing);
qreal h = qAbs(pos.y() - (qreal)topLeft.y())
/ (qreal)(grid.height() + spacing);
pos.setX(topLeft.x() + qRound(w) * (grid.width() + spacing));
pos.setY(topLeft.y() + qRound(h) * (grid.height() + spacing));
int w = (pos.x() - topLeft.x()) / (grid.width() + spacing); // can be negative with DND
int h = (pos.y() - topLeft.y()) / (grid.height() + spacing); // can be negative with DND
pos.setX(topLeft.x() + w * (grid.width() + spacing));
pos.setY(topLeft.y() + h * (grid.height() + spacing));
}
void DesktopWindow::closeEvent(QCloseEvent* event) {
@ -1324,7 +1799,7 @@ void DesktopWindow::closeEvent(QCloseEvent* event) {
event->ignore();
}
void DesktopWindow::paintEvent(QPaintEvent *event) {
void DesktopWindow::paintEvent(QPaintEvent* event) {
paintBackground(event);
QWidget::paintEvent(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

@ -29,6 +29,7 @@
#include <QHash>
#include <QPoint>
#include <QByteArray>
#include <QScreen>
#include <xcb/xcb.h>
#include <libfm-qt/core/folder.h>
@ -84,6 +85,8 @@ public:
void setScreenNum(int num);
QScreen* getDesktopScreen() const;
protected:
virtual void prepareFolderMenu(Fm::FolderMenu* menu) override;
virtual void prepareFileMenu(Fm::FileMenu* menu) override;
@ -98,9 +101,10 @@ protected:
virtual bool event(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 closeEvent(QCloseEvent* event) override;
virtual void paintEvent(QPaintEvent *event) override;
virtual void paintEvent(QPaintEvent* event) override;
protected Q_SLOTS:
void onOpenDirRequested(const Fm::FilePath& path, int target);
@ -112,7 +116,6 @@ protected Q_SLOTS:
void onRowsInserted(const QModelIndex& parent, int start, int end);
void onLayoutChanged();
void onModelSortFilterChanged();
void onIndexesMoved(const QModelIndexList& indexes);
void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
void onFolderStartLoading();
void onFolderFinishLoading();
@ -126,18 +129,34 @@ protected Q_SLOTS:
// file operations
void onCutActivated();
void onCopyActivated();
void onCopyFullPathActivated();
void onPasteActivated();
void onRenameActivated();
void onBulkRenameActivated();
void onDeleteActivated();
void onFilePropertiesActivated();
void updateTrashIcon();
private:
void removeBottomGap();
void addDesktopActions(QMenu* menu);
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);
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:
Fm::ProxyFolderModel* proxyModel_;
Fm::CachedFolderModel* model_;
@ -164,6 +183,11 @@ private:
QHash<QModelIndex, QString> displayNames_; // only for desktop entries and shortcuts
QTimer* relayoutTimer_;
QTimer* selectionTimer_;
QRect dropRect_;
QTimer* trashUpdateTimer_;
GFileMonitor* trashMonitor_;
};
}

@ -31,19 +31,6 @@
<property name="bottomMargin">
<number>0</number>
</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>
<widget class="QSplitter" name="splitter">
<property name="orientation">
@ -57,47 +44,10 @@
</sizepolicy>
</property>
</widget>
<widget class="QFrame" name="frame">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<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>
</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 class="QSplitter" name="viewSplitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</widget>
</item>
@ -187,15 +137,25 @@
<addaction name="actionLocationBar"/>
<addaction name="actionPathButtons"/>
</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="separator"/>
<addaction name="actionShowHidden"/>
<addaction name="actionSplitView"/>
<addaction name="menuSorting"/>
<addaction name="menu_View_2"/>
<addaction name="actionPreserveView"/>
<addaction name="separator"/>
<addaction name="menuToolbars"/>
<addaction name="menuPathBarStyle"/>
<addaction name="separator"/>
<addaction name="menuFiltering"/>
</widget>
<widget class="QMenu" name="menu_Edit">
<property name="title">
@ -245,6 +205,7 @@
</property>
<addaction name="actionOpenTerminal"/>
<addaction name="actionOpenAsRoot"/>
<addaction name="actionCopyFullPath"/>
<addaction name="actionFindFiles"/>
</widget>
<addaction name="menu_File"/>
@ -752,12 +713,20 @@
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Filter</string>
<string>Permanent &amp;filter bar</string>
</property>
<property name="shortcut">
<string>Ctrl+B</string>
</property>
</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">
<property name="icon">
<iconset theme="go-previous">
@ -847,14 +816,41 @@
<string>Ctrl+F2</string>
</property>
</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>
<customwidgets>
<customwidget>
<class>PCManFM::TabBar</class>
<extends>QWidget</extends>
<header>tabbar.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PCManFM::StatusBar</class>
<extends>QStatusBar</extends>

File diff suppressed because it is too large Load Diff

@ -26,12 +26,12 @@
#include <QSortFilterProxyModel>
#include <QLineEdit>
#include <QTabWidget>
#include <libfm/fm.h>
#include <QMessageBox>
#include <QTabBar>
#include <QStackedWidget>
#include <QSplitter>
#include "launcher.h"
#include "tabbar.h"
#include <libfm-qt/core/filepath.h>
#include <libfm-qt/core/bookmarks.h>
@ -42,6 +42,33 @@ class PathBar;
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 Settings;
@ -51,11 +78,21 @@ public:
MainWindow(Fm::FilePath path = Fm::FilePath());
virtual ~MainWindow();
void chdir(Fm::FilePath path);
int addTab(Fm::FilePath path);
void chdir(Fm::FilePath path, ViewFrame* viewFrame);
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() {
return reinterpret_cast<TabPage*>(ui.stackedWidget->currentWidget());
return currentPage(activeViewFrame_);
}
void updateFromSettings(Settings& settings);
@ -103,6 +140,7 @@ protected Q_SLOTS:
void on_actionGo_triggered();
void on_actionShowHidden_triggered(bool check);
void on_actionSplitView_triggered(bool check);
void on_actionPreserveView_triggered(bool checked);
void on_actionByFileName_triggered(bool checked);
@ -115,6 +153,8 @@ protected Q_SLOTS:
void on_actionFolderFirst_triggered(bool checked);
void on_actionCaseSensitive_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_actionPathButtons_triggered(bool checked);
@ -129,6 +169,7 @@ protected Q_SLOTS:
void on_actionOpenTerminal_triggered();
void on_actionOpenAsRoot_triggered();
void on_actionCopyFullPath_triggered();
void on_actionFindFiles_triggered();
void on_actionAbout_triggered();
@ -139,9 +180,6 @@ protected Q_SLOTS:
void onTabBarCurrentChanged(int index);
void onTabBarTabMoved(int from, int to);
void focusFilterBar();
void onFilterStringChanged(QString str);
void onShortcutPrevTab();
void onShortcutNextTab();
void onShortcutJumpToTab();
@ -164,7 +202,10 @@ protected Q_SLOTS:
void onBackForwardContextMenu(QPoint pos);
void onFolderUnmounted();
void tabContextMenu(const QPoint& pos);
void onTabBarClicked(int index);
void closeLeftTabs();
void closeRightTabs();
void closeOtherTabs() {
@ -182,21 +223,27 @@ protected Q_SLOTS:
protected:
bool event(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 closeEvent(QCloseEvent* event) override;
virtual void dragEnterEvent(QDragEnterEvent* event) override;
virtual void dropEvent(QDropEvent* event) override;
virtual bool eventFilter(QObject* watched, QEvent* event);
private:
void loadBookmarksMenu();
void updateUIForCurrentPage();
void updateUIForCurrentPage(bool setFocus = true);
void updateViewMenuForCurrentPage();
void updateEditSelectedActions();
void updateStatusBarForCurrentPage();
void setRTLIcons(bool isRTL);
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();
private:
@ -209,6 +256,12 @@ private:
int rightClickIndex_;
bool updatingViewMenu_;
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_;
};

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

@ -28,7 +28,7 @@
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="currentRow">
<number>-1</number>
<number>0</number>
</property>
<item>
<property name="text">
@ -189,9 +189,11 @@
</item>
<item>
<widget class="QCheckBox" name="quickExec">
<property name="toolTip">
<string>Requires application restart to take effect completely</string>
</property>
<property name="text">
<string>Launch executable files without prompt
(Requires application restart to take effect)</string>
<string>Launch executable files without prompt</string>
</property>
</widget>
</item>
@ -337,8 +339,8 @@
</item>
<item row="2" column="0" colspan="6">
<widget class="QCheckBox" name="showFullNames">
<property name="enabled">
<bool>false</bool>
<property name="toolTip">
<string>Requires application restart to take effect completely</string>
</property>
<property name="text">
<string>Always show full file names</string>
@ -347,8 +349,8 @@
</item>
<item row="3" column="0" colspan="6">
<widget class="QCheckBox" name="shadowHidden">
<property name="enabled">
<bool>false</bool>
<property name="toolTip">
<string>Requires application restart to take effect completely</string>
</property>
<property name="text">
<string>Show icons of hidden files shadowed</string>
@ -479,52 +481,41 @@ only if there are more than one tab.</string>
</widget>
</item>
<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">
<property name="text">
<string>Show 'Close' buttons on tabs </string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="rememberWindowSize">
<property name="text">
<string>Remember the size of the last closed window</string>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="3" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Default width of new windows:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="3" column="1">
<widget class="QSpinBox" name="fixedWindowWidth">
<property name="maximum">
<number>32768</number>
</property>
</widget>
</item>
<item row="5" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Default height of new windows:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="4" column="1">
<widget class="QSpinBox" name="fixedWindowHeight">
<property name="maximum">
<number>32768</number>
@ -534,100 +525,6 @@ above the folder-view and not above the left pane.</string>
</layout>
</widget>
</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>
<spacer name="verticalSpacer_4">
<property name="orientation">
@ -887,6 +784,22 @@ above the folder-view and not above the left pane.</string>
</item>
</layout>
</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>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">

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

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

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

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

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

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

@ -30,6 +30,8 @@
#include <QCursor>
#include <QMessageBox>
#include <QScrollBar>
#include <QToolButton>
#include <QLabel>
#include <QDir>
#include "settings.h"
#include "application.h"
@ -52,6 +54,50 @@ bool ProxyFilter::filterAcceptsRow(const Fm::ProxyFolderModel* model, const std:
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):
QWidget(parent),
folderView_{nullptr},
@ -60,7 +106,8 @@ TabPage::TabPage(QWidget* parent):
proxyFilter_{nullptr},
verticalLayout{nullptr},
overrideCursor_(false),
selectionTimer_(nullptr) {
selectionTimer_(nullptr),
filterBar_(nullptr) {
Settings& settings = static_cast<Application*>(qApp)->settings();
@ -76,6 +123,7 @@ TabPage::TabPage(QWidget* parent):
folderView_ = new View(settings.viewMode(), this);
folderView_->setMargins(settings.folderViewCellMargins());
folderView_->setShadowHidden(settings.shadowHidden());
// newView->setColumnWidth(Fm::FolderModel::ColumnName, 200);
connect(folderView_, &View::openDirRequested, this, &TabPage::openDirRequested);
connect(folderView_, &View::selChanged, this, &TabPage::onSelChanged);
@ -88,6 +136,14 @@ TabPage::TabPage(QWidget* parent):
// FIXME: this is very dirty
folderView_->setModel(proxyModel_);
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() {
@ -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() {
if(folder_) {
if(folderSettings_.isCustomized()) {
@ -285,13 +413,9 @@ void TabPage::onFolderFsInfo() {
guint64 free, total;
QString& msg = statusText_[StatusTextFSInfo];
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)")
.arg(QString::fromUtf8(free_str),
QString::fromUtf8(total_str));
.arg(formatFileSize(free, fm_config->si_unit))
.arg(formatFileSize(total, fm_config->si_unit));
}
else {
msg.clear();
@ -340,21 +464,12 @@ void TabPage::onFolderRemoved() {
void TabPage::onFolderUnmount() {
// the folder we're showing is unmounted, destroy the widget
qDebug("folder unmount");
// NOTE: call deleteLater() directly from this GObject signal handler
// does not work but I don't know why.
// Maybe it's the problem of glib mainloop integration?
// Call it when idle works, though.
Settings& settings = static_cast<Application*>(qApp)->settings();
// NOTE: call deleteLater() directly from this GObject signal handler
// 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());
}
// NOTE: We cannot delete the page or change its directory here
// because unmounting might be done from places view, in which case,
// the mount operation is a child of the places view and should be
// finished before doing anything else.
freeFolder();
Q_EMIT folderUnmounted();
}
void TabPage::onFolderContentChanged() {
@ -372,6 +487,9 @@ QString TabPage::pathName() {
void TabPage::chdir(Fm::FilePath newPath, bool addHistory) {
// qDebug() << "TABPAGE CHDIR:" << newPath.toString().get();
if(filterBar_){
filterBar_->clear();
}
if(folder_) {
// we're already in the specified dir
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::contentChanged, this, &TabPage::onFolderContentChanged);
Settings& settings = static_cast<Application*>(qApp)->settings();
folderModel_ = CachedFolderModel::modelFromFolder(folder_);
folderModel_->setShowFullName(settings.showFullNames());
// set sorting, considering customized folders
Settings& settings = static_cast<Application*>(qApp)->settings();
folderSettings_ = settings.loadFolderSettings(path());
proxyModel_->sort(folderSettings_.sortColumn(), folderSettings_.sortOrder());
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_->setSourceModel(folderModel_);
folderView_->setViewMode(folderSettings_.viewMode());
setViewMode(folderSettings_.viewMode());
if(folder_->isLoaded()) {
onFolderStartLoading();
@ -600,13 +719,22 @@ void TabPage::updateFromSettings(Settings& settings) {
}
void TabPage::setViewMode(Fm::FolderView::ViewMode mode) {
Settings& settings = static_cast<Application*>(qApp)->settings();
if(folderSettings_.viewMode() != mode) {
folderSettings_.setViewMode(mode);
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_->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) {
@ -665,7 +793,12 @@ void TabPage::applyFilter() {
if(!proxyModel_) {
return;
}
int prevSelSize = folderView_->selectionModel()->selectedIndexes().size();
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();
Q_EMIT statusChanged(StatusTextNormal, statusText_[StatusTextNormal]);
}

@ -23,7 +23,7 @@
#include <QWidget>
#include <QVBoxLayout>
#include <libfm/fm.h>
#include <QLineEdit>
#include <libfm-qt/browsehistory.h>
#include "view.h"
#include "settings.h"
@ -58,6 +58,52 @@ private:
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 {
Q_OBJECT
@ -197,6 +243,20 @@ public:
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:
void statusChanged(int type, QString statusText);
void titleChanged(QString title);
@ -204,12 +264,18 @@ Q_SIGNALS:
void sortFilterChanged();
void forwardRequested();
void backwardRequested();
void folderUnmounted();
protected:
virtual bool eventFilter(QObject* watched, QEvent* event);
protected Q_SLOTS:
void onSelChanged();
void onUiUpdated();
void onFileSizeChanged(const QModelIndex& index);
void onFilesAdded(const Fm::FileInfoList files);
void onFilterStringChanged(QString str);
void onLosingFilterBarFocus();
private:
void freeFolder();
@ -242,6 +308,7 @@ private:
bool overrideCursor_;
FolderSettings folderSettings_;
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