CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Windows/W32Util/Misc.cpp
Views: 1401
1
#include "ppsspp_config.h"
2
#include "CommonWindows.h"
3
4
#include <WinUser.h>
5
#include <shellapi.h>
6
#include <commctrl.h>
7
#include <ShlObj.h>
8
9
#include "Misc.h"
10
#include "Common/Data/Encoding/Utf8.h"
11
#include "Common/StringUtils.h"
12
#include "Common/File/FileUtil.h"
13
14
bool KeyDownAsync(int vkey) {
15
#if PPSSPP_PLATFORM(UWP)
16
return 0;
17
#else
18
return (GetAsyncKeyState(vkey) & 0x8000) != 0;
19
#endif
20
}
21
22
namespace W32Util
23
{
24
void CenterWindow(HWND hwnd)
25
{
26
HWND hwndParent;
27
RECT rect, rectP;
28
int width, height;
29
int screenwidth, screenheight;
30
int x, y;
31
32
//make the window relative to its parent
33
hwndParent = GetParent(hwnd);
34
if (!hwndParent)
35
return;
36
37
GetWindowRect(hwnd, &rect);
38
GetWindowRect(hwndParent, &rectP);
39
40
width = rect.right - rect.left;
41
height = rect.bottom - rect.top;
42
43
x = ((rectP.right-rectP.left) - width) / 2 + rectP.left;
44
y = ((rectP.bottom-rectP.top) - height) / 2 + rectP.top;
45
46
screenwidth = GetSystemMetrics(SM_CXSCREEN);
47
screenheight = GetSystemMetrics(SM_CYSCREEN);
48
49
//make sure that the dialog box never moves outside of
50
//the screen
51
if(x < 0) x = 0;
52
if(y < 0) y = 0;
53
if(x + width > screenwidth) x = screenwidth - width;
54
if(y + height > screenheight) y = screenheight - height;
55
56
MoveWindow(hwnd, x, y, width, height, FALSE);
57
}
58
59
BOOL CopyTextToClipboard(HWND hwnd, const char *text) {
60
std::wstring wtext = ConvertUTF8ToWString(text);
61
return CopyTextToClipboard(hwnd, wtext);
62
}
63
64
BOOL CopyTextToClipboard(HWND hwnd, const std::wstring &wtext) {
65
if (!OpenClipboard(hwnd))
66
return FALSE;
67
EmptyClipboard();
68
HANDLE hglbCopy = GlobalAlloc(GMEM_MOVEABLE, (wtext.size() + 1) * sizeof(wchar_t));
69
if (hglbCopy == NULL) {
70
CloseClipboard();
71
return FALSE;
72
}
73
74
// Lock the handle and copy the text to the buffer.
75
76
wchar_t *lptstrCopy = (wchar_t *)GlobalLock(hglbCopy);
77
if (lptstrCopy) {
78
wcscpy(lptstrCopy, wtext.c_str());
79
lptstrCopy[wtext.size()] = (wchar_t) 0; // null character
80
GlobalUnlock(hglbCopy);
81
SetClipboardData(CF_UNICODETEXT, hglbCopy);
82
}
83
CloseClipboard();
84
return lptstrCopy ? TRUE : FALSE;
85
}
86
87
void MakeTopMost(HWND hwnd, bool topMost) {
88
HWND style = HWND_NOTOPMOST;
89
if (topMost) style = HWND_TOPMOST;
90
SetWindowPos(hwnd, style, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE);
91
}
92
93
void GetWindowRes(HWND hWnd, int *xres, int *yres) {
94
RECT rc;
95
GetClientRect(hWnd, &rc);
96
*xres = rc.right - rc.left;
97
*yres = rc.bottom - rc.top;
98
}
99
100
void ShowFileInFolder(const std::string &path) {
101
// SHParseDisplayName can't handle relative paths, so normalize first.
102
std::string resolved = ReplaceAll(File::ResolvePath(path), "/", "\\");
103
104
SFGAOF flags{};
105
PIDLIST_ABSOLUTE pidl = nullptr;
106
HRESULT hr = SHParseDisplayName(ConvertUTF8ToWString(resolved).c_str(), nullptr, &pidl, 0, &flags);
107
108
if (pidl) {
109
if (SUCCEEDED(hr))
110
SHOpenFolderAndSelectItems(pidl, 0, nullptr, 0);
111
CoTaskMemFree(pidl);
112
}
113
}
114
115
static const wchar_t *RemoveExecutableFromCommandLine(const wchar_t *cmdline) {
116
if (!cmdline) {
117
return L"";
118
}
119
120
switch (cmdline[0]) {
121
case '"':
122
// We don't need to handle escaped quotes, since filenames can't have that.
123
cmdline = wcschr(cmdline + 1, '"');
124
if (cmdline) {
125
++cmdline;
126
if (cmdline[0] == ' ') {
127
++cmdline;
128
}
129
}
130
break;
131
132
default:
133
cmdline = wcschr(cmdline, ' ');
134
if (cmdline) {
135
++cmdline;
136
}
137
break;
138
}
139
140
return cmdline;
141
}
142
143
void GetSelfExecuteParams(std::wstring &workingDirectory, std::wstring &moduleFilename) {
144
workingDirectory.resize(MAX_PATH);
145
size_t sz = GetCurrentDirectoryW((DWORD)workingDirectory.size(), &workingDirectory[0]);
146
if (sz != 0 && sz < workingDirectory.size()) {
147
// This means success, so now we can remove the null terminator.
148
workingDirectory.resize(sz);
149
} else if (sz > workingDirectory.size()) {
150
// If insufficient, sz will include the null terminator, so we remove after.
151
workingDirectory.resize(sz);
152
sz = GetCurrentDirectoryW((DWORD)sz, &workingDirectory[0]);
153
workingDirectory.resize(sz);
154
}
155
156
moduleFilename.clear();
157
do {
158
moduleFilename.resize(moduleFilename.size() + MAX_PATH);
159
// On failure, this will return the same value as passed in, but success will always be one lower.
160
sz = GetModuleFileName(GetModuleHandle(nullptr), &moduleFilename[0], (DWORD)moduleFilename.size());
161
} while (sz >= moduleFilename.size());
162
moduleFilename.resize(sz);
163
}
164
165
void ExitAndRestart(bool overrideArgs, const std::string &args) {
166
SpawnNewInstance(overrideArgs, args);
167
168
ExitProcess(0);
169
}
170
171
bool ExecuteAndGetReturnCode(const wchar_t *executable, const wchar_t *cmdline, const wchar_t *currentDirectory, DWORD *exitCode) {
172
PROCESS_INFORMATION processInformation = { 0 };
173
STARTUPINFO startupInfo = { 0 };
174
startupInfo.cb = sizeof(startupInfo);
175
176
std::wstring cmdlineW;
177
cmdlineW += L"PPSSPP "; // could also put the executable name as first argument, but concerned about escaping.
178
cmdlineW += cmdline;
179
180
// Create the process
181
bool result = CreateProcess(executable, (LPWSTR)cmdlineW.c_str(),
182
NULL, NULL, FALSE,
183
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
184
NULL, currentDirectory, &startupInfo, &processInformation);
185
186
if (!result) {
187
// We failed.
188
return false;
189
}
190
191
// Successfully created the process. Wait for it to finish.
192
WaitForSingleObject(processInformation.hProcess, INFINITE);
193
result = GetExitCodeProcess(processInformation.hProcess, exitCode);
194
CloseHandle(processInformation.hProcess);
195
CloseHandle(processInformation.hThread);
196
return result != 0;
197
}
198
199
void SpawnNewInstance(bool overrideArgs, const std::string &args) {
200
// This preserves arguments (for example, config file) and working directory.
201
std::wstring workingDirectory;
202
std::wstring moduleFilename;
203
GetSelfExecuteParams(workingDirectory, moduleFilename);
204
205
const wchar_t *cmdline;
206
std::wstring wargs;
207
if (overrideArgs) {
208
wargs = ConvertUTF8ToWString(args);
209
cmdline = wargs.c_str();
210
} else {
211
cmdline = RemoveExecutableFromCommandLine(GetCommandLineW());
212
}
213
ShellExecute(nullptr, nullptr, moduleFilename.c_str(), cmdline, workingDirectory.c_str(), SW_SHOW);
214
}
215
216
ClipboardData::ClipboardData(const char *format, size_t sz) {
217
format_ = RegisterClipboardFormatA(format);
218
handle_ = format_ != 0 ? GlobalAlloc(GHND, sz) : 0;
219
data = handle_ != 0 ? GlobalLock(handle_) : nullptr;
220
}
221
222
ClipboardData::ClipboardData(UINT format, size_t sz) {
223
format_ = format;
224
handle_ = GlobalAlloc(GHND, sz);
225
data = handle_ != 0 ? GlobalLock(handle_) : nullptr;
226
}
227
228
ClipboardData::~ClipboardData() {
229
if (handle_ != 0) {
230
GlobalUnlock(handle_);
231
GlobalFree(handle_);
232
}
233
}
234
235
void ClipboardData::Set() {
236
if (format_ == 0 || handle_ == 0 || data == 0)
237
return;
238
SetClipboardData(format_, handle_);
239
}
240
}
241
242
static constexpr UINT_PTR IDT_UPDATE = 0xC0DE0042;
243
static constexpr UINT UPDATE_DELAY = 1000 / 60;
244
245
GenericListControl::GenericListControl(HWND hwnd, const GenericListViewDef& def)
246
: handle(hwnd), columns(def.columns),columnCount(def.columnCount),valid(false),
247
inResizeColumns(false),updating(false)
248
{
249
DWORD style = GetWindowLong(handle,GWL_STYLE) | LVS_REPORT;
250
SetWindowLong(handle, GWL_STYLE, style);
251
252
SetWindowLongPtr(handle,GWLP_USERDATA,(LONG_PTR)this);
253
oldProc = (WNDPROC) SetWindowLongPtr(handle,GWLP_WNDPROC,(LONG_PTR)wndProc);
254
255
auto exStyle = LVS_EX_FULLROWSELECT;
256
if (def.checkbox)
257
exStyle |= LVS_EX_CHECKBOXES;
258
SendMessage(handle, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, exStyle);
259
260
LVCOLUMN lvc;
261
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
262
lvc.iSubItem = 0;
263
264
RECT rect;
265
GetClientRect(handle,&rect);
266
267
int totalListSize = rect.right-rect.left;
268
for (int i = 0; i < columnCount; i++) {
269
lvc.cx = (int)(columns[i].size * totalListSize);
270
lvc.pszText = (LPTSTR)columns[i].name;
271
272
if (columns[i].flags & GLVC_CENTERED)
273
lvc.fmt = LVCFMT_CENTER;
274
else
275
lvc.fmt = LVCFMT_LEFT;
276
277
ListView_InsertColumn(handle, i, &lvc);
278
}
279
280
if (def.columnOrder != NULL)
281
ListView_SetColumnOrderArray(handle,columnCount,def.columnOrder);
282
283
SetSendInvalidRows(false);
284
valid = true;
285
}
286
287
GenericListControl::~GenericListControl() {
288
SetWindowLongPtr(handle, GWLP_USERDATA, (LONG_PTR)nullptr);
289
// Don't destroy the image list, it's done automatically by the list view.
290
}
291
292
void GenericListControl::SetIconList(int w, int h, const std::vector<HICON> &icons) {
293
images_ = ImageList_Create(w, h, ILC_COLOR32 | ILC_MASK, 0, (int)icons.size());
294
for (const HICON &icon : icons)
295
ImageList_AddIcon((HIMAGELIST)images_, icon);
296
297
ListView_SetImageList(handle, (HIMAGELIST)images_, LVSIL_STATE);
298
}
299
300
int GenericListControl::HandleNotify(LPARAM lParam) {
301
LPNMHDR mhdr = (LPNMHDR) lParam;
302
303
if (mhdr->code == NM_DBLCLK)
304
{
305
LPNMITEMACTIVATE item = (LPNMITEMACTIVATE) lParam;
306
if ((item->iItem != -1 && item->iItem < GetRowCount()) || sendInvalidRows)
307
OnDoubleClick(item->iItem,item->iSubItem);
308
return 0;
309
}
310
311
if (mhdr->code == NM_RCLICK)
312
{
313
const LPNMITEMACTIVATE item = (LPNMITEMACTIVATE)lParam;
314
if ((item->iItem != -1 && item->iItem < GetRowCount()) || sendInvalidRows)
315
OnRightClick(item->iItem,item->iSubItem,item->ptAction);
316
return 0;
317
}
318
319
if (mhdr->code == NM_CUSTOMDRAW && (ListenRowPrePaint() || ListenColPrePaint())) {
320
LPNMLVCUSTOMDRAW msg = (LPNMLVCUSTOMDRAW)lParam;
321
switch (msg->nmcd.dwDrawStage) {
322
case CDDS_PREPAINT:
323
return CDRF_NOTIFYITEMDRAW;
324
325
case CDDS_ITEMPREPAINT:
326
if (OnRowPrePaint((int)msg->nmcd.dwItemSpec, msg)) {
327
return CDRF_NEWFONT;
328
}
329
return ListenColPrePaint() ? CDRF_NOTIFYSUBITEMDRAW : CDRF_DODEFAULT;
330
331
case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
332
if (OnColPrePaint((int)msg->nmcd.dwItemSpec, msg->iSubItem, msg)) {
333
return CDRF_NEWFONT;
334
}
335
return CDRF_DODEFAULT;
336
}
337
338
return CDRF_DODEFAULT;
339
}
340
341
if (mhdr->code == LVN_GETDISPINFO)
342
{
343
NMLVDISPINFO* dispInfo = (NMLVDISPINFO*)lParam;
344
345
stringBuffer[0] = 0;
346
GetColumnText(stringBuffer,dispInfo->item.iItem,dispInfo->item.iSubItem);
347
348
if (stringBuffer[0] == 0)
349
wcscat(stringBuffer,L"Invalid");
350
351
dispInfo->item.pszText = stringBuffer;
352
dispInfo->item.mask |= LVIF_TEXT;
353
return 0;
354
}
355
356
// handle checkboxes
357
if (mhdr->code == LVN_ITEMCHANGED && updating == false)
358
{
359
NMLISTVIEW* item = (NMLISTVIEW*) lParam;
360
if (item->iItem != -1 && (item->uChanged & LVIF_STATE) != 0)
361
{
362
// image is 1 if unchcked, 2 if checked
363
int oldImage = (item->uOldState & LVIS_STATEIMAGEMASK) >> 12;
364
int newImage = (item->uNewState & LVIS_STATEIMAGEMASK) >> 12;
365
if (oldImage != newImage)
366
OnToggle(item->iItem,newImage == 2);
367
}
368
369
return 0;
370
}
371
372
if (mhdr->code == LVN_INCREMENTALSEARCH) {
373
NMLVFINDITEM *request = (NMLVFINDITEM *)lParam;
374
uint32_t supported = LVFI_WRAP | LVFI_STRING | LVFI_PARTIAL | LVFI_SUBSTRING;
375
if ((request->lvfi.flags & ~supported) == 0 && (request->lvfi.flags & LVFI_STRING) != 0) {
376
bool wrap = (request->lvfi.flags & LVFI_WRAP) != 0;
377
bool partial = (request->lvfi.flags & (LVFI_PARTIAL | LVFI_SUBSTRING)) != 0;
378
379
// It seems like 0 is always sent for start, let's override.
380
int startRow = request->iStart;
381
if (startRow == 0)
382
startRow = GetSelectedIndex();
383
int result = OnIncrementalSearch(startRow, request->lvfi.psz, wrap, partial);
384
if (result != -1) {
385
request->lvfi.flags = LVFI_PARAM;
386
request->lvfi.lParam = (LPARAM)result;
387
}
388
}
389
}
390
391
return 0;
392
}
393
394
int GenericListControl::OnIncrementalSearch(int startRow, const wchar_t *str, bool wrap, bool partial) {
395
int size = GetRowCount();
396
size_t searchlen = wcslen(str);
397
if (!wrap)
398
size -= startRow;
399
400
// We start with the earliest column, preferring matches on the leftmost columns by default.
401
for (int c = 0; c < columnCount; ++c) {
402
for (int i = 0; i < size; ++i) {
403
int r = (startRow + i) % size;
404
stringBuffer[0] = 0;
405
GetColumnText(stringBuffer, r, c);
406
int difference = partial ? _wcsnicmp(str, stringBuffer, searchlen) : _wcsicmp(str, stringBuffer);
407
if (difference == 0)
408
return r;
409
}
410
}
411
412
return -1;
413
}
414
415
void GenericListControl::Update() {
416
if (!updateScheduled_) {
417
SetTimer(handle, IDT_UPDATE, UPDATE_DELAY, nullptr);
418
updateScheduled_ = true;
419
}
420
}
421
422
void GenericListControl::ProcessUpdate() {
423
updating = true;
424
int newRows = GetRowCount();
425
426
int items = ListView_GetItemCount(handle);
427
ListView_SetItemCount(handle, newRows);
428
429
// Scroll to top if we're removing items. It kinda does this automatically, but it's buggy.
430
if (items > newRows) {
431
POINT pt{};
432
ListView_GetOrigin(handle, &pt);
433
434
if (pt.x != 0 || pt.y != 0)
435
ListView_Scroll(handle, -pt.x, -pt.y);
436
}
437
438
while (items < newRows)
439
{
440
LVITEM lvI;
441
lvI.pszText = LPSTR_TEXTCALLBACK; // Sends an LVN_GETDISPINFO message.
442
lvI.mask = LVIF_TEXT | LVIF_IMAGE |LVIF_STATE;
443
lvI.stateMask = 0;
444
lvI.iSubItem = 0;
445
lvI.state = 0;
446
lvI.iItem = items;
447
lvI.iImage = items;
448
449
ListView_InsertItem(handle, &lvI);
450
items++;
451
}
452
453
while (items > newRows)
454
{
455
ListView_DeleteItem(handle,--items);
456
}
457
458
for (auto &act : pendingActions_) {
459
switch (act.action) {
460
case Action::CHECK:
461
ListView_SetCheckState(handle, act.item, act.state ? TRUE : FALSE);
462
break;
463
464
case Action::IMAGE:
465
ListView_SetItemState(handle, act.item, (act.state & 0xF) << 12, LVIS_STATEIMAGEMASK);
466
break;
467
}
468
}
469
pendingActions_.clear();
470
471
ResizeColumns();
472
473
InvalidateRect(handle, nullptr, TRUE);
474
ListView_RedrawItems(handle, 0, newRows - 1);
475
UpdateWindow(handle);
476
updating = false;
477
}
478
479
480
void GenericListControl::SetCheckState(int item, bool state) {
481
pendingActions_.push_back({ Action::CHECK, item, state ? 1 : 0 });
482
Update();
483
}
484
485
void GenericListControl::SetItemState(int item, uint8_t state) {
486
pendingActions_.push_back({ Action::IMAGE, item, (int)state });
487
Update();
488
}
489
490
void GenericListControl::ResizeColumns()
491
{
492
if (inResizeColumns)
493
return;
494
inResizeColumns = true;
495
496
RECT rect;
497
GetClientRect(handle, &rect);
498
499
int totalListSize = rect.right - rect.left;
500
for (int i = 0; i < columnCount; i++)
501
{
502
ListView_SetColumnWidth(handle, i, columns[i].size * totalListSize);
503
}
504
inResizeColumns = false;
505
}
506
507
LRESULT CALLBACK GenericListControl::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
508
{
509
GenericListControl* list = (GenericListControl*) GetWindowLongPtr(hwnd,GWLP_USERDATA);
510
if (!list)
511
return FALSE;
512
513
LRESULT returnValue;
514
if (list->valid && list->WindowMessage(msg,wParam,lParam,returnValue) == true)
515
return returnValue;
516
517
switch (msg)
518
{
519
case WM_SIZE:
520
list->ResizeColumns();
521
break;
522
523
case WM_KEYDOWN:
524
switch (wParam)
525
{
526
case VK_INSERT:
527
case 'C':
528
if (KeyDownAsync(VK_CONTROL))
529
list->ProcessCopy();
530
break;
531
532
case 'A':
533
if (KeyDownAsync(VK_CONTROL))
534
list->SelectAll();
535
break;
536
}
537
break;
538
539
case WM_TIMER:
540
if (wParam == IDT_UPDATE) {
541
list->ProcessUpdate();
542
list->updateScheduled_ = false;
543
KillTimer(hwnd, wParam);
544
}
545
break;
546
}
547
548
return (LRESULT)CallWindowProc((WNDPROC)list->oldProc,hwnd,msg,wParam,lParam);
549
}
550
551
void GenericListControl::ProcessCopy()
552
{
553
int start = GetSelectedIndex();
554
int size;
555
if (start == -1)
556
size = GetRowCount();
557
else
558
size = ListView_GetSelectedCount(handle);
559
560
CopyRows(start, size);
561
}
562
563
void GenericListControl::CopyRows(int start, int size)
564
{
565
std::wstring data;
566
567
if (start == 0 && size == GetRowCount())
568
{
569
// Let's also copy the header if everything is selected.
570
for (int c = 0; c < columnCount; ++c)
571
{
572
data.append(columns[c].name);
573
if (c < columnCount - 1)
574
data.append(L"\t");
575
else
576
data.append(L"\r\n");
577
}
578
}
579
580
for (int r = start; r < start + size; ++r)
581
{
582
for (int c = 0; c < columnCount; ++c)
583
{
584
stringBuffer[0] = 0;
585
GetColumnText(stringBuffer, r, c);
586
data.append(stringBuffer);
587
if (c < columnCount - 1)
588
data.append(L"\t");
589
else
590
data.append(L"\r\n");
591
}
592
}
593
W32Util::CopyTextToClipboard(handle, data);
594
}
595
596
void GenericListControl::SelectAll()
597
{
598
ListView_SetItemState(handle, -1, LVIS_SELECTED, LVIS_SELECTED);
599
}
600
601
int GenericListControl::GetSelectedIndex()
602
{
603
return ListView_GetNextItem(handle, -1, LVNI_SELECTED);
604
}
605
606