Two optical corrections
[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 // reset the cursor
114 #ifdef __WXMOTIF__
115 SetCursor(* wxSTANDARD_CURSOR);
116 #endif
117 #ifdef __WXMSW__
118 SetCursor(wxCursor());
119 #endif
120
121 if (event.LeftDown())
122 {
123 if ( sashHit != wxSASH_NONE )
124 {
125 CaptureMouse();
126
127 // Required for X to specify that
128 // that we wish to draw on top of all windows
129 // - and we optimise by specifying the area
130 // for creating the overlap window.
131 // Find the first frame or dialog and use this to specify
132 // the area to draw on.
133 wxWindow* parent = this;
134
135 while (parent && !parent->IsKindOf(CLASSINFO(wxDialog)) &&
136 !parent->IsKindOf(CLASSINFO(wxFrame)))
137 parent = parent->GetParent();
138
139 wxScreenDC::StartDrawingOnTop(parent);
140
141 // We don't say we're dragging yet; we leave that
142 // decision for the Dragging() branch, to ensure
143 // the user has dragged a little bit.
144 m_dragMode = wxSASH_DRAG_LEFT_DOWN;
145 m_draggingEdge = sashHit;
146 m_firstX = x;
147 m_firstY = y;
148 }
149 }
150 else if ( event.LeftUp() && m_dragMode == wxSASH_DRAG_LEFT_DOWN )
151 {
152 // Wasn't a proper drag
153 ReleaseMouse();
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 ReleaseMouse();
163 // Erase old tracker
164 DrawSashTracker(m_draggingEdge, m_oldX, m_oldY);
165
166 // End drawing on top (frees the window used for drawing
167 // over the screen)
168 wxScreenDC::EndDrawingOnTop();
169
170 int w, h;
171 GetSize(&w, &h);
172 int xp, yp;
173 GetPosition(&xp, &yp);
174
175 wxSashEdgePosition edge = m_draggingEdge;
176 m_draggingEdge = wxSASH_NONE;
177
178 wxRect dragRect;
179 wxSashDragStatus status = wxSASH_STATUS_OK;
180 switch (edge)
181 {
182 case wxSASH_TOP:
183 {
184 if (y > (yp + h))
185 status = wxSASH_STATUS_OUT_OF_RANGE;
186 int newHeight = (h - y);
187 newHeight=wxMax(newHeight,m_minimumPaneSizeY);
188 newHeight=wxMin(newHeight,m_maximumPaneSizeY);
189 dragRect = wxRect(xp, (yp + h) - newHeight, w, newHeight);
190 break;
191 }
192 case wxSASH_BOTTOM:
193 {
194 if (y < 0)
195 status = wxSASH_STATUS_OUT_OF_RANGE;
196 int newHeight = y;
197 newHeight=wxMax(newHeight,m_minimumPaneSizeY);
198 newHeight=wxMin(newHeight,m_maximumPaneSizeY);
199 dragRect = wxRect(xp, yp, w, newHeight);
200 break;
201 }
202 case wxSASH_LEFT:
203 {
204 if (x > (xp + w))
205 status = wxSASH_STATUS_OUT_OF_RANGE;
206 int newWidth = (w - x);
207 newWidth=wxMax(newWidth,m_minimumPaneSizeX);
208 newWidth=wxMin(newWidth,m_maximumPaneSizeX);
209 dragRect = wxRect((xp + w) - newWidth, yp, newWidth, h);
210 break;
211 }
212 case wxSASH_RIGHT:
213 {
214 if (x < 0)
215 status = wxSASH_STATUS_OUT_OF_RANGE;
216 int newWidth = x;
217 newWidth=wxMax(newWidth,m_minimumPaneSizeX);
218 newWidth=wxMin(newWidth,m_maximumPaneSizeX);
219 dragRect = wxRect(xp, yp, newWidth, h);
220 break;
221 }
222 case wxSASH_NONE:
223 {
224 break;
225 }
226 }
227
228 wxSashEvent event(GetId(), edge);
229 event.SetEventObject(this);
230 event.SetDragStatus(status);
231 event.SetDragRect(dragRect);
232 GetEventHandler()->ProcessEvent(event);
233 }
234 else if (event.Moving() && !event.Dragging())
235 {
236 // Just change the cursor if required
237 if ( sashHit != wxSASH_NONE )
238 {
239 if ( (sashHit == wxSASH_LEFT) || (sashHit == wxSASH_RIGHT) )
240 {
241 SetCursor(*m_sashCursorWE);
242 }
243 else
244 {
245 SetCursor(*m_sashCursorNS);
246 }
247 }
248 }
249 else if ( event.Dragging() &&
250 ((m_dragMode == wxSASH_DRAG_DRAGGING) || (m_dragMode == wxSASH_DRAG_LEFT_DOWN))
251 )
252 {
253 if ( (m_draggingEdge == wxSASH_LEFT) || (m_draggingEdge == wxSASH_RIGHT) )
254 {
255 SetCursor(*m_sashCursorWE);
256 }
257 else
258 {
259 SetCursor(*m_sashCursorNS);
260 }
261
262 if (m_dragMode == wxSASH_DRAG_LEFT_DOWN)
263 {
264 m_dragMode = wxSASH_DRAG_DRAGGING;
265 DrawSashTracker(m_draggingEdge, x, y);
266 }
267 else
268 {
269 if ( m_dragMode == wxSASH_DRAG_DRAGGING )
270 {
271 // Erase old tracker
272 DrawSashTracker(m_draggingEdge, m_oldX, m_oldY);
273
274 // Draw new one
275 DrawSashTracker(m_draggingEdge, x, y);
276 }
277 }
278 m_oldX = x;
279 m_oldY = y;
280 }
281 else if ( event.LeftDClick() )
282 {
283 // Nothing
284 }
285 else
286 {
287 }
288 }
289
290 void wxSashWindow::OnSize(wxSizeEvent& WXUNUSED(event))
291 {
292 SizeWindows();
293 }
294
295 wxSashEdgePosition wxSashWindow::SashHitTest(int x, int y, int WXUNUSED(tolerance))
296 {
297 int cx, cy;
298 GetClientSize(& cx, & cy);
299
300 int i;
301 for (i = 0; i < 4; i++)
302 {
303 wxSashEdge& edge = m_sashes[i];
304 wxSashEdgePosition position = (wxSashEdgePosition) i ;
305
306 if (edge.m_show)
307 {
308 switch (position)
309 {
310 case wxSASH_TOP:
311 {
312 if (y >= 0 && y <= GetEdgeMargin(position))
313 return wxSASH_TOP;
314 break;
315 }
316 case wxSASH_RIGHT:
317 {
318 if ((x >= cx - GetEdgeMargin(position)) && (x <= cx))
319 return wxSASH_RIGHT;
320 break;
321 }
322 case wxSASH_BOTTOM:
323 {
324 if ((y >= cy - GetEdgeMargin(position)) && (y <= cy))
325 return wxSASH_BOTTOM;
326 break;
327 }
328 case wxSASH_LEFT:
329 {
330 if ((x <= GetEdgeMargin(position)) && (x >= 0))
331 return wxSASH_LEFT;
332 break;
333 }
334 case wxSASH_NONE:
335 {
336 break;
337 }
338 }
339 }
340 }
341 return wxSASH_NONE;
342 }
343
344 // Draw 3D effect borders
345 void wxSashWindow::DrawBorders(wxDC& dc)
346 {
347 int w, h;
348 GetClientSize(&w, &h);
349
350 wxPen mediumShadowPen(m_mediumShadowColour, 1, wxSOLID);
351 wxPen darkShadowPen(m_darkShadowColour, 1, wxSOLID);
352 wxPen lightShadowPen(m_lightShadowColour, 1, wxSOLID);
353 wxPen hilightPen(m_hilightColour, 1, wxSOLID);
354
355 if ( GetWindowStyleFlag() & wxSP_3D )
356 {
357 dc.SetPen(mediumShadowPen);
358 dc.DrawLine(0, 0, w-1, 0);
359 dc.DrawLine(0, 0, 0, h - 1);
360
361 dc.SetPen(darkShadowPen);
362 dc.DrawLine(1, 1, w-2, 1);
363 dc.DrawLine(1, 1, 1, h-2);
364
365 dc.SetPen(hilightPen);
366 dc.DrawLine(0, h-1, w-1, h-1);
367 dc.DrawLine(w-1, 0, w-1, h); // Surely the maximum y pos. should be h - 1.
368 /// Anyway, h is required for MSW.
369
370 dc.SetPen(lightShadowPen);
371 dc.DrawLine(w-2, 1, w-2, h-2); // Right hand side
372 dc.DrawLine(1, h-2, w-1, h-2); // Bottom
373 }
374 else if ( GetWindowStyleFlag() & wxSP_BORDER )
375 {
376 dc.SetBrush(*wxTRANSPARENT_BRUSH);
377 dc.SetPen(*wxBLACK_PEN);
378 dc.DrawRectangle(0, 0, w-1, h-1);
379 }
380
381 dc.SetPen(wxNullPen);
382 dc.SetBrush(wxNullBrush);
383 }
384
385 void wxSashWindow::DrawSashes(wxDC& dc)
386 {
387 int i;
388 for (i = 0; i < 4; i++)
389 if (m_sashes[i].m_show)
390 DrawSash((wxSashEdgePosition) i, dc);
391 }
392
393 // Draw the sash
394 void wxSashWindow::DrawSash(wxSashEdgePosition edge, wxDC& dc)
395 {
396 int w, h;
397 GetClientSize(&w, &h);
398
399 wxPen facePen(m_faceColour, 1, wxSOLID);
400 wxBrush faceBrush(m_faceColour, wxSOLID);
401 wxPen mediumShadowPen(m_mediumShadowColour, 1, wxSOLID);
402 wxPen darkShadowPen(m_darkShadowColour, 1, wxSOLID);
403 wxPen lightShadowPen(m_lightShadowColour, 1, wxSOLID);
404 wxPen hilightPen(m_hilightColour, 1, wxSOLID);
405 wxPen blackPen(wxColour(0, 0, 0), 1, wxSOLID);
406 wxPen whitePen(wxColour(255, 255, 255), 1, wxSOLID);
407
408 if ( edge == wxSASH_LEFT || edge == wxSASH_RIGHT )
409 {
410 int sashPosition = 0;
411 if (edge == wxSASH_LEFT)
412 sashPosition = 0;
413 else
414 sashPosition = w - GetEdgeMargin(edge);
415
416 dc.SetPen(facePen);
417 dc.SetBrush(faceBrush);
418 dc.DrawRectangle(sashPosition, 0, GetEdgeMargin(edge), h);
419
420 if (GetWindowStyleFlag() & wxSW_3D)
421 {
422 if (edge == wxSASH_LEFT)
423 {
424 // Draw a black line on the left to indicate that the
425 // sash is raised
426 dc.SetPen(blackPen);
427 dc.DrawLine(GetEdgeMargin(edge), 0, GetEdgeMargin(edge), h);
428 }
429 else
430 {
431 // Draw a white line on the right to indicate that the
432 // sash is raised
433 dc.SetPen(whitePen);
434 dc.DrawLine(w - GetEdgeMargin(edge), 0, w - GetEdgeMargin(edge), h);
435 }
436 }
437 }
438 else // top or bottom
439 {
440 int sashPosition = 0;
441 if (edge == wxSASH_TOP)
442 sashPosition = 0;
443 else
444 sashPosition = h - GetEdgeMargin(edge);
445
446 dc.SetPen(facePen);
447 dc.SetBrush(faceBrush);
448 dc.DrawRectangle(0, sashPosition, w, GetEdgeMargin(edge));
449
450 if (GetWindowStyleFlag() & wxSW_3D)
451 {
452 if (edge == wxSASH_BOTTOM)
453 {
454 // Draw a black line on the bottom to indicate that the
455 // sash is raised
456 dc.SetPen(blackPen);
457 dc.DrawLine(0, h - GetEdgeMargin(edge), w, h - GetEdgeMargin(edge));
458 }
459 else
460 {
461 // Draw a white line on the top to indicate that the
462 // sash is raised
463 dc.SetPen(whitePen);
464 dc.DrawLine(0, GetEdgeMargin(edge), w, GetEdgeMargin(edge));
465 }
466 }
467 }
468
469 dc.SetPen(wxNullPen);
470 dc.SetBrush(wxNullBrush);
471 }
472
473 // Draw the sash tracker (for whilst moving the sash)
474 void wxSashWindow::DrawSashTracker(wxSashEdgePosition edge, int x, int y)
475 {
476 int w, h;
477 GetClientSize(&w, &h);
478
479 wxScreenDC screenDC;
480 int x1, y1;
481 int x2, y2;
482
483 if ( edge == wxSASH_LEFT || edge == wxSASH_RIGHT )
484 {
485 x1 = x; y1 = 2;
486 x2 = x; y2 = h-2;
487
488 if ( (edge == wxSASH_LEFT) && (x1 > w) )
489 {
490 x1 = w; x2 = w;
491 }
492 else if ( (edge == wxSASH_RIGHT) && (x1 < 0) )
493 {
494 x1 = 0; x2 = 0;
495 }
496 }
497 else
498 {
499 x1 = 2; y1 = y;
500 x2 = w-2; y2 = y;
501
502 if ( (edge == wxSASH_TOP) && (y1 > h) )
503 {
504 y1 = h;
505 y2 = h;
506 }
507 else if ( (edge == wxSASH_BOTTOM) && (y1 < 0) )
508 {
509 y1 = 0;
510 y2 = 0;
511 }
512 }
513
514 ClientToScreen(&x1, &y1);
515 ClientToScreen(&x2, &y2);
516
517 wxPen sashTrackerPen(*wxBLACK, 2, wxSOLID);
518
519 screenDC.SetLogicalFunction(wxXOR);
520 screenDC.SetPen(sashTrackerPen);
521 screenDC.SetBrush(*wxTRANSPARENT_BRUSH);
522
523 screenDC.DrawLine(x1, y1, x2, y2);
524
525 screenDC.SetLogicalFunction(wxCOPY);
526
527 screenDC.SetPen(wxNullPen);
528 screenDC.SetBrush(wxNullBrush);
529 }
530
531 // Position and size subwindows.
532 // Note that the border size applies to each subwindow, not
533 // including the edges next to the sash.
534 void wxSashWindow::SizeWindows()
535 {
536 int cw, ch;
537 GetClientSize(&cw, &ch);
538
539 if (GetChildren().Number() == 1)
540 {
541 wxWindow* child = (wxWindow*) (GetChildren().First()->Data());
542
543 int x = 0;
544 int y = 0;
545 int width = cw;
546 int height = ch;
547
548 // Top
549 if (m_sashes[0].m_show)
550 {
551 y = m_borderSize;
552 height -= m_borderSize;
553 }
554 y += m_extraBorderSize;
555
556 // Left
557 if (m_sashes[3].m_show)
558 {
559 x = m_borderSize;
560 width -= m_borderSize;
561 }
562 x += m_extraBorderSize;
563
564 // Right
565 if (m_sashes[1].m_show)
566 {
567 width -= m_borderSize;
568 }
569 width -= 2*m_extraBorderSize;
570
571 // Bottom
572 if (m_sashes[2].m_show)
573 {
574 height -= m_borderSize;
575 }
576 height -= 2*m_extraBorderSize;
577
578 child->SetSize(x, y, width, height);
579 }
580 else if (GetChildren().Number() > 1)
581 {
582 // Perhaps multiple children are themselves sash windows.
583 // TODO: this doesn't really work because the subwindows sizes/positions
584 // must be set to leave a gap for the parent's sash (hit-test and decorations).
585 // Perhaps we can allow for this within LayoutWindow, testing whether the parent
586 // is a sash window, and if so, allowing some space for the edges.
587 wxLayoutAlgorithm layout;
588 layout.LayoutWindow(this);
589 }
590
591 wxClientDC dc(this);
592 DrawBorders(dc);
593 DrawSashes(dc);
594 }
595
596 // Initialize colours
597 void wxSashWindow::InitColours()
598 {
599 // Shadow colours
600 #if defined(__WIN95__)
601 m_faceColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DFACE);
602 m_mediumShadowColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DSHADOW);
603 m_darkShadowColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DDKSHADOW);
604 m_lightShadowColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DLIGHT);
605 m_hilightColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DHILIGHT);
606 #else
607 m_faceColour = *(wxTheColourDatabase->FindColour("LIGHT GREY"));
608 m_mediumShadowColour = *(wxTheColourDatabase->FindColour("GREY"));
609 m_darkShadowColour = *(wxTheColourDatabase->FindColour("BLACK"));
610 m_lightShadowColour = *(wxTheColourDatabase->FindColour("LIGHT GREY"));
611 m_hilightColour = *(wxTheColourDatabase->FindColour("WHITE"));
612 #endif
613 }
614
615 void wxSashWindow::SetSashVisible(wxSashEdgePosition edge, bool sash)
616 {
617 m_sashes[edge].m_show = sash;
618 if (sash)
619 m_sashes[edge].m_margin = m_borderSize;
620 else
621 m_sashes[edge].m_margin = 0;
622 }
623