]> git.saurik.com Git - wxWidgets.git/blobdiff - src/msw/treectrl.cpp
handle correctly never/always shown scrollbars in GetClientSize()
[wxWidgets.git] / src / msw / treectrl.cpp
index 631b4ebce3435c605e48cd9be4fbabf139c6a78a..7aa4079cf659ceb0082b46044e91e83311e5ec3a 100644 (file)
@@ -72,19 +72,38 @@ typedef struct tagNMTVITEMCHANGE
 #endif
 
 
-// this global variable is used on vista systems for preventing unwanted
-// item state changes in the vista tree control.  It is only used in
+// this helper class is used on vista systems for preventing unwanted
+// item state changes in the vista tree control.  It is only effective in
 // multi-select mode on vista systems.
 
-static HTREEITEM gs_unlockItem = NULL;
+// The vista tree control includes some new code that originally broke the
+// multi-selection tree, causing seemingly spurious item selection state changes
+// during Shift or Ctrl-click item selection. (To witness the original broken
+// behavior, simply make IsLocked() below always return false). This problem was
+// solved by using the following class to 'unlock' an item's selection state.
 
 class TreeItemUnlocker
 {
 public:
-    TreeItemUnlocker(HTREEITEM item) { gs_unlockItem = item; }
-    ~TreeItemUnlocker() { gs_unlockItem = NULL; }
+    // unlock a single item
+    TreeItemUnlocker(HTREEITEM item) { ms_unlockedItem = item; }
+
+    // unlock all items, don't use unless absolutely necessary
+    TreeItemUnlocker() { ms_unlockedItem = (HTREEITEM)-1; }
+
+    // lock everything back
+    ~TreeItemUnlocker() { ms_unlockedItem = NULL; }
+
+
+    // check if the item state is currently locked
+    static bool IsLocked(HTREEITEM item)
+        { return ms_unlockedItem != (HTREEITEM)-1 && item != ms_unlockedItem; }
+
+private:
+    static HTREEITEM ms_unlockedItem;
 };
 
+HTREEITEM TreeItemUnlocker::ms_unlockedItem = NULL;
 
 // ----------------------------------------------------------------------------
 // private functions
