]> git.saurik.com Git - wxWidgets.git/blob - contrib/src/gizmos/splittree.cpp
double click handling is finally correct in the treectrl
[wxWidgets.git] / contrib / src / gizmos / splittree.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: splittree.cpp
3 // Purpose: Classes to achieve a remotely-scrolled tree in a splitter
4 // window that can be scrolled by a scrolled window higher in the
5 // hierarchy
6 // Author: Julian Smart
7 // Modified by:
8 // Created: 8/7/2000
9 // RCS-ID: $Id$
10 // Copyright: (c) Julian Smart
11 // Licence: wxWindows licence
12 /////////////////////////////////////////////////////////////////////////////
13
14 // ============================================================================
15 // declarations
16 // ============================================================================
17
18 // ----------------------------------------------------------------------------
19 // headers
20 // ----------------------------------------------------------------------------
21 #ifdef __GNUG__
22 #pragma implementation "splittree.h"
23 #endif
24
25 // For compilers that support precompilation, includes "wx/wx.h".
26 #include "wx/wxprec.h"
27
28 #ifdef __BORLANDC__
29 #pragma hdrstop
30 #endif
31
32 // for all others, include the necessary headers (this file is usually all you
33 // need because it includes almost all "standard" wxWindows headers)
34 #ifndef WX_PRECOMP
35 #include "wx/wx.h"
36 #endif
37
38 #ifdef __WXMSW__
39 #include <windows.h>
40 #include "wx/msw/winundef.h"
41 #endif
42
43 #include "wx/generic/treectlg.h"
44
45 #include "wx/gizmos/splittree.h"
46 #include <math.h>
47
48 /*
49 * wxRemotelyScrolledTreeCtrl
50 */
51
52 #if USE_GENERIC_TREECTRL
53 IMPLEMENT_CLASS(wxRemotelyScrolledTreeCtrl, wxGenericTreeCtrl)
54 #else
55 IMPLEMENT_CLASS(wxRemotelyScrolledTreeCtrl, wxTreeCtrl)
56 #endif
57
58 #if USE_GENERIC_TREECTRL
59 BEGIN_EVENT_TABLE(wxRemotelyScrolledTreeCtrl, wxGenericTreeCtrl)
60 #else
61 BEGIN_EVENT_TABLE(wxRemotelyScrolledTreeCtrl, wxTreeCtrl)
62 #endif
63 EVT_SIZE(wxRemotelyScrolledTreeCtrl::OnSize)
64 EVT_TREE_ITEM_EXPANDED(-1, wxRemotelyScrolledTreeCtrl::OnExpand)
65 EVT_TREE_ITEM_COLLAPSED(-1, wxRemotelyScrolledTreeCtrl::OnExpand)
66 EVT_SCROLLWIN(wxRemotelyScrolledTreeCtrl::OnScroll)
67 END_EVENT_TABLE()
68
69 wxRemotelyScrolledTreeCtrl::wxRemotelyScrolledTreeCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pt,
70 const wxSize& sz, long style):
71 wxTreeCtrl(parent, id, pt, sz, style)
72 {
73 m_companionWindow = NULL;
74 }
75
76 wxRemotelyScrolledTreeCtrl::~wxRemotelyScrolledTreeCtrl()
77 {
78 }
79
80 void wxRemotelyScrolledTreeCtrl::HideVScrollbar()
81 {
82 #ifdef __WXMSW__
83 if (!IsKindOf(CLASSINFO(wxGenericTreeCtrl)))
84 {
85 ::ShowScrollBar((HWND) GetHWND(), SB_VERT, FALSE);
86 }
87 else
88 #endif
89 {
90 // Implicit in overriding SetScrollbars
91 }
92 }
93
94 // Number of pixels per user unit (0 or -1 for no scrollbar)
95 // Length of virtual canvas in user units
96 // Length of page in user units
97 void wxRemotelyScrolledTreeCtrl::SetScrollbars(int pixelsPerUnitX, int pixelsPerUnitY,
98 int noUnitsX, int noUnitsY,
99 int xPos, int yPos,
100 bool noRefresh)
101 {
102 if (IsKindOf(CLASSINFO(wxGenericTreeCtrl)))
103 {
104 wxGenericTreeCtrl* win = (wxGenericTreeCtrl*) this;
105 win->wxGenericTreeCtrl::SetScrollbars(pixelsPerUnitX, 0, noUnitsX, 0, xPos, 0, noRefresh);
106
107 wxScrolledWindow* scrolledWindow = GetScrolledWindow();
108 if (scrolledWindow)
109 {
110 scrolledWindow->SetScrollbars(0, pixelsPerUnitY, 0, noUnitsY, 0, yPos, noRefresh);
111 }
112 }
113 }
114
115 // In case we're using the generic tree control.
116 int wxRemotelyScrolledTreeCtrl::GetScrollPos(int orient) const
117 {
118 wxScrolledWindow* scrolledWindow = GetScrolledWindow();
119
120 if (IsKindOf(CLASSINFO(wxGenericTreeCtrl)))
121 {
122 wxGenericTreeCtrl* win = (wxGenericTreeCtrl*) this;
123
124 if (orient == wxHORIZONTAL)
125 return win->wxGenericTreeCtrl::GetScrollPos(orient);
126 else
127 {
128 return scrolledWindow->GetScrollPos(orient);
129 }
130 }
131 return 0;
132 }
133
134
135 // In case we're using the generic tree control.
136 // Get the view start
137 void wxRemotelyScrolledTreeCtrl::GetViewStart(int *x, int *y) const
138 {
139 wxScrolledWindow* scrolledWindow = GetScrolledWindow();
140
141 if (IsKindOf(CLASSINFO(wxGenericTreeCtrl)))
142 {
143
144 wxGenericTreeCtrl* win = (wxGenericTreeCtrl*) this;
145 int x1, y1, x2, y2;
146 win->wxGenericTreeCtrl::GetViewStart(& x1, & y1);
147 * x = x1; * y = y1;
148 if (!scrolledWindow)
149 return;
150
151 scrolledWindow->GetViewStart(& x2, & y2);
152 * y = y2;
153 }
154 else
155 {
156 // x is wrong since the horizontal scrollbar is controlled by the
157 // tree control, but we probably don't need it.
158 scrolledWindow->GetViewStart(x, y);
159 }
160 }
161
162 // In case we're using the generic tree control.
163 void wxRemotelyScrolledTreeCtrl::PrepareDC(wxDC& dc)
164 {
165 if (IsKindOf(CLASSINFO(wxGenericTreeCtrl)))
166 {
167 wxScrolledWindow* scrolledWindow = GetScrolledWindow();
168
169 wxGenericTreeCtrl* win = (wxGenericTreeCtrl*) this;
170
171 int startX, startY;
172 GetViewStart(& startX, & startY);
173
174 int xppu1, yppu1, xppu2, yppu2;
175 win->wxGenericTreeCtrl::GetScrollPixelsPerUnit(& xppu1, & yppu1);
176 scrolledWindow->GetScrollPixelsPerUnit(& xppu2, & yppu2);
177
178 dc.SetDeviceOrigin( -startX * xppu1, -startY * yppu2 );
179 // dc.SetUserScale( win->GetScaleX(), win->GetScaleY() );
180 }
181 }
182
183 // Scroll to the given line (in scroll units where each unit is
184 // the height of an item)
185 void wxRemotelyScrolledTreeCtrl::ScrollToLine(int posHoriz, int posVert)
186 {
187 #ifdef __WXMSW__
188 if (!IsKindOf(CLASSINFO(wxGenericTreeCtrl)))
189 {
190 UINT sbCode = SB_THUMBPOSITION;
191 HWND vertScrollBar = 0;
192 MSWDefWindowProc((WXUINT) WM_VSCROLL, MAKELONG(sbCode, posVert), (WXHWND) vertScrollBar);
193 }
194 else
195 #endif
196 {
197 wxGenericTreeCtrl* win = (wxGenericTreeCtrl*) this;
198 win->Refresh();
199 /* Doesn't work yet because scrolling is ignored by Scroll
200 int xppu, yppu;
201 wxScrolledWindow* scrolledWindow = GetScrolledWindow();
202 if (scrolledWindow)
203 {
204 scrolledWindow->GetScrollPixelsPerUnit(& xppu, & yppu);
205 win->Scroll(-1, posVert*yppu);
206 }
207 */
208 }
209 }
210
211 void wxRemotelyScrolledTreeCtrl::OnSize(wxSizeEvent& event)
212 {
213 HideVScrollbar();
214 AdjustRemoteScrollbars();
215 event.Skip();
216 }
217
218 void wxRemotelyScrolledTreeCtrl::OnExpand(wxTreeEvent& event)
219 {
220 AdjustRemoteScrollbars();
221 event.Skip();
222
223 // If we don't have this, we get some bits of lines still remaining
224 if (event.GetEventType() == wxEVT_COMMAND_TREE_ITEM_COLLAPSED)
225 Refresh();
226
227 // Pass on the event
228 if (m_companionWindow)
229 m_companionWindow->GetEventHandler()->ProcessEvent(event);
230 }
231
232 // Adjust the containing wxScrolledWindow's scrollbars appropriately
233 void wxRemotelyScrolledTreeCtrl::AdjustRemoteScrollbars()
234 {
235 if (IsKindOf(CLASSINFO(wxGenericTreeCtrl)))
236 {
237 // This is for the generic tree control.
238 // It calls SetScrollbars which has been overridden
239 // to adjust the parent scrolled window vertical
240 // scrollbar.
241 ((wxGenericTreeCtrl*) this)->AdjustMyScrollbars();
242 return;
243 }
244 else
245 {
246 // This is for the wxMSW tree control
247 wxScrolledWindow* scrolledWindow = GetScrolledWindow();
248 if (scrolledWindow)
249 {
250 wxRect itemRect;
251 if (GetBoundingRect(GetRootItem(), itemRect))
252 {
253 // Actually, the real height seems to be 1 less than reported
254 // (e.g. 16 instead of 16)
255 int itemHeight = itemRect.GetHeight() - 1;
256
257 int w, h;
258 GetClientSize(&w, &h);
259
260 wxRect rect(0, 0, 0, 0);
261 CalcTreeSize(rect);
262
263 double f = ((double) (rect.GetHeight()) / (double) itemHeight) ;
264 int treeViewHeight = (int) ceil(f);
265
266 int scrollPixelsPerLine = itemHeight;
267 int scrollPos = - (itemRect.y / itemHeight);
268
269 scrolledWindow->SetScrollbars(0, scrollPixelsPerLine, 0, treeViewHeight, 0, scrollPos);
270
271 // Ensure that when a scrollbar becomes hidden or visible,
272 // the contained window sizes are right.
273 // Problem: this is called too early (?)
274 wxSizeEvent event(scrolledWindow->GetSize(), scrolledWindow->GetId());
275 scrolledWindow->GetEventHandler()->ProcessEvent(event);
276 }
277 }
278 }
279 }
280
281
282 // Calculate the area that contains both rectangles
283 static wxRect CombineRectangles(const wxRect& rect1, const wxRect& rect2)
284 {
285 wxRect rect;
286
287 int right1 = rect1.GetRight();
288 int bottom1 = rect1.GetBottom();
289 int right2 = rect2.GetRight();
290 int bottom2 = rect2.GetBottom();
291
292 wxPoint topLeft = wxPoint(wxMin(rect1.x, rect2.x), wxMin(rect1.y, rect2.y));
293 wxPoint bottomRight = wxPoint(wxMax(right1, right2), wxMax(bottom1, bottom2));
294
295 rect.x = topLeft.x; rect.y = topLeft.y;
296 rect.SetRight(bottomRight.x);
297 rect.SetBottom(bottomRight.y);
298
299 return rect;
300 }
301
302
303 // Calculate the tree overall size so we can set the scrollbar
304 // correctly
305 void wxRemotelyScrolledTreeCtrl::CalcTreeSize(wxRect& rect)
306 {
307 CalcTreeSize(GetRootItem(), rect);
308 }
309
310 void wxRemotelyScrolledTreeCtrl::CalcTreeSize(const wxTreeItemId& id, wxRect& rect)
311 {
312 // More efficient implementation would be to find the last item (but how?)
313 // Q: is the bounding rect relative to the top of the virtual tree workspace
314 // or the top of the window? How would we convert?
315 wxRect itemSize;
316 if (GetBoundingRect(id, itemSize))
317 {
318 rect = CombineRectangles(rect, itemSize);
319 }
320
321 long cookie;
322 wxTreeItemId childId = GetFirstChild(id, cookie);
323 while (childId != 0)
324 {
325 CalcTreeSize(childId, rect);
326 childId = GetNextChild(childId, cookie);
327 }
328 }
329
330 // Find the scrolled window that contains this control
331 wxScrolledWindow* wxRemotelyScrolledTreeCtrl::GetScrolledWindow() const
332 {
333 wxWindow* parent = wxWindow::GetParent();
334 while (parent)
335 {
336 if (parent->IsKindOf(CLASSINFO(wxScrolledWindow)))
337 return (wxScrolledWindow*) parent;
338 parent = parent->GetParent();
339 }
340 return NULL;
341 }
342
343 void wxRemotelyScrolledTreeCtrl::OnScroll(wxScrollWinEvent& event)
344 {
345 int orient = event.GetOrientation();
346 if (orient == wxHORIZONTAL)
347 {
348 event.Skip();
349 return;
350 }
351 wxScrolledWindow* scrollWin = GetScrolledWindow();
352 if (!scrollWin)
353 return;
354
355 int x, y;
356 scrollWin->GetViewStart(& x, & y);
357
358 ScrollToLine(-1, y);
359 }
360
361 /*
362 * wxTreeCompanionWindow
363 *
364 * A window displaying values associated with tree control items.
365 */
366
367 IMPLEMENT_CLASS(wxTreeCompanionWindow, wxWindow)
368
369 BEGIN_EVENT_TABLE(wxTreeCompanionWindow, wxWindow)
370 EVT_PAINT(wxTreeCompanionWindow::OnPaint)
371 EVT_SCROLLWIN(wxTreeCompanionWindow::OnScroll)
372 EVT_TREE_ITEM_EXPANDED(-1, wxTreeCompanionWindow::OnExpand)
373 EVT_TREE_ITEM_COLLAPSED(-1, wxTreeCompanionWindow::OnExpand)
374 END_EVENT_TABLE()
375
376 wxTreeCompanionWindow::wxTreeCompanionWindow(wxWindow* parent, wxWindowID id,
377 const wxPoint& pos,
378 const wxSize& sz,
379 long style):
380 wxWindow(parent, id, pos, sz, style)
381 {
382 m_treeCtrl = NULL;
383 }
384
385 void wxTreeCompanionWindow::DrawItem(wxDC& dc, wxTreeItemId id, const wxRect& rect)
386 {
387 // TEST CODE
388 #if 1
389 if (m_treeCtrl)
390 {
391 wxString text = m_treeCtrl->GetItemText(id);
392 dc.SetTextForeground(* wxBLACK);
393 dc.SetBackgroundMode(wxTRANSPARENT);
394
395 int textW, textH;
396 dc.GetTextExtent(text, & textW, & textH);
397
398 int x = 5;
399 int y = rect.GetY() + wxMax(0, (rect.GetHeight() - textH) / 2);
400
401 dc.DrawText(text, x, y);
402 }
403 #endif
404 }
405
406 void wxTreeCompanionWindow::OnPaint(wxPaintEvent& event)
407 {
408 wxPaintDC dc(this);
409
410 if (!m_treeCtrl)
411 return;
412
413 wxPen pen(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DLIGHT), 1, wxSOLID);
414 dc.SetPen(pen);
415 dc.SetBrush(* wxTRANSPARENT_BRUSH);
416 wxFont font(wxSystemSettings::GetSystemFont(wxSYS_DEFAULT_GUI_FONT));
417 dc.SetFont(font);
418
419 wxSize clientSize = GetClientSize();
420 wxRect itemRect;
421 int cy=0;
422 wxTreeItemId h, lastH;
423 for(h=m_treeCtrl->GetFirstVisibleItem();h;h=m_treeCtrl->GetNextVisible(h))
424 {
425 if (m_treeCtrl->GetBoundingRect(h, itemRect))
426 {
427 cy = itemRect.GetTop();
428 wxRect drawItemRect(0, cy, clientSize.x, itemRect.GetHeight());
429
430 lastH = h;
431
432 // Draw the actual item
433 DrawItem(dc, h, drawItemRect);
434 dc.DrawLine(0, cy, clientSize.x, cy);
435 }
436 }
437 if (lastH.IsOk() && m_treeCtrl->GetBoundingRect(lastH, itemRect))
438 {
439 cy = itemRect.GetBottom();
440 dc.DrawLine(0, cy, clientSize.x, cy);
441 }
442 }
443
444 void wxTreeCompanionWindow::OnScroll(wxScrollWinEvent& event)
445 {
446 int orient = event.GetOrientation();
447 if (orient == wxHORIZONTAL)
448 {
449 event.Skip();
450 return;
451 }
452 if (!m_treeCtrl)
453 return;
454
455 // TODO: scroll the window physically instead of just refreshing.
456 Refresh(TRUE);
457 }
458
459 void wxTreeCompanionWindow::OnExpand(wxTreeEvent& event)
460 {
461 // TODO: something more optimized than simply refresh the whole
462 // window when the tree is expanded/collapsed. Tricky.
463 Refresh();
464 }
465
466 /*
467 * wxThinSplitterWindow
468 */
469
470 IMPLEMENT_CLASS(wxThinSplitterWindow, wxSplitterWindow)
471
472 BEGIN_EVENT_TABLE(wxThinSplitterWindow, wxSplitterWindow)
473 EVT_SIZE(wxThinSplitterWindow::OnSize)
474 END_EVENT_TABLE()
475
476 wxThinSplitterWindow::wxThinSplitterWindow(wxWindow* parent, wxWindowID id,
477 const wxPoint& pos,
478 const wxSize& sz,
479 long style):
480 wxSplitterWindow(parent, id, pos, sz, style)
481 {
482 }
483
484 void wxThinSplitterWindow::SizeWindows()
485 {
486 // The client size may have changed inbetween
487 // the sizing of the first window and the sizing of
488 // the second. So repeat SizeWindows.
489 wxSplitterWindow::SizeWindows();
490 wxSplitterWindow::SizeWindows();
491 }
492
493 // Tests for x, y over sash
494 bool wxThinSplitterWindow::SashHitTest(int x, int y, int tolerance)
495 {
496 return wxSplitterWindow::SashHitTest(x, y, 4);
497 }
498
499 void wxThinSplitterWindow::DrawSash(wxDC& dc)
500 {
501 if ( m_sashPosition == 0 || !m_windowTwo)
502 return;
503 if (GetWindowStyle() & wxSP_NOSASH)
504 return;
505
506 int w, h;
507 GetClientSize(&w, &h);
508
509 if ( m_splitMode == wxSPLIT_VERTICAL )
510 {
511 dc.SetPen(* m_facePen);
512 dc.SetBrush(* m_faceBrush);
513 int h1 = h-1;
514 int y1 = 0;
515 if ( (GetWindowStyleFlag() & wxSP_BORDER) != wxSP_BORDER && (GetWindowStyleFlag() & wxSP_3DBORDER) != wxSP_3DBORDER )
516 h1 += 1; // Not sure why this is necessary...
517 if ( (GetWindowStyleFlag() & wxSP_3DBORDER) == wxSP_3DBORDER)
518 {
519 y1 = 2; h1 -= 3;
520 }
521 dc.DrawRectangle(m_sashPosition, y1, m_sashSize, h1);
522 }
523 else
524 {
525 dc.SetPen(* m_facePen);
526 dc.SetBrush(* m_faceBrush);
527 int w1 = w-1;
528 int x1 = 0;
529 if ( (GetWindowStyleFlag() & wxSP_BORDER) != wxSP_BORDER && (GetWindowStyleFlag() & wxSP_3DBORDER) != wxSP_3DBORDER )
530 w1 ++;
531 if ( (GetWindowStyleFlag() & wxSP_3DBORDER) == wxSP_3DBORDER)
532 {
533 x1 = 2; w1 -= 3;
534 }
535 dc.DrawRectangle(x1, m_sashPosition, w1, m_sashSize);
536 }
537
538 dc.SetPen(wxNullPen);
539 dc.SetBrush(wxNullBrush);
540 }
541
542 void wxThinSplitterWindow::OnSize(wxSizeEvent& event)
543 {
544 wxSplitterWindow::OnSize(event);
545 }
546
547 /*
548 * wxSplitterScrolledWindow
549 */
550
551 IMPLEMENT_CLASS(wxSplitterScrolledWindow, wxScrolledWindow)
552
553 BEGIN_EVENT_TABLE(wxSplitterScrolledWindow, wxScrolledWindow)
554 EVT_SCROLLWIN(wxSplitterScrolledWindow::OnScroll)
555 EVT_SIZE(wxSplitterScrolledWindow::OnSize)
556 END_EVENT_TABLE()
557
558 wxSplitterScrolledWindow::wxSplitterScrolledWindow(wxWindow* parent, wxWindowID id,
559 const wxPoint& pos,
560 const wxSize& sz,
561 long style):
562 wxScrolledWindow(parent, id, pos, sz, style)
563 {
564 }
565
566 void wxSplitterScrolledWindow::OnSize(wxSizeEvent& event)
567 {
568 wxSize sz = GetClientSize();
569 if (GetChildren().First())
570 {
571 ((wxWindow*) GetChildren().First()->Data())->SetSize(0, 0, sz.x, sz.y);
572 }
573 }
574
575 void wxSplitterScrolledWindow::OnScroll(wxScrollWinEvent& event)
576 {
577 // Ensure that events being propagated back up the window hierarchy
578 // don't cause an infinite loop
579 static bool inOnScroll = FALSE;
580 if (inOnScroll)
581 {
582 event.Skip();
583 return;
584 }
585 inOnScroll = TRUE;
586
587 int orient = event.GetOrientation();
588
589 int nScrollInc = CalcScrollInc(event);
590 if (nScrollInc == 0)
591 {
592 inOnScroll = FALSE;
593 return;
594 }
595
596 if (orient == wxHORIZONTAL)
597 {
598 inOnScroll = FALSE;
599 event.Skip();
600 return;
601 #if 0
602 int newPos = m_xScrollPosition + nScrollInc;
603 SetScrollPos(wxHORIZONTAL, newPos, TRUE );
604 #endif
605 }
606 else
607 {
608 int newPos = m_yScrollPosition + nScrollInc;
609 SetScrollPos(wxVERTICAL, newPos, TRUE );
610 }
611
612 if (orient == wxHORIZONTAL)
613 {
614 m_xScrollPosition += nScrollInc;
615 }
616 else
617 {
618 m_yScrollPosition += nScrollInc;
619 }
620
621 // Find targets in splitter window and send the event to them
622 wxNode* node = GetChildren().First();
623 while (node)
624 {
625 wxWindow* child = (wxWindow*) node->Data();
626 if (child->IsKindOf(CLASSINFO(wxSplitterWindow)))
627 {
628 wxSplitterWindow* splitter = (wxSplitterWindow*) child;
629 if (splitter->GetWindow1())
630 splitter->GetWindow1()->ProcessEvent(event);
631 if (splitter->GetWindow2())
632 splitter->GetWindow2()->ProcessEvent(event);
633 break;
634 }
635 node = node->Next();
636 }
637
638 #ifdef __WXMAC__
639 m_targetWindow->MacUpdateImmediately() ;
640 #endif
641
642 inOnScroll = FALSE;
643 }
644