Include wx/utils.h according to precompiled headers of wx/wx.h (with other minor...
[wxWidgets.git] / src / generic / scrlwing.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/scrolwin.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$
9 // Copyright: (c) wxWidgets team
10 // Licence: wxWindows licence
11 /////////////////////////////////////////////////////////////////////////////
12
13 // ============================================================================
14 // declarations
15 // ============================================================================
16
17 // ----------------------------------------------------------------------------
18 // headers
19 // ----------------------------------------------------------------------------
20
21 #ifdef __VMS
22 #define XtDisplay XTDISPLAY
23 #endif
24
25 // For compilers that support precompilation, includes "wx.h".
26 #include "wx/wxprec.h"
27
28 #ifdef __BORLANDC__
29 #pragma hdrstop
30 #endif
31
32 #include "wx/scrolwin.h"
33
34 #ifndef WX_PRECOMP
35 #include "wx/utils.h"
36 #endif
37
38 #include "wx/dcclient.h"
39
40 #include "wx/panel.h"
41 #if wxUSE_TIMER
42 #include "wx/timer.h"
43 #endif
44 #include "wx/sizer.h"
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:
77 wxScrollHelperEvtHandler(wxScrollHelper *scrollHelper)
78 {
79 m_scrollHelper = scrollHelper;
80 }
81
82 virtual bool ProcessEvent(wxEvent& event);
83
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
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
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
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_SCROLLWIN_TOP ||
246 evType == wxEVT_SCROLLWIN_BOTTOM ||
247 evType == wxEVT_SCROLLWIN_LINEUP ||
248 evType == wxEVT_SCROLLWIN_LINEDOWN ||
249 evType == wxEVT_SCROLLWIN_PAGEUP ||
250 evType == wxEVT_SCROLLWIN_PAGEDOWN ||
251 evType == wxEVT_SCROLLWIN_THUMBTRACK ||
252 evType == wxEVT_SCROLLWIN_THUMBRELEASE )
253 {
254 m_scrollHelper->HandleOnScroll((wxScrollWinEvent &)event);
255 return !event.GetSkipped();
256 }
257
258 if ( evType == wxEVT_ENTER_WINDOW )
259 {
260 m_scrollHelper->HandleOnMouseEnter((wxMouseEvent &)event);
261 }
262 else if ( evType == wxEVT_LEAVE_WINDOW )
263 {
264 m_scrollHelper->HandleOnMouseLeave((wxMouseEvent &)event);
265 }
266 #if wxUSE_MOUSEWHEEL
267 else if ( evType == wxEVT_MOUSEWHEEL )
268 {
269 m_scrollHelper->HandleOnMouseWheel((wxMouseEvent &)event);
270 }
271 #endif // wxUSE_MOUSEWHEEL
272 else if ( evType == wxEVT_CHAR )
273 {
274 m_scrollHelper->HandleOnChar((wxKeyEvent &)event);
275 return !event.GetSkipped();
276 }
277
278 return false;
279 }
280
281 // ----------------------------------------------------------------------------
282 // wxScrollHelper construction
283 // ----------------------------------------------------------------------------
284
285 wxScrollHelper::wxScrollHelper(wxWindow *win)
286 {
287 wxASSERT_MSG( win, _T("associated window can't be NULL in wxScrollHelper") );
288
289 m_xScrollPixelsPerLine =
290 m_yScrollPixelsPerLine =
291 m_xScrollPosition =
292 m_yScrollPosition =
293 m_xScrollLines =
294 m_yScrollLines =
295 m_xScrollLinesPerPage =
296 m_yScrollLinesPerPage = 0;
297
298 m_xScrollingEnabled =
299 m_yScrollingEnabled = true;
300
301 m_scaleX =
302 m_scaleY = 1.0;
303 #if wxUSE_MOUSEWHEEL
304 m_wheelRotation = 0;
305 #endif
306
307 m_win =
308 m_targetWindow = (wxWindow *)NULL;
309
310 m_timerAutoScroll = (wxTimer *)NULL;
311
312 m_handler = NULL;
313
314 m_win = win;
315
316 // by default, the associated window is also the target window
317 DoSetTargetWindow(win);
318 }
319
320 wxScrollHelper::~wxScrollHelper()
321 {
322 StopAutoScrolling();
323
324 DeleteEvtHandler();
325 }
326
327 // ----------------------------------------------------------------------------
328 // setting scrolling parameters
329 // ----------------------------------------------------------------------------
330
331 void wxScrollHelper::SetScrollbars(int pixelsPerUnitX,
332 int pixelsPerUnitY,
333 int noUnitsX,
334 int noUnitsY,
335 int xPos,
336 int yPos,
337 bool noRefresh)
338 {
339 int xpos, ypos;
340
341 CalcUnscrolledPosition(xPos, yPos, &xpos, &ypos);
342 bool do_refresh =
343 (
344 (noUnitsX != 0 && m_xScrollLines == 0) ||
345 (noUnitsX < m_xScrollLines && xpos > pixelsPerUnitX * noUnitsX) ||
346
347 (noUnitsY != 0 && m_yScrollLines == 0) ||
348 (noUnitsY < m_yScrollLines && ypos > pixelsPerUnitY * noUnitsY) ||
349 (xPos != m_xScrollPosition) ||
350 (yPos != m_yScrollPosition)
351 );
352
353 m_xScrollPixelsPerLine = pixelsPerUnitX;
354 m_yScrollPixelsPerLine = pixelsPerUnitY;
355 m_xScrollPosition = xPos;
356 m_yScrollPosition = yPos;
357
358 int w = noUnitsX * pixelsPerUnitX;
359 int h = noUnitsY * pixelsPerUnitY;
360
361 // For better backward compatibility we set persisting limits
362 // here not just the size. It makes SetScrollbars 'sticky'
363 // emulating the old non-autoscroll behaviour.
364 // m_targetWindow->SetVirtualSizeHints( w, h );
365
366 // The above should arguably be deprecated, this however we still need.
367
368 // take care not to set 0 virtual size, 0 means that we don't have any
369 // scrollbars and hence we should use the real size instead of the virtual
370 // one which is indicated by using wxDefaultCoord
371 m_targetWindow->SetVirtualSize( w ? w : wxDefaultCoord,
372 h ? h : wxDefaultCoord);
373
374 if (do_refresh && !noRefresh)
375 m_targetWindow->Refresh(true, GetScrollRect());
376
377 #ifndef __WXUNIVERSAL__
378 // If the target is not the same as the window with the scrollbars,
379 // then we need to update the scrollbars here, since they won't have
380 // been updated by SetVirtualSize().
381 if ( m_targetWindow != m_win )
382 #endif // !__WXUNIVERSAL__
383 {
384 AdjustScrollbars();
385 }
386 #ifndef __WXUNIVERSAL__
387 else
388 {
389 // otherwise this has been done by AdjustScrollbars, above
390 }
391 #endif // !__WXUNIVERSAL__
392 }
393
394 // ----------------------------------------------------------------------------
395 // [target] window handling
396 // ----------------------------------------------------------------------------
397
398 void wxScrollHelper::DeleteEvtHandler()
399 {
400 // search for m_handler in the handler list
401 if ( m_win && m_handler )
402 {
403 if ( m_win->RemoveEventHandler(m_handler) )
404 {
405 delete m_handler;
406 }
407 //else: something is very wrong, so better [maybe] leak memory than
408 // risk a crash because of double deletion
409
410 m_handler = NULL;
411 }
412 }
413
414 void wxScrollHelper::DoSetTargetWindow(wxWindow *target)
415 {
416 m_targetWindow = target;
417 #ifdef __WXMAC__
418 target->MacSetClipChildren( true ) ;
419 #endif
420
421 // install the event handler which will intercept the events we're
422 // interested in (but only do it for our real window, not the target window
423 // which we scroll - we don't need to hijack its events)
424 if ( m_targetWindow == m_win )
425 {
426 // if we already have a handler, delete it first
427 DeleteEvtHandler();
428
429 m_handler = new wxScrollHelperEvtHandler(this);
430 m_targetWindow->PushEventHandler(m_handler);
431 }
432 }
433
434 void wxScrollHelper::SetTargetWindow(wxWindow *target)
435 {
436 wxCHECK_RET( target, wxT("target window must not be NULL") );
437
438 if ( target == m_targetWindow )
439 return;
440
441 DoSetTargetWindow(target);
442 }
443
444 wxWindow *wxScrollHelper::GetTargetWindow() const
445 {
446 return m_targetWindow;
447 }
448
449 // ----------------------------------------------------------------------------
450 // scrolling implementation itself
451 // ----------------------------------------------------------------------------
452
453 void wxScrollHelper::HandleOnScroll(wxScrollWinEvent& event)
454 {
455 int nScrollInc = CalcScrollInc(event);
456 if ( nScrollInc == 0 )
457 {
458 // can't scroll further
459 event.Skip();
460
461 return;
462 }
463
464 int orient = event.GetOrientation();
465 if (orient == wxHORIZONTAL)
466 {
467 m_xScrollPosition += nScrollInc;
468 m_win->SetScrollPos(wxHORIZONTAL, m_xScrollPosition);
469 }
470 else
471 {
472 m_yScrollPosition += nScrollInc;
473 m_win->SetScrollPos(wxVERTICAL, m_yScrollPosition);
474 }
475
476 bool needsRefresh = false;
477 int dx = 0,
478 dy = 0;
479 if (orient == wxHORIZONTAL)
480 {
481 if ( m_xScrollingEnabled )
482 {
483 dx = -m_xScrollPixelsPerLine * nScrollInc;
484 }
485 else
486 {
487 needsRefresh = true;
488 }
489 }
490 else
491 {
492 if ( m_yScrollingEnabled )
493 {
494 dy = -m_yScrollPixelsPerLine * nScrollInc;
495 }
496 else
497 {
498 needsRefresh = true;
499 }
500 }
501
502 if ( needsRefresh )
503 {
504 m_targetWindow->Refresh(true, GetScrollRect());
505 }
506 else
507 {
508 m_targetWindow->ScrollWindow(dx, dy, GetScrollRect());
509 }
510 }
511
512 int wxScrollHelper::CalcScrollInc(wxScrollWinEvent& event)
513 {
514 int pos = event.GetPosition();
515 int orient = event.GetOrientation();
516
517 int nScrollInc = 0;
518 if (event.GetEventType() == wxEVT_SCROLLWIN_TOP)
519 {
520 if (orient == wxHORIZONTAL)
521 nScrollInc = - m_xScrollPosition;
522 else
523 nScrollInc = - m_yScrollPosition;
524 } else
525 if (event.GetEventType() == wxEVT_SCROLLWIN_BOTTOM)
526 {
527 if (orient == wxHORIZONTAL)
528 nScrollInc = m_xScrollLines - m_xScrollPosition;
529 else
530 nScrollInc = m_yScrollLines - m_yScrollPosition;
531 } else
532 if (event.GetEventType() == wxEVT_SCROLLWIN_LINEUP)
533 {
534 nScrollInc = -1;
535 } else
536 if (event.GetEventType() == wxEVT_SCROLLWIN_LINEDOWN)
537 {
538 nScrollInc = 1;
539 } else
540 if (event.GetEventType() == wxEVT_SCROLLWIN_PAGEUP)
541 {
542 if (orient == wxHORIZONTAL)
543 nScrollInc = -GetScrollPageSize(wxHORIZONTAL);
544 else
545 nScrollInc = -GetScrollPageSize(wxVERTICAL);
546 } else
547 if (event.GetEventType() == wxEVT_SCROLLWIN_PAGEDOWN)
548 {
549 if (orient == wxHORIZONTAL)
550 nScrollInc = GetScrollPageSize(wxHORIZONTAL);
551 else
552 nScrollInc = GetScrollPageSize(wxVERTICAL);
553 } else
554 if ((event.GetEventType() == wxEVT_SCROLLWIN_THUMBTRACK) ||
555 (event.GetEventType() == wxEVT_SCROLLWIN_THUMBRELEASE))
556 {
557 if (orient == wxHORIZONTAL)
558 nScrollInc = pos - m_xScrollPosition;
559 else
560 nScrollInc = pos - m_yScrollPosition;
561 }
562
563 if (orient == wxHORIZONTAL)
564 {
565 if (m_xScrollPixelsPerLine > 0)
566 {
567 if ( m_xScrollPosition + nScrollInc < 0 )
568 {
569 // As -ve as we can go
570 nScrollInc = -m_xScrollPosition;
571 }
572 else // check for the other bound
573 {
574 const int posMax = m_xScrollLines - m_xScrollLinesPerPage;
575 if ( m_xScrollPosition + nScrollInc > posMax )
576 {
577 // As +ve as we can go
578 nScrollInc = posMax - m_xScrollPosition;
579 }
580 }
581 }
582 else
583 m_targetWindow->Refresh(true, GetScrollRect());
584 }
585 else
586 {
587 if ( m_yScrollPixelsPerLine > 0 )
588 {
589 if ( m_yScrollPosition + nScrollInc < 0 )
590 {
591 // As -ve as we can go
592 nScrollInc = -m_yScrollPosition;
593 }
594 else // check for the other bound
595 {
596 const int posMax = m_yScrollLines - m_yScrollLinesPerPage;
597 if ( m_yScrollPosition + nScrollInc > posMax )
598 {
599 // As +ve as we can go
600 nScrollInc = posMax - m_yScrollPosition;
601 }
602 }
603 }
604 else
605 {
606 // VZ: why do we do this? (FIXME)
607 m_targetWindow->Refresh(true, GetScrollRect());
608 }
609 }
610
611 return nScrollInc;
612 }
613
614 // Adjust the scrollbars - new version.
615 void wxScrollHelper::AdjustScrollbars()
616 {
617 static wxRecursionGuardFlag s_flagReentrancy;
618 wxRecursionGuard guard(s_flagReentrancy);
619 if ( guard.IsInside() )
620 {
621 // don't reenter AdjustScrollbars() while another call to
622 // AdjustScrollbars() is in progress because this may lead to calling
623 // ScrollWindow() twice and this can really happen under MSW if
624 // SetScrollbar() call below adds or removes the scrollbar which
625 // changes the window size and hence results in another
626 // AdjustScrollbars() call
627 return;
628 }
629
630 int w = 0, h = 0;
631 int oldw, oldh;
632
633 int oldXScroll = m_xScrollPosition;
634 int oldYScroll = m_yScrollPosition;
635
636 // VZ: at least under Windows this loop is useless because when scrollbars
637 // [dis]appear we get a WM_SIZE resulting in another call to
638 // AdjustScrollbars() anyhow. As it doesn't seem to do any harm I leave
639 // it here for now but it would be better to ensure that all ports
640 // generate EVT_SIZE when scrollbars [dis]appear, emulating it if
641 // necessary, and remove it later
642 // JACS: Stop potential infinite loop by limiting number of iterations
643 int iterationCount = 0;
644 const int iterationMax = 5;
645 do
646 {
647 iterationCount ++;
648
649 GetTargetSize(&w, 0);
650
651 // scroll lines per page: if 0, no scrolling is needed
652 int linesPerPage;
653
654 if ( m_xScrollPixelsPerLine == 0 )
655 {
656 // scrolling is disabled
657 m_xScrollLines = 0;
658 m_xScrollPosition = 0;
659 linesPerPage = 0;
660 }
661 else // might need scrolling
662 {
663 // Round up integer division to catch any "leftover" client space.
664 const int wVirt = m_targetWindow->GetVirtualSize().GetWidth();
665 m_xScrollLines = (wVirt + m_xScrollPixelsPerLine - 1) / m_xScrollPixelsPerLine;
666
667 // Calculate page size i.e. number of scroll units you get on the
668 // current client window.
669 linesPerPage = w / m_xScrollPixelsPerLine;
670
671 // Special case. When client and virtual size are very close but
672 // the client is big enough, kill scrollbar.
673 if ((linesPerPage < m_xScrollLines) && (w >= wVirt)) ++linesPerPage;
674
675 if (linesPerPage >= m_xScrollLines)
676 {
677 // we're big enough to not need scrolling
678 linesPerPage =
679 m_xScrollLines =
680 m_xScrollPosition = 0;
681 }
682 else // we do need a scrollbar
683 {
684 if ( linesPerPage < 1 )
685 linesPerPage = 1;
686
687 // Correct position if greater than extent of canvas minus
688 // the visible portion of it or if below zero
689 const int posMax = m_xScrollLines - linesPerPage;
690 if ( m_xScrollPosition > posMax )
691 m_xScrollPosition = posMax;
692 else if ( m_xScrollPosition < 0 )
693 m_xScrollPosition = 0;
694 }
695 }
696
697 m_win->SetScrollbar(wxHORIZONTAL, m_xScrollPosition,
698 linesPerPage, m_xScrollLines);
699
700 // The amount by which we scroll when paging
701 SetScrollPageSize(wxHORIZONTAL, linesPerPage);
702
703 GetTargetSize(0, &h);
704
705 if ( m_yScrollPixelsPerLine == 0 )
706 {
707 // scrolling is disabled
708 m_yScrollLines = 0;
709 m_yScrollPosition = 0;
710 linesPerPage = 0;
711 }
712 else // might need scrolling
713 {
714 // Round up integer division to catch any "leftover" client space.
715 const int hVirt = m_targetWindow->GetVirtualSize().GetHeight();
716 m_yScrollLines = ( hVirt + m_yScrollPixelsPerLine - 1 ) / m_yScrollPixelsPerLine;
717
718 // Calculate page size i.e. number of scroll units you get on the
719 // current client window.
720 linesPerPage = h / m_yScrollPixelsPerLine;
721
722 // Special case. When client and virtual size are very close but
723 // the client is big enough, kill scrollbar.
724 if ((linesPerPage < m_yScrollLines) && (h >= hVirt)) ++linesPerPage;
725
726 if (linesPerPage >= m_yScrollLines)
727 {
728 // we're big enough to not need scrolling
729 linesPerPage =
730 m_yScrollLines =
731 m_yScrollPosition = 0;
732 }
733 else // we do need a scrollbar
734 {
735 if ( linesPerPage < 1 )
736 linesPerPage = 1;
737
738 // Correct position if greater than extent of canvas minus
739 // the visible portion of it or if below zero
740 const int posMax = m_yScrollLines - linesPerPage;
741 if ( m_yScrollPosition > posMax )
742 m_yScrollPosition = posMax;
743 else if ( m_yScrollPosition < 0 )
744 m_yScrollPosition = 0;
745 }
746 }
747
748 m_win->SetScrollbar(wxVERTICAL, m_yScrollPosition,
749 linesPerPage, m_yScrollLines);
750
751 // The amount by which we scroll when paging
752 SetScrollPageSize(wxVERTICAL, linesPerPage);
753
754
755 // If a scrollbar (dis)appeared as a result of this, adjust them again.
756 oldw = w;
757 oldh = h;
758
759 GetTargetSize( &w, &h );
760 } while ( (w != oldw || h != oldh) && (iterationCount < iterationMax) );
761
762 #ifdef __WXMOTIF__
763 // Sorry, some Motif-specific code to implement a backing pixmap
764 // for the wxRETAINED style. Implementing a backing store can't
765 // be entirely generic because it relies on the wxWindowDC implementation
766 // to duplicate X drawing calls for the backing pixmap.
767
768 if ( m_targetWindow->GetWindowStyle() & wxRETAINED )
769 {
770 Display* dpy = XtDisplay((Widget)m_targetWindow->GetMainWidget());
771
772 int totalPixelWidth = m_xScrollLines * m_xScrollPixelsPerLine;
773 int totalPixelHeight = m_yScrollLines * m_yScrollPixelsPerLine;
774 if (m_targetWindow->GetBackingPixmap() &&
775 !((m_targetWindow->GetPixmapWidth() == totalPixelWidth) &&
776 (m_targetWindow->GetPixmapHeight() == totalPixelHeight)))
777 {
778 XFreePixmap (dpy, (Pixmap) m_targetWindow->GetBackingPixmap());
779 m_targetWindow->SetBackingPixmap((WXPixmap) 0);
780 }
781
782 if (!m_targetWindow->GetBackingPixmap() &&
783 (m_xScrollLines != 0) && (m_yScrollLines != 0))
784 {
785 int depth = wxDisplayDepth();
786 m_targetWindow->SetPixmapWidth(totalPixelWidth);
787 m_targetWindow->SetPixmapHeight(totalPixelHeight);
788 m_targetWindow->SetBackingPixmap((WXPixmap) XCreatePixmap (dpy, RootWindow (dpy, DefaultScreen (dpy)),
789 m_targetWindow->GetPixmapWidth(), m_targetWindow->GetPixmapHeight(), depth));
790 }
791
792 }
793 #endif // Motif
794
795 if (oldXScroll != m_xScrollPosition)
796 {
797 if (m_xScrollingEnabled)
798 m_targetWindow->ScrollWindow( m_xScrollPixelsPerLine * (oldXScroll - m_xScrollPosition), 0,
799 GetScrollRect() );
800 else
801 m_targetWindow->Refresh(true, GetScrollRect());
802 }
803
804 if (oldYScroll != m_yScrollPosition)
805 {
806 if (m_yScrollingEnabled)
807 m_targetWindow->ScrollWindow( 0, m_yScrollPixelsPerLine * (oldYScroll-m_yScrollPosition),
808 GetScrollRect() );
809 else
810 m_targetWindow->Refresh(true, GetScrollRect());
811 }
812 }
813
814 void wxScrollHelper::DoPrepareDC(wxDC& dc)
815 {
816 wxPoint pt = dc.GetDeviceOrigin();
817 dc.SetDeviceOrigin( pt.x - m_xScrollPosition * m_xScrollPixelsPerLine,
818 pt.y - m_yScrollPosition * m_yScrollPixelsPerLine );
819 dc.SetUserScale( m_scaleX, m_scaleY );
820 }
821
822 void wxScrollHelper::SetScrollRate( int xstep, int ystep )
823 {
824 int old_x = m_xScrollPixelsPerLine * m_xScrollPosition;
825 int old_y = m_yScrollPixelsPerLine * m_yScrollPosition;
826
827 m_xScrollPixelsPerLine = xstep;
828 m_yScrollPixelsPerLine = ystep;
829
830 int new_x = m_xScrollPixelsPerLine * m_xScrollPosition;
831 int new_y = m_yScrollPixelsPerLine * m_yScrollPosition;
832
833 m_win->SetScrollPos( wxHORIZONTAL, m_xScrollPosition );
834 m_win->SetScrollPos( wxVERTICAL, m_yScrollPosition );
835 m_targetWindow->ScrollWindow( old_x - new_x, old_y - new_y );
836
837 AdjustScrollbars();
838 }
839
840 void wxScrollHelper::GetScrollPixelsPerUnit (int *x_unit, int *y_unit) const
841 {
842 if ( x_unit )
843 *x_unit = m_xScrollPixelsPerLine;
844 if ( y_unit )
845 *y_unit = m_yScrollPixelsPerLine;
846 }
847
848 int wxScrollHelper::GetScrollPageSize(int orient) const
849 {
850 if ( orient == wxHORIZONTAL )
851 return m_xScrollLinesPerPage;
852 else
853 return m_yScrollLinesPerPage;
854 }
855
856 void wxScrollHelper::SetScrollPageSize(int orient, int pageSize)
857 {
858 if ( orient == wxHORIZONTAL )
859 m_xScrollLinesPerPage = pageSize;
860 else
861 m_yScrollLinesPerPage = pageSize;
862 }
863
864 /*
865 * Scroll to given position (scroll position, not pixel position)
866 */
867 void wxScrollHelper::Scroll( int x_pos, int y_pos )
868 {
869 if (!m_targetWindow)
870 return;
871
872 if (((x_pos == -1) || (x_pos == m_xScrollPosition)) &&
873 ((y_pos == -1) || (y_pos == m_yScrollPosition))) return;
874
875 int w = 0, h = 0;
876 GetTargetSize(&w, &h);
877
878 if ((x_pos != -1) && (m_xScrollPixelsPerLine))
879 {
880 int old_x = m_xScrollPosition;
881 m_xScrollPosition = x_pos;
882
883 // Calculate page size i.e. number of scroll units you get on the
884 // current client window
885 int noPagePositions = (int) ( (w/(double)m_xScrollPixelsPerLine) + 0.5 );
886 if (noPagePositions < 1) noPagePositions = 1;
887
888 // Correct position if greater than extent of canvas minus
889 // the visible portion of it or if below zero
890 m_xScrollPosition = wxMin( m_xScrollLines-noPagePositions, m_xScrollPosition );
891 m_xScrollPosition = wxMax( 0, m_xScrollPosition );
892
893 if (old_x != m_xScrollPosition) {
894 m_win->SetScrollPos( wxHORIZONTAL, m_xScrollPosition );
895 m_targetWindow->ScrollWindow( (old_x-m_xScrollPosition)*m_xScrollPixelsPerLine, 0,
896 GetScrollRect() );
897 }
898 }
899 if ((y_pos != -1) && (m_yScrollPixelsPerLine))
900 {
901 int old_y = m_yScrollPosition;
902 m_yScrollPosition = y_pos;
903
904 // Calculate page size i.e. number of scroll units you get on the
905 // current client window
906 int noPagePositions = (int) ( (h/(double)m_yScrollPixelsPerLine) + 0.5 );
907 if (noPagePositions < 1) noPagePositions = 1;
908
909 // Correct position if greater than extent of canvas minus
910 // the visible portion of it or if below zero
911 m_yScrollPosition = wxMin( m_yScrollLines-noPagePositions, m_yScrollPosition );
912 m_yScrollPosition = wxMax( 0, m_yScrollPosition );
913
914 if (old_y != m_yScrollPosition) {
915 m_win->SetScrollPos( wxVERTICAL, m_yScrollPosition );
916 m_targetWindow->ScrollWindow( 0, (old_y-m_yScrollPosition)*m_yScrollPixelsPerLine,
917 GetScrollRect() );
918 }
919 }
920 }
921
922 void wxScrollHelper::EnableScrolling (bool x_scroll, bool y_scroll)
923 {
924 m_xScrollingEnabled = x_scroll;
925 m_yScrollingEnabled = y_scroll;
926 }
927
928 // Where the current view starts from
929 void wxScrollHelper::GetViewStart (int *x, int *y) const
930 {
931 if ( x )
932 *x = m_xScrollPosition;
933 if ( y )
934 *y = m_yScrollPosition;
935 }
936
937 void wxScrollHelper::DoCalcScrolledPosition(int x, int y, int *xx, int *yy) const
938 {
939 if ( xx )
940 *xx = x - m_xScrollPosition * m_xScrollPixelsPerLine;
941 if ( yy )
942 *yy = y - m_yScrollPosition * m_yScrollPixelsPerLine;
943 }
944
945 void wxScrollHelper::DoCalcUnscrolledPosition(int x, int y, int *xx, int *yy) const
946 {
947 if ( xx )
948 *xx = x + m_xScrollPosition * m_xScrollPixelsPerLine;
949 if ( yy )
950 *yy = y + m_yScrollPosition * m_yScrollPixelsPerLine;
951 }
952
953 // ----------------------------------------------------------------------------
954 // geometry
955 // ----------------------------------------------------------------------------
956
957 bool wxScrollHelper::ScrollLayout()
958 {
959 if ( m_win->GetSizer() && m_targetWindow == m_win )
960 {
961 // If we're the scroll target, take into account the
962 // virtual size and scrolled position of the window.
963
964 int x = 0, y = 0, w = 0, h = 0;
965 CalcScrolledPosition(0,0, &x,&y);
966 m_win->GetVirtualSize(&w, &h);
967 m_win->GetSizer()->SetDimension(x, y, w, h);
968 return true;
969 }
970
971 // fall back to default for LayoutConstraints
972 return m_win->wxWindow::Layout();
973 }
974
975 void wxScrollHelper::ScrollDoSetVirtualSize(int x, int y)
976 {
977 m_win->wxWindow::DoSetVirtualSize( x, y );
978 AdjustScrollbars();
979
980 if (m_win->GetAutoLayout())
981 m_win->Layout();
982 }
983
984 // wxWindow's GetBestVirtualSize returns the actual window size,
985 // whereas we want to return the virtual size
986 wxSize wxScrollHelper::ScrollGetBestVirtualSize() const
987 {
988 wxSize clientSize(m_win->GetClientSize());
989 if ( m_win->GetSizer() )
990 clientSize.IncTo(m_win->GetSizer()->CalcMin());
991
992 return clientSize;
993 }
994
995 // return the window best size from the given best virtual size
996 wxSize
997 wxScrollHelper::ScrollGetWindowSizeForVirtualSize(const wxSize& size) const
998 {
999 // Only use the content to set the window size in the direction
1000 // where there's no scrolling; otherwise we're going to get a huge
1001 // window in the direction in which scrolling is enabled
1002 int ppuX, ppuY;
1003 GetScrollPixelsPerUnit(&ppuX, &ppuY);
1004
1005 wxSize minSize = m_win->GetMinSize();
1006 if ( !minSize.IsFullySpecified() )
1007 minSize = m_win->GetSize();
1008
1009 wxSize best(size);
1010 if (ppuX > 0)
1011 best.x = minSize.x;
1012 if (ppuY > 0)
1013 best.y = minSize.y;
1014
1015 return best;
1016 }
1017
1018 // ----------------------------------------------------------------------------
1019 // event handlers
1020 // ----------------------------------------------------------------------------
1021
1022 // Default OnSize resets scrollbars, if any
1023 void wxScrollHelper::HandleOnSize(wxSizeEvent& WXUNUSED(event))
1024 {
1025 if ( m_targetWindow->GetAutoLayout() )
1026 {
1027 wxSize size = m_targetWindow->GetBestVirtualSize();
1028
1029 // This will call ::Layout() and ::AdjustScrollbars()
1030 m_win->SetVirtualSize( size );
1031 }
1032 else
1033 {
1034 AdjustScrollbars();
1035 }
1036 }
1037
1038 // This calls OnDraw, having adjusted the origin according to the current
1039 // scroll position
1040 void wxScrollHelper::HandleOnPaint(wxPaintEvent& WXUNUSED(event))
1041 {
1042 // don't use m_targetWindow here, this is always called for ourselves
1043 wxPaintDC dc(m_win);
1044 DoPrepareDC(dc);
1045
1046 OnDraw(dc);
1047 }
1048
1049 // kbd handling: notice that we use OnChar() and not OnKeyDown() for
1050 // compatibility here - if we used OnKeyDown(), the programs which process
1051 // arrows themselves in their OnChar() would never get the message and like
1052 // this they always have the priority
1053 void wxScrollHelper::HandleOnChar(wxKeyEvent& event)
1054 {
1055 int stx = 0, sty = 0, // view origin
1056 szx = 0, szy = 0, // view size (total)
1057 clix = 0, cliy = 0; // view size (on screen)
1058
1059 GetViewStart(&stx, &sty);
1060 GetTargetSize(&clix, &cliy);
1061 m_targetWindow->GetVirtualSize(&szx, &szy);
1062
1063 if( m_xScrollPixelsPerLine )
1064 {
1065 clix /= m_xScrollPixelsPerLine;
1066 szx /= m_xScrollPixelsPerLine;
1067 }
1068 else
1069 {
1070 clix = 0;
1071 szx = -1;
1072 }
1073 if( m_yScrollPixelsPerLine )
1074 {
1075 cliy /= m_yScrollPixelsPerLine;
1076 szy /= m_yScrollPixelsPerLine;
1077 }
1078 else
1079 {
1080 cliy = 0;
1081 szy = -1;
1082 }
1083
1084 int xScrollOld = m_xScrollPosition,
1085 yScrollOld = m_yScrollPosition;
1086
1087 int dsty;
1088 switch ( event.GetKeyCode() )
1089 {
1090 case WXK_PAGEUP:
1091 dsty = sty - (5 * cliy / 6);
1092 Scroll(-1, (dsty == -1) ? 0 : dsty);
1093 break;
1094
1095 case WXK_PAGEDOWN:
1096 Scroll(-1, sty + (5 * cliy / 6));
1097 break;
1098
1099 case WXK_HOME:
1100 Scroll(0, event.ControlDown() ? 0 : -1);
1101 break;
1102
1103 case WXK_END:
1104 Scroll(szx - clix, event.ControlDown() ? szy - cliy : -1);
1105 break;
1106
1107 case WXK_UP:
1108 Scroll(-1, sty - 1);
1109 break;
1110
1111 case WXK_DOWN:
1112 Scroll(-1, sty + 1);
1113 break;
1114
1115 case WXK_LEFT:
1116 Scroll(stx - 1, -1);
1117 break;
1118
1119 case WXK_RIGHT:
1120 Scroll(stx + 1, -1);
1121 break;
1122
1123 default:
1124 // not for us
1125 event.Skip();
1126 }
1127
1128 if ( m_xScrollPosition != xScrollOld )
1129 {
1130 wxScrollWinEvent event(wxEVT_SCROLLWIN_THUMBTRACK, m_xScrollPosition,
1131 wxHORIZONTAL);
1132 event.SetEventObject(m_win);
1133 m_win->GetEventHandler()->ProcessEvent(event);
1134 }
1135
1136 if ( m_yScrollPosition != yScrollOld )
1137 {
1138 wxScrollWinEvent event(wxEVT_SCROLLWIN_THUMBTRACK, m_yScrollPosition,
1139 wxVERTICAL);
1140 event.SetEventObject(m_win);
1141 m_win->GetEventHandler()->ProcessEvent(event);
1142 }
1143 }
1144
1145 // ----------------------------------------------------------------------------
1146 // autoscroll stuff: these functions deal with sending fake scroll events when
1147 // a captured mouse is being held outside the window
1148 // ----------------------------------------------------------------------------
1149
1150 bool wxScrollHelper::SendAutoScrollEvents(wxScrollWinEvent& event) const
1151 {
1152 // only send the event if the window is scrollable in this direction
1153 wxWindow *win = (wxWindow *)event.GetEventObject();
1154 return win->HasScrollbar(event.GetOrientation());
1155 }
1156
1157 void wxScrollHelper::StopAutoScrolling()
1158 {
1159 #if wxUSE_TIMER
1160 if ( m_timerAutoScroll )
1161 {
1162 delete m_timerAutoScroll;
1163 m_timerAutoScroll = (wxTimer *)NULL;
1164 }
1165 #endif
1166 }
1167
1168 void wxScrollHelper::HandleOnMouseEnter(wxMouseEvent& event)
1169 {
1170 StopAutoScrolling();
1171
1172 event.Skip();
1173 }
1174
1175 void wxScrollHelper::HandleOnMouseLeave(wxMouseEvent& event)
1176 {
1177 // don't prevent the usual processing of the event from taking place
1178 event.Skip();
1179
1180 // when a captured mouse leave a scrolled window we start generate
1181 // scrolling events to allow, for example, extending selection beyond the
1182 // visible area in some controls
1183 if ( wxWindow::GetCapture() == m_targetWindow )
1184 {
1185 // where is the mouse leaving?
1186 int pos, orient;
1187 wxPoint pt = event.GetPosition();
1188 if ( pt.x < 0 )
1189 {
1190 orient = wxHORIZONTAL;
1191 pos = 0;
1192 }
1193 else if ( pt.y < 0 )
1194 {
1195 orient = wxVERTICAL;
1196 pos = 0;
1197 }
1198 else // we're lower or to the right of the window
1199 {
1200 wxSize size = m_targetWindow->GetClientSize();
1201 if ( pt.x > size.x )
1202 {
1203 orient = wxHORIZONTAL;
1204 pos = m_xScrollLines;
1205 }
1206 else if ( pt.y > size.y )
1207 {
1208 orient = wxVERTICAL;
1209 pos = m_yScrollLines;
1210 }
1211 else // this should be impossible
1212 {
1213 // but seems to happen sometimes under wxMSW - maybe it's a bug
1214 // there but for now just ignore it
1215
1216 //wxFAIL_MSG( _T("can't understand where has mouse gone") );
1217
1218 return;
1219 }
1220 }
1221
1222 // only start the auto scroll timer if the window can be scrolled in
1223 // this direction
1224 if ( !m_targetWindow->HasScrollbar(orient) )
1225 return;
1226
1227 #if wxUSE_TIMER
1228 delete m_timerAutoScroll;
1229 m_timerAutoScroll = new wxAutoScrollTimer
1230 (
1231 m_targetWindow, this,
1232 pos == 0 ? wxEVT_SCROLLWIN_LINEUP
1233 : wxEVT_SCROLLWIN_LINEDOWN,
1234 pos,
1235 orient
1236 );
1237 m_timerAutoScroll->Start(50); // FIXME: make configurable
1238 #else
1239 wxUnusedVar(pos);
1240 #endif
1241 }
1242 }
1243
1244 #if wxUSE_MOUSEWHEEL
1245
1246 void wxScrollHelper::HandleOnMouseWheel(wxMouseEvent& event)
1247 {
1248 m_wheelRotation += event.GetWheelRotation();
1249 int lines = m_wheelRotation / event.GetWheelDelta();
1250 m_wheelRotation -= lines * event.GetWheelDelta();
1251
1252 if (lines != 0)
1253 {
1254
1255 wxScrollWinEvent newEvent;
1256
1257 newEvent.SetPosition(0);
1258 newEvent.SetOrientation(wxVERTICAL);
1259 newEvent.SetEventObject(m_win);
1260
1261 if (event.IsPageScroll())
1262 {
1263 if (lines > 0)
1264 newEvent.SetEventType(wxEVT_SCROLLWIN_PAGEUP);
1265 else
1266 newEvent.SetEventType(wxEVT_SCROLLWIN_PAGEDOWN);
1267
1268 m_win->GetEventHandler()->ProcessEvent(newEvent);
1269 }
1270 else
1271 {
1272 lines *= event.GetLinesPerAction();
1273 if (lines > 0)
1274 newEvent.SetEventType(wxEVT_SCROLLWIN_LINEUP);
1275 else
1276 newEvent.SetEventType(wxEVT_SCROLLWIN_LINEDOWN);
1277
1278 int times = abs(lines);
1279 for (; times > 0; times--)
1280 m_win->GetEventHandler()->ProcessEvent(newEvent);
1281 }
1282 }
1283 }
1284
1285 #endif // wxUSE_MOUSEWHEEL
1286
1287 // ----------------------------------------------------------------------------
1288 // wxScrolledWindow implementation
1289 // ----------------------------------------------------------------------------
1290
1291 IMPLEMENT_DYNAMIC_CLASS(wxScrolledWindow, wxPanel)
1292
1293 BEGIN_EVENT_TABLE(wxScrolledWindow, wxPanel)
1294 EVT_PAINT(wxScrolledWindow::OnPaint)
1295 END_EVENT_TABLE()
1296
1297 bool wxScrolledWindow::Create(wxWindow *parent,
1298 wxWindowID id,
1299 const wxPoint& pos,
1300 const wxSize& size,
1301 long style,
1302 const wxString& name)
1303 {
1304 m_targetWindow = this;
1305 #ifdef __WXMAC__
1306 MacSetClipChildren( true ) ;
1307 #endif
1308
1309 bool ok = wxPanel::Create(parent, id, pos, size, style|wxHSCROLL|wxVSCROLL, name);
1310
1311 return ok;
1312 }
1313
1314 wxScrolledWindow::~wxScrolledWindow()
1315 {
1316 }
1317
1318 void wxScrolledWindow::OnPaint(wxPaintEvent& event)
1319 {
1320 // the user code didn't really draw the window if we got here, so set this
1321 // flag to try to call OnDraw() later
1322 m_handler->ResetDrawnFlag();
1323
1324 event.Skip();
1325 }
1326
1327 #ifdef __WXMSW__
1328 WXLRESULT wxScrolledWindow::MSWWindowProc(WXUINT nMsg,
1329 WXWPARAM wParam,
1330 WXLPARAM lParam)
1331 {
1332 WXLRESULT rc = wxPanel::MSWWindowProc(nMsg, wParam, lParam);
1333
1334 #ifndef __WXWINCE__
1335 // we need to process arrows ourselves for scrolling
1336 if ( nMsg == WM_GETDLGCODE )
1337 {
1338 rc |= DLGC_WANTARROWS;
1339 }
1340 #endif
1341
1342 return rc;
1343 }
1344
1345 #endif // __WXMSW__