@@ -732,58 +751,6 @@ bool wxTreeCtrl::Create(wxWindow *parent,
     SetForegroundColour(wxWindow::GetParent()->GetForegroundColour());
 #endif
 
-
-    // VZ: this is some experimental code which may be used to get the
-    //     TVS_CHECKBOXES style functionality for comctl32.dll < 4.71.
-    //     AFAIK, the standard DLL does about the same thing anyhow.
-#if 0
-    if ( m_windowStyle & wxTR_MULTIPLE )
-    {
-        wxBitmap bmp;
-
-        // create the DC compatible with the current screen
-        HDC hdcMem = CreateCompatibleDC(NULL);
-
-        // create a mono bitmap of the standard size
-        int x = ::GetSystemMetrics(SM_CXMENUCHECK);
-        int y = ::GetSystemMetrics(SM_CYMENUCHECK);
-        wxImageList imagelistCheckboxes(x, y, false, 2);
-        HBITMAP hbmpCheck = CreateBitmap(x, y,   // bitmap size
-                                         1,      // # of color planes
-                                         1,      // # bits needed for one pixel
-                                         0);     // array containing colour data
-        SelectObject(hdcMem, hbmpCheck);
-
-        // then draw a check mark into it
-        RECT rect = { 0, 0, x, y };
-        if ( !::DrawFrameControl(hdcMem, &rect,
-                                 DFC_BUTTON,
-                                 DFCS_BUTTONCHECK | DFCS_CHECKED) )
-        {
-            wxLogLastError(wxT("DrawFrameControl(check)"));
-        }
-
-        bmp.SetHBITMAP((WXHBITMAP)hbmpCheck);
-        imagelistCheckboxes.Add(bmp);
-
-        if ( !::DrawFrameControl(hdcMem, &rect,
-                                 DFC_BUTTON,
-                                 DFCS_BUTTONCHECK) )
-        {
-            wxLogLastError(wxT("DrawFrameControl(uncheck)"));
-        }
-
-        bmp.SetHBITMAP((WXHBITMAP)hbmpCheck);
-        imagelistCheckboxes.Add(bmp);
-
-        // clean up
-        ::DeleteDC(hdcMem);
-
-        // set the imagelist
-        SetStateImageList(&imagelistCheckboxes);
-    }
-#endif // 0
-
     wxSetCCUnicodeFormat(GetHwnd());
 
     return true;
@@ -1425,33 +1392,6 @@ wxTreeItemId wxTreeCtrl::GetPrevVisible(const wxTreeItemId& item) const
 // multiple selections emulation
 // ----------------------------------------------------------------------------
 
-bool wxTreeCtrl::IsItemChecked(const wxTreeItemId& item) const
-{
-    wxCHECK_MSG( item.IsOk(), false, wxT("invalid tree item") );
-
-    // receive the desired information.
-    wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_STATEIMAGEMASK);
-    DoGetItem(&tvItem);
-
-    // state image indices are 1 based
-    return ((tvItem.state >> 12) - 1) == 1;
-}
-
-void wxTreeCtrl::SetItemCheck(const wxTreeItemId& item, bool check)
-{
-    wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
-
-    // receive the desired information.
-    wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_STATEIMAGEMASK);
-
-    DoGetItem(&tvItem);
-
-    // state images are one-based
-    tvItem.state = (check ? 2 : 1) << 12;
-
-    DoSetItem(&tvItem);
-}
-
 size_t wxTreeCtrl::GetSelections(wxArrayTreeItemIds& selections) const
 {
     TraverseSelections selector(this, selections);
@@ -1512,12 +1452,27 @@ wxTreeItemId wxTreeCtrl::DoInsertAfter(const wxTreeItemId& parent,
     tvIns.item.lParam = (LPARAM)param;
     tvIns.item.mask = mask;
 
+    // don't use the hack below for the children of hidden root: this results
+    // in a crash inside comctl32.dll when we call TreeView_GetItemRect()
+    const bool firstChild = !IsHiddenRoot(parent) &&
+                                !TreeView_GetChild(GetHwnd(), HITEM(parent));
+
     HTREEITEM id = TreeView_InsertItem(GetHwnd(), &tvIns);
     if ( id == 0 )
     {
         wxLogLastError(wxT("TreeView_InsertItem"));
     }
 
+    // apparently some Windows versions (2000 and XP are reported to do this)
+    // sometimes don't refresh the tree after adding the first child and so we
+    // need this to make the "[+]" appear
+    if ( firstChild )
+    {
+        RECT rect;
+        TreeView_GetItemRect(GetHwnd(), HITEM(parent), &rect, FALSE);
+        ::InvalidateRect(GetHwnd(), &rect, FALSE);
+    }
+
     // associate the application tree item with Win32 tree item handle
     param->SetItem(id);
 
@@ -1586,6 +1541,10 @@ wxTreeItemId wxTreeCtrl::DoInsertItem(const wxTreeItemId& parent,
 
 void wxTreeCtrl::Delete(const wxTreeItemId& item)
 {
+    // unlock tree selections on vista, without this the
+    // tree ctrl will eventually crash after item deletion
+    TreeItemUnlocker unlock_all;
+
     if ( !TreeView_DeleteItem(GetHwnd(), HITEM(item)) )
     {
         wxLogLastError(wxT("TreeView_DeleteItem"));
@@ -1595,6 +1554,9 @@ void wxTreeCtrl::Delete(const wxTreeItemId& item)
 // delete all children (but don't delete the item itself)
 void wxTreeCtrl::DeleteChildren(const wxTreeItemId& item)
 {
+    // unlock tree selections on vista for the duration of this call
+    TreeItemUnlocker unlock_all;
+
     wxTreeItemIdValue cookie;
 
     wxArrayTreeItemIds children;
@@ -1618,6 +1580,9 @@ void wxTreeCtrl::DeleteChildren(const wxTreeItemId& item)
 
 void wxTreeCtrl::DeleteAllItems()
 {
+    // unlock tree selections on vista for the duration of this call
+    TreeItemUnlocker unlock_all;
+
     // delete the "virtual" root item.
     if ( GET_VIRTUAL_ROOT() )
     {
@@ -1665,7 +1630,7 @@ void wxTreeCtrl::DoExpand(const wxTreeItemId& item, int flag)
                                                            : IDX_COLLAPSE]
                                          [IDX_DONE],
                            this, item);
-        (void)GetEventHandler()->ProcessEvent(event);
+        (void)HandleWindowEvent(event);
     }
     //else: change didn't took place, so do nothing at all
 }
@@ -1727,7 +1692,7 @@ void wxTreeCtrl::SelectItem(const wxTreeItemId& item, bool select)
                   _T("SelectItem(false) works only for multiselect") );
 
     wxTreeEvent event(wxEVT_COMMAND_TREE_SEL_CHANGING, this, item);
-    if ( !GetEventHandler()->ProcessEvent(event) || event.IsAllowed() )
+    if ( !HandleWindowEvent(event) || event.IsAllowed() )
     {
         if ( HasFlag(wxTR_MULTIPLE) )
         {
@@ -1748,7 +1713,7 @@ void wxTreeCtrl::SelectItem(const wxTreeItemId& item, bool select)
         }
 
         event.SetEventType(wxEVT_COMMAND_TREE_SEL_CHANGED);
-        (void)GetEventHandler()->ProcessEvent(event);
+        (void)HandleWindowEvent(event);
     }
     //else: program vetoed the change
 }
@@ -1948,7 +1913,9 @@ bool wxTreeCtrl::MSWShouldPreProcessMessage(WXMSG* msg)
 {
     if ( msg->message == WM_KEYDOWN )
     {
-        if ( msg->wParam == VK_RETURN )
+        // Only eat VK_RETURN if not being used by the application in
+        // conjunction with modifiers
+        if ( (msg->wParam == VK_RETURN) && !wxIsAnyModifierDown() )
         {
             // we need VK_RETURN to generate wxEVT_COMMAND_TREE_ITEM_ACTIVATED
             return false;
@@ -2039,7 +2006,7 @@ WXLRESULT wxTreeCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lPara
 
         event.m_pointDrag = pt;
 
-        if ( GetEventHandler()->ProcessEvent(event) )
+        if ( HandleWindowEvent(event) )
             processed = true;
         //else: continue with generating wxEVT_CONTEXT_MENU in base class code
     }
@@ -2147,6 +2114,38 @@ WXLRESULT wxTreeCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lPara
                 }
                 break;
 
+            case WM_RBUTTONDOWN:
+                // default handler removes the highlight from the currently
+                // focused item when right mouse button is pressed on another
+                // one but keeps the remaining items highlighted, which is
+                // confusing, so override this default behaviour for tree with
+                // multiple selections
+                if ( isMultiple )
+                {
+                    if ( !IsItemSelected(GetHwnd(), htItem) )
+                    {
+                        UnselectAll();
+                        SelectItem(htItem);
+                        ::SetFocus(GetHwnd(), htItem);
+                    }
+
+                    // fire EVT_RIGHT_DOWN
+                    HandleMouseEvent(nMsg, x, y, wParam);
+
+                    // send NM_RCLICK
+                    NMHDR nmhdr;
+                    nmhdr.hwndFrom = GetHwnd();
+                    nmhdr.idFrom = ::GetWindowLong(GetHwnd(), GWL_ID);
+                    nmhdr.code = NM_RCLICK;
+                    ::SendMessage(::GetParent(GetHwnd()), WM_NOTIFY,
+                                  nmhdr.idFrom, (LPARAM)&nmhdr);
+
+                    // prevent tree control default processing, as we've
+                    // already done everything
+                    processed = true;
+                }
+                break;
+
             case WM_MOUSEMOVE:
 #ifndef __WXWINCE__
                 if ( m_htClickedItem )
@@ -2246,7 +2245,7 @@ WXLRESULT wxTreeCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lPara
                     wxTreeEvent event(wxEVT_COMMAND_TREE_END_DRAG, this, htItem);
                     event.m_pointDrag = wxPoint(x, y);
 
-                    (void)GetEventHandler()->ProcessEvent(event);
+                    (void)HandleWindowEvent(event);
 
                     // if we don't do it, the tree seems to think that 2 items
                     // are selected simultaneously which is quite weird
@@ -2571,8 +2570,7 @@ bool wxTreeCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
                 // fabricate the lParam and wParam parameters sufficiently
                 // similar to the ones from a "real" WM_KEYDOWN so that
                 // CreateKeyEvent() works correctly
-                const bool isAltDown = ::GetKeyState(VK_MENU) < 0;
-                WXLPARAM lParam = (isAltDown ? KF_ALTDOWN : 0) << 16;
+                WXLPARAM lParam = (wxIsAltDown() ? KF_ALTDOWN : 0) << 16;
 
                 WXWPARAM wParam = info->wVKey;
 
@@ -2590,7 +2588,7 @@ bool wxTreeCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
                                                 wParam);
 
                 // a separate event for Space/Return
-                if ( !wxIsCtrlDown() && !wxIsShiftDown() && !isAltDown &&
+                if ( !wxIsAnyModifierDown() &&
                      ((info->wVKey == VK_SPACE) || (info->wVKey == VK_RETURN)) )
                 {
                    wxTreeItemId item;
@@ -2599,7 +2597,7 @@ bool wxTreeCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
 
                    wxTreeEvent event2(wxEVT_COMMAND_TREE_ITEM_ACTIVATED,
                                         this, item);
-                   (void)GetEventHandler()->ProcessEvent(event2);
+                   (void)HandleWindowEvent(event2);
                 }
             }
             break;
@@ -2608,11 +2606,12 @@ bool wxTreeCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
         // Vista's tree control has introduced some problems with our
         // multi-selection tree.  When TreeView_SelectItem() is called,
         // the wrong items are deselected.
-        
+
         // Fortunately, Vista provides a new notification, TVN_ITEMCHANGING
         // that can be used to regulate this incorrect behavior.  The
         // following messages will allow only the unlocked item's selection
         // state to change
+
         case TVN_ITEMCHANGINGA:
         case TVN_ITEMCHANGINGW:
             {
@@ -2621,11 +2620,11 @@ bool wxTreeCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
                 {
                     // get info about the item about to be changed
                     NMTVITEMCHANGE* info = (NMTVITEMCHANGE*)lParam;
-                    if (info->hItem != gs_unlockItem)
+                    if (TreeItemUnlocker::IsLocked(info->hItem))
                     {
                         // item's state is locked, don't allow the change
                         // returning 1 will disallow the change
-                        *result = 1; 
+                        *result = 1;
                         return true;
                     }
                 }
@@ -2692,6 +2691,43 @@ bool wxTreeCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
                         // notify us before painting each item
                         *result = m_hasAnyAttr ? CDRF_NOTIFYITEMDRAW
                                                : CDRF_DODEFAULT;
+
+                        // windows in TreeCtrl use one-based index for item state images,
+                        // 0 indexed image is not being used, we're using zero-based index,
+                        // so we have to add temp image (of zero index) to state image list
+                        // before we draw any item, then after items are drawn we have to
+                        // delete it (in POSTPAINT notify)
+                        if (m_imageListState && m_imageListState->GetImageCount() > 0)
+                        {
+                            #define hImageList (HIMAGELIST)m_imageListState->GetHIMAGELIST()
+
+                            // add temporary image
+                            int width, height;
+                            m_imageListState->GetSize(0, width, height);
+
+                            HBITMAP hbmpTemp = ::CreateBitmap(width, height, 1, 1, NULL);
+                            int index = ::ImageList_Add(hImageList, hbmpTemp, hbmpTemp);
+                            ::DeleteObject(hbmpTemp);
+
+                            if ( index != -1 )
+                            {
+                                // move images to right
+                                for ( int i = index; i > 0; i-- )
+                                    ::ImageList_Copy(hImageList, i, hImageList, i-1, 0);
+
+                                // we must remove the image in POSTPAINT notify
+                                *result |= CDRF_NOTIFYPOSTPAINT;
+                            }
+
+                            #undef hImageList
+                        }
+                        break;
+
+                    case CDDS_POSTPAINT:
+                        // we are deleting temp image of 0 index, which was
+                        // added before items were drawn (in PREPAINT notify)
+                        if (m_imageListState && m_imageListState->GetImageCount() > 0)
+                            m_imageListState->Remove(0);
                         break;
 
                     case CDDS_ITEMPREPAINT:
@@ -2816,17 +2852,18 @@ bool wxTreeCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
     if ( event.m_item.IsOk() )
         event.SetClientObject(GetItemData(event.m_item));
 
-    bool processed = GetEventHandler()->ProcessEvent(event);
+    bool processed = HandleWindowEvent(event);
 
     // post processing
     switch ( hdr->code )
     {
         case NM_DBLCLK:
-            // we translate NM_DBLCLK into ACTIVATED event, so don't interpret
-            // the return code of this event handler as the return value for
-            // NM_DBLCLK - otherwise, double clicking the item to toggle its
-            // expanded status would never work
-            *result = false;
+            // we translate NM_DBLCLK into ACTIVATED event and if the user
+            // handled the activation of the item we shouldn't proceed with
+            // also using the same double click for toggling the item expanded
+            // state -- but OTOH do let the user to expand/collapse the item by
+            // double clicking on it if the activation is not handled specially
+            *result = processed;
             break;
 
         case NM_RCLICK:
@@ -3027,40 +3064,29 @@ bool wxTreeCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
 // why do they define INDEXTOSTATEIMAGEMASK but not the inverse?
 #define STATEIMAGEMASKTOINDEX(state) (((state) & TVIS_STATEIMAGEMASK) >> 12)
 
-void wxTreeCtrl::SetState(const wxTreeItemId& node, int state)
+int wxTreeCtrl::DoGetItemState(const wxTreeItemId& item) const
 {
-    TV_ITEM tvi;
-    tvi.hItem = (HTREEITEM)node.m_pItem;
-    tvi.mask = TVIF_STATE;
-    tvi.stateMask = TVIS_STATEIMAGEMASK;
-
-    // Select the specified state, or -1 == cycle to the next one.
-    if ( state == -1 )
-    {
-        TreeView_GetItem(GetHwnd(), &tvi);
-
-        state = STATEIMAGEMASKTOINDEX(tvi.state) + 1;
-        if ( state == m_imageListState->GetImageCount() )
-            state = 1;
-    }
+    wxCHECK_MSG( item.IsOk(), wxTREE_ITEMSTATE_NONE, wxT("invalid tree item") );
 
-    wxCHECK_RET( state < m_imageListState->GetImageCount(),
-                 _T("wxTreeCtrl::SetState(): item index out of bounds") );
-
-    tvi.state = INDEXTOSTATEIMAGEMASK(state);
+    // receive the desired information
+    wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_STATEIMAGEMASK);
+    DoGetItem(&tvItem);
 
-    TreeView_SetItem(GetHwnd(), &tvi);
+    // state images are one-based
+    return STATEIMAGEMASKTOINDEX(tvItem.state) - 1;
 }
 
-int wxTreeCtrl::GetState(const wxTreeItemId& node)
+void wxTreeCtrl::DoSetItemState(const wxTreeItemId& item, int state)
 {
-    TV_ITEM tvi;
-    tvi.hItem = (HTREEITEM)node.m_pItem;
-    tvi.mask = TVIF_STATE;
-    tvi.stateMask = TVIS_STATEIMAGEMASK;
-    TreeView_GetItem(GetHwnd(), &tvi);
+    wxCHECK_RET( item.IsOk(), wxT("invalid tree item") );
 
-    return STATEIMAGEMASKTOINDEX(tvi.state);
+    wxTreeViewItem tvItem(item, TVIF_STATE, TVIS_STATEIMAGEMASK);
+
+    // state images are one-based
+    // 0 if no state image display (wxTREE_ITEMSTATE_NONE = -1)
+    tvItem.state = INDEXTOSTATEIMAGEMASK(state + 1);
+
+    DoSetItem(&tvItem);
 }
 
 #endif // wxUSE_TREECTRL