From 6d2ce9035d8ee4fd4bc17f16bae42b8246a6f48c Mon Sep 17 00:00:00 2001 From: Roy Qu Date: Mon, 24 Oct 2022 17:23:37 +0800 Subject: [PATCH] - reduce memory usage when deciding file types - enhancement: refresh project view for git status won't redraw project structure - enhancement: auto save project options after the compilerset option for project resetted - enhancement: "." and ".." in paths of issues not correctly handled --- NEWS.md | 3 + RedPandaIDE/compiler/compiler.cpp | 3 +- RedPandaIDE/compiler/projectcompiler.cpp | 4 +- RedPandaIDE/debugger.cpp | 6 +- RedPandaIDE/mainwindow.cpp | 36 ++++------ RedPandaIDE/parser/parserutils.cpp | 4 +- RedPandaIDE/project.cpp | 67 ++++++++++++++----- RedPandaIDE/project.h | 4 ++ .../settingsdialog/projectgeneralwidget.cpp | 2 +- RedPandaIDE/vcs/gitrepository.cpp | 2 +- RedPandaIDE/widgets/bookmarkmodel.cpp | 4 +- RedPandaIDE/widgets/headercompletionpopup.cpp | 3 +- RedPandaIDE/widgets/newprojectdialog.cpp | 4 +- RedPandaIDE/widgets/newtemplatedialog.cpp | 2 +- libs/redpanda_qt_utils/qt_utils/utils.cpp | 10 +++ libs/redpanda_qt_utils/qt_utils/utils.h | 3 + 16 files changed, 102 insertions(+), 55 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4f0bf060..0433968b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,9 @@ Red Panda C++ Version 2.0 - enhancement: keep current position in the class browser after contents modified - fix: "." and ".." in included header paths not correctly handled - reduce memory usage when deciding file types + - enhancement: refresh project view for git status won't redraw project structure + - enhancement: auto save project options after the compilerset option for project resetted + - enhancement: "." and ".." in paths of issues not correctly handled Red Panda C++ Version 1.5 diff --git a/RedPandaIDE/compiler/compiler.cpp b/RedPandaIDE/compiler/compiler.cpp index 406483ea..5a099c5a 100644 --- a/RedPandaIDE/compiler/compiler.cpp +++ b/RedPandaIDE/compiler/compiler.cpp @@ -113,7 +113,8 @@ QString Compiler::getFileNameFromOutputLine(QString &line) { break; } } - return temp; + QFileInfo info(temp); + return info.isAbsolute()?cleanPath(temp):absolutePath(mDirectory,temp); } int Compiler::getLineNumberFromOutputLine(QString &line) diff --git a/RedPandaIDE/compiler/projectcompiler.cpp b/RedPandaIDE/compiler/projectcompiler.cpp index c4875fc9..61436d60 100644 --- a/RedPandaIDE/compiler/projectcompiler.cpp +++ b/RedPandaIDE/compiler/projectcompiler.cpp @@ -194,7 +194,7 @@ void ProjectCompiler::writeMakeDefines(QFile &file) // Mention progress in the logs if (!ObjResFile.isEmpty()) { - log(tr("- Resource File: %1").arg(QDir(mProject->directory()).absoluteFilePath(ObjResFile))); + log(tr("- Resource File: %1").arg(absolutePath(mProject->directory(),ObjResFile))); } log(""); @@ -515,7 +515,7 @@ bool ProjectCompiler::prepareForRebuild() QString ProjectCompiler::getFileNameFromOutputLine(QString &line) { QString temp=Compiler::getFileNameFromOutputLine(line); - return QDir(mDirectory).absoluteFilePath(temp); + return absolutePath(mDirectory,temp); } bool ProjectCompiler::prepareForCompile() diff --git a/RedPandaIDE/debugger.cpp b/RedPandaIDE/debugger.cpp index 6b9d88a6..a10b97dd 100644 --- a/RedPandaIDE/debugger.cpp +++ b/RedPandaIDE/debugger.cpp @@ -447,9 +447,8 @@ void Debugger::loadForProject(const QString &filename, const QString &projectFol mProjectLastLoadtime = 0; PDebugConfig pConfig = load(filename, forProject); if (pConfig->timestamp>0) { - QDir dir(projectFolder); foreach (const PBreakpoint& breakpoint, pConfig->breakpoints) { - breakpoint->filename = dir.absoluteFilePath(breakpoint->filename); + breakpoint->filename = absolutePath(projectFolder,breakpoint->filename); } mBreakpointModel->setBreakpoints(pConfig->breakpoints,forProject); mWatchModel->setWatchVars(pConfig->watchVars,forProject); @@ -685,13 +684,12 @@ void Debugger::save(const QString &filename, const QString& projectFolder) std::shared_ptr pConfig = load(filename, forProject); QFile file(filename); if (file.open(QFile::WriteOnly | QFile::Truncate)) { - QDir folder(projectFolder); foreach (const PBreakpoint& breakpoint, pConfig->breakpoints) { QString key = QString("%1-%2").arg(breakpoint->filename).arg(breakpoint->line); if (!breakpointCompareSet.contains(key)) { breakpointCompareSet.insert(key); if (forProject) - breakpoint->filename=folder.absoluteFilePath(breakpoint->filename); + breakpoint->filename=absolutePath(projectFolder,breakpoint->filename); mBreakpointModel->addBreakpoint(breakpoint,forProject); } } diff --git a/RedPandaIDE/mainwindow.cpp b/RedPandaIDE/mainwindow.cpp index 010c26cf..009de4f3 100644 --- a/RedPandaIDE/mainwindow.cpp +++ b/RedPandaIDE/mainwindow.cpp @@ -944,8 +944,7 @@ void MainWindow::onFileSaved(const QString &path, bool inProject) if (pSettings->vcs().gitOk()) { QString branch; if (inProject && mProject && mProject->model()->iconProvider()->VCSRepository()->hasRepository(branch)) { - mProject->model()->beginUpdate(); - mProject->model()->endUpdate(); + mProject->model()->refreshIcon(path); } QModelIndex index = mFileSystemModel.index(path); if (index.isValid()) { @@ -4663,6 +4662,7 @@ void MainWindow::updateProjectView() void MainWindow::onFileChanged(const QString &path) { + qDebug()<directory()).absoluteFilePath(newFileName); + newFileName = absolutePath(mProject->directory(),newFileName); } if (fileExists(newFileName)) { QMessageBox::critical(this,tr("File Already Exists!"), @@ -6633,8 +6633,7 @@ void MainWindow::newProjectUnitFile() if (pSettings->vcs().gitOk() && mProject->model()->iconProvider()->VCSRepository()->hasRepository(branch)) { QString output; mProject->model()->iconProvider()->VCSRepository()->add(newFileName,output); - mProject->model()->beginUpdate(); - mProject->model()->endUpdate(); + mProject->model()->refreshIcon(newFileName); } updateProjectView(); } @@ -7928,8 +7927,7 @@ void MainWindow::on_actionGit_Create_Repository_triggered() if (mProject && mProject->folder() == mFileSystemModel.rootPath()) { mProject->addUnit(includeTrailingPathDelimiter(mProject->folder())+".gitignore", mProject->rootNode()); } else if (mProject && mFileSystemModel.index(mProject->folder()).isValid()) { - mProject->model()->beginUpdate(); - mProject->model()->endUpdate(); + mProject->model()->refreshIcons(); } } else if (ui->projectView->isVisible() && mProject) { GitManager vcsManager; @@ -7988,13 +7986,10 @@ void MainWindow::on_actionGit_Add_Files_triggered() QString output; vcsManager.add(info.absolutePath(),info.fileName(),output); } + mProject->model()->refreshIcon(index); } } - //update icons in project view - if (mProject) { - mProject->model()->beginUpdate(); - mProject->model()->endUpdate(); - } + //update icons in files view too mFileSystemModelIconProvider.update(); mFileSystemModel.setIconProvider(&mFileSystemModelIconProvider); @@ -8046,8 +8041,7 @@ void MainWindow::on_actionGit_Commit_triggered() if (vcsManager.commit(folder,message,true,output)) { //update project view if (mProject) { - mProject->model()->beginUpdate(); - mProject->model()->endUpdate(); + mProject->model()->refreshIcons(); } //update files view mFileSystemModelIconProvider.update(); @@ -8076,8 +8070,7 @@ void MainWindow::on_actionGit_Restore_triggered() //update project view if (mProject) { - mProject->model()->beginUpdate(); - mProject->model()->endUpdate(); + mProject->model()->refreshIcons(); } //update files view mFileSystemModelIconProvider.update(); @@ -8114,8 +8107,7 @@ void MainWindow::on_actionGit_Branch_triggered() if (dialog.exec()==QDialog::Accepted) { //update project view if (mProject) { - mProject->model()->beginUpdate(); - mProject->model()->endUpdate(); + mProject->model()->refreshIcons(); } //update files view setFilesViewRoot(pSettings->environment().currentFolder()); @@ -8137,8 +8129,7 @@ void MainWindow::on_actionGit_Merge_triggered() if (dialog.exec()==QDialog::Accepted) { //update project view if (mProject) { - mProject->model()->beginUpdate(); - mProject->model()->endUpdate(); + mProject->model()->refreshIcons(); } //update files view setFilesViewRoot(pSettings->environment().currentFolder()); @@ -8160,8 +8151,7 @@ void MainWindow::on_actionGit_Log_triggered() if (dialog.exec()==QDialog::Accepted) { //update project view if (mProject) { - mProject->model()->beginUpdate(); - mProject->model()->endUpdate(); + mProject->model()->refreshIcons(); } //update files view setFilesViewRoot(pSettings->environment().currentFolder()); diff --git a/RedPandaIDE/parser/parserutils.cpp b/RedPandaIDE/parser/parserutils.cpp index 59070c75..0a31cb6d 100644 --- a/RedPandaIDE/parser/parserutils.cpp +++ b/RedPandaIDE/parser/parserutils.cpp @@ -401,7 +401,7 @@ QString getLocalHeaderFilename(const QString &relativeTo, const QString &fileNam QDir dir = relativeFile.dir(); // Search local directory if (dir.exists(fileName)) { - return QDir::cleanPath(dir.absoluteFilePath(fileName)); + return cleanPath(dir.absoluteFilePath(fileName)); } return ""; } @@ -413,7 +413,7 @@ QString getSystemHeaderFilename(const QString &fileName, const QStringList& incl for (const QString& path:includePaths) { QDir dir(path); if (dir.exists(fileName)) { - return QDir::cleanPath(dir.absoluteFilePath(fileName)); + return cleanPath(dir.absoluteFilePath(fileName)); } } //not found diff --git a/RedPandaIDE/project.cpp b/RedPandaIDE/project.cpp index 472d22c8..140cc8fd 100644 --- a/RedPandaIDE/project.cpp +++ b/RedPandaIDE/project.cpp @@ -197,8 +197,8 @@ void Project::open() PProjectUnit newUnit = std::make_shared(this); QByteArray groupName = toByteArray(QString("Unit%1").arg(i+1)); newUnit->setFileName( - dir.absoluteFilePath( - fromByteArray(ini.GetValue(groupName,"FileName","")))); + cleanPath(dir.absoluteFilePath( + fromByteArray(ini.GetValue(groupName,"FileName",""))))); // if (!QFileInfo(newUnit->fileName()).exists()) { // QMessageBox::critical(nullptr, // tr("File Not Found"), @@ -328,10 +328,10 @@ PProjectUnit Project::newUnit(PProjectModelNode parentNode, const QString& custo // Find unused 'new' filename if (customFileName.isEmpty()) { do { - s = dir.absoluteFilePath(tr("untitled")+QString("%1").arg(getNewFileNumber())); + s = cleanPath(dir.absoluteFilePath(tr("untitled")+QString("%1").arg(getNewFileNumber()))); } while (fileExists(s)); } else { - s = dir.absoluteFilePath(customFileName); + s = cleanPath(dir.absoluteFilePath(customFileName)); } if (mOptions.modelType == ProjectModelType::FileSystem) { // in file system mode, parentNode is determined by file's path @@ -896,9 +896,9 @@ bool Project::assignTemplate(const std::shared_ptr aTemplate, b // Copy icon to project directory if (!mOptions.icon.isEmpty()) { - QString originIcon = QFileInfo(aTemplate->fileName()).absoluteDir().absoluteFilePath(mOptions.icon); + QString originIcon = cleanPath(QFileInfo(aTemplate->fileName()).absoluteDir().absoluteFilePath(mOptions.icon)); if (fileExists(originIcon)) { - QString destIcon = QFileInfo(mFilename).absoluteDir().absoluteFilePath("app.ico"); + QString destIcon = cleanPath(QFileInfo(mFilename).absoluteDir().absoluteFilePath("app.ico")); QFile::copy(originIcon,destIcon); mOptions.icon = destIcon; } else { @@ -917,7 +917,7 @@ bool Project::assignTemplate(const std::shared_ptr aTemplate, b if (!templateUnit->Target.isEmpty()) target = templateUnit->Target; QFile::copy( - dir.absoluteFilePath(templateUnit->Source), + cleanPath(dir.absoluteFilePath(templateUnit->Source)), includeTrailingPathDelimiter(this->directory())+target); unit = newUnit(mRootNode, target); } else { @@ -937,7 +937,7 @@ bool Project::assignTemplate(const std::shared_ptr aTemplate, b this, true); - QString s2 = dir.absoluteFilePath(s); + QString s2 = cleanPath(dir.absoluteFilePath(s)); if (fileExists(s2) && !s.isEmpty()) { try { editor->loadFile(s2); @@ -973,7 +973,7 @@ bool Project::saveAsTemplate(const QString &templateFolder, return false; } - QString fileName = dir.absoluteFilePath(TEMPLATE_INFO_FILE); + QString fileName = cleanPath(dir.absoluteFilePath(TEMPLATE_INFO_FILE)); PSimpleIni ini = std::make_shared(); ini->SetLongValue("Template","Ver",3); @@ -983,7 +983,7 @@ bool Project::saveAsTemplate(const QString &templateFolder, ini->SetValue("Template", "Description", description.toUtf8()); if (fileExists(mOptions.icon)) { QString iconName = extractFileName(mOptions.icon); - copyFile(mOptions.icon, dir.absoluteFilePath(iconName),true); + copyFile(mOptions.icon, cleanPath(dir.absoluteFilePath(iconName)),true); if (dir.exists(iconName)) ini->SetValue("Template", "Icon", iconName.toUtf8()); } @@ -1033,10 +1033,10 @@ bool Project::saveAsTemplate(const QString &templateFolder, foreach (const PProjectUnit &unit, mUnits) { QString unitName = extractFileName(unit->fileName()); QByteArray section = toByteArray(QString("Unit%1").arg(i)); - if (!copyFile(unit->fileName(), dir.absoluteFilePath(unitName),true)) { + if (!copyFile(unit->fileName(), cleanPath(dir.absoluteFilePath(unitName)),true)) { QMessageBox::warning(nullptr, tr("Warning"), - tr("Can't save file %1").arg(dir.absoluteFilePath(unitName)), + tr("Can't save file %1").arg(cleanPath(dir.absoluteFilePath(unitName))), QMessageBox::Ok); } switch(getFileType(unit->fileName())) { @@ -1429,7 +1429,7 @@ void Project::buildPrivateResource(bool forceSave) contents.append("}"); } - rcFile = QDir(directory()).absoluteFilePath(rcFile); + rcFile = absolutePath(directory(),rcFile); if (contents.count() > 3) { stringsToFile(contents,rcFile); mOptions.privateResource = extractRelativePath(directory(), rcFile); @@ -1864,7 +1864,7 @@ void Project::loadOptions(SimpleIni& ini) if (icon.isEmpty()) { mOptions.icon = ""; } else { - mOptions.icon = QDir(directory()).absoluteFilePath(icon); + mOptions.icon = absolutePath(directory(),icon); } mOptions.version = ini.GetLongValue("Project", "Ver", 0); if (mOptions.version > 0) { // ver > 0 is at least a v5 project @@ -1962,6 +1962,7 @@ void Project::loadOptions(SimpleIni& ini) QMessageBox::Ok ); setCompilerSet(pSettings->compilerSets().defaultIndex()); + saveOptions(); } Settings::PCompilerSet pSet = pSettings->compilerSets().getSet(mOptions.compilerSet); @@ -2609,7 +2610,7 @@ bool ProjectModel::setData(const QModelIndex &index, const QVariant &value, int return false; QString oldName = unit->fileName(); QString curDir = extractFilePath(oldName); - newName = QDir(curDir).absoluteFilePath(newName); + newName = absolutePath(curDir,newName); // Only continue if the user says so... if (fileExists(newName) && newName.compare(oldName, PATH_SENSITIVITY)!=0) { // don't remove when changing case for example @@ -2684,6 +2685,42 @@ bool ProjectModel::setData(const QModelIndex &index, const QVariant &value, int return false; } +void ProjectModel::refreshIcon(const QModelIndex &index, bool update) +{ + if (!index.isValid()) + return; + if (update) + mIconProvider->update(); + QVector roles; + roles.append(Qt::DecorationRole); + emit dataChanged(index,index, roles); +} + +void ProjectModel::refreshIcon(const QString &filename) +{ + PProjectUnit unit=mProject->findUnit(filename); + if (!unit) + return; + PProjectModelNode node=unit->node(); + QModelIndex index = getNodeIndex(node.get()); + refreshIcon(index); +} + +void ProjectModel::refreshIcons() +{ + mIconProvider->update(); + mProject->rootNode(); +} + +void ProjectModel::refreshNodeIconRecursive(PProjectModelNode node) +{ + QModelIndex index=getNodeIndex(node.get()); + refreshIcon(index,false); + foreach( PProjectModelNode child, node->children) { + refreshNodeIconRecursive(child); + } +} + QModelIndex ProjectModel::getNodeIndex(ProjectModelNode *node) const { if (!node) diff --git a/RedPandaIDE/project.h b/RedPandaIDE/project.h index 8e71e4f0..1bfe7f30 100644 --- a/RedPandaIDE/project.h +++ b/RedPandaIDE/project.h @@ -146,6 +146,10 @@ public: QVariant data(const QModelIndex &index, int role) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; + void refreshIcon(const QModelIndex& index, bool update=true); + void refreshIcon(const QString& filename); + void refreshIcons(); + void refreshNodeIconRecursive(PProjectModelNode node); QModelIndex getNodeIndex(ProjectModelNode *node) const; QModelIndex getParentIndex(ProjectModelNode * node) const; diff --git a/RedPandaIDE/settingsdialog/projectgeneralwidget.cpp b/RedPandaIDE/settingsdialog/projectgeneralwidget.cpp index e5043fe8..86d90ce1 100644 --- a/RedPandaIDE/settingsdialog/projectgeneralwidget.cpp +++ b/RedPandaIDE/settingsdialog/projectgeneralwidget.cpp @@ -112,7 +112,7 @@ void ProjectGeneralWidget::doSave() #endif project->options().icon = ""; } else { - QString iconPath = QFileInfo(project->filename()).absoluteDir().absoluteFilePath("app.ico"); + QString iconPath = absolutePath(project->directory(),"app.ico"); if (iconPath!=mIconPath) { if (QFile(iconPath).exists()) { if (!QFile::remove(iconPath)) { diff --git a/RedPandaIDE/vcs/gitrepository.cpp b/RedPandaIDE/vcs/gitrepository.cpp index fccb176f..e0ff6acb 100644 --- a/RedPandaIDE/vcs/gitrepository.cpp +++ b/RedPandaIDE/vcs/gitrepository.cpp @@ -116,7 +116,7 @@ void GitRepository::convertFilesListToSet(const QStringList &filesList, QSetfilename = dir.absoluteFilePath(bookmark->filename); + bookmark->filename = cleanPath(dir.absoluteFilePath(bookmark->filename)); } list.append(bookmark); @@ -332,7 +332,7 @@ void BookmarkModel::loadProjectBookmarks(const QString &filename, const QString& mProjectBookmarks = load(filename,0,&t); QDir folder(projectFolder); foreach (PBookmark bookmark, mProjectBookmarks) { - bookmark->filename=folder.absoluteFilePath(bookmark->filename); + bookmark->filename=cleanPath(folder.absoluteFilePath(bookmark->filename)); } if (mIsForProject) endResetModel(); diff --git a/RedPandaIDE/widgets/headercompletionpopup.cpp b/RedPandaIDE/widgets/headercompletionpopup.cpp index a970a696..9aa5c5c8 100644 --- a/RedPandaIDE/widgets/headercompletionpopup.cpp +++ b/RedPandaIDE/widgets/headercompletionpopup.cpp @@ -20,6 +20,7 @@ #include #include #include +#include "../utils.h" HeaderCompletionPopup::HeaderCompletionPopup(QWidget* parent):QWidget(parent) { @@ -231,7 +232,7 @@ void HeaderCompletionPopup::addFile(const QDir& dir, const QFileInfo& fileInfo, PHeaderCompletionListItem item = std::make_shared(); item->filename = fileName; item->itemType = type; - item->fullpath = dir.absoluteFilePath(fileName); + item->fullpath = cleanPath(dir.absoluteFilePath(fileName)); item->usageCount = mHeaderUsageCounts.value(item->fullpath,0); item->isFolder = fileInfo.isDir(); mFullCompletionList.insert(fileName,item); diff --git a/RedPandaIDE/widgets/newprojectdialog.cpp b/RedPandaIDE/widgets/newprojectdialog.cpp index 1d8f8ca8..1757fb29 100644 --- a/RedPandaIDE/widgets/newprojectdialog.cpp +++ b/RedPandaIDE/widgets/newprojectdialog.cpp @@ -145,7 +145,7 @@ void NewProjectDialog::readTemplateDir(const QString& folderPath) } else if (fileInfo.isDir()) { QDir subDir(fileInfo.absoluteFilePath()); if (subDir.exists(TEMPLATE_INFO_FILE)) { - addTemplate(subDir.absoluteFilePath(TEMPLATE_INFO_FILE)); + addTemplate(cleanPath(subDir.absoluteFilePath(TEMPLATE_INFO_FILE))); } } } @@ -186,7 +186,7 @@ void NewProjectDialog::updateView() QString tabText = mTemplatesTabBar->tabText(index); if (category == tabText) { QListWidgetItem * item; - QString iconFilename = QFileInfo(t->fileName()).absoluteDir().absoluteFilePath(t->icon()); + QString iconFilename = cleanPath(QFileInfo(t->fileName()).absoluteDir().absoluteFilePath(t->icon())); QIcon icon=QIcon(iconFilename); if (icon.isNull()) { //todo : use an default icon diff --git a/RedPandaIDE/widgets/newtemplatedialog.cpp b/RedPandaIDE/widgets/newtemplatedialog.cpp index 5e835778..9fbc5501 100644 --- a/RedPandaIDE/widgets/newtemplatedialog.cpp +++ b/RedPandaIDE/widgets/newtemplatedialog.cpp @@ -74,7 +74,7 @@ void NewTemplateDialog::readTemplateCategoriesInDir(const QString &folderPath, Q readTemplateCategory(fileInfo.absoluteFilePath(),categories); } else if (fileInfo.isDir()) { QDir subDir(fileInfo.absoluteFilePath()); - readTemplateCategory(subDir.absoluteFilePath(TEMPLATE_INFO_FILE),categories); + readTemplateCategory(cleanPath(subDir.absoluteFilePath(TEMPLATE_INFO_FILE)),categories); } } diff --git a/libs/redpanda_qt_utils/qt_utils/utils.cpp b/libs/redpanda_qt_utils/qt_utils/utils.cpp index 00f29393..af562e93 100644 --- a/libs/redpanda_qt_utils/qt_utils/utils.cpp +++ b/libs/redpanda_qt_utils/qt_utils/utils.cpp @@ -679,3 +679,13 @@ void createFile(const QString &fileName) { stringToFile("",fileName); } + +QString cleanPath(const QString &dirPath) +{ + return QDir::cleanPath(dirPath); +} + +QString absolutePath(const QString &dirPath, const QString &relativePath) +{ + return QDir::cleanPath(QDir(dirPath).absoluteFilePath(relativePath)); +} diff --git a/libs/redpanda_qt_utils/qt_utils/utils.h b/libs/redpanda_qt_utils/qt_utils/utils.h index 8106d479..80e5e226 100644 --- a/libs/redpanda_qt_utils/qt_utils/utils.h +++ b/libs/redpanda_qt_utils/qt_utils/utils.h @@ -128,6 +128,9 @@ QString extractFileName(const QString& fileName); QString extractFileDir(const QString& fileName); QString extractFilePath(const QString& filePath); QString extractAbsoluteFilePath(const QString& filePath); +QString cleanPath(const QString& dirPath); +QString absolutePath(const QString& dirPath, const QString& relativePath); + bool isReadOnly(const QString& filename);