update for HEAD-2003091401
[reactos.git] / subsys / system / explorer / Seashell / SeaShellExt / UIFixTB.cpp
1 f////////////////////////////////////////////////////////////////
2 // Copyright 1998 Paul DiLascia
3 // If this code works, it was written by Paul DiLascia.
4 // If not, I don't know who wrote it.
5 //
6 // CFixMFCToolBar fixes sizing bugs in MFC CToolBar, so it works with
7 // modern toolbars in versions of comctl32.dll > 4.70.
8 //
9 // Most of this code is copied from MFC, with slight modifications marked "PD"
10 //
11 #include "StdAfx.h"
12 #include "UIFixTB.h"
13 #include "UIModulVer.h"
14
15 #ifdef _DEBUG
16 #define new DEBUG_NEW
17 #undef THIS_FILE
18 static char THIS_FILE[] = __FILE__;
19 #endif
20
21 IMPLEMENT_DYNAMIC(CFixMFCToolBar, CToolBar)
22
23 BEGIN_MESSAGE_MAP(CFixMFCToolBar, CToolBar)
24         ON_MESSAGE(TB_SETBITMAPSIZE, OnSetBitmapSize)
25         ON_MESSAGE(TB_SETBUTTONSIZE, OnSetButtonSize)
26         ON_MESSAGE(WM_SETTINGCHANGE, OnSettingChange)
27         ON_MESSAGE(WM_SETFONT,            OnSettingChange)
28 END_MESSAGE_MAP()
29
30 /////////////////
31 // This function gets the version number of comctl32.dll.
32 //
33 static int GetVerComCtl32()
34 {
35         CModuleVersion ver;
36         DLLVERSIONINFO dvi;
37         VERIFY(ver.DllGetVersion(_T("comctl32.dll"), dvi));
38         return dvi.dwMajorVersion*100 + dvi.dwMinorVersion;
39 }
40
41 int CFixMFCToolBar::iVerComCtl32 = GetVerComCtl32();
42
43 CFixMFCToolBar::CFixMFCToolBar()
44 {
45         m_bShowDropdownArrowWhenVertical = FALSE;
46 }
47
48 CFixMFCToolBar::~CFixMFCToolBar()
49 {
50 }
51
52 //////////////////
53 // These functions duplicate functionalityin VC 6.0
54 // Need to set transparent/flat style before setting
55 // button/image size or font to allow zero-height border.
56 //
57 LRESULT CFixMFCToolBar::OnSetBitmapSize(WPARAM, LPARAM lp)
58 {
59         return OnSizeHelper(m_sizeImage, lp);
60 }
61 LRESULT CFixMFCToolBar::OnSetButtonSize(WPARAM, LPARAM lp)
62 {
63         return OnSizeHelper(m_sizeButton, lp);
64 }
65 LRESULT CFixMFCToolBar::OnSettingChange(WPARAM, LPARAM lp)
66 {
67         return OnSizeHelper(CSize(0,0), lp);
68 }
69 LRESULT CFixMFCToolBar::OnSizeHelper(CSize& sz, LPARAM lp)
70 {
71         ASSERT(iVerComCtl32 > 0);
72         BOOL bModStyle =FALSE;
73         DWORD dwStyle =0;
74         if (iVerComCtl32 >= 471) {
75                 dwStyle = GetStyle();
76                 bModStyle = ModifyStyle(0, TBSTYLE_TRANSPARENT|TBSTYLE_FLAT);           
77         }
78
79         LRESULT lRet = Default();
80         if (lRet)
81                 sz = lp;
82
83         if (bModStyle)
84                 SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);
85
86         return lRet;
87 }
88
89 //////////////////
90 // **PD**
91 // This is the all-important function that gets the true size of a button,
92 // instead of using m_sizeButton. And it's virtual, so you can override if
93 // my algorithm doesn't work, as will surely be the case in some circumstances.
94 //
95 CSize CFixMFCToolBar::GetButtonSize(TBBUTTON* pData, int iButton)
96 {
97         ASSERT(iVerComCtl32 > 0);
98
99         // Get the actual size of the button, not what's in m_sizeButton.
100         // Make sure to do SendMessage instead of calling MFC's GetItemRect,
101         // which has all sorts of bad side-effects! (Go ahead, take a look at it.)
102         // 
103         CRect rc;
104         SendMessage(TB_GETITEMRECT, iButton, (LPARAM)&rc);
105         CSize sz = rc.Size();
106
107         ////////////////
108         // Now must do special case for various versions of comctl32.dll,
109         //
110         DWORD dwStyle = pData[iButton].fsStyle;
111         if ((pData[iButton].fsState & TBSTATE_WRAP)) {
112                 if (dwStyle & TBSTYLE_SEP) {
113                         // this is the last separator in the row (eg vertically docked)
114                         // fudge the height, and ignore the width. TB_GETITEMRECT will return
115                         // size = (8 x 22) even for a separator in vertical toolbar
116                         //
117                         if (iVerComCtl32 <= 470)
118                                 sz.cy -= 3;             // empircally good fudge factor
119                         else if (iVerComCtl32 != 471)
120                                 sz.cy = sz.cx;
121                         sz.cx = 0;                      // separator takes no width if it's the last one
122
123                 } else if (dwStyle & TBSTYLE_DROPDOWN &&
124                         !m_bShowDropdownArrowWhenVertical) {
125                         // ignore width of dropdown
126                         sz.cx = 0;
127                 }
128         }
129         return sz;
130 }
131
132 //////////////////
133 // **PD**
134 // Part 2 of correction for MFC is to recalculate everything when the bar
135 // goes from docked to undocked because the AdjustSize calculation happens
136 // when the bar is in the old state, and thus wrong. After the bar is
137 // docked/undocked, I'll recalculate with the new style and commit the change.
138 //
139 void CFixMFCToolBar::OnBarStyleChange(DWORD dwOldStyle, DWORD dwNewStyle)
140 {
141         CToolBar::OnBarStyleChange(dwOldStyle, dwNewStyle);
142         
143         if (dwOldStyle != dwNewStyle) {
144                 DWORD dwMode = 0;
145                 if ((dwNewStyle & CBRS_SIZE_DYNAMIC) && (dwNewStyle & CBRS_FLOATING))
146                         dwMode = LM_HORZ | LM_MRUWIDTH;
147                 else if (dwNewStyle & CBRS_ORIENT_HORZ)
148                         dwMode = LM_HORZ | LM_HORZDOCK;
149                 else
150                         dwMode =  LM_VERTDOCK;
151
152                 CalcDynamicLayout(-1, dwMode | LM_COMMIT);
153         }
154 }
155
156 ////////////////////////////////////////////////////////////////
157 // Stuff below is copied from MFC; only mod is to call GetButtonSize.
158
159 #ifdef _MAC
160         #define CX_OVERLAP  1
161 #else
162         #define CX_OVERLAP  0
163 #endif
164
165 CSize CFixMFCToolBar::CalcSize(TBBUTTON* pData, int nCount)
166 {
167         ASSERT(pData != NULL && nCount > 0);
168
169         CPoint cur(0,0);
170         CSize sizeResult(0,0);
171         int cyTallestOnRow = 0;
172
173         for (int i = 0; i < nCount; i++)
174         {
175                 if (pData[i].fsState & TBSTATE_HIDDEN)
176                         continue;
177
178                 // **PD**
179                 // Load actual size of button into a local variable
180                 // called m_sizeButton. C++ will use this instead of
181                 // CToolBar::m_sizeButton.
182                 //
183                 CSize m_sizeButton = GetButtonSize(pData, i);
184
185                 // **PD**
186                 // I also changed the logic below to be more correct.
187                 cyTallestOnRow = max(cyTallestOnRow, m_sizeButton.cy);
188                 sizeResult.cx = max(cur.x + m_sizeButton.cx, sizeResult.cx);
189                 sizeResult.cy = max(cur.y + m_sizeButton.cy, sizeResult.cy);
190
191                 cur.x += m_sizeButton.cx - CX_OVERLAP;
192
193                 if (pData[i].fsState & TBSTATE_WRAP)
194                 {
195                         cur.x = 0;
196                         cur.y += cyTallestOnRow;
197                         cyTallestOnRow = 0;
198                         if (pData[i].fsStyle & TBSTYLE_SEP)
199                                 cur.y += m_sizeButton.cy;
200                 }
201         }
202         return sizeResult;
203 }
204
205 int CFixMFCToolBar::WrapToolBar(TBBUTTON* pData, int nCount, int nWidth)
206 {
207         ASSERT(pData != NULL && nCount > 0);
208
209         int nResult = 0;
210         int x = 0;
211         for (int i = 0; i < nCount; i++)
212         {
213                 pData[i].fsState &= ~TBSTATE_WRAP;
214
215                 if (pData[i].fsState & TBSTATE_HIDDEN)
216                         continue;
217
218                 int dx, dxNext;
219
220                 // **PD**
221                 // Load actual size of button into a local variable
222                 // called m_sizeButton. C++ will use this instead of
223                 // CToolBar::m_sizeButton.
224                 //
225                 CSize m_sizeButton = GetButtonSize(pData, i);
226
227                 dx = m_sizeButton.cx;
228                 dxNext = dx - CX_OVERLAP;
229
230                 if (x + dx > nWidth)
231                 {
232                         BOOL bFound = FALSE;
233                         for (int j = i; j >= 0  &&  !(pData[j].fsState & TBSTATE_WRAP); j--)
234                         {
235                                 // Find last separator that isn't hidden
236                                 // a separator that has a command ID is not
237                                 // a separator, but a custom control.
238                                 if ((pData[j].fsStyle & TBSTYLE_SEP) &&
239                                         (pData[j].idCommand == 0) &&
240                                         !(pData[j].fsState & TBSTATE_HIDDEN))
241                                 {
242                                         bFound = TRUE; i = j; x = 0;
243                                         pData[j].fsState |= TBSTATE_WRAP;
244                                         nResult++;
245                                         break;
246                                 }
247                         }
248                         if (!bFound)
249                         {
250                                 for (int j = i - 1; j >= 0 && !(pData[j].fsState & TBSTATE_WRAP); j--)
251                                 {
252                                         // Never wrap anything that is hidden,
253                                         // or any custom controls
254                                         if ((pData[j].fsState & TBSTATE_HIDDEN) ||
255                                                 ((pData[j].fsStyle & TBSTYLE_SEP) &&
256                                                 (pData[j].idCommand != 0)))
257                                                 continue;
258
259                                         bFound = TRUE; i = j; x = 0;
260                                         pData[j].fsState |= TBSTATE_WRAP;
261                                         nResult++;
262                                         break;
263                                 }
264                                 if (!bFound)
265                                         x += dxNext;
266                         }
267                 }
268                 else
269                         x += dxNext;
270         }
271         return nResult + 1;
272 }
273
274 //////////////////////////////////////////////////////////////////////////
275 // **PD**
276 // Functions below are NOT actually modified. They're only here because they
277 // calls the modified functions above, which are NOT virtual.
278 //////////////////////////////////////////////////////////////////////////
279
280 void  CFixMFCToolBar::SizeToolBar(TBBUTTON* pData, int nCount, int nLength, BOOL bVert)
281 {
282         ASSERT(pData != NULL && nCount > 0);
283
284         if (!bVert)
285         {
286                 int nMin, nMax, nTarget, nCurrent, nMid;
287
288                 // Wrap ToolBar as specified
289                 nMax = nLength;
290                 nTarget = WrapToolBar(pData, nCount, nMax);
291
292                 // Wrap ToolBar vertically
293                 nMin = 0;
294                 nCurrent = WrapToolBar(pData, nCount, nMin);
295
296                 if (nCurrent != nTarget)
297                 {
298                         while (nMin < nMax)
299                         {
300                                 nMid = (nMin + nMax) / 2;
301                                 nCurrent = WrapToolBar(pData, nCount, nMid);
302
303                                 if (nCurrent == nTarget)
304                                         nMax = nMid;
305                                 else
306                                 {
307                                         if (nMin == nMid)
308                                         {
309                                                 WrapToolBar(pData, nCount, nMax);
310                                                 break;
311                                         }
312                                         nMin = nMid;
313                                 }
314                         }
315                 }
316                 CSize size = CalcSize(pData, nCount);
317                 WrapToolBar(pData, nCount, size.cx);
318         }
319         else
320         {
321                 CSize sizeMax, sizeMin, sizeMid;
322
323                 // Wrap ToolBar vertically
324                 WrapToolBar(pData, nCount, 0);
325                 sizeMin = CalcSize(pData, nCount);
326
327                 // Wrap ToolBar horizontally
328                 WrapToolBar(pData, nCount, 32767);
329                 sizeMax = CalcSize(pData, nCount);
330
331                 while (sizeMin.cx < sizeMax.cx)
332                 {
333                         sizeMid.cx = (sizeMin.cx + sizeMax.cx) / 2;
334                         WrapToolBar(pData, nCount, sizeMid.cx);
335                         sizeMid = CalcSize(pData, nCount);
336
337                         if (nLength < sizeMid.cy)
338                         {
339                                 if (sizeMin == sizeMid)
340                                 {
341                                         WrapToolBar(pData, nCount, sizeMax.cx);
342                                         return;
343                                 }
344                                 sizeMin = sizeMid;
345                         }
346                         else if (nLength > sizeMid.cy)
347                                 sizeMax = sizeMid;
348                         else
349                                 return;
350                 }
351         }
352 }
353
354 struct _AFX_CONTROLPOS
355 {
356         int nIndex, nID;
357         CRect rectOldPos;
358 };
359
360 CSize CFixMFCToolBar::CalcLayout(DWORD dwMode, int nLength)
361 {
362         ASSERT_VALID(this);
363         ASSERT(::IsWindow(m_hWnd));
364         if (dwMode & LM_HORZDOCK)
365                 ASSERT(dwMode & LM_HORZ);
366
367         int nCount;
368         TBBUTTON* pData;
369         CSize sizeResult(0,0);
370
371         // Load Buttons
372         {
373                 nCount = SendMessage(TB_BUTTONCOUNT, 0, 0);
374                 if (nCount != 0)
375                 {
376                         int i;
377                         pData = new TBBUTTON[nCount];
378                         for (i = 0; i < nCount; i++)
379                                 GetButton(i, &pData[i]); // **PD** renamed from _GetButton
380                 }
381         }
382
383         if (nCount > 0)
384         {
385                 if (!(m_dwStyle & CBRS_SIZE_FIXED))
386                 {
387                         BOOL bDynamic = m_dwStyle & CBRS_SIZE_DYNAMIC;
388
389                         if (bDynamic && (dwMode & LM_MRUWIDTH))
390                                 SizeToolBar(pData, nCount, m_nMRUWidth);
391                         else if (bDynamic && (dwMode & LM_HORZDOCK))
392                                 SizeToolBar(pData, nCount, 32767);
393                         else if (bDynamic && (dwMode & LM_VERTDOCK))
394                                 SizeToolBar(pData, nCount, 0);
395                         else if (bDynamic && (nLength != -1))
396                         {
397                                 CRect rect; rect.SetRectEmpty();
398                                 CalcInsideRect(rect, (dwMode & LM_HORZ));
399                                 BOOL bVert = (dwMode & LM_LENGTHY);
400                                 int nLen = nLength + (bVert ? rect.Height() : rect.Width());
401
402                                 SizeToolBar(pData, nCount, nLen, bVert);
403                         }
404                         else if (bDynamic && (m_dwStyle & CBRS_FLOATING))
405                                 SizeToolBar(pData, nCount, m_nMRUWidth);
406                         else
407                                 SizeToolBar(pData, nCount, (dwMode & LM_HORZ) ? 32767 : 0);
408                 }
409
410                 sizeResult = CalcSize(pData, nCount);
411
412                 if (dwMode & LM_COMMIT)
413                 {
414                         _AFX_CONTROLPOS* pControl = NULL;
415                         int nControlCount = 0;
416                         BOOL bIsDelayed = m_bDelayedButtonLayout;
417                         m_bDelayedButtonLayout = FALSE;
418
419                         for(int i = 0; i < nCount; i++)
420                                 if ((pData[i].fsStyle & TBSTYLE_SEP) && (pData[i].idCommand != 0))
421                                         nControlCount++;
422
423                         if (nControlCount > 0)
424                         {
425                                 pControl = new _AFX_CONTROLPOS[nControlCount];
426                                 nControlCount = 0;
427
428                                 for(int i = 0; i < nCount; i++)
429                                 {
430                                         if ((pData[i].fsStyle & TBSTYLE_SEP) && (pData[i].idCommand != 0))
431                                         {
432                                                 pControl[nControlCount].nIndex = i;
433                                                 pControl[nControlCount].nID = pData[i].idCommand;
434
435                                                 CRect rect;
436                                                 GetItemRect(i, &rect);
437                                                 ClientToScreen(&rect);
438                                                 pControl[nControlCount].rectOldPos = rect;
439
440                                                 nControlCount++;
441                                         }
442                                 }
443                         }
444
445                         if ((m_dwStyle & CBRS_FLOATING) && (m_dwStyle & CBRS_SIZE_DYNAMIC))
446                                 m_nMRUWidth = sizeResult.cx;
447                         for (i = 0; i < nCount; i++)
448                                 SetButton(i, &pData[i]); // **PD** renamed from _SetButton
449
450                         if (nControlCount > 0)
451                         {
452                                 for (int i = 0; i < nControlCount; i++)
453                                 {
454                                         CWnd* pWnd = GetDlgItem(pControl[i].nID);
455                                         if (pWnd != NULL)
456                                         {
457                                                 CRect rect;
458                                                 pWnd->GetWindowRect(&rect);
459                                                 CPoint pt = rect.TopLeft() - pControl[i].rectOldPos.TopLeft();
460                                                 GetItemRect(pControl[i].nIndex, &rect);
461                                                 pt = rect.TopLeft() + pt;
462                                                 pWnd->SetWindowPos(NULL, pt.x, pt.y, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
463                                         }
464                                 }
465                                 delete[] pControl;
466                         }
467                         m_bDelayedButtonLayout = bIsDelayed;
468                 }
469                 delete[] pData;
470         }
471
472         //BLOCK: Adjust Margins
473         {
474                 CRect rect; rect.SetRectEmpty();
475                 CalcInsideRect(rect, (dwMode & LM_HORZ));
476                 sizeResult.cy -= rect.Height();
477                 sizeResult.cx -= rect.Width();
478
479                 CSize size = CControlBar::CalcFixedLayout((dwMode & LM_STRETCH), (dwMode & LM_HORZ));
480                 sizeResult.cx = max(sizeResult.cx, size.cx);
481                 sizeResult.cy = max(sizeResult.cy, size.cy);
482         }
483         return sizeResult;
484 }
485
486 CSize CFixMFCToolBar::CalcFixedLayout(BOOL bStretch, BOOL bHorz)
487 {
488         DWORD dwMode = bStretch ? LM_STRETCH : 0;
489         dwMode |= bHorz ? LM_HORZ : 0;
490
491         return CalcLayout(dwMode);
492 }
493
494 CSize CFixMFCToolBar::CalcDynamicLayout(int nLength, DWORD dwMode)
495 {
496         if ((nLength == -1) && !(dwMode & LM_MRUWIDTH) && !(dwMode & LM_COMMIT) &&
497                 ((dwMode & LM_HORZDOCK) || (dwMode & LM_VERTDOCK)))
498         {
499                 return CalcFixedLayout(dwMode & LM_STRETCH, dwMode & LM_HORZDOCK);
500         }
501         return CalcLayout(dwMode, nLength);
502 }
503
504 /////////////////////////////////////////////////////////////////////////////
505 // CToolBar attribute access
506
507 // **PD** I renamed this from _GetButton.
508 //
509 void CFixMFCToolBar::GetButton(int nIndex, TBBUTTON* pButton) const
510 {
511         CToolBar* pBar = (CToolBar*)this;
512         VERIFY(pBar->SendMessage(TB_GETBUTTON, nIndex, (LPARAM)pButton));
513         // TBSTATE_ENABLED == TBBS_DISABLED so invert it
514         pButton->fsState ^= TBSTATE_ENABLED;
515 }
516
517 // **PD** I renamed this from _SetButton.
518 //
519 void CFixMFCToolBar::SetButton(int nIndex, TBBUTTON* pButton)
520 {
521         // get original button state
522         TBBUTTON button;
523         VERIFY(SendMessage(TB_GETBUTTON, nIndex, (LPARAM)&button));
524
525         // prepare for old/new button comparsion
526         button.bReserved[0] = 0;
527         button.bReserved[1] = 0;
528         // TBSTATE_ENABLED == TBBS_DISABLED so invert it
529         pButton->fsState ^= TBSTATE_ENABLED;
530         pButton->bReserved[0] = 0;
531         pButton->bReserved[1] = 0;
532
533         // nothing to do if they are the same
534         if (memcmp(pButton, &button, sizeof(TBBUTTON)) != 0)
535         {
536                 // don't redraw everything while setting the button
537                 DWORD dwStyle = GetStyle();
538                 ModifyStyle(WS_VISIBLE, 0);
539                 VERIFY(SendMessage(TB_DELETEBUTTON, nIndex, 0));
540                 VERIFY(SendMessage(TB_INSERTBUTTON, nIndex, (LPARAM)pButton));
541                 ModifyStyle(0, dwStyle & WS_VISIBLE);
542
543                 // invalidate appropriate parts
544                 if (((pButton->fsStyle ^ button.fsStyle) & TBSTYLE_SEP) ||
545                         ((pButton->fsStyle & TBSTYLE_SEP) && pButton->iBitmap != button.iBitmap))
546                 {
547                         // changing a separator
548                         Invalidate(FALSE);
549                 }
550                 else
551                 {
552                         // invalidate just the button
553                         CRect rect;
554                         if (SendMessage(TB_GETITEMRECT, nIndex, (LPARAM)&rect))
555                                 InvalidateRect(rect, FALSE);    // don't erase background
556                 }
557         }
558 }