// File: WindowThread.cpp // Written by: // Grant Macklem (Grant.Macklem@colorado.edu) // Gregory Schmelter (Gregory.Schmelter@colorado.edu) // Alan Schmidt (Alan.Schmidt@colorado.edu) // Ivan Stashak (Ivan.Stashak@colorado.edu) // CSCI 4830/7818: API Programming // University of Colorado at Boulder, Spring 2003 // http://www.cs.colorado.edu/~main/bgi // #include // Provides the Win32 API #include // Provides message cracker macros (p. 96) #include "winbgi.h" // External API routines #include "winbgitypes.h" // Internal structures and routines #include // Used in BGI__WindowTable #include // Provides queue // This structure is how the user interacts with the window. Upon creation,y // the user gets the index into this array of the window he created. We will // use this array for any function which does not deal with the window // directly, but relies on the current window. // MGM: hInstance is no longer a handle to the DLL, but instead it is // a "self" handle, returned by GetCurrentThread( ). std::vector BGI__WindowTable; int BGI__WindowCount = 0; // Number of windows currently in use int BGI__CurrentWindow = NO_CURRENT_WINDOW; // Index to current window HINSTANCE BGI__hInstance; // Handle to the instance of the DLL (creating the window class) #include using namespace std; // ID numbers for new options that are added to the system menu: #define BGI_PRINT_SMALL 1 #define BGI_PRINT_MEDIUM 2 #define BGI_PRINT_LARGE 3 #define BGI_SAVE_AS 4 // This is the window creation and message processing function. Each new // window is created in its own thread and handles all its own messages. // It creates the window, sets the current window of the application to this // window, and signals the main thread that the window has been created. // DWORD WINAPI BGI__ThreadInitWindow( LPVOID pThreadData ) { HWND hWindow; // A handle to the window MSG Message; // A windows event message HDC hDC; // The device context of the window HBITMAP hBitmap; // A compatible bitmap of the DC for the Memory DC HMENU hMenu; // Handle to the system menu int CaptionHeight, xBorder, yBorder; WindowData *pWndData = (WindowData*)pThreadData; // Thread creation data if (pWndData->title.size( )) { CaptionHeight = GetSystemMetrics( SM_CYCAPTION ); // Height of caption } else { CaptionHeight = 0; // Height of caption } xBorder = GetSystemMetrics( SM_CXFIXEDFRAME ); // Width of border yBorder = GetSystemMetrics( SM_CYFIXEDFRAME ); // Height of border int height = pWndData->height + CaptionHeight + 2*yBorder; // Calculate total height int width = pWndData->width + 2*xBorder; // Calculate total width int top = pWndData->inittop; // MGM: Initial top int left = pWndData->initleft; // MGM: Initial left hWindow = CreateWindowEx( 0, // Extended window styles _T( "BGILibrary" ), // What kind of window pWndData->title.c_str( ), // Title at top of the window pWndData->title.size( ) ? (WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_DLGFRAME) : (WS_POPUP|WS_DLGFRAME), left, top, width, height, // left top width height NULL, // HANDLE to the parent window NULL, // HANDLE to the menu BGI__hInstance, // HANDLE to this program NULL ); // Address of window-creation data // Check if the window was created if ( hWindow == 0 ) { showerrorbox( ); return 0; } // Add the print options to the system menu, as shown in Chapter 10 (p 460) of Petzold hMenu = GetSystemMenu( hWindow, FALSE ); AppendMenu( hMenu, MF_SEPARATOR, 0, NULL ); AppendMenu( hMenu, MF_STRING, BGI_SAVE_AS, TEXT("Save image as...") ); AppendMenu( hMenu, MF_STRING, BGI_PRINT_SMALL, TEXT("Print 2\" wide...") ); AppendMenu( hMenu, MF_STRING, BGI_PRINT_MEDIUM, TEXT("Print 4.5\" wide...") ); AppendMenu( hMenu, MF_STRING, BGI_PRINT_LARGE, TEXT("Print 7\" wide...") ); AppendMenu( hMenu, MF_SEPARATOR, 0, NULL ); // Store the HANDLE in the structure pWndData->hWnd = hWindow; // Store the address of the WindowData structure in the window's user data // MGM TODO: Change this to SetWindowLongPtr: // SetWindowLongPtr( hWindow, GWL_USERDATA, (LONG_PTR)pWndData ); SetWindowLong( hWindow, GWL_USERDATA, (LONG)pWndData ); // Set the default active and visual page. These must be set here in // addition to setting all the defaults in initwindow because the paint // method depends on using the correct page. pWndData->ActivePage = 0; pWndData->VisualPage = 0; // Clear the mouse handler array and turn off queuing memset( pWndData->mouse_handlers, 0, (WM_MOUSELAST-WM_MOUSEFIRST+1) * sizeof(Handler) ); memset( pWndData->mouse_queuing, 0, (WM_MOUSELAST-WM_MOUSEFIRST+1) * sizeof(bool) ); // Create a memory Device Context used for drawing. The image is copied from here // to the screen in the paint method. The DC and bitmaps are deleted // in cls_OnDestroy() hDC = GetDC( hWindow ); pWndData->hDCMutex = CreateMutex(NULL, FALSE, NULL); WaitForSingleObject(pWndData->hDCMutex, 5000); for ( int i = 0; i < MAX_PAGES; i++ ) { pWndData->hDC[i] = CreateCompatibleDC( hDC ); // Create a bitmap for the memory DC. This is where the drawn image is stored. hBitmap = CreateCompatibleBitmap( hDC, pWndData->width, pWndData->height ); pWndData->hOldBitmap[i] = (HBITMAP)SelectObject( pWndData->hDC[i], hBitmap ); } ReleaseMutex(pWndData->hDCMutex); // Release the original DC and set up the mutex for the hDC array ReleaseDC( hWindow, hDC ); // Make the window visible ShowWindow( hWindow, SW_SHOWNORMAL ); // Make the window visible UpdateWindow( hWindow ); // Flush output buffer // Tell the user thread that the window was created successfully SetEvent( pWndData->WindowCreated ); // The message loop, which stops when a WM_QUIT message arrives while( GetMessage( &Message, NULL, 0, 0 ) ) { TranslateMessage( &Message ); DispatchMessage( &Message ); } // Free memory used by thread structure delete pWndData; return (DWORD)Message.wParam; } // This function handles the WM_CHAR message. This message is sent whenever // the user presses a key in the window (after a WM_KEYDOWN and WM_KEYUP // message, a WM_CHAR message is added). It adds the key pressed to the // keyboard queue for the window. // void cls_OnChar( HWND hWnd, TCHAR ch, int repeat ) { // This gets the address of the WindowData structure associated with the window WindowData *pWndData = BGI__GetWindowDataPtr( hWnd ); pWndData->kbd_queue.push( (TCHAR)ch );// Add the key to the queue SetEvent( pWndData->key_waiting ); // Notify the waiting thread, if any FORWARD_WM_CHAR( hWnd, ch, repeat, DefWindowProc ); } static void cls_OnClose( HWND hWnd ) { // This gets the address of the WindowData structure associated with the window WindowData *pWndData = BGI__GetWindowDataPtr( hWnd ); exit(0); } // This function handles the destroy message. It will cause the application // to send WM_QUIT, which will then terminate the message pump thread. // static void cls_OnDestroy( HWND hWnd ) { // This gets the address of the WindowData structure associated with the window WindowData *pWndData = BGI__GetWindowDataPtr( hWnd ); WaitForSingleObject(pWndData->hDCMutex, 5000); for ( int i = 0; i < MAX_PAGES; i++ ) { // Delete the pen in the DC's DeletePen( SelectPen( pWndData->hDC[i], GetStockPen( WHITE_PEN ) ) ); // Delete the brush in the DC's DeleteBrush( SelectBrush( pWndData->hDC[i], GetStockBrush( WHITE_BRUSH ) ) ); // Here we clean up the memory device contexts used by the program. // This selects the original bitmap back into the memory DC. The SelectObject // function returns the current bitmap which we then delete. DeleteObject( SelectObject( pWndData->hDC[i], pWndData->hOldBitmap[i] ) ); // Finally, we delete the MemoryDC DeleteObject( pWndData->hDC[i] ); } ReleaseMutex(pWndData->hDCMutex); // Clean up the bitmap memory DeleteBitmap( pWndData->hbitmap ); // Delete the two events created CloseHandle( pWndData->key_waiting ); CloseHandle( pWndData->WindowCreated ); PostQuitMessage( 0 ); } // This function handles the KEYDOWN message and will translate the virtual // keys to the keys expected by the user // static void cls_OnKey( HWND hWnd, UINT vk, BOOL down, int repeat, UINT flags ) { // This gets the address of the WindowData structure associated with the window // TODO: Set event for each key WindowData *pWndData = BGI__GetWindowDataPtr( hWnd ); switch ( vk ) { case VK_CLEAR: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_CENTER ); break; case VK_PRIOR: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_PGUP ); break; case VK_NEXT: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_PGDN ); break; case VK_END: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_END ); break; case VK_HOME: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_HOME ); break; case VK_LEFT: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_LEFT ); SetEvent( pWndData->key_waiting ); break; case VK_UP: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_UP ); SetEvent( pWndData->key_waiting ); break; case VK_RIGHT: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_RIGHT ); SetEvent( pWndData->key_waiting ); break; case VK_DOWN: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_DOWN ); SetEvent( pWndData->key_waiting ); break; case VK_INSERT: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_INSERT ); break; case VK_DELETE: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_DELETE ); break; case VK_F1: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_F1 ); break; case VK_F2: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_F2 ); break; case VK_F3: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_F3 ); break; case VK_F4: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_F4 ); break; case VK_F5: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_F5 ); break; case VK_F6: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_F6 ); break; case VK_F7: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_F7 ); break; case VK_F8: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_F8 ); break; case VK_F9: pWndData->kbd_queue.push( (TCHAR)0 ); pWndData->kbd_queue.push( (TCHAR)KEY_F9 ); break; } FORWARD_WM_KEYDOWN( hWnd, vk, repeat, flags, DefWindowProc ); } #include static void cls_OnPaint( HWND hWnd ) { PAINTSTRUCT ps; // BeginPaint puts info about the paint request here HDC hSrcDC; // The device context to copy from WindowData *pWndData = BGI__GetWindowDataPtr( hWnd ); int width, height; // Area that needs to be redrawn POINT srcCorner; // Logical coordinates of the source image upper left point BOOL success; // Is the BitBlt successful? int i; // Count for how many bitblts have been tried. WaitForSingleObject(pWndData->hDCMutex, INFINITE); BeginPaint( hWnd, &ps ); hSrcDC = pWndData->hDC[pWndData->VisualPage]; // The source (memory) DC // Get the dimensions of the area that needs to be redrawn. width = ps.rcPaint.right - ps.rcPaint.left; height = ps.rcPaint.bottom - ps.rcPaint.top; // The region that needs to be updated is specified in device units (pixels) for the actual DC. // However, if a viewport is specified, the source image is referenced in logical // units. Perform the conversion. // MGM TODO: When is the DPtoLP call needed? srcCorner.x = ps.rcPaint.left; srcCorner.y = ps.rcPaint.top; DPtoLP( hSrcDC, &srcCorner, 1 ); // MGM: Screen BitBlts are not always successful, although I don't know why. success = BitBlt( ps.hdc, ps.rcPaint.left, ps.rcPaint.top, width, height, hSrcDC, srcCorner.x, srcCorner.y, SRCCOPY ); EndPaint( hWnd, &ps ); // Validates the rectangle ReleaseMutex(pWndData->hDCMutex); if ( !success ) { // I would like to invalidate the rectangle again // since BitBlt wasn't successful, but the recursion seems // to hang some machines. // delay(100); // InvalidateRect( hWnd, &(ps.rcPaint), FALSE ); // std::cout << "Failure in cls_OnPaint" << std:: endl; } } // The message-handler function for the window // LRESULT CALLBACK WndProc ( HWND hWnd, UINT uiMessage, WPARAM wParam, LPARAM lParam ) { const std::queue EMPTY; POINTS where; WindowData *pWndData = BGI__GetWindowDataPtr( hWnd ); int type; // Type of mouse message Handler handler; // Registered mouse handler UINT uHitTest; // If this is a mouse message, set our internal state if ( pWndData && ( uiMessage >= WM_MOUSEFIRST ) && ( uiMessage <= WM_MOUSELAST ) ) { type = uiMessage - WM_MOUSEFIRST; if (!(pWndData->mouse_queuing[type]) && pWndData->clicks[type].size( ) ) { pWndData->clicks[type] = EMPTY; } pWndData->clicks[type].push(where = MAKEPOINTS( lParam )); // Set the current position for the event type pWndData->mouse = where; // Set the current mouse position // If the user has registered a mouse handler, call it now handler = pWndData->mouse_handlers[type]; if ( handler != NULL ) handler( where.x, where.y ); } switch ( uiMessage ) { HANDLE_MSG( hWnd, WM_CHAR, cls_OnChar ); HANDLE_MSG( hWnd, WM_DESTROY, cls_OnDestroy ); HANDLE_MSG( hWnd, WM_KEYDOWN, cls_OnKey ); HANDLE_MSG( hWnd, WM_PAINT, cls_OnPaint ); case WM_LBUTTONDBLCLK: return TRUE; case WM_NCHITTEST: uHitTest = DefWindowProc(hWnd, WM_NCHITTEST, wParam, lParam); if(uHitTest != HTCLIENT && pWndData && pWndData->title.size( ) == 0) return HTCAPTION; else return uHitTest; case WM_CLOSE: if ( pWndData->CloseBehavior ) { HANDLE_WM_CLOSE( hWnd, wParam, lParam, cls_OnClose ); } return TRUE; case WM_SYSCOMMAND: switch ( LOWORD(wParam) ) { case BGI_SAVE_AS: writeimagefile(NULL, 0, 0, INT_MAX, INT_MAX, false, hWnd); return 0; case BGI_PRINT_SMALL: printimage(NULL, 2.0, 0.75, 0.75, 0, 0, INT_MAX, INT_MAX, false, hWnd); return 0; case BGI_PRINT_MEDIUM: printimage(NULL, 4.5, 0.75, 0.75, 0, 0, INT_MAX, INT_MAX, false, hWnd); return 0; case BGI_PRINT_LARGE: printimage(NULL, 7.0, 0.75, 0.75, 0, 0, INT_MAX, INT_MAX, false, hWnd); return 0; } break; } return DefWindowProc( hWnd, uiMessage, wParam, lParam ); }