496 lines
17 KiB
C++
496 lines
17 KiB
C++
#include "stdafx.h"
|
|
#include "FileView.h"
|
|
#include "SandMan.h"
|
|
#include "../MiscHelpers/Common/Common.h"
|
|
#include "../MiscHelpers/Common/Settings.h"
|
|
#include "../MiscHelpers/Common/TreeItemModel.h"
|
|
#include "../MiscHelpers/Common/OtherFunctions.h"
|
|
#include "../QSbieAPI/SbieUtils.h"
|
|
#include "../Windows/SettingsWindow.h"
|
|
|
|
CFileView::CFileView(QWidget *parent)
|
|
: QWidget(parent)
|
|
{
|
|
m_pMainLayout = new QGridLayout();
|
|
m_pMainLayout->setContentsMargins(0,0,0,0);
|
|
this->setLayout(m_pMainLayout);
|
|
|
|
m_pTreeView = new QTreeViewEx();
|
|
m_pTreeView->setColumnFixed(0, true);
|
|
m_pTreeView->setAlternatingRowColors(theConf->GetBool("Options/AltRowColors", false));
|
|
m_pMainLayout->addWidget(m_pTreeView, 0, 0);
|
|
|
|
m_pFileModel = NULL;
|
|
|
|
m_pTreeView->setSortingEnabled(true);
|
|
m_pTreeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
|
|
QStyle* pStyle = QStyleFactory::create("windows");
|
|
m_pTreeView->setStyle(pStyle);
|
|
m_pTreeView->setItemDelegate(new CTreeItemDelegate());
|
|
|
|
m_pTreeView->setExpandsOnDoubleClick(false);
|
|
|
|
m_pTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(m_pTreeView, SIGNAL(customContextMenuRequested( const QPoint& )), this, SLOT(OnFileMenu(const QPoint &)));
|
|
connect(m_pTreeView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(OnFileDblClick(const QModelIndex &)));
|
|
}
|
|
|
|
CFileView::~CFileView()
|
|
{
|
|
SaveState();
|
|
}
|
|
|
|
void CFileView::SaveState()
|
|
{
|
|
if(m_pFileModel)
|
|
theConf->SetBlob("MainWindow/FileTree_Columns", m_pTreeView->header()->saveState());
|
|
}
|
|
|
|
void CFileView::SetBox(const CSandBoxPtr& pBox)
|
|
{
|
|
if (!m_pBox.isNull()) disconnect(m_pBox.data(), SIGNAL(AboutToBeModified()), this, SLOT(OnAboutToBeModified()));
|
|
|
|
m_pBox = pBox;
|
|
|
|
if (!m_pBox.isNull()) connect(m_pBox.data(), SIGNAL(AboutToBeModified()), this, SLOT(OnAboutToBeModified()));
|
|
|
|
QString Root;
|
|
if (!pBox.isNull() && QFile::exists(pBox->GetFileRoot()))
|
|
Root = pBox->GetFileRoot();
|
|
//if (Root.isEmpty()) {
|
|
// //Root = theAPI->GetSbiePath();
|
|
// m_pTreeView->setEnabled(false);
|
|
//}
|
|
//else
|
|
// m_pTreeView->setEnabled(true);
|
|
|
|
if (m_pFileModel) {
|
|
SaveState();
|
|
|
|
delete m_pFileModel;
|
|
m_pFileModel = NULL;
|
|
}
|
|
if (!Root.isEmpty()) {
|
|
m_pFileModel = new QFileSystemModel(this);
|
|
m_pFileModel->setFilter(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files | QDir::Hidden | QDir::System);
|
|
|
|
QByteArray Columns = theConf->GetBlob("MainWindow/FileTree_Columns");
|
|
if (!Columns.isEmpty())
|
|
m_pTreeView->header()->restoreState(Columns);
|
|
}
|
|
m_pTreeView->setModel(m_pFileModel);
|
|
|
|
if (!Root.isEmpty())
|
|
{
|
|
m_pTreeView->setRootIndex(m_pFileModel->setRootPath(Root));
|
|
|
|
m_pTreeView->expand(m_pFileModel->index(Root + "/drive"));
|
|
m_pTreeView->expand(m_pFileModel->index(Root + "/share"));
|
|
m_pTreeView->expand(m_pFileModel->index(Root + "/user"));
|
|
//m_pTreeView->expand(m_pFileModel->index(Root + "/user/all"));
|
|
//m_pTreeView->expand(m_pFileModel->index(Root + "/user/current"));
|
|
}
|
|
}
|
|
|
|
void CFileView::OnAboutToBeModified()
|
|
{
|
|
if (sender() == m_pBox.data())
|
|
SetBox(CSandBoxPtr());
|
|
}
|
|
|
|
#include <windows.h>
|
|
#include <Shlobj.h>
|
|
#include <atlbase.h>
|
|
|
|
#define MENU_SANDBOX -1
|
|
#define MENU_RECOVER 1
|
|
#define MENU_RECOVER_TO_ANY 2
|
|
#define MENU_CREATE_SHORTCUT 3
|
|
#define MENU_CHECK_FILE 4
|
|
#define MENU_PIN_FILE 5
|
|
|
|
void addSeparatorToShellContextMenu(HMENU hMenu)
|
|
{
|
|
MENUITEMINFO menu_item_info;
|
|
memset(&menu_item_info, 0, sizeof(MENUITEMINFO));
|
|
menu_item_info.cbSize = sizeof(MENUITEMINFO);
|
|
menu_item_info.fType = MFT_SEPARATOR;
|
|
menu_item_info.wID = 0;
|
|
InsertMenuItem(hMenu, 0, TRUE, &menu_item_info);
|
|
}
|
|
|
|
void addItemToShellContextMenu(HMENU hMenu, const wchar_t *name, int ID, bool bChecked = false)
|
|
{
|
|
MENUITEMINFO menu_item_info;
|
|
memset(&menu_item_info, 0, sizeof(MENUITEMINFO));
|
|
menu_item_info.cbSize = sizeof(MENUITEMINFO);
|
|
menu_item_info.fMask = MIIM_ID | MIIM_STRING | MIIM_DATA;
|
|
if (bChecked) {
|
|
menu_item_info.fMask |= MIIM_STATE;
|
|
menu_item_info.fState |= MFS_CHECKED;
|
|
}
|
|
menu_item_info.wID = 0xF000 + ID;
|
|
menu_item_info.dwTypeData = (wchar_t*)name;
|
|
InsertMenuItem(hMenu, 0, TRUE, &menu_item_info);
|
|
}
|
|
|
|
void RemoveMenuItemByVerb(HMENU hMenu, IContextMenu* pContextMenu, UINT idCmdFirst, UINT idCmdLast, const std::wstring& verbToRemove)
|
|
{
|
|
int itemCount = GetMenuItemCount(hMenu);
|
|
for (int i = itemCount - 1; i >= 0; --i)
|
|
{
|
|
MENUITEMINFO menuItemInfo = { 0 };
|
|
menuItemInfo.cbSize = sizeof(MENUITEMINFO);
|
|
menuItemInfo.fMask = MIIM_ID | MIIM_SUBMENU;
|
|
|
|
if (GetMenuItemInfo(hMenu, i, TRUE, &menuItemInfo))
|
|
{
|
|
if (menuItemInfo.hSubMenu)
|
|
{
|
|
// Recursive call for submenus
|
|
RemoveMenuItemByVerb(menuItemInfo.hSubMenu, pContextMenu, idCmdFirst, idCmdLast, verbToRemove);
|
|
|
|
// Remove the submenu if it's empty
|
|
if (GetMenuItemCount(menuItemInfo.hSubMenu) == 0)
|
|
{
|
|
DeleteMenu(hMenu, i, MF_BYPOSITION);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UINT cmdID = menuItemInfo.wID;
|
|
if (cmdID >= idCmdFirst && cmdID < idCmdLast)
|
|
{
|
|
// Retrieve the verb associated with this command ID
|
|
wchar_t verbBuffer[256] = { 0 };
|
|
HRESULT hr = pContextMenu->GetCommandString(
|
|
cmdID - idCmdFirst, // Adjust for idCmdFirst
|
|
GCS_VERBW,
|
|
NULL,
|
|
(LPSTR)verbBuffer,
|
|
sizeof(verbBuffer) / sizeof(wchar_t)
|
|
);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (_wcsicmp(verbBuffer, verbToRemove.c_str()) == 0)
|
|
{
|
|
// Remove the menu item
|
|
DeleteMenu(hMenu, i, MF_BYPOSITION);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int openShellContextMenu(const QStringList& Files, void* parentWindow, const CSandBoxPtr& pBox, QString* pPin = NULL)
|
|
{
|
|
CComPtr<IShellFolder> pDesktop;
|
|
if (!SUCCEEDED(SHGetDesktopFolder(&pDesktop)))
|
|
return 0;
|
|
|
|
std::list<CComHeapPtr<ITEMIDLIST_ABSOLUTE>> items;
|
|
items.resize(Files.count());
|
|
auto IT = items.begin();
|
|
foreach(QString File, Files) {
|
|
//CComPtr<IShellItem> item;
|
|
//SHCreateItemFromParsingName(File.toStdWString().c_str(), NULL, IID_PPV_ARGS(&item));
|
|
//CComQIPtr<IPersistIDList> idl(item);
|
|
//idl->GetIDList(&*IT++);
|
|
pDesktop->ParseDisplayName((HWND)parentWindow, NULL, (wchar_t*)File.toStdWString().c_str(), NULL, &*IT++, NULL);
|
|
}
|
|
|
|
std::vector<LPCITEMIDLIST> list;
|
|
list.resize(items.size());
|
|
LPCITEMIDLIST* listPtr = &list.front();
|
|
for (auto I = items.begin(); I != items.end(); I++)
|
|
*listPtr++ = *I;
|
|
|
|
CComPtr<IContextMenu> pContextMenu;
|
|
//CComPtr<IShellItemArray> array;
|
|
//SHCreateShellItemArrayFromIDLists(list.size(), (LPCITEMIDLIST*)&list.front(), &array);
|
|
//array->BindToHandler(NULL, BHID_SFUIObject, IID_PPV_ARGS(&pContextMenu)); // note: this fails when the files have different parent folders
|
|
//if (!pContextMenu) // note: with the below approach properties and delete does not work, so a custom handler would be needed
|
|
// pDesktop->GetUIObjectOf((HWND)parentWindow, list.size(), (LPCITEMIDLIST*)&list.front(), IID_IContextMenu, NULL, (void**)&pContextMenu);
|
|
{
|
|
DEFCONTEXTMENU details = { 0 };
|
|
details.hwnd = (HWND)parentWindow;
|
|
//details.pcmcb = pContextMenuCB;
|
|
//details.pidlFolder = NULL;
|
|
details.psf = pDesktop;
|
|
details.cidl = list.size();
|
|
details.apidl = reinterpret_cast<PCUITEMID_CHILD_ARRAY>(&list.front());
|
|
//details.punkAssociationInfo = NULL;
|
|
//details.cKeys = 0;
|
|
//details.aKeys = NULL;
|
|
SHCreateDefaultContextMenu(&details, IID_IContextMenu, reinterpret_cast<LPVOID*>(&pContextMenu));
|
|
}
|
|
if (!pContextMenu)
|
|
return 0;
|
|
|
|
HMENU hMenu = CreatePopupMenu();
|
|
if (!hMenu)
|
|
return 0;
|
|
UINT idCmdFirst = 1;
|
|
HRESULT hrMenu = pContextMenu->QueryContextMenu(hMenu, 0, idCmdFirst, 0x7FFF, CMF_NORMAL);
|
|
if (SUCCEEDED(hrMenu))
|
|
{
|
|
UINT nMenuItems = HRESULT_CODE(hrMenu);
|
|
UINT idCmdLast = idCmdFirst + nMenuItems;
|
|
|
|
RemoveMenuItemByVerb(hMenu, pContextMenu, idCmdFirst, idCmdLast, L"open");
|
|
|
|
addSeparatorToShellContextMenu(hMenu);
|
|
|
|
std::wstring Str1 = CFileView::tr("Create Shortcut").toStdWString();
|
|
if (Files.count() == 1)
|
|
{
|
|
addItemToShellContextMenu(hMenu, Str1.c_str(), MENU_CREATE_SHORTCUT);
|
|
|
|
if (pPin)
|
|
{
|
|
auto pBoxPlus = pBox.objectCast<CSandBoxPlus>();
|
|
QStringList RunOptions = pBox->GetTextList("RunCommand", true);
|
|
|
|
QString FoundPin;
|
|
QString FileName = Files.first();
|
|
foreach(const QString & RunOption, RunOptions) {
|
|
QVariantMap Entry = GetRunEntry(RunOption);
|
|
QString CmdFile = pBoxPlus->GetCommandFile(Entry["Command"].toString());
|
|
if(CmdFile.compare(FileName, Qt::CaseInsensitive) == 0) {
|
|
FoundPin = RunOption;
|
|
break;
|
|
}
|
|
}
|
|
|
|
*pPin = FoundPin;
|
|
|
|
std::wstring Str5 = CFileView::tr("Pin to Box Run Menu").toStdWString();
|
|
addItemToShellContextMenu(hMenu, Str5.c_str(), MENU_PIN_FILE, !FoundPin.isEmpty());
|
|
}
|
|
|
|
addSeparatorToShellContextMenu(hMenu);
|
|
}
|
|
|
|
std::wstring Str2 = CFileView::tr("Recover to Any Folder").toStdWString();
|
|
addItemToShellContextMenu(hMenu, Str2.c_str(), MENU_RECOVER_TO_ANY);
|
|
std::wstring Str3 = CFileView::tr("Recover to Same Folder").toStdWString();
|
|
addItemToShellContextMenu(hMenu, Str3.c_str(), MENU_RECOVER);
|
|
|
|
if (!CSandMan::GetFileCheckers(pBox).isEmpty()) {
|
|
std::wstring Str4 = CFileView::tr("Run Recovery Checks").toStdWString();
|
|
addItemToShellContextMenu(hMenu, Str4.c_str(), MENU_CHECK_FILE);
|
|
}
|
|
|
|
POINT point;
|
|
GetCursorPos(&point);
|
|
int iCmd = TrackPopupMenuEx(hMenu, TPM_RETURNCMD, point.x, point.y, (HWND)parentWindow, NULL);
|
|
if (iCmd > 0)
|
|
{
|
|
if ((iCmd & 0xF000) == 0xF000) {
|
|
DestroyMenu(hMenu);
|
|
return iCmd & 0x0FFF;
|
|
}
|
|
|
|
wchar_t verbBuffer[256] = {0};
|
|
HRESULT hr = pContextMenu->GetCommandString(iCmd - 1, GCS_VERBW, NULL, (LPSTR)verbBuffer, sizeof(verbBuffer) / sizeof(wchar_t));
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
qDebug() << verbBuffer;
|
|
if (_wcsicmp(verbBuffer, L"sandbox") == 0) {
|
|
DestroyMenu(hMenu);
|
|
return MENU_SANDBOX;
|
|
}
|
|
}
|
|
|
|
CMINVOKECOMMANDINFOEX info = { 0 };
|
|
info.cbSize = sizeof(info);
|
|
info.fMask = CMIC_MASK_UNICODE;
|
|
info.hwnd = (HWND)parentWindow;
|
|
info.lpVerb = MAKEINTRESOURCEA(iCmd - 1);
|
|
info.lpVerbW = MAKEINTRESOURCEW(iCmd - 1);
|
|
info.nShow = SW_SHOWNORMAL;
|
|
pContextMenu->InvokeCommand((LPCMINVOKECOMMANDINFO)&info);
|
|
}
|
|
}
|
|
DestroyMenu(hMenu);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void CFileView::OnFileMenu(const QPoint&)
|
|
{
|
|
if (!m_pFileModel) return;
|
|
|
|
QStringList Files;
|
|
foreach(const QModelIndex & Index, m_pTreeView->selectionModel()->selectedIndexes()) {
|
|
QString BoxedPath = m_pFileModel->fileInfo(Index).absoluteFilePath().replace("/", "\\");
|
|
if (m_pFileModel->fileInfo(Index).isDir())
|
|
BoxedPath += "\\";
|
|
|
|
bool bFound = false;
|
|
foreach(const QString & File, Files) {
|
|
if (BoxedPath.contains(File))
|
|
bFound = true;
|
|
else if (File.contains(BoxedPath))
|
|
Files.removeOne(File);
|
|
}
|
|
|
|
if(!bFound)
|
|
Files.append(BoxedPath);
|
|
}
|
|
|
|
if (Files.isEmpty())
|
|
return;
|
|
|
|
QString FoundPin;
|
|
int iCmd = openShellContextMenu(Files, (void*)this->winId(), m_pBox, &FoundPin);
|
|
if (iCmd == 0)
|
|
return;
|
|
|
|
QString RecoveryFolder;
|
|
switch (iCmd)
|
|
{
|
|
case MENU_SANDBOX:{
|
|
if (!Files.isEmpty()) {
|
|
QString WrkDir = QFileInfo(Files.first()).absoluteDir().path().replace("/", "\\");
|
|
foreach(const QString & Command, Files)
|
|
theGUI->RunStart(m_pBox->GetName(), Command, CSbieAPI::eStartDefault, WrkDir);
|
|
}
|
|
break;
|
|
}
|
|
case MENU_RECOVER_TO_ANY:
|
|
RecoveryFolder = QFileDialog::getExistingDirectory(this, CFileView::tr("Select Directory")).replace("/", "\\");
|
|
if (RecoveryFolder.isEmpty())
|
|
break;
|
|
case MENU_RECOVER:
|
|
{
|
|
QStringList AllFiles;
|
|
foreach(const QString& File, Files)
|
|
{
|
|
if (File.right(1) == "\\") {
|
|
foreach(QString SubFile, ListDir(File))
|
|
AllFiles.append(File + SubFile.replace("/", "\\"));
|
|
}
|
|
else
|
|
AllFiles.append(File);
|
|
}
|
|
|
|
QList<QPair<QString, QString>> FileList;
|
|
foreach(QString BoxedPath, AllFiles)
|
|
{
|
|
if (!RecoveryFolder.isEmpty()) {
|
|
QString FileName = BoxedPath.mid(BoxedPath.lastIndexOf("\\") + 1);
|
|
FileList.append(qMakePair(BoxedPath, RecoveryFolder + "\\" + FileName));
|
|
}
|
|
else {
|
|
QString RealPath = theAPI->GetRealPath(m_pBox.data(), BoxedPath);
|
|
FileList.append(qMakePair(BoxedPath, RealPath));
|
|
}
|
|
}
|
|
|
|
SB_PROGRESS Status = theGUI->RecoverFiles(m_pBox->GetName(), FileList, theGUI, 0);
|
|
if (Status.GetStatus() == OP_ASYNC)
|
|
theGUI->AddAsyncOp(Status.GetValue());
|
|
|
|
break;
|
|
}
|
|
case MENU_CHECK_FILE:
|
|
{
|
|
SB_PROGRESS Status = theGUI->CheckFiles(m_pBox->GetName(), Files);
|
|
if (Status.GetStatus() == OP_ASYNC)
|
|
theGUI->AddAsyncOp(Status.GetValue());
|
|
break;
|
|
}
|
|
case MENU_PIN_FILE:
|
|
{
|
|
auto pBoxPlus = m_pBox.objectCast<CSandBoxPlus>();
|
|
if (FoundPin.isEmpty())
|
|
pBoxPlus->AppendText("RunCommand", Split2(Files.first(), "\\", true).second + "|\"" + pBoxPlus->MakeBoxCommand(Files.first()) + "\"");
|
|
else
|
|
pBoxPlus->DelValue("RunCommand", FoundPin);
|
|
break;
|
|
}
|
|
case MENU_CREATE_SHORTCUT:
|
|
{
|
|
QString BoxName = m_pBox->GetName();
|
|
QString LinkPath = Files.first();
|
|
QString LinkName = LinkPath.mid(LinkPath.lastIndexOf("\\") + 1);
|
|
|
|
QString Path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).replace("/", "\\");
|
|
//Path = QFileDialog::getExistingDirectory(this, tr("Select Directory to create Shortcut in"), Path).replace("/", "\\");
|
|
//if (Path.isEmpty())
|
|
// return;
|
|
|
|
if (Path.right(1) != "\\")
|
|
Path.append("\\");
|
|
Path += "[" + BoxName + "] " + LinkName;
|
|
|
|
Path = QFileDialog::getSaveFileName(this, tr("Create Shortcut to sandbox %1").arg(BoxName), Path, QString("Shortcut files (*.lnk)")).replace("/", "\\");
|
|
if (Path.isEmpty())
|
|
return;
|
|
|
|
QString StartExe = theAPI->GetSbiePath() + "\\SandMan.exe";
|
|
CSbieUtils::CreateShortcut(StartExe, Path, LinkName, BoxName, LinkPath, LinkPath);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void CFileView::OnFileDblClick(const QModelIndex &)
|
|
{
|
|
if (!m_pFileModel) return;
|
|
|
|
QString BoxedPath = m_pFileModel->fileInfo(m_pTreeView->currentIndex()).absoluteFilePath();
|
|
|
|
ShellExecute(NULL, NULL, BoxedPath.toStdWString().c_str(), NULL, m_pBox->GetFileRoot().toStdWString().c_str(), SW_SHOWNORMAL);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// CFileBrowserWindow
|
|
|
|
CFileBrowserWindow::CFileBrowserWindow(const CSandBoxPtr& pBox, QWidget *parent)
|
|
: QDialog(parent)
|
|
{
|
|
Qt::WindowFlags flags = windowFlags();
|
|
flags |= Qt::CustomizeWindowHint;
|
|
//flags &= ~Qt::WindowContextHelpButtonHint;
|
|
//flags &= ~Qt::WindowSystemMenuHint;
|
|
//flags &= ~Qt::WindowMinMaxButtonsHint;
|
|
flags |= Qt::WindowMinimizeButtonHint;
|
|
//flags &= ~Qt::WindowCloseButtonHint;
|
|
setWindowFlags(flags);
|
|
|
|
this->setWindowFlag(Qt::WindowStaysOnTopHint, theGUI->IsAlwaysOnTop());
|
|
|
|
m_pMainLayout = new QGridLayout(this);
|
|
m_FileView = new CFileView();
|
|
m_FileView->SetBox(pBox);
|
|
m_pMainLayout->addWidget(m_FileView, 0, 0);
|
|
|
|
this->setWindowTitle(tr("%1 - Files").arg(pBox->GetName()));
|
|
|
|
|
|
//statusBar();
|
|
|
|
restoreGeometry(theConf->GetBlob("FileBrowserWindow/Window_Geometry"));
|
|
}
|
|
|
|
CFileBrowserWindow::~CFileBrowserWindow()
|
|
{
|
|
theConf->SetBlob("FileBrowserWindow/Window_Geometry",saveGeometry());
|
|
}
|
|
|
|
void CFileBrowserWindow::closeEvent(QCloseEvent *e)
|
|
{
|
|
emit Closed();
|
|
this->deleteLater();
|
|
}
|