1 /*
2  * PROJECT:     ReactOS VGA Font Editor
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Implements the main window of the application
5  * COPYRIGHT:   Copyright 2008 Colin Finck (colin@reactos.org)
6  */
7 
8 #include "precomp.h"
9 
10 static const WCHAR szMainWndClass[] = L"VGAFontEditMainWndClass";
11 
12 static VOID
13 InitResources(IN PMAIN_WND_INFO Info)
14 {
15     HDC hMemDC;
16     HDC hMainDC;
17     HPEN hPen, hPenOld;
18     RECT rect;
19     HBITMAP hBitmapOld;
20 
21     hMemDC = CreateCompatibleDC(NULL);
22     hMainDC = GetDC(Info->hMainWnd);
23 
24     // Create the "Box" bitmap
25     Info->hBoxBmp = CreateCompatibleBitmap(hMainDC, CHARACTER_BOX_WIDTH, CHARACTER_BOX_HEIGHT);
26     hBitmapOld = SelectObject(hMemDC, Info->hBoxBmp);
27 
28     rect.left = 0;
29     rect.top = 0;
30     rect.right = CHARACTER_INFO_BOX_WIDTH;
31     rect.bottom = CHARACTER_INFO_BOX_HEIGHT;
32     FillRect( hMemDC, &rect, (HBRUSH)(COLOR_BTNFACE + 1) );
33 
34     hPenOld = SelectObject( hMemDC, GetStockObject(WHITE_PEN) );
35     Rectangle(hMemDC, 0, 0, CHARACTER_INFO_BOX_WIDTH - 1, 2);
36     Rectangle(hMemDC, 0, 2, 2, CHARACTER_INFO_BOX_HEIGHT - 1);
37     hPen = SelectObject(hMemDC, hPenOld);
38 
39     hPen = CreatePen( PS_SOLID, 1, RGB(128, 128, 128) );
40     hPenOld = SelectObject(hMemDC, hPen);
41     Rectangle(hMemDC, 1, CHARACTER_INFO_BOX_HEIGHT - 2, CHARACTER_INFO_BOX_WIDTH, CHARACTER_INFO_BOX_HEIGHT);
42     Rectangle(hMemDC, CHARACTER_INFO_BOX_WIDTH - 2, 1, CHARACTER_INFO_BOX_WIDTH, CHARACTER_INFO_BOX_HEIGHT - 2);
43 
44     SetPixel( hMemDC, CHARACTER_INFO_BOX_WIDTH - 1, 0, RGB(128, 128, 128) );
45     SetPixel( hMemDC, 0, CHARACTER_INFO_BOX_HEIGHT - 1, RGB(128, 128, 128) );
46     SelectObject(hMemDC, hBitmapOld);
47 
48     hPen = SelectObject(hMemDC, hPenOld);
49     DeleteObject(hPen);
50     DeleteDC(hMemDC);
51     ReleaseDC(Info->hMainWnd, hMainDC);
52 }
53 
54 static VOID
55 UnInitResources(IN PMAIN_WND_INFO Info)
56 {
57     DeleteObject(Info->hBoxBmp);
58 }
59 
60 static VOID
61 AddToolbarButton(IN PMAIN_WND_INFO Info, IN INT iBitmap, IN INT idCommand, IN UINT uID)
62 {
63     PWSTR pszTooltip;
64     TBBUTTON tbb = {0,};
65 
66     if( AllocAndLoadString(&pszTooltip, uID) )
67     {
68         tbb.fsState = TBSTATE_ENABLED;
69         tbb.iBitmap = iBitmap;
70         tbb.idCommand = idCommand;
71         tbb.iString = (INT_PTR)pszTooltip;
72 
73         SendMessageW( Info->hToolbar, TB_ADDBUTTONSW, 1, (LPARAM)&tbb );
74         HeapFree(hProcessHeap, 0, pszTooltip);
75     }
76 }
77 
78 static VOID
79 SetToolbarButtonState(IN PMAIN_WND_INFO Info, INT idCommand, BOOL bEnabled)
80 {
81     TBBUTTONINFOW tbbi = {0,};
82 
83     tbbi.cbSize = sizeof(tbbi);
84     tbbi.dwMask = TBIF_STATE;
85     tbbi.fsState = (bEnabled ? TBSTATE_ENABLED : 0);
86 
87     SendMessageW(Info->hToolbar, TB_SETBUTTONINFOW, idCommand, (LPARAM)&tbbi);
88 }
89 
90 VOID
91 SetToolbarFileButtonState(IN PMAIN_WND_INFO Info, BOOL bEnabled)
92 {
93     SetToolbarButtonState(Info, ID_FILE_SAVE, bEnabled);
94     SetToolbarButtonState(Info, ID_EDIT_GLYPH, bEnabled);
95     SetToolbarButtonState(Info, ID_EDIT_COPY, bEnabled);
96 }
97 
98 static VOID
99 AddToolbarSeparator(IN PMAIN_WND_INFO Info)
100 {
101     TBBUTTON tbb = {0,};
102 
103     tbb.fsStyle = BTNS_SEP;
104 
105     SendMessageW( Info->hToolbar, TB_ADDBUTTONSW, 1, (LPARAM)&tbb );
106 }
107 
108 static VOID
109 InitMainWnd(IN PMAIN_WND_INFO Info)
110 {
111     CLIENTCREATESTRUCT ccs;
112     INT iCustomBitmaps;
113     INT iStandardBitmaps;
114     TBADDBITMAP tbab;
115 
116     // Add the toolbar
117     Info->hToolbar = CreateWindowExW(0,
118                                      TOOLBARCLASSNAMEW,
119                                      NULL,
120                                      WS_VISIBLE | WS_CHILD | TBSTYLE_TOOLTIPS,
121                                      0,
122                                      0,
123                                      0,
124                                      0,
125                                      Info->hMainWnd,
126                                      NULL,
127                                      hInstance,
128                                      NULL);
129 
130     // Identify the used Common Controls version
131     SendMessageW(Info->hToolbar, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
132 
133     // Enable Tooltips
134     SendMessageW(Info->hToolbar, TB_SETMAXTEXTROWS, 0, 0);
135 
136     // Add the toolbar bitmaps
137     tbab.hInst = HINST_COMMCTRL;
138     tbab.nID = IDB_STD_SMALL_COLOR;
139     iStandardBitmaps = (INT)SendMessageW(Info->hToolbar, TB_ADDBITMAP, 0, (LPARAM)&tbab);
140 
141     tbab.hInst = hInstance;
142     tbab.nID = IDB_MAIN_TOOLBAR;
143     iCustomBitmaps = (INT)SendMessageW(Info->hToolbar, TB_ADDBITMAP, 0, (LPARAM)&tbab);
144 
145     // Add the toolbar buttons
146     AddToolbarButton(Info, iStandardBitmaps + STD_FILENEW, ID_FILE_NEW, IDS_TOOLTIP_NEW);
147     AddToolbarButton(Info, iStandardBitmaps + STD_FILEOPEN, ID_FILE_OPEN, IDS_TOOLTIP_OPEN);
148     AddToolbarButton(Info, iStandardBitmaps + STD_FILESAVE, ID_FILE_SAVE, IDS_TOOLTIP_SAVE);
149     AddToolbarSeparator(Info);
150     AddToolbarButton(Info, iCustomBitmaps + TOOLBAR_EDIT_GLYPH, ID_EDIT_GLYPH, IDS_TOOLTIP_EDIT_GLYPH);
151     AddToolbarSeparator(Info);
152     AddToolbarButton(Info, iStandardBitmaps + STD_COPY, ID_EDIT_COPY, IDS_TOOLTIP_COPY);
153     AddToolbarButton(Info, iStandardBitmaps + STD_PASTE, ID_EDIT_PASTE, IDS_TOOLTIP_PASTE);
154 
155     SetToolbarFileButtonState(Info, FALSE);
156     SetPasteButtonState(Info);
157 
158     // Add the MDI client area
159     ccs.hWindowMenu = GetSubMenu(Info->hMenu, 2);
160     ccs.idFirstChild = ID_MDI_FIRSTCHILD;
161 
162     Info->hMdiClient = CreateWindowExW(WS_EX_CLIENTEDGE,
163                                        L"MDICLIENT",
164                                        NULL,
165                                        WS_VISIBLE | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VSCROLL | WS_HSCROLL,
166                                        0,
167                                        0,
168                                        0,
169                                        0,
170                                        Info->hMainWnd,
171                                        NULL,
172                                        hInstance,
173                                        &ccs);
174 
175     // Initialize the file handling
176     FileInitialize(Info->hMainWnd);
177 }
178 
179 static VOID
180 InitMenuPopup(IN PMAIN_WND_INFO Info)
181 {
182     UINT uState;
183 
184     uState = MF_BYCOMMAND | !(Info->CurrentFontWnd);
185 
186     EnableMenuItem(Info->hMenu, ID_FILE_CLOSE, uState);
187     EnableMenuItem(Info->hMenu, ID_FILE_SAVE, uState);
188     EnableMenuItem(Info->hMenu, ID_FILE_SAVE_AS, uState);
189 
190     EnableMenuItem(Info->hMenu, ID_EDIT_COPY, uState);
191     EnableMenuItem(Info->hMenu, ID_EDIT_GLYPH, uState);
192 
193     uState = MF_BYCOMMAND | !(Info->CurrentFontWnd && IsClipboardFormatAvailable(uCharacterClipboardFormat));
194     EnableMenuItem(Info->hMenu, ID_EDIT_PASTE, uState);
195 }
196 
197 static VOID
198 DoFileNew(IN PMAIN_WND_INFO Info)
199 {
200     PFONT_OPEN_INFO OpenInfo;
201 
202     OpenInfo = (PFONT_OPEN_INFO) HeapAlloc( hProcessHeap, HEAP_ZERO_MEMORY, sizeof(FONT_OPEN_INFO) );
203     OpenInfo->bCreateNew = TRUE;
204 
205     CreateFontWindow(Info, OpenInfo);
206 }
207 
208 static VOID
209 DoFileOpen(IN PMAIN_WND_INFO Info)
210 {
211     PFONT_OPEN_INFO OpenInfo;
212 
213     OpenInfo = (PFONT_OPEN_INFO) HeapAlloc( hProcessHeap, HEAP_ZERO_MEMORY, sizeof(FONT_OPEN_INFO) );
214     OpenInfo->pszFileName = HeapAlloc(hProcessHeap, 0, MAX_PATH);
215     OpenInfo->pszFileName[0] = 0;
216 
217     if( DoOpenFile(OpenInfo->pszFileName) )
218     {
219         OpenInfo->bCreateNew = FALSE;
220         CreateFontWindow(Info, OpenInfo);
221     }
222 }
223 
224 VOID
225 DoFileSave(IN PMAIN_WND_INFO Info, IN BOOL bSaveAs)
226 {
227     DWORD dwBytesWritten;
228     HANDLE hFile;
229 
230     // Show the "Save" dialog
231     //   - if "Save As" was clicked
232     //   - if the file was not yet saved
233     //   - if another format than the binary format was opened
234     if(bSaveAs || !Info->CurrentFontWnd->OpenInfo->bBinaryFileOpened)
235     {
236         if(!Info->CurrentFontWnd->OpenInfo->pszFileName)
237         {
238             Info->CurrentFontWnd->OpenInfo->pszFileName = (PWSTR) HeapAlloc(hProcessHeap, 0, MAX_PATH);
239             Info->CurrentFontWnd->OpenInfo->pszFileName[0] = 0;
240         }
241         else if(!Info->CurrentFontWnd->OpenInfo->bBinaryFileOpened)
242         {
243             // For a file in another format, the user has to enter a new file name as well
244             Info->CurrentFontWnd->OpenInfo->pszFileName[0] = 0;
245         }
246 
247         if( !DoSaveFile(Info->CurrentFontWnd->OpenInfo->pszFileName) )
248             return;
249     }
250 
251     // Save the binary font
252     hFile = CreateFileW(Info->CurrentFontWnd->OpenInfo->pszFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
253 
254     if(hFile == INVALID_HANDLE_VALUE)
255     {
256         LocalizedError( IDS_OPENERROR, GetLastError() );
257         return;
258     }
259 
260     if( !WriteFile(hFile, Info->CurrentFontWnd->Font, sizeof(BITMAP_FONT), &dwBytesWritten, NULL) )
261         LocalizedError( IDS_WRITEERROR, GetLastError() );
262 
263     CloseHandle(hFile);
264 }
265 
266 static VOID
267 CopyCurrentGlyph(IN PFONT_WND_INFO FontWndInfo)
268 {
269     HGLOBAL hMem;
270     PUCHAR pCharacterBits;
271 
272     if(!OpenClipboard(NULL))
273         return;
274 
275     EmptyClipboard();
276 
277     hMem = GlobalAlloc(GMEM_MOVEABLE, 8);
278     pCharacterBits = GlobalLock(hMem);
279     RtlCopyMemory(pCharacterBits, FontWndInfo->Font->Bits + FontWndInfo->uSelectedCharacter * 8, 8);
280     GlobalUnlock(hMem);
281 
282     SetClipboardData(uCharacterClipboardFormat, hMem);
283 
284     CloseClipboard();
285 }
286 
287 static VOID
288 PasteIntoCurrentGlyph(IN PFONT_WND_INFO FontWndInfo)
289 {
290     HGLOBAL hMem;
291 
292     if(!IsClipboardFormatAvailable(uCharacterClipboardFormat))
293         return;
294 
295     if(!OpenClipboard(NULL))
296         return;
297 
298     hMem = GetClipboardData(uCharacterClipboardFormat);
299     if(hMem)
300     {
301         PUCHAR pCharacterBits;
302 
303         pCharacterBits = GlobalLock(hMem);
304         if(pCharacterBits)
305         {
306             RECT CharacterRect;
307             UINT uFontRow;
308             UINT uFontColumn;
309 
310             RtlCopyMemory(FontWndInfo->Font->Bits + FontWndInfo->uSelectedCharacter * 8, pCharacterBits, 8);
311             GlobalUnlock(hMem);
312 
313             FontWndInfo->OpenInfo->bModified = TRUE;
314 
315             GetCharacterPosition(FontWndInfo->uSelectedCharacter, &uFontRow, &uFontColumn);
316             GetCharacterRect(uFontRow, uFontColumn, &CharacterRect);
317             InvalidateRect(FontWndInfo->hFontBoxesWnd, &CharacterRect, FALSE);
318         }
319     }
320 
321     CloseClipboard();
322 }
323 
324 VOID
325 SetPasteButtonState(IN PMAIN_WND_INFO Info)
326 {
327     SetToolbarButtonState(Info,
328                           ID_EDIT_PASTE,
329                           (Info->CurrentFontWnd && IsClipboardFormatAvailable(uCharacterClipboardFormat)));
330 }
331 
332 static BOOL
333 MenuCommand(IN INT nMenuItemID, IN PMAIN_WND_INFO Info)
334 {
335     switch(nMenuItemID)
336     {
337         // File Menu
338         case ID_FILE_NEW:
339             DoFileNew(Info);
340             return TRUE;
341 
342         case ID_FILE_OPEN:
343             DoFileOpen(Info);
344             return TRUE;
345 
346         case ID_FILE_CLOSE:
347             SendMessageW(Info->CurrentFontWnd->hSelf, WM_CLOSE, 0, 0);
348             return TRUE;
349 
350         case ID_FILE_SAVE:
351             DoFileSave(Info, FALSE);
352             return TRUE;
353 
354         case ID_FILE_SAVE_AS:
355             DoFileSave(Info, TRUE);
356             return TRUE;
357 
358         case ID_FILE_EXIT:
359             PostMessage(Info->hMainWnd, WM_CLOSE, 0, 0);
360             return TRUE;
361 
362         // Edit Menu
363         case ID_EDIT_GLYPH:
364             EditCurrentGlyph(Info->CurrentFontWnd);
365             return TRUE;
366 
367         case ID_EDIT_COPY:
368             CopyCurrentGlyph(Info->CurrentFontWnd);
369             return TRUE;
370 
371         case ID_EDIT_PASTE:
372             PasteIntoCurrentGlyph(Info->CurrentFontWnd);
373             return TRUE;
374 
375         // Window Menu
376         case ID_WINDOW_TILE_HORZ:
377             SendMessageW(Info->hMdiClient, WM_MDITILE, MDITILE_HORIZONTAL, 0);
378             return TRUE;
379 
380         case ID_WINDOW_TILE_VERT:
381             SendMessageW(Info->hMdiClient, WM_MDITILE, MDITILE_VERTICAL, 0);
382             return TRUE;
383 
384         case ID_WINDOW_CASCADE:
385             SendMessageW(Info->hMdiClient, WM_MDICASCADE, 0, 0);
386             return TRUE;
387 
388         case ID_WINDOW_ARRANGE:
389             SendMessageW(Info->hMdiClient, WM_MDIICONARRANGE, 0, 0);
390             return TRUE;
391 
392         case ID_WINDOW_NEXT:
393             SendMessageW(Info->hMdiClient, WM_MDINEXT, 0, 0);
394             return TRUE;
395 
396         // Help Menu
397         case ID_HELP_ABOUT:
398             DialogBoxW( hInstance, MAKEINTRESOURCEW(IDD_ABOUT), Info->hMainWnd, AboutDlgProc );
399             return TRUE;
400     }
401 
402     return FALSE;
403 }
404 
405 static VOID
406 MainWndSize(PMAIN_WND_INFO Info, INT cx, INT cy)
407 {
408     HDWP dwp;
409     INT iMdiTop;
410     RECT ToolbarRect;
411 
412     iMdiTop = 0;
413 
414     dwp = BeginDeferWindowPos(2);
415     if(!dwp)
416         return;
417 
418     if(Info->hToolbar)
419     {
420         GetWindowRect(Info->hToolbar, &ToolbarRect);
421         iMdiTop += ToolbarRect.bottom - ToolbarRect.top;
422 
423         dwp = DeferWindowPos(dwp, Info->hToolbar, NULL, 0, 0, cx, ToolbarRect.bottom - ToolbarRect.top, SWP_NOZORDER);
424         if(!dwp)
425             return;
426     }
427 
428     if(Info->hMdiClient)
429     {
430         dwp = DeferWindowPos(dwp, Info->hMdiClient, NULL, 0, iMdiTop, cx, cy - iMdiTop, SWP_NOZORDER);
431         if(!dwp)
432             return;
433     }
434 
435     EndDeferWindowPos(dwp);
436 }
437 
438 static LRESULT CALLBACK
439 MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
440 {
441     static HWND hNextClipboardViewer;
442 
443     PMAIN_WND_INFO Info;
444 
445     Info = (PMAIN_WND_INFO) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
446 
447     if(Info || uMsg == WM_CREATE)
448     {
449         switch(uMsg)
450         {
451             case WM_COMMAND:
452                 if( MenuCommand( LOWORD(wParam), Info ) )
453                     return 0;
454 
455                 break;
456 
457             case WM_CHANGECBCHAIN:
458                 if((HWND)wParam == hNextClipboardViewer)
459                     hNextClipboardViewer = (HWND)lParam;
460                 else
461                     SendMessage(hNextClipboardViewer, uMsg, wParam, lParam);
462 
463                 return 0;
464 
465             case WM_CLOSE:
466                 if(Info->FirstFontWnd)
467                 {
468                     // Send WM_CLOSE to all subwindows, so they can prompt for saving unsaved files
469                     PFONT_WND_INFO pNextWnd;
470                     PFONT_WND_INFO pWnd;
471 
472                     pWnd = Info->FirstFontWnd;
473 
474                     do
475                     {
476                         // The pWnd structure might already be destroyed after the WM_CLOSE, so we have to preserve the address of the next window here
477                         pNextWnd = pWnd->NextFontWnd;
478 
479                         // Send WM_USER_APPCLOSE, so we can check for a custom return value
480                         // In this case, we check if the user clicked the "Cancel" button in one of the prompts and if so, we don't close the app
481                         if( !SendMessage(pWnd->hSelf, WM_USER_APPCLOSE, 0, 0) )
482                             return 0;
483                     }
484                     while( (pWnd = pNextWnd) );
485                 }
486                 break;
487 
488             case WM_CREATE:
489                 Info = (PMAIN_WND_INFO)( ( (LPCREATESTRUCT)lParam )->lpCreateParams );
490                 Info->hMainWnd = hwnd;
491                 Info->hMenu = GetMenu(hwnd);
492                 SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)Info);
493 
494                 hNextClipboardViewer = SetClipboardViewer(hwnd);
495 
496                 InitMainWnd(Info);
497                 InitResources(Info);
498 
499                 ShowWindow(hwnd, Info->nCmdShow);
500                 return 0;
501 
502             case WM_DESTROY:
503                 UnInitResources(Info);
504 
505                 HeapFree(hProcessHeap, 0, Info);
506                 SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
507                 PostQuitMessage(0);
508                 return 0;
509 
510             case WM_DRAWCLIPBOARD:
511                 SetPasteButtonState(Info);
512 
513                 // Pass the message to the next clipboard window in the chain
514                 SendMessage(hNextClipboardViewer, uMsg, wParam, lParam);
515                 return 0;
516 
517             case WM_INITMENUPOPUP:
518                 InitMenuPopup(Info);
519                 break;
520 
521             case WM_SIZE:
522                 MainWndSize( Info, LOWORD(lParam), HIWORD(lParam) );
523                 return 0;
524         }
525     }
526 
527     if(Info && Info->hMdiClient)
528         return DefFrameProcW(hwnd, Info->hMdiClient, uMsg, wParam, lParam);
529     else
530         return DefWindowProcW(hwnd, uMsg, wParam, lParam);
531 }
532 
533 BOOL
534 CreateMainWindow(IN INT nCmdShow, OUT PMAIN_WND_INFO* Info)
535 {
536     HWND hMainWnd;
537 
538     *Info = (PMAIN_WND_INFO) HeapAlloc( hProcessHeap, HEAP_ZERO_MEMORY, sizeof(MAIN_WND_INFO) );
539 
540     if(*Info)
541     {
542         (*Info)->nCmdShow = nCmdShow;
543 
544         hMainWnd = CreateWindowExW(0,
545                                    szMainWndClass,
546                                    szAppName,
547                                    WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
548                                    CW_USEDEFAULT,
549                                    CW_USEDEFAULT,
550                                    CW_USEDEFAULT,
551                                    CW_USEDEFAULT,
552                                    NULL,
553                                    LoadMenuW(hInstance, MAKEINTRESOURCEW(IDM_MAINMENU)),
554                                    hInstance,
555                                    *Info);
556 
557         if(hMainWnd)
558             return TRUE;
559         else
560             HeapFree(hProcessHeap, 0, *Info);
561     }
562 
563     return FALSE;
564 }
565 
566 BOOL
567 InitMainWndClass(VOID)
568 {
569     WNDCLASSW wc = {0,};
570 
571     wc.lpfnWndProc    = MainWndProc;
572     wc.hInstance      = hInstance;
573     wc.hCursor        = LoadCursor( NULL, IDC_ARROW );
574     wc.hIcon          = LoadIconW( hInstance, MAKEINTRESOURCEW(IDI_MAIN) );
575     wc.hbrBackground  = (HBRUSH)( COLOR_BTNFACE + 1 );
576     wc.lpszClassName  = szMainWndClass;
577 
578     return RegisterClassW(&wc) != 0;
579 }
580 
581 VOID
582 UnInitMainWndClass(VOID)
583 {
584     UnregisterClassW(szMainWndClass, hInstance);
585 }
586