Skip to content

Commit e6f5065

Browse files
committed
[EVENTVWR] Rewrite the way data is copied into the clipboard
CORE-20023 - Besides copying the event information, copy also its formatted data. - Update translations with new IDS_COPY* strings. - Eliminate all statically-sized temporary buffers, in favour of carefully calculating the size, and allocating an adequately sized buffer to hold the data to be copied. - By default, the "title" and event info on the single-line fields, are separated with TABs (to facilitate data import in spreadsheets). Add a mode where, when the user presses the SHIFT key while clicking on the "Copy" button, the separation is instead done with space padding, to be able to prettify information display when copying into text files instead.
1 parent 85a6976 commit e6f5065

File tree

27 files changed

+557
-308
lines changed

27 files changed

+557
-308
lines changed

base/applications/mscutils/eventvwr/evtdetctl.c

Lines changed: 289 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@
1212

1313
#include <shellapi.h>
1414

15+
/**
16+
* @brief
17+
* ReactOS-only feature:
18+
* Enable or disable support for copying event info text using space padding
19+
* between header titles and data, when pressing the SHIFT key while clicking
20+
* on the "Copy" button, instead of using TABs as separators.
21+
*
22+
* @see CopyEventEntry().
23+
**/
24+
#define COPY_EVTTEXT_SPACE_PADDING_MODE
25+
1526
// FIXME:
1627
#define EVENT_MESSAGE_EVENTTEXT_BUFFER (1024*10)
1728
extern WCHAR szTitle[];
@@ -250,83 +261,291 @@ DisplayEventData(
250261
HeapFree(GetProcessHeap(), 0, pTextBuffer);
251262
}
252263

253-
static
254-
HFONT
255-
CreateMonospaceFont(VOID)
256-
{
257-
LOGFONTW tmpFont = {0};
258-
HFONT hFont;
259-
HDC hDC;
264+
#ifdef COPY_EVTTEXT_SPACE_PADDING_MODE
260265

261-
hDC = GetDC(NULL);
266+
static inline
267+
int my_cType3ToWidth(WORD wType, wchar_t ucs)
268+
{
269+
if (wType & C3_HALFWIDTH)
270+
return 1;
271+
else if (wType & (C3_FULLWIDTH | C3_KATAKANA | C3_HIRAGANA | C3_IDEOGRAPH))
272+
return 2;
273+
/*
274+
* HACK for Wide Hangul characters not recognized by GetStringTypeW(CT_CTYPE3)
275+
* See:
276+
* https://unicode.org/reports/tr11/
277+
* https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt
278+
* https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedEastAsianWidth.txt
279+
* (or the /Public/UNIDATA/ files)
280+
*/
281+
else if ((ucs >= 0x1100 && ucs <= 0x115F) || (ucs >= 0x302E && ucs <= 0x302F) ||
282+
(ucs >= 0x3131 && ucs <= 0x318E) || (ucs >= 0x3260 && ucs <= 0x327F) ||
283+
(ucs >= 0xA960 && ucs <= 0xA97C) || (ucs >= 0xAC00 && ucs <= 0xD7A3))
284+
return 2;
285+
else if (wType & (C3_SYMBOL | C3_KASHIDA | C3_LEXICAL | C3_ALPHA))
286+
return 1;
287+
else // if (wType & (C3_NONSPACING | C3_DIACRITIC | C3_VOWELMARK | C3_HIGHSURROGATE | C3_LOWSURROGATE | C3_NOTAPPLICABLE))
288+
return 0;
289+
}
262290

263-
tmpFont.lfHeight = -MulDiv(8, GetDeviceCaps(hDC, LOGPIXELSY), 72);
264-
tmpFont.lfWeight = FW_NORMAL;
265-
wcscpy(tmpFont.lfFaceName, L"Courier New");
291+
int my_wcwidth(wchar_t ucs)
292+
{
293+
WORD wType = 0;
294+
GetStringTypeW(CT_CTYPE3, &ucs, sizeof(ucs)/sizeof(WCHAR), &wType);
295+
return my_cType3ToWidth(wType, ucs);
296+
}
266297

267-
hFont = CreateFontIndirectW(&tmpFont);
298+
int my_wcswidth(const wchar_t *pwcs, size_t n)
299+
{
300+
int width = 0;
301+
PWORD pwType, pwt;
268302

269-
ReleaseDC(NULL, hDC);
303+
pwType = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, n * sizeof(WORD));
304+
if (!pwType)
305+
return 0;
306+
if (!GetStringTypeW(CT_CTYPE3, pwcs, n, pwType))
307+
goto Quit;
270308

271-
return hFont;
309+
for (pwt = pwType; n-- > 0; ++pwt, ++pwcs)
310+
{
311+
width += my_cType3ToWidth(*pwt, *pwcs);
312+
}
313+
Quit:
314+
HeapFree(GetProcessHeap(), 0, pwType);
315+
return width;
272316
}
273317

