1 /********************************************************************************
2 *                                                                               *
3 *                      S c r o l l A r e a   W i d g e t                        *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 1998,2006 by Jeroen van der Zijp.   All Rights Reserved.        *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or                 *
9 * modify it under the terms of the GNU Lesser General Public                    *
10 * License as published by the Free Software Foundation; either                  *
11 * version 2.1 of the License, or (at your option) any later version.            *
12 *                                                                               *
13 * This library is distributed in the hope that it will be useful,               *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of                *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU             *
16 * Lesser General Public License for more details.                               *
17 *                                                                               *
18 * You should have received a copy of the GNU Lesser General Public              *
19 * License along with this library; if not, write to the Free Software           *
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.    *
21 *********************************************************************************
22 * $Id: FXScrollArea.cpp 4937 2019-03-10 19:59:30Z arthurcnorman $                    *
23 ********************************************************************************/
24 #include "xincs.h"
25 #include "fxver.h"
26 #include "fxdefs.h"
27 #include "FXHash.h"
28 #include "FXThread.h"
29 #include "FXStream.h"
30 #include "FXString.h"
31 #include "FXSize.h"
32 #include "FXPoint.h"
33 #include "FXRectangle.h"
34 #include "FXRegistry.h"
35 #include "FXAccelTable.h"
36 #include "FXApp.h"
37 #include "FXDCWindow.h"
38 #include "FXScrollBar.h"
39 #include "FXScrollArea.h"
40 
41 
42 /*
43   To do:
44   - In new HSCROLLING_OFF mode, default width should be computed
45     from contents (new virtual for that), and presence of scrollbars
46     (determined by flags, as well as need).
47   - The original content size should be returned from getContentWidth(),
48     and getContentHeight().
49   - When tabbing, we will never put focus on scrollbar.
50   - Perhaps scroll windows should observe FRAME_SUNKEN etc.
51   - Here's a new idea:- perhaps the scrollbars should be GUI-updated from the
52     FXScrollArea.  Then layout() will do nothing but place the bars.
53   - What if we want to keep two scrolled windows in sync, i.e. scroll them
54     both the same amount.
55   - Do we need to be able to map mouse wheel to horizontal scrollbar instead?
56     or perhaps even both ways (depending on whether scrolling is possible
57     in one or another direction).
58 */
59 
60 
61 #define AUTOSCROLL_FUDGE  11       // Proximity to wall at which we start autoscrolling
62 #define SCROLLER_MASK     (HSCROLLER_ALWAYS|HSCROLLER_NEVER|VSCROLLER_ALWAYS|VSCROLLER_NEVER|SCROLLERS_DONT_TRACK)
63 
64 using namespace FX;
65 
66 /*******************************************************************************/
67 
68 namespace FX {
69 
70 FXDEFMAP(FXScrollArea) FXScrollAreaMap[]={
71   FXMAPFUNC(SEL_MOUSEWHEEL,0,FXScrollArea::onVMouseWheel),
72   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_HSCROLLED,FXScrollArea::onHScrollerChanged),
73   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_VSCROLLED,FXScrollArea::onVScrollerChanged),
74   FXMAPFUNC(SEL_CHANGED,FXWindow::ID_HSCROLLED,FXScrollArea::onHScrollerDragged),
75   FXMAPFUNC(SEL_CHANGED,FXWindow::ID_VSCROLLED,FXScrollArea::onVScrollerDragged),
76   FXMAPFUNC(SEL_TIMEOUT,FXWindow::ID_AUTOSCROLL,FXScrollArea::onAutoScroll),
77   };
78 
79 
80 // Object implementation
81 FXIMPLEMENT(FXScrollArea,FXComposite,FXScrollAreaMap,ARRAYNUMBER(FXScrollAreaMap))
82 
83 
84 // Scroll acceleration near edge
85 static const FXint acceleration[AUTOSCROLL_FUDGE+1]={1,1,1,2,3,4,6,7,8,16,32,64};
86 
87 
88 
89 // Deserialization
FXScrollArea()90 FXScrollArea::FXScrollArea(){
91   flags|=FLAG_SHOWN;
92   horizontal=NULL;
93   vertical=NULL;
94   corner=NULL;
95   viewport_w=1;
96   viewport_h=1;
97   pos_x=0;
98   pos_y=0;
99   }
100 
101 
102 // Construct and init
FXScrollArea(FXComposite * p,FXuint opts,FXint x,FXint y,FXint w,FXint h)103 FXScrollArea::FXScrollArea(FXComposite* p,FXuint opts,FXint x,FXint y,FXint w,FXint h):
104   FXComposite(p,opts,x,y,w,h){
105   FXuint jumpy=0;
106   flags|=FLAG_SHOWN;
107   if(opts&SCROLLERS_DONT_TRACK) jumpy=SCROLLBAR_WHEELJUMP;
108   horizontal=new FXScrollBar(this,this,FXWindow::ID_HSCROLLED,SCROLLBAR_HORIZONTAL|jumpy);
109   vertical=new FXScrollBar(this,this,FXWindow::ID_VSCROLLED,SCROLLBAR_VERTICAL|jumpy);
110   corner=new FXScrollCorner(this);
111   backColor=getApp()->getBackColor();
112   viewport_w=1;
113   viewport_h=1;
114   pos_x=0;
115   pos_y=0;
116   }
117 
118 
119 // This should really add the scroll bar size only when required; however,
120 // that depends on the actual size.  We are potentially being called at
121 // a moment when this is not known yet, so we return a size which reflects
122 // the situation when the scrollbars have been placed; this way, we should
123 // at least have enough space to fully see the contents, and a bit extra
124 // when the scrollbars turn out to have been unnecessary.
125 
126 // Get default width
getDefaultWidth()127 FXint FXScrollArea::getDefaultWidth(){
128   FXint w=0;
129   FXint t;
130   if((options&HSCROLLER_NEVER)&&(options&HSCROLLER_ALWAYS)) w=getContentWidth();
131   if(!(options&HSCROLLER_NEVER) && (t=horizontal->getDefaultWidth())>w) w=t;
132   if(!(options&VSCROLLER_NEVER)) w+=vertical->getDefaultWidth();
133   return FXMAX(w,1);
134   }
135 
136 
137 // Get default height
getDefaultHeight()138 FXint FXScrollArea::getDefaultHeight(){
139   FXint h=0;
140   FXint t;
141   if((options&VSCROLLER_NEVER)&&(options&VSCROLLER_ALWAYS)) h=getContentHeight();
142   if(!(options&VSCROLLER_NEVER) && (t=vertical->getDefaultHeight())>h) h=t;
143   if(!(options&HSCROLLER_NEVER)) h+=horizontal->getDefaultHeight();
144   return FXMAX(h,1);
145   }
146 
147 
148 // Move content
moveContents(FXint x,FXint y)149 void FXScrollArea::moveContents(FXint x,FXint y){
150   FXint dx=x-pos_x;
151   FXint dy=y-pos_y;
152   pos_x=x;
153   pos_y=y;
154   scroll(0,0,viewport_w,viewport_h,dx,dy);
155   }
156 
157 
158 // Changed
onHScrollerChanged(FXObject *,FXSelector,void * ptr)159 long FXScrollArea::onHScrollerChanged(FXObject*,FXSelector,void* ptr){
160   FXint new_x=-(FXint)(FXival)ptr;
161   if(new_x!=pos_x){
162     moveContents(new_x,pos_y);
163     }
164   flags&=~FLAG_TIP;
165   return 1;
166   }
167 
168 
169 // Changed
onVScrollerChanged(FXObject *,FXSelector,void * ptr)170 long FXScrollArea::onVScrollerChanged(FXObject*,FXSelector,void* ptr){
171   FXint new_y=-(FXint)(FXival)ptr;
172   if(new_y!=pos_y){
173     moveContents(pos_x,new_y);
174     }
175   flags&=~FLAG_TIP;
176   return 1;
177   }
178 
179 
180 // Dragged
onHScrollerDragged(FXObject *,FXSelector,void * ptr)181 long FXScrollArea::onHScrollerDragged(FXObject*,FXSelector,void* ptr){
182   if(!(options&SCROLLERS_DONT_TRACK)){
183     FXint new_x=-(FXint)(FXival)ptr;
184     if(new_x!=pos_x){
185       moveContents(new_x,pos_y);
186       }
187     }
188   flags&=~FLAG_TIP;
189   return 1;
190   }
191 
192 
193 // Dragged
onVScrollerDragged(FXObject *,FXSelector,void * ptr)194 long FXScrollArea::onVScrollerDragged(FXObject*,FXSelector,void* ptr){
195   if(!(options&SCROLLERS_DONT_TRACK)){
196     FXint new_y=-(FXint)(FXival)ptr;
197     if(new_y!=pos_y){
198       moveContents(pos_x,new_y);
199       }
200     }
201   flags&=~FLAG_TIP;
202   return 1;
203   }
204 
205 
206 // Mouse wheel used for vertical scrolling
onVMouseWheel(FXObject * sender,FXSelector sel,void * ptr)207 long FXScrollArea::onVMouseWheel(FXObject* sender,FXSelector sel,void* ptr){
208   vertical->handle(sender,sel,ptr);
209   return 1;
210   }
211 
212 
213 // Mouse wheel used for horizontal scrolling
onHMouseWheel(FXObject * sender,FXSelector sel,void * ptr)214 long FXScrollArea::onHMouseWheel(FXObject* sender,FXSelector sel,void* ptr){
215   horizontal->handle(sender,sel,ptr);
216   return 1;
217   }
218 
219 
220 // Timeout
onAutoScroll(FXObject *,FXSelector sel,void * ptr)221 long FXScrollArea::onAutoScroll(FXObject*,FXSelector sel,void* ptr){
222   FXEvent* event=(FXEvent*)ptr;
223   FXint dx=0;
224   FXint dy=0;
225 
226   // If scrolling only while inside, and not inside, we stop scrolling
227   if((flags&FLAG_SCROLLINSIDE) && !(0<=event->win_x && 0<=event->win_y && event->win_x<viewport_w && event->win_y<viewport_h)) return 0;
228 
229   // Figure scroll amount x
230   if(event->win_x<AUTOSCROLL_FUDGE) dx=AUTOSCROLL_FUDGE-event->win_x;
231   else if(viewport_w-AUTOSCROLL_FUDGE<=event->win_x) dx=viewport_w-AUTOSCROLL_FUDGE-event->win_x;
232 
233   // Figure scroll amount y
234   if(event->win_y<AUTOSCROLL_FUDGE) dy=AUTOSCROLL_FUDGE-event->win_y;
235   else if(viewport_h-AUTOSCROLL_FUDGE<=event->win_y) dy=viewport_h-AUTOSCROLL_FUDGE-event->win_y;
236 
237   // Keep autoscrolling
238   if(dx || dy){
239     FXint oldposx=pos_x;
240     FXint oldposy=pos_y;
241     if(flags&FLAG_SCROLLINSIDE){
242       FXASSERT(FXABS(dx)<=AUTOSCROLL_FUDGE);
243       FXASSERT(FXABS(dy)<=AUTOSCROLL_FUDGE);
244       dx*=acceleration[FXABS(dx)];
245       dy*=acceleration[FXABS(dy)];
246       }
247 
248     // Scroll a bit
249     setPosition(pos_x+dx,pos_y+dy);
250 
251     // Setup next timer if we can still scroll some more
252     if((pos_x!=oldposx) || (pos_y!=oldposy)){
253       getApp()->addTimeout(this,FXSELID(sel),getApp()->getScrollSpeed(),event);
254       }
255     }
256 
257   // Kill tip
258   flags&=~FLAG_TIP;
259   return 0;
260   }
261 
262 
263 // Start automatic scrolling
startAutoScroll(FXEvent * event,FXbool onlywheninside)264 FXbool FXScrollArea::startAutoScroll(FXEvent *event,FXbool onlywheninside){
265   FXbool autoscrolling=FALSE;
266   flags&=~FLAG_SCROLLINSIDE;
267   if(onlywheninside) flags|=FLAG_SCROLLINSIDE;
268   if(horizontal->getPage()<horizontal->getRange()){
269     if((event->win_x<AUTOSCROLL_FUDGE) && (0<horizontal->getPosition())) autoscrolling=TRUE;
270     else if((viewport_w-AUTOSCROLL_FUDGE<=event->win_x) && (horizontal->getPosition()<horizontal->getRange()-horizontal->getPage())) autoscrolling=TRUE;
271     }
272   if(vertical->getPage()<vertical->getRange()){
273     if((event->win_y<AUTOSCROLL_FUDGE) && (0<vertical->getPosition())) autoscrolling=TRUE;
274     else if((viewport_h-AUTOSCROLL_FUDGE<=event->win_y) && (vertical->getPosition()<vertical->getRange()-vertical->getPage())) autoscrolling=TRUE;
275     }
276   if(onlywheninside && (event->win_x<0 || event->win_y<0 || viewport_w<=event->win_x || viewport_h<=event->win_y)) autoscrolling=FALSE;
277   if(autoscrolling){
278     if(!getApp()->hasTimeout(this,ID_AUTOSCROLL)){
279       getApp()->addTimeout(this,ID_AUTOSCROLL,getApp()->getScrollSpeed(),event);
280       }
281     }
282   else{
283     getApp()->removeTimeout(this,ID_AUTOSCROLL);
284     }
285   return autoscrolling;
286   }
287 
288 
289 // Stop automatic scrolling
stopAutoScroll()290 void FXScrollArea::stopAutoScroll(){
291   getApp()->removeTimeout(this,ID_AUTOSCROLL);
292   flags&=~FLAG_SCROLLINSIDE;
293   }
294 
295 
296 // Set scroll style
setScrollStyle(FXuint style)297 void FXScrollArea::setScrollStyle(FXuint style){
298   FXuint opts=(options&~SCROLLER_MASK) | (style&SCROLLER_MASK);
299   if(options!=opts){
300     if(opts&SCROLLERS_DONT_TRACK){
301       horizontal->setScrollBarStyle(horizontal->getScrollBarStyle()|SCROLLBAR_WHEELJUMP);
302       vertical->setScrollBarStyle(vertical->getScrollBarStyle()|SCROLLBAR_WHEELJUMP);
303       }
304     else{
305       horizontal->setScrollBarStyle(horizontal->getScrollBarStyle()&~SCROLLBAR_WHEELJUMP);
306       vertical->setScrollBarStyle(vertical->getScrollBarStyle()&~SCROLLBAR_WHEELJUMP);
307       }
308     options=opts;
309     recalc();
310     }
311   }
312 
313 
314 // Get scroll style
getScrollStyle() const315 FXuint FXScrollArea::getScrollStyle() const {
316   return (options&SCROLLER_MASK);
317   }
318 
319 
320 // True if horizontally scrollable enabled
isHorizontalScrollable() const321 FXbool FXScrollArea::isHorizontalScrollable() const {
322   return !((options&HSCROLLER_NEVER) && (options&HSCROLLER_ALWAYS));
323   }
324 
325 
326 // True if vertically scrollable enabled
isVerticalScrollable() const327 FXbool FXScrollArea::isVerticalScrollable() const {
328   return !((options&VSCROLLER_NEVER) && (options&VSCROLLER_ALWAYS));
329   }
330 
331 
332 
333 // Default viewport width
getViewportWidth()334 FXint FXScrollArea::getViewportWidth(){
335   return width;
336   }
337 
338 
339 // Default viewport height
getViewportHeight()340 FXint FXScrollArea::getViewportHeight(){
341   return height;
342   }
343 
344 
345 // Determine minimum content width of scroll area
getContentWidth()346 FXint FXScrollArea::getContentWidth(){
347   return 1;
348   }
349 
350 
351 // Determine minimum content height of scroll area
getContentHeight()352 FXint FXScrollArea::getContentHeight(){
353   return 1;
354   }
355 
356 
357 // Recalculate layout
layout()358 void FXScrollArea::layout(){
359   FXint new_x,new_y,content_w,content_h;
360   FXint sh_h=0;
361   FXint sv_w=0;
362 
363   // Inviolate
364   FXASSERT(pos_x<=0 && pos_y<=0);
365 
366   // Initial viewport size
367   viewport_w=getViewportWidth();
368   viewport_h=getViewportHeight();
369 
370   // ALWAYS determine content size
371   content_w=getContentWidth();
372   content_h=getContentHeight();
373 
374   // Get dimensions of the scroll bars
375   if(!(options&HSCROLLER_NEVER)) sh_h=horizontal->getDefaultHeight();
376   if(!(options&VSCROLLER_NEVER)) sv_w=vertical->getDefaultWidth();
377 
378   // Should we disable the scroll bars?  A bit tricky as the scrollbars
379   // may influence each other's presence.  Also, we don't allow more than
380   // 50% of the viewport to be taken up by scrollbars; when the scrollbars
381   // take up more than 50% of the available space we simply turn them off.
382   if(!(options&(HSCROLLER_ALWAYS|VSCROLLER_ALWAYS)) && (content_w<=viewport_w) && (content_h<=viewport_h)){sh_h=sv_w=0;}
383   if(!(options&HSCROLLER_ALWAYS) && ((content_w<=viewport_w-sv_w) || (0>=viewport_h-sh_h-sh_h))) sh_h=0;
384   if(!(options&VSCROLLER_ALWAYS) && ((content_h<=viewport_h-sh_h) || (0>=viewport_w-sv_w-sv_w))) sv_w=0;
385   if(!(options&HSCROLLER_ALWAYS) && ((content_w<=viewport_w-sv_w) || (0>=viewport_h-sh_h-sh_h))) sh_h=0;
386 
387   // Viewport size with scroll bars taken into account
388   viewport_w-=sv_w;
389   viewport_h-=sh_h;
390 
391   // Adjust content size, now that we know about those scroll bars
392   if((options&HSCROLLER_NEVER)&&(options&HSCROLLER_ALWAYS)) content_w=viewport_w;
393   if((options&VSCROLLER_NEVER)&&(options&VSCROLLER_ALWAYS)) content_h=viewport_h;
394 
395   // Furthermore, content size won't be smaller than the viewport
396   if(content_w<viewport_w) content_w=viewport_w;
397   if(content_h<viewport_h) content_h=viewport_h;
398 
399   // Content size
400   horizontal->setRange(content_w);
401   vertical->setRange(content_h);
402 
403   // Page size may have changed
404   horizontal->setPage(viewport_w);
405   vertical->setPage(viewport_h);
406 
407   // Position may have changed
408   horizontal->setPosition(-pos_x);
409   vertical->setPosition(-pos_y);
410 
411   // Get back the adjusted position
412   new_x=-horizontal->getPosition();
413   new_y=-vertical->getPosition();
414 
415   // Scroll to force position back into range
416   if(new_x!=pos_x || new_y!=pos_y){
417     moveContents(new_x,new_y);
418     }
419 
420   // Read back validated position
421   pos_x=-horizontal->getPosition();
422   pos_y=-vertical->getPosition();
423 
424   // Hide or show horizontal scroll bar
425   if(sh_h){
426     horizontal->position(0,height-sh_h,width-sv_w,sh_h);
427     horizontal->show();
428     horizontal->raise();
429     }
430   else{
431     horizontal->hide();
432     }
433 
434   // Hide or show vertical scroll bar
435   if(sv_w){
436     vertical->position(width-sv_w,0,sv_w,height-sh_h);
437     vertical->show();
438     vertical->raise();
439     }
440   else{
441     vertical->hide();
442     }
443 
444   // Hide or show scroll corner
445   if(sv_w && sh_h){
446     corner->position(width-sv_w,height-sh_h,sv_w,sh_h);
447     corner->show();
448     corner->raise();
449     }
450   else{
451     corner->hide();
452     }
453 
454   // No more dirty
455   flags&=~FLAG_DIRTY;
456   }
457 
458 
459 // Set position
setPosition(FXint x,FXint y)460 void FXScrollArea::setPosition(FXint x,FXint y){
461   FXint new_x,new_y;
462 
463   // Set scroll bars
464   horizontal->setPosition(-x);
465   vertical->setPosition(-y);
466 
467   // Then read back valid position from scroll bars
468   new_x=-horizontal->getPosition();
469   new_y=-vertical->getPosition();
470 
471   // Move content if there's a change
472   if(new_x!=pos_x || new_y!=pos_y){
473     moveContents(new_x,new_y);
474     }
475   }
476 
477 
478 // Clean up
~FXScrollArea()479 FXScrollArea::~FXScrollArea(){
480   getApp()->removeTimeout(this,ID_AUTOSCROLL);
481   horizontal=(FXScrollBar*)-1L;
482   vertical=(FXScrollBar*)-1L;
483   corner=(FXScrollCorner*)-1L;
484   }
485 
486 }
487