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