318+
#endif // COPY_EVTTEXT_SPACE_PADDING_MODE
319+
320+
/**
321+
* @brief
322+
* Retrieves the already-gathered event information, structure it in
323+
* text format and copy it into the clipboard for user consumption.
324+
*
325+
* The copied event information has the following text format, where
326+
* each text line ends with CR-LF newlines:
327+
* ```
328+
* Event Type: <event_type>\r\n
329+
* Event Source: <event_source>\r\n
330+
* Event Category: <event_cat>\r\n
331+
* Event ID: <event_id>\r\n
332+
* Date: <event_date>\r\n
333+
* Time: <event_time>\r\n
334+
* User: <event_user>\r\n
335+
* Computer: <event_computer>\r\n
336+
* Description:\r\n
337+
* <event_description>\r\n
338+
* Data:\r\n
339+
* <event...
340+
* ...data...
341+
* ...if any>\r\n
342+
* ```
343+
*
344+
* For the single-line fields, the spacing between the header title and
345+
* information is either a TAB (default), to facilitate data import in
346+
* spreadsheet programs, or space-padding (when the user presses the
347+
* SHIFT key while copying the data) to prettify information display.
348+
* (This latter functionality is supported only if the program is compiled
349+
* with the @b COPY_EVTTEXT_SPACE_PADDING_MODE define.)
350+
**/
274351
static
275352
VOID
276-
CopyEventEntry(HWND hWnd)
353+
CopyEventEntry(
354+
_In_ HWND hWnd)
277355
{
278-
WCHAR tmpHeader[512];
279-
WCHAR szEventType[MAX_PATH];
280-
WCHAR szSource[MAX_PATH];
281-
WCHAR szCategory[MAX_PATH];
282-
WCHAR szEventID[MAX_PATH];
283-
WCHAR szDate[MAX_PATH];
284-
WCHAR szTime[MAX_PATH];
285-
WCHAR szUser[MAX_PATH];
286-
WCHAR szComputer[MAX_PATH];
287-
WCHAR evtDesc[EVENT_MESSAGE_EVENTTEXT_BUFFER];
288-
ULONG size = 0;
289-
LPWSTR output;
356+
#ifdef COPY_EVTTEXT_SPACE_PADDING_MODE
357+
static const LONG nTabWidth = 4;
358+
#endif
359+
static const WCHAR szCRLF[] = L"\r\n";
360+
struct
361+
{
362+
WORD uHdrID; // Header string resource ID.
363+
WORD nDlgItemID; // Dialog control ID containing the corresponding info.
364+
WORD bSameLine : 1; // Info follows header on same line (TRUE) or not (FALSE).
365+
WORD bOptional : 1; // Omit if info is empty (TRUE) or keep it (FALSE).
366+
PCWCH pchHdrText; // Pointer to header string resource.
367+
SIZE_T cchHdrLen; // Header string length (number of characters).
368+
SIZE_T cchInfoLen; // Info string length (number of characters).
369+
#ifdef COPY_EVTTEXT_SPACE_PADDING_MODE
370+
UINT nHdrWidth; // Display width of the header string.
371+
UINT nSpacesPad; // Padding after header in number of spaces.
372+
#endif
373+
} CopyData[] =
374+
{
375+
{IDS_COPY_EVTTYPE, IDC_EVENTTYPESTATIC , TRUE , FALSE, NULL, 0, 0},
376+
{IDS_COPY_EVTSRC , IDC_EVENTSOURCESTATIC , TRUE , FALSE, NULL, 0, 0},
377+
{IDS_COPY_EVTCAT , IDC_EVENTCATEGORYSTATIC, TRUE , FALSE, NULL, 0, 0},
378+
{IDS_COPY_EVTID , IDC_EVENTIDSTATIC , TRUE , FALSE, NULL, 0, 0},
379+
{IDS_COPY_EVTDATE, IDC_EVENTDATESTATIC , TRUE , FALSE, NULL, 0, 0},
380+
{IDS_COPY_EVTTIME, IDC_EVENTTIMESTATIC , TRUE , FALSE, NULL, 0, 0},
381+
{IDS_COPY_EVTUSER, IDC_EVENTUSERSTATIC , TRUE , FALSE, NULL, 0, 0},
382+
{IDS_COPY_EVTCOMP, IDC_EVENTCOMPUTERSTATIC, TRUE , FALSE, NULL, 0, 0},
383+
{IDS_COPY_EVTTEXT, IDC_EVENTTEXTEDIT , FALSE, FALSE, NULL, 0, 0},
384+
{IDS_COPY_EVTDATA, IDC_EVENTDATAEDIT , FALSE, TRUE , NULL, 0, 0},
385+
};
386+
USHORT i;
387+
#ifdef COPY_EVTTEXT_SPACE_PADDING_MODE
388+
BOOL bUsePad; // Use space padding (TRUE) or not (FALSE, default).
389+
UINT nMaxHdrWidth = 0;
390+
#endif
391+
SIZE_T size = 0;
392+
PWSTR output;
393+
PWSTR pszDestEnd;
394+
size_t cchRemaining;
290395
HGLOBAL hMem;
291396

292397
/* Try to open the clipboard */
293398
if (!OpenClipboard(hWnd))
294399
return;
295400

296-
/* Get the formatted text needed to place the content into */
297-
size += LoadStringW(hInst, IDS_COPY, tmpHeader, ARRAYSIZE(tmpHeader));
401+
#ifdef COPY_EVTTEXT_SPACE_PADDING_MODE
402+
/* Use space padding only if the user presses SHIFT */
403+
bUsePad = !!(GetKeyState(VK_SHIFT) & 0x8000);
404+
#endif
405+
406+
/*
407+
* Grab all the information and get it ready for the clipboard.
408+
*/
409+
410+
/* Calculate the necessary string buffer size */
411+
for (i = 0; i < _countof(CopyData); ++i)
412+
{
413+
/* Retrieve the event info string length (without NUL terminator) */
414+
CopyData[i].cchInfoLen = GetWindowTextLengthW(GetDlgItem(hWnd, CopyData[i].nDlgItemID));
415+
416+
/* If no data is present and is optional, ignore it */
417+
if ((CopyData[i].cchInfoLen == 0) && CopyData[i].bOptional)
418+
continue;
419+
420+
/* Load the header string from resources */
421+
CopyData[i].cchHdrLen = LoadStringW(hInst, CopyData[i].uHdrID, (PWSTR)&CopyData[i].pchHdrText, 0);
422+
size += CopyData[i].cchHdrLen;
423+
424+
if (CopyData[i].bSameLine)
425+
{
426+
/* The header and info are on the same line */
427+
#ifdef COPY_EVTTEXT_SPACE_PADDING_MODE
428+
if (bUsePad)
429+
{
430+
/* Retrieve the maximum header string displayed
431+
* width for computing space padding later */
432+
CopyData[i].nHdrWidth = my_wcswidth(CopyData[i].pchHdrText, CopyData[i].cchHdrLen);
433+
nMaxHdrWidth = max(nMaxHdrWidth, CopyData[i].nHdrWidth);
434+
}
435+
else
436+
#endif
437+
{
438+
/* Count a TAB separator */
439+
size++;
440+
}
441+
}
442+
else
443+
{
444+
/* The data is on a separate line, count a newline */
445+
size += _countof(szCRLF)-1;
446+
}
447+
448+
/* Count the event info string and the newline that follows it */
449+
size += CopyData[i].cchInfoLen;
450+
size += _countof(szCRLF)-1;
451+
}
452+
#ifdef COPY_EVTTEXT_SPACE_PADDING_MODE
453+
if (bUsePad)
454+
{
455+
/* Round nMaxHdrWidth to the next TAB width, and
456+
* compute the space padding for each field */
457+
UINT nSpaceWidth = 1; // my_wcwidth(L' ');
458+
nMaxHdrWidth = ((nMaxHdrWidth / nTabWidth) + 1) * nTabWidth;
459+
for (i = 0; i < _countof(CopyData); ++i)
460+
{
461+
/* If no data is present and is optional, ignore it */
462+
if ((CopyData[i].cchInfoLen == 0) && CopyData[i].bOptional)
463+
continue;
298464

299-
/* Grab all the information and get it ready for the clipboard */
300-
size += GetDlgItemTextW(hWnd, IDC_EVENTTYPESTATIC, szEventType, ARRAYSIZE(szEventType));
301-
size += GetDlgItemTextW(hWnd, IDC_EVENTSOURCESTATIC, szSource, ARRAYSIZE(szSource));
302-
size += GetDlgItemTextW(hWnd, IDC_EVENTCATEGORYSTATIC, szCategory, ARRAYSIZE(szCategory));
303-
size += GetDlgItemTextW(hWnd, IDC_EVENTIDSTATIC, szEventID, ARRAYSIZE(szEventID));
304-
size += GetDlgItemTextW(hWnd, IDC_EVENTDATESTATIC, szDate, ARRAYSIZE(szDate));
305-
size += GetDlgItemTextW(hWnd, IDC_EVENTTIMESTATIC, szTime, ARRAYSIZE(szTime));
306-
size += GetDlgItemTextW(hWnd, IDC_EVENTUSERSTATIC, szUser, ARRAYSIZE(szUser));
307-
size += GetDlgItemTextW(hWnd, IDC_EVENTCOMPUTERSTATIC, szComputer, ARRAYSIZE(szComputer));
308-
size += GetDlgItemTextW(hWnd, IDC_EVENTTEXTEDIT, evtDesc, ARRAYSIZE(evtDesc));
465+
/* If the data is on a separate line, ignore padding */
466+
if (!CopyData[i].bSameLine)
467+
continue;
309468

310-
size++; /* Null-termination */
311-
size *= sizeof(WCHAR);
469+
/* Compute the padding */
470+
CopyData[i].nSpacesPad = (nMaxHdrWidth - CopyData[i].nHdrWidth) / nSpaceWidth;
471+
size += CopyData[i].nSpacesPad;
472+
}
473+
}
474+
#endif // COPY_EVTTEXT_SPACE_PADDING_MODE
475+
/* Add NUL-termination */
476+
size++;
312477

313478
/*
314-
* Consolidate the information into one big piece and
315-
* sort out the memory needed to write to the clipboard.
479+
* Consolidate the information into a single buffer to copy in the clipboard.
316480
*/
317-
hMem = GlobalAlloc(GMEM_MOVEABLE, size);
318-
if (hMem == NULL) goto Quit;
481+
hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, size * sizeof(WCHAR));
482+
if (!hMem)
483+
goto Quit;
319484

