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