diff --git a/PowerEditor/src/Notepad_plus.cpp b/PowerEditor/src/Notepad_plus.cpp index d1ca0cfc0..2b05c76b3 100644 --- a/PowerEditor/src/Notepad_plus.cpp +++ b/PowerEditor/src/Notepad_plus.cpp @@ -5899,7 +5899,9 @@ std::vector Notepad_plus::loadCommandlineParams(const TCHAR * co Session session2Load; if ((NppParameters::getInstance()).loadSession(session2Load, fnss.getFileName(0))) { - loadSession(session2Load); + const bool isSnapshotMode = false; + const bool shouldLoadFileBrowser = true; + loadSession(session2Load, isSnapshotMode, shouldLoadFileBrowser); } return std::vector(); } diff --git a/PowerEditor/src/Notepad_plus.h b/PowerEditor/src/Notepad_plus.h index c3fad6dad..27fa1f8d5 100644 --- a/PowerEditor/src/Notepad_plus.h +++ b/PowerEditor/src/Notepad_plus.h @@ -216,7 +216,7 @@ public: void getCurrentOpenedFiles(Session& session, bool includUntitledDoc = false); bool fileLoadSession(const TCHAR* fn = nullptr); - const TCHAR * fileSaveSession(size_t nbFile, TCHAR ** fileNames, const TCHAR *sessionFile2save); + const TCHAR * fileSaveSession(size_t nbFile, TCHAR ** fileNames, const TCHAR *sessionFile2save, bool includeFileBrowser = false); const TCHAR * fileSaveSession(size_t nbFile = 0, TCHAR** fileNames = nullptr); void changeToolBarIcons(); @@ -228,7 +228,7 @@ public: void macroPlayback(Macro); void loadLastSession(); - bool loadSession(Session & session, bool isSnapshotMode = false); + bool loadSession(Session & session, bool isSnapshotMode = false, bool shouldLoadFileBrowser = false); void prepareBufferChangedDialog(Buffer * buffer); void notifyBufferChanged(Buffer * buffer, int mask); diff --git a/PowerEditor/src/NppIO.cpp b/PowerEditor/src/NppIO.cpp index 007b06675..8bf0e306d 100644 --- a/PowerEditor/src/NppIO.cpp +++ b/PowerEditor/src/NppIO.cpp @@ -31,11 +31,13 @@ #include #include "Notepad_plus_Window.h" #include "FileDialog.h" +#include "CustomFileDialog.h" #include "EncodingMapper.h" #include "VerticalFileSwitcher.h" #include "functionListPanel.h" #include "ReadDirectoryChanges.h" #include "ReadFileChanges.h" +#include "fileBrowser.h" #include #include @@ -1939,7 +1941,7 @@ void Notepad_plus::loadLastSession() _isFolding = false; } -bool Notepad_plus::loadSession(Session & session, bool isSnapshotMode) +bool Notepad_plus::loadSession(Session & session, bool isSnapshotMode, bool shouldLoadFileBrowser) { NppParameters& nppParam = NppParameters::getInstance(); bool allSessionFilesLoaded = true; @@ -2187,6 +2189,12 @@ bool Notepad_plus::loadSession(Session & session, bool isSnapshotMode) if (_pFileSwitcherPanel) _pFileSwitcherPanel->reload(); + if (shouldLoadFileBrowser && !session._fileBrowserRoots.empty()) + { + // Force launch file browser and add roots + launchFileBrowser(session._fileBrowserRoots, session._fileBrowserSelectedItem, true); + } + return allSessionFilesLoaded; } @@ -2248,7 +2256,9 @@ bool Notepad_plus::fileLoadSession(const TCHAR *fn) if ((NppParameters::getInstance()).loadSession(session2Load, sessionFileName)) { - isAllSuccessful = loadSession(session2Load); + const bool isSnapshotMode = false; + const bool shouldLoadFileBrowser = true; + isAllSuccessful = loadSession(session2Load, isSnapshotMode, shouldLoadFileBrowser); result = true; } if (!isAllSuccessful) @@ -2266,7 +2276,7 @@ bool Notepad_plus::fileLoadSession(const TCHAR *fn) return result; } -const TCHAR * Notepad_plus::fileSaveSession(size_t nbFile, TCHAR ** fileNames, const TCHAR *sessionFile2save) +const TCHAR * Notepad_plus::fileSaveSession(size_t nbFile, TCHAR ** fileNames, const TCHAR *sessionFile2save, bool includeFileBrowser) { if (sessionFile2save) { @@ -2282,6 +2292,16 @@ const TCHAR * Notepad_plus::fileSaveSession(size_t nbFile, TCHAR ** fileNames, c else getCurrentOpenedFiles(currentSession); + currentSession._includeFileBrowser = includeFileBrowser; + if (includeFileBrowser && _pFileBrowser && !_pFileBrowser->isClosed()) + { + currentSession._fileBrowserSelectedItem = _pFileBrowser->getSelectedItemPath(); + for (auto&& rootFileName : _pFileBrowser->getRoots()) + { + currentSession._fileBrowserRoots.push_back({ rootFileName }); + } + } + (NppParameters::getInstance()).writeSession(currentSession, sessionFile2save); return sessionFile2save; } @@ -2292,7 +2312,7 @@ const TCHAR * Notepad_plus::fileSaveSession(size_t nbFile, TCHAR ** fileNames) { const TCHAR *sessionFileName = NULL; - FileDialog fDlg(_pPublicInterface->getHSelf(), _pPublicInterface->getHinst()); + CustomFileDialog fDlg(_pPublicInterface->getHSelf()); const TCHAR *ext = NppParameters::getInstance().getNppGUI()._definedSessionExt.c_str(); generic_string sessionExt = TEXT(""); @@ -2301,14 +2321,16 @@ const TCHAR * Notepad_plus::fileSaveSession(size_t nbFile, TCHAR ** fileNames) if (*ext != '.') sessionExt += TEXT("."); sessionExt += ext; - fDlg.setExtFilter(TEXT("Session file"), sessionExt.c_str(), NULL); + fDlg.setExtFilter(TEXT("Session file"), sessionExt.c_str()); fDlg.setDefExt(ext); fDlg.setExtIndex(0); // 0 index for "custom extension types" } - fDlg.setExtFilter(TEXT("All types"), TEXT(".*"), NULL); + fDlg.setExtFilter(TEXT("All types"), TEXT(".*")); + const bool isCheckboxActive = _pFileBrowser && !_pFileBrowser->isClosed(); + fDlg.setCheckbox(TEXT("Save Folder as Workspace"), isCheckboxActive); sessionFileName = fDlg.doSaveDlg(); - return fileSaveSession(nbFile, fileNames, sessionFileName); + return fileSaveSession(nbFile, fileNames, sessionFileName, fDlg.getCheckboxState()); } diff --git a/PowerEditor/src/Parameters.cpp b/PowerEditor/src/Parameters.cpp index 1d116b0a4..626086007 100644 --- a/PowerEditor/src/Parameters.cpp +++ b/PowerEditor/src/Parameters.cpp @@ -2204,6 +2204,28 @@ bool NppParameters::getSessionFromXmlTree(TiXmlDocument *pSessionDoc, Session *p } } + // Node structure and naming corresponds to config.xml + TiXmlNode *fileBrowserRoot = sessionRoot->FirstChildElement(TEXT("FileBrowser")); + if (fileBrowserRoot) + { + const TCHAR *selectedItemPath = (fileBrowserRoot->ToElement())->Attribute(TEXT("latestSelectedItem")); + if (selectedItemPath) + { + (*ptrSession)._fileBrowserSelectedItem = selectedItemPath; + } + + for (TiXmlNode *childNode = fileBrowserRoot->FirstChildElement(TEXT("root")); + childNode; + childNode = childNode->NextSibling(TEXT("root"))) + { + const TCHAR *fileName = (childNode->ToElement())->Attribute(TEXT("foldername")); + if (fileName) + { + (*ptrSession)._fileBrowserRoots.push_back({ fileName }); + } + } + } + return true; } @@ -3232,6 +3254,18 @@ void NppParameters::writeSession(const Session & session, const TCHAR *fileName) } } } + + if (session._includeFileBrowser) + { + // Node structure and naming corresponds to config.xml + TiXmlNode* fileBrowserRootNode = sessionNode->InsertEndChild(TiXmlElement(TEXT("FileBrowser"))); + fileBrowserRootNode->ToElement()->SetAttribute(TEXT("latestSelectedItem"), session._fileBrowserSelectedItem.c_str()); + for (const auto& root : session._fileBrowserRoots) + { + TiXmlNode *fileNameNode = fileBrowserRootNode->InsertEndChild(TiXmlElement(TEXT("root"))); + (fileNameNode->ToElement())->SetAttribute(TEXT("foldername"), root.c_str()); + } + } } _pXmlSessionDoc->SaveFile(); } diff --git a/PowerEditor/src/Parameters.h b/PowerEditor/src/Parameters.h index 53c4a2d08..d0b7a13dd 100644 --- a/PowerEditor/src/Parameters.h +++ b/PowerEditor/src/Parameters.h @@ -203,8 +203,11 @@ struct Session size_t _activeView = 0; size_t _activeMainIndex = 0; size_t _activeSubIndex = 0; + bool _includeFileBrowser = false; + generic_string _fileBrowserSelectedItem; std::vector _mainViewFiles; std::vector _subViewFiles; + std::vector _fileBrowserRoots; }; diff --git a/PowerEditor/src/WinControls/FileBrowser/fileBrowser.h b/PowerEditor/src/WinControls/FileBrowser/fileBrowser.h index c4f326021..96ef992bc 100644 --- a/PowerEditor/src/WinControls/FileBrowser/fileBrowser.h +++ b/PowerEditor/src/WinControls/FileBrowser/fileBrowser.h @@ -151,7 +151,7 @@ public: generic_string getNodePath(HTREEITEM node) const; generic_string getNodeName(HTREEITEM node) const; - void addRootFolder(generic_string); + void addRootFolder(generic_string rootFolderPath); HTREEITEM getRootFromFullPath(const generic_string & rootPath) const; HTREEITEM findChildNodeFromName(HTREEITEM parent, const generic_string&) const; diff --git a/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.cpp b/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.cpp new file mode 100644 index 000000000..4cf2593ef --- /dev/null +++ b/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.cpp @@ -0,0 +1,251 @@ +// This file is part of Notepad++ project +// Copyright (C) 2021 The Notepad++ Contributors. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// Note that the GPL places important restrictions on "derived works", yet +// it does not provide a detailed definition of that term. To avoid +// misunderstandings, we consider an application to constitute a +// "derivative work" for the purpose of this license if it does any of the +// following: +// 1. Integrates source code from Notepad++. +// 2. Integrates/includes/aggregates Notepad++ into a proprietary executable +// installer, such as those produced by InstallShield. +// 3. Links to a library or executes a program that does any of the above. +// +// 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +// Windows Vista is a minimum required version for Common File Dialogs. +// Define it only for current source file. +#if defined(_WIN32_WINNT) && (!defined(_WIN32_WINNT_VISTA) || (_WIN32_WINNT < _WIN32_WINNT_VISTA)) +#undef _WIN32_WINNT +#define _WIN32_WINNT _WIN32_WINNT_VISTA +#endif + +#include + +#include "CustomFileDialog.h" +#include "Parameters.h" + +// Private impelemnation to avoid pollution with includes and defines in header. +class CustomFileDialog::Impl +{ +public: + Impl() + { + memset(_fileName, 0, std::size(_fileName)); + } + + ~Impl() + { + if (_customize) + _customize->Release(); + if (_dialog) + _dialog->Release(); + } + + bool initSave() + { + if (_dialog) + return false; // Avoid double initizliation + + HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&_dialog)); + + if (SUCCEEDED(hr) && _defExt && _defExt[0] != '\0') + hr = _dialog->SetDefaultExtension(_defExt); + + if (SUCCEEDED(hr) && !_filterSpec.empty()) + { + std::vector fileTypes; + for (auto&& filter : _filterSpec) + fileTypes.push_back({ filter.first.data(), filter.second.data() }); + hr = _dialog->SetFileTypes(static_cast(fileTypes.size()), fileTypes.data()); + } + + if (SUCCEEDED(hr) && _fileTypeIndex >= 0) + hr = _dialog->SetFileTypeIndex(_fileTypeIndex + 1); // This index is 1-based + + return SUCCEEDED(hr); + } + + bool addFlags(DWORD dwNewFlags) + { + // Before setting, always get the options first in order + // not to override existing options. + DWORD dwOldFlags = 0; + HRESULT hr = _dialog->GetOptions(&dwOldFlags); + if (SUCCEEDED(hr)) + hr = _dialog->SetOptions(dwOldFlags | dwNewFlags); + return SUCCEEDED(hr); + } + + bool addControls() + { + HRESULT hr = _dialog->QueryInterface(IID_PPV_ARGS(&_customize)); + if (SUCCEEDED(hr)) + { + if (_checkboxLabel && _checkboxLabel[0] != '\0') + { + const BOOL isChecked = FALSE; + hr = _customize->AddCheckButton(IDC_FILE_CHECKBOX, _checkboxLabel, isChecked); + if (SUCCEEDED(hr) && !_isCheckboxActive) + { + hr = _customize->SetControlState(IDC_FILE_CHECKBOX, CDCS_INACTIVE | CDCS_VISIBLE); + } + } + } + return SUCCEEDED(hr); + } + + bool setInitDir(const TCHAR* dir) + { + IShellItem* psi = nullptr; + HRESULT hr = SHCreateItemFromParsingName(dir, + 0, + IID_IShellItem, + reinterpret_cast(&psi)); + if (SUCCEEDED(hr)) + hr = _dialog->SetFolder(psi); + return SUCCEEDED(hr); + } + + bool show() + { + HRESULT hr = _dialog->Show(_hwndOwner); + return SUCCEEDED(hr); + } + + BOOL getCheckboxState() const + { + if (_customize) + { + BOOL bChecked = FALSE; + HRESULT hr = _customize->GetCheckButtonState(IDC_FILE_CHECKBOX, &bChecked); + if (SUCCEEDED(hr)) + return bChecked; + } + return FALSE; + } + + const TCHAR* getResultFilename() + { + bool bOk = false; + IShellItem* psiResult = nullptr; + HRESULT hr = _dialog->GetResult(&psiResult); + if (SUCCEEDED(hr)) + { + PWSTR pszFilePath = NULL; + hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath); + if (SUCCEEDED(hr)) + { + size_t len = pszFilePath ? wcslen(pszFilePath) : 0; + if (len > 0 && len <= std::size(_fileName)) + { + wcsncpy_s(_fileName, pszFilePath, len); + bOk = true; + } + CoTaskMemFree(pszFilePath); + } + psiResult->Release(); + } + return bOk ? _fileName : nullptr; + } + + static const int IDC_FILE_CHECKBOX = 4; + + HWND _hwndOwner = nullptr; + IFileDialog* _dialog = nullptr; + IFileDialogCustomize* _customize = nullptr; + const TCHAR* _defExt = nullptr; + const TCHAR* _checkboxLabel = nullptr; + bool _isCheckboxActive = true; + std::vector> _filterSpec; // text + extension + TCHAR _fileName[MAX_PATH * 8]; + int _fileTypeIndex = -1; +}; + +CustomFileDialog::CustomFileDialog(HWND hwnd) : _impl{std::make_unique()} +{ + _impl->_hwndOwner = hwnd; +} + +CustomFileDialog::~CustomFileDialog() = default; + +void CustomFileDialog::setExtFilter(const TCHAR *extText, const TCHAR *exts) +{ + // Add an asterisk before each dot in file patterns + generic_string newExts{ exts }; + for (size_t pos = 0; pos < newExts.size(); ++pos) + { + pos = newExts.find(_T('.'), pos); + if (pos == generic_string::npos) + break; + if (pos == 0 || newExts[pos - 1] != _T('*')) + { + newExts.insert(pos, 1, _T('*')); + ++pos; + } + } + + _impl->_filterSpec.push_back({ extText, newExts }); +} + +void CustomFileDialog::setDefExt(const TCHAR* ext) +{ + _impl->_defExt = ext; +} + +void CustomFileDialog::setCheckbox(const TCHAR* text, bool isActive) +{ + _impl->_checkboxLabel = text; + _impl->_isCheckboxActive = isActive; +} + +void CustomFileDialog::setExtIndex(int extTypeIndex) +{ + _impl->_fileTypeIndex = extTypeIndex; +} + +bool CustomFileDialog::getCheckboxState() const +{ + return _impl->getCheckboxState(); +} + +const TCHAR* CustomFileDialog::doSaveDlg() +{ + if (!_impl->initSave()) + return nullptr; + + TCHAR dir[MAX_PATH]; + ::GetCurrentDirectory(MAX_PATH, dir); + + NppParameters& params = NppParameters::getInstance(); + _impl->setInitDir(params.getWorkingDir()); + + _impl->addFlags(FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_FORCEFILESYSTEM); + _impl->addControls(); + bool bOk = _impl->show(); + + if (params.getNppGUI()._openSaveDir == dir_last) + { + ::GetCurrentDirectory(MAX_PATH, dir); + params.setWorkingDir(dir); + } + ::SetCurrentDirectory(dir); + + return bOk ? _impl->getResultFilename() : nullptr; +} diff --git a/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.h b/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.h new file mode 100644 index 000000000..1436c8320 --- /dev/null +++ b/PowerEditor/src/WinControls/OpenSaveFileDialog/CustomFileDialog.h @@ -0,0 +1,55 @@ +// This file is part of Notepad++ project +// Copyright (C) 2021 The Notepad++ Contributors. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// Note that the GPL places important restrictions on "derived works", yet +// it does not provide a detailed definition of that term. To avoid +// misunderstandings, we consider an application to constitute a +// "derivative work" for the purpose of this license if it does any of the +// following: +// 1. Integrates source code from Notepad++. +// 2. Integrates/includes/aggregates Notepad++ into a proprietary executable +// installer, such as those produced by InstallShield. +// 3. Links to a library or executes a program that does any of the above. +// +// 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +#pragma once + +#include "Common.h" +#include + +// Customizable file dialog. +// It allows adding custom controls like checkbox to the dialog. +// This class loosely follows the interface of the FileDialog class. +// However, the implementation is different. +class CustomFileDialog +{ +public: + explicit CustomFileDialog(HWND hwnd); + ~CustomFileDialog(); + void setExtFilter(const TCHAR* text, const TCHAR* ext); + void setDefExt(const TCHAR* ext); + void setCheckbox(const TCHAR* text, bool isActive = true); + void setExtIndex(int extTypeIndex); + + const TCHAR* doSaveDlg(); + + bool getCheckboxState() const; + +private: + class Impl; + std::unique_ptr _impl; +}; diff --git a/PowerEditor/visual.net/notepadPlus.vcxproj b/PowerEditor/visual.net/notepadPlus.vcxproj index 75be8b9f0..9d939ff5a 100755 --- a/PowerEditor/visual.net/notepadPlus.vcxproj +++ b/PowerEditor/visual.net/notepadPlus.vcxproj @@ -292,6 +292,7 @@ copy ..\src\contextMenu.xml ..\bin64\contextMenu.xml + @@ -576,6 +577,7 @@ copy ..\src\contextMenu.xml ..\bin64\contextMenu.xml +