#include "ctlspriv.h" #define CURSORSLOP 3 static WCHAR szHeaderClassW[] = HEADERCLASSNAMEW; static CCHAR szHeaderClassA[] = HEADERCLASSNAMEA; static HCURSOR hSplit; /* I only need one of these since you can only have capture on one window * at a time */ static struct { HWND hWnd; HWND hOldWnd; HWND hwndParent; UINT ID; int nPart; int nLastVisible; int *pSaveParts; RECT rc; } state = { NULL, NULL, NULL, 0, -1, 0, NULL, { 0, 0, 0, 0 } } ; Static void NEAR PASCAL BeginAdjust(HWND hWnd, PSTATUSINFO pStatusInfo, int nPart, POINT pt); /* Return the partition index if the position is close enough; -1 otherwise */ Static int NEAR PASCAL GetPart(PSTATUSINFO pStatusInfo, PSTRINGINFO pStringInfo, POINT pt) { int nParts; for (nParts = 0; nParts < pStatusInfo->nParts - 1; ++nParts, ++pStringInfo) { if (pStringInfo->right - pt.x < CURSORSLOP && pStringInfo->right - pt.x > (1 - CURSORSLOP)) break; if (nParts >= pStatusInfo->nParts - 2) { nParts = -1; break; } } return(nParts); } Static int NEAR PASCAL GetPrevVisible(PSTATUSINFO pStatusInfo, int nPart) { for (--nPart; nPart >= 0; --nPart) if (pStatusInfo->sInfo[nPart].right > 0) break; return(nPart); } Static int NEAR PASCAL GetNextVisible(PSTATUSINFO pStatusInfo, int nPart) { for (++nPart; nPart < pStatusInfo->nParts; ++nPart) if (pStatusInfo->sInfo[nPart].right > 0) return(nPart); return(-1); } /* Note that the handle returned here should NOT be freed by the caller */ Static HLOCAL NEAR PASCAL GetHeaderParts(HWND hWnd, PSTATUSINFO pStatusInfo, BOOL bAdjustLast) { static HLOCAL hSaveParts = NULL; static int nSaveParts = 0; HLOCAL hTemp; PSTRINGINFO pSaveParts, pStringInfo, pNewInfo; int nParts, nSprings, nTotal, nTemp, nLast, nLastVisible; RECT rcClient; /* Allocate a single moveable buffer that is large enough to hold the * largest array of parts. */ nParts = pStatusInfo->nParts; if (nParts > nSaveParts) { if (hSaveParts) { hTemp = LocalReAlloc(hSaveParts, nParts * sizeof(STRINGINFO), LMEM_MOVEABLE); if (!hTemp) return(NULL); } else { hTemp = LocalAlloc(LMEM_FIXED, nParts * sizeof(STRINGINFO)); if (!hTemp) return(NULL); } hSaveParts = hTemp; nSaveParts = nParts; } pSaveParts = (PSTRINGINFO) hSaveParts; /* Go through the list, counting the number of "springs" and the * minimum width. */ for (nTotal = 0, nSprings = 0, pNewInfo = pSaveParts, pStringInfo = pStatusInfo->sInfo; nParts > 0; --nParts, ++pNewInfo, ++pStringInfo) { pNewInfo->pString = pStringInfo->pString; pNewInfo->uType = pStringInfo->uType | SBT_NOBORDERS; nTemp = pStringInfo->right; if (nTemp < 0) { nTemp = 0; pNewInfo->uType &= ~HBT_SPRING; } if (pNewInfo->uType & HBT_SPRING) ++nSprings; nTotal += nTemp; pNewInfo->right = nTemp; } /* Determine the amount left to distribute to the springs, and then * distribute this amount evenly. */ GetClientRect(hWnd, &rcClient); nTotal = rcClient.right - nTotal; if (nTotal < 0) nTotal = 0; for (nParts = 0, nLast = 0, nLastVisible = -1, pNewInfo = pSaveParts, pStringInfo = pStatusInfo->sInfo; nParts < pStatusInfo->nParts; ++nParts, ++pNewInfo, ++pStringInfo) { if ((pNewInfo->uType&HBT_SPRING) && nSprings) { nTemp = nTotal / nSprings; --nSprings; nTotal -= nTemp; pNewInfo->right += nTemp; } /* Save the rightmost visible guy. */ if (pNewInfo->right) nLastVisible = nParts; /* Transform the width to an absolute position. */ pNewInfo->right += nLast; nLast = pNewInfo->right; } if (bAdjustLast && nLastVisible>=0) { for (pNewInfo = pSaveParts + nLastVisible; nLastVisible < pStatusInfo->nParts; ++nLastVisible, ++pNewInfo) pNewInfo->right = rcClient.right; } return(hSaveParts); } Static void NEAR PASCAL AdjustBorders(HWND hWnd, PSTATUSINFO pStatusInfo) { POINT pt, ptTemp, ptSave; int nPart; int accel = 0; HWND hwndParent; UINT uiID; MSG msg; RECT rc; int nStart; int nDirection; HLOCAL hSaveParts; PSTRINGINFO pSaveParts; GetCursorPos(&ptSave); GetClientRect(hWnd, &rc); hwndParent = GetParent(hWnd); uiID = GetWindowLong(hWnd, GWL_ID); ShowCursor(TRUE); pt.x = 0; pt.y = (pStatusInfo->nFontHeight+1) / 2; ClientToScreen(hWnd, &pt); nDirection = 1; nPart = pStatusInfo->nParts - 2; goto MoveTheCursor; for ( ; ; ) { while (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE|PM_NOYIELD)) ; switch (msg.message) { case WM_KEYDOWN: switch (msg.wParam) { case VK_TAB: DoTab: /* If the sift key is down, go backwards */ if (GetKeyState(VK_SHIFT) & 0x8000) nDirection = -1; else nDirection = 1; MoveTheCursor: /* Make sure the previous adjust is cleaned up, * then tell the app we are starting. */ if (state.nPart >= 0) SendMessage(hWnd, WM_LBUTTONUP, 0, 0L); SendMessage (hwndParent, WM_COMMAND, GET_WM_COMMAND_MPS(uiID,0, HBN_BEGINADJUST)); hSaveParts = GetHeaderParts(hWnd, pStatusInfo, FALSE); if (!hSaveParts) goto EndAdjust; pSaveParts = (PSTRINGINFO) hSaveParts; /* Don't try to adjust anything that is not * currently on the screen or has no visible * cols to its right. */ nStart = nPart; do { nPart += nDirection; if (nPart >= pStatusInfo->nParts - 1) nPart = 0; if (nPart < 0) nPart = pStatusInfo->nParts-2; if (nPart == nStart) goto EndAdjust; } while ((UINT)pSaveParts[nPart].right >= (UINT)rc.right || pStatusInfo->sInfo[nPart].right < 0 || GetNextVisible(pStatusInfo, nPart) < 0); /* Immediately go into adjusting mode; send BEGINADJUST right * afterwards to get the right MenuHelp */ BeginAdjust(hWnd, pStatusInfo, nPart, pt); SendMessage (hwndParent, WM_COMMAND, GET_WM_COMMAND_MPS(uiID,state.nPart, HBN_BEGINADJUST)); break; case VK_LEFT: case VK_RIGHT: case VK_UP: case VK_DOWN: GetCursorPos(&ptTemp); ++accel; if (msg.wParam == VK_LEFT || msg.wParam == VK_UP) ptTemp.x -= accel; else ptTemp.x += accel; SetCursorPos(ptTemp.x, ptTemp.y); break; case VK_RETURN: DoReturn: SendMessage(hWnd, WM_LBUTTONUP, 0, 0L); goto EndAdjust; case VK_ESCAPE: SendMessage(hWnd, WM_CHAR, VK_ESCAPE, 0L); goto EndAdjust; default: break; } break; case WM_KEYUP: accel = 0; break; case WM_LBUTTONDOWN: case WM_LBUTTONUP: goto DoReturn; case WM_RBUTTONDOWN: goto DoTab; default: TranslateMessage(&msg); DispatchMessage(&msg); break; } } EndAdjust: ShowCursor(FALSE); SetCursorPos(ptSave.x, ptSave.y); SendMessage (hwndParent, WM_COMMAND, GET_WM_COMMAND_MPS(uiID, 0, HBN_ENDADJUST)); } Static void NEAR PASCAL PaintHeaderWnd(HWND hWnd, PSTATUSINFO pStatusInfo) { HLOCAL hSaveParts; PSTRINGINFO pStringInfo, pSaveParts; int nParts, nBorderY; // andrewbe int nBorderWidth, nBorderHeight; // andrewbe HDC hDC; HBRUSH hBrush, hOldBrush; RECT rcClient; hSaveParts = GetHeaderParts(hWnd, pStatusInfo, TRUE); if (!hSaveParts) return; pSaveParts = (PSTRINGINFO) hSaveParts; /* Let the status bar code draw the text */ PaintStatusWnd (hWnd, pStatusInfo, pSaveParts, pStatusInfo->nParts, pStatusInfo->nBorderX, TRUE); /* Now draw the lines between panes */ hBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOWFRAME)); if (hBrush) { hDC = GetDC(hWnd); hOldBrush = SelectObject(hDC, hBrush); if (hOldBrush) { GetClientRect(hWnd, &rcClient); nBorderWidth = pStatusInfo->nBorderPart; // andrewbe nBorderY = 0; // nBorderHeight = rcClient.bottom; // To stop the // blasted if (!(GetWindowLong(hWnd, GWL_STYLE)&CCS_NOHILITE)) { // flickering nBorderY++; // (andrewbe) nBorderHeight--; // } // for (nParts = pStatusInfo->nParts - 1, pStringInfo = pSaveParts; nParts > 0; --nParts, ++pStringInfo) { PatBlt (hDC, pStringInfo->right, nBorderY, nBorderWidth, nBorderHeight, PATCOPY); } SelectObject(hDC, hOldBrush); } ReleaseDC(hWnd, hDC); DeleteObject(hBrush); } } Static void NEAR PASCAL BeginAdjust(HWND hWnd, PSTATUSINFO pStatusInfo, int nPart, POINT pt) { HLOCAL hSaveParts; PSTRINGINFO pSaveParts; RECT rc; int nParts; /* Just make sure we are cleaned up from the last SetCapture */ if (state.nPart >= 0) SendMessage(state.hWnd, WM_LBUTTONUP, 0, 0L); hSaveParts = GetHeaderParts(hWnd, pStatusInfo, FALSE); if (!hSaveParts) return; if (nPart >= 0) state.nPart = nPart; else { state.nPart = GetPart(pStatusInfo, hSaveParts, pt); if (state.nPart < 0) return; } /* Save the current state in case the user aborts */ state.pSaveParts = (int *)LocalAlloc(LMEM_FIXED, pStatusInfo->nParts * sizeof(int)); if (!state.pSaveParts) { state.nPart = -1; return; } for (nParts = pStatusInfo->nParts - 1; nParts >= 0; --nParts) state.pSaveParts[nParts] = pStatusInfo->sInfo[nParts].right; /* Set all min widths to their current widths. Special case nParts=0. */ pSaveParts = (PSTRINGINFO)hSaveParts; for (nParts = pStatusInfo->nParts - 1, state.nLastVisible =- 1; nParts > 0; --nParts) { if (state.pSaveParts[nParts] > 0) { pStatusInfo->sInfo[nParts].right = pSaveParts[nParts].right - pSaveParts[nParts-1].right; if (state.nLastVisible < 0) state.nLastVisible = nParts; } } /* Set the last visible one very wide so there is never any spring. */ if (state.nLastVisible >= 0) pStatusInfo->sInfo[state.nLastVisible].right = 0x3fff; if (nParts == 0 && state.pSaveParts[0]> 0 ) pStatusInfo->sInfo[0].right = pSaveParts[0].right; state.hWnd = hWnd; pt.x = pSaveParts[state.nPart].right; ClientToScreen(hWnd, &pt); SetCursorPos(pt.x, pt.y); SetCapture(hWnd); state.hOldWnd = SetFocus(hWnd); GetClientRect(hWnd, &state.rc); if (state.nPart > 0) state.rc.left = pSaveParts[state.nPart-1].right; /* Clip the cursor to the appropriate area. */ rc = state.rc; ++rc.left; /* Some code below assumes that state.rc.right is the width of the * window. */ rc.right -= rc.left; ClientToScreen(hWnd, (LPPOINT)&rc); rc.right += rc.left; rc.top = 0; rc.bottom = GetSystemMetrics(SM_CYSCREEN); ClipCursor(&rc); SendMessage (hWnd, WM_SETCURSOR, (WPARAM)hWnd, MAKELONG(HTCLIENT, WM_LBUTTONDOWN)); state.ID = GetWindowLong(hWnd, GWL_ID); state.hwndParent = GetParent(hWnd); SendMessage (state.hwndParent, WM_COMMAND, GET_WM_COMMAND_MPS(state.ID,state.nPart, HBN_BEGINDRAG)); } Static void NEAR PASCAL TermAdjust(void) { if (state.nPart < 0) return; state.nPart = -1; LocalFree((HLOCAL)state.pSaveParts); ReleaseCapture(); if (state.hOldWnd) SetFocus(state.hOldWnd); ClipCursor(NULL); /* Send a dragging message just in case */ SendMessage(state.hwndParent, WM_COMMAND, GET_WM_COMMAND_MPS(state.ID,state.nPart, HBN_DRAGGING)); SendMessage(state.hwndParent, WM_COMMAND, GET_WM_COMMAND_MPS(state.ID,state.nPart, HBN_ENDDRAG)); } Static void NEAR PASCAL AbortAdjust(PSTATUSINFO pStatusInfo) { int nPart; if (state.nPart < 0) return; for (nPart = pStatusInfo->nParts - 1; nPart >= 0; --nPart) pStatusInfo->sInfo[nPart].right = state.pSaveParts[nPart]; InvalidateRect(state.hWnd, NULL, TRUE); TermAdjust(); } /* Since a header bar and a status bar are so similar, I am just going to * code the differences here, and call StatusWndProc for any messages I * don't want to handle */ LRESULT CALLBACK HeaderWndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam) { PSTATUSINFO pStatusInfo; pStatusInfo = (PSTATUSINFO)GetWindowLong(hWnd, GWL_PSTATUSINFO); if (!pStatusInfo) { if (uMessage == WM_CREATE) { #define lpcs ((LPCREATESTRUCT)lParam) if (!(lpcs->style&(CCS_TOP|CCS_NOMOVEY|CCS_BOTTOM))) { lpcs->style |= CCS_NOMOVEY; SetWindowLong(hWnd, GWL_STYLE, lpcs->style); } } goto DoDefault; } switch (uMessage) { /* We just use the system font for DBCS systems */ #ifndef DBCS case WM_SETFONT: if (wParam == 0) { HDC hDC; hDC = GetDC(hWnd); wParam = (WPARAM) #if 0 // // Use unicode font // CreateFont(-8 * GetDeviceCaps(hDC, LOGPIXELSY) / 72, 0, 0, 0, 400, 0, 0, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH | FF_SWISS, UNICODE_FONT_NAME); #endif CreateFont(-8 * GetDeviceCaps(hDC, LOGPIXELSY) / 72, 0, 0, 0, 400, 0, 0, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH | FF_SWISS, szSansSerif); ReleaseDC(hWnd, hDC); if (!wParam) break; StatusWndProc(hWnd, uMessage, wParam, lParam); pStatusInfo->bDefFont = TRUE; return(TRUE); } break; #endif case WM_PAINT: PaintHeaderWnd(hWnd, pStatusInfo); return(TRUE); case SB_SETBORDERS: { int nBorder; LPINT lpInt; lpInt = (LPINT)lParam; nBorder = *lpInt++; pStatusInfo->nBorderX = nBorder<0 ? 0 : nBorder; nBorder = *lpInt++; pStatusInfo->nBorderY = nBorder<0 ? 0 : nBorder; nBorder = *lpInt; pStatusInfo->nBorderPart = nBorder<0 ? GetSystemMetrics(SM_CXBORDER) // andrewbe : nBorder; return(TRUE); } case HB_SAVERESTORE: { int *pInt; BOOL bRet; LPTSTR *lpNames; pInt = (int *)LocalAlloc(LMEM_FIXED, pStatusInfo->nParts * sizeof(int)); if (!pInt) return(FALSE); lpNames = (LPTSTR *)lParam; if (wParam) { SendMessage (hWnd, SB_GETPARTS, pStatusInfo->nParts, (LPARAM)(LPINT)pInt); bRet = WritePrivateProfileStruct(lpNames[0], szHeaderClassW, (LPBYTE)pInt, pStatusInfo->nParts*sizeof(int), lpNames[1]); } else { bRet = GetPrivateProfileStruct(lpNames[0], szHeaderClassW, (LPBYTE)pInt, pStatusInfo->nParts*sizeof(int), lpNames[1]); if (bRet) SendMessage (hWnd, SB_SETPARTS, pStatusInfo->nParts, (LPARAM)(LPINT)pInt); } LocalFree((HLOCAL)pInt); return(bRet); } case HB_ADJUST: AdjustBorders(hWnd, pStatusInfo); break; case HB_GETPARTS: { HLOCAL hSaveParts; PSTRINGINFO pSaveParts; LPINT lpResult; hSaveParts = GetHeaderParts(hWnd, pStatusInfo, FALSE); if (!hSaveParts) return(0L); pSaveParts = (PSTRINGINFO) hSaveParts; if (wParam > (WPARAM)pStatusInfo->nParts) wParam = pStatusInfo->nParts; for (lpResult = (LPINT)lParam; wParam > 0; --wParam, ++lpResult, ++pSaveParts) *lpResult = pSaveParts->right; return((LRESULT)pStatusInfo->nParts); } case HB_SHOWTOGGLE: if (wParam >= (WPARAM)pStatusInfo->nParts) return(FALSE); pStatusInfo->sInfo[wParam].right = -pStatusInfo->sInfo[wParam].right; InvalidateRect(hWnd, NULL, TRUE); return(TRUE); case WM_SETCURSOR: if ((HWND)wParam == hWnd) { POINT pt; HLOCAL hSaveParts; if (state.nPart >= 0) { SetCursor(hSplit); return(TRUE); } else { GetCursorPos(&pt); ScreenToClient(hWnd, &pt); hSaveParts = GetHeaderParts(hWnd, pStatusInfo, FALSE); if (!hSaveParts) return(FALSE); if (GetPart(pStatusInfo, hSaveParts, pt) >= 0) { SetCursor(hSplit); return(TRUE); } } } break; case WM_LBUTTONDOWN: { POINT pt; LONG2POINT( lParam,pt ); BeginAdjust(hWnd, pStatusInfo, -1, pt); break; } case WM_CHAR: if (wParam == VK_ESCAPE) AbortAdjust(pStatusInfo); break; case WM_MOUSEMOVE: if (state.nPart>=0 && hWnd==state.hWnd) { /* We need to get the current position in case old MOUSEMOVE * messages haven't been cleared yet. */ // andrewbe -- Fixed attempt to squeeze 64 bits into lParam { POINT CursorPos; GetCursorPos(&CursorPos); ScreenToClient(hWnd, &CursorPos); lParam = MAKELPARAM((WORD)CursorPos.x, (WORD)CursorPos.y); } pStatusInfo->sInfo[state.nPart].right = LOWORD(lParam) - state.rc.left; InvalidateRect(hWnd, &state.rc, FALSE); UpdateWindow(hWnd); SendMessage (state.hwndParent, WM_COMMAND, GET_WM_COMMAND_MPS(state.ID,state.nPart, HBN_DRAGGING)); } break; case WM_LBUTTONUP: if (state.nPart >= 0 && hWnd == state.hWnd) // fix access violation (andrewbe) { HLOCAL hSaveParts; PSTRINGINFO pSaveParts; /* Save the width of the last column if it is visible. */ if (state.nLastVisible >= 0) { hSaveParts = GetHeaderParts(hWnd, pStatusInfo, FALSE); if (hSaveParts) { pSaveParts = (PSTRINGINFO) hSaveParts; if (pSaveParts[state.nLastVisible-1].right < state.rc.right) { pStatusInfo->sInfo[state.nLastVisible].right = state.rc.right - pSaveParts[state.nLastVisible-1].right; } else { pStatusInfo->sInfo[state.nLastVisible].right = state.pSaveParts[state.nLastVisible]; } } } TermAdjust(); break; } default: break; } DoDefault: return(StatusWndProc(hWnd, uMessage, wParam, lParam)); } BOOL FAR PASCAL InitHeaderClass(HINSTANCE hInstance) { WNDCLASS rClass; if (GetClassInfo(hInstance, szHeaderClassW, &rClass)) return(TRUE); hSplit = LoadCursor(hInst, (LPTSTR) MAKEINTRESOURCE(IDC_SPLIT)); rClass.style = CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS; rClass.lpfnWndProc = (WNDPROC)HeaderWndProc; rClass.cbClsExtra = 0; rClass.cbWndExtra = sizeof(PSTATUSINFO); rClass.hInstance = hInstance; rClass.hIcon = NULL; rClass.hCursor = LoadCursor(NULL, IDC_ARROW); rClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); rClass.lpszMenuName = NULL; rClass.lpszClassName = szHeaderClassW; return(RegisterClass(&rClass)); } HWND WINAPI CreateHeaderWindowA(LONG style, LPCSTR lpszText, HWND hwndParent, WORD wID) { /* Create a default window and return */ return(CreateWindowA (szHeaderClassA, lpszText, style, -100, -100, 10, 10, hwndParent, (HMENU)wID, hInst, NULL)); } HWND WINAPI CreateHeaderWindowW(LONG style, LPCWSTR lpszText, HWND hwndParent, WORD wID) { /* Create a default window and return */ return(CreateWindowW (szHeaderClassW, lpszText, style, -100, -100, 10, 10, hwndParent, (HMENU)wID, hInst, NULL)); }