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