CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Windows/W32Util/Misc.cpp
Views: 1401
#include "ppsspp_config.h"1#include "CommonWindows.h"23#include <WinUser.h>4#include <shellapi.h>5#include <commctrl.h>6#include <ShlObj.h>78#include "Misc.h"9#include "Common/Data/Encoding/Utf8.h"10#include "Common/StringUtils.h"11#include "Common/File/FileUtil.h"1213bool KeyDownAsync(int vkey) {14#if PPSSPP_PLATFORM(UWP)15return 0;16#else17return (GetAsyncKeyState(vkey) & 0x8000) != 0;18#endif19}2021namespace W32Util22{23void CenterWindow(HWND hwnd)24{25HWND hwndParent;26RECT rect, rectP;27int width, height;28int screenwidth, screenheight;29int x, y;3031//make the window relative to its parent32hwndParent = GetParent(hwnd);33if (!hwndParent)34return;3536GetWindowRect(hwnd, &rect);37GetWindowRect(hwndParent, &rectP);3839width = rect.right - rect.left;40height = rect.bottom - rect.top;4142x = ((rectP.right-rectP.left) - width) / 2 + rectP.left;43y = ((rectP.bottom-rectP.top) - height) / 2 + rectP.top;4445screenwidth = GetSystemMetrics(SM_CXSCREEN);46screenheight = GetSystemMetrics(SM_CYSCREEN);4748//make sure that the dialog box never moves outside of49//the screen50if(x < 0) x = 0;51if(y < 0) y = 0;52if(x + width > screenwidth) x = screenwidth - width;53if(y + height > screenheight) y = screenheight - height;5455MoveWindow(hwnd, x, y, width, height, FALSE);56}5758BOOL CopyTextToClipboard(HWND hwnd, const char *text) {59std::wstring wtext = ConvertUTF8ToWString(text);60return CopyTextToClipboard(hwnd, wtext);61}6263BOOL CopyTextToClipboard(HWND hwnd, const std::wstring &wtext) {64if (!OpenClipboard(hwnd))65return FALSE;66EmptyClipboard();67HANDLE hglbCopy = GlobalAlloc(GMEM_MOVEABLE, (wtext.size() + 1) * sizeof(wchar_t));68if (hglbCopy == NULL) {69CloseClipboard();70return FALSE;71}7273// Lock the handle and copy the text to the buffer.7475wchar_t *lptstrCopy = (wchar_t *)GlobalLock(hglbCopy);76if (lptstrCopy) {77wcscpy(lptstrCopy, wtext.c_str());78lptstrCopy[wtext.size()] = (wchar_t) 0; // null character79GlobalUnlock(hglbCopy);80SetClipboardData(CF_UNICODETEXT, hglbCopy);81}82CloseClipboard();83return lptstrCopy ? TRUE : FALSE;84}8586void MakeTopMost(HWND hwnd, bool topMost) {87HWND style = HWND_NOTOPMOST;88if (topMost) style = HWND_TOPMOST;89SetWindowPos(hwnd, style, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE);90}9192void GetWindowRes(HWND hWnd, int *xres, int *yres) {93RECT rc;94GetClientRect(hWnd, &rc);95*xres = rc.right - rc.left;96*yres = rc.bottom - rc.top;97}9899void ShowFileInFolder(const std::string &path) {100// SHParseDisplayName can't handle relative paths, so normalize first.101std::string resolved = ReplaceAll(File::ResolvePath(path), "/", "\\");102103SFGAOF flags{};104PIDLIST_ABSOLUTE pidl = nullptr;105HRESULT hr = SHParseDisplayName(ConvertUTF8ToWString(resolved).c_str(), nullptr, &pidl, 0, &flags);106107if (pidl) {108if (SUCCEEDED(hr))109SHOpenFolderAndSelectItems(pidl, 0, nullptr, 0);110CoTaskMemFree(pidl);111}112}113114static const wchar_t *RemoveExecutableFromCommandLine(const wchar_t *cmdline) {115if (!cmdline) {116return L"";117}118119switch (cmdline[0]) {120case '"':121// We don't need to handle escaped quotes, since filenames can't have that.122cmdline = wcschr(cmdline + 1, '"');123if (cmdline) {124++cmdline;125if (cmdline[0] == ' ') {126++cmdline;127}128}129break;130131default:132cmdline = wcschr(cmdline, ' ');133if (cmdline) {134++cmdline;135}136break;137}138139return cmdline;140}141142void GetSelfExecuteParams(std::wstring &workingDirectory, std::wstring &moduleFilename) {143workingDirectory.resize(MAX_PATH);144size_t sz = GetCurrentDirectoryW((DWORD)workingDirectory.size(), &workingDirectory[0]);145if (sz != 0 && sz < workingDirectory.size()) {146// This means success, so now we can remove the null terminator.147workingDirectory.resize(sz);148} else if (sz > workingDirectory.size()) {149// If insufficient, sz will include the null terminator, so we remove after.150workingDirectory.resize(sz);151sz = GetCurrentDirectoryW((DWORD)sz, &workingDirectory[0]);152workingDirectory.resize(sz);153}154155moduleFilename.clear();156do {157moduleFilename.resize(moduleFilename.size() + MAX_PATH);158// On failure, this will return the same value as passed in, but success will always be one lower.159sz = GetModuleFileName(GetModuleHandle(nullptr), &moduleFilename[0], (DWORD)moduleFilename.size());160} while (sz >= moduleFilename.size());161moduleFilename.resize(sz);162}163164void ExitAndRestart(bool overrideArgs, const std::string &args) {165SpawnNewInstance(overrideArgs, args);166167ExitProcess(0);168}169170bool ExecuteAndGetReturnCode(const wchar_t *executable, const wchar_t *cmdline, const wchar_t *currentDirectory, DWORD *exitCode) {171PROCESS_INFORMATION processInformation = { 0 };172STARTUPINFO startupInfo = { 0 };173startupInfo.cb = sizeof(startupInfo);174175std::wstring cmdlineW;176cmdlineW += L"PPSSPP "; // could also put the executable name as first argument, but concerned about escaping.177cmdlineW += cmdline;178179// Create the process180bool result = CreateProcess(executable, (LPWSTR)cmdlineW.c_str(),181NULL, NULL, FALSE,182NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,183NULL, currentDirectory, &startupInfo, &processInformation);184185if (!result) {186// We failed.187return false;188}189190// Successfully created the process. Wait for it to finish.191WaitForSingleObject(processInformation.hProcess, INFINITE);192result = GetExitCodeProcess(processInformation.hProcess, exitCode);193CloseHandle(processInformation.hProcess);194CloseHandle(processInformation.hThread);195return result != 0;196}197198void SpawnNewInstance(bool overrideArgs, const std::string &args) {199// This preserves arguments (for example, config file) and working directory.200std::wstring workingDirectory;201std::wstring moduleFilename;202GetSelfExecuteParams(workingDirectory, moduleFilename);203204const wchar_t *cmdline;205std::wstring wargs;206if (overrideArgs) {207wargs = ConvertUTF8ToWString(args);208cmdline = wargs.c_str();209} else {210cmdline = RemoveExecutableFromCommandLine(GetCommandLineW());211}212ShellExecute(nullptr, nullptr, moduleFilename.c_str(), cmdline, workingDirectory.c_str(), SW_SHOW);213}214215ClipboardData::ClipboardData(const char *format, size_t sz) {216format_ = RegisterClipboardFormatA(format);217handle_ = format_ != 0 ? GlobalAlloc(GHND, sz) : 0;218data = handle_ != 0 ? GlobalLock(handle_) : nullptr;219}220221ClipboardData::ClipboardData(UINT format, size_t sz) {222format_ = format;223handle_ = GlobalAlloc(GHND, sz);224data = handle_ != 0 ? GlobalLock(handle_) : nullptr;225}226227ClipboardData::~ClipboardData() {228if (handle_ != 0) {229GlobalUnlock(handle_);230GlobalFree(handle_);231}232}233234void ClipboardData::Set() {235if (format_ == 0 || handle_ == 0 || data == 0)236return;237SetClipboardData(format_, handle_);238}239}240241static constexpr UINT_PTR IDT_UPDATE = 0xC0DE0042;242static constexpr UINT UPDATE_DELAY = 1000 / 60;243244GenericListControl::GenericListControl(HWND hwnd, const GenericListViewDef& def)245: handle(hwnd), columns(def.columns),columnCount(def.columnCount),valid(false),246inResizeColumns(false),updating(false)247{248DWORD style = GetWindowLong(handle,GWL_STYLE) | LVS_REPORT;249SetWindowLong(handle, GWL_STYLE, style);250251SetWindowLongPtr(handle,GWLP_USERDATA,(LONG_PTR)this);252oldProc = (WNDPROC) SetWindowLongPtr(handle,GWLP_WNDPROC,(LONG_PTR)wndProc);253254auto exStyle = LVS_EX_FULLROWSELECT;255if (def.checkbox)256exStyle |= LVS_EX_CHECKBOXES;257SendMessage(handle, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle);258259LVCOLUMN lvc;260lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;261lvc.iSubItem = 0;262263RECT rect;264GetClientRect(handle,&rect);265266int totalListSize = rect.right-rect.left;267for (int i = 0; i < columnCount; i++) {268lvc.cx = (int)(columns[i].size * totalListSize);269lvc.pszText = (LPTSTR)columns[i].name;270271if (columns[i].flags & GLVC_CENTERED)272lvc.fmt = LVCFMT_CENTER;273else274lvc.fmt = LVCFMT_LEFT;275276ListView_InsertColumn(handle, i, &lvc);277}278279if (def.columnOrder != NULL)280ListView_SetColumnOrderArray(handle,columnCount,def.columnOrder);281282SetSendInvalidRows(false);283valid = true;284}285286GenericListControl::~GenericListControl() {287SetWindowLongPtr(handle, GWLP_USERDATA, (LONG_PTR)nullptr);288// Don't destroy the image list, it's done automatically by the list view.289}290291void GenericListControl::SetIconList(int w, int h, const std::vector<HICON> &icons) {292images_ = ImageList_Create(w, h, ILC_COLOR32 | ILC_MASK, 0, (int)icons.size());293for (const HICON &icon : icons)294ImageList_AddIcon((HIMAGELIST)images_, icon);295296ListView_SetImageList(handle, (HIMAGELIST)images_, LVSIL_STATE);297}298299int GenericListControl::HandleNotify(LPARAM lParam) {300LPNMHDR mhdr = (LPNMHDR) lParam;301302if (mhdr->code == NM_DBLCLK)303{304LPNMITEMACTIVATE item = (LPNMITEMACTIVATE) lParam;305if ((item->iItem != -1 && item->iItem < GetRowCount()) || sendInvalidRows)306OnDoubleClick(item->iItem,item->iSubItem);307return 0;308}309310if (mhdr->code == NM_RCLICK)311{312const LPNMITEMACTIVATE item = (LPNMITEMACTIVATE)lParam;313if ((item->iItem != -1 && item->iItem < GetRowCount()) || sendInvalidRows)314OnRightClick(item->iItem,item->iSubItem,item->ptAction);315return 0;316}317318if (mhdr->code == NM_CUSTOMDRAW && (ListenRowPrePaint() || ListenColPrePaint())) {319LPNMLVCUSTOMDRAW msg = (LPNMLVCUSTOMDRAW)lParam;320switch (msg->nmcd.dwDrawStage) {321case CDDS_PREPAINT:322return CDRF_NOTIFYITEMDRAW;323324case CDDS_ITEMPREPAINT:325if (OnRowPrePaint((int)msg->nmcd.dwItemSpec, msg)) {326return CDRF_NEWFONT;327}328return ListenColPrePaint() ? CDRF_NOTIFYSUBITEMDRAW : CDRF_DODEFAULT;329330case CDDS_SUBITEM | CDDS_ITEMPREPAINT:331if (OnColPrePaint((int)msg->nmcd.dwItemSpec, msg->iSubItem, msg)) {332return CDRF_NEWFONT;333}334return CDRF_DODEFAULT;335}336337return CDRF_DODEFAULT;338}339340if (mhdr->code == LVN_GETDISPINFO)341{342NMLVDISPINFO* dispInfo = (NMLVDISPINFO*)lParam;343344stringBuffer[0] = 0;345GetColumnText(stringBuffer,dispInfo->item.iItem,dispInfo->item.iSubItem);346347if (stringBuffer[0] == 0)348wcscat(stringBuffer,L"Invalid");349350dispInfo->item.pszText = stringBuffer;351dispInfo->item.mask |= LVIF_TEXT;352return 0;353}354355// handle checkboxes356if (mhdr->code == LVN_ITEMCHANGED && updating == false)357{358NMLISTVIEW* item = (NMLISTVIEW*) lParam;359if (item->iItem != -1 && (item->uChanged & LVIF_STATE) != 0)360{361// image is 1 if unchcked, 2 if checked362int oldImage = (item->uOldState & LVIS_STATEIMAGEMASK) >> 12;363int newImage = (item->uNewState & LVIS_STATEIMAGEMASK) >> 12;364if (oldImage != newImage)365OnToggle(item->iItem,newImage == 2);366}367368return 0;369}370371if (mhdr->code == LVN_INCREMENTALSEARCH) {372NMLVFINDITEM *request = (NMLVFINDITEM *)lParam;373uint32_t supported = LVFI_WRAP | LVFI_STRING | LVFI_PARTIAL | LVFI_SUBSTRING;374if ((request->lvfi.flags & ~supported) == 0 && (request->lvfi.flags & LVFI_STRING) != 0) {375bool wrap = (request->lvfi.flags & LVFI_WRAP) != 0;376bool partial = (request->lvfi.flags & (LVFI_PARTIAL | LVFI_SUBSTRING)) != 0;377378// It seems like 0 is always sent for start, let's override.379int startRow = request->iStart;380if (startRow == 0)381startRow = GetSelectedIndex();382int result = OnIncrementalSearch(startRow, request->lvfi.psz, wrap, partial);383if (result != -1) {384request->lvfi.flags = LVFI_PARAM;385request->lvfi.lParam = (LPARAM)result;386}387}388}389390return 0;391}392393int GenericListControl::OnIncrementalSearch(int startRow, const wchar_t *str, bool wrap, bool partial) {394int size = GetRowCount();395size_t searchlen = wcslen(str);396if (!wrap)397size -= startRow;398399// We start with the earliest column, preferring matches on the leftmost columns by default.400for (int c = 0; c < columnCount; ++c) {401for (int i = 0; i < size; ++i) {402int r = (startRow + i) % size;403stringBuffer[0] = 0;404GetColumnText(stringBuffer, r, c);405int difference = partial ? _wcsnicmp(str, stringBuffer, searchlen) : _wcsicmp(str, stringBuffer);406if (difference == 0)407return r;408}409}410411return -1;412}413414void GenericListControl::Update() {415if (!updateScheduled_) {416SetTimer(handle, IDT_UPDATE, UPDATE_DELAY, nullptr);417updateScheduled_ = true;418}419}420421void GenericListControl::ProcessUpdate() {422updating = true;423int newRows = GetRowCount();424425int items = ListView_GetItemCount(handle);426ListView_SetItemCount(handle, newRows);427428// Scroll to top if we're removing items. It kinda does this automatically, but it's buggy.429if (items > newRows) {430POINT pt{};431ListView_GetOrigin(handle, &pt);432433if (pt.x != 0 || pt.y != 0)434ListView_Scroll(handle, -pt.x, -pt.y);435}436437while (items < newRows)438{439LVITEM lvI;440lvI.pszText = LPSTR_TEXTCALLBACK; // Sends an LVN_GETDISPINFO message.441lvI.mask = LVIF_TEXT | LVIF_IMAGE |LVIF_STATE;442lvI.stateMask = 0;443lvI.iSubItem = 0;444lvI.state = 0;445lvI.iItem = items;446lvI.iImage = items;447448ListView_InsertItem(handle, &lvI);449items++;450}451452while (items > newRows)453{454ListView_DeleteItem(handle,--items);455}456457for (auto &act : pendingActions_) {458switch (act.action) {459case Action::CHECK:460ListView_SetCheckState(handle, act.item, act.state ? TRUE : FALSE);461break;462463case Action::IMAGE:464ListView_SetItemState(handle, act.item, (act.state & 0xF) << 12, LVIS_STATEIMAGEMASK);465break;466}467}468pendingActions_.clear();469470ResizeColumns();471472InvalidateRect(handle, nullptr, TRUE);473ListView_RedrawItems(handle, 0, newRows - 1);474UpdateWindow(handle);475updating = false;476}477478479void GenericListControl::SetCheckState(int item, bool state) {480pendingActions_.push_back({ Action::CHECK, item, state ? 1 : 0 });481Update();482}483484void GenericListControl::SetItemState(int item, uint8_t state) {485pendingActions_.push_back({ Action::IMAGE, item, (int)state });486Update();487}488489void GenericListControl::ResizeColumns()490{491if (inResizeColumns)492return;493inResizeColumns = true;494495RECT rect;496GetClientRect(handle, &rect);497498int totalListSize = rect.right - rect.left;499for (int i = 0; i < columnCount; i++)500{501ListView_SetColumnWidth(handle, i, columns[i].size * totalListSize);502}503inResizeColumns = false;504}505506LRESULT CALLBACK GenericListControl::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)507{508GenericListControl* list = (GenericListControl*) GetWindowLongPtr(hwnd,GWLP_USERDATA);509if (!list)510return FALSE;511512LRESULT returnValue;513if (list->valid && list->WindowMessage(msg,wParam,lParam,returnValue) == true)514return returnValue;515516switch (msg)517{518case WM_SIZE:519list->ResizeColumns();520break;521522case WM_KEYDOWN:523switch (wParam)524{525case VK_INSERT:526case 'C':527if (KeyDownAsync(VK_CONTROL))528list->ProcessCopy();529break;530531case 'A':532if (KeyDownAsync(VK_CONTROL))533list->SelectAll();534break;535}536break;537538case WM_TIMER:539if (wParam == IDT_UPDATE) {540list->ProcessUpdate();541list->updateScheduled_ = false;542KillTimer(hwnd, wParam);543}544break;545}546547return (LRESULT)CallWindowProc((WNDPROC)list->oldProc,hwnd,msg,wParam,lParam);548}549550void GenericListControl::ProcessCopy()551{552int start = GetSelectedIndex();553int size;554if (start == -1)555size = GetRowCount();556else557size = ListView_GetSelectedCount(handle);558559CopyRows(start, size);560}561562void GenericListControl::CopyRows(int start, int size)563{564std::wstring data;565566if (start == 0 && size == GetRowCount())567{568// Let's also copy the header if everything is selected.569for (int c = 0; c < columnCount; ++c)570{571data.append(columns[c].name);572if (c < columnCount - 1)573data.append(L"\t");574else575data.append(L"\r\n");576}577}578579for (int r = start; r < start + size; ++r)580{581for (int c = 0; c < columnCount; ++c)582{583stringBuffer[0] = 0;584GetColumnText(stringBuffer, r, c);585data.append(stringBuffer);586if (c < columnCount - 1)587data.append(L"\t");588else589data.append(L"\r\n");590}591}592W32Util::CopyTextToClipboard(handle, data);593}594595void GenericListControl::SelectAll()596{597ListView_SetItemState(handle, -1, LVIS_SELECTED, LVIS_SELECTED);598}599600int GenericListControl::GetSelectedIndex()601{602return ListView_GetNextItem(handle, -1, LVNI_SELECTED);603}604605606