From c3a3a25fb9410bbcb1c4494d789350c53e45a370 Mon Sep 17 00:00:00 2001 From: jolavillette Date: Tue, 30 Dec 2025 20:01:18 +0100 Subject: [PATCH 1/9] Fix Windows freeze in Download mode by making the local file system model initialization conditional --- .../src/gui/common/RsCollectionDialog.cpp | 91 +++++++++++-------- 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/retroshare-gui/src/gui/common/RsCollectionDialog.cpp b/retroshare-gui/src/gui/common/RsCollectionDialog.cpp index 97464645a..27fc6130c 100644 --- a/retroshare-gui/src/gui/common/RsCollectionDialog.cpp +++ b/retroshare-gui/src/gui/common/RsCollectionDialog.cpp @@ -1,21 +1,21 @@ /******************************************************************************* * retroshare-gui/src/gui/common/RsCollectionDialog.cpp * - * * + * * * Copyright (C) 2011, Retroshare Team * - * * + * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU Affero General Public License as * * published by the Free Software Foundation, either version 3 of the * * License, or (at your option) any later version. * - * * + * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Affero General Public License for more details. * - * * + * * * You should have received a copy of the GNU Affero General Public License * * along with this program. If not, see . * - * * + * * *******************************************************************************/ #include "gui/common/FilesDefs.h" @@ -36,6 +36,7 @@ #include #include #include +#include #define COLUMN_FILE 0 #define COLUMN_FILEPATH 1 @@ -125,7 +126,7 @@ protected: * @param readOnly: Open dialog for RsColl as ReadOnly */ RsCollectionDialog::RsCollectionDialog(const QString& collectionFileName, RsCollectionDialogMode mode) - : _mode(mode) + : _mode(mode), _dirModel(0), _tree_proxyModel(0), _selectionProxy(0) { RsCollection::RsCollectionErrorCode err_code; mCollection = new RsCollection(collectionFileName,err_code); @@ -140,7 +141,7 @@ RsCollectionDialog::RsCollectionDialog(const QString& collectionFileName, RsColl } RsCollectionDialog::RsCollectionDialog(const RsCollection& coll, RsCollectionDialogMode mode) - : _mode(mode) + : _mode(mode), _dirModel(0), _tree_proxyModel(0), _selectionProxy(0) { mCollection = new RsCollection(coll); init(QString()); @@ -209,21 +210,32 @@ void RsCollectionDialog::init(const QString& collectionFileName) connect(ui._download_PB, SIGNAL(clicked()), this, SLOT(download())); connect(ui._hashBox, SIGNAL(fileHashingFinished(QList)), this, SLOT(fileHashingFinished(QList))); - // 3 Initialize List - _dirModel = new QFileSystemModel(this); - _dirModel->setRootPath("/"); - _dirModel->setFilter(QDir::AllEntries | QDir::NoSymLinks | QDir::NoDotAndDotDot); - _dirLoaded = false; - connect(_dirModel, SIGNAL(directoryLoaded(QString)), this, SLOT(directoryLoaded(QString))); + // 3 Initialize List ONLY in EDIT mode to avoid Windows background scanning freezes. + if (_mode == EDIT) + { + _dirModel = new QFileSystemModel(this); + _dirModel->setRootPath("/"); + _dirModel->setFilter(QDir::AllEntries | QDir::NoSymLinks | QDir::NoDotAndDotDot); + _dirLoaded = false; + connect(_dirModel, SIGNAL(directoryLoaded(QString)), this, SLOT(directoryLoaded(QString))); - _tree_proxyModel = new FSMSortFilterProxyModel(this); - _tree_proxyModel->setSourceModel(_dirModel); - _tree_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); - _tree_proxyModel->setSortRole(Qt::DisplayRole); + _tree_proxyModel = new FSMSortFilterProxyModel(this); + _tree_proxyModel->setSourceModel(_dirModel); + _tree_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + _tree_proxyModel->setSortRole(Qt::DisplayRole); - ui._systemFileTW->setModel(_tree_proxyModel); - //Selection Setup - _selectionProxy = ui._systemFileTW->selectionModel(); + ui._systemFileTW->setModel(_tree_proxyModel); + _selectionProxy = ui._systemFileTW->selectionModel(); + + ui._systemFileTW->installEventFilter(this); + } + else + { + _dirModel = 0; + _tree_proxyModel = 0; + _selectionProxy = 0; + _dirLoaded = true; + } // 4 Restore Configuration // load settings @@ -236,8 +248,6 @@ void RsCollectionDialog::init(const QString& collectionFileName) ui._treeViewFrame->setVisible(_mode == EDIT); ui._download_PB->setVisible(_mode == DOWNLOAD); - ui._systemFileTW->installEventFilter(this); - // 6 Add HashBox setAcceptDrops(true); ui._hashBox->setDropWidget(this); @@ -359,6 +369,8 @@ void RsCollectionDialog::processSettings(bool bLoad) */ void RsCollectionDialog::directoryLoaded(QString dirLoaded) { + if(!_dirModel) return; + if(!_dirLoaded) { @@ -545,13 +557,15 @@ static void recursBuildFileTree(const QString& path,RsFileTree& tree,RsFileTree: } /** * @brief RsCollectionDialog::addRecursive: Add Selected item to RSCollection - * -Add File seperatly if parent folder not selected - * -Add File in folder if selected - * -Get root folder the selected one + * -Add File seperatly if parent folder not selected + * -Add File in folder if selected + * -Get root folder the selected one * @param recursive: If true, add all selected directory childrens */ void RsCollectionDialog::addSelection(bool recursive) { + if(!_dirModel) return; + QMap dirToAdd; QModelIndexList milSelectionList = ui._systemFileTW->selectionModel()->selectedIndexes(); @@ -599,7 +613,6 @@ void RsCollectionDialog::addSelection(bool recursive) void RsCollectionDialog::remove() { QMap dirToRemove; - int count=0;//to not scan all items on list .count() QModelIndexList milSelectionList = ui._fileEntriesTW->selectionModel()->selectedIndexes(); @@ -654,7 +667,7 @@ void RsCollectionDialog::makeDir() /** * @brief RsCollectionDialog::fileHashingFinished: Connected to ui._hashBox.fileHashingFinished - * Add finished File to collection in respective directory + * Add finished File to collection in respective directory * @param hashedFiles: List of the file finished */ void RsCollectionDialog::fileHashingFinished(QList hashedFiles) @@ -756,22 +769,22 @@ void RsCollectionDialog::download() mb.setText(tr("Incompatible filename.")); mb.setInformativeText(tr("This filename is not usable on your system.")+"\n"+tr("Retroshare can replace every problematic chars by '_'.") +"\n"+tr("What do you want to do?")); - QAbstractButton *btnCorrect = mb.addButton(tr("Correct filename"), QMessageBox::YesRole); - QAbstractButton *btnCorrectAll = mb.addButton(tr("Correct all"), QMessageBox::AcceptRole); - QAbstractButton *btnSkip = mb.addButton(tr("Skip this file"), QMessageBox::NoRole); - QAbstractButton *btnSkipAll = mb.addButton(tr("Skip all"), QMessageBox::RejectRole); + mb.addButton(tr("Correct filename"), QMessageBox::YesRole); + mb.addButton(tr("Correct all"), QMessageBox::AcceptRole); + mb.addButton(tr("Skip this file"), QMessageBox::NoRole); + mb.addButton(tr("Skip all"), QMessageBox::RejectRole); mb.setIcon(QMessageBox::Question); mb.exec(); - if(mb.clickedButton() == btnSkipAll) + if(mb.clickedButton() == (QAbstractButton*)mb.buttons().at(3)) // btnSkipAll { auto_skip = true; continue; } - if(mb.clickedButton() == btnSkip) + if(mb.clickedButton() == (QAbstractButton*)mb.buttons().at(2)) // btnSkip continue; - if(mb.clickedButton() == btnCorrectAll) + if(mb.clickedButton() == (QAbstractButton*)mb.buttons().at(1)) // btnCorrectAll auto_correct = true; } @@ -801,12 +814,12 @@ void RsCollectionDialog::save() close(); } -bool RsCollectionDialog::editExistingCollection(const QString& fileName, bool showError /* = true*/) +bool RsCollectionDialog::editExistingCollection(const QString& fileName, bool /*showError*/) { return RsCollectionDialog(fileName,EDIT).exec(); } -bool RsCollectionDialog::openExistingCollection(const QString& fileName, bool showError /* = true*/) +bool RsCollectionDialog::openExistingCollection(const QString& fileName, bool /*showError*/) { return RsCollectionDialog(fileName,DOWNLOAD).exec(); } @@ -838,12 +851,12 @@ bool RsCollectionDialog::openNewCollection(const RsFileTree& tree) QMessageBox mb; mb.setText(tr("Save Collection File.")); mb.setInformativeText(tr("File already exists.")+"\n"+tr("What do you want to do?")); - QAbstractButton *btnOwerWrite = mb.addButton(tr("Overwrite"), QMessageBox::YesRole); - QAbstractButton *btnCancel = mb.addButton(tr("Cancel"), QMessageBox::ResetRole); + mb.addButton(tr("Overwrite"), QMessageBox::YesRole); + mb.addButton(tr("Cancel"), QMessageBox::ResetRole); mb.setIcon(QMessageBox::Question); mb.exec(); - if (mb.clickedButton()==btnCancel) + if (mb.clickedButton()==(QAbstractButton*)mb.buttons().at(1)) // btnCancel return false; } From 0bca0d3b271c35d02999354d2ee003cd7226b6a0 Mon Sep 17 00:00:00 2001 From: jolavillette Date: Wed, 31 Dec 2025 07:23:22 +0100 Subject: [PATCH 2/9] override the keyPressEvent function, enabling the statistics window to close when the Escape key is pressed --- retroshare-gui/src/gui/statistics/StatisticsWindow.cpp | 10 ++++++++++ retroshare-gui/src/gui/statistics/StatisticsWindow.h | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp b/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp index 684fdc69d..ff15eed31 100644 --- a/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp +++ b/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -228,3 +229,12 @@ void StatisticsWindow::setNewPage(int page) ui->stackPages->setCurrentIndex(page); } } + +void StatisticsWindow::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) { + close(); // Close window is escape is pressed + } else { + QMainWindow::keyPressEvent(event); + } +} diff --git a/retroshare-gui/src/gui/statistics/StatisticsWindow.h b/retroshare-gui/src/gui/statistics/StatisticsWindow.h index 2450812d7..47635377f 100644 --- a/retroshare-gui/src/gui/statistics/StatisticsWindow.h +++ b/retroshare-gui/src/gui/statistics/StatisticsWindow.h @@ -66,7 +66,8 @@ public slots: protected: void changeEvent(QEvent *e); - void closeEvent (QCloseEvent * event); + void closeEvent (QCloseEvent * event); + void keyPressEvent(QKeyEvent *event) override; private: void initStackedPage(); From 70bf22d6cae0f0a59a8d9cf19d55bd7c19fffe6d Mon Sep 17 00:00:00 2001 From: jolavillette Date: Fri, 2 Jan 2026 23:39:12 +0100 Subject: [PATCH 3/9] possibly fix window reopening issues --- .../src/gui/statistics/StatisticsWindow.cpp | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp b/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp index ff15eed31..d72df73e7 100644 --- a/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp +++ b/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp @@ -74,8 +74,14 @@ void StatisticsWindow::showYourself() mInstance = new StatisticsWindow(); } + /* Ensure the window is visible and restored if minimized */ + if (mInstance->isMinimized()) { + mInstance->showNormal(); + } + mInstance->show(); - mInstance->activateWindow(); + mInstance->raise(); /* Bring to front */ + mInstance->activateWindow(); /* Give focus */ } StatisticsWindow* StatisticsWindow::getInstance() @@ -93,22 +99,26 @@ void StatisticsWindow::releaseInstance() /********************************************** STATIC WINDOW *************************************/ - StatisticsWindow::StatisticsWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::StatisticsWindow) { ui->setupUi(this); - Settings->loadWidgetInformation(this); - + /* Automatically destroy the object when the window is closed */ + setAttribute(Qt::WA_DeleteOnClose); + + Settings->loadWidgetInformation(this); + initStackedPage(); connect(ui->stackPages, SIGNAL(currentChanged(int)), this, SLOT(setNewPage(int))); ui->stackPages->setCurrentIndex(0); - int toolSize = Settings->getToolButtonSize(); - ui->toolBar->setToolButtonStyle(Settings->getToolButtonStyle()); - ui->toolBar->setIconSize(QSize(toolSize,toolSize)); - setWindowTitle("RetroShare Statistics - " + MainWindow::getInstance()->get_nameAndLocation()); + + int toolSize = Settings->getToolButtonSize(); + ui->toolBar->setToolButtonStyle(Settings->getToolButtonStyle()); + ui->toolBar->setIconSize(QSize(toolSize,toolSize)); + + setWindowTitle("RetroShare Statistics - " + MainWindow::getInstance()->get_nameAndLocation()); } StatisticsWindow::~StatisticsWindow() @@ -233,8 +243,11 @@ void StatisticsWindow::setNewPage(int page) void StatisticsWindow::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { - close(); // Close window is escape is pressed + /* This will trigger the closeEvent and, thanks to WA_DeleteOnClose, the destructor */ + close(); } else { + /* Pass the event to the base class for default handling */ QMainWindow::keyPressEvent(event); } } + From 13afc743c081439491c2ab489c65a411f269e007 Mon Sep 17 00:00:00 2001 From: jolavillette Date: Sat, 3 Jan 2026 19:17:29 +0100 Subject: [PATCH 4/9] use QPointer to fix crash when closing StatisticsWindow during async loading --- .../gui/statistics/GxsTransportStatistics.cpp | 55 ++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp b/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp index a6c729862..eaf4d5766 100644 --- a/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp +++ b/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp @@ -33,6 +33,7 @@ #include #include #include +#include // Required for thread safety check #include #include @@ -452,42 +453,58 @@ void GxsTransportStatistics::loadGroupStats(const RsGxsGroupId& groupId) } #endif - void GxsTransportStatistics::loadGroups() { mStateHelper->setLoading(GXSTRANS_GROUP_META, true); - RsThread::async([this]() + // FIX: Use a QPointer to track 'this'. Since the window can now be closed via + // the Escape key, we must ensure the object still exists before updating it. + QPointer self(this); + + RsThread::async([self]() { - // 1 - get message data from p3GxsForums + // 1 - get message data from p3GxsForums #ifdef DEBUG_FORUMS - std::cerr << "Retrieving post data for post " << mThreadId << std::endl; + // Original dev debug trace + std::cerr << "Retrieving post data for post " << mThreadId << std::endl; #endif - auto stats = new std::map(); + auto stats = new std::map(); if(!rsGxsTrans->getGroupStatistics(*stats)) { RS_ERR("Cannot retrieve group statistics in GxsTransportStatistics"); - delete stats; + delete stats; return; } - RsQThreadUtils::postToObject( [stats, this]() + // Only proceed if the widget hasn't been destroyed by the user + if (self) { - /* Here it goes any code you want to be executed on the Qt Gui - * thread, for example to update the data model with new information - * after a blocking call to RetroShare API complete */ + RsQThreadUtils::postToObject( [stats, self]() + { + /* Here it goes any code you want to be executed on the Qt Gui + * thread, for example to update the data model with new information + * after a blocking call to RetroShare API complete */ - // TODO: consider making mGroupStats an unique_ptr to avoid copying - mGroupStats = *stats; - updateContent(); - mStateHelper->setLoading(GXSTRANS_GROUP_META, false); + // Final safety check: ensure the object wasn't deleted while + // waiting for the GUI thread event loop. + if (self) + { + // TODO: consider making mGroupStats an unique_ptr to avoid copying + self->mGroupStats = *stats; + self->updateContent(); + self->mStateHelper->setLoading(GXSTRANS_GROUP_META, false); + } - delete stats; - }, this ); - - }); + delete stats; + }, self.data() ); + } + else + { + // Clean up memory if the window was closed during background processing + delete stats; + } + }); } - From 4ba66b8f3373ef93b4c4777ed058e0f3b2ff9768 Mon Sep 17 00:00:00 2001 From: jolavillette Date: Sun, 4 Jan 2026 18:07:08 +0100 Subject: [PATCH 5/9] various fixes according to cyril s comments --- .../src/gui/common/RsCollectionDialog.cpp | 80 ++++++++++--------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/retroshare-gui/src/gui/common/RsCollectionDialog.cpp b/retroshare-gui/src/gui/common/RsCollectionDialog.cpp index 27fc6130c..8352597b4 100644 --- a/retroshare-gui/src/gui/common/RsCollectionDialog.cpp +++ b/retroshare-gui/src/gui/common/RsCollectionDialog.cpp @@ -1,21 +1,21 @@ /******************************************************************************* * retroshare-gui/src/gui/common/RsCollectionDialog.cpp * - * * + * * * Copyright (C) 2011, Retroshare Team * - * * + * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU Affero General Public License as * * published by the Free Software Foundation, either version 3 of the * * License, or (at your option) any later version. * - * * + * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Affero General Public License for more details. * - * * + * * * You should have received a copy of the GNU Affero General Public License * * along with this program. If not, see . * - * * + * * *******************************************************************************/ #include "gui/common/FilesDefs.h" @@ -55,12 +55,12 @@ #define ROLE_FILEC Qt::UserRole + 6 #define ROLE_SELFILEC Qt::UserRole + 7 -#define MAX_FILE_ADDED_BEFORE_ASK 500 //Number of file added in Recursive mode before asking to continue +#define MAX_FILE_ADDED_BEFORE_ASK 500 //Number of files added in Recursive mode before asking to continue #define IMAGE_SEARCH ":/icons/svg/magnifying-glass.svg" /** - * @brief The FSMSortFilterProxyModel class sort directory before file. + * @brief The FSMSortFilterProxyModel class sorts directories before files. */ class FSMSortFilterProxyModel : public QSortFilterProxyModel { @@ -98,21 +98,20 @@ protected: return asc; - /*If sorting by Size (Take real size, not Display one 10<2)*/ + /*If sorting by Size (Take real size, not Display string)*/ if ((sortColumn()==1) && (!leftFileInfo.isDir() && !rightFileInfo.isDir())) { if (leftFileInfo.size() < rightFileInfo.size()) return true; if (leftFileInfo.size() > rightFileInfo.size()) return false; } - /*If sorting by Date Modified (Take real date, not Display one 01-10-2014<02-01-1980)*/ + /*If sorting by Date Modified (Take real date, not Display string)*/ if (sortColumn()==3) { if (leftFileInfo.lastModified() < rightFileInfo.lastModified()) return true; if (leftFileInfo.lastModified() > rightFileInfo.lastModified()) return false; } - //Columns found here:https://qt.gitorious.org/qt/qt/source/9e8abb63ba4609887d988ee15ba6daee0b01380e:src/gui/dialogs/qfilesystemmodel.cpp return QSortFilterProxyModel::lessThan(left, right); } @@ -122,11 +121,10 @@ protected: /** * @brief RsCollectionDialog::RsCollectionDialog * @param collectionFileName: Filename of RSCollection saved - * @param creation: Open dialog as RsColl Creation or RsColl DownLoad - * @param readOnly: Open dialog for RsColl as ReadOnly + * @param mode: Open dialog as RsColl Creation or RsColl DownLoad */ RsCollectionDialog::RsCollectionDialog(const QString& collectionFileName, RsCollectionDialogMode mode) - : _mode(mode), _dirModel(0), _tree_proxyModel(0), _selectionProxy(0) + : _mode(mode), _dirModel(nullptr), _tree_proxyModel(nullptr), _selectionProxy(nullptr) { RsCollection::RsCollectionErrorCode err_code; mCollection = new RsCollection(collectionFileName,err_code); @@ -141,11 +139,12 @@ RsCollectionDialog::RsCollectionDialog(const QString& collectionFileName, RsColl } RsCollectionDialog::RsCollectionDialog(const RsCollection& coll, RsCollectionDialogMode mode) - : _mode(mode), _dirModel(0), _tree_proxyModel(0), _selectionProxy(0) + : _mode(mode), _dirModel(nullptr), _tree_proxyModel(nullptr), _selectionProxy(nullptr) { mCollection = new RsCollection(coll); init(QString()); } + void RsCollectionDialog::init(const QString& collectionFileName) { ui.setupUi(this) ; @@ -210,11 +209,11 @@ void RsCollectionDialog::init(const QString& collectionFileName) connect(ui._download_PB, SIGNAL(clicked()), this, SLOT(download())); connect(ui._hashBox, SIGNAL(fileHashingFinished(QList)), this, SLOT(fileHashingFinished(QList))); - // 3 Initialize List ONLY in EDIT mode to avoid Windows background scanning freezes. + // 3 Initialize Local System List ONLY in EDIT mode if (_mode == EDIT) { _dirModel = new QFileSystemModel(this); - _dirModel->setRootPath("/"); + _dirModel->setRootPath(QDir::homePath()); _dirModel->setFilter(QDir::AllEntries | QDir::NoSymLinks | QDir::NoDotAndDotDot); _dirLoaded = false; connect(_dirModel, SIGNAL(directoryLoaded(QString)), this, SLOT(directoryLoaded(QString))); @@ -231,9 +230,9 @@ void RsCollectionDialog::init(const QString& collectionFileName) } else { - _dirModel = 0; - _tree_proxyModel = 0; - _selectionProxy = 0; + _dirModel = nullptr; + _tree_proxyModel = nullptr; + _selectionProxy = nullptr; _dirLoaded = true; } @@ -316,7 +315,7 @@ void RsCollectionDialog::processSettings(bool bLoad) // load settings if(_mode == EDIT){ - // Load windows geometrie + // Load windows geometry restoreGeometry(Settings->value("WindowGeometrie_CM").toByteArray()); // Load splitters state ui._mainSplitter->restoreState(Settings->value("MainSplitterState_CM").toByteArray()); @@ -326,7 +325,7 @@ void RsCollectionDialog::processSettings(bool bLoad) // Load file entries header configuration ui._fileEntriesTW->header()->restoreState(Settings->value("FileEntriesHeader_CM").toByteArray()); } else { - // Load windows geometrie + // Load windows geometry restoreGeometry(Settings->value("WindowGeometrie").toByteArray()); // Load splitters state ui._mainSplitter->restoreState(Settings->value("MainSplitterState").toByteArray()); @@ -338,7 +337,7 @@ void RsCollectionDialog::processSettings(bool bLoad) } } else { if(_mode == EDIT){ - // Save windows geometrie + // Save windows geometry Settings->setValue("WindowGeometrie_CM",saveGeometry()); // Save splitters state Settings->setValue("MainSplitterState_CM", ui._mainSplitter->saveState()); @@ -348,7 +347,7 @@ void RsCollectionDialog::processSettings(bool bLoad) // Save file entries header configuration Settings->setValue("FileEntriesHeader_CM", ui._fileEntriesTW->header()->saveState()); } else { - // Save windows geometrie + // Save windows geometry Settings->setValue("WindowGeometrie",saveGeometry()); // Save splitter state Settings->setValue("MainSplitterState", ui._mainSplitter->saveState()); @@ -769,26 +768,31 @@ void RsCollectionDialog::download() mb.setText(tr("Incompatible filename.")); mb.setInformativeText(tr("This filename is not usable on your system.")+"\n"+tr("Retroshare can replace every problematic chars by '_'.") +"\n"+tr("What do you want to do?")); - mb.addButton(tr("Correct filename"), QMessageBox::YesRole); - mb.addButton(tr("Correct all"), QMessageBox::AcceptRole); - mb.addButton(tr("Skip this file"), QMessageBox::NoRole); - mb.addButton(tr("Skip all"), QMessageBox::RejectRole); + QAbstractButton *btnCorrect = mb.addButton(tr("Correct filename"), QMessageBox::YesRole); + QAbstractButton *btnCorrectAll = mb.addButton(tr("Correct all"), QMessageBox::AcceptRole); + QAbstractButton *btnSkip = mb.addButton(tr("Skip this file"), QMessageBox::NoRole); + QAbstractButton *btnSkipAll = mb.addButton(tr("Skip all"), QMessageBox::RejectRole); mb.setIcon(QMessageBox::Question); mb.exec(); - if(mb.clickedButton() == (QAbstractButton*)mb.buttons().at(3)) // btnSkipAll + if(mb.clickedButton() == btnSkipAll) { auto_skip = true; continue; } - if(mb.clickedButton() == (QAbstractButton*)mb.buttons().at(2)) // btnSkip + if(mb.clickedButton() == btnSkip) continue; - if(mb.clickedButton() == (QAbstractButton*)mb.buttons().at(1)) // btnCorrectAll + if(mb.clickedButton() == btnCorrectAll) + { auto_correct = true; - } + } - std::cerr << "Requesting file " << corrected_name << " to directory " << path << std::endl; + if(mb.clickedButton() == btnCorrect) + { + auto_correct = false; // logic placeholder + } + } rsFiles->FileRequest(corrected_name,f.hash,f.size,path,RS_FILE_REQ_ANONYMOUS_ROUTING,std::list()); } @@ -851,13 +855,18 @@ bool RsCollectionDialog::openNewCollection(const RsFileTree& tree) QMessageBox mb; mb.setText(tr("Save Collection File.")); mb.setInformativeText(tr("File already exists.")+"\n"+tr("What do you want to do?")); - mb.addButton(tr("Overwrite"), QMessageBox::YesRole); - mb.addButton(tr("Cancel"), QMessageBox::ResetRole); + QAbstractButton *btnOwerWrite = mb.addButton(tr("Overwrite"), QMessageBox::YesRole); + QAbstractButton *btnCancel = mb.addButton(tr("Cancel"), QMessageBox::ResetRole); mb.setIcon(QMessageBox::Question); mb.exec(); - if (mb.clickedButton()==(QAbstractButton*)mb.buttons().at(1)) // btnCancel + if (mb.clickedButton()==btnCancel) return false; + + if (mb.clickedButton()==btnOwerWrite) + { + // Proceed to overwrite + } } if(!collection.save(fileName)) @@ -865,4 +874,3 @@ bool RsCollectionDialog::openNewCollection(const RsFileTree& tree) return RsCollectionDialog(fileName,EDIT).exec(); } - From c435d2295f7339f99e20954dcbde5bb8a73c8e2d Mon Sep 17 00:00:00 2001 From: jolavillette Date: Sun, 4 Jan 2026 18:27:42 +0100 Subject: [PATCH 6/9] Fix StatisticsWindow crash by preserving static instance per Cyril s feedback --- .../gui/statistics/GxsTransportStatistics.cpp | 9 +++------ .../src/gui/statistics/StatisticsWindow.cpp | 16 +++++++--------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp b/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp index eaf4d5766..eda285ba2 100644 --- a/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp +++ b/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp @@ -457,8 +457,8 @@ void GxsTransportStatistics::loadGroups() { mStateHelper->setLoading(GXSTRANS_GROUP_META, true); - // FIX: Use a QPointer to track 'this'. Since the window can now be closed via - // the Escape key, we must ensure the object still exists before updating it. + // Use a QPointer to safely track 'this'. Even if the window is static, + // this prevents crashes if the instance is ever released during async execution. QPointer self(this); RsThread::async([self]() @@ -478,7 +478,6 @@ void GxsTransportStatistics::loadGroups() return; } - // Only proceed if the widget hasn't been destroyed by the user if (self) { RsQThreadUtils::postToObject( [stats, self]() @@ -487,8 +486,6 @@ void GxsTransportStatistics::loadGroups() * thread, for example to update the data model with new information * after a blocking call to RetroShare API complete */ - // Final safety check: ensure the object wasn't deleted while - // waiting for the GUI thread event loop. if (self) { // TODO: consider making mGroupStats an unique_ptr to avoid copying @@ -502,7 +499,7 @@ void GxsTransportStatistics::loadGroups() } else { - // Clean up memory if the window was closed during background processing + // Clean up memory if the object was somehow released delete stats; } }); diff --git a/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp b/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp index d72df73e7..a277ddb90 100644 --- a/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp +++ b/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp @@ -98,26 +98,24 @@ void StatisticsWindow::releaseInstance() /********************************************** STATIC WINDOW *************************************/ - StatisticsWindow::StatisticsWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::StatisticsWindow) { ui->setupUi(this); - /* Automatically destroy the object when the window is closed */ - setAttribute(Qt::WA_DeleteOnClose); - Settings->loadWidgetInformation(this); + // Ensure the window is NOT destroyed on close to preserve temporal curves + // and avoid async race conditions. + this->setAttribute(Qt::WA_DeleteOnClose, false); + initStackedPage(); connect(ui->stackPages, SIGNAL(currentChanged(int)), this, SLOT(setNewPage(int))); ui->stackPages->setCurrentIndex(0); - int toolSize = Settings->getToolButtonSize(); ui->toolBar->setToolButtonStyle(Settings->getToolButtonStyle()); ui->toolBar->setIconSize(QSize(toolSize,toolSize)); - setWindowTitle("RetroShare Statistics - " + MainWindow::getInstance()->get_nameAndLocation()); } @@ -243,10 +241,10 @@ void StatisticsWindow::setNewPage(int page) void StatisticsWindow::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { - /* This will trigger the closeEvent and, thanks to WA_DeleteOnClose, the destructor */ - close(); + // Just call close(), which will hide the window without destroying + // the static instance or resetting statistics data. + this->close(); } else { - /* Pass the event to the base class for default handling */ QMainWindow::keyPressEvent(event); } } From 3f107957d4a29dd45310f1a1a7cf4cd68079d137 Mon Sep 17 00:00:00 2001 From: jolavillette Date: Mon, 5 Jan 2026 22:13:50 +0100 Subject: [PATCH 7/9] simplify GXS transport statistics async logic by removing redundant QPointer checks --- .../gui/statistics/GxsTransportStatistics.cpp | 68 +++++++------------ 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp b/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp index eda285ba2..d3c1f6613 100644 --- a/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp +++ b/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp @@ -455,53 +455,33 @@ void GxsTransportStatistics::loadGroupStats(const RsGxsGroupId& groupId) void GxsTransportStatistics::loadGroups() { - mStateHelper->setLoading(GXSTRANS_GROUP_META, true); + mStateHelper->setLoading(GXSTRANS_GROUP_META, true); - // Use a QPointer to safely track 'this'. Even if the window is static, - // this prevents crashes if the instance is ever released during async execution. - QPointer self(this); + /* Perform the statistics retrieval in a background thread to avoid UI lag */ + RsThread::async([this]() + { + /* Temporary storage for statistics retrieved from the GXS transport service */ + auto stats = new std::map(); - RsThread::async([self]() - { - // 1 - get message data from p3GxsForums + if(!rsGxsTrans->getGroupStatistics(*stats)) + { + RS_ERR("Cannot retrieve group statistics in GxsTransportStatistics"); + delete stats; + return; + } -#ifdef DEBUG_FORUMS - // Original dev debug trace - std::cerr << "Retrieving post data for post " << mThreadId << std::endl; -#endif - auto stats = new std::map(); + /* Switch back to the GUI thread to update the display components */ + RsQThreadUtils::postToObject([stats, this]() + { + /* Update the local cache and refresh the tree widgets. + * Since StatisticsWindow is no longer destroyed on close, + * 'this' is guaranteed to be valid here. */ + mGroupStats = *stats; + updateContent(); + mStateHelper->setLoading(GXSTRANS_GROUP_META, false); - if(!rsGxsTrans->getGroupStatistics(*stats)) - { - RS_ERR("Cannot retrieve group statistics in GxsTransportStatistics"); - delete stats; - return; - } - - if (self) - { - RsQThreadUtils::postToObject( [stats, self]() - { - /* Here it goes any code you want to be executed on the Qt Gui - * thread, for example to update the data model with new information - * after a blocking call to RetroShare API complete */ - - if (self) - { - // TODO: consider making mGroupStats an unique_ptr to avoid copying - self->mGroupStats = *stats; - self->updateContent(); - self->mStateHelper->setLoading(GXSTRANS_GROUP_META, false); - } - - delete stats; - }, self.data() ); - } - else - { - // Clean up memory if the object was somehow released - delete stats; - } - }); + delete stats; + }, this); + }); } From 8e3f8f1bb0245b8e3e65fb18d346cb879a3ea863 Mon Sep 17 00:00:00 2001 From: jolavillette Date: Fri, 26 Dec 2025 23:33:25 +0100 Subject: [PATCH 8/9] Attenpt to fix dangling friends list in network view --- .../src/gui/common/NewFriendList.cpp | 52 ++++++++----------- retroshare-gui/src/gui/common/NewFriendList.h | 1 + 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/retroshare-gui/src/gui/common/NewFriendList.cpp b/retroshare-gui/src/gui/common/NewFriendList.cpp index 492b089b0..a164830d8 100644 --- a/retroshare-gui/src/gui/common/NewFriendList.cpp +++ b/retroshare-gui/src/gui/common/NewFriendList.cpp @@ -1153,63 +1153,57 @@ void NewFriendList::removeGroup() void NewFriendList::applyWhileKeepingTree(std::function predicate) { + // 1. Store the current vertical scroll position to prevent the list from jumping + int scrollValue = ui->peerTreeWidget->verticalScrollBar()->value(); + std::set expanded_indexes; QString selected; + // 2. Save the current state of the tree (which groups are open and what is selected) saveExpandedPathsAndSelection(expanded_indexes, selected); -#ifdef DEBUG_NEW_FRIEND_LIST - std::cerr << "After collecting selection, selected paths is: \"" << selected.toStdString() << "\", " ; - std::cerr << "expanded paths are: " << std::endl; - for(auto path:expanded_indexes) - std::cerr << " \"" << path.toStdString() << "\"" << std::endl; - std::cerr << "Current sort column is: " << mLastSortColumn << " and order is " << mLastSortOrder << std::endl; -#endif + // 3. Clear selection and block signals to avoid UI flicker during updates whileBlocking(ui->peerTreeWidget)->clearSelection(); - // This is a hack to avoid crashes on windows while calling endInsertRows(). I'm not sure wether these crashes are - // due to a Qt bug, or a misuse of the proxy model on my side. Anyway, this solves them for good. - // As a side effect we need to save/restore hidden columns because setSourceModel() resets this setting. - - // save hidden columns and sizes + // 4. Save current column visibility and widths + // Detaching the model resets these settings, so we must back them up std::vector col_visible(RsFriendListModel::COLUMN_THREAD_NB_COLUMNS); std::vector col_sizes(RsFriendListModel::COLUMN_THREAD_NB_COLUMNS); - for(int i=0;ipeerTreeWidget->isColumnHidden(i); col_sizes[i] = ui->peerTreeWidget->columnWidth(i); } -#ifdef DEBUG_NEW_FRIEND_LIST - std::cerr << "Applying predicate..." << std::endl; -#endif + // 5. Detach the model from the view + // This "hack" prevents crashes on some platforms (like Windows) during deep data updates mProxyModel->setSourceModel(nullptr); + // 6. Execute the actual data update (the predicate) predicate(); + // 7. Reattach the model and restore expanded items/selection QModelIndex selected_index; mProxyModel->setSourceModel(mModel); - restoreExpandedPathsAndSelection(expanded_indexes,selected,selected_index); + restoreExpandedPathsAndSelection(expanded_indexes, selected, selected_index); - // restore hidden columns - for(uint32_t i=0;ipeerTreeWidget->setColumnHidden(i,!col_visible[i]); - ui->peerTreeWidget->setColumnWidth(i,col_sizes[i]); + ui->peerTreeWidget->setColumnHidden(i, !col_visible[i]); + ui->peerTreeWidget->setColumnWidth(i, col_sizes[i]); } - // restore sorting - // sortColumn(mLastSortColumn,mLastSortOrder); -#ifdef DEBUG_NEW_FRIEND_LIST - std::cerr << "Sorting again with sort column: " << mLastSortColumn << " and order " << mLastSortOrder << std::endl; -#endif + // 9. Re-apply the current sorting to the list mProxyModel->setSortingEnabled(true); - mProxyModel->sort(mLastSortColumn,mLastSortOrder); + mProxyModel->sort(mLastSortColumn, mLastSortOrder); mProxyModel->setSortingEnabled(false); - if(selected_index.isValid()) - ui->peerTreeWidget->scrollTo(selected_index); + // 10. CRITICAL FIX: Restore the exact scroll position + // We use setValue() instead of scrollTo() to ensure the view stays exactly where it was, + // even if a friend connects/disconnects outside of the visible area. + ui->peerTreeWidget->verticalScrollBar()->setValue(scrollValue); } void NewFriendList::sortColumn(int col,Qt::SortOrder so) diff --git a/retroshare-gui/src/gui/common/NewFriendList.h b/retroshare-gui/src/gui/common/NewFriendList.h index 0f3b9dda3..a2f7c2a24 100644 --- a/retroshare-gui/src/gui/common/NewFriendList.h +++ b/retroshare-gui/src/gui/common/NewFriendList.h @@ -30,6 +30,7 @@ #include "FriendListModel.h" #include "retroshare/rsstatus.h" #include "util/FontSizeHandler.h" +#include namespace Ui { class NewFriendList; From 303ac670a4d0ddac21e79d5e575d0441d1fd3068 Mon Sep 17 00:00:00 2001 From: jolavillette Date: Tue, 6 Jan 2026 07:57:51 +0100 Subject: [PATCH 9/9] fix avatar randomly disappearing when filtering the friends list --- .../src/gui/common/FriendListModel.cpp | 113 +++++++++--------- 1 file changed, 57 insertions(+), 56 deletions(-) diff --git a/retroshare-gui/src/gui/common/FriendListModel.cpp b/retroshare-gui/src/gui/common/FriendListModel.cpp index 4f0babc4c..5418b8e10 100644 --- a/retroshare-gui/src/gui/common/FriendListModel.cpp +++ b/retroshare-gui/src/gui/common/FriendListModel.cpp @@ -726,30 +726,33 @@ QVariant RsFriendListModel::displayRole(const EntryIndex& e, int col) const case COLUMN_THREAD_IP: case COLUMN_THREAD_LAST_CONTACT: { - if(!isProfileExpanded(e)) + // BUG FIX: Removed 'if(!isProfileExpanded(e))' to keep the last contact and IP + // visible even when the profile is expanded (e.g. during search filtering). + const HierarchicalProfileInformation *hn = getProfileInfo(e); + + if(!hn) return QVariant(); + + QDateTime most_recent_time = DateTime::DateTimeFromTime_t(0); + QString most_recent_ip("---"); + + // We aggregate the most recent contact info from all child nodes/locations + for(uint32_t i=0;ichild_node_indices.size();++i) { - const HierarchicalProfileInformation *hn = getProfileInfo(e); + const HierarchicalNodeInformation& node = mLocations[hn->child_node_indices[i]]; + auto node_time = DateTime::DateTimeFromTime_t(node.node_info.lastConnect); - QDateTime most_recent_time = DateTime::DateTimeFromTime_t(0); - QString most_recent_ip("---"); - - for(uint32_t i=0;ichild_node_indices.size();++i) + if(most_recent_time < node_time) { - const HierarchicalNodeInformation& node = mLocations[hn->child_node_indices[i]]; - auto node_time = DateTime::DateTimeFromTime_t(node.node_info.lastConnect); - - if(most_recent_time < node_time) - { - most_recent_time = node_time; - most_recent_ip = (node.node_info.state & RS_PEER_STATE_CONNECTED) ? StatusDefs::connectStateIpString(node.node_info) : QString("---"); - } + most_recent_time = node_time; + most_recent_ip = (node.node_info.state & RS_PEER_STATE_CONNECTED) ? StatusDefs::connectStateIpString(node.node_info) : QString("---"); } - - if(col == COLUMN_THREAD_LAST_CONTACT) return QVariant(most_recent_time); - if(col == COLUMN_THREAD_IP) return QVariant(most_recent_ip); } - }// Fall-through + if(col == COLUMN_THREAD_LAST_CONTACT) return QVariant(most_recent_time); + if(col == COLUMN_THREAD_IP) return QVariant(most_recent_ip); + + return QVariant(); + } default: return QVariant(); } @@ -994,49 +997,47 @@ QVariant RsFriendListModel::decorationRole(const EntryIndex& entry,int col) cons } case ENTRY_TYPE_PROFILE: { - if(!isProfileExpanded(entry)) - { - QPixmap sslAvatar; - bool foundAvatar = false; - const HierarchicalProfileInformation *hn = getProfileInfo(entry); - RsStatusValue status = RsStatusValue::RS_STATUS_OFFLINE; - const HierarchicalNodeInformation *bestNodeInformation = NULL; + // BUG FIX: Removed 'if(!isProfileExpanded(entry))' to keep the icon visible + // even when the profile is expanded (e.g. during search filtering). + + QPixmap sslAvatar; + bool foundAvatar = false; + const HierarchicalProfileInformation *hn = getProfileInfo(entry); + RsStatusValue status = RsStatusValue::RS_STATUS_OFFLINE; + const HierarchicalNodeInformation *bestNodeInformation = NULL; - if (mDisplayStatusIcon) { - bestNodeInformation = getBestNodeInformation(hn, &status); - if (bestNodeInformation) { - if (AvatarDefs::getAvatarFromSslId(RsPeerId(bestNodeInformation->node_info.id.toStdString()), sslAvatar, "")) { - /* Use avatar from best node */ - foundAvatar = true; - } - } - } + if (mDisplayStatusIcon) { + bestNodeInformation = getBestNodeInformation(hn, &status); + if (bestNodeInformation) { + if (AvatarDefs::getAvatarFromSslId(RsPeerId(bestNodeInformation->node_info.id.toStdString()), sslAvatar, "")) { + /* Use avatar from best node */ + foundAvatar = true; + } + } + } - if (!foundAvatar) { - /* Use first available avatar */ - for(uint32_t i=0;ichild_node_indices.size();++i) { - if(AvatarDefs::getAvatarFromSslId(RsPeerId(mLocations[hn->child_node_indices[i]].node_info.id.toStdString()), sslAvatar, "")) { - foundAvatar = true; - break; - } - } - } + if (!foundAvatar) { + /* Use first available avatar */ + for(uint32_t i=0;ichild_node_indices.size();++i) { + if(AvatarDefs::getAvatarFromSslId(RsPeerId(mLocations[hn->child_node_indices[i]].node_info.id.toStdString()), sslAvatar, "")) { + foundAvatar = true; + break; + } + } + } - if (!foundAvatar || sslAvatar.isNull()) { - sslAvatar = FilesDefs::getPixmapFromQtResourcePath(AVATAR_DEFAULT_IMAGE); - } + if (!foundAvatar || sslAvatar.isNull()) { + sslAvatar = FilesDefs::getPixmapFromQtResourcePath(AVATAR_DEFAULT_IMAGE); + } - if (mDisplayStatusIcon) { - if (bestNodeInformation) { - QPixmap sslOverlayIcon = FilesDefs::getPixmapFromQtResourcePath(StatusDefs::imageStatus(status)); - return QVariant(QIcon(createAvatar(sslAvatar, sslOverlayIcon))); - } - } + if (mDisplayStatusIcon) { + if (bestNodeInformation) { + QPixmap sslOverlayIcon = FilesDefs::getPixmapFromQtResourcePath(StatusDefs::imageStatus(status)); + return QVariant(QIcon(createAvatar(sslAvatar, sslOverlayIcon))); + } + } - return QVariant(QIcon(sslAvatar)); - } - - return QVariant(); + return QVariant(QIcon(sslAvatar)); } case ENTRY_TYPE_NODE: