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