Made wxLayoutAlgorithm more general (copes with nested sash windows)
[wxWidgets.git] / src / generic / sashwin.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: sashwin.cpp
3 // Purpose: wxSashWindow implementation. A sash window has an optional
4 // sash on each edge, allowing it to be dragged. An event
5 // is generated when the sash is released.
6 // Author: Julian Smart
7 // Modified by:
8 // Created: 01/02/97
9 // RCS-ID: $Id$
10 // Copyright: (c) Julian Smart
11 // Licence: wxWindows license
12 /////////////////////////////////////////////////////////////////////////////
13
14 #ifdef __GNUG__
15 #pragma implementation "sashwin.h"
16 #endif
17
18 // For compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20
21 #ifdef __BORLANDC__
22 #pragma hdrstop
23 #endif
24
25 #ifndef WX_PRECOMP
26 #include "wx/wx.h"
27 #endif
28
29 #include <math.h>
30 #include <stdlib.h>
31
32 #include "wx/string.h"
33 #include "wx/dcscreen.h"
34 #include "wx/sashwin.h"
35 #include "wx/laywin.h"
36
37 #if !USE_SHARED_LIBRARY
38 IMPLEMENT_DYNAMIC_CLASS(wxSashWindow, wxWindow)
39 IMPLEMENT_DYNAMIC_CLASS(wxSashEvent, wxCommandEvent)
40
41 BEGIN_EVENT_TABLE(wxSashWindow, wxWindow)
42 EVT_PAINT(wxSashWindow::OnPaint)
43 EVT_SIZE(wxSashWindow::OnSize)
44 EVT_MOUSE_EVENTS(wxSashWindow::OnMouseEvent)
45 END_EVENT_TABLE()
46 #endif
47
48 wxSashWindow::wxSashWindow()
49 {
50 m_draggingEdge = wxSASH_NONE;
51 m_dragMode = wxSASH_DRAG_NONE;
52 m_oldX = 0;
53 m_oldY = 0;
54 m_firstX = 0;
55 m_firstY = 0;
56 m_borderSize = 3 ;
57 m_extraBorderSize = 0;
58 m_sashCursorWE = NULL;
59 m_sashCursorNS = NULL;
60
61 m_minimumPaneSizeX = 0;
62 m_minimumPaneSizeY = 0;
63 m_maximumPaneSizeX = 10000;
64 m_maximumPaneSizeY = 10000;
65 }
66
67 wxSashWindow::wxSashWindow(wxWindow *parent, wxWindowID id, const wxPoint& pos,
68 const wxSize& size, long style, const wxString& name)
69 :wxWindow(parent, id, pos, size, style, name)
70 {
71 m_draggingEdge = wxSASH_NONE;
72 m_dragMode = wxSASH_DRAG_NONE;
73 m_oldX = 0;
74 m_oldY = 0;
75 m_firstX = 0;
76 m_firstY = 0;
77 m_borderSize = 3;
78 m_extraBorderSize = 0;
79 m_minimumPaneSizeX = 0;
80 m_minimumPaneSizeY = 0;
81 m_maximumPaneSizeX = 10000;
82 m_maximumPaneSizeY = 10000;
83 m_sashCursorWE = new wxCursor(wxCURSOR_SIZEWE);
84 m_sashCursorNS = new wxCursor(wxCURSOR_SIZENS);
85
86 // Eventually, we'll respond to colour change messages
87 InitColours();
88 }
89
90 wxSashWindow::~wxSashWindow()
91 {
92 delete m_sashCursorWE;
93 delete m_sashCursorNS;
94 }
95
96 void wxSashWindow::OnPaint(wxPaintEvent& WXUNUSED(event))
97 {
98 wxPaintDC dc(this);
99
100 // if ( m_borderSize > 0 )
101 DrawBorders(dc);
102
103 DrawSashes(dc);
104 }
105
106 void wxSashWindow::OnMouseEvent(wxMouseEvent& event)
107 {
108 long x, y;
109 event.Position(&x, &y);
110
111 wxSashEdgePosition sashHit = SashHitTest(x, y);
112
113 if (event.LeftDown())
114 {
115 if ( sashHit != wxSASH_NONE )
116 {
117 CaptureMouse();
118
119 // Required for X to specify that
120 // that we wish to draw on top of all windows
121 // - and we optimise by specifying the area
122 // for creating the overlap window.
123 // Find the first frame or dialog and use this to specify
124 // the area to draw on.
125 wxWindow* parent = this;
126
127 while (parent && !parent->IsKindOf(CLASSINFO(wxDialog)) &&
128 !parent->IsKindOf(CLASSINFO(wxFrame)))
129 parent = parent->GetParent();
130
131 wxScreenDC::StartDrawingOnTop(parent);
132
133 // We don't say we're dragging yet; we leave that
134 // decision for the Dragging() branch, to ensure
135 // the user has dragged a little bit.
136 m_dragMode = wxSASH_DRAG_LEFT_DOWN;
137 m_draggingEdge = sashHit;
138 m_firstX = x;
139 m_firstY = y;
140 }
141 }
142 else if ( event.LeftUp() && m_dragMode == wxSASH_DRAG_LEFT_DOWN )
143 {
144 // Wasn't a proper drag
145 ReleaseMouse();
146 wxScreenDC::EndDrawingOnTop();
147 m_dragMode = wxSASH_DRAG_NONE;
148 m_draggingEdge = wxSASH_NONE;
149
150 SetCursor(*wxSTANDARD_CURSOR);
151 }
152 else if (event.LeftUp() && m_dragMode == wxSASH_DRAG_DRAGGING)
153 {
154 // We can stop dragging now and see what we've got.
155 m_dragMode = wxSASH_DRAG_NONE;
156 ReleaseMouse();
157 // Erase old tracker
158 DrawSashTracker(m_draggingEdge, m_oldX, m_oldY);
159
160 // End drawing on top (frees the window used for drawing
161 // over the screen)
162 wxScreenDC::EndDrawingOnTop();
163
164 int w, h;
165 GetSize(&w, &h);
166 int xp, yp;
167 GetPosition(&xp, &yp);
168
169 wxSashEdgePosition edge = m_draggingEdge;
170 m_draggingEdge = wxSASH_NONE;
171
172 wxRect dragRect;
173 wxSashDragStatus status = wxSASH_STATUS_OK;
174 switch (edge)
175 {
176 case wxSASH_TOP:
177 {
178 if (y > (yp + h))
179 status = wxSASH_STATUS_OUT_OF_RANGE;
180 int newHeight = (h - y);
181 newHeight=wxMax(newHeight,m_minimumPaneSizeY);
182 newHeight=wxMin(newHeight,m_maximumPaneSizeY);
183 dragRect = wxRect(xp, (yp + h) - newHeight, w, newHeight);
184 break;
185 }
186 case wxSASH_BOTTOM:
187 {
188 if (y < 0)
189 status = wxSASH_STATUS_OUT_OF_RANGE;
190 int newHeight = y;
191 newHeight=wxMax(newHeight,m_minimumPaneSizeY);
192 newHeight=wxMin(newHeight,m_maximumPaneSizeY);
193 dragRect = wxRect(xp, yp, w, newHeight);
194 break;
195 }
196 case wxSASH_LEFT:
197 {
198 if (x > (xp + w))
199 status = wxSASH_STATUS_OUT_OF_RANGE;
200 int newWidth = (w - x);
201 newWidth=wxMax(newWidth,m_minimumPaneSizeX);
202 newWidth=wxMin(newWidth,m_maximumPaneSizeX);
203 dragRect = wxRect((xp + w) - newWidth, yp, newWidth, h);
204 break;
205 }
206 case wxSASH_RIGHT:
207 {
208 if (x < 0)
209 status = wxSASH_STATUS_OUT_OF_RANGE;
210 int newWidth = x;
211 newWidth=wxMax(newWidth,m_minimumPaneSizeX);
212 newWidth=wxMin(newWidth,m_maximumPaneSizeX);
213 dragRect = wxRect(xp, yp, newWidth, h);
214 break;
215 }
216 case wxSASH_NONE:
217 {
218 break;
219 }
220 }
221
222 wxSashEvent event(GetId(), edge);
223 event.SetEventObject(this);
224 event.SetDragStatus(status);
225 event.SetDragRect(dragRect);
226 GetEventHandler()->ProcessEvent(event);
227 }
228 else if (event.Moving() && !event.Dragging())
229 {
230 // Just change the cursor if required
231 if ( sashHit != wxSASH_NONE )
232 {
233 if ( (sashHit == wxSASH_LEFT) || (sashHit == wxSASH_RIGHT) )
234 {
235 SetCursor(*m_sashCursorWE);
236 }
237 else
238 {
239 SetCursor(*m_sashCursorNS);
240 }
241 }
242 else
243 {
244 SetCursor(*wxSTANDARD_CURSOR);
245 }
246 }
247 else if ( event.Dragging() &&
248 ((m_dragMode == wxSASH_DRAG_DRAGGING) || (m_dragMode == wxSASH_DRAG_LEFT_DOWN))
249 )
250 {
251 if ( (m_draggingEdge == wxSASH_LEFT) || (m_draggingEdge == wxSASH_RIGHT) )
252 {
253 SetCursor(*m_sashCursorWE);
254 }
255 else
256 {
257 SetCursor(*m_sashCursorNS);
258 }
259
260 if (m_dragMode == wxSASH_DRAG_LEFT_DOWN)
261 {
262 m_dragMode = wxSASH_DRAG_DRAGGING;
263 DrawSashTracker(m_draggingEdge, x, y);
264 }
265 else
266 {
267 if ( m_dragMode == wxSASH_DRAG_DRAGGING )
268 {
269 // Erase old tracker
270 DrawSashTracker(m_draggingEdge, m_oldX, m_oldY);
271
272 // Draw new one
273 DrawSashTracker(m_draggingEdge, x, y);
274 }
275 }
276 m_oldX = x;
277 m_oldY = y;
278 }
279 else if ( event.LeftDClick() )
280 {
281 // Nothing
282 }
283 else
284 {
285 }
286 }
287
288 void wxSashWindow::OnSize(wxSizeEvent& WXUNUSED(event))
289 {
290 SizeWindows();
291 }
292
293 wxSashEdgePosition wxSashWindow::SashHitTest(int x, int y, int WXUNUSED(tolerance))
294 {
295 int cx, cy;
296 GetClientSize(& cx, & cy);
297
298 int i;
299 for (i = 0; i < 4; i++)
300 {
301 wxSashEdge& edge = m_sashes[i];
302 wxSashEdgePosition position = (wxSashEdgePosition) i ;
303
304 if (edge.m_show)
305 {
306 switch (position)
307 {
308 case wxSASH_TOP:
309 {
310 if (y >= 0 && y <= GetEdgeMargin(position))
311 return wxSASH_TOP;
312 break;
313 }
314 case wxSASH_RIGHT:
315 {
316 if ((x >= cx - GetEdgeMargin(position)) && (x <= cx))
317 return wxSASH_RIGHT;
318 break;
319 }
320 case wxSASH_BOTTOM:
321 {
322 if ((y >= cy - GetEdgeMargin(position)) && (y <= cy))
323 return wxSASH_BOTTOM;
324 break;
325 }
326 case wxSASH_LEFT:
327 {
328 if ((x <= GetEdgeMargin(position)) && (x >= 0))
329 return wxSASH_LEFT;
330 break;
331 }
332 case wxSASH_NONE:
333 {
334 break;
335 }
336 }
337 }
338 }
339 return wxSASH_NONE;
340 }
341
342 // Draw 3D effect borders
343 void wxSashWindow::DrawBorders(wxDC& dc)
344 {
345 int w, h;
346 GetClientSize(&w, &h);
347
348 wxPen mediumShadowPen(m_mediumShadowColour, 1, wxSOLID);
349 wxPen darkShadowPen(m_darkShadowColour, 1, wxSOLID);
350 wxPen lightShadowPen(m_lightShadowColour, 1, wxSOLID);
351 wxPen hilightPen(m_hilightColour, 1, wxSOLID);
352
353 if ( GetWindowStyleFlag() & wxSP_3D )
354 {
355 dc.SetPen(mediumShadowPen);
356 dc.DrawLine(0, 0, w-1, 0);
357 dc.DrawLine(0, 0, 0, h - 1);
358
359 dc.SetPen(darkShadowPen);
360 dc.DrawLine(1, 1, w-2, 1);
361 dc.DrawLine(1, 1, 1, h-2);
362
363 dc.SetPen(hilightPen);
364 dc.DrawLine(0, h-1, w-1, h-1);
365 dc.DrawLine(w-1, 0, w-1, h); // Surely the maximum y pos. should be h - 1.
366 /// Anyway, h is required for MSW.
367
368 dc.SetPen(lightShadowPen);
369 dc.DrawLine(w-2, 1, w-2, h-2); // Right hand side
370 dc.DrawLine(1, h-2, w-1, h-2); // Bottom
371 }
372 else if ( GetWindowStyleFlag() & wxSP_BORDER )
373 {
374 dc.SetBrush(*wxTRANSPARENT_BRUSH);
375 dc.SetPen(*wxBLACK_PEN);
376 dc.DrawRectangle(0, 0, w-1, h-1);
377 }
378
379 dc.SetPen(wxNullPen);
380 dc.SetBrush(wxNullBrush);
381 }
382
383 void wxSashWindow::DrawSashes(wxDC& dc)
384 {
385 int i;
386 for (i = 0; i < 4; i++)
387 if (m_sashes[i].m_show)
388 DrawSash((wxSashEdgePosition) i, dc);
389 }
390
391 // Draw the sash
392 void wxSashWindow::DrawSash(wxSashEdgePosition edge, wxDC& dc)
393 {
394 int w, h;
395 GetClientSize(&w, &h);
396
397 wxPen facePen(m_faceColour, 1, wxSOLID);
398 wxBrush faceBrush(m_faceColour, wxSOLID);
399 wxPen mediumShadowPen(m_mediumShadowColour, 1, wxSOLID);
400 wxPen darkShadowPen(m_darkShadowColour, 1, wxSOLID);
401 wxPen lightShadowPen(m_lightShadowColour, 1, wxSOLID);
402 wxPen hilightPen(m_hilightColour, 1, wxSOLID);
403 wxPen blackPen(wxColour(0, 0, 0), 1, wxSOLID);
404 wxPen whitePen(wxColour(255, 255, 255), 1, wxSOLID);
405
406 if ( edge == wxSASH_LEFT || edge == wxSASH_RIGHT )
407 {
408 int sashPosition = 0;
409 if (edge == wxSASH_LEFT)
410 sashPosition = 0;
411 else
412 sashPosition = w - GetEdgeMargin(edge);
413
414 dc.SetPen(facePen);
415 dc.SetBrush(faceBrush);
416 dc.DrawRectangle(sashPosition, 0, GetEdgeMargin(edge), h);
417
418 if (GetWindowStyleFlag() & wxSW_3D)
419 {
420 if (edge == wxSASH_LEFT)
421 {
422 // Draw a black line on the left to indicate that the
423 // sash is raised
424 dc.SetPen(blackPen);
425 dc.DrawLine(GetEdgeMargin(edge), 0, GetEdgeMargin(edge), h);
426 }
427 else
428 {
429 // Draw a white line on the right to indicate that the
430 // sash is raised
431 dc.SetPen(whitePen);
432 dc.DrawLine(w - GetEdgeMargin(edge), 0, w - GetEdgeMargin(edge), h);
433 }
434 }
435 }
436 else // top or bottom
437 {
438 int sashPosition = 0;
439 if (edge == wxSASH_TOP)
440 sashPosition = 0;
441 else
442 sashPosition = h - GetEdgeMargin(edge);
443
444 dc.SetPen(facePen);
445 dc.SetBrush(faceBrush);
446 dc.DrawRectangle(0, sashPosition, w, GetEdgeMargin(edge));
447
448 if (GetWindowStyleFlag() & wxSW_3D)
449 {
450 if (edge == wxSASH_BOTTOM)
451 {
452 // Draw a black line on the bottom to indicate that the
453 // sash is raised
454 dc.SetPen(blackPen);
455 dc.DrawLine(0, h - GetEdgeMargin(edge), w, h - GetEdgeMargin(edge));
456 }
457 else
458 {
459 // Draw a white line on the top to indicate that the
460 // sash is raised
461 dc.SetPen(whitePen);
462 dc.DrawLine(0, GetEdgeMargin(edge), w, GetEdgeMargin(edge));
463 }
464 }
465 }
466
467 dc.SetPen(wxNullPen);
468 dc.SetBrush(wxNullBrush);
469 }
470
471 // Draw the sash tracker (for whilst moving the sash)
472 void wxSashWindow::DrawSashTracker(wxSashEdgePosition edge, int x, int y)
473 {
474 int w, h;
475 GetClientSize(&w, &h);
476
477 wxScreenDC screenDC;
478 int x1, y1;
479 int x2, y2;
480
481 if ( edge == wxSASH_LEFT || edge == wxSASH_RIGHT )
482 {
483 x1 = x; y1 = 2;
484 x2 = x; y2 = h-2;
485
486 if ( (edge == wxSASH_LEFT) && (x1 > w) )
487 {
488 x1 = w; x2 = w;
489 }
490 else if ( (edge == wxSASH_RIGHT) && (x1 < 0) )
491 {
492 x1 = 0; x2 = 0;
493 }
494 }
495 else
496 {
497 x1 = 2; y1 = y;
498 x2 = w-2; y2 = y;
499
500 if ( (edge == wxSASH_TOP) && (y1 > h) )
501 {
502 y1 = h;
503 y2 = h;
504 }
505 else if ( (edge == wxSASH_BOTTOM) && (y1 < 0) )
506 {
507 y1 = 0;
508 y2 = 0;
509 }
510 }
511
512 ClientToScreen(&x1, &y1);
513 ClientToScreen(&x2, &y2);
514
515 wxPen sashTrackerPen(*wxBLACK, 2, wxSOLID);
516
517 screenDC.SetLogicalFunction(wxXOR);
518 screenDC.SetPen(sashTrackerPen);
519 screenDC.SetBrush(*wxTRANSPARENT_BRUSH);
520
521 screenDC.DrawLine(x1, y1, x2, y2);
522
523 screenDC.SetLogicalFunction(wxCOPY);
524
525 screenDC.SetPen(wxNullPen);
526 screenDC.SetBrush(wxNullBrush);
527 }
528
529 // Position and size subwindows.
530 // Note that the border size applies to each subwindow, not
531 // including the edges next to the sash.
532 void wxSashWindow::SizeWindows()
533 {
534 int cw, ch;
535 GetClientSize(&cw, &ch);
536
537 if (GetChildren().Number() == 1)
538 {
539 wxWindow* child = (wxWindow*) (GetChildren().First()->Data());
540
541 int x = 0;
542 int y = 0;
543 int width = cw;
544 int height = ch;
545
546 // Top
547 if (m_sashes[0].m_show)
548 {
549 y = m_borderSize;
550 height -= m_borderSize;
551 }
552 y += m_extraBorderSize;
553
554 // Left
555 if (m_sashes[3].m_show)
556 {
557 x = m_borderSize;
558 width -= m_borderSize;
559 }
560 x += m_extraBorderSize;
561
562 // Right
563 if (m_sashes[1].m_show)
564 {
565 width -= m_borderSize;
566 }
567 width -= 2*m_extraBorderSize;
568
569 // Bottom
570 if (m_sashes[2].m_show)
571 {
572 height -= m_borderSize;
573 }
574 height -= 2*m_extraBorderSize;
575
576 child->SetSize(x, y, width, height);
577 }
578 else if (GetChildren().Number() > 1)
579 {
580 // Perhaps multiple children are themselves sash windows.
581 // TODO: this doesn't really work because the subwindows sizes/positions
582 // must be set to leave a gap for the parent's sash (hit-test and decorations).
583 // Perhaps we can allow for this within LayoutWindow, testing whether the parent
584 // is a sash window, and if so, allowing some space for the edges.
585 wxLayoutAlgorithm layout;
586 layout.LayoutWindow(this);
587 }
588
589 wxClientDC dc(this);
590 DrawBorders(dc);
591 DrawSashes(dc);
592 }
593
594 // Initialize colours
595 void wxSashWindow::InitColours()
596 {
597 // Shadow colours
598 #if defined(__WIN95__)
599 m_faceColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DFACE);
600 m_mediumShadowColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DSHADOW);
601 m_darkShadowColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DDKSHADOW);
602 m_lightShadowColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DLIGHT);
603 m_hilightColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DHILIGHT);
604 #else
605 m_faceColour = *(wxTheColourDatabase->FindColour("LIGHT GREY"));
606 m_mediumShadowColour = *(wxTheColourDatabase->FindColour("GREY"));
607 m_darkShadowColour = *(wxTheColourDatabase->FindColour("BLACK"));
608 m_lightShadowColour = *(wxTheColourDatabase->FindColour("LIGHT GREY"));
609 m_hilightColour = *(wxTheColourDatabase->FindColour("WHITE"));
610 #endif
611 }
612
613 void wxSashWindow::SetSashVisible(wxSashEdgePosition edge, bool sash)
614 {
615 m_sashes[edge].m_show = sash;
616 if (sash)
617 m_sashes[edge].m_margin = m_borderSize;
618 else
619 m_sashes[edge].m_margin = 0;
620 }
621