Files
fuel-scm/src/Utils.cpp
2021-09-23 16:18:57 +02:00

652 lines
20 KiB
C++

#include "Utils.h"
#include <QCryptographicHash>
#include <QDialogButtonBox>
#include <QEventLoop>
#include <QFileDialog>
#include <QMessageBox>
#include <QProcess>
#include <QUrl>
#include <qt5keychain/keychain.h>
///////////////////////////////////////////////////////////////////////////////
QMessageBox::StandardButton DialogQuery(QWidget *parent, const QString &title, const QString &query, QMessageBox::StandardButtons buttons)
{
QMessageBox mb(QMessageBox::Question, title, query, buttons, parent, Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint | Qt::Sheet);
mb.setDefaultButton(QMessageBox::No);
mb.setWindowModality(Qt::WindowModal);
mb.setModal(true);
mb.exec();
QMessageBox::StandardButton res = mb.standardButton(mb.clickedButton());
return res;
}
//-----------------------------------------------------------------------------
QString QuotePath(const QString &path)
{
return path;
}
//-----------------------------------------------------------------------------
QStringList QuotePaths(const QStringList &paths)
{
QStringList res;
for (int i = 0; i < paths.size(); ++i)
res.append(QuotePath(paths[i]));
return res;
}
//-----------------------------------------------------------------------------
QString SelectExe(QWidget *parent, const QString &description)
{
QString filter(QObject::tr("Applications"));
#ifdef Q_OS_WIN
filter += " (*.exe)";
#else
filter += " (*)";
#endif
QString path = QFileDialog::getOpenFileName(parent, description, QString(), filter, &filter);
if (!QFile::exists(path))
return QString();
#ifdef Q_OS_MACX
// Guess actual executable from bundle dir
QFileInfo fi(path);
if (fi.isDir() && path.indexOf(".app") != -1)
{
path += "/Contents/MacOS/" + fi.baseName();
if (!QFile::exists(path))
return QString();
}
#endif
return path;
}
//-----------------------------------------------------------------------------
#if 0 // Unused
# include <QInputDialog>
static bool DialogQueryText(QWidget *parent, const QString &title, const QString &query, QString &text, bool isPassword=false)
{
QInputDialog dlg(parent, Qt::Sheet);
dlg.setWindowTitle(title);
dlg.setInputMode(QInputDialog::TextInput);
dlg.setWindowModality(Qt::WindowModal);
dlg.setModal(true);
dlg.setLabelText(query);
dlg.setTextValue(text);
if(isPassword)
dlg.setTextEchoMode(QLineEdit::Password);
if(dlg.exec() == QDialog::Rejected)
return false;
text = dlg.textValue();
return true;
}
#endif
#ifdef Q_OS_WIN
// Explorer File Context Menu support. Based on http://www.microsoft.com/msj/0497/wicked/wicked0497.aspx
# include <shlobj.h>
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// FUNCTION: DoExplorerMenu
//
// DESCRIPTION: Given a path name to a file or folder object, displays
// the shell's context menu for that object and executes
// the menu command (if any) selected by the user.
//
// INPUT: hwnd = Handle of the window in which the menu will be
// displayed.
//
// pszPath = Pointer to an ANSI or Unicode string
// specifying the path to the object.
//
// point = x and y coordinates of the point where the
// menu's upper left corner should be located, in
// client coordinates relative to hwnd.
//
// RETURNS: TRUE if successful, FALSE if not.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool ShowExplorerMenu(HWND hwnd, const QString &path, const QPoint &qpoint)
{
struct Util
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// FUNCTION: GetNextItem
// DESCRIPTION: Finds the next item in an item ID list.
// INPUT: pidl = Pointer to an item ID list.
// RETURNS: Pointer to the next item.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static LPITEMIDLIST GetNextItem(LPITEMIDLIST pidl)
{
USHORT nLen;
if ((nLen = pidl->mkid.cb) == 0)
return NULL;
return (LPITEMIDLIST)(((LPBYTE)pidl) + nLen);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// FUNCTION: GetItemCount
// DESCRIPTION: Computes the number of item IDs in an item ID list.
// INPUT: pidl = Pointer to an item ID list.
// RETURNS: Number of item IDs in the list.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static UINT GetItemCount(LPITEMIDLIST pidl)
{
USHORT nLen;
UINT nCount;
nCount = 0;
while ((nLen = pidl->mkid.cb) != 0)
{
pidl = GetNextItem(pidl);
nCount++;
}
return nCount;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// FUNCTION: DuplicateItem
// DESCRIPTION: Makes a copy of the next item in an item ID list.
// INPUT: pMalloc = Pointer to an IMalloc interface.
// pidl = Pointer to an item ID list.
// RETURNS: Pointer to an ITEMIDLIST containing the copied item ID.
// NOTES: It is the caller's responsibility to free the memory
// allocated by this function when the item ID is no longer
// needed. Example:
// pidlItem = DuplicateItem (pMalloc, pidl);
// .
// .
// .
// pMalloc->lpVtbl->Free (pMalloc, pidlItem);
// Failure to free the ITEMIDLIST will result in memory
// leaks.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static LPITEMIDLIST DuplicateItem(LPMALLOC pMalloc, LPITEMIDLIST pidl)
{
USHORT nLen;
LPITEMIDLIST pidlNew;
nLen = pidl->mkid.cb;
if (nLen == 0)
return NULL;
pidlNew = (LPITEMIDLIST)pMalloc->Alloc(nLen + sizeof(USHORT));
if (pidlNew == NULL)
return NULL;
CopyMemory(pidlNew, pidl, nLen);
*((USHORT *)(((LPBYTE)pidlNew) + nLen)) = 0;
return pidlNew;
}
};
LPITEMIDLIST pidlMain, pidlItem, pidlNextItem, *ppidl;
UINT nCount;
POINT point;
point.x = qpoint.x();
point.y = qpoint.y();
WCHAR wchPath[MAX_PATH];
memset(wchPath, 0, sizeof(wchPath));
Q_ASSERT(path.length() < MAX_PATH);
path.toWCharArray(wchPath);
//
// Get pointers to the shell's IMalloc interface and the desktop's
// IShellFolder interface.
//
bool bResult = false;
LPMALLOC pMalloc;
if (!SUCCEEDED(SHGetMalloc(&pMalloc)))
return bResult;
LPSHELLFOLDER psfFolder;
if (!SUCCEEDED(SHGetDesktopFolder(&psfFolder)))
{
pMalloc->Release();
return bResult;
}
//
// Convert the path name into a pointer to an item ID list (pidl).
//
ULONG ulCount, ulAttr;
if (SUCCEEDED(psfFolder->ParseDisplayName(hwnd, NULL, wchPath, &ulCount, &pidlMain, &ulAttr)) && (pidlMain != NULL))
{
if ((nCount = Util::GetItemCount(pidlMain)) > 0)
{ // nCount must be > 0
//
// Initialize psfFolder with a pointer to the IShellFolder
// interface of the folder that contains the item whose context
// menu we're after, and initialize pidlItem with a pointer to
// the item's item ID. If nCount > 1, this requires us to walk
// the list of item IDs stored in pidlMain and bind to each
// subfolder referenced in the list.
//
pidlItem = pidlMain;
while (--nCount)
{
//
// Create a 1-item item ID list for the next item in pidlMain.
//
pidlNextItem = Util::DuplicateItem(pMalloc, pidlItem);
if (pidlNextItem == NULL)
{
pMalloc->Free(pidlMain);
psfFolder->Release();
pMalloc->Release();
return bResult;
}
//
// Bind to the folder specified in the new item ID list.
//
LPSHELLFOLDER psfNextFolder;
if (!SUCCEEDED(psfFolder->BindToObject(pidlNextItem, NULL, IID_IShellFolder, (void **)&psfNextFolder)))
{
pMalloc->Free(pidlNextItem);
pMalloc->Free(pidlMain);
psfFolder->Release();
pMalloc->Release();
return bResult;
}
//
// Release the IShellFolder pointer to the parent folder
// and set psfFolder equal to the IShellFolder pointer for
// the current folder.
//
psfFolder->Release();
psfFolder = psfNextFolder;
//
// Release the storage for the 1-item item ID list we created
// just a moment ago and initialize pidlItem so that it points
// to the next item in pidlMain.
//
pMalloc->Free(pidlNextItem);
pidlItem = Util::GetNextItem(pidlItem);
}
//
// Get a pointer to the item's IContextMenu interface and call
// IContextMenu::QueryContextMenu to initialize a context menu.
//
ppidl = &pidlItem;
LPCONTEXTMENU pContextMenu;
if (SUCCEEDED(psfFolder->GetUIObjectOf(hwnd, 1, (const ITEMIDLIST **)ppidl, IID_IContextMenu, NULL, (void **)&pContextMenu)))
{
HMENU hMenu = CreatePopupMenu();
if (SUCCEEDED(pContextMenu->QueryContextMenu(hMenu, 0, 1, 0x7FFF, CMF_EXPLORE)))
{
//
// Display the context menu.
//
UINT nCmd = TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hwnd, NULL);
//
// If a command was selected from the menu, execute it.
//
if (nCmd)
{
CMINVOKECOMMANDINFO ici;
memset(&ici, 0, sizeof(ici));
ici.cbSize = sizeof(CMINVOKECOMMANDINFO);
ici.fMask = 0;
ici.hwnd = hwnd;
ici.lpVerb = MAKEINTRESOURCEA(nCmd - 1);
ici.lpParameters = NULL;
ici.lpDirectory = NULL;
ici.nShow = SW_SHOWNORMAL;
ici.dwHotKey = 0;
ici.hIcon = NULL;
if (SUCCEEDED(pContextMenu->InvokeCommand((CMINVOKECOMMANDINFO *)&ici)))
bResult = true;
}
}
DestroyMenu(hMenu);
pContextMenu->Release();
}
}
pMalloc->Free(pidlMain);
}
//
// Clean up and return.
//
psfFolder->Release();
pMalloc->Release();
return bResult;
}
#endif
//-----------------------------------------------------------------------------
void ParseProperties(QStringMap &properties, const QStringList &lines, QChar separator)
{
properties.clear();
foreach (QString l, lines)
{
l = l.trimmed();
int index = l.indexOf(separator);
QString key;
QString value;
if (index != -1)
{
key = l.left(index).trimmed();
value = l.mid(index + 1).trimmed();
}
else
key = l;
properties.insert(key, value);
}
}
//------------------------------------------------------------------------------
void GetStandardItemTextRecursive(QString &name, const QStandardItem &item, const QChar &separator)
{
if (item.parent())
{
GetStandardItemTextRecursive(name, *item.parent());
name.append(separator);
}
name.append(item.data(Qt::DisplayRole).toString());
}
//------------------------------------------------------------------------------
void BuildNameToModelIndex(name_modelindex_map_t &map, const QStandardItem &item)
{
QString name;
GetStandardItemTextRecursive(name, item);
map.insert(name, item.index());
for (int i = 0; i < item.rowCount(); ++i)
{
const QStandardItem *child = item.child(i);
Q_ASSERT(child);
BuildNameToModelIndex(map, *child);
}
}
//------------------------------------------------------------------------------
void BuildNameToModelIndex(name_modelindex_map_t &map, const QStandardItemModel &model)
{
for (int i = 0; i < model.rowCount(); ++i)
{
const QStandardItem *item = model.item(i);
Q_ASSERT(item);
BuildNameToModelIndex(map, *item);
}
}
//------------------------------------------------------------------------------
bool KeychainSet(QObject *parent, const QUrl &url, QSettings &settings)
{
QEventLoop loop(parent);
QKeychain::WritePasswordJob job(UrlToStringNoCredentials(url));
#ifdef Q_OS_WIN
settings.beginGroup("Keychain");
job.setSettings(&settings);
#else
Q_UNUSED(settings)
#endif
job.connect(&job, SIGNAL(finished(QKeychain::Job *)), &loop, SLOT(quit()));
job.setAutoDelete(false);
job.setInsecureFallback(false);
job.setKey(url.userName());
job.setTextData(url.password());
job.start();
loop.exec();
#ifdef Q_OS_WIN
settings.endGroup();
#endif
return job.error() == QKeychain::NoError;
}
//------------------------------------------------------------------------------
bool KeychainGet(QObject *parent, QUrl &url, QSettings &settings)
{
QEventLoop loop(parent);
QKeychain::ReadPasswordJob job(UrlToStringNoCredentials(url));
#ifdef Q_OS_WIN
settings.beginGroup("Keychain");
job.setSettings(&settings);
#else
Q_UNUSED(settings)
#endif
job.connect(&job, SIGNAL(finished(QKeychain::Job *)), &loop, SLOT(quit()));
job.setAutoDelete(false);
job.setInsecureFallback(false);
job.setAutoDelete(false);
job.setKey(url.userName());
job.start();
loop.exec();
#ifdef Q_OS_WIN
settings.endGroup();
#endif
if (job.error() != QKeychain::NoError)
return false;
url.setUserName(job.key());
url.setPassword(job.textData());
return true;
}
//------------------------------------------------------------------------------
bool KeychainDelete(QObject *parent, const QUrl &url, QSettings &settings)
{
QEventLoop loop(parent);
QKeychain::DeletePasswordJob job(UrlToStringNoCredentials(url));
#ifdef Q_OS_WIN
settings.beginGroup("Keychain");
job.setSettings(&settings);
#else
Q_UNUSED(settings)
#endif
job.connect(&job, SIGNAL(finished(QKeychain::Job *)), &loop, SLOT(quit()));
job.setAutoDelete(false);
job.setInsecureFallback(false);
job.setAutoDelete(false);
job.setKey(url.userName());
job.start();
loop.exec();
#ifdef Q_OS_WIN
settings.endGroup();
#endif
return job.error() == QKeychain::NoError;
}
//------------------------------------------------------------------------------
QString HashString(const QString &str)
{
QCryptographicHash hash(QCryptographicHash::Sha1);
const QByteArray ba(str.toUtf8());
hash.addData(ba.data(), ba.size());
QString str_out(hash.result().toHex());
return str_out;
}
//------------------------------------------------------------------------------
QString UrlToStringDisplay(const QUrl &url)
{
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
return url.toString(QUrl::RemovePassword);
#else
return url.toDisplayString();
#endif
}
//------------------------------------------------------------------------------
QString UrlToStringNoCredentials(const QUrl &url)
{
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
return url.toString(QUrl::RemoveUserInfo);
#else
return url.toString(QUrl::PrettyDecoded | QUrl::RemoveUserInfo);
#endif
}
//------------------------------------------------------------------------------
// Attempt to address weird behaviour of QUrl to string conversions
QString UrlToString(const QUrl &url)
{
QString username = url.userName();
// QUrl generates bad local file url for Windows local paths with drive letters
if (url.isLocalFile())
return url.toLocalFile();
else if (username.isEmpty()) // QUrl generates an invalid url like http://@host.domain/path
return UrlToStringNoCredentials(url);
else
return url.toEncoded();
}
//------------------------------------------------------------------------------
void SplitCommandLine(const QString &commandLine, QString &command, QString &extraParams)
{
// Process command string
command = commandLine.trimmed();
Q_ASSERT(!command.isEmpty());
// Split command from embedded params
extraParams.clear();
// Command ends after first space
QChar cmd_char_end = ' ';
int cmd_start = 0;
int backtrack = 0;
// ...unless it is a quoted command
if (command[0] == '"')
{
cmd_char_end = '"';
cmd_start = 1;
backtrack = 1;
}
int cmd_end = command.indexOf(cmd_char_end, cmd_start);
if (cmd_end > 0)
{
extraParams = command.mid(cmd_end + 1);
command = command.mid(cmd_start, cmd_end - backtrack);
}
command = command.trimmed();
extraParams = extraParams.trimmed();
}
//------------------------------------------------------------------------------
bool SpawnExternalProcess(QObject *processParent, const QString &command, const QStringList &fileList, const stringset_t &pathSet, const QString &workspaceDir, UICallback &uiCallback)
{
static const char *MACRO_FILE = "%FILE";
static const char *MACRO_FOLDER = "%FOLDER";
static const char *MACRO_WORKSPACE = "%WORKSPACE";
QStringList params;
QString cmd, extra_params;
SplitCommandLine(command, cmd, extra_params);
// Push all additional params, except those containing macros
QString macro_file;
QString macro_folder;
if (!extra_params.isEmpty())
{
QStringList extra_param_list = extra_params.split(' ');
foreach (const QString &p, extra_param_list)
{
if (p.indexOf(MACRO_FILE) != -1)
{
macro_file = p;
continue;
}
else if (p.indexOf(MACRO_FOLDER) != -1)
{
macro_folder = p;
continue;
}
else if (p.indexOf(MACRO_WORKSPACE) != -1)
{
// Add in-place
QString n = p;
n.replace(MACRO_WORKSPACE, QDir::toNativeSeparators(workspaceDir), Qt::CaseInsensitive);
params.push_back(n);
continue;
}
params.push_back(p);
}
}
// Build file params
if (!macro_file.isEmpty())
{
foreach (const QString &f, fileList)
{
QString path = QDir::toNativeSeparators(QFileInfo(workspaceDir + PATH_SEPARATOR + f).absoluteFilePath());
QString macro = macro_file;
path = macro.replace(MACRO_FILE, path, Qt::CaseInsensitive);
params.append(path);
}
}
// Build folder params
if (!macro_folder.isEmpty())
{
foreach (const QString &f, pathSet)
{
QString path = QDir::toNativeSeparators(QFileInfo(workspaceDir + PATH_SEPARATOR + f).absoluteFilePath());
QString macro = macro_folder;
path = macro.replace(MACRO_FOLDER, path, Qt::CaseInsensitive);
params.append(path);
}
}
uiCallback.logText("<b>" + cmd + " " + params.join(" ") + "</b><br>", true);
QProcess proc(processParent);
return proc.startDetached(cmd, params);
}
//------------------------------------------------------------------------------
void TrimStringList(QStringList &list)
{
for (int i = 0; i < list.length(); ++i)
list[i] = list[i].trimmed();
}