1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/scrlwing.cpp
3 // Purpose: wxScrolledWindow implementation
4 // Author: Julian Smart
5 // Modified by: Vadim Zeitlin on 31.08.00: wxScrollHelper allows to implement.
6 // Ron Lee on 10.4.02: virtual size / auto scrollbars et al.
7 // Created: 01/02/97
8 // RCS-ID: $Id: scrlwing.cpp 60600 2009-05-12 10:33:49Z VZ $
9 // Copyright: (c) wxWidgets team
10 // Licence: wxWindows licence
11 /////////////////////////////////////////////////////////////////////////////
12
13 // ============================================================================
14 // declarations
15 // ============================================================================
16
17 // ----------------------------------------------------------------------------
18 // headers
19 // ----------------------------------------------------------------------------
20
21 // For compilers that support precompilation, includes "wx.h".
22 #include "wx/wxprec.h"
23
24 #ifdef __BORLANDC__
25 #pragma hdrstop
26 #endif
27
28 #include "wx/scrolwin.h"
29
30 #ifndef WX_PRECOMP
31 #include "wx/utils.h"
32 #include "wx/panel.h"
33 #include "wx/dcclient.h"
34 #if wxUSE_TIMER
35 #include "wx/timer.h"
36 #endif
37 #include "wx/sizer.h"
38 #include "wx/settings.h"
39 #endif
40
41 #ifdef __WXMAC__
42 #include "wx/scrolbar.h"
43 #endif
44
45 #include "wx/recguard.h"
46
47 #ifdef __WXMSW__
48 #include <windows.h> // for DLGC_WANTARROWS
49 #include "wx/msw/winundef.h"
50 #endif
51
52 #ifdef __WXMOTIF__
53 // For wxRETAINED implementation
54 #ifdef __VMS__ //VMS's Xm.h is not (yet) compatible with C++
55 //This code switches off the compiler warnings
56 # pragma message disable nosimpint
57 #endif
58 #include <Xm/Xm.h>
59 #ifdef __VMS__
60 # pragma message enable nosimpint
61 #endif
62 #endif
63
64 /*
65 TODO PROPERTIES
66 style wxHSCROLL | wxVSCROLL
67 */
68
69 // ----------------------------------------------------------------------------
70 // wxScrollHelperEvtHandler: intercept the events from the window and forward
71 // them to wxScrollHelper
72 // ----------------------------------------------------------------------------
73
74 class WXDLLEXPORT wxScrollHelperEvtHandler : public wxEvtHandler
75 {
76 public:
wxScrollHelperEvtHandler(wxScrollHelper * scrollHelper)77 wxScrollHelperEvtHandler(wxScrollHelper *scrollHelper)
78 {
79 m_scrollHelper = scrollHelper;
80 }
81
82 virtual bool ProcessEvent(wxEvent& event);
83
ResetDrawnFlag()84 void ResetDrawnFlag() { m_hasDrawnWindow = false; }
85
86 private:
87 wxScrollHelper *m_scrollHelper;
88
89 bool m_hasDrawnWindow;
90
91 DECLARE_NO_COPY_CLASS(wxScrollHelperEvtHandler)
92 };
93
94 #if wxUSE_TIMER
95 // ----------------------------------------------------------------------------
96 // wxAutoScrollTimer: the timer used to generate a stream of scroll events when
97 // a captured mouse is held outside the window
98 // ----------------------------------------------------------------------------
99
100 class wxAutoScrollTimer : public wxTimer
101 {
102 public:
103 wxAutoScrollTimer(wxWindow *winToScroll, wxScrollHelper *scroll,
104 wxEventType eventTypeToSend,
105 int pos, int orient);
106
107 virtual void Notify();
108
109 private:
110 wxWindow *m_win;
111 wxScrollHelper *m_scrollHelper;
112 wxEventType m_eventType;
113 int m_pos,
114 m_orient;
115
116 DECLARE_NO_COPY_CLASS(wxAutoScrollTimer)
117 };
118
119 // ============================================================================
120 // implementation
121 // ============================================================================
122
123 // ----------------------------------------------------------------------------
124 // wxAutoScrollTimer
125 // ----------------------------------------------------------------------------
126
wxAutoScrollTimer(wxWindow * winToScroll,wxScrollHelper * scroll,wxEventType eventTypeToSend,int pos,int orient)127 wxAutoScrollTimer::wxAutoScrollTimer(wxWindow *winToScroll,
128 wxScrollHelper *scroll,
129 wxEventType eventTypeToSend,
130 int pos, int orient)
131 {
132 m_win = winToScroll;
133 m_scrollHelper = scroll;
134 m_eventType = eventTypeToSend;
135 m_pos = pos;
136 m_orient = orient;
137 }
138
Notify()139 void wxAutoScrollTimer::Notify()
140 {
141 // only do all this as long as the window is capturing the mouse
142 if ( wxWindow::GetCapture() != m_win )
143 {
144 Stop();
145 }
146 else // we still capture the mouse, continue generating events
147 {
148 // first scroll the window if we are allowed to do it
149 wxScrollWinEvent event1(m_eventType, m_pos, m_orient);
150 event1.SetEventObject(m_win);
151 if ( m_scrollHelper->SendAutoScrollEvents(event1) &&
152 m_win->GetEventHandler()->ProcessEvent(event1) )
153 {
154 // and then send a pseudo mouse-move event to refresh the selection
155 wxMouseEvent event2(wxEVT_MOTION);
156 wxGetMousePosition(&event2.m_x, &event2.m_y);
157
158 // the mouse event coordinates should be client, not screen as
159 // returned by wxGetMousePosition
160 wxWindow *parentTop = m_win;
161 while ( parentTop->GetParent() )
162 parentTop = parentTop->GetParent();
163 wxPoint ptOrig = parentTop->GetPosition();
164 event2.m_x -= ptOrig.x;
165 event2.m_y -= ptOrig.y;
166
167 event2.SetEventObject(m_win);
168
169 // FIXME: we don't fill in the other members - ok?
170
171 m_win->GetEventHandler()->ProcessEvent(event2);
172 }
173 else // can't scroll further, stop
174 {
175 Stop();
176 }
177 }
178 }
179 #endif
180
181 // ----------------------------------------------------------------------------
182 // wxScrollHelperEvtHandler
183 // ----------------------------------------------------------------------------
184
ProcessEvent(wxEvent & event)185 bool wxScrollHelperEvtHandler::ProcessEvent(wxEvent& event)
186 {
187 wxEventType evType = event.GetEventType();
188
189 // the explanation of wxEVT_PAINT processing hack: for historic reasons
190 // there are 2 ways to process this event in classes deriving from
191 // wxScrolledWindow. The user code may
192 //
193 // 1. override wxScrolledWindow::OnDraw(dc)
194 // 2. define its own OnPaint() handler
195 //
196 // In addition, in wxUniversal wxWindow defines OnPaint() itself and
197 // always processes the draw event, so we can't just try the window
198 // OnPaint() first and call our HandleOnPaint() if it doesn't process it
199 // (the latter would never be called in wxUniversal).
200 //
201 // So the solution is to have a flag telling us whether the user code drew
202 // anything in the window. We set it to true here but reset it to false in
203 // wxScrolledWindow::OnPaint() handler (which wouldn't be called if the
204 // user code defined OnPaint() in the derived class)
205 m_hasDrawnWindow = true;
206
207 // pass it on to the real handler
208 bool processed = wxEvtHandler::ProcessEvent(event);
209
210 // always process the size events ourselves, even if the user code handles
211 // them as well, as we need to AdjustScrollbars()
212 //
213 // NB: it is important to do it after processing the event in the normal
214 // way as HandleOnSize() may generate a wxEVT_SIZE itself if the
215 // scrollbar[s] (dis)appear and it should be seen by the user code
216 // after this one
217 if ( evType == wxEVT_SIZE )
218 {
219 m_scrollHelper->HandleOnSize((wxSizeEvent &)event);
220
221 return true;
222 }
223
224 if ( processed )
225 {
226 // normally, nothing more to do here - except if it was a paint event
227 // which wasn't really processed, then we'll try to call our
228 // OnDraw() below (from HandleOnPaint)
229 if ( m_hasDrawnWindow || event.IsCommandEvent() )
230 {
231 return true;
232 }
233 }
234
235 // reset the skipped flag to false as it might have been set to true in
236 // ProcessEvent() above
237 event.Skip(false);
238
239 if ( evType == wxEVT_PAINT )
240 {
241 m_scrollHelper->HandleOnPaint((wxPaintEvent &)event);
242 return true;
243 }
244
245 if ( evType == wxEVT_CHILD_FOCUS )
246 {
247 m_scrollHelper->HandleOnChildFocus((wxChildFocusEvent &)event);
248 return true;
249 }
250
251 if ( evType == wxEVT_SCROLLWIN_TOP ||
252 evType == wxEVT_SCROLLWIN_BOTTOM ||
253 evType == wxEVT_SCROLLWIN_LINEUP ||
254 evType == wxEVT_SCROLLWIN_LINEDOWN ||
255 evType == wxEVT_SCROLLWIN_PAGEUP ||
256 evType == wxEVT_SCROLLWIN_PAGEDOWN ||
257 evType == wxEVT_SCROLLWIN_THUMBTRACK ||
258 evType == wxEVT_SCROLLWIN_THUMBRELEASE )
259 {
260 m_scrollHelper->HandleOnScroll((wxScrollWinEvent &)event);
261 return !event.GetSkipped();
262 }
263
264 if ( evType == wxEVT_ENTER_WINDOW )
265 {
266 m_scrollHelper->HandleOnMouseEnter((wxMouseEvent &)event);
267 }
268 else if ( evType == wxEVT_LEAVE_WINDOW )
269 {
270 m_scrollHelper->HandleOnMouseLeave((wxMouseEvent &)event);
271 }
272 #if wxUSE_MOUSEWHEEL
273 else if ( evType == wxEVT_MOUSEWHEEL )
274 {
275 m_scrollHelper->HandleOnMouseWheel((wxMouseEvent &)event);
276 return true;
277 }
278 #endif // wxUSE_MOUSEWHEEL
279 else if ( evType == wxEVT_CHAR )
280 {
281 m_scrollHelper->HandleOnChar((wxKeyEvent &)event);
282 return !event.GetSkipped();
283 }
284
285 return false;
286 }
287
288 // ----------------------------------------------------------------------------
289 // wxScrollHelper construction
290 // ----------------------------------------------------------------------------
291
wxScrollHelper(wxWindow * win)292 wxScrollHelper::wxScrollHelper(wxWindow *win)
293 {
294 wxASSERT_MSG( win, _T("associated window can't be NULL in wxScrollHelper") );
295
296 m_xScrollPixelsPerLine =
297 m_yScrollPixelsPerLine =
298 m_xScrollPosition =
299 m_yScrollPosition =
300 m_xScrollLines =
301 m_yScrollLines =
302 m_xScrollLinesPerPage =
303 m_yScrollLinesPerPage = 0;
304
305 m_xScrollingEnabled =
306 m_yScrollingEnabled = true;
307
308 m_scaleX =
309 m_scaleY = 1.0;
310 #if wxUSE_MOUSEWHEEL
311 m_wheelRotation = 0;
312 #endif
313
314 m_win =
315 m_targetWindow = (wxWindow *)NULL;
316
317 m_timerAutoScroll = (wxTimer *)NULL;
318
319 m_handler = NULL;
320
321 m_win = win;
322
323 m_win->SetScrollHelper( this );
324
325 // by default, the associated window is also the target window
326 DoSetTargetWindow(win);
327 }
328
~wxScrollHelper()329 wxScrollHelper::~wxScrollHelper()
330 {
331 StopAutoScrolling();
332
333 DeleteEvtHandler();
334 }
335
336 // ----------------------------------------------------------------------------
337 // setting scrolling parameters
338 // ----------------------------------------------------------------------------
339
SetScrollbars(int pixelsPerUnitX,int pixelsPerUnitY,int noUnitsX,int noUnitsY,int xPos,int yPos,bool noRefresh)340 void wxScrollHelper::SetScrollbars(int pixelsPerUnitX,
341 int pixelsPerUnitY,
342 int noUnitsX,
343 int noUnitsY,
344 int xPos,
345 int yPos,
346 bool noRefresh)
347 {
348 int xpos, ypos;
349
350 CalcUnscrolledPosition(xPos, yPos, &xpos, &ypos);
351 bool do_refresh =
352 (
353 (noUnitsX != 0 && m_xScrollLines == 0) ||
354 (noUnitsX < m_xScrollLines && xpos > pixelsPerUnitX * noUnitsX) ||
355
356 (noUnitsY != 0 && m_yScrollLines == 0) ||
357 (noUnitsY < m_yScrollLines && ypos > pixelsPerUnitY * noUnitsY) ||
358 (xPos != m_xScrollPosition) ||
359 (yPos != m_yScrollPosition)
360 );
361
362 m_xScrollPixelsPerLine = pixelsPerUnitX;
363 m_yScrollPixelsPerLine = pixelsPerUnitY;
364 m_xScrollPosition = xPos;
365 m_yScrollPosition = yPos;
366
367 int w = noUnitsX * pixelsPerUnitX;
368 int h = noUnitsY * pixelsPerUnitY;
369
370 // For better backward compatibility we set persisting limits
371 // here not just the size. It makes SetScrollbars 'sticky'
372 // emulating the old non-autoscroll behaviour.
373 // m_targetWindow->SetVirtualSizeHints( w, h );
374
375 // The above should arguably be deprecated, this however we still need.
376
377 // take care not to set 0 virtual size, 0 means that we don't have any
378 // scrollbars and hence we should use the real size instead of the virtual
379 // one which is indicated by using wxDefaultCoord
380 m_targetWindow->SetVirtualSize( w ? w : wxDefaultCoord,
381 h ? h : wxDefaultCoord);
382
383 if (do_refresh && !noRefresh)
384 m_targetWindow->Refresh(true, GetScrollRect());
385
386 #ifndef __WXUNIVERSAL__
387 // If the target is not the same as the window with the scrollbars,
388 // then we need to update the scrollbars here, since they won't have
389 // been updated by SetVirtualSize().
390 if ( m_targetWindow != m_win )
391 #endif // !__WXUNIVERSAL__
392 {
393 AdjustScrollbars();
394 }
395 #ifndef __WXUNIVERSAL__
396 else
397 {
398 // otherwise this has been done by AdjustScrollbars, above
399 }
400 #endif // !__WXUNIVERSAL__
401 }
402
403 // ----------------------------------------------------------------------------
404 // [target] window handling
405 // ----------------------------------------------------------------------------
406
DeleteEvtHandler()407 void wxScrollHelper::DeleteEvtHandler()
408 {
409 // search for m_handler in the handler list
410 if ( m_win && m_handler )
411 {
412 if ( m_win->RemoveEventHandler(m_handler) )
413 {
414 delete m_handler;
415 }
416 //else: something is very wrong, so better [maybe] leak memory than
417 // risk a crash because of double deletion
418
419 m_handler = NULL;
420 }
421 }
422
DoSetTargetWindow(wxWindow * target)423 void wxScrollHelper::DoSetTargetWindow(wxWindow *target)
424 {
425 m_targetWindow = target;
426 #ifdef __WXMAC__
427 target->MacSetClipChildren( true ) ;
428 #endif
429
430 // install the event handler which will intercept the events we're
431 // interested in (but only do it for our real window, not the target window
432 // which we scroll - we don't need to hijack its events)
433 if ( m_targetWindow == m_win )
434 {
435 // if we already have a handler, delete it first
436 DeleteEvtHandler();
437
438 m_handler = new wxScrollHelperEvtHandler(this);
439 m_targetWindow->PushEventHandler(m_handler);
440 }
441 }
442
SetTargetWindow(wxWindow * target)443 void wxScrollHelper::SetTargetWindow(wxWindow *target)
444 {
445 wxCHECK_RET( target, wxT("target window must not be NULL") );
446
447 if ( target == m_targetWindow )
448 return;
449
450 DoSetTargetWindow(target);
451 }
452
GetTargetWindow() const453 wxWindow *wxScrollHelper::GetTargetWindow() const
454 {
455 return m_targetWindow;
456 }
457
458 // ----------------------------------------------------------------------------
459 // scrolling implementation itself
460 // ----------------------------------------------------------------------------
461
HandleOnScroll(wxScrollWinEvent & event)462 void wxScrollHelper::HandleOnScroll(wxScrollWinEvent& event)
463 {
464 int nScrollInc = CalcScrollInc(event);
465 if ( nScrollInc == 0 )
466 {
467 // can't scroll further
468 event.Skip();
469
470 return;
471 }
472
473 bool needsRefresh = false;
474
475 int dx = 0,
476 dy = 0;
477 int orient = event.GetOrientation();
478 if (orient == wxHORIZONTAL)
479 {
480 if ( m_xScrollingEnabled )
481 {
482 dx = -m_xScrollPixelsPerLine * nScrollInc;
483 }
484 else
485 {
486 needsRefresh = true;
487 }
488 }
489 else
490 {
491 if ( m_yScrollingEnabled )
492 {
493 dy = -m_yScrollPixelsPerLine * nScrollInc;
494 }
495 else
496 {
497 needsRefresh = true;
498 }
499 }
500
501 if ( !needsRefresh )
502 {
503 // flush all pending repaints before we change m_{x,y}ScrollPosition, as
504 // otherwise invalidated area could be updated incorrectly later when
505 // ScrollWindow() makes sure they're repainted before scrolling them
506 #ifdef __WXMAC__
507 // wxWindowMac is taking care of making sure the update area is correctly
508 // set up, while not forcing an immediate redraw
509 #else
510 m_targetWindow->Update();
511 #endif
512 }
513
514 if (orient == wxHORIZONTAL)
515 {
516 m_xScrollPosition += nScrollInc;
517 m_win->SetScrollPos(wxHORIZONTAL, m_xScrollPosition);
518 }
519 else
520 {
521 m_yScrollPosition += nScrollInc;
522 m_win->SetScrollPos(wxVERTICAL, m_yScrollPosition);
523 }
524
525 if ( needsRefresh )
526 {
527 m_targetWindow->Refresh(true, GetScrollRect());
528 }
529 else
530 {
531 m_targetWindow->ScrollWindow(dx, dy, GetScrollRect());
532 }
533 }
534
CalcScrollInc(wxScrollWinEvent & event)535 int wxScrollHelper::CalcScrollInc(wxScrollWinEvent& event)
536 {
537 int pos = event.GetPosition();
538 int orient = event.GetOrientation();
539
540 int nScrollInc = 0;
541 if (event.GetEventType() == wxEVT_SCROLLWIN_TOP)
542 {
543 if (orient == wxHORIZONTAL)
544 nScrollInc = - m_xScrollPosition;
545 else
546 nScrollInc = - m_yScrollPosition;
547 } else
548 if (event.GetEventType() == wxEVT_SCROLLWIN_BOTTOM)
549 {
550 if (orient == wxHORIZONTAL)
551 nScrollInc = m_xScrollLines - m_xScrollPosition;
552 else
553 nScrollInc = m_yScrollLines - m_yScrollPosition;
554 } else
555 if (event.GetEventType() == wxEVT_SCROLLWIN_LINEUP)
556 {
557 nScrollInc = -1;
558 } else
559 if (event.GetEventType() == wxEVT_SCROLLWIN_LINEDOWN)
560 {
561 nScrollInc = 1;
562 } else
563 if (event.GetEventType() == wxEVT_SCROLLWIN_PAGEUP)
564 {
565 if (orient == wxHORIZONTAL)
566 nScrollInc = -GetScrollPageSize(wxHORIZONTAL);
567 else
568 nScrollInc = -GetScrollPageSize(wxVERTICAL);
569 } else
570 if (event.GetEventType() == wxEVT_SCROLLWIN_PAGEDOWN)
571 {
572 if (orient == wxHORIZONTAL)
573 nScrollInc = GetScrollPageSize(wxHORIZONTAL);
574 else
575 nScrollInc = GetScrollPageSize(wxVERTICAL);
576 } else
577 if ((event.GetEventType() == wxEVT_SCROLLWIN_THUMBTRACK) ||
578 (event.GetEventType() == wxEVT_SCROLLWIN_THUMBRELEASE))
579 {
580 if (orient == wxHORIZONTAL)
581 nScrollInc = pos - m_xScrollPosition;
582 else
583 nScrollInc = pos - m_yScrollPosition;
584 }
585
586 if (orient == wxHORIZONTAL)
587 {
588 if (m_xScrollPixelsPerLine > 0)
589 {
590 if ( m_xScrollPosition + nScrollInc < 0 )
591 {
592 // As -ve as we can go
593 nScrollInc = -m_xScrollPosition;
594 }
595 else // check for the other bound
596 {
597 const int posMax = m_xScrollLines - m_xScrollLinesPerPage;
598 if ( m_xScrollPosition + nScrollInc > posMax )
599 {
600 // As +ve as we can go
601 nScrollInc = posMax - m_xScrollPosition;
602 }
603 }
604 }
605 else
606 m_targetWindow->Refresh(true, GetScrollRect());
607 }
608 else
609 {
610 if ( m_yScrollPixelsPerLine > 0 )
611 {
612 if ( m_yScrollPosition + nScrollInc < 0 )
613 {
614 // As -ve as we can go
615 nScrollInc = -m_yScrollPosition;
616 }
617 else // check for the other bound
618 {
619 const int posMax = m_yScrollLines - m_yScrollLinesPerPage;
620 if ( m_yScrollPosition + nScrollInc > posMax )
621 {
622 // As +ve as we can go
623 nScrollInc = posMax - m_yScrollPosition;
624 }
625 }
626 }
627 else
628 {
629 // VZ: why do we do this? (FIXME)
630 m_targetWindow->Refresh(true, GetScrollRect());
631 }
632 }
633
634 return nScrollInc;
635 }
636
637 // Adjust the scrollbars - new version.
AdjustScrollbars()638 void wxScrollHelper::AdjustScrollbars()
639 {
640 static wxRecursionGuardFlag s_flagReentrancy;
641 wxRecursionGuard guard(s_flagReentrancy);
642 if ( guard.IsInside() )
643 {
644 // don't reenter AdjustScrollbars() while another call to
645 // AdjustScrollbars() is in progress because this may lead to calling
646 // ScrollWindow() twice and this can really happen under MSW if
647 // SetScrollbar() call below adds or removes the scrollbar which
648 // changes the window size and hence results in another
649 // AdjustScrollbars() call
650 return;
651 }
652
653 int w = 0, h = 0;
654 int oldw, oldh;
655
656 int oldXScroll = m_xScrollPosition;
657 int oldYScroll = m_yScrollPosition;
658
659 // VZ: at least under Windows this loop is useless because when scrollbars
660 // [dis]appear we get a WM_SIZE resulting in another call to
661 // AdjustScrollbars() anyhow. As it doesn't seem to do any harm I leave
662 // it here for now but it would be better to ensure that all ports
663 // generate EVT_SIZE when scrollbars [dis]appear, emulating it if
664 // necessary, and remove it later
665 // JACS: Stop potential infinite loop by limiting number of iterations
666 int iterationCount = 0;
667 const int iterationMax = 5;
668 do
669 {
670 iterationCount ++;
671
672 GetTargetSize(&w, 0);
673
674 // scroll lines per page: if 0, no scrolling is needed
675 int linesPerPage;
676
677 if ( m_xScrollPixelsPerLine == 0 )
678 {
679 // scrolling is disabled
680 m_xScrollLines = 0;
681 m_xScrollPosition = 0;
682 linesPerPage = 0;
683 }
684 else // might need scrolling
685 {
686 // Round up integer division to catch any "leftover" client space.
687 const int wVirt = m_targetWindow->GetVirtualSize().GetWidth();
688 m_xScrollLines = (wVirt + m_xScrollPixelsPerLine - 1) / m_xScrollPixelsPerLine;
689
690 // Calculate page size i.e. number of scroll units you get on the
691 // current client window.
692 linesPerPage = w / m_xScrollPixelsPerLine;
693
694 // Special case. When client and virtual size are very close but
695 // the client is big enough, kill scrollbar.
696 if ((linesPerPage < m_xScrollLines) && (w >= wVirt)) ++linesPerPage;
697
698 if (linesPerPage >= m_xScrollLines)
699 {
700 // we're big enough to not need scrolling
701 linesPerPage =
702 m_xScrollLines =
703 m_xScrollPosition = 0;
704 }
705 else // we do need a scrollbar
706 {
707 if ( linesPerPage < 1 )
708 linesPerPage = 1;
709
710 // Correct position if greater than extent of canvas minus
711 // the visible portion of it or if below zero
712 const int posMax = m_xScrollLines - linesPerPage;
713 if ( m_xScrollPosition > posMax )
714 m_xScrollPosition = posMax;
715 else if ( m_xScrollPosition < 0 )
716 m_xScrollPosition = 0;
717 }
718 }
719
720 m_win->SetScrollbar(wxHORIZONTAL, m_xScrollPosition,
721 linesPerPage, m_xScrollLines);
722
723 // The amount by which we scroll when paging
724 SetScrollPageSize(wxHORIZONTAL, linesPerPage);
725
726 GetTargetSize(0, &h);
727
728 if ( m_yScrollPixelsPerLine == 0 )
729 {
730 // scrolling is disabled
731 m_yScrollLines = 0;
732 m_yScrollPosition = 0;
733 linesPerPage = 0;
734 }
735 else // might need scrolling
736 {
737 // Round up integer division to catch any "leftover" client space.
738 const int hVirt = m_targetWindow->GetVirtualSize().GetHeight();
739 m_yScrollLines = ( hVirt + m_yScrollPixelsPerLine - 1 ) / m_yScrollPixelsPerLine;
740
741 // Calculate page size i.e. number of scroll units you get on the
742 // current client window.
743 linesPerPage = h / m_yScrollPixelsPerLine;
744
745 // Special case. When client and virtual size are very close but
746 // the client is big enough, kill scrollbar.
747 if ((linesPerPage < m_yScrollLines) && (h >= hVirt)) ++linesPerPage;
748
749 if (linesPerPage >= m_yScrollLines)
750 {
751 // we're big enough to not need scrolling
752 linesPerPage =
753 m_yScrollLines =
754 m_yScrollPosition = 0;
755 }
756 else // we do need a scrollbar
757 {
758 if ( linesPerPage < 1 )
759 linesPerPage = 1;
760
761 // Correct position if greater than extent of canvas minus
762 // the visible portion of it or if below zero
763 const int posMax = m_yScrollLines - linesPerPage;
764 if ( m_yScrollPosition > posMax )
765 m_yScrollPosition = posMax;
766 else if ( m_yScrollPosition < 0 )
767 m_yScrollPosition = 0;
768 }
769 }
770
771 m_win->SetScrollbar(wxVERTICAL, m_yScrollPosition,
772 linesPerPage, m_yScrollLines);
773
774 // The amount by which we scroll when paging
775 SetScrollPageSize(wxVERTICAL, linesPerPage);
776
777
778 // If a scrollbar (dis)appeared as a result of this, adjust them again.
779 oldw = w;
780 oldh = h;
781
782 GetTargetSize( &w, &h );
783 } while ( (w != oldw || h != oldh) && (iterationCount < iterationMax) );
784
785 #ifdef __WXMOTIF__
786 // Sorry, some Motif-specific code to implement a backing pixmap
787 // for the wxRETAINED style. Implementing a backing store can't
788 // be entirely generic because it relies on the wxWindowDC implementation
789 // to duplicate X drawing calls for the backing pixmap.
790
791 if ( m_targetWindow->GetWindowStyle() & wxRETAINED )
792 {
793 Display* dpy = XtDisplay((Widget)m_targetWindow->GetMainWidget());
794
795 int totalPixelWidth = m_xScrollLines * m_xScrollPixelsPerLine;
796 int totalPixelHeight = m_yScrollLines * m_yScrollPixelsPerLine;
797 if (m_targetWindow->GetBackingPixmap() &&
798 !((m_targetWindow->GetPixmapWidth() == totalPixelWidth) &&
799 (m_targetWindow->GetPixmapHeight() == totalPixelHeight)))
800 {
801 XFreePixmap (dpy, (Pixmap) m_targetWindow->GetBackingPixmap());
802 m_targetWindow->SetBackingPixmap((WXPixmap) 0);
803 }
804
805 if (!m_targetWindow->GetBackingPixmap() &&
806 (m_xScrollLines != 0) && (m_yScrollLines != 0))
807 {
808 int depth = wxDisplayDepth();
809 m_targetWindow->SetPixmapWidth(totalPixelWidth);
810 m_targetWindow->SetPixmapHeight(totalPixelHeight);
811 m_targetWindow->SetBackingPixmap((WXPixmap) XCreatePixmap (dpy, RootWindow (dpy, DefaultScreen (dpy)),
812 m_targetWindow->GetPixmapWidth(), m_targetWindow->GetPixmapHeight(), depth));
813 }
814
815 }
816 #endif // Motif
817
818 if (oldXScroll != m_xScrollPosition)
819 {
820 if (m_xScrollingEnabled)
821 m_targetWindow->ScrollWindow( m_xScrollPixelsPerLine * (oldXScroll - m_xScrollPosition), 0,
822 GetScrollRect() );
823 else
824 m_targetWindow->Refresh(true, GetScrollRect());
825 }
826
827 if (oldYScroll != m_yScrollPosition)
828 {
829 if (m_yScrollingEnabled)
830 m_targetWindow->ScrollWindow( 0, m_yScrollPixelsPerLine * (oldYScroll-m_yScrollPosition),
831 GetScrollRect() );
832 else
833 m_targetWindow->Refresh(true, GetScrollRect());
834 }
835 }
836
DoPrepareDC(wxDC & dc)837 void wxScrollHelper::DoPrepareDC(wxDC& dc)
838 {
839 wxPoint pt = dc.GetDeviceOrigin();
840 #ifdef __WXGTK__
841 // It may actually be correct to always query
842 // the m_sign from the DC here, but I leve the
843 // #ifdef GTK for now.
844 if (m_win->GetLayoutDirection() == wxLayout_RightToLeft)
845 dc.SetDeviceOrigin( pt.x + m_xScrollPosition * m_xScrollPixelsPerLine,
846 pt.y - m_yScrollPosition * m_yScrollPixelsPerLine );
847 else
848 #endif
849 dc.SetDeviceOrigin( pt.x - m_xScrollPosition * m_xScrollPixelsPerLine,
850 pt.y - m_yScrollPosition * m_yScrollPixelsPerLine );
851 dc.SetUserScale( m_scaleX, m_scaleY );
852 }
853
SetScrollRate(int xstep,int ystep)854 void wxScrollHelper::SetScrollRate( int xstep, int ystep )
855 {
856 int old_x = m_xScrollPixelsPerLine * m_xScrollPosition;
857 int old_y = m_yScrollPixelsPerLine * m_yScrollPosition;
858
859 m_xScrollPixelsPerLine = xstep;
860 m_yScrollPixelsPerLine = ystep;
861
862 int new_x = m_xScrollPixelsPerLine * m_xScrollPosition;
863 int new_y = m_yScrollPixelsPerLine * m_yScrollPosition;
864
865 m_win->SetScrollPos( wxHORIZONTAL, m_xScrollPosition );
866 m_win->SetScrollPos( wxVERTICAL, m_yScrollPosition );
867 m_targetWindow->ScrollWindow( old_x - new_x, old_y - new_y );
868
869 AdjustScrollbars();
870 }
871
GetScrollPixelsPerUnit(int * x_unit,int * y_unit) const872 void wxScrollHelper::GetScrollPixelsPerUnit (int *x_unit, int *y_unit) const
873 {
874 if ( x_unit )
875 *x_unit = m_xScrollPixelsPerLine;
876 if ( y_unit )
877 *y_unit = m_yScrollPixelsPerLine;
878 }
879
880
GetScrollLines(int orient) const881 int wxScrollHelper::GetScrollLines( int orient ) const
882 {
883 if ( orient == wxHORIZONTAL )
884 return m_xScrollLines;
885 else
886 return m_yScrollLines;
887 }
888
GetScrollPageSize(int orient) const889 int wxScrollHelper::GetScrollPageSize(int orient) const
890 {
891 if ( orient == wxHORIZONTAL )
892 return m_xScrollLinesPerPage;
893 else
894 return m_yScrollLinesPerPage;
895 }
896
SetScrollPageSize(int orient,int pageSize)897 void wxScrollHelper::SetScrollPageSize(int orient, int pageSize)
898 {
899 if ( orient == wxHORIZONTAL )
900 m_xScrollLinesPerPage = pageSize;
901 else
902 m_yScrollLinesPerPage = pageSize;
903 }
904
905 /*
906 * Scroll to given position (scroll position, not pixel position)
907 */
Scroll(int x_pos,int y_pos)908 void wxScrollHelper::Scroll( int x_pos, int y_pos )
909 {
910 if (!m_targetWindow)
911 return;
912
913 if (((x_pos == -1) || (x_pos == m_xScrollPosition)) &&
914 ((y_pos == -1) || (y_pos == m_yScrollPosition))) return;
915
916 int w = 0, h = 0;
917 GetTargetSize(&w, &h);
918
919 // compute new position:
920 int new_x = m_xScrollPosition;
921 int new_y = m_yScrollPosition;
922
923 if ((x_pos != -1) && (m_xScrollPixelsPerLine))
924 {
925 new_x = x_pos;
926
927 // Calculate page size i.e. number of scroll units you get on the
928 // current client window
929 int noPagePositions = w/m_xScrollPixelsPerLine;
930 if (noPagePositions < 1) noPagePositions = 1;
931
932 // Correct position if greater than extent of canvas minus
933 // the visible portion of it or if below zero
934 new_x = wxMin( m_xScrollLines-noPagePositions, new_x );
935 new_x = wxMax( 0, new_x );
936 }
937 if ((y_pos != -1) && (m_yScrollPixelsPerLine))
938 {
939 new_y = y_pos;
940
941 // Calculate page size i.e. number of scroll units you get on the
942 // current client window
943 int noPagePositions = h/m_yScrollPixelsPerLine;
944 if (noPagePositions < 1) noPagePositions = 1;
945
946 // Correct position if greater than extent of canvas minus
947 // the visible portion of it or if below zero
948 new_y = wxMin( m_yScrollLines-noPagePositions, new_y );
949 new_y = wxMax( 0, new_y );
950 }
951
952 if ( new_x == m_xScrollPosition && new_y == m_yScrollPosition )
953 return; // nothing to do, the position didn't change
954
955 // flush all pending repaints before we change m_{x,y}ScrollPosition, as
956 // otherwise invalidated area could be updated incorrectly later when
957 // ScrollWindow() makes sure they're repainted before scrolling them
958 m_targetWindow->Update();
959
960 // update the position and scroll the window now:
961 if (m_xScrollPosition != new_x)
962 {
963 int old_x = m_xScrollPosition;
964 m_xScrollPosition = new_x;
965 m_win->SetScrollPos( wxHORIZONTAL, new_x );
966 m_targetWindow->ScrollWindow( (old_x-new_x)*m_xScrollPixelsPerLine, 0,
967 GetScrollRect() );
968 }
969
970 if (m_yScrollPosition != new_y)
971 {
972 int old_y = m_yScrollPosition;
973 m_yScrollPosition = new_y;
974 m_win->SetScrollPos( wxVERTICAL, new_y );
975 m_targetWindow->ScrollWindow( 0, (old_y-new_y)*m_yScrollPixelsPerLine,
976 GetScrollRect() );
977 }
978 }
979
EnableScrolling(bool x_scroll,bool y_scroll)980 void wxScrollHelper::EnableScrolling (bool x_scroll, bool y_scroll)
981 {
982 m_xScrollingEnabled = x_scroll;
983 m_yScrollingEnabled = y_scroll;
984 }
985
986 // Where the current view starts from
GetViewStart(int * x,int * y) const987 void wxScrollHelper::GetViewStart (int *x, int *y) const
988 {
989 if ( x )
990 *x = m_xScrollPosition;
991 if ( y )
992 *y = m_yScrollPosition;
993 }
994
DoCalcScrolledPosition(int x,int y,int * xx,int * yy) const995 void wxScrollHelper::DoCalcScrolledPosition(int x, int y, int *xx, int *yy) const
996 {
997 if ( xx )
998 *xx = x - m_xScrollPosition * m_xScrollPixelsPerLine;
999 if ( yy )
1000 *yy = y - m_yScrollPosition * m_yScrollPixelsPerLine;
1001 }
1002
DoCalcUnscrolledPosition(int x,int y,int * xx,int * yy) const1003 void wxScrollHelper::DoCalcUnscrolledPosition(int x, int y, int *xx, int *yy) const
1004 {
1005 if ( xx )
1006 *xx = x + m_xScrollPosition * m_xScrollPixelsPerLine;
1007 if ( yy )
1008 *yy = y + m_yScrollPosition * m_yScrollPixelsPerLine;
1009 }
1010
1011 // ----------------------------------------------------------------------------
1012 // geometry
1013 // ----------------------------------------------------------------------------
1014
ScrollLayout()1015 bool wxScrollHelper::ScrollLayout()
1016 {
1017 if ( m_win->GetSizer() && m_targetWindow == m_win )
1018 {
1019 // If we're the scroll target, take into account the
1020 // virtual size and scrolled position of the window.
1021
1022 int x = 0, y = 0, w = 0, h = 0;
1023 CalcScrolledPosition(0,0, &x,&y);
1024 m_win->GetVirtualSize(&w, &h);
1025 m_win->GetSizer()->SetDimension(x, y, w, h);
1026 return true;
1027 }
1028
1029 // fall back to default for LayoutConstraints
1030 return m_win->wxWindow::Layout();
1031 }
1032
ScrollDoSetVirtualSize(int x,int y)1033 void wxScrollHelper::ScrollDoSetVirtualSize(int x, int y)
1034 {
1035 m_win->wxWindow::DoSetVirtualSize( x, y );
1036 AdjustScrollbars();
1037
1038 if (m_win->GetAutoLayout())
1039 m_win->Layout();
1040 }
1041
1042 // wxWindow's GetBestVirtualSize returns the actual window size,
1043 // whereas we want to return the virtual size
ScrollGetBestVirtualSize() const1044 wxSize wxScrollHelper::ScrollGetBestVirtualSize() const
1045 {
1046 wxSize clientSize(m_win->GetClientSize());
1047 if ( m_win->GetSizer() )
1048 clientSize.IncTo(m_win->GetSizer()->CalcMin());
1049
1050 return clientSize;
1051 }
1052
1053 // return the window best size from the given best virtual size
1054 wxSize
ScrollGetWindowSizeForVirtualSize(const wxSize & size) const1055 wxScrollHelper::ScrollGetWindowSizeForVirtualSize(const wxSize& size) const
1056 {
1057 // Only use the content to set the window size in the direction
1058 // where there's no scrolling; otherwise we're going to get a huge
1059 // window in the direction in which scrolling is enabled
1060 int ppuX, ppuY;
1061 GetScrollPixelsPerUnit(&ppuX, &ppuY);
1062
1063 wxSize minSize = m_win->GetMinSize();
1064
1065 wxSize best(size);
1066 if (ppuX > 0)
1067 best.x = minSize.x + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
1068 if (ppuY > 0)
1069 best.y = minSize.y + wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y);
1070
1071 return best;
1072 }
1073
1074 // ----------------------------------------------------------------------------
1075 // event handlers
1076 // ----------------------------------------------------------------------------
1077
1078 // Default OnSize resets scrollbars, if any
HandleOnSize(wxSizeEvent & WXUNUSED (event))1079 void wxScrollHelper::HandleOnSize(wxSizeEvent& WXUNUSED(event))
1080 {
1081 if ( m_targetWindow->GetAutoLayout() )
1082 {
1083 wxSize size = m_targetWindow->GetBestVirtualSize();
1084
1085 // This will call ::Layout() and ::AdjustScrollbars()
1086 m_win->SetVirtualSize( size );
1087 }
1088 else
1089 {
1090 AdjustScrollbars();
1091 }
1092 }
1093
1094 // This calls OnDraw, having adjusted the origin according to the current
1095 // scroll position
HandleOnPaint(wxPaintEvent & WXUNUSED (event))1096 void wxScrollHelper::HandleOnPaint(wxPaintEvent& WXUNUSED(event))
1097 {
1098 // don't use m_targetWindow here, this is always called for ourselves
1099 wxPaintDC dc(m_win);
1100 DoPrepareDC(dc);
1101
1102 OnDraw(dc);
1103 }
1104
1105 // kbd handling: notice that we use OnChar() and not OnKeyDown() for
1106 // compatibility here - if we used OnKeyDown(), the programs which process
1107 // arrows themselves in their OnChar() would never get the message and like
1108 // this they always have the priority
HandleOnChar(wxKeyEvent & event)1109 void wxScrollHelper::HandleOnChar(wxKeyEvent& event)
1110 {
1111 int stx = 0, sty = 0, // view origin
1112 szx = 0, szy = 0, // view size (total)
1113 clix = 0, cliy = 0; // view size (on screen)
1114
1115 GetViewStart(&stx, &sty);
1116 GetTargetSize(&clix, &cliy);
1117 m_targetWindow->GetVirtualSize(&szx, &szy);
1118
1119 if( m_xScrollPixelsPerLine )
1120 {
1121 clix /= m_xScrollPixelsPerLine;
1122 szx /= m_xScrollPixelsPerLine;
1123 }
1124 else
1125 {
1126 clix = 0;
1127 szx = -1;
1128 }
1129 if( m_yScrollPixelsPerLine )
1130 {
1131 cliy /= m_yScrollPixelsPerLine;
1132 szy /= m_yScrollPixelsPerLine;
1133 }
1134 else
1135 {
1136 cliy = 0;
1137 szy = -1;
1138 }
1139
1140 int xScrollOld = m_xScrollPosition,
1141 yScrollOld = m_yScrollPosition;
1142
1143 int dsty;
1144 switch ( event.GetKeyCode() )
1145 {
1146 case WXK_PAGEUP:
1147 dsty = sty - (5 * cliy / 6);
1148 Scroll(-1, (dsty == -1) ? 0 : dsty);
1149 break;
1150
1151 case WXK_PAGEDOWN:
1152 Scroll(-1, sty + (5 * cliy / 6));
1153 break;
1154
1155 case WXK_HOME:
1156 Scroll(0, event.ControlDown() ? 0 : -1);
1157 break;
1158
1159 case WXK_END:
1160 Scroll(szx - clix, event.ControlDown() ? szy - cliy : -1);
1161 break;
1162
1163 case WXK_UP:
1164 Scroll(-1, sty - 1);
1165 break;
1166
1167 case WXK_DOWN:
1168 Scroll(-1, sty + 1);
1169 break;
1170
1171 case WXK_LEFT:
1172 Scroll(stx - 1, -1);
1173 break;
1174
1175 case WXK_RIGHT:
1176 Scroll(stx + 1, -1);
1177 break;
1178
1179 default:
1180 // not for us
1181 event.Skip();
1182 }
1183
1184 if ( m_xScrollPosition != xScrollOld )
1185 {
1186 wxScrollWinEvent event(wxEVT_SCROLLWIN_THUMBTRACK, m_xScrollPosition,
1187 wxHORIZONTAL);
1188 event.SetEventObject(m_win);
1189 m_win->GetEventHandler()->ProcessEvent(event);
1190 }
1191
1192 if ( m_yScrollPosition != yScrollOld )
1193 {
1194 wxScrollWinEvent event(wxEVT_SCROLLWIN_THUMBTRACK, m_yScrollPosition,
1195 wxVERTICAL);
1196 event.SetEventObject(m_win);
1197 m_win->GetEventHandler()->ProcessEvent(event);
1198 }
1199 }
1200
1201 // ----------------------------------------------------------------------------
1202 // autoscroll stuff: these functions deal with sending fake scroll events when
1203 // a captured mouse is being held outside the window
1204 // ----------------------------------------------------------------------------
1205
SendAutoScrollEvents(wxScrollWinEvent & event) const1206 bool wxScrollHelper::SendAutoScrollEvents(wxScrollWinEvent& event) const
1207 {
1208 // only send the event if the window is scrollable in this direction
1209 wxWindow *win = (wxWindow *)event.GetEventObject();
1210 return win->HasScrollbar(event.GetOrientation());
1211 }
1212
StopAutoScrolling()1213 void wxScrollHelper::StopAutoScrolling()
1214 {
1215 #if wxUSE_TIMER
1216 if ( m_timerAutoScroll )
1217 {
1218 delete m_timerAutoScroll;
1219 m_timerAutoScroll = (wxTimer *)NULL;
1220 }
1221 #endif
1222 }
1223
HandleOnMouseEnter(wxMouseEvent & event)1224 void wxScrollHelper::HandleOnMouseEnter(wxMouseEvent& event)
1225 {
1226 StopAutoScrolling();
1227
1228 event.Skip();
1229 }
1230
HandleOnMouseLeave(wxMouseEvent & event)1231 void wxScrollHelper::HandleOnMouseLeave(wxMouseEvent& event)
1232 {
1233 // don't prevent the usual processing of the event from taking place
1234 event.Skip();
1235
1236 // when a captured mouse leave a scrolled window we start generate
1237 // scrolling events to allow, for example, extending selection beyond the
1238 // visible area in some controls
1239 if ( wxWindow::GetCapture() == m_targetWindow )
1240 {
1241 // where is the mouse leaving?
1242 int pos, orient;
1243 wxPoint pt = event.GetPosition();
1244 if ( pt.x < 0 )
1245 {
1246 orient = wxHORIZONTAL;
1247 pos = 0;
1248 }
1249 else if ( pt.y < 0 )
1250 {
1251 orient = wxVERTICAL;
1252 pos = 0;
1253 }
1254 else // we're lower or to the right of the window
1255 {
1256 wxSize size = m_targetWindow->GetClientSize();
1257 if ( pt.x > size.x )
1258 {
1259 orient = wxHORIZONTAL;
1260 pos = m_xScrollLines;
1261 }
1262 else if ( pt.y > size.y )
1263 {
1264 orient = wxVERTICAL;
1265 pos = m_yScrollLines;
1266 }
1267 else // this should be impossible
1268 {
1269 // but seems to happen sometimes under wxMSW - maybe it's a bug
1270 // there but for now just ignore it
1271
1272 //wxFAIL_MSG( _T("can't understand where has mouse gone") );
1273
1274 return;
1275 }
1276 }
1277
1278 // only start the auto scroll timer if the window can be scrolled in
1279 // this direction
1280 if ( !m_targetWindow->HasScrollbar(orient) )
1281 return;
1282
1283 #if wxUSE_TIMER
1284 delete m_timerAutoScroll;
1285 m_timerAutoScroll = new wxAutoScrollTimer
1286 (
1287 m_targetWindow, this,
1288 pos == 0 ? wxEVT_SCROLLWIN_LINEUP
1289 : wxEVT_SCROLLWIN_LINEDOWN,
1290 pos,
1291 orient
1292 );
1293 m_timerAutoScroll->Start(50); // FIXME: make configurable
1294 #else
1295 wxUnusedVar(pos);
1296 #endif
1297 }
1298 }
1299
1300 #if wxUSE_MOUSEWHEEL
1301
HandleOnMouseWheel(wxMouseEvent & event)1302 void wxScrollHelper::HandleOnMouseWheel(wxMouseEvent& event)
1303 {
1304 m_wheelRotation += event.GetWheelRotation();
1305 int lines = m_wheelRotation / event.GetWheelDelta();
1306 m_wheelRotation -= lines * event.GetWheelDelta();
1307
1308 if (lines != 0)
1309 {
1310
1311 wxScrollWinEvent newEvent;
1312
1313 newEvent.SetPosition(0);
1314 newEvent.SetOrientation(wxVERTICAL);
1315 newEvent.SetEventObject(m_win);
1316
1317 if (event.IsPageScroll())
1318 {
1319 if (lines > 0)
1320 newEvent.SetEventType(wxEVT_SCROLLWIN_PAGEUP);
1321 else
1322 newEvent.SetEventType(wxEVT_SCROLLWIN_PAGEDOWN);
1323
1324 m_win->GetEventHandler()->ProcessEvent(newEvent);
1325 }
1326 else
1327 {
1328 lines *= event.GetLinesPerAction();
1329 if (lines > 0)
1330 newEvent.SetEventType(wxEVT_SCROLLWIN_LINEUP);
1331 else
1332 newEvent.SetEventType(wxEVT_SCROLLWIN_LINEDOWN);
1333
1334 int times = abs(lines);
1335 for (; times > 0; times--)
1336 m_win->GetEventHandler()->ProcessEvent(newEvent);
1337 }
1338 }
1339 }
1340
1341 #endif // wxUSE_MOUSEWHEEL
1342
HandleOnChildFocus(wxChildFocusEvent & event)1343 void wxScrollHelper::HandleOnChildFocus(wxChildFocusEvent& event)
1344 {
1345 // this event should be processed by all windows in parenthood chain,
1346 // e.g. so that nested wxScrolledWindows work correctly
1347 event.Skip();
1348
1349 // find the immediate child under which the window receiving focus is:
1350 wxWindow *win = event.GetWindow();
1351
1352 if ( win == m_targetWindow )
1353 return; // nothing to do
1354
1355 #ifdef __WXMAC__
1356 if (wxDynamicCast(win, wxScrollBar))
1357 return;
1358 #endif
1359
1360 // Fixing ticket: http://trac.wxwidgets.org/ticket/9563
1361 // When a child inside a wxControlContainer receives a focus, the
1362 // wxControlContainer generates an artificial wxChildFocusEvent for
1363 // itself, telling its parent that 'it' received the focus. The effect is
1364 // that this->HandleOnChildFocus is called twice, first with the
1365 // artificial wxChildFocusEvent and then with the original event. We need
1366 // to ignore the artificial event here or otherwise HandleOnChildFocus
1367 // would first scroll the target window to make the entire
1368 // wxControlContainer visible and immediately afterwards scroll the target
1369 // window again to make the child widget visible. This leads to ugly
1370 // flickering when using nested wxPanels/wxScrolledWindows.
1371 //
1372 // Ignore this event if 'win' is derived from wxControlContainer AND its
1373 // parent is the m_targetWindow AND 'win' is not actually reciving the
1374 // focus (win != FindFocus). TODO: This affects all wxControlContainer
1375 // objects, but wxControlContainer is not part of the wxWidgets RTTI and
1376 // so wxDynamicCast(win, wxControlContainer) does not compile. Find a way
1377 // to determine if 'win' derives from wxControlContainer. Until then,
1378 // testing if 'win' derives from wxPanel will probably get >90% of all
1379 // cases.
1380
1381 wxWindow *actual_focus=wxWindow::FindFocus();
1382 if (win != actual_focus &&
1383 wxDynamicCast(win, wxPanel) != 0 &&
1384 win->GetParent() == m_targetWindow)
1385 // if win is a wxPanel and receives the focus, it should not be
1386 // scrolled into view
1387 return;
1388
1389 const wxRect viewRect(m_targetWindow->GetClientRect());
1390
1391 // For composite controls such as wxComboCtrl we should try to fit the
1392 // entire control inside the visible area of the target window, not just
1393 // the focused child of the control. Otherwise we'd make only the textctrl
1394 // part of a wxComboCtrl visible and the button would still be outside the
1395 // scrolled area. But do so only if the parent fits *entirely* inside the
1396 // scrolled window. In other situations, such as nested wxPanel or
1397 // wxScrolledWindows, the parent might be way to big to fit inside the
1398 // scrolled window. If that is the case, then make only the focused window
1399 // visible
1400 if ( win->GetParent() != m_targetWindow)
1401 {
1402 wxWindow *parent=win->GetParent();
1403 wxSize parent_size=parent->GetSize();
1404 if (parent_size.GetWidth() <= viewRect.GetWidth() &&
1405 parent_size.GetHeight() <= viewRect.GetHeight())
1406 // make the immediate parent visible instead of the focused control
1407 win=parent;
1408 }
1409
1410 // make win position relative to the m_targetWindow viewing area instead of
1411 // its parent
1412 const wxRect
1413 winRect(m_targetWindow->ScreenToClient(win->GetScreenPosition()),
1414 win->GetSize());
1415
1416 // check if it's fully visible
1417 if ( viewRect.Contains(winRect) )
1418 {
1419 // it is, nothing to do
1420 return;
1421 }
1422
1423 // check if we can make it fully visible: this is only possible if it's not
1424 // larger than our view area
1425 if ( winRect.GetWidth() > viewRect.GetWidth() ||
1426 winRect.GetHeight() > viewRect.GetHeight() )
1427 {
1428 // we can't make it fit so avoid scrolling it at all, this is only
1429 // going to be confusing and not helpful
1430 return;
1431 }
1432
1433
1434 // do make the window fit inside the view area by scrolling to it
1435 int stepx, stepy;
1436 GetScrollPixelsPerUnit(&stepx, &stepy);
1437
1438 int startx, starty;
1439 GetViewStart(&startx, &starty);
1440
1441 // first in vertical direction:
1442 if ( stepy > 0 )
1443 {
1444 int diff = 0;
1445
1446 if ( winRect.GetTop() < 0 )
1447 {
1448 diff = winRect.GetTop();
1449 }
1450 else if ( winRect.GetBottom() > viewRect.GetHeight() )
1451 {
1452 diff = winRect.GetBottom() - viewRect.GetHeight() + 1;
1453 // round up to next scroll step if we can't get exact position,
1454 // so that the window is fully visible:
1455 diff += stepy - 1;
1456 }
1457
1458 starty = (starty * stepy + diff) / stepy;
1459 }
1460
1461 // then horizontal:
1462 if ( stepx > 0 )
1463 {
1464 int diff = 0;
1465
1466 if ( winRect.GetLeft() < 0 )
1467 {
1468 diff = winRect.GetLeft();
1469 }
1470 else if ( winRect.GetRight() > viewRect.GetWidth() )
1471 {
1472 diff = winRect.GetRight() - viewRect.GetWidth() + 1;
1473 // round up to next scroll step if we can't get exact position,
1474 // so that the window is fully visible:
1475 diff += stepx - 1;
1476 }
1477
1478 startx = (startx * stepx + diff) / stepx;
1479 }
1480
1481 Scroll(startx, starty);
1482 }
1483
1484 // ----------------------------------------------------------------------------
1485 // wxScrolledWindow implementation
1486 // ----------------------------------------------------------------------------
1487
IMPLEMENT_DYNAMIC_CLASS(wxScrolledWindow,wxPanel)1488 IMPLEMENT_DYNAMIC_CLASS(wxScrolledWindow, wxPanel)
1489
1490 BEGIN_EVENT_TABLE(wxScrolledWindow, wxPanel)
1491 EVT_PAINT(wxScrolledWindow::OnPaint)
1492 END_EVENT_TABLE()
1493
1494 bool wxScrolledWindow::Create(wxWindow *parent,
1495 wxWindowID id,
1496 const wxPoint& pos,
1497 const wxSize& size,
1498 long style,
1499 const wxString& name)
1500 {
1501 m_targetWindow = this;
1502 #ifdef __WXMAC__
1503 MacSetClipChildren( true ) ;
1504 #endif
1505
1506 bool ok = wxPanel::Create(parent, id, pos, size, style|wxHSCROLL|wxVSCROLL, name);
1507
1508 return ok;
1509 }
1510
~wxScrolledWindow()1511 wxScrolledWindow::~wxScrolledWindow()
1512 {
1513 }
1514
OnPaint(wxPaintEvent & event)1515 void wxScrolledWindow::OnPaint(wxPaintEvent& event)
1516 {
1517 // the user code didn't really draw the window if we got here, so set this
1518 // flag to try to call OnDraw() later
1519 m_handler->ResetDrawnFlag();
1520
1521 event.Skip();
1522 }
1523
1524 #ifdef __WXMSW__
MSWWindowProc(WXUINT nMsg,WXWPARAM wParam,WXLPARAM lParam)1525 WXLRESULT wxScrolledWindow::MSWWindowProc(WXUINT nMsg,
1526 WXWPARAM wParam,
1527 WXLPARAM lParam)
1528 {
1529 WXLRESULT rc = wxPanel::MSWWindowProc(nMsg, wParam, lParam);
1530
1531 #ifndef __WXWINCE__
1532 // we need to process arrows ourselves for scrolling
1533 if ( nMsg == WM_GETDLGCODE )
1534 {
1535 rc |= DLGC_WANTARROWS;
1536 }
1537 #endif
1538
1539 return rc;
1540 }
1541
1542 #endif // __WXMSW__
1543