View2D: More scroller related things
[blender.git] / source / blender / editors / interface / view2d_ops.c
1 /**
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version. 
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19  *
20  * The Original Code is Copyright (C) 2008 Blender Foundation.
21  * All rights reserved.
22  * 
23  * Contributor(s): Blender Foundation, Joshua Leung
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 #include <math.h>
29
30 #include "MEM_guardedalloc.h"
31
32 #include "DNA_scene_types.h"
33 #include "DNA_screen_types.h"
34 #include "DNA_space_types.h"
35 #include "DNA_userdef_types.h"
36 #include "DNA_view2d_types.h"
37
38 #include "BLI_blenlib.h"
39
40 #include "BKE_global.h"
41 #include "BKE_utildefines.h"
42
43 #include "RNA_access.h"
44 #include "RNA_define.h"
45
46 #include "WM_api.h"
47 #include "WM_types.h"
48
49 #include "BIF_gl.h"
50
51 #include "UI_resources.h"
52 #include "UI_view2d.h"
53
54 /* ********************************************************* */
55 /* General Polling Funcs */
56
57 /* Check if mouse is within scrollbars 
58  *      - Returns appropriate code for match
59  *              'h' = in horizontal scrollbar
60  *              'v' = in vertical scrollbar
61  *              0 = not in scrollbar
62  *      
63  *      - x,y   = mouse coordinates in screen (not region) space
64  */
65 static short mouse_in_v2d_scrollers (const bContext *C, View2D *v2d, int x, int y)
66 {
67         ARegion *ar= C->region;
68         int co[2];
69         
70         /* clamp x,y to region-coordinates first */
71         co[0]= x - ar->winrct.xmin;
72         co[1]= y - ar->winrct.ymin;
73         
74         /* check if within scrollbars */
75         if (v2d->scroll & (V2D_SCROLL_HORIZONTAL|V2D_SCROLL_HORIZONTAL_O)) {
76                 if (IN_2D_HORIZ_SCROLL(v2d, co)) return 'h';
77         }
78         if (v2d->scroll & V2D_SCROLL_VERTICAL) {
79                 if (IN_2D_VERT_SCROLL(v2d, co)) return 'v';
80         }       
81         
82         /* not found */
83         return 0;
84
85
86
87 /* ********************************************************* */
88 /* VIEW PANNING OPERATOR                                                                 */
89
90 /*      This group of operators come in several forms:
91  *              1) Modal 'dragging' with MMB - where movement of mouse dictates amount to pan view by
92  *              2) Scrollwheel 'steps' - rolling mousewheel by one step moves view by predefined amount
93  *
94  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
95  *              deltax, deltay  - define how much to move view by (relative to zoom-correction factor)
96  */
97
98 /* ------------------ Shared 'core' stuff ---------------------- */
99  
100 /* temp customdata for operator */
101 typedef struct v2dViewPanData {
102         View2D *v2d;                    /* view2d we're operating in */
103         
104         float facx, facy;               /* amount to move view relative to zoom */
105         
106                 /* options for version 1 */
107         int startx, starty;             /* mouse x/y values in window when operator was initiated */
108         int lastx, lasty;               /* previous x/y values of mouse in window */
109         
110         short in_scroller;              /* for MMB in scrollers (old feature in past, but now not that useful) */
111 } v2dViewPanData;
112  
113 /* initialise panning customdata */
114 static int view_pan_init(bContext *C, wmOperator *op)
115 {
116         v2dViewPanData *vpd;
117         ARegion *ar;
118         View2D *v2d;
119         float winx, winy;
120         
121         /* regions now have v2d-data by default, so check for region */
122         if (C->region == NULL)
123                 return 0;
124         ar= C->region;
125         
126         /* set custom-data for operator */
127         vpd= MEM_callocN(sizeof(v2dViewPanData), "v2dViewPanData");
128         op->customdata= vpd;
129         
130         /* set pointers to owners */
131         vpd->v2d= v2d= &ar->v2d;
132         
133         /* calculate translation factor - based on size of view */
134         winx= (float)(ar->winrct.xmax - ar->winrct.xmin);
135         winy= (float)(ar->winrct.ymax - ar->winrct.ymin);
136         vpd->facx= (v2d->cur.xmax - v2d->cur.xmin) / winx;
137         vpd->facy= (v2d->cur.ymax - v2d->cur.ymin) / winy;
138         
139         return 1;
140 }
141
142 /* apply transform to view (i.e. adjust 'cur' rect) */
143 static void view_pan_apply(bContext *C, wmOperator *op)
144 {
145         v2dViewPanData *vpd= op->customdata;
146         View2D *v2d= vpd->v2d;
147         float dx, dy;
148         
149         /* calculate amount to move view by */
150         dx= vpd->facx * (float)RNA_int_get(op->ptr, "deltax");
151         dy= vpd->facy * (float)RNA_int_get(op->ptr, "deltay");
152         
153         /* only move view on an axis if change is allowed */
154         if ((v2d->keepofs & V2D_LOCKOFS_X)==0) {
155                 v2d->cur.xmin += dx;
156                 v2d->cur.xmax += dx;
157         }
158         if ((v2d->keepofs & V2D_LOCKOFS_Y)==0) {
159                 v2d->cur.ymin += dy;
160                 v2d->cur.ymax += dy;
161         }
162         
163         /* validate that view is in valid configuration after this operation */
164         UI_view2d_curRect_validate(v2d);
165         
166         /* request updates to be done... */
167         WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
168         /* XXX: add WM_NOTE_TIME_CHANGED? */
169 }
170
171 /* cleanup temp customdata  */
172 static void view_pan_exit(bContext *C, wmOperator *op)
173 {
174         if (op->customdata) {
175                 MEM_freeN(op->customdata);
176                 op->customdata= NULL;                           
177         }
178
179  
180 /* ------------------ Modal Drag Version (1) ---------------------- */
181
182 /* for 'redo' only, with no user input */
183 static int view_pan_exec(bContext *C, wmOperator *op)
184 {
185         if (!view_pan_init(C, op))
186                 return OPERATOR_CANCELLED;
187         
188         view_pan_apply(C, op);
189         view_pan_exit(C, op);
190         return OPERATOR_FINISHED;
191 }
192
193 /* set up modal operator and relevant settings */
194 static int view_pan_invoke(bContext *C, wmOperator *op, wmEvent *event)
195 {
196         v2dViewPanData *vpd;
197         View2D *v2d;
198         
199         /* set up customdata */
200         if (!view_pan_init(C, op))
201                 return OPERATOR_CANCELLED;
202         
203         vpd= op->customdata;
204         v2d= vpd->v2d;
205         
206         /* set initial settings */
207         vpd->startx= vpd->lastx= event->x;
208         vpd->starty= vpd->lasty= event->y;
209         RNA_int_set(op->ptr, "deltax", 0);
210         RNA_int_set(op->ptr, "deltay", 0);
211         
212 #if 0 // XXX - enable this when cursors are working properly
213         if (v2d->keepofs & V2D_LOCKOFS_X)
214                 WM_set_cursor(C, BC_NS_SCROLLCURSOR);
215         else if (v2d->keepofs & V2D_LOCKOFS_Y)
216                 WM_set_cursor(C, BC_EW_SCROLLCURSOR);
217         else
218                 WM_set_cursor(C, BC_NSEW_SCROLLCURSOR);
219 #endif // XXX - enable this when cursors are working properly
220         
221         /* add temp handler */
222         WM_event_add_modal_handler(C, &C->window->handlers, op);
223
224         return OPERATOR_RUNNING_MODAL;
225 }
226
227 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
228 static int view_pan_modal(bContext *C, wmOperator *op, wmEvent *event)
229 {
230         v2dViewPanData *vpd= op->customdata;
231         
232         /* execute the events */
233         switch (event->type) {
234                 case MOUSEMOVE:
235                 {
236                         /* calculate new delta transform, then store mouse-coordinates for next-time */
237                         RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->x));
238                         RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->y));
239                         
240                         vpd->lastx= event->x;
241                         vpd->lasty= event->y;
242                         
243                         view_pan_apply(C, op);
244                 }
245                         break;
246                         
247                 case MIDDLEMOUSE:
248                         if (event->val==0) {
249                                 /* calculate overall delta mouse-movement for redo */
250                                 RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx));
251                                 RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty));
252                                 
253                                 view_pan_exit(C, op);
254                                 //WM_set_cursor(C, CURSOR_STD);         // XXX - enable this when cursors are working properly  
255                                 
256                                 return OPERATOR_FINISHED;
257                         }
258                         break;
259         }
260
261         return OPERATOR_RUNNING_MODAL;
262 }
263
264 void ED_View2D_OT_view_pan(wmOperatorType *ot)
265 {
266         PropertyRNA *prop;
267         
268         /* identifiers */
269         ot->name= "Pan View";
270         ot->idname= "ED_View2D_OT_view_pan";
271         
272         /* api callbacks */
273         ot->exec= view_pan_exec;
274         ot->invoke= view_pan_invoke;
275         ot->modal= view_pan_modal;
276         
277         /* operator is repeatable */
278         ot->flag= OPTYPE_REGISTER;
279         
280         /* rna - must keep these in sync with the other operators */
281         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
282         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
283 }
284
285 /* ------------------ Scrollwheel Versions (2) ---------------------- */
286
287 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
288 static int view_scrollright_exec(bContext *C, wmOperator *op)
289 {
290         /* initialise default settings (and validate if ok to run) */
291         if (!view_pan_init(C, op))
292                 return OPERATOR_CANCELLED;
293         
294         /* set RNA-Props - only movement in positive x-direction */
295         RNA_int_set(op->ptr, "deltax", 20);
296         RNA_int_set(op->ptr, "deltay", 0);
297         
298         /* apply movement, then we're done */
299         view_pan_apply(C, op);
300         view_pan_exit(C, op);
301         
302         return OPERATOR_FINISHED;
303 }
304
305 void ED_View2D_OT_view_scrollright(wmOperatorType *ot)
306 {
307         PropertyRNA *prop;
308         
309         /* identifiers */
310         ot->name= "Scroll Right";
311         ot->idname= "ED_View2D_OT_view_rightscroll";
312         
313         /* api callbacks */
314         ot->exec= view_scrollright_exec;
315         
316         /* operator is repeatable */
317         ot->flag= OPTYPE_REGISTER;
318         
319         /* rna - must keep these in sync with the other operators */
320         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
321         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
322 }
323
324
325
326 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
327 static int view_scrollleft_exec(bContext *C, wmOperator *op)
328 {
329         /* initialise default settings (and validate if ok to run) */
330         if (!view_pan_init(C, op))
331                 return OPERATOR_CANCELLED;
332         
333         /* set RNA-Props - only movement in negative x-direction */
334         RNA_int_set(op->ptr, "deltax", -20);
335         RNA_int_set(op->ptr, "deltay", 0);
336         
337         /* apply movement, then we're done */
338         view_pan_apply(C, op);
339         view_pan_exit(C, op);
340         
341         return OPERATOR_FINISHED;
342 }
343
344 void ED_View2D_OT_view_scrollleft(wmOperatorType *ot)
345 {
346         PropertyRNA *prop;
347         
348         /* identifiers */
349         ot->name= "Scroll Left";
350         ot->idname= "ED_View2D_OT_view_leftscroll";
351         
352         /* api callbacks */
353         ot->exec= view_scrollleft_exec;
354         
355         /* operator is repeatable */
356         ot->flag= OPTYPE_REGISTER;
357         
358         /* rna - must keep these in sync with the other operators */
359         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
360         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
361 }
362
363 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
364 static int view_scrolldown_exec(bContext *C, wmOperator *op)
365 {
366         /* initialise default settings (and validate if ok to run) */
367         if (!view_pan_init(C, op))
368                 return OPERATOR_CANCELLED;
369         
370         /* set RNA-Props - only movement in positive x-direction */
371         RNA_int_set(op->ptr, "deltax", 0);
372         RNA_int_set(op->ptr, "deltay", -20);
373         
374         /* apply movement, then we're done */
375         view_pan_apply(C, op);
376         view_pan_exit(C, op);
377         
378         return OPERATOR_FINISHED;
379 }
380
381 void ED_View2D_OT_view_scrolldown(wmOperatorType *ot)
382 {
383         PropertyRNA *prop;
384         
385         /* identifiers */
386         ot->name= "Scroll Down";
387         ot->idname= "ED_View2D_OT_view_downscroll";
388         
389         /* api callbacks */
390         ot->exec= view_scrolldown_exec;
391         
392         /* operator is repeatable */
393         ot->flag= OPTYPE_REGISTER;
394         
395         /* rna - must keep these in sync with the other operators */
396         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
397         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
398 }
399
400
401
402 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
403 static int view_scrollup_exec(bContext *C, wmOperator *op)
404 {
405         /* initialise default settings (and validate if ok to run) */
406         if (!view_pan_init(C, op))
407                 return OPERATOR_CANCELLED;
408         
409         /* set RNA-Props - only movement in negative x-direction */
410         RNA_int_set(op->ptr, "deltax", 0);
411         RNA_int_set(op->ptr, "deltay", 20);
412         
413         /* apply movement, then we're done */
414         view_pan_apply(C, op);
415         view_pan_exit(C, op);
416         
417         return OPERATOR_FINISHED;
418 }
419
420 void ED_View2D_OT_view_scrollup(wmOperatorType *ot)
421 {
422         PropertyRNA *prop;
423         
424         /* identifiers */
425         ot->name= "Scroll Up";
426         ot->idname= "ED_View2D_OT_view_upscroll";
427         
428         /* api callbacks */
429         ot->exec= view_scrollup_exec;
430         
431         /* operator is repeatable */
432         ot->flag= OPTYPE_REGISTER;
433         
434         /* rna - must keep these in sync with the other operators */
435         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
436         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
437 }
438
439 /* ********************************************************* */
440 /* SINGLE-STEP VIEW ZOOMING OPERATOR                                             */
441
442 /*      This group of operators come in several forms:
443  *              1) Scrollwheel 'steps' - rolling mousewheel by one step zooms view by predefined amount
444  *              2) Scrollwheel 'steps' + alt + ctrl/shift - zooms view on one axis only (ctrl=x, shift=y)  // XXX this could be implemented...
445  *              3) Pad +/- Keys - pressing each key moves the zooms the view by a predefined amount
446  *
447  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
448  *              zoomfacx, zoomfacy      - These two zoom factors allow for non-uniform scaling.
449  *                                                        It is safe to scale by 0, as these factors are used to determine
450  *                                                        amount to enlarge 'cur' by
451  */
452
453 /* ------------------ 'Shared' stuff ------------------------ */
454  
455 /* check if step-zoom can be applied */
456 static short view_zoomstep_ok(bContext *C)
457 {
458         View2D *v2d;
459         
460         /* check if there's a region in context to work with */
461         if (C->region == NULL)
462                 return 0;
463         v2d= &C->region->v2d;
464         
465         /* check that 2d-view is zoomable */
466         if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
467                 return 0;
468                 
469         /* view is zoomable */
470         return 1;
471 }
472  
473 /* apply transform to view (i.e. adjust 'cur' rect) */
474 static void view_zoomstep_apply(bContext *C, wmOperator *op)
475 {
476         View2D *v2d= &C->region->v2d;
477         float dx, dy;
478         
479         /* calculate amount to move view by */
480         dx= (v2d->cur.xmax - v2d->cur.xmin) * (float)RNA_float_get(op->ptr, "zoomfacx");
481         dy= (v2d->cur.ymax - v2d->cur.ymin) * (float)RNA_float_get(op->ptr, "zoomfacy");
482         
483         /* only move view on an axis if change is allowed */
484         if ((v2d->keepzoom & V2D_LOCKOFS_X)==0) {
485                 v2d->cur.xmin += dx;
486                 v2d->cur.xmax -= dx;
487         }
488         if ((v2d->keepzoom & V2D_LOCKOFS_Y)==0) {
489                 v2d->cur.ymin += dy;
490                 v2d->cur.ymax -= dy;
491         }
492         
493         /* validate that view is in valid configuration after this operation */
494         UI_view2d_curRect_validate(v2d);
495         
496         /* request updates to be done... */
497         WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
498         /* XXX: add WM_NOTE_TIME_CHANGED? */
499 }
500
501 /* --------------- Individual Operators ------------------- */
502
503 /* this operator only needs this single callback, where it calls the view_zoom_*() methods */
504 static int view_zoomin_exec(bContext *C, wmOperator *op)
505 {
506         /* check that there's an active region, as View2D data resides there */
507         if (!view_zoomstep_ok(C))
508                 return OPERATOR_CANCELLED;
509         
510         /* set RNA-Props - zooming in by uniform factor */
511         RNA_float_set(op->ptr, "zoomfacx", 0.0375f);
512         RNA_float_set(op->ptr, "zoomfacy", 0.0375f);
513         
514         /* apply movement, then we're done */
515         view_zoomstep_apply(C, op);
516         
517         return OPERATOR_FINISHED;
518 }
519
520 void ED_View2D_OT_view_zoomin(wmOperatorType *ot)
521 {
522         PropertyRNA *prop;
523         
524         /* identifiers */
525         ot->name= "Zoom In";
526         ot->idname= "ED_View2D_OT_view_zoomin";
527         
528         /* api callbacks */
529         ot->exec= view_zoomin_exec;
530         
531         /* operator is repeatable */
532         ot->flag= OPTYPE_REGISTER;
533         
534         /* rna - must keep these in sync with the other operators */
535         prop= RNA_def_property(ot->srna, "zoomfacx", PROP_FLOAT, PROP_NONE);
536         prop= RNA_def_property(ot->srna, "zoomfacy", PROP_FLOAT, PROP_NONE);
537 }
538
539
540
541 /* this operator only needs this single callback, where it callsthe view_zoom_*() methods */
542 static int view_zoomout_exec(bContext *C, wmOperator *op)
543 {
544         /* check that there's an active region, as View2D data resides there */
545         if (!view_zoomstep_ok(C))
546                 return OPERATOR_CANCELLED;
547         
548         /* set RNA-Props - zooming in by uniform factor */
549         RNA_float_set(op->ptr, "zoomfacx", -0.0375f);
550         RNA_float_set(op->ptr, "zoomfacy", -0.0375f);
551         
552         /* apply movement, then we're done */
553         view_zoomstep_apply(C, op);
554         
555         return OPERATOR_FINISHED;
556 }
557
558 void ED_View2D_OT_view_zoomout(wmOperatorType *ot)
559 {
560         PropertyRNA *prop;
561         
562         /* identifiers */
563         ot->name= "Zoom Out";
564         ot->idname= "ED_View2D_OT_view_zoomout";
565         
566         /* api callbacks */
567         ot->exec= view_zoomout_exec;
568         
569         /* operator is repeatable */
570         ot->flag= OPTYPE_REGISTER;
571         
572         /* rna - must keep these in sync with the other operators */
573         prop= RNA_def_property(ot->srna, "zoomfacx", PROP_FLOAT, PROP_NONE);
574         prop= RNA_def_property(ot->srna, "zoomfacy", PROP_FLOAT, PROP_NONE);
575 }
576
577 /* ********************************************************* */
578 /* DRAG-ZOOM OPERATOR                                                                    */
579
580 /*      This group of operators come in several forms:
581  *              1) MMB Drag - allows non-uniform scaling by dragging mouse
582  *                                 - method of scaling depends upon U.viewzoom setting (Continue, Dolly, Scale)
583  *                                      XXX should we store this info as RNA prop?
584  *
585  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
586  *              deltax, deltay  - amounts to add to each side of the 'cur' rect
587  */
588  
589 /* ------------------ Shared 'core' stuff ---------------------- */
590  
591 /* temp customdata for operator */
592 typedef struct v2dViewZoomData {
593         View2D *v2d;                    /* view2d we're operating in */
594         
595         int lastx, lasty;               /* previous x/y values of mouse in window */
596         float dx, dy;                   /* running tally of previous delta values (for obtaining final zoom) */
597 } v2dViewZoomData;
598  
599 /* initialise panning customdata */
600 static int view_zoomdrag_init(bContext *C, wmOperator *op)
601 {
602         v2dViewZoomData *vzd;
603         View2D *v2d;
604         
605         /* regions now have v2d-data by default, so check for region */
606         if (C->region == NULL)
607                 return 0;
608         v2d= &C->region->v2d;
609         
610         /* check that 2d-view is zoomable */
611         if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
612                 return 0;
613         
614         /* set custom-data for operator */
615         vzd= MEM_callocN(sizeof(v2dViewZoomData), "v2dViewZoomData");
616         op->customdata= vzd;
617         
618         /* set pointers to owners */
619         vzd->v2d= v2d;
620         
621         return 1;
622 }
623
624 /* apply transform to view (i.e. adjust 'cur' rect) */
625 static void view_zoomdrag_apply(bContext *C, wmOperator *op)
626 {
627         v2dViewZoomData *vzd= op->customdata;
628         View2D *v2d= vzd->v2d;
629         float dx, dy;
630         
631         /* get amount to move view by */
632         dx= RNA_float_get(op->ptr, "deltax");
633         dy= RNA_float_get(op->ptr, "deltay");
634         
635         /* only move view on an axis if change is allowed */
636         if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0) {
637                 v2d->cur.xmin += dx;
638                 v2d->cur.xmax -= dx;
639         }
640         if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0) {
641                 v2d->cur.ymin += dy;
642                 v2d->cur.ymax -= dy;
643         }
644         
645         /* validate that view is in valid configuration after this operation */
646         UI_view2d_curRect_validate(v2d);
647         
648         /* request updates to be done... */
649         WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
650         /* XXX: add WM_NOTE_TIME_CHANGED? */
651 }
652
653 /* cleanup temp customdata  */
654 static void view_zoomdrag_exit(bContext *C, wmOperator *op)
655 {
656         if (op->customdata) {
657                 MEM_freeN(op->customdata);
658                 op->customdata= NULL;                           
659         }
660
661
662 /* for 'redo' only, with no user input */
663 static int view_zoomdrag_exec(bContext *C, wmOperator *op)
664 {
665         if (!view_zoomdrag_init(C, op))
666                 return OPERATOR_CANCELLED;
667         
668         view_zoomdrag_apply(C, op);
669         view_zoomdrag_exit(C, op);
670         return OPERATOR_FINISHED;
671 }
672
673 /* set up modal operator and relevant settings */
674 static int view_zoomdrag_invoke(bContext *C, wmOperator *op, wmEvent *event)
675 {
676         v2dViewZoomData *vzd;
677         View2D *v2d;
678         
679         /* set up customdata */
680         if (!view_zoomdrag_init(C, op))
681                 return OPERATOR_CANCELLED;
682         
683         vzd= op->customdata;
684         v2d= vzd->v2d;
685         
686         /* set initial settings */
687         vzd->lastx= event->x;
688         vzd->lasty= event->y;
689         RNA_float_set(op->ptr, "deltax", 0);
690         RNA_float_set(op->ptr, "deltay", 0);
691         
692 #if 0 // XXX - enable this when cursors are working properly
693         if (v2d->keepofs & V2D_LOCKOFS_X)
694                 WM_set_cursor(C, BC_NS_SCROLLCURSOR);
695         else if (v2d->keepofs & V2D_LOCKOFS_Y)
696                 WM_set_cursor(C, BC_EW_SCROLLCURSOR);
697         else
698                 WM_set_cursor(C, BC_NSEW_SCROLLCURSOR);
699 #endif // XXX - enable this when cursors are working properly
700         
701         /* add temp handler */
702         WM_event_add_modal_handler(C, &C->window->handlers, op);
703
704         return OPERATOR_RUNNING_MODAL;
705 }
706
707 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
708 static int view_zoomdrag_modal(bContext *C, wmOperator *op, wmEvent *event)
709 {
710         v2dViewZoomData *vzd= op->customdata;
711         View2D *v2d= vzd->v2d;
712         
713         /* execute the events */
714         switch (event->type) {
715                 case MOUSEMOVE:
716                 {
717                         float dx, dy;
718                         
719                         /* calculate new delta transform, based on zooming mode */
720                         if (U.viewzoom == USER_ZOOM_SCALE) { // XXX store this setting as RNA prop?
721                                 /* 'scale' zooming */
722                                 float dist;
723                                 
724                                 /* x-axis transform */
725                                 dist = (v2d->mask.xmax - v2d->mask.xmin) / 2.0f;
726                                 dx= 1.0f - (fabs(vzd->lastx - dist) + 2.0f) / (fabs(event->x - dist) + 2.0f);
727                                 dx*= 0.5f * (v2d->cur.xmax - v2d->cur.xmin);
728                                 
729                                 /* y-axis transform */
730                                 dist = (v2d->mask.ymax - v2d->mask.ymin) / 2.0f;
731                                 dy= 1.0f - (fabs(vzd->lasty - dist) + 2.0) / (fabs(event->y - dist) + 2.0f);
732                                 dy*= 0.5f * (v2d->cur.ymax - v2d->cur.ymin);
733                         }
734                         else {
735                                 /* 'continuous' or 'dolly' */
736                                 float fac;
737                                 
738                                 /* x-axis transform */
739                                 fac= 0.01f * (event->x - vzd->lastx);
740                                 dx= fac * (v2d->cur.xmax - v2d->cur.xmin);
741                                 
742                                 /* y-axis transform */
743                                 fac= 0.01f * (event->y - vzd->lasty);
744                                 dy= fac * (v2d->cur.ymax - v2d->cur.ymin);
745                                 
746                                 /* continous zoom shouldn't move that fast... */
747                                 if (U.viewzoom == USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
748                                         dx /= 20.0f;
749                                         dy /= 20.0f;
750                                 }
751                         }
752                         
753                         /* set transform amount, and add current deltas to stored total delta (for redo) */
754                         RNA_float_set(op->ptr, "deltax", dx);
755                         RNA_float_set(op->ptr, "deltay", dy);
756                         vzd->dx += dx;
757                         vzd->dy += dy;
758                         
759                         /* store mouse coordinates for next time, if not doing continuous zoom
760                          *      - continuous zoom only depends on distance of mouse to starting point to determine rate of change
761                          */
762                         if (U.viewzoom != USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
763                                 vzd->lastx= event->x;
764                                 vzd->lasty= event->y;
765                         }
766                         
767                         /* apply zooming */
768                         view_zoomdrag_apply(C, op);
769                 }
770                         break;
771                         
772                 case MIDDLEMOUSE:
773                         if (event->val==0) {
774                                 /* for redo, store the overall deltas - need to respect zoom-locks here... */
775                                 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0)
776                                         RNA_float_set(op->ptr, "deltax", vzd->dx);
777                                 else
778                                         RNA_float_set(op->ptr, "deltax", 0);
779                                         
780                                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0)
781                                         RNA_float_set(op->ptr, "deltay", vzd->dy);
782                                 else
783                                         RNA_float_set(op->ptr, "deltay", 0);
784                                 
785                                 /* free customdata */
786                                 view_zoomdrag_exit(C, op);
787                                 //WM_set_cursor(C, CURSOR_STD);         // XXX - enable this when cursors are working properly  
788                                 
789                                 return OPERATOR_FINISHED;
790                         }
791                         break;
792         }
793
794         return OPERATOR_RUNNING_MODAL;
795 }
796
797 void ED_View2D_OT_view_zoom(wmOperatorType *ot)
798 {
799         PropertyRNA *prop;
800         
801         /* identifiers */
802         ot->name= "Zoom View";
803         ot->idname= "ED_View2D_OT_view_zoom";
804         
805         /* api callbacks */
806         ot->exec= view_zoomdrag_exec;
807         ot->invoke= view_zoomdrag_invoke;
808         ot->modal= view_zoomdrag_modal;
809         
810         /* operator is repeatable */
811         ot->flag= OPTYPE_REGISTER;
812         
813         /* rna - must keep these in sync with the other operators */
814         prop= RNA_def_property(ot->srna, "deltax", PROP_FLOAT, PROP_NONE);
815         prop= RNA_def_property(ot->srna, "deltay", PROP_FLOAT, PROP_NONE);
816 }
817
818 /* ********************************************************* */
819 /* SCROLLERS */
820
821 /*      Scrollers should behave in the following ways, when clicked on with LMB (and dragged):
822  *              1) 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable, 
823  *                      enlarge 'cur' rect on the relevant side 
824  *              2) 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite)
825  *
826  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
827  *              deltax, deltay  - define how much to move view by (relative to zoom-correction factor)
828  */
829
830 /* customdata for scroller-invoke data */
831 typedef struct v2dScrollerMove {
832         View2D *v2d;                    /* View2D data that this operation affects */
833         
834         short scroller;                 /* scroller that mouse is in ('h' or 'v') */
835         short zone;                             /* -1 is min zoomer, 0 is bar, 1 is max zoomer */ // XXX find some way to provide visual feedback of this (active colour?)
836         
837         float fac;                              /* view adjustment factor, based on size of region */
838         float delta;                    /* amount moved by mouse on axis of interest */
839         
840         int lastx, lasty;               /* previous mouse coordinates (in screen coordinates) for determining movement */
841 } v2dScrollerMove;
842
843
844 /* View2DScrollers is typedef'd in UI_view2d.h 
845  * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c, as we only need focus bubble info
846  * WARNING: the start of this struct must not change, so that it stays in sync with the 'real' version
847  *                 For now, we don't need to have a separate (internal) header for structs like this...
848  */
849 struct View2DScrollers {        
850                 /* focus bubbles */
851         int vert_min, vert_max; /* vertical scrollbar */
852         int hor_min, hor_max;   /* horizontal scrollbar */
853 };
854
855 /* quick enum for vsm->zone (scroller handles) */
856 enum {
857         SCROLLHANDLE_MIN= -1,
858         SCROLLHANDLE_BAR,
859         SCROLLHANDLE_MAX
860 } eV2DScrollerHandle_Zone;
861
862 /* ------------------------ */
863
864 /* check if mouse is within scroller handle 
865  *      - mouse                 =       relevant mouse coordinate in region space
866  *      - sc_min, sc_max        =       extents of scroller
867  *      - sh_min, sh_max        =       positions of scroller handles
868  */
869 static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max)
870 {
871         short in_min, in_max, in_view=1;
872         
873         /* firstly, check if 
874          *      - 'bubble' fills entire scroller 
875          *      - 'bubble' completely out of view on either side 
876          */
877         if ((sh_min <= sc_min) && (sh_max >= sc_max)) in_view= 0;
878         if (sh_min == sh_max) {
879                 if (sh_min <= sc_min) in_view= 0;
880                 if (sh_max >= sc_max) in_view= 0;
881         }
882         else {
883                 if (sh_max <= sc_min) in_view= 0;
884                 if (sh_min >= sc_max) in_view= 0;
885         }
886         
887         
888         if (in_view == 0) {
889                 /* handles are only activated if the mouse is within the relative quater lengths of the scroller */
890                 int qLen = (sc_max + sc_min) / 4;
891                 
892                 if (mouse >= (sc_max - qLen))
893                         return SCROLLHANDLE_MAX;
894                 else if (mouse <= qLen)
895                         return SCROLLHANDLE_MIN;
896                 else
897                         return SCROLLHANDLE_BAR;
898         }
899         
900         /* check if mouse is in or past either handle */
901         in_max= ( (mouse >= (sh_max - V2D_SCROLLER_HANDLE_SIZE)) && (mouse <= (sh_max + V2D_SCROLLER_HANDLE_SIZE)) );
902         in_min= ( (mouse <= (sh_min + V2D_SCROLLER_HANDLE_SIZE)) && (mouse >= (sh_min - V2D_SCROLLER_HANDLE_SIZE)) );
903         
904         /* check if overlap --> which means user clicked on bar, as bar is within handles region */
905         if (in_max && in_min)
906                 return SCROLLHANDLE_BAR;
907         else if (in_max)
908                 return SCROLLHANDLE_MAX;
909         else if (in_min)
910                 return SCROLLHANDLE_MIN;
911                 
912         /* unlikely to happen, though we just cover it in case */
913         return SCROLLHANDLE_BAR;
914
915
916 /* initialise customdata for scroller manipulation operator */
917 static void scroller_activate_init(bContext *C, wmOperator *op, wmEvent *event, short in_scroller)
918 {
919         v2dScrollerMove *vsm;
920         View2DScrollers *scrollers;
921         ARegion *ar= C->region;
922         View2D *v2d= &ar->v2d;
923         float mask_size;
924         int x, y;
925         
926         /* set custom-data for operator */
927         vsm= MEM_callocN(sizeof(v2dScrollerMove), "v2dScrollerMove");
928         op->customdata= vsm;
929         
930         /* set general data */
931         vsm->v2d= v2d;
932         vsm->scroller= in_scroller;
933         
934         /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */
935         vsm->lastx = event->x;
936         vsm->lasty = event->y;
937         x= event->x - ar->winrct.xmin;
938         y= event->y - ar->winrct.ymin;
939         
940         /* 'zone' depends on where mouse is relative to bubble 
941          *      - zooming must be allowed on this axis, otherwise, default to pan
942          */
943         scrollers= UI_view2d_scrollers_calc(C, v2d, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY);
944         if (in_scroller == 'h') {
945                 /* horizontal scroller - calculate adjustment factor first */
946                 mask_size= (float)(v2d->hor.xmax - v2d->hor.xmin);
947                 vsm->fac= (v2d->tot.xmax - v2d->tot.xmin) / mask_size;
948                 
949                 /* get 'zone' (i.e. which part of scroller is activated) */
950                 if (v2d->keepzoom & V2D_LOCKZOOM_X) {
951                         /* default to scroll, as handles not usable */
952                         vsm->zone= SCROLLHANDLE_BAR;
953                 }
954                 else {
955                         /* check which handle we're in */
956                         vsm->zone= mouse_in_scroller_handle(x, v2d->hor.xmin, v2d->hor.xmax, scrollers->hor_min, scrollers->hor_max); 
957                 }
958         }
959         else {
960                 /* vertical scroller - calculate adjustment factor first */
961                 mask_size= (float)(v2d->vert.ymax - v2d->vert.ymin);
962                 vsm->fac= (v2d->tot.ymax - v2d->tot.ymin) / mask_size;
963                 
964                 /* get 'zone' (i.e. which part of scroller is activated) */
965                 if (v2d->keepzoom & V2D_LOCKZOOM_Y) {
966                         /* default to scroll, as handles not usable */
967                         vsm->zone= SCROLLHANDLE_BAR;
968                 }
969                 else {
970                         /* check which handle we're in */
971                         vsm->zone= mouse_in_scroller_handle(y, v2d->vert.ymin, v2d->vert.ymax, scrollers->vert_min, scrollers->vert_max); 
972                 }
973         }
974         UI_view2d_scrollers_free(scrollers);
975 }
976
977 /* cleanup temp customdata  */
978 static void scroller_activate_exit(bContext *C, wmOperator *op)
979 {
980         if (op->customdata) {
981                 MEM_freeN(op->customdata);
982                 op->customdata= NULL;                           
983         }
984
985
986 /* apply transform to view (i.e. adjust 'cur' rect) */
987 static void scroller_activate_apply(bContext *C, wmOperator *op)
988 {
989         v2dScrollerMove *vsm= op->customdata;
990         View2D *v2d= vsm->v2d;
991         float temp;
992         
993         /* calculate amount to move view by */
994         temp= vsm->fac * vsm->delta;
995         
996         /* type of movement */
997         switch (vsm->zone) {
998                 case SCROLLHANDLE_MIN:
999                         /* only expand view on axis if zoom is allowed */
1000                         if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1001                                 v2d->cur.xmin -= temp;
1002                         if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1003                                 v2d->cur.ymin -= temp;
1004                         break;
1005                 
1006                 case SCROLLHANDLE_MAX:
1007                         /* only expand view on axis if zoom is allowed */
1008                         if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1009                                 v2d->cur.xmax += temp;
1010                         if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1011                                 v2d->cur.ymax += temp;
1012                         break;
1013                 
1014                 default: /* SCROLLHANDLE_BAR */
1015                         /* only move view on an axis if panning is allowed */
1016                         if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) {
1017                                 v2d->cur.xmin += temp;
1018                                 v2d->cur.xmax += temp;
1019                         }
1020                         if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) {
1021                                 v2d->cur.ymin += temp;
1022                                 v2d->cur.ymax += temp;
1023                         }
1024                         break;
1025         }
1026         
1027         /* validate that view is in valid configuration after this operation */
1028         UI_view2d_curRect_validate(v2d);
1029         
1030         /* request updates to be done... */
1031         WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
1032         /* XXX: add WM_NOTE_TIME_CHANGED? */
1033 }
1034
1035 /* handle user input for scrollers - calculations of mouse-movement need to be done here, not in the apply callback! */
1036 static int scroller_activate_modal(bContext *C, wmOperator *op, wmEvent *event)
1037 {
1038         v2dScrollerMove *vsm= op->customdata;
1039         
1040         /* execute the events */
1041         switch (event->type) {
1042                 case MOUSEMOVE:
1043                 {
1044                         /* calculate new delta transform, then store mouse-coordinates for next-time */
1045                         if (vsm->zone != SCROLLHANDLE_MIN) {
1046                                 /* if using bar (i.e. 'panning') or 'max' zoom widget */
1047                                 switch (vsm->scroller) {
1048                                         case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves opposite to mouse) */
1049                                                 vsm->delta= (float)(event->x - vsm->lastx);
1050                                                 break;
1051                                         case 'v': /* vertical scroller - so only vertical movement ('cur' moves opposite to mouse) */
1052                                                 vsm->delta= (float)(event->y - vsm->lasty);
1053                                                 break;
1054                                 }
1055                         }
1056                         else {
1057                                 /* using 'min' zoom widget */
1058                                 switch (vsm->scroller) {
1059                                         case 'h': /* horizontal scroller - so only horizontal movement ('cur' moves with mouse) */
1060                                                 vsm->delta= (float)(vsm->lastx - event->x);
1061                                                 break;
1062                                         case 'v': /* vertical scroller - so only vertical movement ('cur' moves with to mouse) */
1063                                                 vsm->delta= (float)(vsm->lasty - event->y);
1064                                                 break;
1065                                 }
1066                         }
1067                         
1068                         /* store previous coordinates */
1069                         vsm->lastx= event->x;
1070                         vsm->lasty= event->y;
1071                         
1072                         scroller_activate_apply(C, op);
1073                 }
1074                         break;
1075                         
1076                 case LEFTMOUSE:
1077                         if (event->val==0) {
1078                                 scroller_activate_exit(C, op);
1079                                 return OPERATOR_FINISHED;
1080                         }
1081                         break;
1082         }
1083
1084         return OPERATOR_RUNNING_MODAL;
1085 }
1086
1087
1088 /* a click (or click drag in progress) should have occurred, so check if it happened in scrollbar */
1089 static int scroller_activate_invoke(bContext *C, wmOperator *op, wmEvent *event)
1090 {
1091         View2D *v2d= NULL;
1092         short in_scroller= 0;
1093         
1094         /* firstly, check context to see if mouse is actually in region */
1095         // XXX isn't this the job of poll() callbacks which can't check events, but only context?
1096         if (C->region == NULL) 
1097                 return OPERATOR_CANCELLED;
1098         else
1099                 v2d= &C->region->v2d;
1100                 
1101         /* check if mouse in scrollbars, if they're enabled */
1102         in_scroller= mouse_in_v2d_scrollers(C, v2d, event->x, event->y);
1103         
1104         /* if in a scroller, init customdata then set modal handler which will catch mousedown to start doing useful stuff */
1105         if (in_scroller) {
1106                 v2dScrollerMove *vsm;
1107                 
1108                 /* initialise customdata */
1109                 scroller_activate_init(C, op, event, in_scroller);
1110                 vsm= (v2dScrollerMove *)op->customdata;
1111                 
1112                 /* check if zone is inappropriate (i.e. 'bar' but panning is banned), so cannot continue */
1113                 if (vsm->zone == SCROLLHANDLE_BAR) {
1114                         if ( ((vsm->scroller=='h') && (v2d->keepofs & V2D_LOCKOFS_X)) ||
1115                                  ((vsm->scroller=='v') && (v2d->keepofs & V2D_LOCKOFS_Y)) )
1116                         {
1117                                 /* free customdata initialised */
1118                                 scroller_activate_exit(C, op);
1119                                 
1120                                 /* can't catch this event for ourselves, so let it go to someone else? */
1121                                 return OPERATOR_PASS_THROUGH;
1122                         }                       
1123                 }
1124                 
1125                 /* still ok, so can add */
1126                 WM_event_add_modal_handler(C, &C->window->handlers, op);
1127                 return OPERATOR_RUNNING_MODAL;
1128         }
1129         else {
1130                 /* not in scroller, so nothing happened... (pass through let's something else catch event) */
1131                 return OPERATOR_PASS_THROUGH;
1132         }
1133 }
1134
1135 /* LMB-Drag in Scrollers - not repeatable operator! */
1136 void ED_View2D_OT_scroller_activate(wmOperatorType *ot)
1137 {
1138         /* identifiers */
1139         ot->name= "Scroller Activate";
1140         ot->idname= "ED_View2D_OT_scroller_activate";
1141         
1142         /* api callbacks */
1143         ot->invoke= scroller_activate_invoke;
1144         ot->modal= scroller_activate_modal;
1145 }
1146  
1147 /* ********************************************************* */
1148 /* Registration */
1149
1150 void ui_view2d_operatortypes(void)
1151 {
1152         WM_operatortype_append(ED_View2D_OT_view_pan);
1153         
1154         WM_operatortype_append(ED_View2D_OT_view_scrollleft);
1155         WM_operatortype_append(ED_View2D_OT_view_scrollright);
1156         WM_operatortype_append(ED_View2D_OT_view_scrollup);
1157         WM_operatortype_append(ED_View2D_OT_view_scrolldown);
1158         
1159         WM_operatortype_append(ED_View2D_OT_view_zoomin);
1160         WM_operatortype_append(ED_View2D_OT_view_zoomout);
1161         
1162         WM_operatortype_append(ED_View2D_OT_view_zoom);
1163         
1164         WM_operatortype_append(ED_View2D_OT_scroller_activate);
1165 }
1166
1167 void UI_view2d_keymap(wmWindowManager *wm)
1168 {
1169         ListBase *keymap= WM_keymap_listbase(wm, "View2D", 0, 0);
1170         
1171         /* pan/scroll */
1172         WM_keymap_add_item(keymap, "ED_View2D_OT_view_pan", MIDDLEMOUSE, KM_PRESS, 0, 0);
1173         
1174         WM_keymap_add_item(keymap, "ED_View2D_OT_view_rightscroll", WHEELDOWNMOUSE, KM_ANY, KM_CTRL, 0);
1175         WM_keymap_add_item(keymap, "ED_View2D_OT_view_leftscroll", WHEELUPMOUSE, KM_ANY, KM_CTRL, 0);
1176         
1177         WM_keymap_add_item(keymap, "ED_View2D_OT_view_downscroll", WHEELDOWNMOUSE, KM_ANY, KM_SHIFT, 0);
1178         WM_keymap_add_item(keymap, "ED_View2D_OT_view_upscroll", WHEELUPMOUSE, KM_ANY, KM_SHIFT, 0);
1179         
1180         /* zoom - single step */
1181         WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoomout", WHEELUPMOUSE, KM_ANY, 0, 0);
1182         WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoomin", WHEELDOWNMOUSE, KM_ANY, 0, 0);
1183         WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoomout", PADMINUS, KM_PRESS, 0, 0);
1184         WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoomin", PADPLUSKEY, KM_PRESS, 0, 0);
1185         
1186         /* zoom - drag */
1187         WM_keymap_add_item(keymap, "ED_View2D_OT_view_zoom", MIDDLEMOUSE, KM_PRESS, KM_CTRL, 0);
1188         
1189         /* scrollers */
1190         WM_keymap_add_item(keymap, "ED_View2D_OT_scroller_activate", LEFTMOUSE, KM_PRESS, 0, 0);
1191 }
1192