320485
output = GlobalLock(hMem);
321-
if (output == NULL)
486+
if (!output)
322487
{
323488
GlobalFree(hMem);
324489
goto Quit;
325490
}
326491

327-
StringCbPrintfW(output, size,
328-
tmpHeader, szEventType, szSource, szCategory, szEventID,
329-
szDate, szTime, szUser, szComputer, evtDesc);
492+
/* Build the string */
493+
pszDestEnd = output;
494+
cchRemaining = size;
495+
for (i = 0; i < _countof(CopyData); ++i)
496+
{
497+
SIZE_T sizeDataStr;
498+
499+
/* If no data is present and is optional, ignore it */
500+
if ((CopyData[i].cchInfoLen == 0) && CopyData[i].bOptional)
501+
continue;
502+
503+
/* Copy the header string */
504+
StringCchCopyNExW(pszDestEnd, cchRemaining,
505+
CopyData[i].pchHdrText, CopyData[i].cchHdrLen,
506+
&pszDestEnd, &cchRemaining, 0);
507+
508+
if (CopyData[i].bSameLine)
509+
{
510+
/* The header and info are on the same line, add
511+
* either the space padding or the TAB separator */
512+
#ifdef COPY_EVTTEXT_SPACE_PADDING_MODE
513+
if (bUsePad)
514+
{
515+
UINT j = CopyData[i].nSpacesPad;
516+
while (j--)
517+
{
518+
*pszDestEnd++ = L' ';
519+
cchRemaining--;
520+
}
521+
}
522+
else
523+
#endif
524+
{
525+
*pszDestEnd++ = L'\t';
526+
cchRemaining--;
527+
}
528+
}
529+
else
530+
{
531+
/* The data is on a separate line, add a newline */
532+
StringCchCopyExW(pszDestEnd, cchRemaining, szCRLF,
533+
&pszDestEnd, &cchRemaining, 0);
534+
}
535+
536+
/* Copy the event info */
537+
sizeDataStr = min(cchRemaining, CopyData[i].cchInfoLen + 1);
538+
sizeDataStr = GetDlgItemTextW(hWnd, CopyData[i].nDlgItemID, pszDestEnd, sizeDataStr);
539+
pszDestEnd += sizeDataStr;
540+
cchRemaining -= sizeDataStr;
541+
542+
/* A newline follows the data */
543+
StringCchCopyExW(pszDestEnd, cchRemaining, szCRLF,
544+
&pszDestEnd, &cchRemaining, 0);
545+
}
546+
/* NUL-terminate the buffer */
547+
*pszDestEnd++ = UNICODE_NULL;
548+
cchRemaining--;
330549

331550
GlobalUnlock(hMem);
332551

@@ -777,6 +996,27 @@ ClearContents(
777996
SetDlgItemTextW(hDlg, IDC_EVENTDATAEDIT, L"");
778997
}
779998

999+
static
1000+
HFONT
1001+
CreateMonospaceFont(VOID)
1002+
{
1003+
LOGFONTW tmpFont = {0};
1004+
HFONT hFont;
1005+
HDC hDC;
1006+
1007+
hDC = GetDC(NULL);
1008+
1009+
tmpFont.lfHeight = -MulDiv(8, GetDeviceCaps(hDC, LOGPIXELSY), 72);
1010+
tmpFont.lfWeight = FW_NORMAL;
1011+
wcscpy(tmpFont.lfFaceName, L"Courier New");
1012+
1013+
hFont = CreateFontIndirectW(&tmpFont);
1014+
1015+
ReleaseDC(NULL, hDC);
1016+
1017+
return hFont;
1018+
}
1019+
7801020
static
7811021
VOID
7821022
InitDetailsDlgCtrl(HWND hDlg, PDETAILDATA pData)

0 commit comments

Comments
 (0)