View2D: Presets for Initialising Views
[blender-staging.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) {
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         if(v2d->flag & V2D_VIEWSYNC_X)
169                 WM_event_add_notifier(C, WM_NOTE_TIMELINE_SYNC, V2D_LOCK_COPY, v2d);
170 }
171
172 /* cleanup temp customdata  */
173 static void view_pan_exit(bContext *C, wmOperator *op)
174 {
175         if (op->customdata) {
176                 MEM_freeN(op->customdata);
177                 op->customdata= NULL;                           
178         }
179
180  
181 /* ------------------ Modal Drag Version (1) ---------------------- */
182
183 /* for 'redo' only, with no user input */
184 static int view_pan_exec(bContext *C, wmOperator *op)
185 {
186         if (!view_pan_init(C, op))
187                 return OPERATOR_CANCELLED;
188         
189         view_pan_apply(C, op);
190         view_pan_exit(C, op);
191         return OPERATOR_FINISHED;
192 }
193
194 /* set up modal operator and relevant settings */
195 static int view_pan_invoke(bContext *C, wmOperator *op, wmEvent *event)
196 {
197         v2dViewPanData *vpd;
198         View2D *v2d;
199         
200         /* set up customdata */
201         if (!view_pan_init(C, op))
202                 return OPERATOR_CANCELLED;
203         
204         vpd= op->customdata;
205         v2d= vpd->v2d;
206         
207         /* set initial settings */
208         vpd->startx= vpd->lastx= event->x;
209         vpd->starty= vpd->lasty= event->y;
210         RNA_int_set(op->ptr, "deltax", 0);
211         RNA_int_set(op->ptr, "deltay", 0);
212         
213         if (v2d->keepofs & V2D_LOCKOFS_X)
214                 WM_cursor_modal(C->window, BC_NS_SCROLLCURSOR);
215         else if (v2d->keepofs & V2D_LOCKOFS_Y)
216                 WM_cursor_modal(C->window, BC_EW_SCROLLCURSOR);
217         else
218                 WM_cursor_modal(C->window, BC_NSEW_SCROLLCURSOR);
219         
220         /* add temp handler */
221         WM_event_add_modal_handler(C, &C->window->handlers, op);
222
223         return OPERATOR_RUNNING_MODAL;
224 }
225
226 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
227 static int view_pan_modal(bContext *C, wmOperator *op, wmEvent *event)
228 {
229         v2dViewPanData *vpd= op->customdata;
230         
231         /* execute the events */
232         switch (event->type) {
233                 case MOUSEMOVE:
234                 {
235                         /* calculate new delta transform, then store mouse-coordinates for next-time */
236                         RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->x));
237                         RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->y));
238                         
239                         vpd->lastx= event->x;
240                         vpd->lasty= event->y;
241                         
242                         view_pan_apply(C, op);
243                 }
244                         break;
245                         
246                 case MIDDLEMOUSE:
247                         if (event->val==0) {
248                                 /* calculate overall delta mouse-movement for redo */
249                                 RNA_int_set(op->ptr, "deltax", (vpd->startx - vpd->lastx));
250                                 RNA_int_set(op->ptr, "deltay", (vpd->starty - vpd->lasty));
251                                 
252                                 view_pan_exit(C, op);
253                                 WM_cursor_restore(C->window);
254                                 
255                                 return OPERATOR_FINISHED;
256                         }
257                         break;
258         }
259
260         return OPERATOR_RUNNING_MODAL;
261 }
262
263 void ED_View2D_OT_view_pan(wmOperatorType *ot)
264 {
265         PropertyRNA *prop;
266         
267         /* identifiers */
268         ot->name= "Pan View";
269         ot->idname= "ED_View2D_OT_view_pan";
270         
271         /* api callbacks */
272         ot->exec= view_pan_exec;
273         ot->invoke= view_pan_invoke;
274         ot->modal= view_pan_modal;
275         
276         /* operator is repeatable */
277         ot->flag= OPTYPE_REGISTER;
278         
279         /* rna - must keep these in sync with the other operators */
280         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
281         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
282 }
283
284 /* ------------------ Scrollwheel Versions (2) ---------------------- */
285
286 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
287 static int view_scrollright_exec(bContext *C, wmOperator *op)
288 {
289         /* initialise default settings (and validate if ok to run) */
290         if (!view_pan_init(C, op))
291                 return OPERATOR_CANCELLED;
292         
293         /* set RNA-Props - only movement in positive x-direction */
294         RNA_int_set(op->ptr, "deltax", 20);
295         RNA_int_set(op->ptr, "deltay", 0);
296         
297         /* apply movement, then we're done */
298         view_pan_apply(C, op);
299         view_pan_exit(C, op);
300         
301         return OPERATOR_FINISHED;
302 }
303
304 void ED_View2D_OT_view_scrollright(wmOperatorType *ot)
305 {
306         PropertyRNA *prop;
307         
308         /* identifiers */
309         ot->name= "Scroll Right";
310         ot->idname= "ED_View2D_OT_view_rightscroll";
311         
312         /* api callbacks */
313         ot->exec= view_scrollright_exec;
314         
315         /* operator is repeatable */
316         ot->flag= OPTYPE_REGISTER;
317         
318         /* rna - must keep these in sync with the other operators */
319         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
320         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
321 }
322
323
324
325 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
326 static int view_scrollleft_exec(bContext *C, wmOperator *op)
327 {
328         /* initialise default settings (and validate if ok to run) */
329         if (!view_pan_init(C, op))
330                 return OPERATOR_CANCELLED;
331         
332         /* set RNA-Props - only movement in negative x-direction */
333         RNA_int_set(op->ptr, "deltax", -20);
334         RNA_int_set(op->ptr, "deltay", 0);
335         
336         /* apply movement, then we're done */
337         view_pan_apply(C, op);
338         view_pan_exit(C, op);
339         
340         return OPERATOR_FINISHED;
341 }
342
343 void ED_View2D_OT_view_scrollleft(wmOperatorType *ot)
344 {
345         PropertyRNA *prop;
346         
347         /* identifiers */
348         ot->name= "Scroll Left";
349         ot->idname= "ED_View2D_OT_view_leftscroll";
350         
351         /* api callbacks */
352         ot->exec= view_scrollleft_exec;
353         
354         /* operator is repeatable */
355         ot->flag= OPTYPE_REGISTER;
356         
357         /* rna - must keep these in sync with the other operators */
358         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
359         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
360 }
361
362 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
363 static int view_scrolldown_exec(bContext *C, wmOperator *op)
364 {
365         /* initialise default settings (and validate if ok to run) */
366         if (!view_pan_init(C, op))
367                 return OPERATOR_CANCELLED;
368         
369         /* set RNA-Props - only movement in positive x-direction */
370         RNA_int_set(op->ptr, "deltax", 0);
371         RNA_int_set(op->ptr, "deltay", -20);
372         
373         /* apply movement, then we're done */
374         view_pan_apply(C, op);
375         view_pan_exit(C, op);
376         
377         return OPERATOR_FINISHED;
378 }
379
380 void ED_View2D_OT_view_scrolldown(wmOperatorType *ot)
381 {
382         PropertyRNA *prop;
383         
384         /* identifiers */
385         ot->name= "Scroll Down";
386         ot->idname= "ED_View2D_OT_view_downscroll";
387         
388         /* api callbacks */
389         ot->exec= view_scrolldown_exec;
390         
391         /* operator is repeatable */
392         ot->flag= OPTYPE_REGISTER;
393         
394         /* rna - must keep these in sync with the other operators */
395         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
396         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
397 }
398
399
400
401 /* this operator only needs this single callback, where it callsthe view_pan_*() methods */
402 static int view_scrollup_exec(bContext *C, wmOperator *op)
403 {
404         /* initialise default settings (and validate if ok to run) */
405         if (!view_pan_init(C, op))
406                 return OPERATOR_CANCELLED;
407         
408         /* set RNA-Props - only movement in negative x-direction */
409         RNA_int_set(op->ptr, "deltax", 0);
410         RNA_int_set(op->ptr, "deltay", 20);
411         
412         /* apply movement, then we're done */
413         view_pan_apply(C, op);
414         view_pan_exit(C, op);
415         
416         return OPERATOR_FINISHED;
417 }
418
419 void ED_View2D_OT_view_scrollup(wmOperatorType *ot)
420 {
421         PropertyRNA *prop;
422         
423         /* identifiers */
424         ot->name= "Scroll Up";
425         ot->idname= "ED_View2D_OT_view_upscroll";
426         
427         /* api callbacks */
428         ot->exec= view_scrollup_exec;
429         
430         /* operator is repeatable */
431         ot->flag= OPTYPE_REGISTER;
432         
433         /* rna - must keep these in sync with the other operators */
434         prop= RNA_def_property(ot->srna, "deltax", PROP_INT, PROP_NONE);
435         prop= RNA_def_property(ot->srna, "deltay", PROP_INT, PROP_NONE);
436 }
437
438 /* ********************************************************* */
439 /* SINGLE-STEP VIEW ZOOMING OPERATOR                                             */
440
441 /*      This group of operators come in several forms:
442  *              1) Scrollwheel 'steps' - rolling mousewheel by one step zooms view by predefined amount
443  *              2) Scrollwheel 'steps' + alt + ctrl/shift - zooms view on one axis only (ctrl=x, shift=y)  // XXX this could be implemented...
444  *              3) Pad +/- Keys - pressing each key moves the zooms the view by a predefined amount
445  *
446  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
447  *              zoomfacx, zoomfacy      - These two zoom factors allow for non-uniform scaling.
448  *                                                        It is safe to scale by 0, as these factors are used to determine
449  *                                                        amount to enlarge 'cur' by
450  */
451
452 /* ------------------ 'Shared' stuff ------------------------ */
453  
454 /* check if step-zoom can be applied */
455 static short view_zoomstep_ok(bContext *C)
456 {
457         View2D *v2d;
458         
459         /* check if there's a region in context to work with */
460         if (C->region == NULL)
461                 return 0;
462         v2d= &C->region->v2d;
463         
464         /* check that 2d-view is zoomable */
465         if ((v2d->keepzoom & V2D_LOCKZOOM_X) && (v2d->keepzoom & V2D_LOCKZOOM_Y))
466                 return 0;
467                 
468         /* view is zoomable */
469         return 1;
470 }
471  
472 /* apply transform to view (i.e. adjust 'cur' rect) */
473 static void view_zoomstep_apply(bContext *C, wmOperator *op)
474 {
475         View2D *v2d= &C->region->v2d;
476         float dx, dy;
477         
478         /* calculate amount to move view by */
479         dx= (v2d->cur.xmax - v2d->cur.xmin) * (float)RNA_float_get(op->ptr, "zoomfacx");
480         dy= (v2d->cur.ymax - v2d->cur.ymin) * (float)RNA_float_get(op->ptr, "zoomfacy");
481         
482         /* only move view on an axis if change is allowed */
483         if ((v2d->keepzoom & V2D_LOCKOFS_X)==0) {
484                 v2d->cur.xmin += dx;
485                 v2d->cur.xmax -= dx;
486         }
487         if ((v2d->keepzoom & V2D_LOCKOFS_Y)==0) {
488                 v2d->cur.ymin += dy;
489                 v2d->cur.ymax -= dy;
490         }
491         
492         /* validate that view is in valid configuration after this operation */
493         UI_view2d_curRect_validate(v2d);
494         
495         /* request updates to be done... */
496         WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
497         if(v2d->flag & V2D_VIEWSYNC_X)
498                 WM_event_add_notifier(C, WM_NOTE_TIMELINE_SYNC, V2D_LOCK_COPY, v2d);
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         if(v2d->flag & V2D_VIEWSYNC_X)
651                 WM_event_add_notifier(C, WM_NOTE_TIMELINE_SYNC, V2D_LOCK_COPY, v2d);
652 }
653
654 /* cleanup temp customdata  */
655 static void view_zoomdrag_exit(bContext *C, wmOperator *op)
656 {
657         if (op->customdata) {
658                 MEM_freeN(op->customdata);
659                 op->customdata= NULL;                           
660         }
661
662
663 /* for 'redo' only, with no user input */
664 static int view_zoomdrag_exec(bContext *C, wmOperator *op)
665 {
666         if (!view_zoomdrag_init(C, op))
667                 return OPERATOR_CANCELLED;
668         
669         view_zoomdrag_apply(C, op);
670         view_zoomdrag_exit(C, op);
671         return OPERATOR_FINISHED;
672 }
673
674 /* set up modal operator and relevant settings */
675 static int view_zoomdrag_invoke(bContext *C, wmOperator *op, wmEvent *event)
676 {
677         v2dViewZoomData *vzd;
678         View2D *v2d;
679         
680         /* set up customdata */
681         if (!view_zoomdrag_init(C, op))
682                 return OPERATOR_CANCELLED;
683         
684         vzd= op->customdata;
685         v2d= vzd->v2d;
686         
687         /* set initial settings */
688         vzd->lastx= event->x;
689         vzd->lasty= event->y;
690         RNA_float_set(op->ptr, "deltax", 0);
691         RNA_float_set(op->ptr, "deltay", 0);
692         
693         if (v2d->keepofs & V2D_LOCKOFS_X)
694                 WM_cursor_modal(C->window, BC_NS_SCROLLCURSOR);
695         else if (v2d->keepofs & V2D_LOCKOFS_Y)
696                 WM_cursor_modal(C->window, BC_EW_SCROLLCURSOR);
697         else
698                 WM_cursor_modal(C->window, BC_NSEW_SCROLLCURSOR);
699         
700         /* add temp handler */
701         WM_event_add_modal_handler(C, &C->window->handlers, op);
702
703         return OPERATOR_RUNNING_MODAL;
704 }
705
706 /* handle user input - calculations of mouse-movement need to be done here, not in the apply callback! */
707 static int view_zoomdrag_modal(bContext *C, wmOperator *op, wmEvent *event)
708 {
709         v2dViewZoomData *vzd= op->customdata;
710         View2D *v2d= vzd->v2d;
711         
712         /* execute the events */
713         switch (event->type) {
714                 case MOUSEMOVE:
715                 {
716                         float dx, dy;
717                         
718                         /* calculate new delta transform, based on zooming mode */
719                         if (U.viewzoom == USER_ZOOM_SCALE) {
720                                 /* 'scale' zooming */
721                                 float dist;
722                                 
723                                 /* x-axis transform */
724                                 dist = (v2d->mask.xmax - v2d->mask.xmin) / 2.0f;
725                                 dx= 1.0f - (fabs(vzd->lastx - dist) + 2.0f) / (fabs(event->x - dist) + 2.0f);
726                                 dx*= 0.5f * (v2d->cur.xmax - v2d->cur.xmin);
727                                 
728                                 /* y-axis transform */
729                                 dist = (v2d->mask.ymax - v2d->mask.ymin) / 2.0f;
730                                 dy= 1.0f - (fabs(vzd->lasty - dist) + 2.0) / (fabs(event->y - dist) + 2.0f);
731                                 dy*= 0.5f * (v2d->cur.ymax - v2d->cur.ymin);
732                         }
733                         else {
734                                 /* 'continuous' or 'dolly' */
735                                 float fac;
736                                 
737                                 /* x-axis transform */
738                                 fac= 0.01f * (event->x - vzd->lastx);
739                                 dx= fac * (v2d->cur.xmax - v2d->cur.xmin);
740                                 
741                                 /* y-axis transform */
742                                 fac= 0.01f * (event->y - vzd->lasty);
743                                 dy= fac * (v2d->cur.ymax - v2d->cur.ymin);
744                                 
745                                 /* continous zoom shouldn't move that fast... */
746                                 if (U.viewzoom == USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
747                                         dx /= 20.0f;
748                                         dy /= 20.0f;
749                                 }
750                         }
751                         
752                         /* set transform amount, and add current deltas to stored total delta (for redo) */
753                         RNA_float_set(op->ptr, "deltax", dx);
754                         RNA_float_set(op->ptr, "deltay", dy);
755                         vzd->dx += dx;
756                         vzd->dy += dy;
757                         
758                         /* store mouse coordinates for next time, if not doing continuous zoom
759                          *      - continuous zoom only depends on distance of mouse to starting point to determine rate of change
760                          */
761                         if (U.viewzoom != USER_ZOOM_CONT) { // XXX store this setting as RNA prop?
762                                 vzd->lastx= event->x;
763                                 vzd->lasty= event->y;
764                         }
765                         
766                         /* apply zooming */
767                         view_zoomdrag_apply(C, op);
768                 }
769                         break;
770                         
771                 case MIDDLEMOUSE:
772                         if (event->val==0) {
773                                 /* for redo, store the overall deltas - need to respect zoom-locks here... */
774                                 if ((v2d->keepzoom & V2D_LOCKZOOM_X)==0)
775                                         RNA_float_set(op->ptr, "deltax", vzd->dx);
776                                 else
777                                         RNA_float_set(op->ptr, "deltax", 0);
778                                         
779                                 if ((v2d->keepzoom & V2D_LOCKZOOM_Y)==0)
780                                         RNA_float_set(op->ptr, "deltay", vzd->dy);
781                                 else
782                                         RNA_float_set(op->ptr, "deltay", 0);
783                                 
784                                 /* free customdata */
785                                 view_zoomdrag_exit(C, op);
786                                 WM_cursor_restore(C->window);
787                                 
788                                 return OPERATOR_FINISHED;
789                         }
790                         break;
791         }
792
793         return OPERATOR_RUNNING_MODAL;
794 }
795
796 void ED_View2D_OT_view_zoom(wmOperatorType *ot)
797 {
798         PropertyRNA *prop;
799         
800         /* identifiers */
801         ot->name= "Zoom View";
802         ot->idname= "ED_View2D_OT_view_zoom";
803         
804         /* api callbacks */
805         ot->exec= view_zoomdrag_exec;
806         ot->invoke= view_zoomdrag_invoke;
807         ot->modal= view_zoomdrag_modal;
808         
809         /* operator is repeatable */
810         ot->flag= OPTYPE_REGISTER;
811         
812         /* rna - must keep these in sync with the other operators */
813         prop= RNA_def_property(ot->srna, "deltax", PROP_FLOAT, PROP_NONE);
814         prop= RNA_def_property(ot->srna, "deltay", PROP_FLOAT, PROP_NONE);
815 }
816
817 /* ********************************************************* */
818 /* SCROLLERS */
819
820 /*      Scrollers should behave in the following ways, when clicked on with LMB (and dragged):
821  *              1) 'Handles' on end of 'bubble' - when the axis that the scroller represents is zoomable, 
822  *                      enlarge 'cur' rect on the relevant side 
823  *              2) 'Bubble'/'bar' - just drag, and bar should move with mouse (view pans opposite)
824  *
825  *      In order to make sure this works, each operator must define the following RNA-Operator Props:
826  *              deltax, deltay  - define how much to move view by (relative to zoom-correction factor)
827  */
828
829 /* customdata for scroller-invoke data */
830 typedef struct v2dScrollerMove {
831         View2D *v2d;                    /* View2D data that this operation affects */
832         
833         short scroller;                 /* scroller that mouse is in ('h' or 'v') */
834         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?)
835         
836         float fac;                              /* view adjustment factor, based on size of region */
837         float delta;                    /* amount moved by mouse on axis of interest */
838         
839         int lastx, lasty;               /* previous mouse coordinates (in screen coordinates) for determining movement */
840 } v2dScrollerMove;
841
842
843 /* View2DScrollers is typedef'd in UI_view2d.h 
844  * This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c, as we only need focus bubble info
845  * WARNING: the start of this struct must not change, so that it stays in sync with the 'real' version
846  *                 For now, we don't need to have a separate (internal) header for structs like this...
847  */
848 struct View2DScrollers {        
849                 /* focus bubbles */
850         int vert_min, vert_max; /* vertical scrollbar */
851         int hor_min, hor_max;   /* horizontal scrollbar */
852 };
853
854 /* quick enum for vsm->zone (scroller handles) */
855 enum {
856         SCROLLHANDLE_MIN= -1,
857         SCROLLHANDLE_BAR,
858         SCROLLHANDLE_MAX
859 } eV2DScrollerHandle_Zone;
860
861 /* ------------------------ */
862
863 /* check if mouse is within scroller handle 
864  *      - mouse                 =       relevant mouse coordinate in region space
865  *      - sc_min, sc_max        =       extents of scroller
866  *      - sh_min, sh_max        =       positions of scroller handles
867  */
868 static short mouse_in_scroller_handle(int mouse, int sc_min, int sc_max, int sh_min, int sh_max)
869 {
870         short in_min, in_max, in_view=1;
871         
872         /* firstly, check if 
873          *      - 'bubble' fills entire scroller 
874          *      - 'bubble' completely out of view on either side 
875          */
876         if ((sh_min <= sc_min) && (sh_max >= sc_max)) in_view= 0;
877         if (sh_min == sh_max) {
878                 if (sh_min <= sc_min) in_view= 0;
879                 if (sh_max >= sc_max) in_view= 0;
880         }
881         else {
882                 if (sh_max <= sc_min) in_view= 0;
883                 if (sh_min >= sc_max) in_view= 0;
884         }
885         
886         
887         if (in_view == 0) {
888                 /* handles are only activated if the mouse is within the relative quater lengths of the scroller */
889                 int qLen = (sc_max + sc_min) / 4;
890                 
891                 if (mouse >= (sc_max - qLen))
892                         return SCROLLHANDLE_MAX;
893                 else if (mouse <= qLen)
894                         return SCROLLHANDLE_MIN;
895                 else
896                         return SCROLLHANDLE_BAR;
897         }
898         
899         /* check if mouse is in or past either handle */
900         in_max= ( (mouse >= (sh_max - V2D_SCROLLER_HANDLE_SIZE)) && (mouse <= (sh_max + V2D_SCROLLER_HANDLE_SIZE)) );
901         in_min= ( (mouse <= (sh_min + V2D_SCROLLER_HANDLE_SIZE)) && (mouse >= (sh_min - V2D_SCROLLER_HANDLE_SIZE)) );
902         
903         /* check if overlap --> which means user clicked on bar, as bar is within handles region */
904         if (in_max && in_min)
905                 return SCROLLHANDLE_BAR;
906         else if (in_max)
907                 return SCROLLHANDLE_MAX;
908         else if (in_min)
909                 return SCROLLHANDLE_MIN;
910                 
911         /* unlikely to happen, though we just cover it in case */
912         return SCROLLHANDLE_BAR;
913
914
915 /* initialise customdata for scroller manipulation operator */
916 static void scroller_activate_init(bContext *C, wmOperator *op, wmEvent *event, short in_scroller)
917 {
918         v2dScrollerMove *vsm;
919         View2DScrollers *scrollers;
920         ARegion *ar= C->region;
921         View2D *v2d= &ar->v2d;
922         float mask_size;
923         int x, y;
924         
925         /* set custom-data for operator */
926         vsm= MEM_callocN(sizeof(v2dScrollerMove), "v2dScrollerMove");
927         op->customdata= vsm;
928         
929         /* set general data */
930         vsm->v2d= v2d;
931         vsm->scroller= in_scroller;
932         
933         /* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */
934         vsm->lastx = event->x;
935         vsm->lasty = event->y;
936         x= event->x - ar->winrct.xmin;
937         y= event->y - ar->winrct.ymin;
938         
939         /* 'zone' depends on where mouse is relative to bubble 
940          *      - zooming must be allowed on this axis, otherwise, default to pan
941          */
942         scrollers= UI_view2d_scrollers_calc(C, v2d, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY, V2D_ARG_DUMMY);
943         if (in_scroller == 'h') {
944                 /* horizontal scroller - calculate adjustment factor first */
945                 mask_size= (float)(v2d->hor.xmax - v2d->hor.xmin);
946                 vsm->fac= (v2d->tot.xmax - v2d->tot.xmin) / mask_size;
947                 
948                 /* get 'zone' (i.e. which part of scroller is activated) */
949                 if (v2d->keepzoom & V2D_LOCKZOOM_X) {
950                         /* default to scroll, as handles not usable */
951                         vsm->zone= SCROLLHANDLE_BAR;
952                 }
953                 else {
954                         /* check which handle we're in */
955                         vsm->zone= mouse_in_scroller_handle(x, v2d->hor.xmin, v2d->hor.xmax, scrollers->hor_min, scrollers->hor_max); 
956                 }
957         }
958         else {
959                 /* vertical scroller - calculate adjustment factor first */
960                 mask_size= (float)(v2d->vert.ymax - v2d->vert.ymin);
961                 vsm->fac= (v2d->tot.ymax - v2d->tot.ymin) / mask_size;
962                 
963                 /* get 'zone' (i.e. which part of scroller is activated) */
964                 if (v2d->keepzoom & V2D_LOCKZOOM_Y) {
965                         /* default to scroll, as handles not usable */
966                         vsm->zone= SCROLLHANDLE_BAR;
967                 }
968                 else {
969                         /* check which handle we're in */
970                         vsm->zone= mouse_in_scroller_handle(y, v2d->vert.ymin, v2d->vert.ymax, scrollers->vert_min, scrollers->vert_max); 
971                 }
972         }
973         UI_view2d_scrollers_free(scrollers);
974 }
975
976 /* cleanup temp customdata  */
977 static void scroller_activate_exit(bContext *C, wmOperator *op)
978 {
979         if (op->customdata) {
980                 MEM_freeN(op->customdata);
981                 op->customdata= NULL;                           
982         }
983
984
985 /* apply transform to view (i.e. adjust 'cur' rect) */
986 static void scroller_activate_apply(bContext *C, wmOperator *op)
987 {
988         v2dScrollerMove *vsm= op->customdata;
989         View2D *v2d= vsm->v2d;
990         float temp;
991         
992         /* calculate amount to move view by */
993         temp= vsm->fac * vsm->delta;
994         
995         /* type of movement */
996         switch (vsm->zone) {
997                 case SCROLLHANDLE_MIN:
998                         /* only expand view on axis if zoom is allowed */
999                         if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1000                                 v2d->cur.xmin -= temp;
1001                         if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1002                                 v2d->cur.ymin -= temp;
1003                         break;
1004                 
1005                 case SCROLLHANDLE_MAX:
1006                         /* only expand view on axis if zoom is allowed */
1007                         if ((vsm->scroller == 'h') && !(v2d->keepzoom & V2D_LOCKZOOM_X))
1008                                 v2d->cur.xmax += temp;
1009                         if ((vsm->scroller == 'v') && !(v2d->keepzoom & V2D_LOCKZOOM_Y))
1010                                 v2d->cur.ymax += temp;
1011                         break;
1012                 
1013                 default: /* SCROLLHANDLE_BAR */
1014                         /* only move view on an axis if panning is allowed */
1015                         if ((vsm->scroller == 'h') && !(v2d->keepofs & V2D_LOCKOFS_X)) {
1016                                 v2d->cur.xmin += temp;
1017                                 v2d->cur.xmax += temp;
1018                         }
1019                         if ((vsm->scroller == 'v') && !(v2d->keepofs & V2D_LOCKOFS_Y)) {
1020                                 v2d->cur.ymin += temp;
1021                                 v2d->cur.ymax += temp;
1022                         }
1023                         break;
1024         }
1025         
1026         /* validate that view is in valid configuration after this operation */
1027         UI_view2d_curRect_validate(v2d);
1028         
1029         /* request updates to be done... */
1030         WM_event_add_notifier(C, WM_NOTE_AREA_REDRAW, 0, NULL);
1031         if(v2d->flag & V2D_VIEWSYNC_X)
1032                 WM_event_add_notifier(C, WM_NOTE_TIMELINE_SYNC, V2D_LOCK_COPY, v2d);
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