xref: /reactos/base/applications/charmap/map.c (revision 98e8827a)
1 /*
2  * PROJECT:     ReactOS Character Map
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * FILE:        base/applications/charmap/map.c
5  * PURPOSE:     class implementation for painting glyph region
6  * COPYRIGHT:   Copyright 2007 Ged Murphy <gedmurphy@reactos.org>
7  *
8  */
9 
10 #include "precomp.h"
11 
12 #include <stdlib.h>
13 #include <winnls.h>
14 
15 static const WCHAR szMapWndClass[] = L"FontMapWnd";
16 static const WCHAR szLrgCellWndClass[] = L"LrgCellWnd";
17 
18 #define MAX_ROWS (0xFFFF / XCELLS) + 1 - YCELLS
19 
20 
21 static
22 VOID
23 SetGrid(PMAP infoPtr)
24 {
25     INT x, y;
26 
27     for (y = 0; y < YCELLS; y++)
28     for (x = 0; x < XCELLS; x++)
29     {
30         infoPtr->Cells[y][x].CellExt.left = x * infoPtr->CellSize.cx + 1;
31         infoPtr->Cells[y][x].CellExt.top = y * infoPtr->CellSize.cy + 1;
32         infoPtr->Cells[y][x].CellExt.right = (x + 1) * infoPtr->CellSize.cx + 2;
33         infoPtr->Cells[y][x].CellExt.bottom = (y + 1) * infoPtr->CellSize.cy + 2;
34 
35         CopyRect(&infoPtr->Cells[y][x].CellInt,
36                  &infoPtr->Cells[y][x].CellExt);
37 
38         InflateRect(&infoPtr->Cells[y][x].CellInt,
39                     -1,
40                     -1);
41     }
42 }
43 
44 
45 static
46 VOID
47 DrawActiveCell(PMAP infoPtr,
48                HDC hdc)
49 {
50     Rectangle(hdc,
51               infoPtr->pActiveCell->CellInt.left,
52               infoPtr->pActiveCell->CellInt.top,
53               infoPtr->pActiveCell->CellInt.right,
54               infoPtr->pActiveCell->CellInt.bottom);
55 
56 }
57 
58 
59 static
60 VOID
61 DrawGrid(PMAP infoPtr,
62          PAINTSTRUCT *ps)
63 {
64     INT x, y;
65     RECT rc;
66     PCELL Cell;
67 
68     for (y = 0; y < YCELLS; y++)
69     for (x = 0; x < XCELLS; x++)
70     {
71         Cell = &infoPtr->Cells[y][x];
72 
73         if (!IntersectRect(&rc,
74                            &ps->rcPaint,
75                            &Cell->CellExt))
76         {
77             continue;
78         }
79 
80         Rectangle(ps->hdc,
81                   Cell->CellExt.left,
82                   Cell->CellExt.top,
83                   Cell->CellExt.right,
84                   Cell->CellExt.bottom);
85 
86         if (infoPtr->pActiveCell == Cell)
87         {
88             DrawActiveCell(infoPtr, ps->hdc);
89         }
90     }
91 }
92 
93 
94 static
95 VOID
96 FillGrid(PMAP infoPtr,
97          PAINTSTRUCT *ps)
98 {
99     HFONT hOldFont;
100     WCHAR ch;
101     INT x, y;
102     RECT rc;
103     PCELL Cell;
104     INT i, added;
105 
106     hOldFont = SelectObject(ps->hdc,
107                             infoPtr->hFont);
108 
109     i = XCELLS * infoPtr->iYStart;
110 
111     added = 0;
112 
113     for (y = 0; y < YCELLS; y++)
114     for (x = 0; x < XCELLS; x++)
115     {
116         if (i >= infoPtr->NumValidGlyphs) break;
117 
118         ch = (WCHAR)infoPtr->ValidGlyphs[i];
119 
120         Cell = &infoPtr->Cells[y][x];
121 
122         if (IntersectRect(&rc,
123                             &ps->rcPaint,
124                             &Cell->CellExt))
125         {
126             Cell->ch = ch;
127 
128             DrawTextW(ps->hdc,
129                         &ch,
130                         1,
131                         &Cell->CellInt,
132                         DT_CENTER | DT_VCENTER | DT_SINGLELINE);
133 
134             added++;
135         }
136 
137         i++;
138         ch = (WCHAR)i;
139     }
140     SelectObject(ps->hdc,
141                  hOldFont);
142 }
143 
144 
145 static
146 BOOL
147 CreateLargeCell(PMAP infoPtr)
148 {
149     RECT rLarge;
150 
151     CopyRect(&rLarge,
152              &infoPtr->pActiveCell->CellExt);
153 
154     MapWindowPoints(infoPtr->hMapWnd,
155                     infoPtr->hParent,
156                     (VOID*)&rLarge,
157                     2);
158 
159     InflateRect(&rLarge,
160                 XLARGE - XCELLS,
161                 YLARGE - YCELLS);
162 
163     infoPtr->hLrgWnd = CreateWindowExW(0,
164                                        szLrgCellWndClass,
165                                        NULL,
166                                        WS_CHILDWINDOW | WS_VISIBLE,
167                                        rLarge.left,
168                                        rLarge.top,
169                                        rLarge.right - rLarge.left,
170                                        rLarge.bottom - rLarge.top,
171                                        infoPtr->hParent,
172                                        NULL,
173                                        hInstance,
174                                        infoPtr);
175     if (!infoPtr->hLrgWnd)
176         return FALSE;
177 
178     return TRUE;
179 }
180 
181 
182 static
183 VOID
184 MoveLargeCell(PMAP infoPtr)
185 {
186     RECT rLarge;
187 
188     CopyRect(&rLarge,
189              &infoPtr->pActiveCell->CellExt);
190 
191     MapWindowPoints(infoPtr->hMapWnd,
192                     infoPtr->hParent,
193                     (VOID*)&rLarge,
194                     2);
195 
196     InflateRect(&rLarge,
197                 XLARGE - XCELLS,
198                 YLARGE - YCELLS);
199 
200     MoveWindow(infoPtr->hLrgWnd,
201                rLarge.left,
202                rLarge.top,
203                rLarge.right - rLarge.left,
204                rLarge.bottom - rLarge.top,
205                TRUE);
206 
207     InvalidateRect(infoPtr->hLrgWnd,
208                    NULL,
209                    TRUE);
210 }
211 
212 
213 static
214 VOID
215 GetPossibleCharacters(WCHAR* ch, INT chLen, INT codePageIdx)
216 {
217     INT i, j;
218 
219     memset(ch, 0, sizeof(ch[0]) * chLen);
220 
221     if (codePageIdx <= 0 || codePageIdx > SIZEOF(codePages))
222     {
223         /* this is unicode, so just load up the first MAX_GLYPHS characters
224            start at 0x21 to bypass whitespace characters */
225         INT len = min(MAX_GLYPHS, chLen);
226         for (i = 0x21, j = 0; i < len; i++)
227             ch[j++] = (WCHAR)i;
228     }
229     else
230     {
231         /* This is a codepage, so use NLS to translate the first 256 characters */
232         CHAR multiByteString[256] = { 0 };
233         for (i = 0x21; i < SIZEOF(multiByteString); i++)
234             multiByteString[i] = (CHAR)i;
235 
236         if (!MultiByteToWideChar(codePages[codePageIdx - 1], 0, multiByteString, sizeof(multiByteString), ch, chLen))
237         {
238             /* Failed for some reason, so clear the array */
239             memset(ch, 0, sizeof(ch[0]) * chLen);
240         }
241     }
242 }
243 
244 
245 static
246 VOID
247 SetFont(PMAP infoPtr,
248         LPWSTR lpFontName)
249 {
250     HDC hdc;
251     WCHAR ch[MAX_GLYPHS];
252     WORD out[MAX_GLYPHS];
253     DWORD i, j;
254 
255     /* Destroy Zoom window, since it was created with older font */
256     DestroyWindow(infoPtr->hLrgWnd);
257     infoPtr->hLrgWnd = NULL;
258 
259     if (infoPtr->hFont)
260         DeleteObject(infoPtr->hFont);
261 
262     ZeroMemory(&infoPtr->CurrentFont,
263                sizeof(LOGFONTW));
264 
265     hdc = GetDC(infoPtr->hMapWnd);
266     infoPtr->CurrentFont.lfHeight = GetDeviceCaps(hdc, LOGPIXELSY) / 5;
267 
268     infoPtr->CurrentFont.lfCharSet =  DEFAULT_CHARSET;
269     lstrcpynW(infoPtr->CurrentFont.lfFaceName,
270               lpFontName,
271               SIZEOF(infoPtr->CurrentFont.lfFaceName));
272 
273     infoPtr->hFont = CreateFontIndirectW(&infoPtr->CurrentFont);
274 
275     InvalidateRect(infoPtr->hMapWnd,
276                    NULL,
277                    TRUE);
278 
279     if (infoPtr->pActiveCell)
280         infoPtr->pActiveCell->bActive = FALSE;
281     infoPtr->pActiveCell = &infoPtr->Cells[0][0];
282     infoPtr->pActiveCell->bActive = TRUE;
283 
284     // Get all the valid glyphs in this font
285 
286     SelectObject(hdc, infoPtr->hFont);
287 
288     // Get the code page associated with the selected 'character set'
289     GetPossibleCharacters(ch, MAX_GLYPHS, infoPtr->CharMap);
290 
291     if (GetGlyphIndicesW(hdc,
292                          ch,
293                          MAX_GLYPHS,
294                          out,
295                          GGI_MARK_NONEXISTING_GLYPHS) != GDI_ERROR)
296     {
297         j = 0;
298         for (i = 0; i < MAX_GLYPHS; i++)
299         {
300             if (out[i] != 0xffff && out[i] != 0x0000 && ch[i] != 0x0000)
301             {
302                 infoPtr->ValidGlyphs[j] = ch[i];
303                 j++;
304             }
305         }
306         infoPtr->NumValidGlyphs = j;
307     }
308 
309     ReleaseDC(infoPtr->hMapWnd, hdc);
310 
311     infoPtr->NumRows = infoPtr->NumValidGlyphs / XCELLS;
312     if (infoPtr->NumValidGlyphs % XCELLS)
313         infoPtr->NumRows += 1;
314     infoPtr->NumRows = (infoPtr->NumRows > YCELLS) ? infoPtr->NumRows - YCELLS : 0;
315 
316     SetScrollRange(infoPtr->hMapWnd, SB_VERT, 0, infoPtr->NumRows, FALSE);
317     SetScrollPos(infoPtr->hMapWnd, SB_VERT, 0, TRUE);
318     infoPtr->iYStart = 0;
319 }
320 
321 
322 static
323 LRESULT
324 NotifyParentOfSelection(PMAP infoPtr,
325                         UINT code,
326                         WCHAR ch)
327 {
328     LRESULT Ret = 0;
329 
330     if (infoPtr->hParent != NULL)
331     {
332         DWORD dwIdc = GetWindowLongPtr(infoPtr->hMapWnd, GWLP_ID);
333         /*
334          * Push directly into the event queue instead of waiting
335          * the parent to be unlocked.
336          * High word of LPARAM is still available for future needs...
337          */
338         Ret = PostMessage(infoPtr->hParent,
339                           WM_COMMAND,
340                           MAKELPARAM((WORD)dwIdc, (WORD)code),
341                           (LPARAM)LOWORD(ch));
342     }
343 
344     return Ret;
345 }
346 
347 
348 static
349 VOID
350 OnClick(PMAP infoPtr,
351         WORD ptx,
352         WORD pty)
353 {
354     INT x, y, i;
355 
356     /*
357      * Find the cell the mouse pointer is over.
358      * Since each cell is the same size, this can be done quickly using CellSize.
359      * Clamp to XCELLS - 1 and YCELLS - 1 because the map can sometimes be slightly
360      * larger than infoPtr.CellSize * XCELLS , due to the map size being a non integer
361      * multiple of infoPtr.CellSize .
362      */
363     x = min(XCELLS - 1, ptx / max(1, infoPtr->CellSize.cx));
364     y = min(YCELLS - 1, pty / max(1, infoPtr->CellSize.cy));
365 
366     /* Make sure the mouse is within a valid glyph */
367     i = XCELLS * infoPtr->iYStart + y * XCELLS + x;
368     if (i >= infoPtr->NumValidGlyphs)
369     {
370         if (infoPtr->pActiveCell)
371             infoPtr->pActiveCell->bActive = FALSE;
372         infoPtr->pActiveCell = NULL;
373         return;
374     }
375 
376     /* if the cell is not already active */
377     if (!infoPtr->Cells[y][x].bActive)
378     {
379         /* set previous active cell to inactive */
380         if (infoPtr->pActiveCell)
381         {
382             /* invalidate normal cells, required when
383              * moving a small active cell via keyboard */
384             if (!infoPtr->pActiveCell->bLarge)
385             {
386                 InvalidateRect(infoPtr->hMapWnd,
387                                &infoPtr->pActiveCell->CellInt,
388                                TRUE);
389             }
390 
391             infoPtr->pActiveCell->bActive = FALSE;
392             infoPtr->pActiveCell->bLarge = FALSE;
393         }
394 
395         /* set new cell to active */
396         infoPtr->pActiveCell = &infoPtr->Cells[y][x];
397         infoPtr->pActiveCell->bActive = TRUE;
398         infoPtr->pActiveCell->bLarge = TRUE;
399         if (infoPtr->hLrgWnd)
400             MoveLargeCell(infoPtr);
401         else
402             CreateLargeCell(infoPtr);
403     }
404 }
405 
406 
407 static
408 BOOL
409 MapOnCreate(PMAP infoPtr,
410             HWND hwnd,
411             HWND hParent)
412 {
413     RECT rc;
414     BOOL Ret = FALSE;
415 
416     infoPtr = HeapAlloc(GetProcessHeap(),
417                         0,
418                         sizeof(MAP));
419     if (infoPtr)
420     {
421         SetLastError(0);
422         SetWindowLongPtrW(hwnd,
423                           0,
424                           (DWORD_PTR)infoPtr);
425         if (GetLastError() == 0)
426         {
427             ZeroMemory(infoPtr,
428                        sizeof(MAP));
429 
430             infoPtr->hMapWnd = hwnd;
431             infoPtr->hParent = hParent;
432 
433             GetClientRect(hwnd, &rc);
434             infoPtr->ClientSize.cx = rc.right;
435             infoPtr->ClientSize.cy = rc.bottom;
436             infoPtr->CellSize.cx = infoPtr->ClientSize.cx / XCELLS;
437             infoPtr->CellSize.cy = infoPtr->ClientSize.cy / YCELLS;
438 
439             infoPtr->pActiveCell = NULL;
440 
441             SetGrid(infoPtr);
442 
443             SetScrollPos(infoPtr->hParent, SB_VERT, 0, TRUE);
444 
445             Ret = TRUE;
446         }
447     }
448 
449     return Ret;
450 }
451 
452 
453 static
454 VOID
455 OnVScroll(PMAP infoPtr,
456           INT Value,
457           INT Pos)
458 {
459     INT iYDiff, iOldYStart = infoPtr->iYStart;
460 
461     switch (Value)
462     {
463         case SB_LINEUP:
464             infoPtr->iYStart -=  1;
465             break;
466 
467         case SB_LINEDOWN:
468             infoPtr->iYStart +=  1;
469             break;
470 
471         case SB_PAGEUP:
472             infoPtr->iYStart -= YCELLS;
473             break;
474 
475         case SB_PAGEDOWN:
476             infoPtr->iYStart += YCELLS;
477             break;
478 
479         case SB_THUMBTRACK:
480             infoPtr->iYStart = Pos;
481             break;
482 
483        default:
484             break;
485        }
486 
487     infoPtr->iYStart = max(0, infoPtr->iYStart);
488     infoPtr->iYStart = min(infoPtr->iYStart, infoPtr->NumRows);
489 
490     iYDiff = iOldYStart - infoPtr->iYStart;
491     if (iYDiff)
492     {
493         if (infoPtr->hLrgWnd != NULL)
494         {
495             ShowWindow(infoPtr->hLrgWnd, SW_HIDE);
496         }
497 
498         SetScrollPos(infoPtr->hMapWnd,
499                      SB_VERT,
500                      infoPtr->iYStart,
501                      TRUE);
502 
503         if (abs(iYDiff) < YCELLS)
504         {
505             RECT rect;
506 
507             /* Invalidate the rect around the active cell since a new cell will become active */
508             if (infoPtr->pActiveCell && infoPtr->pActiveCell->bActive)
509             {
510                 InvalidateRect(infoPtr->hMapWnd,
511                                &infoPtr->pActiveCell->CellExt,
512                                TRUE);
513             }
514 
515             GetClientRect(infoPtr->hMapWnd, &rect);
516             rect.top += 2;
517             rect.bottom -= 2;
518             ScrollWindowEx(infoPtr->hMapWnd,
519                            0,
520                            iYDiff * infoPtr->CellSize.cy,
521                            &rect,
522                            &rect,
523                            NULL,
524                            NULL,
525                            SW_INVALIDATE);
526         }
527         else
528         {
529             InvalidateRect(infoPtr->hMapWnd,
530                            NULL,
531                            TRUE);
532         }
533 
534         if (infoPtr->hLrgWnd != NULL)
535         {
536             ShowWindow(infoPtr->hLrgWnd, SW_SHOW);
537         }
538     }
539 }
540 
541 
542 static
543 VOID
544 OnPaint(PMAP infoPtr,
545         WPARAM wParam)
546 {
547     PAINTSTRUCT ps;
548     HDC hdc;
549 
550 
551     if (wParam != 0)
552     {
553         if (!GetUpdateRect(infoPtr->hMapWnd,
554                            &ps.rcPaint,
555                            TRUE))
556         {
557             return;
558         }
559         ps.hdc = (HDC)wParam;
560     }
561     else
562     {
563         hdc = BeginPaint(infoPtr->hMapWnd,
564                          &ps);
565         if (hdc == NULL)
566         {
567             return;
568         }
569     }
570 
571     DrawGrid(infoPtr, &ps);
572 
573     FillGrid(infoPtr, &ps);
574 
575     if (wParam == 0)
576     {
577         EndPaint(infoPtr->hMapWnd,
578                  &ps);
579     }
580 }
581 
582 
583 LRESULT
584 CALLBACK
585 MapWndProc(HWND hwnd,
586            UINT uMsg,
587            WPARAM wParam,
588            LPARAM lParam)
589 {
590     PMAP infoPtr;
591     LRESULT Ret = 0;
592     WCHAR lfFaceName[LF_FACESIZE];
593 
594     infoPtr = (PMAP)GetWindowLongPtrW(hwnd,
595                                       0);
596 
597     switch (uMsg)
598     {
599         case WM_CREATE:
600         {
601             if (!MapOnCreate(infoPtr,
602                              hwnd,
603                              ((LPCREATESTRUCTW)lParam)->hwndParent))
604             {
605                 return (LRESULT)-1;
606             }
607 
608             break;
609         }
610 
611         case WM_LBUTTONDOWN:
612         {
613             OnClick(infoPtr,
614                     LOWORD(lParam),
615                     HIWORD(lParam));
616 
617             break;
618         }
619 
620         case WM_MOUSEMOVE:
621         {
622             if (wParam & MK_LBUTTON)
623             {
624                 OnClick(infoPtr,
625                         LOWORD(lParam),
626                         HIWORD(lParam));
627             }
628             break;
629         }
630 
631         case WM_LBUTTONDBLCLK:
632         {
633             if (!infoPtr->pActiveCell)
634                 break;
635 
636             NotifyParentOfSelection(infoPtr,
637                                     FM_SETCHAR,
638                                     infoPtr->pActiveCell->ch);
639 
640             if (infoPtr->pActiveCell->bLarge)
641             {
642                 DestroyWindow(infoPtr->hLrgWnd);
643                 infoPtr->hLrgWnd = NULL;
644             }
645 
646             infoPtr->pActiveCell->bLarge = FALSE;
647 
648             break;
649         }
650 
651         case WM_VSCROLL:
652         {
653             OnVScroll(infoPtr,
654                       LOWORD(wParam),
655                       HIWORD(wParam));
656 
657             break;
658         }
659 
660         case FM_SETCHARMAP:
661             infoPtr->CharMap = LOWORD(wParam);
662             wcsncpy(lfFaceName,
663                     infoPtr->CurrentFont.lfFaceName,
664                     SIZEOF(lfFaceName));
665             SetFont(infoPtr, lfFaceName);
666             break;
667 
668         case FM_SETFONT:
669             SetFont(infoPtr, (LPWSTR)lParam);
670             break;
671 
672         case FM_GETCHAR:
673         {
674             if (!infoPtr->pActiveCell) return 0;
675             return infoPtr->pActiveCell->ch;
676         }
677 
678         case FM_GETHFONT:
679             return (LRESULT)infoPtr->hFont;
680 
681         case WM_PAINT:
682         {
683             OnPaint(infoPtr,
684                     wParam);
685             break;
686         }
687 
688         case WM_DESTROY:
689         {
690             DeleteObject(infoPtr->hFont);
691             HeapFree(GetProcessHeap(),
692                      0,
693                      infoPtr);
694             SetWindowLongPtrW(hwnd,
695                               0,
696                               (DWORD_PTR)NULL);
697             break;
698         }
699 
700         default:
701         {
702             Ret = DefWindowProcW(hwnd,
703                                  uMsg,
704                                  wParam,
705                                  lParam);
706             break;
707         }
708     }
709 
710     return Ret;
711 }
712 
713 
714 BOOL
715 RegisterMapClasses(HINSTANCE hInstance)
716 {
717     WNDCLASSW wc = {0};
718 
719     wc.style = CS_DBLCLKS;
720     wc.lpfnWndProc = MapWndProc;
721     wc.cbWndExtra = sizeof(PMAP);
722     wc.hInstance = hInstance;
723     wc.hCursor = LoadCursorW(NULL,
724                             (LPWSTR)IDC_ARROW);
725     wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
726     wc.lpszClassName = szMapWndClass;
727 
728     if (RegisterClassW(&wc))
729     {
730         wc.lpfnWndProc = LrgCellWndProc;
731         wc.cbWndExtra = 0;
732         wc.lpszClassName = szLrgCellWndClass;
733 
734         return RegisterClassW(&wc) != 0;
735     }
736 
737     return FALSE;
738 }
739 
740 VOID
741 UnregisterMapClasses(HINSTANCE hInstance)
742 {
743     UnregisterClassW(szMapWndClass,
744                     hInstance);
745 
746     UnregisterClassW(szLrgCellWndClass,
747                     hInstance);
748 }
749