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