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