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