@ -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 ( )
- 2 4; // 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,26 +1122,15 @@ 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 ( ) ) {
break ;
}
}
else {
screen = screenNum_ ;
}
QRect workArea = desktop - > availableGeometry ( screen ) ;
workArea . adjust ( 12 , 12 , - 12 , - 12 ) ; // add a 12 pixel margin to the work area
// qDebug() << "workArea" << screen << workArea;
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 ) {
@ -873,6 +1140,7 @@ void DesktopWindow::relayoutItems() {
// 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 ) ;
@ -883,7 +1151,7 @@ void DesktopWindow::relayoutItems() {
// qDebug() << "set custom pos:" << name << row << index << customPos;
continue ;
}
// check if the current pos is alre dy occupied by a custom item
// check if the current pos is alre a dy occupied by a custom item
bool used = false ;
for ( auto it = customItemPos_ . cbegin ( ) ; it ! = customItemPos_ . cend ( ) ; + + it ) {
QPoint customPos = it - > second ;
@ -906,19 +1174,6 @@ void DesktopWindow::relayoutItems() {
// if the next position may exceed the bottom of work area, go to the top of next column
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 ;
}
}
}
}
if ( row > = rowCount ) {
break ;
}
}
@ -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,69 +1531,267 @@ 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
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 ) {
e - > accept ( ) ;
auto screen = getDesktopScreen ( ) ;
if ( screen = = nullptr ) {
return ;
}
else {
e - > accept ( ) ;
// move selected items to the drop position, preserving their relative positions
const QPoint dropPos = e - > pos ( ) ;
if ( curIndx . isValid ( ) ) {
auto delegate = static_cast < Fm : : FolderItemDelegate * > ( listView_ - > itemDelegateForColumn ( 0 ) ) ;
auto grid = delegate - > itemSize ( ) ;
QRect workArea = screen - > availableVirtualGeometry ( ) ;
workArea . adjust ( WORK_AREA_MARGIN , WORK_AREA_MARGIN , - WORK_AREA_MARGIN , - WORK_AREA_MARGIN ) ;
QPoint curPoint = listView_ - > visualRect ( curIndx ) . topLeft ( ) ;
// first move the current item to the drop position
auto file = proxyModel_ - > fileInfoFromIndex ( curIndx ) ;
if ( file ) {
QPoint pos = dropPos ;
stickToPosition ( file - > name ( ) , pos , workArea , grid ) ;
}
// then move the other items so that their relative postions are preserved
const QModelIndexList selected = selectedIndexes ( ) ;
for ( const QModelIndex & indx : selected ) {
if ( indx = = curIndx ) {
continue ;
}
file = proxyModel_ - > fileInfoFromIndex ( indx ) ;
if ( file ) {
QPoint nxtDropPos = dropPos + listView_ - > visualRect ( indx ) . topLeft ( ) - curPoint ;
nxtDropPos . setX ( qBound ( workArea . left ( ) , nxtDropPos . x ( ) , workArea . right ( ) + 1 ) ) ;
nxtDropPos . setY ( qBound ( workArea . top ( ) , nxtDropPos . y ( ) , workArea . bottom ( ) + 1 ) ) ;
stickToPosition ( file - > name ( ) , nxtDropPos , workArea , grid ) ;
}
}
}
saveItemPositions ( ) ;
queueRelayout ( ) ;
}
else {
// remove the drop indicator
dropRect_ = QRect ( ) ;
listView_ - > viewport ( ) - > update ( ) ;
// move items to Trash if they are dropped on Trash shortcut
QModelIndex dropIndex = listView_ - > indexAt ( e - > pos ( ) ) ;
if ( dropIndex . isValid ( ) ) {
if ( auto file = proxyModel_ - > fileInfoFromIndex ( dropIndex ) ) {
if ( isTrashCan ( file ) ) {
if ( mimeData - > hasUrls ( ) ) {
Fm : : FilePathList paths ;
const QList < QUrl > urlList = mimeData - > urls ( ) ;
for ( const QUrl & url : urlList ) {
QString uri = url . toDisplayString ( ) ;
if ( ! uri . isEmpty ( ) ) {
paths . push_back ( Fm : : FilePath : : fromUri ( uri . toStdString ( ) . c_str ( ) ) ) ;
}
}
if ( ! paths . empty ( ) ) {
e - > accept ( ) ;
Settings & settings = static_cast < Application * > ( qApp ) - > settings ( ) ;
Fm : : FileOperation : : trashFiles ( paths , settings . confirmTrash ( ) ) ;
return ;
}
}
}
}
}
Fm : : FolderView : : childDropEvent ( e ) ;
// 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 ( ) ) ;
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 ( ) ;
}
}
}
// 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 . setX ( pos . x ( ) + grid . width ( ) + listView_ - > spacing ( ) ) ;
pos . setY ( workArea . top ( ) ) ;
pos . setY ( workArea . bottom ( ) + 1 - grid . height ( ) ) ;
}
customItemPos_ [ name ] = pos ;
if ( pos . x ( ) + grid . width ( ) > workArea . right ( ) + 1 ) {
pos . setX ( workArea . right ( ) + 1 - grid . width ( ) ) ;
}
pos . setX ( qMax ( workArea . left ( ) , pos . x ( ) ) ) ;
pos . setY ( qMax ( workArea . top ( ) , pos . y ( ) ) ) ;
alignToGrid ( pos , workArea . topLeft ( ) , grid , listView_ - > spacing ( ) ) ;
}
else { // backward direction: bottom -> top, right -> left
if ( pos . y ( ) < workArea . top ( ) ) {
// reached the top; go to the left bottom
pos . setY ( workArea . bottom ( ) + 1 - grid . height ( ) ) ;
pos . setX ( pos . x ( ) - grid . width ( ) - listView_ - > spacing ( ) ) ;
}
alignToGrid ( pos , workArea . topLeft ( ) , grid , listView_ - > spacing ( ) ) ;
if ( pos . x ( ) < workArea . left ( ) ) {
// there's no space to the left, which means that
// the work area is exhausted, so ignore stickiness
return reachedLastCell ;
}
saveItemPositions ( ) ;
}
// find if there is a sticky item at this position
std : : string otherFile ;
auto oldItem = std : : find_if ( customItemPos_ . cbegin ( ) ,
customItemPos_ . cend ( ) ,
[ pos ] ( const std : : pair < std : : string , QPoint > & elem ) {
return elem . second = = pos ;
} ) ;
if ( oldItem ! = customItemPos_ . cend ( ) ) {
otherFile = oldItem - > first ;
}
// stick to the position
customItemPos_ [ file ] = pos ;
// check whether we are in the last visible cell if it isn't reached already
if ( ! reachedLastCell
& & pos . y ( ) + 2 * grid . height ( ) + listView_ - > spacing ( ) > workArea . bottom ( ) + 1
& & pos . x ( ) + 2 * grid . width ( ) + listView_ - > spacing ( ) > workArea . right ( ) + 1 ) {
reachedLastCell = true ;
}
// find the next position
if ( reachedLastCell ) {
// when this is the last visible cell, reverse the positioning direction
// to avoid off-screen items later
pos . setY ( pos . y ( ) - grid . height ( ) - listView_ - > spacing ( ) ) ;
}
else {
// the last visible cell is not reached yet; go forward
if ( pos . y ( ) + 2 * grid . height ( ) + listView_ - > spacing ( ) > workArea . bottom ( ) + 1 ) {
pos . setY ( workArea . top ( ) ) ;
pos . setX ( pos . x ( ) + grid . width ( ) + listView_ - > spacing ( ) ) ;
}
else {
pos . setY ( pos . y ( ) + grid . height ( ) + listView_ - > spacing ( ) ) ;
}
}
// if there was another sticky item at the same position, move it to the next position
if ( ! otherFile . empty ( ) & & otherFile ! = file ) {
reachedLastCell = stickToPosition ( otherFile , pos , workArea , grid , reachedLastCell ) ;
}
return reachedLastCell ;
}
void DesktopWindow : : alignToGrid ( QPoint & pos , const QPoint & topLeft , const QSize & grid , const int spacing ) {
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 ) {
@ -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